@totemsdk/core 1.0.0

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.
Files changed (175) hide show
  1. package/dist/Streamable.d.ts +268 -0
  2. package/dist/Streamable.js +416 -0
  3. package/dist/adapters/index.d.ts +190 -0
  4. package/dist/adapters/index.js +107 -0
  5. package/dist/adapters/types.d.ts +149 -0
  6. package/dist/adapters/types.js +67 -0
  7. package/dist/binio.d.ts +9 -0
  8. package/dist/binio.js +30 -0
  9. package/dist/bip39.d.ts +91 -0
  10. package/dist/bip39.js +417 -0
  11. package/dist/derive.d.ts +2 -0
  12. package/dist/derive.js +9 -0
  13. package/dist/index.d.ts +30 -0
  14. package/dist/index.js +53 -0
  15. package/dist/javaStreamables.d.ts +233 -0
  16. package/dist/javaStreamables.js +332 -0
  17. package/dist/lease/LeaseMonitor.d.ts +44 -0
  18. package/dist/lease/LeaseMonitor.js +107 -0
  19. package/dist/lease/LeaseStore.d.ts +57 -0
  20. package/dist/lease/LeaseStore.js +144 -0
  21. package/dist/lease/WatermarkStore.d.ts +61 -0
  22. package/dist/lease/WatermarkStore.js +202 -0
  23. package/dist/lease/index.d.ts +14 -0
  24. package/dist/lease/index.js +13 -0
  25. package/dist/lease-client.d.ts +31 -0
  26. package/dist/lease-client.js +39 -0
  27. package/dist/minima32.d.ts +18 -0
  28. package/dist/minima32.js +112 -0
  29. package/dist/minimaWireSerializer.d.ts +60 -0
  30. package/dist/minimaWireSerializer.js +71 -0
  31. package/dist/mmr.d.ts +156 -0
  32. package/dist/mmr.js +342 -0
  33. package/dist/mx.d.ts +1 -0
  34. package/dist/mx.js +1 -0
  35. package/dist/params.d.ts +29 -0
  36. package/dist/params.js +16 -0
  37. package/dist/script.d.ts +7 -0
  38. package/dist/script.js +22 -0
  39. package/dist/scripts/contract-helpers.d.ts +252 -0
  40. package/dist/scripts/contract-helpers.js +394 -0
  41. package/dist/scripts/dapp-types.d.ts +119 -0
  42. package/dist/scripts/dapp-types.js +72 -0
  43. package/dist/scripts/index.d.ts +4 -0
  44. package/dist/scripts/index.js +4 -0
  45. package/dist/scripts/types.d.ts +81 -0
  46. package/dist/scripts/types.js +145 -0
  47. package/dist/scripts/witness-serializer.d.ts +29 -0
  48. package/dist/scripts/witness-serializer.js +288 -0
  49. package/dist/transaction.d.ts +106 -0
  50. package/dist/transaction.js +284 -0
  51. package/dist/treekey.d.ts +383 -0
  52. package/dist/treekey.js +762 -0
  53. package/dist/tx/TransactionLifecycle.d.ts +55 -0
  54. package/dist/tx/TransactionLifecycle.js +189 -0
  55. package/dist/tx/TransactionReceiptStore.d.ts +36 -0
  56. package/dist/tx/TransactionReceiptStore.js +85 -0
  57. package/dist/tx/TransactionService.d.ts +50 -0
  58. package/dist/tx/TransactionService.js +178 -0
  59. package/dist/tx/index.d.ts +13 -0
  60. package/dist/tx/index.js +12 -0
  61. package/dist/tx/types.d.ts +114 -0
  62. package/dist/tx/types.js +5 -0
  63. package/dist/util.d.ts +1 -0
  64. package/dist/util.js +1 -0
  65. package/dist/utils.d.ts +8 -0
  66. package/dist/utils.js +35 -0
  67. package/dist/verify.d.ts +18 -0
  68. package/dist/verify.js +162 -0
  69. package/dist/version.d.ts +12 -0
  70. package/dist/version.js +12 -0
  71. package/dist/wots.d.ts +232 -0
  72. package/dist/wots.js +538 -0
  73. package/package.json +65 -0
  74. package/src/Streamable.d.ts +265 -0
  75. package/src/Streamable.js +431 -0
  76. package/src/Streamable.ts +567 -0
  77. package/src/__tests__/Streamable.parity.test.ts +422 -0
  78. package/src/__tests__/perAddressDerivation.parity.test.ts +152 -0
  79. package/src/__tests__/wots-parity.test.ts +289 -0
  80. package/src/adapters/index.d.ts +190 -0
  81. package/src/adapters/index.js +117 -0
  82. package/src/adapters/index.ts +301 -0
  83. package/src/adapters/types.ts +235 -0
  84. package/src/addr.oracle.test.ts +25 -0
  85. package/src/address.test.ts +55 -0
  86. package/src/binio.ts +21 -0
  87. package/src/bip39.d.ts +91 -0
  88. package/src/bip39.js +427 -0
  89. package/src/bip39.ts +432 -0
  90. package/src/derive.d.ts +2 -0
  91. package/src/derive.js +13 -0
  92. package/src/derive.ts +11 -0
  93. package/src/index.d.ts +26 -0
  94. package/src/index.js +128 -0
  95. package/src/index.test.ts +5 -0
  96. package/src/index.ts +206 -0
  97. package/src/javaStreamables.d.ts +233 -0
  98. package/src/javaStreamables.js +328 -0
  99. package/src/javaStreamables.ts +403 -0
  100. package/src/lease/LeaseMonitor.d.ts +44 -0
  101. package/src/lease/LeaseMonitor.js +111 -0
  102. package/src/lease/LeaseMonitor.ts +157 -0
  103. package/src/lease/LeaseStore.d.ts +57 -0
  104. package/src/lease/LeaseStore.js +148 -0
  105. package/src/lease/LeaseStore.ts +203 -0
  106. package/src/lease/WatermarkStore.d.ts +61 -0
  107. package/src/lease/WatermarkStore.js +206 -0
  108. package/src/lease/WatermarkStore.ts +270 -0
  109. package/src/lease/index.d.ts +14 -0
  110. package/src/lease/index.js +22 -0
  111. package/src/lease/index.ts +41 -0
  112. package/src/lease-client.d.ts +31 -0
  113. package/src/lease-client.js +44 -0
  114. package/src/lease-client.ts +69 -0
  115. package/src/minima32.d.ts +10 -0
  116. package/src/minima32.js +113 -0
  117. package/src/minima32.test.ts +11 -0
  118. package/src/minima32.ts +127 -0
  119. package/src/minimaWireSerializer.d.ts +189 -0
  120. package/src/minimaWireSerializer.js +311 -0
  121. package/src/minimaWireSerializer.test.ts +466 -0
  122. package/src/minimaWireSerializer.ts +117 -0
  123. package/src/mmr.d.ts +153 -0
  124. package/src/mmr.js +351 -0
  125. package/src/mmr.oracle.test.ts +28 -0
  126. package/src/mmr.ts +429 -0
  127. package/src/mx.test.ts +25 -0
  128. package/src/mx.ts +1 -0
  129. package/src/params.d.ts +29 -0
  130. package/src/params.d.ts.map +1 -0
  131. package/src/params.js +20 -0
  132. package/src/params.js.map +1 -0
  133. package/src/params.ts +44 -0
  134. package/src/script.d.ts +7 -0
  135. package/src/script.js +27 -0
  136. package/src/script.ts +24 -0
  137. package/src/scripts/contract-helpers.ts +609 -0
  138. package/src/scripts/dapp-types.ts +240 -0
  139. package/src/scripts/index.ts +22 -0
  140. package/src/scripts/types.ts +247 -0
  141. package/src/scripts/witness-serializer.ts +384 -0
  142. package/src/transaction.ts +430 -0
  143. package/src/treekey.d.ts +383 -0
  144. package/src/treekey.js +778 -0
  145. package/src/treekey.parity.test.ts +459 -0
  146. package/src/treekey.ts +979 -0
  147. package/src/tx/TransactionLifecycle.d.ts +55 -0
  148. package/src/tx/TransactionLifecycle.js +188 -0
  149. package/src/tx/TransactionLifecycle.ts +280 -0
  150. package/src/tx/TransactionReceiptStore.d.ts +36 -0
  151. package/src/tx/TransactionReceiptStore.js +89 -0
  152. package/src/tx/TransactionReceiptStore.ts +112 -0
  153. package/src/tx/TransactionService.d.ts +31 -0
  154. package/src/tx/TransactionService.js +140 -0
  155. package/src/tx/TransactionService.ts +250 -0
  156. package/src/tx/index.d.ts +13 -0
  157. package/src/tx/index.js +20 -0
  158. package/src/tx/index.ts +44 -0
  159. package/src/tx/types.d.ts +80 -0
  160. package/src/tx/types.js +6 -0
  161. package/src/tx/types.ts +127 -0
  162. package/src/util.d.ts +1 -0
  163. package/src/util.js +5 -0
  164. package/src/util.ts +1 -0
  165. package/src/utils.d.ts +8 -0
  166. package/src/utils.js +42 -0
  167. package/src/utils.ts +38 -0
  168. package/src/verify.ts +209 -0
  169. package/src/version.ts +13 -0
  170. package/src/wots.d.ts +224 -0
  171. package/src/wots.d.ts.map +1 -0
  172. package/src/wots.js +559 -0
  173. package/src/wots.js.map +1 -0
  174. package/src/wots.ts +636 -0
  175. package/test-vectors.json +45 -0
