@@ -33,6 +33,36 @@ import { TransactionType } from './TransactionType';
33
33
*/
34
34
export abstract class Transaction {
35
35
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
+
36
66
/**
37
67
* @constructor
38
68
* @param type
@@ -81,29 +111,68 @@ export abstract class Transaction {
81
111
82
112
/**
83
113
* 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
84
118
* @param {string } transactionPayload HexString Payload
85
119
* @param {Array<number> } generationHashBuffer Network generation hash byte
86
120
* @param {NetworkType } networkType Catapult network identifier
87
121
* @returns {string } Returns Transaction Payload hash
88
122
*/
89
123
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 ) ;
101
124
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 ) ;
105
171
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 ) ;
107
176
}
108
177
109
178
/**
0 commit comments