Skip to content

Commit 6cd7408

Browse files
committed
refactor: include length in root
1 parent 7b3bf1f commit 6cd7408

File tree

2 files changed

+26
-15
lines changed

2 files changed

+26
-15
lines changed

src/lib/provable/merkle-tree-indexed.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function IndexedMerkleMap(height: number): typeof IndexedMerkleMapBase {
7171
}
7272

7373
const provableBase = {
74-
root: Field,
74+
_internalRoot: Field,
7575
length: Field,
7676
data: Unconstrained.withEmpty({
7777
nodes: [] as (bigint | undefined)[][],
@@ -81,7 +81,7 @@ const provableBase = {
8181

8282
class IndexedMerkleMapBase {
8383
// data defining the provable interface of a tree
84-
root: Field;
84+
_internalRoot: Field;
8585
length: Field; // length of the leaves array
8686

8787
// static data defining constraints
@@ -102,6 +102,14 @@ class IndexedMerkleMapBase {
102102
readonly sortedLeaves: StoredLeaf[];
103103
}>;
104104

105+
/**
106+
* Public getter for the root that combines the the root of the indexed merkle tree and length.
107+
* This ensures length is included in the commitment.
108+
*/
109+
get root(): Field {
110+
return Poseidon.hash([this._internalRoot, this.length]);
111+
}
112+
105113
// we'd like to do `abstract static provable` here but that's not supported
106114
static provable: Provable<IndexedMerkleMapBase, InferValue<typeof provableBase>> =
107115
undefined as any;
@@ -120,7 +128,7 @@ class IndexedMerkleMapBase {
120128
let firstLeaf = IndexedMerkleMapBase._firstLeaf;
121129
let firstNode = Leaf.hashNode(firstLeaf).toBigInt();
122130
let root = Nodes.setLeaf(nodes, 0, firstNode);
123-
this.root = Field(root);
131+
this._internalRoot = Field(root);
124132
this.length = Field(1);
125133

126134
this.data = Unconstrained.from({ nodes, sortedLeaves: [firstLeaf] });
@@ -142,7 +150,7 @@ class IndexedMerkleMapBase {
142150
*/
143151
clone() {
144152
let cloned = new (this.constructor as typeof IndexedMerkleMapBase)();
145-
cloned.root = this.root;
153+
cloned._internalRoot = this._internalRoot;
146154
cloned.length = this.length;
147155
cloned.data.updateAsProver(() => {
148156
let { nodes, sortedLeaves } = this.data.get();
@@ -171,7 +179,7 @@ class IndexedMerkleMapBase {
171179
overwriteIf(condition: Bool | boolean, other: IndexedMerkleMapBase) {
172180
condition = Bool(condition);
173181

174-
this.root = Provable.if(condition, other.root, this.root);
182+
this._internalRoot = Provable.if(condition, other._internalRoot, this._internalRoot);
175183
this.length = Provable.if(condition, other.length, this.length);
176184
this.data.updateAsProver(() =>
177185
Bool(condition).toBoolean() ? other.clone().data.get() : this.data.get()
@@ -201,7 +209,7 @@ class IndexedMerkleMapBase {
201209

202210
// update low node
203211
let newLow = { ...low, nextKey: key };
204-
this.root = this._proveUpdate(newLow, lowPath);
212+
this._internalRoot = this._proveUpdate(newLow, lowPath);
205213
this._setLeafUnconstrained(true, newLow);
206214

207215
// create new leaf to append
@@ -213,7 +221,7 @@ class IndexedMerkleMapBase {
213221

214222
// prove empty slot in the tree, and insert our leaf
215223
let path = this._proveEmpty(indexBits);
216-
this.root = this._proveUpdate(leaf, path);
224+
this._internalRoot = this._proveUpdate(leaf, path);
217225
this.length = this.length.add(1);
218226
this._setLeafUnconstrained(false, leaf);
219227
}
@@ -238,7 +246,7 @@ class IndexedMerkleMapBase {
238246

239247
// update leaf
240248
let newSelf = { ...self, value };
241-
this.root = this._proveUpdate(newSelf, path);
249+
this._internalRoot = this._proveUpdate(newSelf, path);
242250
this._setLeafUnconstrained(true, newSelf);
243251

244252
return self.value;
@@ -275,7 +283,7 @@ class IndexedMerkleMapBase {
275283

276284
// update low node, or leave it as is
277285
let newLow = { ...low, nextKey: key };
278-
this.root = this._proveUpdate(newLow, lowPath);
286+
this._internalRoot = this._proveUpdate(newLow, lowPath);
279287
this._setLeafUnconstrained(true, newLow);
280288

281289
// prove inclusion of this leaf if it exists
@@ -288,7 +296,7 @@ class IndexedMerkleMapBase {
288296
value,
289297
nextKey: Provable.if(keyExists, self.nextKey, low.nextKey),
290298
});
291-
this.root = this._proveUpdate(newLeaf, path);
299+
this._internalRoot = this._proveUpdate(newLeaf, path);
292300
this.length = Provable.if(keyExists, this.length, this.length.add(1));
293301
this._setLeafUnconstrained(keyExists, newLeaf);
294302

@@ -403,7 +411,7 @@ class IndexedMerkleMapBase {
403411
let node = Leaf.hashNode(leaf);
404412
// here, we don't care at which index the leaf is included, so we pass it in as unconstrained
405413
let { root, path } = this._computeRoot(node, leaf.index);
406-
root.assertEquals(this.root, message ?? 'Leaf is not included in the tree');
414+
root.assertEquals(this._internalRoot, message ?? 'Leaf is not included in the tree');
407415

408416
return path;
409417
}
@@ -416,7 +424,7 @@ class IndexedMerkleMapBase {
416424
// here, we don't care at which index the leaf is included, so we pass it in as unconstrained
417425
let { root } = this._computeRoot(node, leaf.index);
418426
assert(
419-
condition.implies(root.equals(this.root)),
427+
condition.implies(root.equals(this._internalRoot)),
420428
message ?? 'Leaf is not included in the tree'
421429
);
422430
}
@@ -429,7 +437,7 @@ class IndexedMerkleMapBase {
429437
_proveEmpty(index: Bool[]) {
430438
let node = Field(0n);
431439
let { root, path } = this._computeRoot(node, index);
432-
root.assertEquals(this.root, 'Leaf is not empty');
440+
root.assertEquals(this._internalRoot, 'Leaf is not empty');
433441

434442
return path;
435443
}
@@ -442,7 +450,7 @@ class IndexedMerkleMapBase {
442450
_proveInclusionOrEmpty(condition: Bool, index: Bool[], leaf: BaseLeaf, message?: string) {
443451
let node = Provable.if(condition, Leaf.hashNode(leaf), Field(0n));
444452
let { root, path } = this._computeRoot(node, index);
445-
root.assertEquals(this.root, message ?? 'Leaf is not included in the tree');
453+
root.assertEquals(this._internalRoot, message ?? 'Leaf is not included in the tree');
446454

447455
return path;
448456
}

src/lib/provable/test/merkle-tree.unit-test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Provable } from '../provable.js';
99
import { constraintSystem } from '../../testing/constraint-system.js';
1010
import { field } from '../../testing/equivalent.js';
1111
import { throwError } from './test-utils.js';
12+
import { Poseidon } from '../crypto/poseidon.js';
1213

1314
const height = 31;
1415
const IndexedMap30 = IndexedMerkleMap(height);
@@ -99,7 +100,9 @@ console.log(
99100
expect(map.length.toBigInt()).toEqual(1n);
100101
let initialTree = new MerkleTree(3);
101102
initialTree.setLeaf(0n, Leaf.hashNode(IndexedMerkleMap(3)._firstLeaf));
102-
expect(map.root).toEqual(initialTree.getRoot());
103+
// The root now combines the internal root with the length for security
104+
let expectedRoot = Poseidon.hash([initialTree.getRoot(), Field(1)]);
105+
expect(map.root).toEqual(expectedRoot);
103106

104107
// the initial value at key 0 is 0
105108
expect(map.getOption(0n).assertSome().toBigInt()).toEqual(0n);

0 commit comments

Comments
 (0)