package/src/mmr.d.ts ADDED
@@ -0,0 +1,153 @@
1
+ export declare function serializeMMRProof(proof: {
2
+ chunks: Array<{
3
+ isLeft: boolean;
4
+ mmrData: {
5
+ data: Bytes;
6
+ value: bigint;
7
+ };
8
+ }>;
9
+ }, blockTime?: bigint): Bytes;
10
+ export type Bytes = Uint8Array;
11
+ /**
12
+ * Byte-exact one-leaf MMR leaf used by Minima Address.java path:
13
+ * sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
14
+ */
15
+ export declare function mmrLeafExact(script: string): Bytes;
16
+ /** In a single-leaf MMR the root equals the leaf commitment. */
17
+ export declare function mmrRootFromSingleLeaf(script: string): Bytes;
18
+ /**
19
+ * MMRData structure matching Minima's MMRData.java
20
+ * Contains hash and value (for sum tree functionality)
21
+ */
22
+ export interface MMRData {
23
+ data: Bytes;
24
+ value: bigint;
25
+ }
26
+ /**
27
+ * MMREntry structure matching Minima's MMREntry.java
28
+ * Represents a node in the MMR at a specific row and position
29
+ */
30
+ export interface MMREntry {
31
+ row: number;
32
+ entryNumber: bigint;
33
+ mmrData: MMRData;
34
+ }
35
+ /**
36
+ * MMRProofChunk - one step in the proof path
37
+ * Matches Minima's MMRProof structure
38
+ */
39
+ export interface MMRProofChunk {
40
+ isLeft: boolean;
41
+ mmrData: MMRData;
42
+ }
43
+ /**
44
+ * MMRProof structure matching Minima's MMRProof.java
45
+ * Contains proof chunks to verify leaf membership in the tree
46
+ */
47
+ export interface MMRProof {
48
+ chunks: MMRProofChunk[];
49
+ }
50
+ /**
51
+ * Create MMRData leaf node matching Minima's MMRData.CreateMMRDataLeafNode
52
+ *
53
+ * From MMRData.java:
54
+ * MiniData hash = Crypto.getInstance().hashAllObjects(MiniNumber.ZERO, zData, zSumValue);
55
+ *
56
+ * CRITICAL: Crypto.hashAllObjects uses writeDataStream for Streamables:
57
+ * - MiniNumber: scale + len + data (see serializeMiniNumber)
58
+ * - MiniData: 4-byte length + data (see serializeMiniData)
59
+ *
60
+ * For TreeKeyNode, zData is the Winternitz public key (MiniData) and zSumValue is ZERO
61
+ *
62
+ * Serialization order:
63
+ * 1. MiniNumber.ZERO: [0x00, 0x01, 0x00]
64
+ * 2. MiniData (pubkey): [4-byte length] + [bytes] (writeDataStream, NOT writeHashToStream)
65
+ * 3. MiniNumber.ZERO: [0x00, 0x01, 0x00]
66
+ */
67
+ export declare function createMMRDataLeafNode(pubkey: Bytes, sumValue?: bigint): MMRData;
68
+ /**
69
+ * Create MMRData parent node matching Minima's MMRData.CreateMMRDataParentNode
70
+ *
71
+ * From MMRData.java:
72
+ * MiniNumber sumvalue = zLeft.getValue().add(zRight.getValue());
73
+ * MiniData combinedhash = Crypto.getInstance().hashAllObjects(
74
+ * MiniNumber.ONE, zLeft.getData(), zRight.getData(), sumvalue);
75
+ *
76
+ * CRITICAL: The getData() returns MiniData (the hash), which is serialized
77
+ * with writeDataStream (4-byte length) in hashAllObjects.
78
+ *
79
+ * Serialization order:
80
+ * 1. MiniNumber.ONE: [0x00, 0x01, 0x01]
81
+ * 2. MiniData (left.data): [4-byte length] + [bytes]
82
+ * 3. MiniData (right.data): [4-byte length] + [bytes]
83
+ * 4. MiniNumber (sumvalue): serialized MiniNumber
84
+ */
85
+ export declare function createMMRDataParentNode(left: MMRData, right: MMRData): MMRData;
86
+ /**
87
+ * Simple MMR Tree for TreeKeyNode
88
+ * Builds a perfect binary tree from N entries (N must be power of 2 for simplicity)
89
+ *
90
+ * This matches TreeKeyNode.java which always uses 64 leaves (2^6)
91
+ */
92
+ export declare class MMRTree {
93
+ private entries;
94
+ private leafCount;
95
+ private maxRow;
96
+ private getKey;
97
+ private setEntry;
98
+ private getEntry;
99
+ /**
100
+ * Add a leaf entry to the MMR
101
+ * Matches MMR.java addEntry() but simplified for power-of-2 trees
102
+ */
103
+ addLeaf(data: MMRData): MMREntry;
104
+ /**
105
+ * Build tree from array of Winternitz public keys
106
+ * Used by TreeKeyNode to compute wallet public key
107
+ */
108
+ static fromPublicKeys(pubkeys: Bytes[]): MMRTree;
109
+ /**
110
+ * Get the root of the tree
111
+ * For a perfect binary tree with N leaves, root is at row log2(N), entry 0
112
+ */
113
+ getRoot(): MMRData | null;
114
+ /**
115
+ * Get proof for a leaf at given index
116
+ * Matches MMR.java getProofToPeak()
117
+ */
118
+ getProof(leafIndex: number): MMRProof;
119
+ /**
120
+ * Get the leaf MMRData at a specific index
121
+ */
122
+ getLeaf(index: number): MMRData | null;
123
+ }
124
+ /**
125
+ * Calculate root from leaf data and proof
126
+ * Matches SignatureProof.getRootPublicKey() in Java
127
+ *
128
+ * From SignatureProof.java:
129
+ * MMRData pubentry = MMRData.CreateMMRDataLeafNode(mPublicKey, MiniNumber.ZERO);
130
+ * return mProof.calculateProof(pubentry).getData();
131
+ */
132
+ export declare function calculateProofRoot(leafData: MMRData, proof: MMRProof): Bytes;
133
+ /**
134
+ * Verify a proof: check that leaf + proof computes to expected root
135
+ */
136
+ export declare function verifyMMRProof(leafPubkey: Bytes, proof: MMRProof, expectedRoot: Bytes): boolean;
137
+ /**
138
+ * Deserialize MMRProof from bytes matching Minima's MMRProof.readDataStream()
139
+ *
140
+ * Format:
141
+ * 1. blockTime (MiniNumber)
142
+ * 2. chain length (MiniNumber)
143
+ * 3. Each chunk: isLeft (1 byte) + MMRData (hash with 4-byte length prefix + value MiniNumber)
144
+ *
145
+ * CRITICAL: Java MMRData.readDataStream uses mData.readHashFromStream() which reads
146
+ * a 4-byte big-endian length prefix followed by the hash bytes.
147
+ *
148
+ * @returns { proof: MMRProof, blockTime: bigint }
149
+ */
150
+ export declare function deserializeMMRProof(data: Bytes): {
151
+ proof: MMRProof;
152
+ blockTime: bigint;
153
+ };
package/src/mmr.js ADDED
@@ -0,0 +1,351 @@
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
+ // packages/totem-sdk/packages/core/src/mmr.ts
13
+ // Full MMR implementation matching Minima's MMR.java, MMRData.java, and TreeKeyNode.java
14
+ const sha3_1 = require("@noble/hashes/sha3");
15
+ const javaStreamables_1 = require("./javaStreamables");
16
+ // Import canonical wire serialization from Streamable.ts (single source of truth)
17
+ const Streamable_1 = require("./Streamable");
18
+ // Re-export for API compatibility (existing code imports from mmr.ts)
19
+ // Using a wrapper function for better compatibility with dynamic imports
20
+ function serializeMMRProof(proof, blockTime = 0n) {
21
+ // Convert to Streamable format and serialize
22
+ const streamableProof = {
23
+ blockTime: blockTime,
24
+ chunks: proof.chunks.map(c => ({
25
+ isLeft: c.isLeft,
26
+ mmrData: { data: c.mmrData.data, value: c.mmrData.value }
27
+ }))
28
+ };
29
+ return (0, Streamable_1.writeMMRProof)(streamableProof);
30
+ }
31
+ /** SHA3-256 helper returning 32 bytes */
32
+ function sha3(data) {
33
+ return sha3_1.sha3_256(data);
34
+ }
35
+ /** concat utility */
36
+ function concat(...parts) {
37
+ const total = parts.reduce((n, p) => n + p.length, 0);
38
+ const out = new Uint8Array(total);
39
+ let off = 0;
40
+ for (const p of parts) {
41
+ out.set(p, off);
42
+ off += p.length;
43
+ }
44
+ return out;
45
+ }
46
+ /** big-endian u32 */
47
+ const u32be = (n) => new Uint8Array([(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]);
48
+ /**
49
+ * EXACT MiniNumber.ZERO wire format from Java:
50
+ * scale (1 byte) = 0x00
51
+ * length (1 byte) = 0x01
52
+ * unscaled bytes (length=1) = 0x00
53
+ * => 00 01 00
54
+ */
55
+ function encodeMiniNumberZERO() {
56
+ return new Uint8Array([0x00, 0x01, 0x00]);
57
+ }
58
+ /**
59
+ * EXACT MiniString wire format (MiniData of UTF-8):
60
+ * 4-byte big-endian length
61
+ * raw UTF-8 bytes
62
+ */
63
+ function encodeMiniStringUTF8(text) {
64
+ const utf8 = new TextEncoder().encode(text);
65
+ const len = u32be(utf8.length);
66
+ return concat(len, utf8);
67
+ }
68
+ /**
69
+ * Byte-exact one-leaf MMR leaf used by Minima Address.java path:
70
+ * sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
71
+ */
72
+ function mmrLeafExact(script) {
73
+ const zero = encodeMiniNumberZERO();
74
+ const mstr = encodeMiniStringUTF8(script);
75
+ const all = concat(zero, mstr, zero);
76
+ return sha3(all); // 32-byte leaf hash
77
+ }
78
+ /** In a single-leaf MMR the root equals the leaf commitment. */
79
+ function mmrRootFromSingleLeaf(script) {
80
+ return mmrLeafExact(script);
81
+ }
82
+ /**
83
+ * Create MMRData leaf node matching Minima's MMRData.CreateMMRDataLeafNode
84
+ *
85
+ * From MMRData.java:
86
+ * MiniData hash = Crypto.getInstance().hashAllObjects(MiniNumber.ZERO, zData, zSumValue);
87
+ *
88
+ * CRITICAL: Crypto.hashAllObjects uses writeDataStream for Streamables:
89
+ * - MiniNumber: scale + len + data (see serializeMiniNumber)
90
+ * - MiniData: 4-byte length + data (see serializeMiniData)
91
+ *
92
+ * For TreeKeyNode, zData is the Winternitz public key (MiniData) and zSumValue is ZERO
93
+ *
94
+ * Serialization order:
95
+ * 1. MiniNumber.ZERO: [0x00, 0x01, 0x00]
96
+ * 2. MiniData (pubkey): [4-byte length] + [bytes] (writeDataStream, NOT writeHashToStream)
97
+ * 3. MiniNumber.ZERO: [0x00, 0x01, 0x00]
98
+ */
99
+ function createMMRDataLeafNode(pubkey, sumValue = 0n) {
100
+ // Use cached ZERO serialization for efficiency
101
+ const zero = new Uint8Array([0x00, 0x01, 0x00]); // MiniNumber.ZERO
102
+ // IMPORTANT: hashAllObjects uses writeDataStream which is 4-byte length prefixed
103
+ const pubkeySerialized = (0, javaStreamables_1.serializeMiniData)(pubkey);
104
+ // For sumValue, serialize as MiniNumber
105
+ let sumSerialized;
106
+ if (sumValue === 0n) {
107
+ sumSerialized = new Uint8Array([0x00, 0x01, 0x00]); // MiniNumber.ZERO
108
+ }
109
+ else {
110
+ sumSerialized = (0, javaStreamables_1.serializeMiniNumber)(Number(sumValue));
111
+ }
112
+ const hash = (0, javaStreamables_1.javaHashAllObjects)(zero, pubkeySerialized, sumSerialized);
113
+ return { data: hash, value: sumValue };
114
+ }
115
+ /**
116
+ * Create MMRData parent node matching Minima's MMRData.CreateMMRDataParentNode
117
+ *
118
+ * From MMRData.java:
119
+ * MiniNumber sumvalue = zLeft.getValue().add(zRight.getValue());
120
+ * MiniData combinedhash = Crypto.getInstance().hashAllObjects(
121
+ * MiniNumber.ONE, zLeft.getData(), zRight.getData(), sumvalue);
122
+ *
123
+ * CRITICAL: The getData() returns MiniData (the hash), which is serialized
124
+ * with writeDataStream (4-byte length) in hashAllObjects.
125
+ *
126
+ * Serialization order:
127
+ * 1. MiniNumber.ONE: [0x00, 0x01, 0x01]
128
+ * 2. MiniData (left.data): [4-byte length] + [bytes]
129
+ * 3. MiniData (right.data): [4-byte length] + [bytes]
130
+ * 4. MiniNumber (sumvalue): serialized MiniNumber
131
+ */
132
+ function createMMRDataParentNode(left, right) {
133
+ const sumValue = left.value + right.value;
134
+ // MiniNumber.ONE: [0x00, 0x01, 0x01]
135
+ const one = new Uint8Array([0x00, 0x01, 0x01]);
136
+ // Serialize left and right data as MiniData (4-byte length-prefixed)
137
+ const leftDataSerialized = (0, javaStreamables_1.serializeMiniData)(left.data);
138
+ const rightDataSerialized = (0, javaStreamables_1.serializeMiniData)(right.data);
139
+ // Serialize sum value
140
+ let sumSerialized;
141
+ if (sumValue === 0n) {
142
+ sumSerialized = new Uint8Array([0x00, 0x01, 0x00]); // MiniNumber.ZERO
143
+ }
144
+ else {
145
+ sumSerialized = (0, javaStreamables_1.serializeMiniNumber)(Number(sumValue));
146
+ }
147
+ const hash = (0, javaStreamables_1.javaHashAllObjects)(one, leftDataSerialized, rightDataSerialized, sumSerialized);
148
+ return { data: hash, value: sumValue };
149
+ }
150
+ /**
151
+ * Simple MMR Tree for TreeKeyNode
152
+ * Builds a perfect binary tree from N entries (N must be power of 2 for simplicity)
153
+ *
154
+ * This matches TreeKeyNode.java which always uses 64 leaves (2^6)
155
+ */
156
+ class MMRTree {
157
+ constructor() {
158
+ this.entries = new Map();
159
+ this.leafCount = 0;
160
+ this.maxRow = 0;
161
+ }
162
+ getKey(row, entryNumber) {
163
+ return `${row}:${entryNumber}`;
164
+ }
165
+ setEntry(row, entryNumber, data) {
166
+ const entry = { row, entryNumber, mmrData: data };
167
+ this.entries.set(this.getKey(row, entryNumber), entry);
168
+ if (row > this.maxRow)
169
+ this.maxRow = row;
170
+ return entry;
171
+ }
172
+ getEntry(row, entryNumber) {
173
+ return this.entries.get(this.getKey(row, entryNumber));
174
+ }
175
+ /**
176
+ * Add a leaf entry to the MMR
177
+ * Matches MMR.java addEntry() but simplified for power-of-2 trees
178
+ */
179
+ addLeaf(data) {
180
+ const entryNumber = BigInt(this.leafCount);
181
+ const entry = this.setEntry(0, entryNumber, data);
182
+ this.leafCount++;
183
+ // Propagate up the tree
184
+ let current = entry;
185
+ while (current.entryNumber % 2n === 1n) { // Is right child
186
+ const siblingNumber = current.entryNumber - 1n;
187
+ const sibling = this.getEntry(current.row, siblingNumber);
188
+ if (!sibling)
189
+ break;
190
+ // Create parent node
191
+ const parentData = createMMRDataParentNode(sibling.mmrData, current.mmrData);
192
+ const parentRow = current.row + 1;
193
+ const parentEntry = current.entryNumber / 2n;
194
+ current = this.setEntry(parentRow, parentEntry, parentData);
195
+ }
196
+ return entry;
197
+ }
198
+ /**
199
+ * Build tree from array of Winternitz public keys
200
+ * Used by TreeKeyNode to compute wallet public key
201
+ */
202
+ static fromPublicKeys(pubkeys) {
203
+ const tree = new MMRTree();
204
+ for (const pk of pubkeys) {
205
+ const leafData = createMMRDataLeafNode(pk, 0n);
206
+ tree.addLeaf(leafData);
207
+ }
208
+ return tree;
209
+ }
210
+ /**
211
+ * Get the root of the tree
212
+ * For a perfect binary tree with N leaves, root is at row log2(N), entry 0
213
+ */
214
+ getRoot() {
215
+ if (this.leafCount === 0)
216
+ return null;
217
+ // For power of 2 leaves, there's exactly one peak at the top
218
+ const topRow = Math.floor(Math.log2(this.leafCount));
219
+ const root = this.getEntry(topRow, 0n);
220
+ return root?.mmrData || null;
221
+ }
222
+ /**
223
+ * Get proof for a leaf at given index
224
+ * Matches MMR.java getProofToPeak()
225
+ */
226
+ getProof(leafIndex) {
227
+ const chunks = [];
228
+ let row = 0;
229
+ let entryNumber = BigInt(leafIndex);
230
+ // Walk up the tree collecting siblings
231
+ while (row < this.maxRow) {
232
+ const siblingNumber = entryNumber % 2n === 0n
233
+ ? entryNumber + 1n // Current is left, sibling is right
234
+ : entryNumber - 1n; // Current is right, sibling is left
235
+ const sibling = this.getEntry(row, siblingNumber);
236
+ if (!sibling)
237
+ break;
238
+ const isLeft = siblingNumber < entryNumber;
239
+ chunks.push({ isLeft, mmrData: sibling.mmrData });
240
+ // Move to parent
241
+ row++;
242
+ entryNumber = entryNumber / 2n;
243
+ }
244
+ return { chunks };
245
+ }
246
+ /**
247
+ * Get the leaf MMRData at a specific index
248
+ */
249
+ getLeaf(index) {
250
+ const entry = this.getEntry(0, BigInt(index));
251
+ return entry?.mmrData || null;
252
+ }
253
+ }
254
+ exports.MMRTree = MMRTree;
255
+ /**
256
+ * Calculate root from leaf data and proof
257
+ * Matches SignatureProof.getRootPublicKey() in Java
258
+ *
259
+ * From SignatureProof.java:
260
+ * MMRData pubentry = MMRData.CreateMMRDataLeafNode(mPublicKey, MiniNumber.ZERO);
261
+ * return mProof.calculateProof(pubentry).getData();
262
+ */
263
+ function calculateProofRoot(leafData, proof) {
264
+ let current = leafData;
265
+ for (const chunk of proof.chunks) {
266
+ if (chunk.isLeft) {
267
+ // Sibling is on the left, current is on the right
268
+ current = createMMRDataParentNode(chunk.mmrData, current);
269
+ }
270
+ else {
271
+ // Sibling is on the right, current is on the left
272
+ current = createMMRDataParentNode(current, chunk.mmrData);
273
+ }
274
+ }
275
+ return current.data;
276
+ }
277
+ /**
278
+ * Verify a proof: check that leaf + proof computes to expected root
279
+ */
280
+ function verifyMMRProof(leafPubkey, proof, expectedRoot) {
281
+ const leafData = createMMRDataLeafNode(leafPubkey, 0n);
282
+ const computedRoot = calculateProofRoot(leafData, proof);
283
+ if (computedRoot.length !== expectedRoot.length)
284
+ return false;
285
+ for (let i = 0; i < computedRoot.length; i++) {
286
+ if (computedRoot[i] !== expectedRoot[i])
287
+ return false;
288
+ }
289
+ return true;
290
+ }
291
+ // NOTE: serializeMMRProof is now imported from minimaWireSerializer.ts and re-exported above
292
+ // This consolidates wire format serialization into a single source of truth.
293
+ /**
294
+ * Deserialize MMRProof from bytes matching Minima's MMRProof.readDataStream()
295
+ *
296
+ * Format:
297
+ * 1. blockTime (MiniNumber)
298
+ * 2. chain length (MiniNumber)
299
+ * 3. Each chunk: isLeft (1 byte) + MMRData (hash with 4-byte length prefix + value MiniNumber)
300
+ *
301
+ * CRITICAL: Java MMRData.readDataStream uses mData.readHashFromStream() which reads
302
+ * a 4-byte big-endian length prefix followed by the hash bytes.
303
+ *
304
+ * @returns { proof: MMRProof, blockTime: bigint }
305
+ */
306
+ function deserializeMMRProof(data) {
307
+ let offset = 0;
308
+ // Read blockTime as MiniNumber
309
+ const { value: blockTime, bytesRead: btRead } = readMiniNumber(data, offset);
310
+ offset += btRead;
311
+ // Read chain length as MiniNumber
312
+ const { value: numChunks, bytesRead: ncRead } = readMiniNumber(data, offset);
313
+ offset += ncRead;
314
+ const chunks = [];
315
+ for (let i = 0; i < Number(numChunks); i++) {
316
+ // isLeft flag (1 byte)
317
+ const isLeft = data[offset] === 1;
318
+ offset += 1;
319
+ // MMRData: hash (4-byte length prefix + hash bytes) + value (MiniNumber)
320
+ // Read 4-byte big-endian length
321
+ const hashLength = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
322
+ offset += 4;
323
+ const hashData = data.slice(offset, offset + hashLength);
324
+ offset += hashLength;
325
+ const { value: chunkValue, bytesRead: cvRead } = readMiniNumber(data, offset);
326
+ offset += cvRead;
327
+ chunks.push({ isLeft, mmrData: { data: hashData, value: chunkValue } });
328
+ }
329
+ return { proof: { chunks }, blockTime };
330
+ }
331
+ /**
332
+ * Read a MiniNumber from bytes at the given offset
333
+ * Format: [scale: 1 byte] [length: 1 byte] [data: N bytes]
334
+ */
335
+ function readMiniNumber(data, offset) {
336
+ const scale = data[offset];
337
+ const length = data[offset + 1];
338
+ if (length === 0) {
339
+ // Zero value (shouldn't happen with proper serialization)
340
+ return { value: 0n, bytesRead: 2 };
341
+ }
342
+ const dataBytes = data.slice(offset + 2, offset + 2 + length);
343
+ // Convert bytes to bigint
344
+ let value = 0n;
345
+ for (let i = 0; i < dataBytes.length; i++) {
346
+ value = (value << 8n) | BigInt(dataBytes[i]);
347
+ }
348
+ // Apply scale if non-zero (for decimal numbers)
349
+ // For TreeKey MMR, scale is always 0
350
+ return { value, bytesRead: 2 + length };
351
+ }
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mmrRootFromSingleLeaf } from "./mmr";
3
+ import { bytesToHex } from "@noble/hashes/utils";
4
+
5
+ const enc = (s: string) => new TextEncoder().encode(s);
6
+
7
+ describe("MMR roots (single-leaf) – Minima oracles", () => {
8
+ it("RETURN TRUE", () => {
9
+ const root = mmrRootFromSingleLeaf("RETURN TRUE");
10
+ expect(bytesToHex(root).toUpperCase()).toBe(
11
+ "582AA40EA996419A3D8A5D26039A4E9584B746C2C26F860DAC8372BEA4457AF6"
12
+ );
13
+ });
14
+
15
+ it("LET x 1 RETURN x == 1", () => {
16
+ const root = mmrRootFromSingleLeaf("LET x 1 RETURN x == 1");
17
+ expect(bytesToHex(root).toUpperCase()).toBe(
18
+ "B3FA26AEA3FFD4367C23DFFEB55C17BE75AE4A923E4A3A9E790CFEFC5A96FB93"
19
+ );
20
+ });
21
+
22
+ it("RETURN SIGNEDBY(0xFFFF)", () => {
23
+ const root = mmrRootFromSingleLeaf("RETURN SIGNEDBY(0xFFFF)");
24
+ expect(bytesToHex(root).toUpperCase()).toBe(
25
+ "65EC70E6DEF38CBC8E1556305F06637BF8D6B0EF5719C3CD9386981D250B9E95"
26
+ );
27
+ });
28
+ });