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