@totemsdk/core 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/src/Streamable.d.ts +3 -0
- package/src/Streamable.js +36 -51
- package/src/javaStreamables.js +53 -49
- package/src/mmr.d.ts +3 -5
- package/src/mmr.js +27 -39
- package/src/scripts/types.d.ts +81 -0
- package/src/scripts/types.js +145 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totemsdk/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Core cryptographic primitives for Totem SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"types": "./dist/tx/index.d.ts",
|
|
27
27
|
"import": "./dist/tx/index.js"
|
|
28
28
|
},
|
|
29
|
+
"./scripts": {
|
|
30
|
+
"types": "./dist/scripts/index.d.ts",
|
|
31
|
+
"import": "./dist/scripts/index.js"
|
|
32
|
+
},
|
|
29
33
|
"./test-vectors.json": "./test-vectors.json"
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
package/src/Streamable.d.ts
CHANGED
|
@@ -263,3 +263,6 @@ export declare function writeHierarchicalWitness(bundle: HierarchicalWitnessBund
|
|
|
263
263
|
* Write hierarchical witness and return as hex string
|
|
264
264
|
*/
|
|
265
265
|
export declare function writeHierarchicalWitnessToHex(bundle: HierarchicalWitnessBundle): string;
|
|
266
|
+
export declare const encodeMiniNumber: typeof writeMiniNumber;
|
|
267
|
+
export declare const encodeMiniData: typeof writeMiniData;
|
|
268
|
+
export declare const encodeMiniString: typeof writeMiniString;
|
package/src/Streamable.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Streamable.ts - Canonical Java-Compatible Serialization Primitives
|
|
4
3
|
*
|
|
@@ -21,29 +20,7 @@
|
|
|
21
20
|
* Created: 2026-01-20
|
|
22
21
|
* Purpose: Single source of truth for all Minima type serialization
|
|
23
22
|
*/
|
|
24
|
-
|
|
25
|
-
exports.STATETYPE_BOOL = exports.STATETYPE_STRING = exports.STATETYPE_NUMBER = exports.STATETYPE_HEX = void 0;
|
|
26
|
-
exports.hexToBytes = hexToBytes;
|
|
27
|
-
exports.bytesToHex = bytesToHex;
|
|
28
|
-
exports.concat = concat;
|
|
29
|
-
exports.writeMiniByte = writeMiniByte;
|
|
30
|
-
exports.writeMiniData = writeMiniData;
|
|
31
|
-
exports.writeMiniString = writeMiniString;
|
|
32
|
-
exports.bigIntToByteArray = bigIntToByteArray;
|
|
33
|
-
exports.writeMiniNumber = writeMiniNumber;
|
|
34
|
-
exports.writeHashToStream = writeHashToStream;
|
|
35
|
-
exports.writeMMREntryNumber = writeMMREntryNumber;
|
|
36
|
-
exports.normalizeHexString = normalizeHexString;
|
|
37
|
-
exports.writeStateVariable = writeStateVariable;
|
|
38
|
-
exports.writeMMRData = writeMMRData;
|
|
39
|
-
exports.writeMMRProofChunk = writeMMRProofChunk;
|
|
40
|
-
exports.writeMMRProof = writeMMRProof;
|
|
41
|
-
exports.writeSignatureProof = writeSignatureProof;
|
|
42
|
-
exports.writeSignature = writeSignature;
|
|
43
|
-
exports.writeWitness = writeWitness;
|
|
44
|
-
exports.writeHierarchicalWitness = writeHierarchicalWitness;
|
|
45
|
-
exports.writeHierarchicalWitnessToHex = writeHierarchicalWitnessToHex;
|
|
46
|
-
function hexToBytes(hex) {
|
|
23
|
+
export function hexToBytes(hex) {
|
|
47
24
|
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
48
25
|
if (cleanHex.length === 0) {
|
|
49
26
|
return new Uint8Array(0);
|
|
@@ -57,10 +34,10 @@ function hexToBytes(hex) {
|
|
|
57
34
|
}
|
|
58
35
|
return bytes;
|
|
59
36
|
}
|
|
60
|
-
function bytesToHex(bytes) {
|
|
37
|
+
export function bytesToHex(bytes) {
|
|
61
38
|
return '0x' + Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
62
39
|
}
|
|
63
|
-
function concat(...arrays) {
|
|
40
|
+
export function concat(...arrays) {
|
|
64
41
|
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
65
42
|
const result = new Uint8Array(totalLength);
|
|
66
43
|
let offset = 0;
|
|
@@ -75,7 +52,7 @@ function concat(...arrays) {
|
|
|
75
52
|
*
|
|
76
53
|
* Format: single byte (0-255)
|
|
77
54
|
*/
|
|
78
|
-
function writeMiniByte(value) {
|
|
55
|
+
export function writeMiniByte(value) {
|
|
79
56
|
if (typeof value === 'boolean') {
|
|
80
57
|
return new Uint8Array([value ? 1 : 0]);
|
|
81
58
|
}
|
|
@@ -89,7 +66,7 @@ function writeMiniByte(value) {
|
|
|
89
66
|
*
|
|
90
67
|
* Format: 4-byte big-endian int length + raw bytes
|
|
91
68
|
*/
|
|
92
|
-
function writeMiniData(data) {
|
|
69
|
+
export function writeMiniData(data) {
|
|
93
70
|
const length = data.length;
|
|
94
71
|
const lengthBytes = new Uint8Array([
|
|
95
72
|
(length >> 24) & 0xff,
|
|
@@ -105,7 +82,7 @@ function writeMiniData(data) {
|
|
|
105
82
|
* Format: MiniData encoding of UTF-8 bytes
|
|
106
83
|
* = 4-byte int length + UTF-8 bytes
|
|
107
84
|
*/
|
|
108
|
-
function writeMiniString(str) {
|
|
85
|
+
export function writeMiniString(str) {
|
|
109
86
|
const utf8Bytes = new TextEncoder().encode(str);
|
|
110
87
|
return writeMiniData(utf8Bytes);
|
|
111
88
|
}
|
|
@@ -119,7 +96,7 @@ function writeMiniString(str) {
|
|
|
119
96
|
* @param value - The BigInt value (must be non-negative)
|
|
120
97
|
* @returns Uint8Array in two's complement format
|
|
121
98
|
*/
|
|
122
|
-
function bigIntToByteArray(value) {
|
|
99
|
+
export function bigIntToByteArray(value) {
|
|
123
100
|
if (value < 0n) {
|
|
124
101
|
throw new Error(`bigIntToByteArray does not support negative values: ${value}`);
|
|
125
102
|
}
|
|
@@ -152,7 +129,7 @@ function bigIntToByteArray(value) {
|
|
|
152
129
|
* @param value - The unscaled BigInt value
|
|
153
130
|
* @param scale - The scale (decimal places), default 0
|
|
154
131
|
*/
|
|
155
|
-
function writeMiniNumber(value, scale = 0) {
|
|
132
|
+
export function writeMiniNumber(value, scale = 0) {
|
|
156
133
|
if (typeof value !== 'bigint') {
|
|
157
134
|
throw new Error(`writeMiniNumber requires bigint, got ${typeof value}`);
|
|
158
135
|
}
|
|
@@ -176,7 +153,7 @@ function writeMiniNumber(value, scale = 0) {
|
|
|
176
153
|
* Format: 4-byte big-endian int length + hash bytes
|
|
177
154
|
* (Same as MiniData)
|
|
178
155
|
*/
|
|
179
|
-
function writeHashToStream(hash) {
|
|
156
|
+
export function writeHashToStream(hash) {
|
|
180
157
|
return writeMiniData(hash);
|
|
181
158
|
}
|
|
182
159
|
/**
|
|
@@ -194,7 +171,7 @@ function writeHashToStream(hash) {
|
|
|
194
171
|
* [00 01 00] - MiniNumber: scale=0, len=1, data=0x00
|
|
195
172
|
* [00 00 00 LL ...] - MiniData: 4-byte len + BigInteger bytes
|
|
196
173
|
*/
|
|
197
|
-
function writeMMREntryNumber(value, scale = 0) {
|
|
174
|
+
export function writeMMREntryNumber(value, scale = 0) {
|
|
198
175
|
if (value < 0n) {
|
|
199
176
|
throw new Error(`writeMMREntryNumber does not support negative values: ${value}`);
|
|
200
177
|
}
|
|
@@ -208,10 +185,10 @@ function writeMMREntryNumber(value, scale = 0) {
|
|
|
208
185
|
/**
|
|
209
186
|
* StateVariable type constants from Java StateVariable.java
|
|
210
187
|
*/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
188
|
+
export const STATETYPE_HEX = 1;
|
|
189
|
+
export const STATETYPE_NUMBER = 2;
|
|
190
|
+
export const STATETYPE_STRING = 4;
|
|
191
|
+
export const STATETYPE_BOOL = 8;
|
|
215
192
|
/**
|
|
216
193
|
* Normalize hex/address string to 0x format.
|
|
217
194
|
* Handles both Mx (Minima Base32) and 0x formats.
|
|
@@ -220,7 +197,7 @@ exports.STATETYPE_BOOL = 8;
|
|
|
220
197
|
* Note: For Mx conversion, caller should use mxToHex from minima-base32
|
|
221
198
|
* This function handles 0x prefix normalization only.
|
|
222
199
|
*/
|
|
223
|
-
function normalizeHexString(value) {
|
|
200
|
+
export function normalizeHexString(value) {
|
|
224
201
|
if (!value) {
|
|
225
202
|
return '0x00';
|
|
226
203
|
}
|
|
@@ -242,7 +219,7 @@ function normalizeHexString(value) {
|
|
|
242
219
|
* @param sv - StateVariable with high-level value
|
|
243
220
|
* @param mxToHex - Optional function to convert Mx addresses to 0x hex
|
|
244
221
|
*/
|
|
245
|
-
function writeStateVariable(sv, mxToHex) {
|
|
222
|
+
export function writeStateVariable(sv, mxToHex) {
|
|
246
223
|
if (sv.port < 0 || sv.port > 255) {
|
|
247
224
|
throw new Error(`StateVariable port must be 0-255, got ${sv.port}`);
|
|
248
225
|
}
|
|
@@ -251,7 +228,7 @@ function writeStateVariable(sv, mxToHex) {
|
|
|
251
228
|
let dataBytes;
|
|
252
229
|
switch (sv.type) {
|
|
253
230
|
case 'bool':
|
|
254
|
-
typeByte = writeMiniByte(
|
|
231
|
+
typeByte = writeMiniByte(STATETYPE_BOOL);
|
|
255
232
|
if (typeof sv.value === 'boolean') {
|
|
256
233
|
dataBytes = writeMiniByte(sv.value);
|
|
257
234
|
}
|
|
@@ -263,7 +240,7 @@ function writeStateVariable(sv, mxToHex) {
|
|
|
263
240
|
}
|
|
264
241
|
break;
|
|
265
242
|
case 'number':
|
|
266
|
-
typeByte = writeMiniByte(
|
|
243
|
+
typeByte = writeMiniByte(STATETYPE_NUMBER);
|
|
267
244
|
if (typeof sv.value === 'bigint') {
|
|
268
245
|
dataBytes = writeMiniNumber(sv.value);
|
|
269
246
|
}
|
|
@@ -275,7 +252,7 @@ function writeStateVariable(sv, mxToHex) {
|
|
|
275
252
|
}
|
|
276
253
|
break;
|
|
277
254
|
case 'hex':
|
|
278
|
-
typeByte = writeMiniByte(
|
|
255
|
+
typeByte = writeMiniByte(STATETYPE_HEX);
|
|
279
256
|
if (typeof sv.value === 'string') {
|
|
280
257
|
let normalizedHex = sv.value;
|
|
281
258
|
if (sv.value.toLowerCase().startsWith('mx') && mxToHex) {
|
|
@@ -294,7 +271,7 @@ function writeStateVariable(sv, mxToHex) {
|
|
|
294
271
|
}
|
|
295
272
|
break;
|
|
296
273
|
case 'string':
|
|
297
|
-
typeByte = writeMiniByte(
|
|
274
|
+
typeByte = writeMiniByte(STATETYPE_STRING);
|
|
298
275
|
if (typeof sv.value === 'string') {
|
|
299
276
|
let bracketedValue = sv.value;
|
|
300
277
|
if (!sv.value.startsWith('[') || !sv.value.endsWith(']')) {
|
|
@@ -318,7 +295,7 @@ function writeStateVariable(sv, mxToHex) {
|
|
|
318
295
|
* mData.writeHashToStream(zOut); // 4-byte len + hash
|
|
319
296
|
* mValue.writeDataStream(zOut); // MiniNumber
|
|
320
297
|
*/
|
|
321
|
-
function writeMMRData(mmrData) {
|
|
298
|
+
export function writeMMRData(mmrData) {
|
|
322
299
|
const hashBytes = writeHashToStream(mmrData.data);
|
|
323
300
|
const valueBytes = writeMiniNumber(mmrData.value, 0);
|
|
324
301
|
return concat(hashBytes, valueBytes);
|
|
@@ -330,7 +307,7 @@ function writeMMRData(mmrData) {
|
|
|
330
307
|
* mLeft.writeDataStream(zOut); // MiniByte (1 byte)
|
|
331
308
|
* mMMRData.writeDataStream(zOut); // MMRData
|
|
332
309
|
*/
|
|
333
|
-
function writeMMRProofChunk(chunk) {
|
|
310
|
+
export function writeMMRProofChunk(chunk) {
|
|
334
311
|
const leftByte = writeMiniByte(chunk.isLeft);
|
|
335
312
|
const dataBytes = writeMMRData(chunk.mmrData);
|
|
336
313
|
return concat(leftByte, dataBytes);
|
|
@@ -343,7 +320,7 @@ function writeMMRProofChunk(chunk) {
|
|
|
343
320
|
* MiniNumber.WriteToStream(zOut, len); // Array length as MiniNumber
|
|
344
321
|
* for each chunk: chunk.writeDataStream(zOut);
|
|
345
322
|
*/
|
|
346
|
-
function writeMMRProof(proof) {
|
|
323
|
+
export function writeMMRProof(proof) {
|
|
347
324
|
const blockTimeBytes = writeMiniNumber(proof.blockTime, 0);
|
|
348
325
|
const lengthBytes = writeMiniNumber(BigInt(proof.chunks.length), 0);
|
|
349
326
|
const parts = [blockTimeBytes, lengthBytes];
|
|
@@ -360,7 +337,7 @@ function writeMMRProof(proof) {
|
|
|
360
337
|
* mSignature.writeDataStream(zOut); // MiniData
|
|
361
338
|
* mProof.writeDataStream(zOut); // MMRProof
|
|
362
339
|
*/
|
|
363
|
-
function writeSignatureProof(sigProof) {
|
|
340
|
+
export function writeSignatureProof(sigProof) {
|
|
364
341
|
const pubKeyBytes = writeMiniData(sigProof.leafPubkey);
|
|
365
342
|
const sigBytes = writeMiniData(sigProof.signature);
|
|
366
343
|
const proofBytes = writeMMRProof(sigProof.mmrProof);
|
|
@@ -373,7 +350,7 @@ function writeSignatureProof(sigProof) {
|
|
|
373
350
|
* MiniNumber.WriteToStream(zOut, mSignatures.size());
|
|
374
351
|
* for each sig: sig.writeDataStream(zOut);
|
|
375
352
|
*/
|
|
376
|
-
function writeSignature(sig) {
|
|
353
|
+
export function writeSignature(sig) {
|
|
377
354
|
const lengthBytes = writeMiniNumber(BigInt(sig.proofs.length), 0);
|
|
378
355
|
const parts = [lengthBytes];
|
|
379
356
|
for (const proof of sig.proofs) {
|
|
@@ -392,7 +369,7 @@ function writeSignature(sig) {
|
|
|
392
369
|
* MiniNumber.WriteToStream(zOut, mScriptProofs.size());
|
|
393
370
|
* for each sp: sp.writeDataStream(zOut);
|
|
394
371
|
*/
|
|
395
|
-
function writeWitness(witness) {
|
|
372
|
+
export function writeWitness(witness) {
|
|
396
373
|
const parts = [];
|
|
397
374
|
// Signatures
|
|
398
375
|
parts.push(writeMiniNumber(BigInt(witness.signatures.length), 0));
|
|
@@ -415,7 +392,7 @@ function writeWitness(witness) {
|
|
|
415
392
|
* Write a simplified hierarchical witness (signatures only)
|
|
416
393
|
* Used for WOTS transaction signing flow
|
|
417
394
|
*/
|
|
418
|
-
function writeHierarchicalWitness(bundle) {
|
|
395
|
+
export function writeHierarchicalWitness(bundle) {
|
|
419
396
|
const lengthBytes = writeMiniNumber(BigInt(bundle.signatures.length), 0);
|
|
420
397
|
const parts = [lengthBytes];
|
|
421
398
|
for (const sig of bundle.signatures) {
|
|
@@ -426,6 +403,14 @@ function writeHierarchicalWitness(bundle) {
|
|
|
426
403
|
/**
|
|
427
404
|
* Write hierarchical witness and return as hex string
|
|
428
405
|
*/
|
|
429
|
-
function writeHierarchicalWitnessToHex(bundle) {
|
|
406
|
+
export function writeHierarchicalWitnessToHex(bundle) {
|
|
430
407
|
return bytesToHex(writeHierarchicalWitness(bundle));
|
|
431
408
|
}
|
|
409
|
+
// ============================================================
|
|
410
|
+
// EXTENSION-MATCHING ALIASES
|
|
411
|
+
// ============================================================
|
|
412
|
+
// The extension's WitnessSerializer uses encode* naming, while SDK uses write* naming.
|
|
413
|
+
// These aliases provide compatibility with extension code.
|
|
414
|
+
export const encodeMiniNumber = writeMiniNumber;
|
|
415
|
+
export const encodeMiniData = writeMiniData;
|
|
416
|
+
export const encodeMiniString = writeMiniString;
|
package/src/javaStreamables.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Java-Compatible Serialization Helpers
|
|
4
3
|
*
|
|
@@ -13,26 +12,8 @@
|
|
|
13
12
|
* NOTE: This module provides number-based wrappers for backward compatibility.
|
|
14
13
|
* The canonical implementations are in Streamable.ts which uses bigint for precision.
|
|
15
14
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
exports.serializeMiniData = serializeMiniData;
|
|
19
|
-
exports.hashAllObjects = hashAllObjects;
|
|
20
|
-
exports.deriveChainSeedJava = deriveChainSeedJava;
|
|
21
|
-
exports.deriveChildTreeSeedJava = deriveChildTreeSeedJava;
|
|
22
|
-
exports.hashObject = hashObject;
|
|
23
|
-
exports.indexToMiniDataBytes = indexToMiniDataBytes;
|
|
24
|
-
exports.derivePerAddressSeed = derivePerAddressSeed;
|
|
25
|
-
exports.serializeMiniNumberZERO = serializeMiniNumberZERO;
|
|
26
|
-
exports.serializeMiniNumberONE = serializeMiniNumberONE;
|
|
27
|
-
exports.writeHashToStream = writeHashToStream;
|
|
28
|
-
exports.javaHashAllObjects = javaHashAllObjects;
|
|
29
|
-
exports.createMMREntryNumber = createMMREntryNumber;
|
|
30
|
-
exports.serializeMMREntryNumber = serializeMMREntryNumber;
|
|
31
|
-
exports.serializeMMRData = serializeMMRData;
|
|
32
|
-
exports.serializeMMREntry = serializeMMREntry;
|
|
33
|
-
exports.precomputeTransactionCoinID = precomputeTransactionCoinID;
|
|
34
|
-
const sha3_1 = require("@noble/hashes/sha3");
|
|
35
|
-
const Streamable_1 = require("./Streamable");
|
|
15
|
+
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
16
|
+
import { writeMiniNumber as streamableWriteMiniNumber, writeMiniData as streamableWriteMiniData, writeHashToStream as streamableWriteHashToStream, bigIntToByteArray } from './Streamable.js';
|
|
36
17
|
/**
|
|
37
18
|
* Serialize a number in MiniNumber format (Java compatible)
|
|
38
19
|
*
|
|
@@ -41,10 +22,10 @@ const Streamable_1 = require("./Streamable");
|
|
|
41
22
|
* @param n - Non-negative integer to serialize
|
|
42
23
|
* @returns Serialized bytes matching Java MiniNumber format
|
|
43
24
|
*/
|
|
44
|
-
function serializeMiniNumber(n) {
|
|
25
|
+
export function serializeMiniNumber(n) {
|
|
45
26
|
if (n < 0)
|
|
46
27
|
throw new Error('MiniNumber must be non-negative');
|
|
47
|
-
return (
|
|
28
|
+
return streamableWriteMiniNumber(BigInt(n));
|
|
48
29
|
}
|
|
49
30
|
/**
|
|
50
31
|
* Serialize bytes in MiniData format (Java compatible)
|
|
@@ -54,8 +35,8 @@ function serializeMiniNumber(n) {
|
|
|
54
35
|
* @param data - Bytes to serialize
|
|
55
36
|
* @returns Serialized bytes matching Java MiniData format
|
|
56
37
|
*/
|
|
57
|
-
function serializeMiniData(data) {
|
|
58
|
-
return (
|
|
38
|
+
export function serializeMiniData(data) {
|
|
39
|
+
return streamableWriteMiniData(data);
|
|
59
40
|
}
|
|
60
41
|
/**
|
|
61
42
|
* Hash multiple Streamable objects (Java compatible)
|
|
@@ -70,7 +51,7 @@ function serializeMiniData(data) {
|
|
|
70
51
|
* @param items - Array of serialized objects (use serializeMiniNumber/serializeMiniData)
|
|
71
52
|
* @returns 32-byte SHA3-256 hash
|
|
72
53
|
*/
|
|
73
|
-
function hashAllObjects(...items) {
|
|
54
|
+
export function hashAllObjects(...items) {
|
|
74
55
|
// Concatenate all serialized items
|
|
75
56
|
const totalLength = items.reduce((sum, item) => sum + item.length, 0);
|
|
76
57
|
const combined = new Uint8Array(totalLength);
|
|
@@ -80,7 +61,7 @@ function hashAllObjects(...items) {
|
|
|
80
61
|
offset += item.length;
|
|
81
62
|
}
|
|
82
63
|
// SHA3-256 hash
|
|
83
|
-
return
|
|
64
|
+
return sha3_256(combined);
|
|
84
65
|
}
|
|
85
66
|
/**
|
|
86
67
|
* Derive per-chain seed matching Java TreeKeyNode.java exactly
|
|
@@ -92,7 +73,7 @@ function hashAllObjects(...items) {
|
|
|
92
73
|
* @param index - Chain index (0-63)
|
|
93
74
|
* @returns 32-byte derived seed for this chain
|
|
94
75
|
*/
|
|
95
|
-
function deriveChainSeedJava(privateSeed, index) {
|
|
76
|
+
export function deriveChainSeedJava(privateSeed, index) {
|
|
96
77
|
const indexSerialized = serializeMiniNumber(index);
|
|
97
78
|
const seedSerialized = serializeMiniData(privateSeed);
|
|
98
79
|
return hashAllObjects(indexSerialized, seedSerialized);
|
|
@@ -110,7 +91,7 @@ function deriveChainSeedJava(privateSeed, index) {
|
|
|
110
91
|
* @param childIndex - Child index (0-63)
|
|
111
92
|
* @returns 32-byte derived seed for child tree
|
|
112
93
|
*/
|
|
113
|
-
function deriveChildTreeSeedJava(childSeed, childIndex) {
|
|
94
|
+
export function deriveChildTreeSeedJava(childSeed, childIndex) {
|
|
114
95
|
const indexSerialized = serializeMiniNumber(childIndex);
|
|
115
96
|
const seedSerialized = serializeMiniData(childSeed);
|
|
116
97
|
return hashAllObjects(indexSerialized, seedSerialized);
|
|
@@ -126,9 +107,9 @@ function deriveChildTreeSeedJava(childSeed, childIndex) {
|
|
|
126
107
|
* @param data - Raw bytes (will be serialized as MiniData)
|
|
127
108
|
* @returns 32-byte SHA3-256 hash
|
|
128
109
|
*/
|
|
129
|
-
function hashObject(data) {
|
|
110
|
+
export function hashObject(data) {
|
|
130
111
|
const serialized = serializeMiniData(data);
|
|
131
|
-
return
|
|
112
|
+
return sha3_256(serialized);
|
|
132
113
|
}
|
|
133
114
|
/**
|
|
134
115
|
* Convert index to MiniData bytes matching Java's:
|
|
@@ -140,7 +121,7 @@ function hashObject(data) {
|
|
|
140
121
|
* @param index - Non-negative integer (0, 1, 2, ...)
|
|
141
122
|
* @returns Minimal byte representation of the index
|
|
142
123
|
*/
|
|
143
|
-
function indexToMiniDataBytes(index) {
|
|
124
|
+
export function indexToMiniDataBytes(index) {
|
|
144
125
|
if (index === 0) {
|
|
145
126
|
return new Uint8Array([0x00]);
|
|
146
127
|
}
|
|
@@ -168,7 +149,7 @@ function indexToMiniDataBytes(index) {
|
|
|
168
149
|
* @param addressIndex - Address index (0, 1, 2, ... 63)
|
|
169
150
|
* @returns 32-byte private seed for this address's TreeKey
|
|
170
151
|
*/
|
|
171
|
-
function derivePerAddressSeed(baseSeed, addressIndex) {
|
|
152
|
+
export function derivePerAddressSeed(baseSeed, addressIndex) {
|
|
172
153
|
// Step 1: Create modifier bytes from BigInteger representation of index
|
|
173
154
|
const modifierBytes = indexToMiniDataBytes(addressIndex);
|
|
174
155
|
// Step 2: Serialize both as MiniData (4-byte length prefix each)
|
|
@@ -178,20 +159,27 @@ function derivePerAddressSeed(baseSeed, addressIndex) {
|
|
|
178
159
|
const combined = new Uint8Array(baseSeedSerialized.length + modifierSerialized.length);
|
|
179
160
|
combined.set(baseSeedSerialized, 0);
|
|
180
161
|
combined.set(modifierSerialized, baseSeedSerialized.length);
|
|
181
|
-
|
|
162
|
+
const result = sha3_256(combined);
|
|
163
|
+
// DIAGNOSTIC: Log first 8 bytes to detect bundle duplication or implementation differences
|
|
164
|
+
// Enable via: globalThis.TOTEM_SEED_DEBUG = true
|
|
165
|
+
if (typeof console !== 'undefined' && globalThis.TOTEM_SEED_DEBUG) {
|
|
166
|
+
const toHex = (b) => Array.from(b.slice(0, 8)).map(x => x.toString(16).padStart(2, '0')).join('');
|
|
167
|
+
console.log(`[derivePerAddressSeed] idx=${addressIndex} baseSeed[0:8]=${toHex(baseSeed)} -> result[0:8]=${toHex(result)}`);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
182
170
|
}
|
|
183
171
|
/**
|
|
184
172
|
* Serialize MiniNumber.ZERO - cached for performance
|
|
185
173
|
* Returns: [0x00, 0x01, 0x00] = scale(0) + length(1) + value(0)
|
|
186
174
|
*/
|
|
187
|
-
function serializeMiniNumberZERO() {
|
|
175
|
+
export function serializeMiniNumberZERO() {
|
|
188
176
|
return new Uint8Array([0x00, 0x01, 0x00]);
|
|
189
177
|
}
|
|
190
178
|
/**
|
|
191
179
|
* Serialize MiniNumber.ONE
|
|
192
180
|
* Returns: [0x00, 0x01, 0x01] = scale(0) + length(1) + value(1)
|
|
193
181
|
*/
|
|
194
|
-
function serializeMiniNumberONE() {
|
|
182
|
+
export function serializeMiniNumberONE() {
|
|
195
183
|
return new Uint8Array([0x00, 0x01, 0x01]);
|
|
196
184
|
}
|
|
197
185
|
/**
|
|
@@ -202,8 +190,8 @@ function serializeMiniNumberONE() {
|
|
|
202
190
|
* @param data - Hash bytes (max 64 bytes per MINIMA_MAX_HASH_LENGTH)
|
|
203
191
|
* @returns Serialized bytes with 4-byte length prefix
|
|
204
192
|
*/
|
|
205
|
-
function writeHashToStream(data) {
|
|
206
|
-
return (
|
|
193
|
+
export function writeHashToStream(data) {
|
|
194
|
+
return streamableWriteHashToStream(data);
|
|
207
195
|
}
|
|
208
196
|
/**
|
|
209
197
|
* Java hashAllObjects for MMRData hashing
|
|
@@ -223,7 +211,7 @@ function writeHashToStream(data) {
|
|
|
223
211
|
* @param items - Pre-serialized items to concatenate and hash
|
|
224
212
|
* @returns 32-byte SHA3-256 hash
|
|
225
213
|
*/
|
|
226
|
-
function javaHashAllObjects(...items) {
|
|
214
|
+
export function javaHashAllObjects(...items) {
|
|
227
215
|
const totalLength = items.reduce((sum, item) => sum + item.length, 0);
|
|
228
216
|
const combined = new Uint8Array(totalLength);
|
|
229
217
|
let offset = 0;
|
|
@@ -231,12 +219,12 @@ function javaHashAllObjects(...items) {
|
|
|
231
219
|
combined.set(item, offset);
|
|
232
220
|
offset += item.length;
|
|
233
221
|
}
|
|
234
|
-
return
|
|
222
|
+
return sha3_256(combined);
|
|
235
223
|
}
|
|
236
224
|
/**
|
|
237
225
|
* Create MMREntryNumber from bigint (common case for integer positions)
|
|
238
226
|
*/
|
|
239
|
-
function createMMREntryNumber(value) {
|
|
227
|
+
export function createMMREntryNumber(value) {
|
|
240
228
|
return { scale: 0, unscaled: value };
|
|
241
229
|
}
|
|
242
230
|
/**
|
|
@@ -249,7 +237,7 @@ function createMMREntryNumber(value) {
|
|
|
249
237
|
* @param entry - MMREntryNumber to serialize
|
|
250
238
|
* @returns Serialized bytes: MiniNumber(scale) + MiniData(unscaled bytes)
|
|
251
239
|
*/
|
|
252
|
-
function serializeMMREntryNumber(entry) {
|
|
240
|
+
export function serializeMMREntryNumber(entry) {
|
|
253
241
|
const scaleSerialized = serializeMiniNumber(entry.scale);
|
|
254
242
|
const unscaledBytes = bigintToSignedBytes(entry.unscaled);
|
|
255
243
|
const unscaledSerialized = serializeMiniData(unscaledBytes);
|
|
@@ -263,7 +251,7 @@ function serializeMMREntryNumber(entry) {
|
|
|
263
251
|
* Uses the canonical implementation from Streamable.ts
|
|
264
252
|
*/
|
|
265
253
|
function bigintToSignedBytes(n) {
|
|
266
|
-
return
|
|
254
|
+
return bigIntToByteArray(n);
|
|
267
255
|
}
|
|
268
256
|
/**
|
|
269
257
|
* Serialize MMRData matching Java MMRData.writeDataStream()
|
|
@@ -275,7 +263,7 @@ function bigintToSignedBytes(n) {
|
|
|
275
263
|
* @param mmrData - MMRData to serialize
|
|
276
264
|
* @returns Serialized bytes: writeHashToStream(hash) + MiniNumber(value)
|
|
277
265
|
*/
|
|
278
|
-
function serializeMMRData(mmrData) {
|
|
266
|
+
export function serializeMMRData(mmrData) {
|
|
279
267
|
const hashSerialized = writeHashToStream(mmrData.data);
|
|
280
268
|
const valueSerialized = serializeMiniNumber(Number(mmrData.value));
|
|
281
269
|
const result = new Uint8Array(hashSerialized.length + valueSerialized.length);
|
|
@@ -295,7 +283,7 @@ function serializeMMRData(mmrData) {
|
|
|
295
283
|
* @param entry - MMREntry to serialize
|
|
296
284
|
* @returns Serialized bytes: MiniNumber(row) + MMREntryNumber + MMRData
|
|
297
285
|
*/
|
|
298
|
-
function serializeMMREntry(entry) {
|
|
286
|
+
export function serializeMMREntry(entry) {
|
|
299
287
|
const rowSerialized = serializeMiniNumber(entry.row);
|
|
300
288
|
const entryNumberSerialized = serializeMMREntryNumber(entry.entryNumber);
|
|
301
289
|
const mmrDataSerialized = serializeMMRData(entry.mmrData);
|
|
@@ -309,7 +297,23 @@ function serializeMMREntry(entry) {
|
|
|
309
297
|
result.set(mmrDataSerialized, offset);
|
|
310
298
|
return result;
|
|
311
299
|
}
|
|
312
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Precompute output coin IDs before computing the transaction digest.
|
|
302
|
+
*
|
|
303
|
+
* CRITICAL: Java's txnsign command calls TxPoWGenerator.precomputeTransactionCoinID(txn)
|
|
304
|
+
* BEFORE calculateTransactionID(). This means the transaction digest that gets signed
|
|
305
|
+
* includes the PRECOMPUTED output coin IDs, not the placeholder 0x00.
|
|
306
|
+
*
|
|
307
|
+
* Formula: outputCoinID = SHA3-256( writeMiniData(input[0].coinID) || writeMiniNumber(outputIndex) )
|
|
308
|
+
* This matches Java's Crypto.hashObjects(baseCoinID, new MiniNumber(outputNum))
|
|
309
|
+
*
|
|
310
|
+
* Without this, the wallet signs a different digest than what the node verifies against,
|
|
311
|
+
* causing allsignaturesvalid=false on every transaction.
|
|
312
|
+
*
|
|
313
|
+
* @param inputs - Transaction inputs array, each must have a coinId (Uint8Array)
|
|
314
|
+
* @param outputs - Transaction outputs array, each will have its coinId mutated in-place
|
|
315
|
+
*/
|
|
316
|
+
export function precomputeTransactionCoinID(inputs, outputs) {
|
|
313
317
|
if (inputs.length === 0)
|
|
314
318
|
return;
|
|
315
319
|
const baseCoinId = inputs[0].coinId;
|
|
@@ -318,11 +322,11 @@ function precomputeTransactionCoinID(inputs, outputs) {
|
|
|
318
322
|
`(placeholder?). Real coinId from CoinProof required for signing.`);
|
|
319
323
|
}
|
|
320
324
|
for (let i = 0; i < outputs.length; i++) {
|
|
321
|
-
const baseCoinIdStream = (
|
|
322
|
-
const outputIndexStream = (
|
|
325
|
+
const baseCoinIdStream = streamableWriteMiniData(baseCoinId);
|
|
326
|
+
const outputIndexStream = streamableWriteMiniNumber(BigInt(i));
|
|
323
327
|
const combined = new Uint8Array(baseCoinIdStream.length + outputIndexStream.length);
|
|
324
328
|
combined.set(baseCoinIdStream, 0);
|
|
325
329
|
combined.set(outputIndexStream, baseCoinIdStream.length);
|
|
326
|
-
outputs[i].coinId =
|
|
330
|
+
outputs[i].coinId = sha3_256(combined);
|
|
327
331
|
}
|
|
328
332
|
}
|
package/src/mmr.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare function serializeMMRProof(proof: {
|
|
|
7
7
|
};
|
|
8
8
|
}>;
|
|
9
9
|
}, blockTime?: bigint): Bytes;
|
|
10
|
+
export declare const serializeRealMMRProof: typeof serializeMMRProof;
|
|
10
11
|
export type Bytes = Uint8Array;
|
|
11
12
|
/**
|
|
12
13
|
* Byte-exact one-leaf MMR leaf used by Minima Address.java path:
|
|
@@ -147,13 +148,10 @@ export declare function verifyMMRProof(leafPubkey: Bytes, proof: MMRProof, expec
|
|
|
147
148
|
*
|
|
148
149
|
* @returns { proof: MMRProof, blockTime: bigint }
|
|
149
150
|
*/
|
|
150
|
-
export declare function deserializeMMRProof(data: Bytes): {
|
|
151
|
-
proof: MMRProof;
|
|
152
|
-
blockTime: bigint;
|
|
153
|
-
bytesRead: number;
|
|
154
|
-
};
|
|
155
151
|
export declare function parseMMRProofFromHex(data: Bytes): {
|
|
156
152
|
proof: MMRProof;
|
|
157
153
|
blockTime: bigint;
|
|
158
154
|
bytesRead: number;
|
|
159
155
|
};
|
|
156
|
+
/** @deprecated Use parseMMRProofFromHex */
|
|
157
|
+
export declare const deserializeMMRProof: typeof parseMMRProofFromHex;
|
package/src/mmr.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MMRTree = void 0;
|
|
4
|
-
exports.serializeMMRProof = serializeMMRProof;
|
|
5
|
-
exports.mmrLeafExact = mmrLeafExact;
|
|
6
|
-
exports.mmrRootFromSingleLeaf = mmrRootFromSingleLeaf;
|
|
7
|
-
exports.createMMRDataLeafNode = createMMRDataLeafNode;
|
|
8
|
-
exports.createMMRDataParentNode = createMMRDataParentNode;
|
|
9
|
-
exports.calculateProofRoot = calculateProofRoot;
|
|
10
|
-
exports.verifyMMRProof = verifyMMRProof;
|
|
11
|
-
exports.deserializeMMRProof = deserializeMMRProof;
|
|
12
|
-
exports.parseMMRProofFromHex = deserializeMMRProof;
|
|
13
1
|
// packages/totem-sdk/packages/core/src/mmr.ts
|
|
14
2
|
// Full MMR implementation matching Minima's MMR.java, MMRData.java, and TreeKeyNode.java
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
import { sha3_256 as nobleSha3 } from '@noble/hashes/sha3.js';
|
|
4
|
+
import { serializeMiniNumber, serializeMiniData, javaHashAllObjects } from "./javaStreamables.js";
|
|
17
5
|
// Import canonical wire serialization from Streamable.ts (single source of truth)
|
|
18
|
-
|
|
6
|
+
import { writeMMRProof } from "./Streamable.js";
|
|
19
7
|
// Re-export for API compatibility (existing code imports from mmr.ts)
|
|
20
8
|
// Using a wrapper function for better compatibility with dynamic imports
|
|
21
|
-
function serializeMMRProof(proof, blockTime = 0n) {
|
|
9
|
+
export function serializeMMRProof(proof, blockTime = 0n) {
|
|
22
10
|
// Convert to Streamable format and serialize
|
|
23
11
|
const streamableProof = {
|
|
24
12
|
blockTime: blockTime,
|
|
@@ -27,11 +15,12 @@ function serializeMMRProof(proof, blockTime = 0n) {
|
|
|
27
15
|
mmrData: { data: c.mmrData.data, value: c.mmrData.value }
|
|
28
16
|
}))
|
|
29
17
|
};
|
|
30
|
-
return
|
|
18
|
+
return writeMMRProof(streamableProof);
|
|
31
19
|
}
|
|
20
|
+
export const serializeRealMMRProof = serializeMMRProof;
|
|
32
21
|
/** SHA3-256 helper returning 32 bytes */
|
|
33
22
|
function sha3(data) {
|
|
34
|
-
return
|
|
23
|
+
return nobleSha3(data);
|
|
35
24
|
}
|
|
36
25
|
/** concat utility */
|
|
37
26
|
function concat(...parts) {
|
|
@@ -70,14 +59,14 @@ function encodeMiniStringUTF8(text) {
|
|
|
70
59
|
* Byte-exact one-leaf MMR leaf used by Minima Address.java path:
|
|
71
60
|
* sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
72
61
|
*/
|
|
73
|
-
function mmrLeafExact(script) {
|
|
62
|
+
export function mmrLeafExact(script) {
|
|
74
63
|
const zero = encodeMiniNumberZERO();
|
|
75
64
|
const mstr = encodeMiniStringUTF8(script);
|
|
76
65
|
const all = concat(zero, mstr, zero);
|
|
77
66
|
return sha3(all); // 32-byte leaf hash
|
|
78
67
|
}
|
|
79
68
|
/** In a single-leaf MMR the root equals the leaf commitment. */
|
|
80
|
-
function mmrRootFromSingleLeaf(script) {
|
|
69
|
+
export function mmrRootFromSingleLeaf(script) {
|
|
81
70
|
return mmrLeafExact(script);
|
|
82
71
|
}
|
|
83
72
|
/**
|
|
@@ -97,20 +86,20 @@ function mmrRootFromSingleLeaf(script) {
|
|
|
97
86
|
* 2. MiniData (pubkey): [4-byte length] + [bytes] (writeDataStream, NOT writeHashToStream)
|
|
98
87
|
* 3. MiniNumber.ZERO: [0x00, 0x01, 0x00]
|
|
99
88
|
*/
|
|
100
|
-
function createMMRDataLeafNode(pubkey, sumValue = 0n) {
|
|
89
|
+
export function createMMRDataLeafNode(pubkey, sumValue = 0n) {
|
|
101
90
|
// Use cached ZERO serialization for efficiency
|
|
102
91
|
const zero = new Uint8Array([0x00, 0x01, 0x00]); // MiniNumber.ZERO
|
|
103
92
|
// IMPORTANT: hashAllObjects uses writeDataStream which is 4-byte length prefixed
|
|
104
|
-
const pubkeySerialized =
|
|
93
|
+
const pubkeySerialized = serializeMiniData(pubkey);
|
|
105
94
|
// For sumValue, serialize as MiniNumber
|
|
106
95
|
let sumSerialized;
|
|
107
96
|
if (sumValue === 0n) {
|
|
108
97
|
sumSerialized = new Uint8Array([0x00, 0x01, 0x00]); // MiniNumber.ZERO
|
|
109
98
|
}
|
|
110
99
|
else {
|
|
111
|
-
sumSerialized =
|
|
100
|
+
sumSerialized = serializeMiniNumber(Number(sumValue));
|
|
112
101
|
}
|
|
113
|
-
const hash =
|
|
102
|
+
const hash = javaHashAllObjects(zero, pubkeySerialized, sumSerialized);
|
|
114
103
|
return { data: hash, value: sumValue };
|
|
115
104
|
}
|
|
116
105
|
/**
|
|
@@ -130,22 +119,22 @@ function createMMRDataLeafNode(pubkey, sumValue = 0n) {
|
|
|
130
119
|
* 3. MiniData (right.data): [4-byte length] + [bytes]
|
|
131
120
|
* 4. MiniNumber (sumvalue): serialized MiniNumber
|
|
132
121
|
*/
|
|
133
|
-
function createMMRDataParentNode(left, right) {
|
|
122
|
+
export function createMMRDataParentNode(left, right) {
|
|
134
123
|
const sumValue = left.value + right.value;
|
|
135
124
|
// MiniNumber.ONE: [0x00, 0x01, 0x01]
|
|
136
125
|
const one = new Uint8Array([0x00, 0x01, 0x01]);
|
|
137
126
|
// Serialize left and right data as MiniData (4-byte length-prefixed)
|
|
138
|
-
const leftDataSerialized =
|
|
139
|
-
const rightDataSerialized =
|
|
127
|
+
const leftDataSerialized = serializeMiniData(left.data);
|
|
128
|
+
const rightDataSerialized = serializeMiniData(right.data);
|
|
140
129
|
// Serialize sum value
|
|
141
130
|
let sumSerialized;
|
|
142
131
|
if (sumValue === 0n) {
|
|
143
132
|
sumSerialized = new Uint8Array([0x00, 0x01, 0x00]); // MiniNumber.ZERO
|
|
144
133
|
}
|
|
145
134
|
else {
|
|
146
|
-
sumSerialized =
|
|
135
|
+
sumSerialized = serializeMiniNumber(Number(sumValue));
|
|
147
136
|
}
|
|
148
|
-
const hash =
|
|
137
|
+
const hash = javaHashAllObjects(one, leftDataSerialized, rightDataSerialized, sumSerialized);
|
|
149
138
|
return { data: hash, value: sumValue };
|
|
150
139
|
}
|
|
151
140
|
/**
|
|
@@ -154,12 +143,10 @@ function createMMRDataParentNode(left, right) {
|
|
|
154
143
|
*
|
|
155
144
|
* This matches TreeKeyNode.java which always uses 64 leaves (2^6)
|
|
156
145
|
*/
|
|
157
|
-
class MMRTree {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.maxRow = 0;
|
|
162
|
-
}
|
|
146
|
+
export class MMRTree {
|
|
147
|
+
entries = new Map();
|
|
148
|
+
leafCount = 0;
|
|
149
|
+
maxRow = 0;
|
|
163
150
|
getKey(row, entryNumber) {
|
|
164
151
|
return `${row}:${entryNumber}`;
|
|
165
152
|
}
|
|
@@ -252,7 +239,6 @@ class MMRTree {
|
|
|
252
239
|
return entry?.mmrData || null;
|
|
253
240
|
}
|
|
254
241
|
}
|
|
255
|
-
exports.MMRTree = MMRTree;
|
|
256
242
|
/**
|
|
257
243
|
* Calculate root from leaf data and proof
|
|
258
244
|
* Matches SignatureProof.getRootPublicKey() in Java
|
|
@@ -261,7 +247,7 @@ exports.MMRTree = MMRTree;
|
|
|
261
247
|
* MMRData pubentry = MMRData.CreateMMRDataLeafNode(mPublicKey, MiniNumber.ZERO);
|
|
262
248
|
* return mProof.calculateProof(pubentry).getData();
|
|
263
249
|
*/
|
|
264
|
-
function calculateProofRoot(leafData, proof) {
|
|
250
|
+
export function calculateProofRoot(leafData, proof) {
|
|
265
251
|
let current = leafData;
|
|
266
252
|
for (const chunk of proof.chunks) {
|
|
267
253
|
if (chunk.isLeft) {
|
|
@@ -278,7 +264,7 @@ function calculateProofRoot(leafData, proof) {
|
|
|
278
264
|
/**
|
|
279
265
|
* Verify a proof: check that leaf + proof computes to expected root
|
|
280
266
|
*/
|
|
281
|
-
function verifyMMRProof(leafPubkey, proof, expectedRoot) {
|
|
267
|
+
export function verifyMMRProof(leafPubkey, proof, expectedRoot) {
|
|
282
268
|
const leafData = createMMRDataLeafNode(leafPubkey, 0n);
|
|
283
269
|
const computedRoot = calculateProofRoot(leafData, proof);
|
|
284
270
|
if (computedRoot.length !== expectedRoot.length)
|
|
@@ -304,7 +290,7 @@ function verifyMMRProof(leafPubkey, proof, expectedRoot) {
|
|
|
304
290
|
*
|
|
305
291
|
* @returns { proof: MMRProof, blockTime: bigint }
|
|
306
292
|
*/
|
|
307
|
-
function
|
|
293
|
+
export function parseMMRProofFromHex(data) {
|
|
308
294
|
let offset = 0;
|
|
309
295
|
// Read blockTime as MiniNumber
|
|
310
296
|
const { value: blockTime, bytesRead: btRead } = readMiniNumber(data, offset);
|
|
@@ -350,3 +336,5 @@ function readMiniNumber(data, offset) {
|
|
|
350
336
|
// For TreeKey MMR, scale is always 0
|
|
351
337
|
return { value, bytesRead: 2 + length };
|
|
352
338
|
}
|
|
339
|
+
/** @deprecated Use parseMMRProofFromHex */
|
|
340
|
+
export const deserializeMMRProof = parseMMRProofFromHex;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { MMRProofChunk, MMRProof } from '../mmr.js';
|
|
2
|
+
export type { MMRProof };
|
|
3
|
+
export type StateVariableType = 'STATE' | 'PREVSTATE' | 'SAMESTATE';
|
|
4
|
+
export interface StateValue {
|
|
5
|
+
port: number;
|
|
6
|
+
value: string | bigint | Uint8Array | boolean;
|
|
7
|
+
type: 'bool' | 'number' | 'hex' | 'string';
|
|
8
|
+
}
|
|
9
|
+
export interface VerifyOutExpectation {
|
|
10
|
+
inputIndex: number | '@INPUT';
|
|
11
|
+
outputAddress: string;
|
|
12
|
+
amount: string | bigint;
|
|
13
|
+
tokenId: string;
|
|
14
|
+
keepState: boolean;
|
|
15
|
+
}
|
|
16
|
+
export type ScriptType = 'signedby' | 'multisig' | 'multisig_mofn' | 'timelock' | 'htlc' | 'mast' | 'exchange' | 'vault' | 'flashcash' | 'slowcash' | 'stateful' | 'custom';
|
|
17
|
+
export interface ScriptDescriptor {
|
|
18
|
+
address: string;
|
|
19
|
+
scriptType: ScriptType;
|
|
20
|
+
script: string;
|
|
21
|
+
wotsRootPublicKey?: string;
|
|
22
|
+
mastProof?: MMRProof;
|
|
23
|
+
extraScripts?: Map<string, string>;
|
|
24
|
+
stateVariables?: StateValue[];
|
|
25
|
+
storeState?: boolean;
|
|
26
|
+
verifyOutExpectations?: VerifyOutExpectation[];
|
|
27
|
+
timelockBlock?: bigint;
|
|
28
|
+
htlcHash?: string;
|
|
29
|
+
htlcPreimage?: string;
|
|
30
|
+
multisigKeys?: string[];
|
|
31
|
+
multisigThreshold?: number;
|
|
32
|
+
externalSignatures?: ExternalSignature[];
|
|
33
|
+
}
|
|
34
|
+
export interface ExternalSignature {
|
|
35
|
+
publicKey: string;
|
|
36
|
+
signature: string;
|
|
37
|
+
proof?: MMRProof;
|
|
38
|
+
signatureType: 'wots' | 'standard';
|
|
39
|
+
validated?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ScriptCatalogEntry {
|
|
42
|
+
address: string;
|
|
43
|
+
script: string;
|
|
44
|
+
scriptType: ScriptType;
|
|
45
|
+
createdAt: number;
|
|
46
|
+
lastUsed: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ScriptProofResult {
|
|
49
|
+
script: string;
|
|
50
|
+
proof: MMRProof;
|
|
51
|
+
serialized: Uint8Array;
|
|
52
|
+
}
|
|
53
|
+
export interface TransactionRoundState {
|
|
54
|
+
round: number;
|
|
55
|
+
previousRound: number;
|
|
56
|
+
preservedPorts: number[];
|
|
57
|
+
newStates: StateValue[];
|
|
58
|
+
}
|
|
59
|
+
export interface FlatMMRProofChunk {
|
|
60
|
+
isLeft: boolean;
|
|
61
|
+
data: Uint8Array;
|
|
62
|
+
}
|
|
63
|
+
export interface LegacyMMRProof {
|
|
64
|
+
blockTime: bigint;
|
|
65
|
+
proofChain: FlatMMRProofChunk[];
|
|
66
|
+
}
|
|
67
|
+
export declare function convertFlatChunkToSDK(chunk: FlatMMRProofChunk): MMRProofChunk;
|
|
68
|
+
export declare function convertLegacyProofToSDK(legacy: LegacyMMRProof): {
|
|
69
|
+
proof: MMRProof;
|
|
70
|
+
blockTime: bigint;
|
|
71
|
+
};
|
|
72
|
+
export declare function createEmptyMMRProof(): MMRProof;
|
|
73
|
+
export declare function createSignedByDescriptor(address: string, wotsRootPublicKey: string): ScriptDescriptor;
|
|
74
|
+
export declare function createMultisigDescriptor(address: string, publicKey1: string, publicKey2: string, ownPublicKey: string): ScriptDescriptor;
|
|
75
|
+
export declare function createMofNMultisigDescriptor(address: string, threshold: number, publicKeys: string[], ownPublicKey: string): ScriptDescriptor;
|
|
76
|
+
export declare function createTimelockDescriptor(address: string, publicKey: string, unlockBlock: bigint): ScriptDescriptor;
|
|
77
|
+
export declare function createHTLCDescriptor(address: string, ownerPublicKey: string, recipientPublicKey: string, hashLock: string, timeoutBlock: bigint, isOwner: boolean, preimage?: string): ScriptDescriptor;
|
|
78
|
+
export declare function createMASTDescriptor(address: string, rootHash: string, branchScript: string, branchProof: string, wotsPublicKey?: string): ScriptDescriptor;
|
|
79
|
+
export declare function createExchangeDescriptor(address: string, ownerPublicKey: string, desiredAddress: string, desiredAmount: string, desiredTokenId: string): ScriptDescriptor;
|
|
80
|
+
export declare function createFlashCashDescriptor(address: string, ownerPublicKey: string, interestMultiplier?: string): ScriptDescriptor;
|
|
81
|
+
export declare function createSlowCashDescriptor(address: string, ownerPublicKey: string, withdrawalPercent?: string, cooldownBlocks?: bigint): ScriptDescriptor;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
function kissHex(hex) {
|
|
2
|
+
const raw = hex.startsWith('0x') || hex.startsWith('0X') ? hex.slice(2) : hex;
|
|
3
|
+
return '0x' + raw.toUpperCase();
|
|
4
|
+
}
|
|
5
|
+
export function convertFlatChunkToSDK(chunk) {
|
|
6
|
+
return {
|
|
7
|
+
isLeft: chunk.isLeft,
|
|
8
|
+
mmrData: { data: chunk.data, value: 0n }
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function convertLegacyProofToSDK(legacy) {
|
|
12
|
+
return {
|
|
13
|
+
proof: {
|
|
14
|
+
chunks: legacy.proofChain.map(convertFlatChunkToSDK)
|
|
15
|
+
},
|
|
16
|
+
blockTime: legacy.blockTime
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createEmptyMMRProof() {
|
|
20
|
+
return { chunks: [] };
|
|
21
|
+
}
|
|
22
|
+
export function createSignedByDescriptor(address, wotsRootPublicKey) {
|
|
23
|
+
return {
|
|
24
|
+
address,
|
|
25
|
+
scriptType: 'signedby',
|
|
26
|
+
script: `RETURN SIGNEDBY(${kissHex(wotsRootPublicKey)})`,
|
|
27
|
+
wotsRootPublicKey,
|
|
28
|
+
mastProof: createEmptyMMRProof(),
|
|
29
|
+
storeState: false
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createMultisigDescriptor(address, publicKey1, publicKey2, ownPublicKey) {
|
|
33
|
+
return {
|
|
34
|
+
address,
|
|
35
|
+
scriptType: 'multisig',
|
|
36
|
+
script: `RETURN SIGNEDBY(${kissHex(publicKey1)}) AND SIGNEDBY(${kissHex(publicKey2)})`,
|
|
37
|
+
wotsRootPublicKey: ownPublicKey,
|
|
38
|
+
multisigKeys: [publicKey1, publicKey2],
|
|
39
|
+
multisigThreshold: 2,
|
|
40
|
+
mastProof: createEmptyMMRProof(),
|
|
41
|
+
storeState: false
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function createMofNMultisigDescriptor(address, threshold, publicKeys, ownPublicKey) {
|
|
45
|
+
const formattedKeys = publicKeys.map(pk => kissHex(pk)).join(' ');
|
|
46
|
+
return {
|
|
47
|
+
address,
|
|
48
|
+
scriptType: 'multisig_mofn',
|
|
49
|
+
script: `RETURN MULTISIG(${threshold} ${formattedKeys})`,
|
|
50
|
+
wotsRootPublicKey: ownPublicKey,
|
|
51
|
+
multisigKeys: publicKeys,
|
|
52
|
+
multisigThreshold: threshold,
|
|
53
|
+
mastProof: createEmptyMMRProof(),
|
|
54
|
+
storeState: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function createTimelockDescriptor(address, publicKey, unlockBlock) {
|
|
58
|
+
return {
|
|
59
|
+
address,
|
|
60
|
+
scriptType: 'timelock',
|
|
61
|
+
script: `RETURN SIGNEDBY(${kissHex(publicKey)}) AND @BLOCK GT ${unlockBlock}`,
|
|
62
|
+
wotsRootPublicKey: publicKey,
|
|
63
|
+
timelockBlock: unlockBlock,
|
|
64
|
+
mastProof: createEmptyMMRProof(),
|
|
65
|
+
storeState: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function createHTLCDescriptor(address, ownerPublicKey, recipientPublicKey, hashLock, timeoutBlock, isOwner, preimage) {
|
|
69
|
+
const script = `IF @BLOCK GT ${timeoutBlock} AND SIGNEDBY(${kissHex(ownerPublicKey)}) THEN RETURN TRUE ENDIF RETURN (SIGNEDBY(${kissHex(recipientPublicKey)}) AND SHA3(STATE(1)) EQ ${kissHex(hashLock)})`;
|
|
70
|
+
const descriptor = {
|
|
71
|
+
address,
|
|
72
|
+
scriptType: 'htlc',
|
|
73
|
+
script,
|
|
74
|
+
wotsRootPublicKey: isOwner ? ownerPublicKey : recipientPublicKey,
|
|
75
|
+
htlcHash: hashLock,
|
|
76
|
+
timelockBlock: timeoutBlock,
|
|
77
|
+
mastProof: createEmptyMMRProof(),
|
|
78
|
+
storeState: false
|
|
79
|
+
};
|
|
80
|
+
if (preimage) {
|
|
81
|
+
descriptor.htlcPreimage = preimage;
|
|
82
|
+
descriptor.stateVariables = [{ port: 1, value: preimage, type: 'string' }];
|
|
83
|
+
}
|
|
84
|
+
return descriptor;
|
|
85
|
+
}
|
|
86
|
+
export function createMASTDescriptor(address, rootHash, branchScript, branchProof, wotsPublicKey) {
|
|
87
|
+
return {
|
|
88
|
+
address,
|
|
89
|
+
scriptType: 'mast',
|
|
90
|
+
script: `MAST ${kissHex(rootHash)}`,
|
|
91
|
+
wotsRootPublicKey: wotsPublicKey,
|
|
92
|
+
extraScripts: new Map([[branchScript, branchProof]]),
|
|
93
|
+
mastProof: createEmptyMMRProof(),
|
|
94
|
+
storeState: false
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function createExchangeDescriptor(address, ownerPublicKey, desiredAddress, desiredAmount, desiredTokenId) {
|
|
98
|
+
const ownerPk = kissHex(ownerPublicKey);
|
|
99
|
+
const script = `IF SIGNEDBY(PREVSTATE(0)) THEN RETURN TRUE ENDIF ASSERT VERIFYOUT(@INPUT PREVSTATE(1) PREVSTATE(2) PREVSTATE(3) TRUE) RETURN TRUE`;
|
|
100
|
+
return {
|
|
101
|
+
address,
|
|
102
|
+
scriptType: 'exchange',
|
|
103
|
+
script,
|
|
104
|
+
wotsRootPublicKey: ownerPublicKey,
|
|
105
|
+
stateVariables: [
|
|
106
|
+
{ port: 0, value: ownerPk, type: 'hex' },
|
|
107
|
+
{ port: 1, value: desiredAddress, type: 'hex' },
|
|
108
|
+
{ port: 2, value: desiredAmount, type: 'number' },
|
|
109
|
+
{ port: 3, value: desiredTokenId, type: 'hex' }
|
|
110
|
+
],
|
|
111
|
+
verifyOutExpectations: [{
|
|
112
|
+
inputIndex: '@INPUT',
|
|
113
|
+
outputAddress: desiredAddress,
|
|
114
|
+
amount: desiredAmount,
|
|
115
|
+
tokenId: desiredTokenId,
|
|
116
|
+
keepState: true
|
|
117
|
+
}],
|
|
118
|
+
mastProof: createEmptyMMRProof(),
|
|
119
|
+
storeState: true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function createFlashCashDescriptor(address, ownerPublicKey, interestMultiplier = '1.01') {
|
|
123
|
+
const script = `IF SIGNEDBY(PREVSTATE(1)) THEN RETURN TRUE ENDIF ASSERT SAMESTATE(1 1) RETURN VERIFYOUT(@INPUT @ADDRESS @AMOUNT*${interestMultiplier} @TOKENID TRUE)`;
|
|
124
|
+
return {
|
|
125
|
+
address,
|
|
126
|
+
scriptType: 'flashcash',
|
|
127
|
+
script,
|
|
128
|
+
wotsRootPublicKey: ownerPublicKey,
|
|
129
|
+
stateVariables: [{ port: 1, value: ownerPublicKey, type: 'hex' }],
|
|
130
|
+
mastProof: createEmptyMMRProof(),
|
|
131
|
+
storeState: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function createSlowCashDescriptor(address, ownerPublicKey, withdrawalPercent = '0.9', cooldownBlocks = 10000n) {
|
|
135
|
+
const script = `IF @COINAGE LT ${cooldownBlocks} THEN RETURN FALSE ENDIF ASSERT SIGNEDBY(${kissHex(ownerPublicKey)}) AND VERIFYOUT(@INPUT @ADDRESS @AMOUNT*${withdrawalPercent} @TOKENID TRUE)`;
|
|
136
|
+
return {
|
|
137
|
+
address,
|
|
138
|
+
scriptType: 'slowcash',
|
|
139
|
+
script,
|
|
140
|
+
wotsRootPublicKey: ownerPublicKey,
|
|
141
|
+
timelockBlock: cooldownBlocks,
|
|
142
|
+
mastProof: createEmptyMMRProof(),
|
|
143
|
+
storeState: false
|
|
144
|
+
};
|
|
145
|
+
}
|