6
6
// USAGE
7
7
// node ./gobject-hash-debugger.js
8
8
9
- const DV_LE = true ;
10
- // const DV_BE = false;
11
-
12
9
var GObj = module . exports ;
13
10
14
11
GObj . _type = 0b0000010 ; // from SER_GETHASH (bitwise enum)
@@ -17,19 +14,48 @@ GObj._protocalVersion = 70231; // 0x00011257 (BE) => 0x57120100 (LE)
17
14
GObj . _protocalVersionBytes = Uint8Array . from ( [ 0x57 , 0x12 , 0x01 , 0x00 ] ) ;
18
15
19
16
/**
20
- * Only the serialize hex (string) form is canonical.
21
- * Typically the serialization sorts keys in lexicographical order.
22
- * Example:
23
- * {
24
- * "end_epoch": 1721285247,
25
- * "name": "test-proposal-4",
26
- * "payment_address": "yM7M4YJFv58hgea9FUxLtjKBpKXCsjxWNX",
27
- * "payment_amount": 100,
28
- * "start_epoch": 1721275247,
29
- * "type": 1,
30
- * "url": "https://www.dashcentral.org/p/test-proposal-4"
31
- * }
32
- * @typedef {GObjectData }
17
+ * Returns the number of extra bytes needed to encode the variable-length `nSize`.
18
+ * If it is less than 253, we will store `nSize` in just the one (assumed) byte.
19
+ * Otherwise, we scale up according to the smallest power of 2 integer size it can fit in. (u16, u32, u64)
20
+ * Returns the number of bytes after the first byte that we will need to preserve after `WriteCompactSize` is done.
21
+ * @param {number } nSize
22
+ */
23
+ function GetCompactSizeExtraOffset ( nSize ) {
24
+ return nSize < 253
25
+ ? 0
26
+ : nSize <= 2 ** 16 - 1
27
+ ? 2
28
+ : nSize <= 2 ** 32 - 1
29
+ ? 4
30
+ : 8 ;
31
+ }
32
+
33
+ /**
34
+ * Writes `nSize` out to `dv` in a variable-length encoding.
35
+ * Assumes you want to write out the data of `nSize` length to `dv` afterwards.
36
+ * @param {DataView } dv
37
+ * @param {number } offset
38
+ * @param {number } nSize
39
+ */
40
+ function WriteCompactSize (
41
+ dv ,
42
+ offset ,
43
+ nSize ,
44
+ s = GetCompactSizeExtraOffset ( nSize ) ,
45
+ ) {
46
+ switch ( s ) {
47
+ case 2 :
48
+ case 4 :
49
+ case 8 :
50
+ dv . setBigUint64 ( offset + 1 , BigInt ( nSize ) , true ) ;
51
+ nSize = 252 + Math . log2 ( s ) ;
52
+ default :
53
+ dv . setUint8 ( offset , nSize ) ;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * @typedef GObjectData
33
59
* @prop {Uint53 } end_epoch - whole seconds since epoch (like web-standard `exp`)
34
60
* @prop {String } name - kebab case (no spaces)
35
61
* @prop {String } payment_address - base58-encoded p2pkh
@@ -49,72 +75,80 @@ GObj._protocalVersionBytes = Uint8Array.from([0x57, 0x12, 0x01, 0x00]);
49
75
* - NO bls data signature (happens on MN)
50
76
*
51
77
* However, it does include all pieces of data required to verify authenticity from proposer.
52
- *
53
78
* @typedef GObject
54
79
* @prop {Uint8Array } hashParent - typically null / all 0s (32 bytes)
55
80
* @prop {Uint32 } revision - typically 1 or 2, etc (4 bytes)
56
81
* @prop {Uint53 } time - seconds since epoch (8 bytes)
57
82
* @prop {String } hexJson - variable
58
- * @prop {null } [masternodeOutpoint] - ??
59
- * @prop {null } [collateralTxOutputIndex] - 4 bytes of 0xffs
60
- * @prop {null } [collateralTxId] - 32 bytes of 0x00s
61
- * @prop {null } [collateralTxOutputIndex] - 4 bytes of 0xffs
62
- * @prop {null } [signature] - 0 bytes
83
+ * @param {GObject } gobj
84
+ * @returns {Uint8Array }
63
85
*/
64
- GObj . serializeForCollateralTx = function ( gobj ) {
65
- let dataLen = 32 + 4 + 8 + gobj . hexJson . length + 36 + 0 ;
66
-
67
- // ORIGINAL C++ CODE:
68
- // CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
69
- // ss << hashParent;
70
- // ss << revision;
71
- // ss << time;
72
- // ss << HexStr(vchData);
73
- // ss << masternodeOutpoint << uint8_t{} << 0xffffffff; // adding dummy values here to match old hashing
74
- // ss << vchSig;
75
- // return ss.GetHash();
76
-
77
- let bytes = new Uint8Array ( dataLen ) ;
78
- let dv = new DataView ( bytes . buffer ) ;
79
-
80
- // IMPORTANT
81
- // dv.set and bytes.set SWAP THE ORDER of VALUE and OFFSET !!!
82
- let offset = 0 ;
86
+ // * @prop {null } [masternodeOutpoint] - ??
87
+ // * @prop {null } [collateralTxOutputIndex] - 4 bytes of 0xffs
88
+ // * @prop {null } [collateralTxId] - 32 bytes of 0x00s
89
+ // * @prop {null } [collateralTxOutputIndex] - 4 bytes of 0xffs
90
+ // * @prop {null } [signature] - 0 bytes
91
+ GObj . serializeForCollateralTx = function ( {
92
+ hexJson,
93
+ hashParent,
94
+ revision,
95
+ time,
96
+ } ) {
97
+ const compactSizeExtraOffset = GetCompactSizeExtraOffset ( hexJson . length ) ;
98
+
99
+ const dataLen =
100
+ 32 + // hashParent
101
+ 4 + // revision
102
+ 8 + // time
103
+ 1 +
104
+ compactSizeExtraOffset + // compacted length header for HexStr(vchData)
105
+ hexJson . length + // HexStr(vchData)
106
+ 32 +
107
+ 4 + // masterNodeOutpoint (not used, so these bytes are the defaults)
108
+ 1 +
109
+ 4 + // dummy values to match old hashing
110
+ 1 ; // compacted length header for `vchSig` in C++ version (always a single `0` byte)
111
+
112
+ const bytes = new Uint8Array ( dataLen ) ;
113
+ const dv = new DataView ( bytes . buffer ) ;
83
114
84
- bytes . set ( gobj . hashParent , offset ) ; // TODO swap byte order or no?
85
- offset += 32 ; // 32
115
+ let offset = 0 ;
86
116
87
- dv . setInt32 ( offset , gobj . revision , DV_LE ) ;
88
- offset += 4 ; // 36
117
+ bytes . set ( hashParent , offset ) ;
118
+ offset += 32 ;
119
+ dv . setInt32 ( offset , revision , true ) ;
120
+ offset += 4 ;
121
+ dv . setBigInt64 ( offset , BigInt ( time ) , true ) ;
122
+ offset += 8 ;
89
123
90
- {
91
- let time = BigInt ( gobj . time ) ;
92
- dv . setBigInt64 ( 36 , time , DV_LE ) ;
93
- offset += 8 ; // 44
94
- }
124
+ // Write out hexJson, with a compacted size in front
125
+ WriteCompactSize ( dv , offset , hexJson . length , compactSizeExtraOffset ) ;
126
+ offset += 1 + compactSizeExtraOffset ;
127
+ bytes . set ( new TextEncoder ( ) . encode ( hexJson ) , offset ) ;
128
+ offset += hexJson . length ;
95
129
96
130
{
97
- let encoder = new TextEncoder ( ) ;
98
- let hexBytes = encoder . encode ( gobj . hexJson ) ;
99
- bytes . set ( hexBytes , offset ) ;
100
- offset += hexBytes . length ; // 44 + n
101
- }
131
+ // masternodeOutpoint exists in the C++ object and needs to be included,
132
+ // however, it is not filled with data for our purposes.
102
133
103
- // 'bytes' is zero-filled, and there is no masternodeOutpoint yet
104
- // bytes.set(gobj.masternodeOutpointTxId, offset);
105
- offset += 32 ; // 76 + n
134
+ // Write out empty masternodeHash
135
+ offset += 32 ;
106
136
107
- {
137
+ // Write out default mastNode `n` (index)
108
138
let masternodeOutpointIndex = 0xffffffff ;
109
- dv . setUint32 ( offset , masternodeOutpointIndex , DV_LE ) ;
110
- offset += 4 ; // 80 + n
111
- }
139
+ dv . setUint32 ( offset , masternodeOutpointIndex , true ) ;
140
+ offset += 4 ;
112
141
113
- // no BLS signature, so no bytes
114
- // bytes.set(gobj.signature, offset);
115
- // offset += 32 // 112 + n
116
- offset += 0 ; // 80 + n
142
+ // adding dummy values here to match old hashing
143
+ offset += 1 ;
144
+ dv . setUint32 ( offset , 0xffffffff , true ) ;
145
+ offset += 4 ;
146
+ }
117
147
148
+ // In the C++ version, `vchSig` must have its length written out in `WriteCompactSize` fashion.
149
+ // Then, if the length is greater than 0, `vchSig` is written out too.
150
+ // However, we never need a signature here, so we just write out a `0`.
151
+ offset += 1 ;
118
152
return bytes ;
119
153
} ;
120
154
@@ -127,7 +161,6 @@ function bytesToHex(bytes) {
127
161
hexes . push ( h ) ;
128
162
}
129
163
let hex = hexes . join ( "" ) ;
130
-
131
164
return hex ;
132
165
}
133
166
@@ -141,7 +174,6 @@ function hexToBytes(hex) {
141
174
bytes [ j ] = b ;
142
175
j += 1 ;
143
176
}
144
-
145
177
return bytes ;
146
178
}
147
179
@@ -159,51 +191,42 @@ async function main() {
159
191
hexJson :
160
192
"7b2273746172745f65706f6368223a313732313237353234372c22656e645f65706f6368223a313732313238353234372c226e616d65223a22746573742d70726f706f73616c2d34222c227061796d656e745f61646472657373223a22794d374d34594a4676353868676561394655784c746a4b42704b5843736a78574e58222c227061796d656e745f616d6f756e74223a3130302c2274797065223a312c2275726c223a2268747470733a2f2f7777772e6461736863656e7472616c2e6f72672f702f746573742d70726f706f73616c2d34227d" ,
161
193
} ;
194
+
195
+ // Note to AJ: that .GetHex() function in the C++ code reversed the bytes!!!!!!
162
196
let knownHash =
163
- "a9f2d073c2e6c80c340f15580fbfd622e8d74f4c6719708560bb94b259ae7e25 " ;
197
+ "257eae59b294bb60857019674c4fd7e822d6bf0f58150f340cc8e6c273d0f2a9 " ;
164
198
165
199
let gobjCollateralBytes = GObj . serializeForCollateralTx ( gobj ) ;
166
- {
167
- let gobjCollateralHex = bytesToHex ( gobjCollateralBytes ) ;
168
-
169
- console . log ( "Parsed Hex:" ) ;
170
- let offset = 0 ;
171
-
172
- let hashParent = gobjCollateralHex . substr ( offset , 2 * 32 ) ;
173
- offset += 2 * 32 ;
174
- logLine64 ( hashParent , "# hashParent (??LE/BE??)" ) ;
175
-
176
- let revision = gobjCollateralHex . substr ( offset , 2 * 4 ) ;
177
- offset += 2 * 4 ;
178
- logLine64 ( revision , `# revision (LE)` , gobj . revision ) ;
179
200
180
- let time = gobjCollateralHex . substr ( offset , 2 * 8 ) ;
181
- offset += 2 * 8 ;
182
- logLine64 ( time , `# time (LE)` , gobj . time ) ;
183
-
184
- let hexJson = gobjCollateralHex . substr ( offset , 2 * gobj . hexJson . length ) ;
185
- offset += 2 * gobj . hexJson . length ;
186
- logLine64 ( hexJson , `# hexJson (not json utf8 bytes)` ) ;
187
-
188
- let txId = gobjCollateralHex . substr ( offset , 2 * 32 ) ;
189
- offset += 2 * 32 ;
190
- logLine64 ( txId , ` # masternodeOutpointId (??LE/BE??)` ) ;
191
-
192
- let txOut = gobjCollateralHex . substr ( offset , 2 * 4 ) ;
193
- offset += 2 * 4 ;
194
- logLine64 ( txOut , ` # masternodeOutpointIndex (LE)` ) ;
195
-
196
- let sig = gobjCollateralHex . substr ( offset , 2 * 32 ) ;
197
- offset += 2 * 0 ;
198
- logLine64 ( sig , " # signature (0 bytes)" ) ;
199
-
200
- console . log ( "" ) ;
201
- console . log ( "Full Hex" ) ;
202
- console . log ( gobjCollateralHex ) ;
201
+ if ( 1 === 1 ) {
202
+ const hex_bytes = bytesToHex ( gobjCollateralBytes ) ;
203
+
204
+ const data = [
205
+ [ "hashParent" , 32 ] ,
206
+ [ "revision" , 4 ] ,
207
+ [ "time" , 8 ] ,
208
+ [ "hexJSONLen-header" , 1 ] ,
209
+ [ "hexJSONLen" , GetCompactSizeExtraOffset ( gobj . hexJson . length ) ] ,
210
+ [ "hexJSON" , gobj . hexJson . length ] ,
211
+ [ "masterNodeOutpointHash" , 32 ] ,
212
+ [ "masterNodeOutpointIndex" , 4 ] ,
213
+ [ "dummyByte" , 1 ] ,
214
+ [ "dummy4Bytes" , 4 ] ,
215
+ [ "vchSigLen" , 1 ] ,
216
+ ] ;
217
+
218
+ const maxLength = data . reduce ( ( a , c ) => Math . max ( a , c [ 0 ] . length ) , 0 ) ;
219
+
220
+ let start = 0 ;
221
+ for ( const [ name , chunkLen ] of data ) {
222
+ console . log (
223
+ name . padStart ( maxLength , " " ) ,
224
+ "" ,
225
+ hex_bytes . slice ( start , ( start += 2 * chunkLen ) ) ,
226
+ ) ;
227
+ }
203
228
}
204
229
205
- console . log ( "" ) ;
206
-
207
230
let hashBytes ;
208
231
{
209
232
let hash1 = await crypto . subtle . digest ( "SHA-256" , gobjCollateralBytes ) ;
0 commit comments