Skip to content

Commit 0f975c6

Browse files
Validarkcoolaj86
authored andcommitted
fix: swap bytes for hash and add varints
1 parent 326b024 commit 0f975c6

File tree

1 file changed

+130
-107
lines changed

1 file changed

+130
-107
lines changed

gobject-hash-debugger.js

Lines changed: 130 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
// USAGE
77
// node ./gobject-hash-debugger.js
88

9-
const DV_LE = true;
10-
// const DV_BE = false;
11-
129
var GObj = module.exports;
1310

1411
GObj._type = 0b0000010; // from SER_GETHASH (bitwise enum)
@@ -17,19 +14,48 @@ GObj._protocalVersion = 70231; // 0x00011257 (BE) => 0x57120100 (LE)
1714
GObj._protocalVersionBytes = Uint8Array.from([0x57, 0x12, 0x01, 0x00]);
1815

1916
/**
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
3359
* @prop {Uint53} end_epoch - whole seconds since epoch (like web-standard `exp`)
3460
* @prop {String} name - kebab case (no spaces)
3561
* @prop {String} payment_address - base58-encoded p2pkh
@@ -49,72 +75,80 @@ GObj._protocalVersionBytes = Uint8Array.from([0x57, 0x12, 0x01, 0x00]);
4975
* - NO bls data signature (happens on MN)
5076
*
5177
* However, it does include all pieces of data required to verify authenticity from proposer.
52-
*
5378
* @typedef GObject
5479
* @prop {Uint8Array} hashParent - typically null / all 0s (32 bytes)
5580
* @prop {Uint32} revision - typically 1 or 2, etc (4 bytes)
5681
* @prop {Uint53} time - seconds since epoch (8 bytes)
5782
* @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}
6385
*/
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);
83114

84-
bytes.set(gobj.hashParent, offset); // TODO swap byte order or no?
85-
offset += 32; // 32
115+
let offset = 0;
86116

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;
89123

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;
95129

96130
{
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.
102133

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;
106136

107-
{
137+
// Write out default mastNode `n` (index)
108138
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;
112141

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+
}
117147

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;
118152
return bytes;
119153
};
120154

@@ -127,7 +161,6 @@ function bytesToHex(bytes) {
127161
hexes.push(h);
128162
}
129163
let hex = hexes.join("");
130-
131164
return hex;
132165
}
133166

@@ -141,7 +174,6 @@ function hexToBytes(hex) {
141174
bytes[j] = b;
142175
j += 1;
143176
}
144-
145177
return bytes;
146178
}
147179

@@ -159,51 +191,42 @@ async function main() {
159191
hexJson:
160192
"7b2273746172745f65706f6368223a313732313237353234372c22656e645f65706f6368223a313732313238353234372c226e616d65223a22746573742d70726f706f73616c2d34222c227061796d656e745f61646472657373223a22794d374d34594a4676353868676561394655784c746a4b42704b5843736a78574e58222c227061796d656e745f616d6f756e74223a3130302c2274797065223a312c2275726c223a2268747470733a2f2f7777772e6461736863656e7472616c2e6f72672f702f746573742d70726f706f73616c2d34227d",
161193
};
194+
195+
// Note to AJ: that .GetHex() function in the C++ code reversed the bytes!!!!!!
162196
let knownHash =
163-
"a9f2d073c2e6c80c340f15580fbfd622e8d74f4c6719708560bb94b259ae7e25";
197+
"257eae59b294bb60857019674c4fd7e822d6bf0f58150f340cc8e6c273d0f2a9";
164198

165199
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);
179200

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+
}
203228
}
204229

205-
console.log("");
206-
207230
let hashBytes;
208231
{
209232
let hash1 = await crypto.subtle.digest("SHA-256", gobjCollateralBytes);

0 commit comments

Comments
 (0)