@@ -33,6 +33,36 @@ import { TransactionType } from './TransactionType';
3333 */
3434export abstract class Transaction {
3535
36+ /**
37+ * Transaction header size
38+ *
39+ * Included fields are `size`, `verifiableEntityHeader_Reserved1`,
40+ * `signature`, `signerPublicKey` and `entityBody_Reserved1`.
41+ *
42+ * @var {number}
43+ */
44+ public static readonly Header_Size : number = 8 + 64 + 32 + 4 ;
45+
46+ /**
47+ * Index of the transaction *type*
48+ *
49+ * Included fields are the transaction header, `version`
50+ * and `network`
51+ *
52+ * @var {number}
53+ */
54+ public static readonly Type_Index : number = Transaction . Header_Size + 2 ;
55+
56+ /**
57+ * Index of the transaction *body*
58+ *
59+ * Included fields are the transaction header, `version`,
60+ * `network`, `type`, `maxFee` and `deadline`
61+ *
62+ * @var {number}
63+ */
64+ public static readonly Body_Index : number = Transaction . Header_Size + 1 + 1 + 2 + 8 + 8 ;
65+
3666 /**
3767 * @constructor
3868 * @param type
@@ -81,29 +111,68 @@ export abstract class Transaction {
81111
82112 /**
83113 * Generate transaction hash hex
114+ *
115+ * @see https://github.com/nemtech/catapult-server/blob/master/src/catapult/model/EntityHasher.cpp#L32
116+ * @see https://github.com/nemtech/catapult-server/blob/master/src/catapult/model/EntityHasher.cpp#L35
117+ * @see https://github.com/nemtech/catapult-server/blob/master/sdk/src/extensions/TransactionExtensions.cpp#L46
84118 * @param {string } transactionPayload HexString Payload
85119 * @param {Array<number> } generationHashBuffer Network generation hash byte
86120 * @param {NetworkType } networkType Catapult network identifier
87121 * @returns {string } Returns Transaction Payload hash
88122 */
89123 public static createTransactionHash ( transactionPayload : string , generationHashBuffer : number [ ] , networkType : NetworkType ) : string {
90- const type = parseInt ( Convert . uint8ToHex ( Convert . hexToUint8 ( transactionPayload . substring ( 220 , 224 ) ) . reverse ( ) ) , 16 ) ;
91- const byteBuffer = Array . from ( Convert . hexToUint8 ( transactionPayload ) ) ;
92- const byteBufferWithoutHeader = byteBuffer . slice ( 4 + 64 + 32 + 8 ) ;
93- const dataBytes = type === TransactionType . AGGREGATE_BONDED || type === TransactionType . AGGREGATE_COMPLETE ?
94- generationHashBuffer . concat ( byteBufferWithoutHeader . slice ( 0 , 52 ) ) :
95- generationHashBuffer . concat ( byteBufferWithoutHeader ) ;
96- const signingBytes = byteBuffer
97- . slice ( 8 , 40 ) // first half of signature
98- . concat ( byteBuffer
99- . slice ( 4 + 4 + 64 , 8 + 64 + 32 ) ) // signer
100- . concat ( dataBytes ) ;
101124
102- const hash = new Uint8Array ( 32 ) ;
103- const signSchema = SHA3Hasher . resolveSignSchema ( networkType ) ;
104- SHA3Hasher . func ( hash , signingBytes , 32 , signSchema ) ;
125+ // prepare
126+ const entityHash : Uint8Array = new Uint8Array ( 32 ) ;
127+ const transactionBytes : Uint8Array = Convert . hexToUint8 ( transactionPayload ) ;
128+
129+ // read transaction type
130+ const typeIdx : number = Transaction . Type_Index ;
131+ const typeBytes : Uint8Array = transactionBytes . slice ( typeIdx , typeIdx + 2 ) . reverse ( ) ; // REVERSED
132+ const entityType : TransactionType = parseInt ( Convert . uint8ToHex ( typeBytes ) , 16 ) ;
133+ const isAggregateTransaction = [
134+ TransactionType . AGGREGATE_BONDED ,
135+ TransactionType . AGGREGATE_COMPLETE ,
136+ ] . find ( ( type : TransactionType ) => entityType === type ) !== undefined ;
137+
138+ // 1) take "R" part of a signature (first 32 bytes)
139+ const signatureR : Uint8Array = transactionBytes . slice ( 8 , 8 + 32 ) ;
140+
141+ // 2) add public key to match sign/verify behavior (32 bytes)
142+ const pubKeyIdx : number = signatureR . length ;
143+ const publicKey : Uint8Array = transactionBytes . slice ( 8 + 64 , 8 + 64 + 32 ) ;
144+
145+ // 3) add generationHash (32 bytes)
146+ const generationHashIdx : number = pubKeyIdx + publicKey . length ;
147+ const generationHash : Uint8Array = Uint8Array . from ( generationHashBuffer ) ;
148+
149+ // 4) add transaction data without header (EntityDataBuffer)
150+ // @link https://github.com/nemtech/catapult-server/blob/master/src/catapult/model/EntityHasher.cpp#L30
151+ const transactionBodyIdx : number = generationHashIdx + generationHash . length ;
152+ let transactionBody : Uint8Array = transactionBytes . slice ( Transaction . Header_Size ) ;
153+
154+ // in case of aggregate transactions, we hash only the merkle transaction hash.
155+ if ( isAggregateTransaction ) {
156+ transactionBody = transactionBytes . slice ( Transaction . Header_Size , Transaction . Body_Index + 32 ) ;
157+ }
158+
159+ // 5) concatenate binary hash parts
160+ // layout: `signature_R || signerPublicKey || generationHash || EntityDataBuffer`
161+ const entityHashBytes : Uint8Array = new Uint8Array (
162+ signatureR . length
163+ + publicKey . length
164+ + generationHash . length
165+ + transactionBody . length ,
166+ ) ;
167+ entityHashBytes . set ( signatureR , 0 ) ;
168+ entityHashBytes . set ( publicKey , pubKeyIdx ) ;
169+ entityHashBytes . set ( generationHash , generationHashIdx ) ;
170+ entityHashBytes . set ( transactionBody , transactionBodyIdx ) ;
105171
106- return Convert . uint8ToHex ( hash ) ;
172+ // 6) create SHA3 hash of transaction data
173+ // Note: Transaction hashing *always* uses SHA3
174+ SHA3Hasher . func ( entityHash , entityHashBytes , 32 , SignSchema . SHA3 ) ;
175+ return Convert . uint8ToHex ( entityHash ) ;
107176 }
108177
109178 /**
0 commit comments