@ocash/sdk 0.1.4-rc.0 → 0.1.4-rc.2
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/browser.cjs +413 -286
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +18 -16
- package/dist/browser.d.ts +18 -16
- package/dist/browser.js +413 -286
- package/dist/browser.js.map +1 -1
- package/dist/{index-CI7UllxU.d.cts → index-Dvl0HZkw.d.cts} +66 -64
- package/dist/{index-CI7UllxU.d.ts → index-Dvl0HZkw.d.ts} +66 -64
- package/dist/index.cjs +353 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +353 -249
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +459 -337
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +24 -23
- package/dist/node.d.ts +24 -23
- package/dist/node.js +459 -337
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3289,8 +3289,8 @@ var MemoryStore = class {
|
|
|
3289
3289
|
this.utxos = /* @__PURE__ */ new Map();
|
|
3290
3290
|
this.operations = [];
|
|
3291
3291
|
this.merkleLeavesByChain = /* @__PURE__ */ new Map();
|
|
3292
|
-
this.
|
|
3293
|
-
this.
|
|
3292
|
+
this.chairmanMerkleVersionsByChain = /* @__PURE__ */ new Map();
|
|
3293
|
+
this.chairmanMerkleNodesByChain = /* @__PURE__ */ new Map();
|
|
3294
3294
|
this.entryMemosByChain = /* @__PURE__ */ new Map();
|
|
3295
3295
|
this.entryNullifiersByChain = /* @__PURE__ */ new Map();
|
|
3296
3296
|
const max = options?.maxOperations;
|
|
@@ -3306,8 +3306,8 @@ var MemoryStore = class {
|
|
|
3306
3306
|
this.utxos.clear();
|
|
3307
3307
|
this.operations = [];
|
|
3308
3308
|
this.merkleLeavesByChain.clear();
|
|
3309
|
-
this.
|
|
3310
|
-
this.
|
|
3309
|
+
this.chairmanMerkleVersionsByChain.clear();
|
|
3310
|
+
this.chairmanMerkleNodesByChain.clear();
|
|
3311
3311
|
this.entryMemosByChain.clear();
|
|
3312
3312
|
this.entryNullifiersByChain.clear();
|
|
3313
3313
|
}
|
|
@@ -3423,49 +3423,64 @@ var MemoryStore = class {
|
|
|
3423
3423
|
return { chainId, cid: row.cid, commitment: row.commitment };
|
|
3424
3424
|
}
|
|
3425
3425
|
/**
|
|
3426
|
-
* Get a
|
|
3426
|
+
* Get a chairmanMerkle tree node by id.
|
|
3427
3427
|
*/
|
|
3428
|
-
async
|
|
3429
|
-
return this.
|
|
3428
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
3429
|
+
return this.chairmanMerkleNodesByChain.get(chainId)?.get(id);
|
|
3430
3430
|
}
|
|
3431
3431
|
/**
|
|
3432
|
-
*
|
|
3432
|
+
* Put chairmanMerkle tree nodes for a chain.
|
|
3433
3433
|
*/
|
|
3434
|
-
async
|
|
3434
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
3435
3435
|
if (!nodes.length) return;
|
|
3436
|
-
let map = this.
|
|
3436
|
+
let map = this.chairmanMerkleNodesByChain.get(chainId);
|
|
3437
3437
|
if (!map) {
|
|
3438
3438
|
map = /* @__PURE__ */ new Map();
|
|
3439
|
-
this.
|
|
3439
|
+
this.chairmanMerkleNodesByChain.set(chainId, map);
|
|
3440
3440
|
}
|
|
3441
3441
|
for (const node of nodes) {
|
|
3442
3442
|
map.set(node.id, { ...node, chainId });
|
|
3443
3443
|
}
|
|
3444
3444
|
}
|
|
3445
3445
|
/**
|
|
3446
|
-
*
|
|
3446
|
+
* Get a chairmanMerkle version record by chain and version number.
|
|
3447
3447
|
*/
|
|
3448
|
-
async
|
|
3449
|
-
this.
|
|
3448
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
3449
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3450
|
+
const record = byVersion?.get(version);
|
|
3451
|
+
return record ? { ...record } : void 0;
|
|
3450
3452
|
}
|
|
3451
3453
|
/**
|
|
3452
|
-
* Get
|
|
3454
|
+
* Get the latest chairmanMerkle version record (highest version number) for a chain.
|
|
3453
3455
|
*/
|
|
3454
|
-
async
|
|
3455
|
-
const
|
|
3456
|
-
|
|
3456
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
3457
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3458
|
+
if (!byVersion || byVersion.size === 0) return void 0;
|
|
3459
|
+
let latest;
|
|
3460
|
+
for (const record of byVersion.values()) {
|
|
3461
|
+
if (!latest || record.version > latest.version) {
|
|
3462
|
+
latest = record;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
return latest ? { ...latest } : void 0;
|
|
3457
3466
|
}
|
|
3458
3467
|
/**
|
|
3459
|
-
* Persist
|
|
3468
|
+
* Persist a chairmanMerkle version record.
|
|
3460
3469
|
*/
|
|
3461
|
-
async
|
|
3462
|
-
this.
|
|
3470
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
3471
|
+
let byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3472
|
+
if (!byVersion) {
|
|
3473
|
+
byVersion = /* @__PURE__ */ new Map();
|
|
3474
|
+
this.chairmanMerkleVersionsByChain.set(chainId, byVersion);
|
|
3475
|
+
}
|
|
3476
|
+
byVersion.set(record.version, { ...record, chainId });
|
|
3463
3477
|
}
|
|
3464
3478
|
/**
|
|
3465
|
-
* Clear
|
|
3479
|
+
* Clear all chairmanMerkle tree state (both nodes and versions) for a chain.
|
|
3466
3480
|
*/
|
|
3467
|
-
async
|
|
3468
|
-
this.
|
|
3481
|
+
async clearChairmanMerkleTree(chainId) {
|
|
3482
|
+
this.chairmanMerkleNodesByChain.delete(chainId);
|
|
3483
|
+
this.chairmanMerkleVersionsByChain.delete(chainId);
|
|
3469
3484
|
}
|
|
3470
3485
|
/**
|
|
3471
3486
|
* Upsert entry memos (raw EntryService cache).
|
|
@@ -3989,13 +4004,14 @@ var KeyValueStore = class {
|
|
|
3989
4004
|
this.utxoCache = /* @__PURE__ */ new Map();
|
|
3990
4005
|
this.operationCache = /* @__PURE__ */ new Map();
|
|
3991
4006
|
this.merkleLeafCids = {};
|
|
3992
|
-
this.
|
|
3993
|
-
this.
|
|
4007
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4008
|
+
this.chairmanMerkleNodeIds = {};
|
|
4009
|
+
this.chairmanMerkleVersionNums = {};
|
|
3994
4010
|
this.entryMemoCids = {};
|
|
3995
4011
|
this.entryNullifierNids = {};
|
|
3996
4012
|
this.loadedMerkleLeaves = /* @__PURE__ */ new Set();
|
|
3997
|
-
this.
|
|
3998
|
-
this.
|
|
4013
|
+
this.loadedChairmanMerkleVersions = /* @__PURE__ */ new Set();
|
|
4014
|
+
this.loadedChairmanMerkleNodes = /* @__PURE__ */ new Set();
|
|
3999
4015
|
this.loadedEntryMemos = /* @__PURE__ */ new Set();
|
|
4000
4016
|
this.loadedEntryNullifiers = /* @__PURE__ */ new Set();
|
|
4001
4017
|
this.saveChain = Promise.resolve();
|
|
@@ -4029,9 +4045,6 @@ var KeyValueStore = class {
|
|
|
4029
4045
|
walletOperationKey(id) {
|
|
4030
4046
|
return `${this.walletBaseKey()}:operation:${id}`;
|
|
4031
4047
|
}
|
|
4032
|
-
sharedChainKey(part, chainId) {
|
|
4033
|
-
return `${this.keyPrefix()}:shared:${part}:${chainId}`;
|
|
4034
|
-
}
|
|
4035
4048
|
sharedChainMetaKey(part, chainId) {
|
|
4036
4049
|
return `${this.keyPrefix()}:shared:${part}:${chainId}:meta`;
|
|
4037
4050
|
}
|
|
@@ -4084,13 +4097,14 @@ var KeyValueStore = class {
|
|
|
4084
4097
|
this.operationCache.clear();
|
|
4085
4098
|
this.walletMetaLoaded = false;
|
|
4086
4099
|
this.merkleLeafCids = {};
|
|
4087
|
-
this.
|
|
4088
|
-
this.
|
|
4100
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4101
|
+
this.chairmanMerkleNodeIds = {};
|
|
4102
|
+
this.chairmanMerkleVersionNums = {};
|
|
4089
4103
|
this.entryMemoCids = {};
|
|
4090
4104
|
this.entryNullifierNids = {};
|
|
4091
4105
|
this.loadedMerkleLeaves.clear();
|
|
4092
|
-
this.
|
|
4093
|
-
this.
|
|
4106
|
+
this.loadedChairmanMerkleVersions.clear();
|
|
4107
|
+
this.loadedChairmanMerkleNodes.clear();
|
|
4094
4108
|
this.loadedEntryMemos.clear();
|
|
4095
4109
|
this.loadedEntryNullifiers.clear();
|
|
4096
4110
|
}
|
|
@@ -4185,20 +4199,19 @@ var KeyValueStore = class {
|
|
|
4185
4199
|
this.merkleLeafCids[key] = new Set(this.parseNumberIndex(cidsRaw));
|
|
4186
4200
|
this.loadedMerkleLeaves.add(chainId);
|
|
4187
4201
|
}
|
|
4188
|
-
async
|
|
4189
|
-
if (this.
|
|
4202
|
+
async ensureChairmanMerkleVersionsLoaded(chainId) {
|
|
4203
|
+
if (this.loadedChairmanMerkleVersions.has(chainId)) return;
|
|
4190
4204
|
const key = String(chainId);
|
|
4191
|
-
const
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
this.loadedMerkleTrees.add(chainId);
|
|
4205
|
+
const numsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleVersions", chainId));
|
|
4206
|
+
this.chairmanMerkleVersionNums[key] = new Set(this.parseNumberIndex(numsRaw));
|
|
4207
|
+
this.loadedChairmanMerkleVersions.add(chainId);
|
|
4195
4208
|
}
|
|
4196
|
-
async
|
|
4197
|
-
if (this.
|
|
4209
|
+
async ensureChairmanMerkleNodesLoaded(chainId) {
|
|
4210
|
+
if (this.loadedChairmanMerkleNodes.has(chainId)) return;
|
|
4198
4211
|
const key = String(chainId);
|
|
4199
|
-
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("
|
|
4200
|
-
this.
|
|
4201
|
-
this.
|
|
4212
|
+
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleNodes", chainId));
|
|
4213
|
+
this.chairmanMerkleNodeIds[key] = new Set(this.parseStringIndex(idsRaw));
|
|
4214
|
+
this.loadedChairmanMerkleNodes.add(chainId);
|
|
4202
4215
|
}
|
|
4203
4216
|
async ensureEntryMemosLoaded(chainId) {
|
|
4204
4217
|
if (this.loadedEntryMemos.has(chainId)) return;
|
|
@@ -4214,64 +4227,88 @@ var KeyValueStore = class {
|
|
|
4214
4227
|
this.entryNullifierNids[key] = new Set(this.parseNumberIndex(nidsRaw));
|
|
4215
4228
|
this.loadedEntryNullifiers.add(chainId);
|
|
4216
4229
|
}
|
|
4217
|
-
async
|
|
4218
|
-
await this.
|
|
4219
|
-
if (!this.
|
|
4220
|
-
const raw = await this.options.client.get(this.sharedRecordKey("
|
|
4230
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
4231
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4232
|
+
if (!this.chairmanMerkleNodeIds[String(chainId)]?.has(id)) return void 0;
|
|
4233
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleNodes", chainId, id));
|
|
4221
4234
|
const node = this.parseJson(raw, null);
|
|
4222
4235
|
if (!node) return void 0;
|
|
4223
4236
|
const hash = node.hash;
|
|
4224
4237
|
if (typeof hash !== "string" || !hash.startsWith("0x")) return void 0;
|
|
4225
4238
|
return { ...node, chainId };
|
|
4226
4239
|
}
|
|
4227
|
-
async
|
|
4240
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
4228
4241
|
if (!nodes.length) return;
|
|
4229
|
-
await this.
|
|
4242
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4230
4243
|
const key = String(chainId);
|
|
4231
|
-
const ids = this.
|
|
4244
|
+
const ids = this.chairmanMerkleNodeIds[key] ?? /* @__PURE__ */ new Set();
|
|
4232
4245
|
const beforeSize = ids.size;
|
|
4233
4246
|
for (const node of nodes) {
|
|
4234
4247
|
ids.add(node.id);
|
|
4235
4248
|
}
|
|
4236
|
-
this.
|
|
4249
|
+
this.chairmanMerkleNodeIds[key] = ids;
|
|
4237
4250
|
await this.enqueueWrite(async () => {
|
|
4238
|
-
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("
|
|
4251
|
+
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("chairmanMerkleNodes", chainId, node.id), { ...node, chainId })));
|
|
4239
4252
|
if (ids.size !== beforeSize) {
|
|
4240
|
-
await this.writeJson(this.sharedChainMetaKey("
|
|
4253
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), Array.from(ids));
|
|
4241
4254
|
}
|
|
4242
4255
|
});
|
|
4243
4256
|
}
|
|
4244
|
-
async
|
|
4245
|
-
await this.
|
|
4246
|
-
|
|
4247
|
-
|
|
4257
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
4258
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4259
|
+
if (!this.chairmanMerkleVersionNums[String(chainId)]?.has(version)) return void 0;
|
|
4260
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleVersions", chainId, version));
|
|
4261
|
+
const record = this.parseJson(raw, null);
|
|
4262
|
+
if (!record) return void 0;
|
|
4263
|
+
if (typeof record.rootHash !== "string" || !record.rootHash.startsWith("0x")) return void 0;
|
|
4264
|
+
if (typeof record.rootId !== "string") return void 0;
|
|
4265
|
+
const v = Number(record.version);
|
|
4266
|
+
if (!Number.isFinite(v) || v < 0) return void 0;
|
|
4267
|
+
return { chainId, version: Math.floor(v), rootId: record.rootId, rootHash: record.rootHash };
|
|
4268
|
+
}
|
|
4269
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
4270
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4271
|
+
const nums = this.chairmanMerkleVersionNums[String(chainId)];
|
|
4272
|
+
if (!nums || nums.size === 0) return void 0;
|
|
4273
|
+
const maxVersion = Math.max(...nums);
|
|
4274
|
+
return this.getChairmanMerkleVersion(chainId, maxVersion);
|
|
4275
|
+
}
|
|
4276
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
4277
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4278
|
+
const key = String(chainId);
|
|
4279
|
+
const nums = this.chairmanMerkleVersionNums[key] ?? /* @__PURE__ */ new Set();
|
|
4280
|
+
const beforeSize = nums.size;
|
|
4281
|
+
nums.add(record.version);
|
|
4282
|
+
this.chairmanMerkleVersionNums[key] = nums;
|
|
4283
|
+
const current = this.chairmanMerkleLatestVersions[key];
|
|
4284
|
+
if (!current || record.version >= current.version) {
|
|
4285
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
4286
|
+
}
|
|
4287
|
+
const row = { ...record, chainId };
|
|
4248
4288
|
await this.enqueueWrite(async () => {
|
|
4249
|
-
await
|
|
4250
|
-
|
|
4289
|
+
await this.writeJson(this.sharedRecordKey("chairmanMerkleVersions", chainId, record.version), row);
|
|
4290
|
+
if (nums.size !== beforeSize) {
|
|
4291
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), Array.from(nums).sort((a, b) => a - b));
|
|
4292
|
+
}
|
|
4251
4293
|
});
|
|
4252
4294
|
}
|
|
4253
|
-
async
|
|
4254
|
-
await this.
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
const
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
4262
|
-
return { chainId, root, totalElements: Math.floor(totalElements), lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0 };
|
|
4263
|
-
}
|
|
4264
|
-
async setMerkleTree(chainId, tree) {
|
|
4265
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4266
|
-
const row = { ...tree, chainId };
|
|
4267
|
-
this.merkleTrees[String(chainId)] = row;
|
|
4268
|
-
await this.enqueueWrite(() => this.writeJson(this.sharedChainKey("merkleTrees", chainId), row));
|
|
4269
|
-
}
|
|
4270
|
-
async clearMerkleTree(chainId) {
|
|
4271
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4272
|
-
delete this.merkleTrees[String(chainId)];
|
|
4295
|
+
async clearChairmanMerkleTree(chainId) {
|
|
4296
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4297
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4298
|
+
const nodeIds = Array.from(this.chairmanMerkleNodeIds[String(chainId)] ?? []);
|
|
4299
|
+
const versionNums = Array.from(this.chairmanMerkleVersionNums[String(chainId)] ?? []);
|
|
4300
|
+
delete this.chairmanMerkleNodeIds[String(chainId)];
|
|
4301
|
+
delete this.chairmanMerkleVersionNums[String(chainId)];
|
|
4302
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
4273
4303
|
await this.enqueueWrite(async () => {
|
|
4274
|
-
await
|
|
4304
|
+
await Promise.all([
|
|
4305
|
+
...nodeIds.map((id) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleNodes", chainId, id), null)),
|
|
4306
|
+
...versionNums.map((v) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleVersions", chainId, v), null))
|
|
4307
|
+
]);
|
|
4308
|
+
await Promise.all([
|
|
4309
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), []),
|
|
4310
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), [])
|
|
4311
|
+
]);
|
|
4275
4312
|
});
|
|
4276
4313
|
}
|
|
4277
4314
|
async upsertEntryMemos(memos) {
|
|
@@ -7115,6 +7152,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7115
7152
|
this.hydrateInFlight = /* @__PURE__ */ new Map();
|
|
7116
7153
|
this.mode = options?.mode ?? "hybrid";
|
|
7117
7154
|
this.treeDepth = Math.max(1, Math.floor(options?.treeDepth ?? TREE_DEPTH_DEFAULT));
|
|
7155
|
+
this.readContractRoot = options?.readContractRoot;
|
|
7118
7156
|
}
|
|
7119
7157
|
/**
|
|
7120
7158
|
* Compute the current merkle root index from total elements.
|
|
@@ -7123,9 +7161,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7123
7161
|
if (totalElements <= tempArraySize) return 0;
|
|
7124
7162
|
return Math.floor((totalElements - 1) / tempArraySize);
|
|
7125
7163
|
}
|
|
7126
|
-
/**
|
|
7127
|
-
* Get or initialize the pending leaf buffer for a chain.
|
|
7128
|
-
*/
|
|
7129
7164
|
ensurePendingLeaves(chainId) {
|
|
7130
7165
|
let pending = this.pendingLeavesByChain.get(chainId);
|
|
7131
7166
|
if (!pending) {
|
|
@@ -7134,9 +7169,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7134
7169
|
}
|
|
7135
7170
|
return pending;
|
|
7136
7171
|
}
|
|
7137
|
-
/**
|
|
7138
|
-
* Get or initialize chain-level merkle state.
|
|
7139
|
-
*/
|
|
7140
7172
|
ensureChainState(chainId) {
|
|
7141
7173
|
let state = this.chainStateByChain.get(chainId);
|
|
7142
7174
|
if (!state) {
|
|
@@ -7145,15 +7177,32 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7145
7177
|
}
|
|
7146
7178
|
return state;
|
|
7147
7179
|
}
|
|
7148
|
-
|
|
7149
|
-
* Poseidon2 merkle hash for a left/right pair.
|
|
7150
|
-
*/
|
|
7180
|
+
// ── Hashing ──
|
|
7151
7181
|
static hashPair(left, right) {
|
|
7152
7182
|
return Poseidon2.hashToHex(BigInt(left), BigInt(right), Poseidon2Domain.Merkle);
|
|
7153
7183
|
}
|
|
7184
|
+
static normalizeHex32(value, name) {
|
|
7185
|
+
try {
|
|
7186
|
+
const bi = BigInt(value);
|
|
7187
|
+
if (bi < 0n) throw new Error("negative");
|
|
7188
|
+
const hex = bi.toString(16).padStart(64, "0");
|
|
7189
|
+
if (hex.length > 64) throw new Error("too_large");
|
|
7190
|
+
return `0x${hex}`;
|
|
7191
|
+
} catch (error) {
|
|
7192
|
+
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7193
|
+
}
|
|
7194
|
+
}
|
|
7195
|
+
// ── Static helpers ──
|
|
7196
|
+
static totalElementsInTree(totalElements, tempArraySize = TEMP_ARRAY_SIZE_DEFAULT) {
|
|
7197
|
+
if (tempArraySize <= 0) throw new SdkError("MERKLE", "tempArraySize must be greater than zero", { tempArraySize });
|
|
7198
|
+
if (totalElements <= 0n) return 0;
|
|
7199
|
+
const size = BigInt(tempArraySize);
|
|
7200
|
+
return Number((totalElements - 1n) / size * size);
|
|
7201
|
+
}
|
|
7202
|
+
// ── Subtree (levels 0-5, 32 leaves → 1 root) ──
|
|
7154
7203
|
/**
|
|
7155
7204
|
* Build a fixed-depth subtree from 32 contiguous leaves.
|
|
7156
|
-
* Returns the subtree root and all intermediate nodes for storage.
|
|
7205
|
+
* Returns the subtree root hash and all intermediate nodes for storage.
|
|
7157
7206
|
*/
|
|
7158
7207
|
static buildSubtree(leafCommitments, baseIndex) {
|
|
7159
7208
|
if (leafCommitments.length !== SUBTREE_SIZE) {
|
|
@@ -7174,71 +7223,71 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7174
7223
|
const position = basePos + i;
|
|
7175
7224
|
nodesToStore.push({
|
|
7176
7225
|
chainId: 0,
|
|
7177
|
-
id:
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7226
|
+
id: `st-${level}-${position}`,
|
|
7227
|
+
hash: next[i],
|
|
7228
|
+
leftId: null,
|
|
7229
|
+
rightId: null
|
|
7181
7230
|
});
|
|
7182
7231
|
}
|
|
7183
7232
|
currentLevel = next;
|
|
7184
7233
|
}
|
|
7185
7234
|
return { subtreeRoot: currentLevel[0], nodesToStore };
|
|
7186
7235
|
}
|
|
7236
|
+
// ── ChairmanMerkle tree (persistent segment tree, levels 5-32) ──
|
|
7187
7237
|
/**
|
|
7188
|
-
*
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
*
|
|
7196
|
-
*/
|
|
7197
|
-
async
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
const
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7238
|
+
* Insert a subtree root into the persistent main tree.
|
|
7239
|
+
*
|
|
7240
|
+
* Top-down recursive: descends from root (level treeDepth) to the target
|
|
7241
|
+
* leaf position (level SUBTREE_DEPTH). At each level only the node on the
|
|
7242
|
+
* update path is newly created; the sibling is shared from the previous
|
|
7243
|
+
* version's tree.
|
|
7244
|
+
*
|
|
7245
|
+
* @returns new root node ID/hash and all newly created nodes.
|
|
7246
|
+
*/
|
|
7247
|
+
async insertSubtreeRoot(chainId, prevRootId, subtreeRootHash, batchIndex, version) {
|
|
7248
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7249
|
+
const nodes = [];
|
|
7250
|
+
const descend = async (nodeId, depth) => {
|
|
7251
|
+
const originalLevel = this.treeDepth - depth;
|
|
7252
|
+
if (depth === MAIN_DEPTH) {
|
|
7253
|
+
const newId2 = `cm-${version}-${originalLevel}`;
|
|
7254
|
+
nodes.push({ chainId, id: newId2, hash: subtreeRootHash, leftId: null, rightId: null });
|
|
7255
|
+
return { id: newId2, hash: subtreeRootHash };
|
|
7256
|
+
}
|
|
7257
|
+
let prevLeftId = null;
|
|
7258
|
+
let prevRightId = null;
|
|
7259
|
+
if (nodeId) {
|
|
7260
|
+
const prevNode = await this.storage?.getChairmanMerkleNode?.(chainId, nodeId);
|
|
7261
|
+
if (prevNode) {
|
|
7262
|
+
prevLeftId = prevNode.leftId;
|
|
7263
|
+
prevRightId = prevNode.rightId;
|
|
7213
7264
|
}
|
|
7214
|
-
currentValue = _MerkleEngine.hashPair(currentValue, getZeroHash(level));
|
|
7215
|
-
} else {
|
|
7216
|
-
const leftHash = await this.getNodeHash(input.chainId, `frontier-${level}`) ?? getZeroHash(level);
|
|
7217
|
-
currentValue = _MerkleEngine.hashPair(leftHash, currentValue);
|
|
7218
7265
|
}
|
|
7219
|
-
const
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
hash:
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7266
|
+
const childLevel = originalLevel - 1;
|
|
7267
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7268
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7269
|
+
let leftResult;
|
|
7270
|
+
let rightResult;
|
|
7271
|
+
if (goRight) {
|
|
7272
|
+
const leftHash = prevLeftId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevLeftId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7273
|
+
leftResult = { id: prevLeftId, hash: leftHash };
|
|
7274
|
+
const right = await descend(prevRightId, depth + 1);
|
|
7275
|
+
rightResult = { id: right.id, hash: right.hash };
|
|
7276
|
+
} else {
|
|
7277
|
+
const left = await descend(prevLeftId, depth + 1);
|
|
7278
|
+
leftResult = { id: left.id, hash: left.hash };
|
|
7279
|
+
const rightHash = prevRightId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevRightId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7280
|
+
rightResult = { id: prevRightId, hash: rightHash };
|
|
7281
|
+
}
|
|
7282
|
+
const hash = _MerkleEngine.hashPair(leftResult.hash, rightResult.hash);
|
|
7283
|
+
const newId = `cm-${version}-${originalLevel}`;
|
|
7284
|
+
nodes.push({ chainId, id: newId, hash, leftId: leftResult.id, rightId: rightResult.id });
|
|
7285
|
+
return { id: newId, hash };
|
|
7286
|
+
};
|
|
7287
|
+
const root = await descend(prevRootId, 0);
|
|
7288
|
+
return { rootId: root.id, rootHash: root.hash, nodes };
|
|
7238
7289
|
}
|
|
7239
|
-
|
|
7240
|
-
* Hydrate local merkle state from storage on first use.
|
|
7241
|
-
*/
|
|
7290
|
+
// ── Hydration ──
|
|
7242
7291
|
async hydrateFromStorage(chainId) {
|
|
7243
7292
|
if (this.mode === "remote") return;
|
|
7244
7293
|
if (this.hydratedChains.has(chainId)) return;
|
|
@@ -7247,31 +7296,17 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7247
7296
|
const task = (async () => {
|
|
7248
7297
|
try {
|
|
7249
7298
|
const state = this.ensureChainState(chainId);
|
|
7250
|
-
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7251
|
-
if (!leaves || leaves.length === 0) return;
|
|
7252
|
-
const sorted = [...leaves].map((l) => ({
|
|
7253
|
-
cid: l.cid,
|
|
7254
|
-
commitment: _MerkleEngine.normalizeHex32(l.commitment, "memo.commitment")
|
|
7255
|
-
})).sort((a, b) => a.cid - b.cid);
|
|
7256
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
7257
|
-
if (sorted[i].cid !== i) throw new Error(`Non-contiguous persisted merkle leaves: expected cid=${i}, got cid=${sorted[i].cid}`);
|
|
7258
|
-
}
|
|
7259
|
-
const storedTree = await this.storage?.getMerkleTree?.(chainId);
|
|
7260
|
-
const leafCount = sorted.length;
|
|
7261
|
-
const mergedFromLeaves = _MerkleEngine.totalElementsInTree(BigInt(leafCount));
|
|
7262
|
-
const mergedFromTree = typeof storedTree?.totalElements === "number" && storedTree.totalElements > 0 ? storedTree.totalElements : 0;
|
|
7263
|
-
const mergedElements = Math.max(mergedFromLeaves, mergedFromTree);
|
|
7264
7299
|
const pending = this.ensurePendingLeaves(chainId);
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7300
|
+
const latest = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7301
|
+
if (latest) {
|
|
7302
|
+
state.mergedElements = latest.version;
|
|
7303
|
+
state.root = _MerkleEngine.normalizeHex32(latest.rootHash, "chairmanMerkleVersion.rootHash");
|
|
7269
7304
|
}
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7305
|
+
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7306
|
+
if (leaves && leaves.length > state.mergedElements) {
|
|
7307
|
+
const sorted = [...leaves].sort((a, b) => a.cid - b.cid).slice(state.mergedElements);
|
|
7308
|
+
pending.length = 0;
|
|
7309
|
+
pending.push(...sorted.map((l) => _MerkleEngine.normalizeHex32(l.commitment, "leaf.commitment")));
|
|
7275
7310
|
}
|
|
7276
7311
|
} catch (error) {
|
|
7277
7312
|
if (this.mode === "hybrid") return;
|
|
@@ -7284,26 +7319,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7284
7319
|
this.hydrateInFlight.set(chainId, task);
|
|
7285
7320
|
return task;
|
|
7286
7321
|
}
|
|
7287
|
-
|
|
7288
|
-
* Normalize unknown values to a 32-byte hex string.
|
|
7289
|
-
*/
|
|
7290
|
-
static normalizeHex32(value, name) {
|
|
7291
|
-
try {
|
|
7292
|
-
const bi = BigInt(value);
|
|
7293
|
-
if (bi < 0n) throw new Error("negative");
|
|
7294
|
-
const hex = bi.toString(16).padStart(64, "0");
|
|
7295
|
-
if (hex.length > 64) throw new Error("too_large");
|
|
7296
|
-
return `0x${hex}`;
|
|
7297
|
-
} catch (error) {
|
|
7298
|
-
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7299
|
-
}
|
|
7300
|
-
}
|
|
7301
|
-
/**
|
|
7302
|
-
* Feed contiguous (cid-ordered) memo leaves into the local merkle tree.
|
|
7303
|
-
*
|
|
7304
|
-
* This mirrors the client/app behavior: only after we have a full consecutive batch of 32 leaves
|
|
7305
|
-
* do we merge them into the main tree. Leaves that are still in the buffer do not get local proofs.
|
|
7306
|
-
*/
|
|
7322
|
+
// ── Ingestion ──
|
|
7307
7323
|
async ingestEntryMemos(chainId, memos) {
|
|
7308
7324
|
if (this.mode === "remote") return;
|
|
7309
7325
|
await this.hydrateFromStorage(chainId);
|
|
@@ -7331,26 +7347,42 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7331
7347
|
expected++;
|
|
7332
7348
|
while (pending.length >= SUBTREE_SIZE) {
|
|
7333
7349
|
const batch = pending.splice(0, SUBTREE_SIZE);
|
|
7334
|
-
const
|
|
7335
|
-
const subtree = _MerkleEngine.buildSubtree(batch,
|
|
7336
|
-
const
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
const
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7350
|
+
const batchIndex = state.mergedElements / SUBTREE_SIZE;
|
|
7351
|
+
const subtree = _MerkleEngine.buildSubtree(batch, state.mergedElements);
|
|
7352
|
+
const subtreeNodes = subtree.nodesToStore.map((n) => ({ ...n, chainId }));
|
|
7353
|
+
const prevVersion = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7354
|
+
const prevRootId = prevVersion?.rootId ?? null;
|
|
7355
|
+
const newVersion = state.mergedElements + SUBTREE_SIZE;
|
|
7356
|
+
const result = await this.insertSubtreeRoot(chainId, prevRootId, subtree.subtreeRoot, batchIndex, newVersion);
|
|
7357
|
+
if (this.readContractRoot) {
|
|
7358
|
+
const rootIndex = newVersion / SUBTREE_SIZE;
|
|
7359
|
+
const onChainRoot = await this.readContractRoot(chainId, rootIndex).catch(() => null);
|
|
7360
|
+
if (onChainRoot !== null) {
|
|
7361
|
+
const onChainNorm = _MerkleEngine.normalizeHex32(onChainRoot, "onChainRoot");
|
|
7362
|
+
const isZero = BigInt(onChainNorm) === 0n;
|
|
7363
|
+
if (!isZero && onChainNorm !== result.rootHash) {
|
|
7364
|
+
const target = state.mergedElements;
|
|
7365
|
+
await this.rollback(chainId, target);
|
|
7366
|
+
throw new SdkError("MERKLE", "Local merkle root mismatch with on-chain root \u2014 rolled back", {
|
|
7367
|
+
chainId,
|
|
7368
|
+
rootIndex,
|
|
7369
|
+
localRoot: result.rootHash,
|
|
7370
|
+
onChainRoot: onChainNorm,
|
|
7371
|
+
version: newVersion,
|
|
7372
|
+
rollbackTarget: target
|
|
7373
|
+
});
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
await this.storage?.putChairmanMerkleNodes?.(chainId, [...subtreeNodes, ...result.nodes]);
|
|
7378
|
+
await this.storage?.putChairmanMerkleVersion?.(chainId, {
|
|
7349
7379
|
chainId,
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7380
|
+
version: newVersion,
|
|
7381
|
+
rootId: result.rootId,
|
|
7382
|
+
rootHash: result.rootHash
|
|
7353
7383
|
});
|
|
7384
|
+
state.mergedElements = newVersion;
|
|
7385
|
+
state.root = result.rootHash;
|
|
7354
7386
|
}
|
|
7355
7387
|
}
|
|
7356
7388
|
this.hydratedChains.add(chainId);
|
|
@@ -7361,15 +7393,59 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7361
7393
|
throw new SdkError("MERKLE", "Failed to ingest local merkle leaves", { chainId, leafCount: leaves.length }, error);
|
|
7362
7394
|
}
|
|
7363
7395
|
}
|
|
7396
|
+
// ── Rollback (tree O(1) + sync cursor reset) ──
|
|
7364
7397
|
/**
|
|
7365
|
-
*
|
|
7398
|
+
* Unified rollback: rewind tree to a previous batch boundary AND reset the
|
|
7399
|
+
* sync cursor so memo sync restarts from the same point.
|
|
7400
|
+
*
|
|
7401
|
+
* What gets rolled back:
|
|
7402
|
+
* - ChairmanMerkle tree version pointer (O(1) — old nodes still in storage)
|
|
7403
|
+
* - Pending leaves buffer (cleared)
|
|
7404
|
+
* - Sync cursor: memo + merkle fields (nullifier left unchanged — independent)
|
|
7405
|
+
*
|
|
7406
|
+
* @param targetMergedElements Must be a non-negative multiple of 32.
|
|
7407
|
+
* Pass 0 to reset to the empty tree.
|
|
7408
|
+
* @returns true if rollback succeeded, false if the target version doesn't exist.
|
|
7366
7409
|
*/
|
|
7367
|
-
async
|
|
7368
|
-
|
|
7410
|
+
async rollback(chainId, targetMergedElements) {
|
|
7411
|
+
if (targetMergedElements < 0 || targetMergedElements % SUBTREE_SIZE !== 0) {
|
|
7412
|
+
throw new SdkError("MERKLE", "rollback target must be a non-negative multiple of 32", { targetMergedElements });
|
|
7413
|
+
}
|
|
7414
|
+
const state = this.ensureChainState(chainId);
|
|
7415
|
+
const pending = this.ensurePendingLeaves(chainId);
|
|
7416
|
+
if (targetMergedElements === 0) {
|
|
7417
|
+
state.mergedElements = 0;
|
|
7418
|
+
state.root = getZeroHash(this.treeDepth);
|
|
7419
|
+
pending.length = 0;
|
|
7420
|
+
await this.resetSyncCursor(chainId, 0);
|
|
7421
|
+
return true;
|
|
7422
|
+
}
|
|
7423
|
+
const version = await this.storage?.getChairmanMerkleVersion?.(chainId, targetMergedElements);
|
|
7424
|
+
if (!version) return false;
|
|
7425
|
+
state.mergedElements = targetMergedElements;
|
|
7426
|
+
state.root = _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash");
|
|
7427
|
+
pending.length = 0;
|
|
7428
|
+
this.hydratedChains.add(chainId);
|
|
7429
|
+
await this.resetSyncCursor(chainId, targetMergedElements);
|
|
7430
|
+
return true;
|
|
7369
7431
|
}
|
|
7370
7432
|
/**
|
|
7371
|
-
*
|
|
7433
|
+
* Reset the sync cursor's memo field to `targetMemo` (and derive merkle cursor),
|
|
7434
|
+
* but only if the current cursor is ahead of the target.
|
|
7435
|
+
* Nullifier cursor is left unchanged — nullifiers are independent of tree state.
|
|
7372
7436
|
*/
|
|
7437
|
+
async resetSyncCursor(chainId, targetMemo) {
|
|
7438
|
+
if (!this.storage?.getSyncCursor || !this.storage?.setSyncCursor) return;
|
|
7439
|
+
const cursor = await this.storage.getSyncCursor(chainId);
|
|
7440
|
+
if (!cursor || cursor.memo <= targetMemo) return;
|
|
7441
|
+
cursor.memo = targetMemo;
|
|
7442
|
+
cursor.merkle = this.currentMerkleRootIndex(targetMemo);
|
|
7443
|
+
await this.storage.setSyncCursor(chainId, cursor);
|
|
7444
|
+
}
|
|
7445
|
+
// ── Proof generation ──
|
|
7446
|
+
async getProofByCid(input) {
|
|
7447
|
+
return this.getProofByCids({ chainId: input.chainId, cids: [input.cid], totalElements: input.totalElements });
|
|
7448
|
+
}
|
|
7373
7449
|
async getProofByCids(input) {
|
|
7374
7450
|
const cids = [...input.cids];
|
|
7375
7451
|
if (cids.length === 0) throw new SdkError("MERKLE", "No cids provided", { chainId: input.chainId });
|
|
@@ -7384,15 +7460,16 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7384
7460
|
await this.hydrateFromStorage(input.chainId);
|
|
7385
7461
|
const canUseLocal = this.mode !== "remote";
|
|
7386
7462
|
if (canUseLocal) {
|
|
7387
|
-
const
|
|
7388
|
-
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.
|
|
7389
|
-
if (hasDb
|
|
7390
|
-
|
|
7463
|
+
const version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
|
|
7464
|
+
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.getChairmanMerkleNode === "function" && (contractTreeElements === 0 || !!version);
|
|
7465
|
+
if (hasDb) {
|
|
7466
|
+
const state = this.ensureChainState(input.chainId);
|
|
7467
|
+
if (contractTreeElements > 0 && state.mergedElements < contractTreeElements) {
|
|
7391
7468
|
if (this.mode === "local") {
|
|
7392
7469
|
throw new SdkError("MERKLE", "Local merkle db is behind contract", {
|
|
7393
7470
|
chainId: input.chainId,
|
|
7394
7471
|
cids,
|
|
7395
|
-
|
|
7472
|
+
localMergedElements: state.mergedElements,
|
|
7396
7473
|
contractTreeElements
|
|
7397
7474
|
});
|
|
7398
7475
|
}
|
|
@@ -7404,30 +7481,13 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7404
7481
|
proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
|
|
7405
7482
|
continue;
|
|
7406
7483
|
}
|
|
7407
|
-
const
|
|
7408
|
-
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7409
|
-
const path = [leaf.commitment];
|
|
7410
|
-
for (let level = 1; level <= this.treeDepth; level++) {
|
|
7411
|
-
const siblingIndex = cid >> level - 1 ^ 1;
|
|
7412
|
-
if (level === 1) {
|
|
7413
|
-
const siblingLeaf = await this.storage.getMerkleLeaf(input.chainId, siblingIndex);
|
|
7414
|
-
path.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7415
|
-
continue;
|
|
7416
|
-
}
|
|
7417
|
-
const targetLevel = level - 1;
|
|
7418
|
-
const siblingNode = await this.storage.getMerkleNode(input.chainId, `${targetLevel}-${siblingIndex}`);
|
|
7419
|
-
path.push(siblingNode?.hash ?? getZeroHash(targetLevel));
|
|
7420
|
-
}
|
|
7484
|
+
const path = await this.buildLocalProofPath(input.chainId, cid, version);
|
|
7421
7485
|
proof.push({ leaf_index: cid, path });
|
|
7422
7486
|
}
|
|
7423
|
-
|
|
7424
|
-
if (tree.totalElements > contractTreeElements && contractTreeElements > 0) {
|
|
7425
|
-
const checkpoint = await this.storage.getMerkleNode(input.chainId, `checkpoint-${contractTreeElements}`);
|
|
7426
|
-
if (checkpoint) effectiveRoot = checkpoint.hash;
|
|
7427
|
-
}
|
|
7487
|
+
const effectiveRoot = contractTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
|
|
7428
7488
|
return {
|
|
7429
7489
|
proof,
|
|
7430
|
-
merkle_root:
|
|
7490
|
+
merkle_root: effectiveRoot,
|
|
7431
7491
|
latest_cid: totalElements > 0n ? Number(totalElements - 1n) : -1
|
|
7432
7492
|
};
|
|
7433
7493
|
} catch (error) {
|
|
@@ -7437,7 +7497,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7437
7497
|
}
|
|
7438
7498
|
}
|
|
7439
7499
|
} else if (this.mode === "local" && needsTreeProof.length) {
|
|
7440
|
-
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "
|
|
7500
|
+
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
|
|
7441
7501
|
}
|
|
7442
7502
|
}
|
|
7443
7503
|
if (needsTreeProof.length === 0) {
|
|
@@ -7462,15 +7522,64 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7462
7522
|
};
|
|
7463
7523
|
}
|
|
7464
7524
|
/**
|
|
7465
|
-
*
|
|
7525
|
+
* Build a local proof path by traversing the chairmanMerkle tree.
|
|
7526
|
+
*
|
|
7527
|
+
* Levels 0-4: sibling hashes from subtree internal nodes (st-{level}-{pos}).
|
|
7528
|
+
* Levels 5-31: sibling hashes from chairmanMerkle tree traversal (top-down from version root).
|
|
7466
7529
|
*/
|
|
7530
|
+
async buildLocalProofPath(chainId, cid, version) {
|
|
7531
|
+
const leaf = await this.storage.getMerkleLeaf(chainId, cid);
|
|
7532
|
+
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7533
|
+
const path = [leaf.commitment];
|
|
7534
|
+
for (let level = 1; level <= SUBTREE_DEPTH; level++) {
|
|
7535
|
+
const siblingPos = cid >> level - 1 ^ 1;
|
|
7536
|
+
if (level === 1) {
|
|
7537
|
+
const siblingLeaf = await this.storage.getMerkleLeaf(chainId, siblingPos);
|
|
7538
|
+
path.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7539
|
+
} else {
|
|
7540
|
+
const targetLevel = level - 1;
|
|
7541
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, `st-${targetLevel}-${siblingPos}`);
|
|
7542
|
+
path.push(node?.hash ?? getZeroHash(targetLevel));
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
const batchIndex = cid >> SUBTREE_DEPTH;
|
|
7546
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7547
|
+
const mainSiblings = [];
|
|
7548
|
+
let nodeId = version.rootId;
|
|
7549
|
+
for (let depth = 0; depth < MAIN_DEPTH; depth++) {
|
|
7550
|
+
const childLevel = this.treeDepth - depth - 1;
|
|
7551
|
+
if (!nodeId) {
|
|
7552
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7553
|
+
continue;
|
|
7554
|
+
}
|
|
7555
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, nodeId);
|
|
7556
|
+
if (!node) {
|
|
7557
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7558
|
+
nodeId = null;
|
|
7559
|
+
continue;
|
|
7560
|
+
}
|
|
7561
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7562
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7563
|
+
if (goRight) {
|
|
7564
|
+
const leftNode = node.leftId ? await this.storage.getChairmanMerkleNode(chainId, node.leftId) : null;
|
|
7565
|
+
mainSiblings.push(leftNode?.hash ?? getZeroHash(childLevel));
|
|
7566
|
+
nodeId = node.rightId;
|
|
7567
|
+
} else {
|
|
7568
|
+
const rightNode = node.rightId ? await this.storage.getChairmanMerkleNode(chainId, node.rightId) : null;
|
|
7569
|
+
mainSiblings.push(rightNode?.hash ?? getZeroHash(childLevel));
|
|
7570
|
+
nodeId = node.leftId;
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
for (let i = mainSiblings.length - 1; i >= 0; i--) {
|
|
7574
|
+
path.push(mainSiblings[i]);
|
|
7575
|
+
}
|
|
7576
|
+
return path;
|
|
7577
|
+
}
|
|
7578
|
+
// ── Remote helpers ──
|
|
7467
7579
|
async fetchRemoteRootOnly(chainId) {
|
|
7468
7580
|
const remote = await this.fetchRemoteProofFromService({ chainId, cids: [0] });
|
|
7469
7581
|
return _MerkleEngine.normalizeHex32(remote.merkle_root, "remote.merkle_root");
|
|
7470
7582
|
}
|
|
7471
|
-
/**
|
|
7472
|
-
* Fetch proofs from the remote merkle service.
|
|
7473
|
-
*/
|
|
7474
7583
|
async fetchRemoteProofFromService(input) {
|
|
7475
7584
|
const chain = this.getChain(input.chainId);
|
|
7476
7585
|
if (!chain.merkleProofUrl) {
|
|
@@ -7479,9 +7588,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7479
7588
|
const client = new MerkleClient(chain.merkleProofUrl);
|
|
7480
7589
|
return client.getProofByCids(input.cids);
|
|
7481
7590
|
}
|
|
7482
|
-
|
|
7483
|
-
* Build membership witnesses for provided UTXOs from a remote proof response.
|
|
7484
|
-
*/
|
|
7591
|
+
// ── Witness builders (unchanged) ──
|
|
7485
7592
|
buildAccMemberWitnesses(input) {
|
|
7486
7593
|
return input.utxos.map((utxo, idx) => {
|
|
7487
7594
|
const remoteProof = input.remote.proof[idx];
|
|
@@ -7495,9 +7602,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7495
7602
|
};
|
|
7496
7603
|
});
|
|
7497
7604
|
}
|
|
7498
|
-
/**
|
|
7499
|
-
* Convert UTXOs into circuit input secrets, decrypting memos and padding if needed.
|
|
7500
|
-
*/
|
|
7501
7605
|
async buildInputSecretsFromUtxos(input) {
|
|
7502
7606
|
if (!Array.isArray(input.utxos) || input.utxos.length === 0) {
|
|
7503
7607
|
throw new SdkError("MERKLE", "No utxos provided", { count: 0 });
|