@totemsdk/core 1.0.8 → 1.0.10

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 (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -5
  3. package/dist/Streamable.js +54 -31
  4. package/dist/adapters/index.js +18 -8
  5. package/dist/adapters/types.js +19 -8
  6. package/dist/binio.js +5 -1
  7. package/dist/bip39.js +24 -14
  8. package/dist/derive.js +11 -7
  9. package/dist/index.js +130 -22
  10. package/dist/javaStreamables.js +49 -30
  11. package/dist/lease/LeaseMonitor.js +7 -3
  12. package/dist/lease/LeaseStore.js +7 -3
  13. package/dist/lease/WatermarkStore.js +7 -3
  14. package/dist/lease/index.js +13 -4
  15. package/dist/lease-client.js +8 -3
  16. package/dist/minima32.js +22 -13
  17. package/dist/minimaWireSerializer.js +20 -14
  18. package/dist/mmr.js +35 -23
  19. package/dist/mx.js +10 -1
  20. package/dist/params.js +9 -5
  21. package/dist/script.js +12 -7
  22. package/dist/scripts/contract-helpers.js +37 -26
  23. package/dist/scripts/dapp-types.js +11 -6
  24. package/dist/scripts/index.js +38 -4
  25. package/dist/scripts/types.js +26 -12
  26. package/dist/scripts/witness-serializer.js +48 -30
  27. package/dist/transaction.js +55 -46
  28. package/dist/treekey.js +74 -58
  29. package/dist/tx/TransactionLifecycle.js +11 -5
  30. package/dist/tx/TransactionReceiptStore.js +7 -3
  31. package/dist/tx/TransactionService.js +14 -10
  32. package/dist/tx/index.js +11 -3
  33. package/dist/tx/types.js +2 -1
  34. package/dist/util.js +5 -1
  35. package/dist/utils.js +12 -5
  36. package/dist/verify.js +36 -25
  37. package/dist/version.js +5 -2
  38. package/dist/wots.js +94 -64
  39. package/package.json +35 -6
  40. package/src/Streamable.d.ts +0 -265
  41. package/src/Streamable.js +0 -431
  42. package/src/Streamable.ts +0 -567
  43. package/src/__tests__/Streamable.parity.test.ts +0 -422
  44. package/src/__tests__/perAddressDerivation.parity.test.ts +0 -152
  45. package/src/__tests__/roottree-prepare.test.ts +0 -101
  46. package/src/__tests__/wots-parity.test.ts +0 -289
  47. package/src/adapters/index.d.ts +0 -190
  48. package/src/adapters/index.js +0 -117
  49. package/src/adapters/index.ts +0 -301
  50. package/src/adapters/types.ts +0 -235
  51. package/src/addr.oracle.test.ts +0 -25
  52. package/src/address.test.ts +0 -55
  53. package/src/binio.ts +0 -21
  54. package/src/bip39.d.ts +0 -91
  55. package/src/bip39.js +0 -427
  56. package/src/bip39.ts +0 -432
  57. package/src/derive.d.ts +0 -2
  58. package/src/derive.js +0 -13
  59. package/src/derive.ts +0 -11
  60. package/src/index.d.ts +0 -26
  61. package/src/index.js +0 -128
  62. package/src/index.test.ts +0 -5
  63. package/src/index.ts +0 -221
  64. package/src/javaStreamables.d.ts +0 -233
  65. package/src/javaStreamables.js +0 -328
  66. package/src/javaStreamables.ts +0 -403
  67. package/src/lease/LeaseMonitor.d.ts +0 -44
  68. package/src/lease/LeaseMonitor.js +0 -111
  69. package/src/lease/LeaseMonitor.ts +0 -157
  70. package/src/lease/LeaseStore.d.ts +0 -57
  71. package/src/lease/LeaseStore.js +0 -148
  72. package/src/lease/LeaseStore.ts +0 -203
  73. package/src/lease/WatermarkStore.d.ts +0 -61
  74. package/src/lease/WatermarkStore.js +0 -206
  75. package/src/lease/WatermarkStore.ts +0 -270
  76. package/src/lease/index.d.ts +0 -14
  77. package/src/lease/index.js +0 -22
  78. package/src/lease/index.ts +0 -41
  79. package/src/lease-client.d.ts +0 -31
  80. package/src/lease-client.js +0 -44
  81. package/src/lease-client.ts +0 -69
  82. package/src/minima32.d.ts +0 -10
  83. package/src/minima32.js +0 -113
  84. package/src/minima32.test.ts +0 -11
  85. package/src/minima32.ts +0 -127
  86. package/src/minimaWireSerializer.d.ts +0 -189
  87. package/src/minimaWireSerializer.js +0 -311
  88. package/src/minimaWireSerializer.test.ts +0 -466
  89. package/src/minimaWireSerializer.ts +0 -117
  90. package/src/mmr.d.ts +0 -159
  91. package/src/mmr.js +0 -352
  92. package/src/mmr.oracle.test.ts +0 -28
  93. package/src/mmr.ts +0 -429
  94. package/src/mx.test.ts +0 -25
  95. package/src/mx.ts +0 -1
  96. package/src/params.d.ts +0 -29
  97. package/src/params.d.ts.map +0 -1
  98. package/src/params.js +0 -20
  99. package/src/params.js.map +0 -1
  100. package/src/params.ts +0 -44
  101. package/src/script.d.ts +0 -7
  102. package/src/script.js +0 -27
  103. package/src/script.ts +0 -24
  104. package/src/scripts/contract-helpers.ts +0 -609
  105. package/src/scripts/dapp-types.ts +0 -240
  106. package/src/scripts/index.ts +0 -22
  107. package/src/scripts/types.ts +0 -247
  108. package/src/scripts/witness-serializer.ts +0 -384
  109. package/src/transaction.ts +0 -430
  110. package/src/treekey.d.ts +0 -383
  111. package/src/treekey.js +0 -774
  112. package/src/treekey.parity.test.ts +0 -459
  113. package/src/treekey.ts +0 -975
  114. package/src/tx/TransactionLifecycle.d.ts +0 -55
  115. package/src/tx/TransactionLifecycle.js +0 -188
  116. package/src/tx/TransactionLifecycle.ts +0 -280
  117. package/src/tx/TransactionReceiptStore.d.ts +0 -36
  118. package/src/tx/TransactionReceiptStore.js +0 -89
  119. package/src/tx/TransactionReceiptStore.ts +0 -112
  120. package/src/tx/TransactionService.d.ts +0 -31
  121. package/src/tx/TransactionService.js +0 -140
  122. package/src/tx/TransactionService.ts +0 -252
  123. package/src/tx/index.d.ts +0 -13
  124. package/src/tx/index.js +0 -20
  125. package/src/tx/index.ts +0 -44
  126. package/src/tx/types.d.ts +0 -80
  127. package/src/tx/types.js +0 -6
  128. package/src/tx/types.ts +0 -130
  129. package/src/util.d.ts +0 -1
  130. package/src/util.js +0 -5
  131. package/src/util.ts +0 -1
  132. package/src/utils.d.ts +0 -8
  133. package/src/utils.js +0 -42
  134. package/src/utils.ts +0 -38
  135. package/src/verify.ts +0 -210
  136. package/src/version.ts +0 -13
  137. package/src/wots.d.ts +0 -224
  138. package/src/wots.d.ts.map +0 -1
  139. package/src/wots.js +0 -565
  140. package/src/wots.js.map +0 -1
  141. package/src/wots.ts +0 -651
  142. package/test-vectors.json +0 -45
package/src/treekey.ts DELETED
@@ -1,975 +0,0 @@
1
- /**
2
- * TreeKey/TreeKeyNode Implementation matching Minima's TreeKey.java and TreeKeyNode.java
3
- *
4
- * This implements the hierarchical key tree structure where:
5
- * - Each TreeKeyNode contains 64 Winternitz keys
6
- * - The node's PUBLIC KEY = MMR root of all 64 Winternitz public keys
7
- * - Signatures include the Winternitz leaf pubkey + signature + MMR proof
8
- *
9
- * Default structure: 3 levels x 64 keys = 64^3 = 262,144 one-time signatures
10
- */
11
-
12
- import { sha3_256 } from '@noble/hashes/sha3.js';
13
- import { deriveChainSeedJava, hashObject, serializeMiniNumber, serializeMiniData, derivePerAddressSeed } from './javaStreamables.js';
14
- import { derivePKdigest, deriveFullPublicKey, wotsSign, wotsVerifyDigest, hex } from './wots.js';
15
- import { getParamSet } from './params.js';
16
- import {
17
- MMRTree,
18
- MMRProof,
19
- createMMRDataLeafNode,
20
- calculateProofRoot,
21
- serializeMMRProof,
22
- parseMMRProofFromHex,
23
- Bytes
24
- } from './mmr.js';
25
- import {
26
- writeSignature,
27
- type Signature as StreamableSignature,
28
- type SignatureProof as StreamableSignatureProof
29
- } from './Streamable.js';
30
- import type { LoggerAdapter } from './adapters/index.js';
31
- import { NoopLogger } from './adapters/index.js';
32
-
33
- // Module-level debug logger - can be set externally for parity testing
34
- let treeKeyLogger: LoggerAdapter = new NoopLogger();
35
- let treeKeyDebugEnabled = false;
36
-
37
- /**
38
- * Enable TreeKey debug logging with a custom logger
39
- * Use this for parity testing to capture wallet init intermediate values
40
- *
41
- * WARNING: Debug logging outputs sensitive cryptographic material including
42
- * seeds and private key derivation. NEVER enable in production builds.
43
- * This is intended for development/testing parity verification only.
44
- */
45
- export function setTreeKeyLogger(logger: LoggerAdapter): void {
46
- treeKeyLogger = logger;
47
- treeKeyDebugEnabled = true;
48
- }
49
-
50
- /**
51
- * Disable TreeKey debug logging
52
- */
53
- export function disableTreeKeyLogger(): void {
54
- treeKeyLogger = new NoopLogger();
55
- treeKeyDebugEnabled = false;
56
- }
57
-
58
- /**
59
- * Check if TreeKey debug logging is enabled
60
- */
61
- export function isTreeKeyDebugEnabled(): boolean {
62
- return treeKeyDebugEnabled;
63
- }
64
-
65
- export const DEFAULT_KEYS_PER_LEVEL = 64;
66
- export const DEFAULT_LEVELS = 3;
67
-
68
- /**
69
- * Progress callback for key generation
70
- */
71
- export interface KeyGenProgress {
72
- phase: 'wots_keys' | 'mmr_build' | 'address_derive' | 'complete';
73
- current: number;
74
- total: number;
75
- message: string;
76
- }
77
-
78
- export type ProgressCallback = (progress: KeyGenProgress) => void;
79
-
80
- /**
81
- * SignatureProof structure matching Minima's SignatureProof.java
82
- *
83
- * Contains:
84
- * - leafPubkey: The 32-byte WOTS public key DIGEST (SHA3-256 of full L×32 key)
85
- * - signature: The 1088-byte Winternitz signature (L×32 bytes)
86
- * - mmrProof: Proof linking the leaf pubkey to the tree node's root
87
- *
88
- * CRITICAL FIX (January 2026): Java's Winternitz.getPublicKey() returns a 32-byte digest!
89
- *
90
- * From BouncyCastle WinternitzOTSignature.getPublicKey() (lines 103-121):
91
- * byte[] buf = new byte[keysize * mdsize]; // Full 1088 bytes (34×32)
92
- * // ... hash each chain 255 times into buf ...
93
- * messDigestOTS.update(buf, 0, buf.length); // Hash the full key
94
- * byte[] tmp = new byte[mdsize]; // 32 bytes
95
- * messDigestOTS.doFinal(tmp, 0); // SHA3-256
96
- * return tmp; // Returns 32-byte DIGEST!
97
- *
98
- * Similarly, WinternitzOTSVerify.Verify() recovers the full key then hashes to 32 bytes.
99
- * Winternitz.verify() then compares the 32-byte recovered digest to mPublicKey (32 bytes).
100
- *
101
- * Previous bug: We stored 1088-byte full keys, Java expected 32-byte digests → always failed.
102
- */
103
- export interface SignatureProof {
104
- leafPubkey: Bytes; // 32-byte WOTS public key DIGEST (SHA3-256 of full key) - matches Java
105
- signature: Bytes; // L×32-byte Winternitz signature (1088 bytes for L=34)
106
- mmrProof: MMRProof; // MMR proof from leaf to root
107
- }
108
-
109
- /**
110
- * Full Signature structure matching Minima's Signature.java
111
- *
112
- * For a 3-level tree, contains 3 SignatureProofs:
113
- * - Level 0: Signs level 1's root public key
114
- * - Level 1: Signs level 2's root public key
115
- * - Level 2: Signs the actual data
116
- */
117
- export interface TreeSignature {
118
- proofs: SignatureProof[];
119
- }
120
-
121
- /**
122
- * Compute root public key from a Winternitz signature proof
123
- * Matches SignatureProof.getRootPublicKey() in Java
124
- */
125
- export function getRootPublicKey(proof: SignatureProof): Bytes {
126
- const leafData = createMMRDataLeafNode(proof.leafPubkey, 0n);
127
- return calculateProofRoot(leafData, proof.mmrProof);
128
- }
129
-
130
- /**
131
- * TreeKeyNode - One node in the key tree containing 64 Winternitz keys
132
- *
133
- * Matches TreeKeyNode.java (see attached_assets/TreeKeyNode_1767574401422.java):
134
- *
135
- * Key generation (lines 44-62):
136
- * - Creates 64 Winternitz keys from a deterministic seed
137
- * - For each key: MiniData pubkey = wots.getPublicKey() returns 32-byte DIGEST
138
- * - Adds to MMR: MMRData.CreateMMRDataLeafNode(pubkey, MiniNumber.ZERO)
139
- * - Public key = MMR root (mPublicKey = mTree.getRoot().getData())
140
- *
141
- * MMR leaf construction (see MMRData.java lines 30-36):
142
- * MMRData.CreateMMRDataLeafNode(pubkeyDigest, MiniNumber.ZERO)
143
- * → hash = Crypto.hashAllObjects(MiniNumber.ZERO, zData, zSumValue)
144
- * → Serialization: [0x00,0x01,0x00] + [4-byte-len + pubkey] + [0x00,0x01,0x00]
145
- *
146
- * MMR parent construction (see MMRData.java lines 38-50):
147
- * MMRData.CreateMMRDataParentNode(left, right)
148
- * → hash = Crypto.hashAllObjects(MiniNumber.ONE, left.data, right.data, sumValue)
149
- *
150
- * IMPORTANT: Minima NEVER stores the 1088-byte full WOTS public key.
151
- * Only the 32-byte digest is stored and used for MMR construction.
152
- */
153
- export class TreeKeyNode {
154
- private seed: Bytes;
155
- private childSeed: Bytes;
156
- private keysPerLevel: number;
157
- private publicKeyDigests: Bytes[]; // 32-byte SHA3 digests - matches Java's Winternitz.getPublicKey() which returns SHA3-256(full_key)
158
- private mmrTree: MMRTree;
159
- private rootPubkey: Bytes;
160
- private childCache: Map<number, TreeKeyNode> = new Map();
161
-
162
- constructor(privateSeed: Bytes, keysPerLevel: number = DEFAULT_KEYS_PER_LEVEL) {
163
- if (privateSeed.length !== 32) {
164
- throw new Error('Private seed must be 32 bytes');
165
- }
166
-
167
- if (treeKeyDebugEnabled) {
168
- treeKeyLogger.debug(`[TreeKeyNode] ========== INIT START ==========`);
169
- treeKeyLogger.debug(`[TreeKeyNode] Private seed: ${hex(privateSeed)}`);
170
- treeKeyLogger.debug(`[TreeKeyNode] Keys per level: ${keysPerLevel}`);
171
- }
172
-
173
- this.seed = privateSeed;
174
- this.keysPerLevel = keysPerLevel;
175
-
176
- // Hash the seed to create child seed (for deriving child nodes)
177
- // From TreeKeyNode.java: mChildSeed = Crypto.getInstance().hashObject(zPrivateSeed);
178
- // hashObject serializes as MiniData (length-prefixed) then hashes
179
- this.childSeed = hashObject(privateSeed);
180
-
181
- if (treeKeyDebugEnabled) {
182
- treeKeyLogger.debug(`[TreeKeyNode] Child seed (hashObject): ${hex(this.childSeed)}`);
183
- }
184
-
185
- // Generate all Winternitz public key DIGESTS (32 bytes each)
186
- // Java's Winternitz.getPublicKey() returns SHA3-256(full_key) = 32 bytes
187
- // See WinternitzOTSignature.getPublicKey() lines 103-121: hashes full 1088B key, returns 32B digest
188
- // TreeKeyNode.java line 53: MiniData pubkey = wots.getPublicKey() - stores the 32B digest
189
- // MMRData.CreateMMRDataLeafNode(pubkey, MiniNumber.ZERO) - builds leaves from digests
190
- this.publicKeyDigests = [];
191
- for (let i = 0; i < keysPerLevel; i++) {
192
- // Digest (32 bytes) - used for MMR tree, SignatureProof.leafPubkey, and address derivation
193
- const pkDigest = derivePKdigest(privateSeed, i, getParamSet());
194
- this.publicKeyDigests.push(pkDigest);
195
-
196
- // Log first few keys for debugging
197
- if (treeKeyDebugEnabled && i < 3) {
198
- treeKeyLogger.debug(`[TreeKeyNode] WOTS key[${i}] digest (32B): ${hex(pkDigest)}`);
199
- }
200
- }
201
-
202
- if (treeKeyDebugEnabled) {
203
- treeKeyLogger.debug(`[TreeKeyNode] Generated ${this.publicKeyDigests.length} WOTS key digests`);
204
- const lastIdx = this.publicKeyDigests.length - 1;
205
- treeKeyLogger.debug(`[TreeKeyNode] WOTS key[${lastIdx}] digest: ${hex(this.publicKeyDigests[lastIdx])}`);
206
- }
207
-
208
- // Build MMR tree from 32-byte PUBLIC KEY DIGESTS (Java-compatible)
209
- // Java's TreeKeyNode: MMRData.CreateMMRDataLeafNode(wots.getPublicKey(), ZERO)
210
- // where getPublicKey() returns SHA3-256(full_key) = 32-byte DIGEST
211
- this.mmrTree = MMRTree.fromPublicKeys(this.publicKeyDigests);
212
-
213
- // Root public key = MMR root
214
- const root = this.mmrTree.getRoot();
215
- if (!root) {
216
- throw new Error('Failed to compute MMR root');
217
- }
218
- this.rootPubkey = root.data;
219
-
220
- if (treeKeyDebugEnabled) {
221
- treeKeyLogger.debug(`[TreeKeyNode] MMR root (node public key): ${hex(this.rootPubkey)}`);
222
- treeKeyLogger.debug(`[TreeKeyNode] ========== INIT END ==========`);
223
- }
224
- }
225
-
226
- /**
227
- * Async factory method for TreeKeyNode with progress reporting
228
- * Yields to event loop every few keys to keep UI responsive
229
- */
230
- static async createWithProgress(
231
- privateSeed: Bytes,
232
- keysPerLevel: number = DEFAULT_KEYS_PER_LEVEL,
233
- onProgress?: ProgressCallback
234
- ): Promise<TreeKeyNode> {
235
- if (privateSeed.length !== 32) {
236
- throw new Error('Private seed must be 32 bytes');
237
- }
238
-
239
- const node = Object.create(TreeKeyNode.prototype) as TreeKeyNode;
240
- node.seed = privateSeed;
241
- node.keysPerLevel = keysPerLevel;
242
- node.childSeed = hashObject(privateSeed);
243
- node.publicKeyDigests = [];
244
- node.childCache = new Map();
245
-
246
- // Generate Winternitz public key DIGESTS with progress (32 bytes each)
247
- // Java's Winternitz.getPublicKey() returns SHA3-256(full_key) = 32 bytes
248
- // See WinternitzOTSignature.getPublicKey() lines 103-121: hashes full 1088B key, returns 32B digest
249
- for (let i = 0; i < keysPerLevel; i++) {
250
- // Digest (32 bytes) - used for MMR tree, SignatureProof.leafPubkey, and address derivation
251
- const pkDigest = derivePKdigest(privateSeed, i, getParamSet());
252
- node.publicKeyDigests.push(pkDigest);
253
-
254
- // Report progress every 4 keys and yield to event loop
255
- if (onProgress && (i + 1) % 4 === 0) {
256
- onProgress({
257
- phase: 'wots_keys',
258
- current: i + 1,
259
- total: keysPerLevel,
260
- message: `Generating signing key ${i + 1}/${keysPerLevel}`
261
- });
262
- // Yield to event loop to keep UI responsive
263
- await new Promise(resolve => setTimeout(resolve, 0));
264
- }
265
- }
266
-
267
- // Report MMR build phase
268
- if (onProgress) {
269
- onProgress({
270
- phase: 'mmr_build',
271
- current: 0,
272
- total: 1,
273
- message: 'Building secure Merkle tree...'
274
- });
275
- }
276
-
277
- // Build MMR tree from 32-byte PUBLIC KEY DIGESTS (Java-compatible)
278
- // Java's TreeKeyNode: MMRData.CreateMMRDataLeafNode(wots.getPublicKey(), ZERO)
279
- // where getPublicKey() returns SHA3-256(full_key) = 32-byte DIGEST
280
- node.mmrTree = MMRTree.fromPublicKeys(node.publicKeyDigests);
281
-
282
- const root = node.mmrTree.getRoot();
283
- if (!root) {
284
- throw new Error('Failed to compute MMR root');
285
- }
286
- node.rootPubkey = root.data;
287
-
288
- if (onProgress) {
289
- onProgress({
290
- phase: 'mmr_build',
291
- current: 1,
292
- total: 1,
293
- message: 'Merkle tree complete'
294
- });
295
- }
296
-
297
- return node;
298
- }
299
-
300
- /**
301
- * Get the public key for this tree node (MMR root of all 64 Winternitz keys)
302
- */
303
- getPublicKey(): Bytes {
304
- return this.rootPubkey;
305
- }
306
-
307
- /**
308
- * Get the full Winternitz public key at a specific index (0-63)
309
- * Returns the full L×32 byte public key (1088 bytes), derived on-demand
310
- *
311
- * NOTE: This is only used for local signature verification in tests.
312
- * Java's Winternitz.getPublicKey() returns a 32-byte digest, not this.
313
- * For production code, use getWOTSPublicKeyDigest() instead.
314
- *
315
- * @deprecated Use getWOTSPublicKeyDigest() for Minima compatibility
316
- */
317
- getWOTSPublicKey(index: number): Bytes {
318
- if (index < 0 || index >= this.keysPerLevel) {
319
- throw new Error(`Key index ${index} out of range [0, ${this.keysPerLevel})`);
320
- }
321
- // Derive on-demand - expensive but only used for tests
322
- return deriveFullPublicKey(this.seed, index, getParamSet());
323
- }
324
-
325
- /**
326
- * Get the Winternitz public key digest at a specific index (0-63)
327
- * Returns the 32-byte SHA3 hash of the full public key
328
- */
329
- getWOTSPublicKeyDigest(index: number): Bytes {
330
- if (index < 0 || index >= this.keysPerLevel) {
331
- throw new Error(`Key index ${index} out of range [0, ${this.keysPerLevel})`);
332
- }
333
- return this.publicKeyDigests[index];
334
- }
335
-
336
- /**
337
- * Get the MMR proof for a specific key index
338
- */
339
- getProof(keyIndex: number): MMRProof {
340
- return this.mmrTree.getProof(keyIndex);
341
- }
342
-
343
- /**
344
- * Sign data with a specific key from this node
345
- * Returns a SignatureProof containing the 32-byte leaf pubkey DIGEST, signature, and MMR proof
346
- *
347
- * CRITICAL: Java's WinternitzOTSignature.getSignature() ALWAYS hashes the message first,
348
- * regardless of input length. From BouncyCastle WinternitzOTSignature.java lines 137-138:
349
- * messDigestOTS.update(message, 0, message.length);
350
- * messDigestOTS.doFinal(hash, 0);
351
- *
352
- * We MUST always hash to match Java verification, which also always hashes.
353
- *
354
- * CRITICAL FIX (January 2026): leafPubkey is the 32-byte WOTS public key DIGEST.
355
- * Java's Winternitz.getPublicKey() returns SHA3-256(full_key) = 32 bytes!
356
- * Previous bug: We stored 1088-byte full keys, Java expected 32-byte digests → verification failed.
357
- */
358
- sign(keyIndex: number, data: Bytes): SignatureProof {
359
- if (keyIndex < 0 || keyIndex >= this.keysPerLevel) {
360
- throw new Error(`Key index ${keyIndex} out of range [0, ${this.keysPerLevel})`);
361
- }
362
-
363
- if (treeKeyDebugEnabled) {
364
- treeKeyLogger.debug(`[TreeKeyNode.sign] ========== SIGN START ==========`);
365
- treeKeyLogger.debug(`[TreeKeyNode.sign] keyIndex: ${keyIndex}`);
366
- treeKeyLogger.debug(`[TreeKeyNode.sign] input data (${data.length}B): ${hex(data).substring(0, 64)}${data.length > 32 ? '...' : ''}`);
367
- }
368
-
369
- // Get the 32-byte Winternitz public key DIGEST for SignatureProof
370
- // CRITICAL FIX: Java's Winternitz.getPublicKey() returns SHA3-256(full_key) = 32 bytes!
371
- // NOT the 1088-byte full key! This was the bug causing "allsignaturesvalid":false errors.
372
- const leafPubkey = this.publicKeyDigests[keyIndex];
373
-
374
- if (treeKeyDebugEnabled) {
375
- treeKeyLogger.debug(`[TreeKeyNode.sign] leafPubkey (${leafPubkey.length}B DIGEST): ${hex(leafPubkey)}`);
376
- }
377
-
378
- // CRITICAL: Do NOT pre-hash here! wotsSign() now handles internal hashing (matching Java/BouncyCastle).
379
- // Java's WinternitzOTSignature.getSignature() hashes internally, so wotsSign() does too.
380
- // Call sites pass the same 32-byte values Java passes:
381
- // - For TX signing: the 32-byte transaction digest
382
- // - For parent→child: the 32-byte childRoot (MMR root of child proof)
383
- // WOTS hashes once internally - this is by design for Minima compatibility.
384
-
385
- if (treeKeyDebugEnabled) {
386
- treeKeyLogger.debug(`[TreeKeyNode.sign] dataToSign (raw input, WOTS hashes internally): ${hex(data)}`);
387
- }
388
-
389
- // Sign data directly - wotsSign() handles internal hashing
390
- const signature = wotsSign(this.seed, keyIndex, data, getParamSet());
391
-
392
- if (treeKeyDebugEnabled) {
393
- treeKeyLogger.debug(`[TreeKeyNode.sign] signature (${signature.length}B): ${hex(signature).substring(0, 64)}...${hex(signature).substring(hex(signature).length - 32)}`);
394
- }
395
-
396
- // Get the MMR proof for this key
397
- const mmrProof = this.getProof(keyIndex);
398
-
399
- if (treeKeyDebugEnabled) {
400
- treeKeyLogger.debug(`[TreeKeyNode.sign] MMR proof chunks: ${mmrProof.chunks.length}`);
401
- treeKeyLogger.debug(`[TreeKeyNode.sign] ========== SIGN END ==========`);
402
- }
403
-
404
- return { leafPubkey, signature, mmrProof };
405
- }
406
-
407
- /**
408
- * Create a child TreeKeyNode at the specified index
409
- * Matches TreeKeyNode.java getChild()
410
- *
411
- * PERFORMANCE FIX: Child nodes are now cached to avoid regenerating
412
- * 64 WOTS keys on every getChild() call. This is critical for address
413
- * derivation performance where getChild() is called 64 times.
414
- */
415
- getChild(childIndex: number): TreeKeyNode {
416
- if (childIndex < 0 || childIndex >= this.keysPerLevel) {
417
- throw new Error(`Child index ${childIndex} out of range [0, ${this.keysPerLevel})`);
418
- }
419
-
420
- // Check cache first
421
- const cached = this.childCache.get(childIndex);
422
- if (cached) {
423
- if (treeKeyDebugEnabled) {
424
- treeKeyLogger.debug(`[TreeKeyNode.getChild] Using cached child at index ${childIndex}`);
425
- }
426
- return cached;
427
- }
428
-
429
- if (treeKeyDebugEnabled) {
430
- treeKeyLogger.debug(`[TreeKeyNode.getChild] Creating child at index ${childIndex}`);
431
- treeKeyLogger.debug(`[TreeKeyNode.getChild] Parent childSeed: ${hex(this.childSeed)}`);
432
- }
433
-
434
- // From TreeKeyNode.java:
435
- // MiniData seed = Crypto.getInstance().hashAllObjects(new MiniNumber(zChild), mChildSeed);
436
- const childSeed = deriveChainSeedJava(this.childSeed, childIndex);
437
-
438
- if (treeKeyDebugEnabled) {
439
- treeKeyLogger.debug(`[TreeKeyNode.getChild] Derived childSeed: ${hex(childSeed)}`);
440
- }
441
-
442
- const child = new TreeKeyNode(childSeed, this.keysPerLevel);
443
-
444
- if (treeKeyDebugEnabled) {
445
- treeKeyLogger.debug(`[TreeKeyNode.getChild] Child public key: ${hex(child.getPublicKey())}`);
446
- }
447
-
448
- // Cache for future use
449
- this.childCache.set(childIndex, child);
450
-
451
- return child;
452
- }
453
- }
454
-
455
- /**
456
- * TreeKey - Full hierarchical key tree with multiple levels
457
- *
458
- * Matches TreeKey.java:
459
- * - Default: 3 levels x 64 keys = 262,144 one-time signatures
460
- * - Tracks usage count to determine which key to use
461
- * - Produces multi-level signatures
462
- */
463
- export class TreeKey {
464
- private privateSeed: Bytes;
465
- private levels: number;
466
- private keysPerLevel: number;
467
- private root: TreeKeyNode;
468
- private publicKey: Bytes;
469
- private uses: number = 0;
470
-
471
- /**
472
- * Parent-child signature cache for efficiency
473
- * Key format: "l1" for root->level1, "l1,l2" for level1->level2
474
- * Value: SignatureProof for parent signing child's pubkey
475
- */
476
- private parentChildSigCache: Map<string, SignatureProof> = new Map();
477
-
478
- constructor(privateSeed: Bytes, keysPerLevel: number = DEFAULT_KEYS_PER_LEVEL, levels: number = DEFAULT_LEVELS) {
479
- if (privateSeed.length !== 32) {
480
- throw new Error('Private seed must be 32 bytes');
481
- }
482
-
483
- if (treeKeyDebugEnabled) {
484
- treeKeyLogger.debug(`[TreeKey] ========== WALLET INIT START ==========`);
485
- treeKeyLogger.debug(`[TreeKey] Private seed: ${hex(privateSeed)}`);
486
- treeKeyLogger.debug(`[TreeKey] Levels: ${levels}, Keys per level: ${keysPerLevel}`);
487
- treeKeyLogger.debug(`[TreeKey] Total capacity: ${Math.pow(keysPerLevel, levels)} signatures`);
488
- }
489
-
490
- this.privateSeed = privateSeed;
491
- this.levels = levels;
492
- this.keysPerLevel = keysPerLevel;
493
-
494
- // Initialize root node
495
- this.root = new TreeKeyNode(privateSeed, keysPerLevel);
496
- this.publicKey = this.root.getPublicKey();
497
-
498
- if (treeKeyDebugEnabled) {
499
- treeKeyLogger.debug(`[TreeKey] Root public key: ${hex(this.publicKey)}`);
500
- treeKeyLogger.debug(`[TreeKey] ========== WALLET INIT END ==========`);
501
- }
502
- }
503
-
504
- /**
505
- * Async factory method for TreeKey with progress reporting
506
- * Reports progress as the root TreeKeyNode generates its 64 signing keys
507
- */
508
- static async createWithProgress(
509
- privateSeed: Bytes,
510
- keysPerLevel: number = DEFAULT_KEYS_PER_LEVEL,
511
- levels: number = DEFAULT_LEVELS,
512
- onProgress?: ProgressCallback
513
- ): Promise<TreeKey> {
514
- if (privateSeed.length !== 32) {
515
- throw new Error('Private seed must be 32 bytes');
516
- }
517
-
518
- const treeKey = Object.create(TreeKey.prototype) as TreeKey;
519
- treeKey.privateSeed = privateSeed;
520
- treeKey.levels = levels;
521
- treeKey.keysPerLevel = keysPerLevel;
522
- treeKey.uses = 0;
523
- treeKey.parentChildSigCache = new Map();
524
-
525
- // Create root node with progress reporting
526
- treeKey.root = await TreeKeyNode.createWithProgress(privateSeed, keysPerLevel, onProgress);
527
- treeKey.publicKey = treeKey.root.getPublicKey();
528
-
529
- if (onProgress) {
530
- onProgress({
531
- phase: 'complete',
532
- current: keysPerLevel,
533
- total: keysPerLevel,
534
- message: 'Key tree ready'
535
- });
536
- }
537
-
538
- return treeKey;
539
- }
540
-
541
- /**
542
- * Get the root TreeKeyNode (for internal use)
543
- */
544
- getRootNode(): TreeKeyNode {
545
- return this.root;
546
- }
547
-
548
- /**
549
- * Get the root public key (for watermark tracking)
550
- */
551
- getRootPublicKey(): Bytes {
552
- return this.publicKey;
553
- }
554
-
555
- /**
556
- * Get the wallet's public key (root of the key tree)
557
- */
558
- getPublicKey(): Bytes {
559
- return this.publicKey;
560
- }
561
-
562
- /**
563
- * Get the maximum number of signatures this tree can produce
564
- */
565
- getMaxUses(): number {
566
- return Math.pow(this.keysPerLevel, this.levels);
567
- }
568
-
569
- /**
570
- * Get current usage count
571
- */
572
- getUses(): number {
573
- return this.uses;
574
- }
575
-
576
- /**
577
- * Set the usage counter (for resuming from a known state)
578
- */
579
- setUses(uses: number): void {
580
- this.uses = uses;
581
- }
582
-
583
- /**
584
- * Generate cache key for parent-child signature
585
- * @param path - Array of indices leading to the child (e.g., [l1] or [l1, l2])
586
- */
587
- private getCacheKey(path: number[]): string {
588
- return path.join(',');
589
- }
590
-
591
- /**
592
- * Check if a parent-child signature is cached
593
- * @param path - Array of indices (e.g., [l1] for root->level1)
594
- */
595
- hasParentChildSig(path: number[]): boolean {
596
- return this.parentChildSigCache.has(this.getCacheKey(path));
597
- }
598
-
599
- /**
600
- * Get a cached parent-child signature
601
- * @param path - Array of indices (e.g., [l1] for root->level1)
602
- */
603
- getParentChildSig(path: number[]): SignatureProof | undefined {
604
- return this.parentChildSigCache.get(this.getCacheKey(path));
605
- }
606
-
607
- /**
608
- * Cache a parent-child signature for reuse
609
- * This allows the same signature to be reused across multiple signing operations
610
- *
611
- * @param path - Array of indices leading to the child (e.g., [l1] or [l1, l2])
612
- * @param sig - SignatureProof from parent signing child's public key
613
- */
614
- setParentChildSig(path: number[], sig: SignatureProof): void {
615
- this.parentChildSigCache.set(this.getCacheKey(path), sig);
616
- }
617
-
618
- /**
619
- * Get all cached parent-child signatures (for serialization/persistence)
620
- */
621
- getCachedSignatures(): Map<string, SignatureProof> {
622
- return new Map(this.parentChildSigCache);
623
- }
624
-
625
- /**
626
- * Restore cached signatures (for hydrating from persistence)
627
- */
628
- restoreCachedSignatures(cache: Map<string, SignatureProof>): void {
629
- this.parentChildSigCache = new Map(cache);
630
- }
631
-
632
- /**
633
- * Convert a usage number to the path through the tree
634
- * Matches TreeKey.java baseConversion()
635
- *
636
- * For uses=0: [0,0,0]
637
- * For uses=1: [0,0,1]
638
- * For uses=64: [0,1,0]
639
- * etc.
640
- */
641
- private baseConversion(num: number): number[] {
642
- const result: number[] = [];
643
- let counter = num;
644
-
645
- while (counter !== 0) {
646
- const div = Math.floor(counter / this.keysPerLevel);
647
- const remain = counter - (div * this.keysPerLevel);
648
- result.push(remain);
649
- counter = div;
650
- }
651
-
652
- // Pad to required levels
653
- while (result.length < this.levels) {
654
- result.push(0);
655
- }
656
-
657
- // Reverse to get [addressIndex, l1, l2] order
658
- result.reverse();
659
- return result;
660
- }
661
-
662
- /**
663
- * Sign data with the current key and increment usage
664
- *
665
- * Matches TreeKey.java sign():
666
- * - Determines path through tree based on usage count
667
- * - Each level's key signs the next level's root public key
668
- * - Final level signs the actual data
669
- *
670
- * CRITICAL FIX (January 2026): Build proofs bottom-up to sign child's getRootPublicKey()
671
- *
672
- * Java's TreeKey.verify() verifies non-leaf signatures against childsig.getRootPublicKey(),
673
- * which is the 32-byte MMR root computed from the NEXT proof's leafPubkey + MMRproof.
674
- *
675
- * Uses parent-child signature caching for efficiency:
676
- * - Parent-child signatures are cached and reused
677
- * - Only the final data signature is computed fresh each time
678
- */
679
- sign(data: Bytes): TreeSignature {
680
- if (this.uses >= this.getMaxUses()) {
681
- throw new Error('No more keys available (tree exhausted)');
682
- }
683
-
684
- // Get the path through the tree
685
- const path = this.baseConversion(this.uses);
686
-
687
- // Navigate to all nodes first
688
- const nodes: TreeKeyNode[] = [this.root];
689
- let currentNode = this.root;
690
- for (let i = 0; i < this.levels - 1; i++) {
691
- currentNode = currentNode.getChild(path[i]);
692
- nodes.push(currentNode);
693
- }
694
- // nodes = [root, L1, L2] for a 3-level tree
695
-
696
- // ═══════════════════════════════════════════════════════════════════════════
697
- // BUILD PROOFS BOTTOM-UP: Start from leaf, work up to root
698
- // This ensures we sign exactly what Java's verification will compute.
699
- // ═══════════════════════════════════════════════════════════════════════════
700
-
701
- const proofs: SignatureProof[] = new Array(this.levels);
702
-
703
- // STEP 1: Create leaf proof FIRST (signs actual data)
704
- const leafDepth = this.levels - 1;
705
- const leafNode = nodes[leafDepth];
706
- const leafKeyIndex = path[leafDepth];
707
- const leafProof = leafNode.sign(leafKeyIndex, data);
708
- proofs[leafDepth] = leafProof;
709
-
710
- // STEP 2: Work backwards, each parent signs childProof.getRootPublicKey()
711
- for (let depth = this.levels - 2; depth >= 0; depth--) {
712
- const parentNode = nodes[depth];
713
- const keyIndex = path[depth];
714
- const childProof = proofs[depth + 1];
715
- const childRoot = getRootPublicKey(childProof);
716
-
717
- // Cache key includes full path to ensure uniqueness
718
- const cachePath = path.slice(0, depth + 2);
719
- let sigProof = this.getParentChildSig(cachePath);
720
-
721
- if (!sigProof) {
722
- // Not cached: compute and cache
723
- sigProof = parentNode.sign(keyIndex, childRoot); // Sign the 32-byte MMR root!
724
- this.setParentChildSig(cachePath, sigProof);
725
- }
726
-
727
- proofs[depth] = sigProof;
728
- }
729
-
730
- // Increment usage counter
731
- this.uses++;
732
-
733
- return { proofs };
734
- }
735
-
736
-
737
- /**
738
- * Get the public key for a level-1 address (single index)
739
- * This is the MMR root of the level-1 TreeKeyNode's 64 Winternitz keys.
740
- *
741
- * Use this for wallet addresses where each address = one level-1 node.
742
- *
743
- * @param l1 - Level 1 index (0-63, corresponds to wallet address index)
744
- * @returns 32-byte MMR root public key for SIGNEDBY scripts
745
- */
746
- getAddressPublicKey(l1: number): Bytes {
747
- // Get level-1 child node's public key (MMR root of its 64 WOTS keys)
748
- const level1Node = this.root.getChild(l1);
749
- return level1Node.getPublicKey();
750
- }
751
-
752
- /**
753
- * Get the public key for a specific signing key at tree index (l1, l2)
754
- * This navigates to the level-2 node for signing operations.
755
- *
756
- * @param l1 - Level 1 index (address)
757
- * @param l2 - Level 2 index (signing key within address)
758
- * @returns 32-byte MMR root public key of level-2 node
759
- */
760
- getSigningNodePublicKey(l1: number, l2: number): Bytes {
761
- // Navigate to level 2 node
762
- const level1Node = this.root.getChild(l1);
763
- const level2Node = level1Node.getChild(l2);
764
- return level2Node.getPublicKey();
765
- }
766
- }
767
-
768
- /**
769
- * Verify a tree signature against expected public key and data
770
- *
771
- * Matches TreeKey.java verify():
772
- * - First proof's computed root must match expected public key
773
- * - Each intermediate proof must sign the next level's root
774
- * - Final proof must verify against the actual data
775
- */
776
- export function verifyTreeSignature(
777
- expectedPubkey: Bytes,
778
- data: Bytes,
779
- signature: TreeSignature
780
- ): boolean {
781
- const { proofs } = signature;
782
-
783
- if (proofs.length === 0) {
784
- return false;
785
- }
786
-
787
- // Import wotsVerify for verification
788
- // wotsVerify already imported at top of file
789
- const paramSet = getParamSet();
790
-
791
- for (let depth = 0; depth < proofs.length; depth++) {
792
- const proof = proofs[depth];
793
-
794
- // Compute the root public key from this proof
795
- const rootPubkey = getRootPublicKey(proof);
796
-
797
- // First level: must match expected public key
798
- if (depth === 0) {
799
- for (let i = 0; i < expectedPubkey.length; i++) {
800
- if (rootPubkey[i] !== expectedPubkey[i]) {
801
- return false;
802
- }
803
- }
804
- }
805
-
806
- // Determine what was signed
807
- let signedData: Bytes;
808
- if (depth === proofs.length - 1) {
809
- // Final level: signed the actual data
810
- signedData = data;
811
- } else {
812
- // Intermediate level: signed the next level's root public key
813
- signedData = getRootPublicKey(proofs[depth + 1]);
814
- }
815
-
816
- // Verify the Winternitz signature
817
- // Use wotsVerifyDigest since leafPubkey is now a 32-byte digest (not 1088-byte full key)
818
- if (!wotsVerifyDigest(proof.signature, signedData, proof.leafPubkey, paramSet)) {
819
- return false;
820
- }
821
- }
822
-
823
- return true;
824
- }
825
-
826
- /**
827
- * Serialize a TreeSignature to bytes
828
- *
829
- * Uses Streamable.writeSignature() for byte-exact compatibility
830
- * with Java's Signature.writeDataStream().
831
- */
832
- export function serializeTreeSignature(sig: TreeSignature): Bytes {
833
- // Convert to Streamable format (adds blockTime to MMRProof)
834
- const streamableSig: StreamableSignature = {
835
- proofs: sig.proofs.map(proof => ({
836
- leafPubkey: proof.leafPubkey,
837
- signature: proof.signature,
838
- mmrProof: {
839
- blockTime: 0n, // Default for compatibility
840
- chunks: proof.mmrProof.chunks
841
- }
842
- }))
843
- };
844
-
845
- // Use canonical serializer for byte-exact Java compatibility
846
- return writeSignature(streamableSig);
847
- }
848
-
849
- /**
850
- * Deserialize a TreeSignature from bytes
851
- *
852
- * Matches Java's Signature.readDataStream():
853
- * - Number of proofs: MiniNumber format
854
- * - Each SignatureProof: MiniData(pubkey) + MiniData(signature) + MMRProof
855
- */
856
- export function deserializeTreeSignature(data: Bytes): TreeSignature {
857
- let offset = 0;
858
-
859
- // Read MiniNumber for proof count
860
- // Format: [scale: 1 byte] [length: 1 byte] [data: N bytes]
861
- const scale = data[offset];
862
- const numBytesLen = data[offset + 1];
863
- offset += 2;
864
-
865
- // Read the number bytes (big-endian)
866
- let numProofs = 0;
867
- for (let i = 0; i < numBytesLen; i++) {
868
- numProofs = (numProofs << 8) | data[offset + i];
869
- }
870
- offset += numBytesLen;
871
-
872
- const proofs: SignatureProof[] = [];
873
-
874
- for (let i = 0; i < numProofs; i++) {
875
- // Leaf pubkey - MiniData format (4-byte length prefix + data)
876
- const pubkeyLen = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
877
- offset += 4;
878
- const leafPubkey = data.slice(offset, offset + pubkeyLen);
879
- offset += pubkeyLen;
880
-
881
- // Signature - MiniData format (4-byte length prefix + data)
882
- const sigLen = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
883
- offset += 4;
884
- const signature = data.slice(offset, offset + sigLen);
885
- offset += sigLen;
886
-
887
- // MMR proof - returns { proof, blockTime, bytesRead }
888
- const { proof: mmrProof, bytesRead: mmrProofLen } = parseMMRProofFromHex(data.slice(offset));
889
- offset += mmrProofLen;
890
-
891
- proofs.push({ leafPubkey, signature, mmrProof });
892
- }
893
-
894
- return { proofs };
895
- }
896
-
897
- /**
898
- * ============================================================================
899
- * PER-ADDRESS TREEKEY FACTORY
900
- * ============================================================================
901
- *
902
- * Minima Wallet.createNewKey() creates independent TreeKeys per address:
903
- * MiniData modifier = new MiniData(new BigInteger(Integer.toString(numkeys)));
904
- * MiniData privseed = Crypto.getInstance().hashObjects(baseSeed, modifier);
905
- * TreeKey treekey = TreeKey.createDefault(privseed);
906
- *
907
- * Each address gets its own TreeKey with:
908
- * - Unique seed derived from baseSeed + addressIndex
909
- * - Size=64, depth=3 (matching TreeKey.createDefault)
910
- * - 4096 one-time signatures per address (64×64)
911
- *
912
- * Total wallet capacity: 64 addresses × 4096 signatures = 262,144 signatures
913
- * ============================================================================
914
- */
915
-
916
- /**
917
- * Create a per-address TreeKey matching Minima Wallet.createNewKey()
918
- *
919
- * @param baseSeed - 32-byte wallet base seed (from mnemonic)
920
- * @param addressIndex - Address index (0-63)
921
- * @returns TreeKey for this address with size=64, depth=3
922
- */
923
- export function createPerAddressTreeKey(
924
- baseSeed: Bytes,
925
- addressIndex: number
926
- ): TreeKey {
927
- const addressSeed = derivePerAddressSeed(baseSeed, addressIndex);
928
- return new TreeKey(addressSeed, 64, 3);
929
- }
930
-
931
- /**
932
- * Async version with progress reporting for UI
933
- *
934
- * @param baseSeed - 32-byte wallet base seed
935
- * @param addressIndex - Address index (0-63)
936
- * @param onProgress - Optional progress callback
937
- * @returns Promise resolving to TreeKey for this address
938
- */
939
- export async function createPerAddressTreeKeyAsync(
940
- baseSeed: Bytes,
941
- addressIndex: number,
942
- onProgress?: ProgressCallback
943
- ): Promise<TreeKey> {
944
- const addressSeed = derivePerAddressSeed(baseSeed, addressIndex);
945
- return TreeKey.createWithProgress(addressSeed, 64, 3, onProgress);
946
- }
947
-
948
- /**
949
- * Derive address public key from base seed and address index
950
- *
951
- * This is the fast path for getting an address public key without
952
- * needing to construct the full TreeKey. Useful for address derivation
953
- * during wallet initialization.
954
- *
955
- * @param baseSeed - 32-byte wallet base seed
956
- * @param addressIndex - Address index (0-63)
957
- * @returns 32-byte address public key (MMR root of per-address TreeKey)
958
- */
959
- export function deriveAddressPublicKey(baseSeed: Bytes, addressIndex: number): Bytes {
960
- const treeKey = createPerAddressTreeKey(baseSeed, addressIndex);
961
- return treeKey.getPublicKey();
962
- }
963
-
964
- /**
965
- * Get the address public key from a per-address TreeKey
966
- *
967
- * In the per-address architecture, each address has its own TreeKey,
968
- * and the address public key is simply the TreeKey's root public key.
969
- *
970
- * @param treeKey - Per-address TreeKey
971
- * @returns 32-byte address public key (TreeKey root)
972
- */
973
- export function getPerAddressPublicKey(treeKey: TreeKey): Bytes {
974
- return treeKey.getPublicKey();
975
- }