o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
294 lines (247 loc) • 9.54 kB
text/typescript
import { Bool, Field } from '../wrapped.js';
import { MerkleTree, conditionalSwap } from '../merkle-tree.js';
import { Random, test } from '../../testing/property.js';
import { expect } from 'expect';
import { MerkleMap, MerkleMapWitness } from '../merkle-map.js';
import { IndexedMerkleMap, Leaf } from '../merkle-tree-indexed.js';
import { synchronousRunners } from '../core/provable-context.js';
import { Provable } from '../provable.js';
import { constraintSystem } from '../../testing/constraint-system.js';
import { field } from '../../testing/equivalent.js';
import { throwError } from './test-utils.js';
const height = 31;
const IndexedMap30 = IndexedMerkleMap(height);
const indexedMap = new IndexedMap30();
// compare constraints used by indexed merkle map (with 1B leaves) vs sparse merkle map
console.log(
'indexed merkle map (get)',
constraintSystem.size({ from: [field] }, (key) => {
indexedMap.get(key);
})
);
console.log(
'indexed merkle map (get option)',
constraintSystem.size({ from: [field] }, (key) => {
indexedMap.getOption(key);
})
);
console.log(
'sparse merkle map (get)',
constraintSystem.size({ from: [field, field, field, field] }, (root, key, value) => {
let mapWitness = Provable.witness(MerkleMapWitness, () => throwError('unused'));
let [actualRoot, actualKey] = mapWitness.computeRootAndKey(value);
key.assertEquals(actualKey);
root.assertEquals(actualRoot);
})
);
console.log(
'\nindexed merkle map (insert)',
constraintSystem.size({ from: [field, field] }, (key, value) => {
indexedMap.insert(key, value);
})
);
console.log(
'indexed merkle map (update)',
constraintSystem.size({ from: [field, field] }, (key, value) => {
indexedMap.update(key, value);
})
);
console.log(
'indexed merkle map (set)',
constraintSystem.size({ from: [field, field] }, (key, value) => {
indexedMap.set(key, value);
})
);
console.log(
'sparse merkle map (set)',
constraintSystem.size({ from: [field, field, field, field] }, (root, key, oldValue, value) => {
let mapWitness = Provable.witness(MerkleMapWitness, () => throwError('unused'));
let [actualRoot, actualKey] = mapWitness.computeRootAndKey(oldValue);
key.assertEquals(actualKey);
root.assertEquals(actualRoot);
let [_newRoot] = mapWitness.computeRootAndKey(value);
})
);
console.log(
'\nindexed merkle map (assert included)',
constraintSystem.size({ from: [field] }, (key) => {
indexedMap.assertIncluded(key);
})
);
console.log(
'indexed merkle map (assert not included)',
constraintSystem.size({ from: [field] }, (key) => {
indexedMap.assertNotIncluded(key);
})
);
console.log(
'indexed merkle map (is included)',
constraintSystem.size({ from: [field] }, (key) => {
indexedMap.isIncluded(key);
})
);
// some manual tests for IndexedMerkleMap
{
let map = new (IndexedMerkleMap(3))();
const minus1 = Field.ORDER - 1n;
// there's 1 element in the map at the beginning
// check initial root against `MerkleTree` implementation
expect(map.length.toBigInt()).toEqual(1n);
let initialTree = new MerkleTree(3);
initialTree.setLeaf(0n, Leaf.hashNode(IndexedMerkleMap(3)._firstLeaf));
expect(map.root).toEqual(initialTree.getRoot());
// the initial value at key 0 is 0
expect(map.getOption(0n).assertSome().toBigInt()).toEqual(0n);
map.insert(-1n, 14n); // -1 is the largest possible value
map.insert(1n, 13n);
expect(map.getOption(1n).assertSome().toBigInt()).toEqual(13n);
expect(map.getOption(-1n).assertSome().toBigInt()).toEqual(14n);
expect(map.getOption(3n).isSome.toBoolean()).toEqual(false);
map.update(-1n, 15n);
map.update(0n, 12n);
expect(map.getOption(-1n).assertSome().toBigInt()).toEqual(15n);
expect(map.get(0n).toBigInt()).toEqual(12n);
expect(map.getOption(0n).assertSome().toBigInt()).toEqual(12n);
// can't insert the same key twice
expect(() => map.insert(1n, 17n)).toThrow('Key already exists');
// can't update a non-existent key
expect(() => map.update(3n, 16n)).toThrow('Key does not exist');
map.set(4n, 16n);
map.set(1n, 17n);
map.set(0n, 12n);
expect(map.get(4n).toBigInt()).toEqual(16n);
expect(map.getOption(1n).assertSome().toBigInt()).toEqual(17n);
expect(map.getOption(5n).isSome.toBoolean()).toEqual(false);
// can't insert more than 2^(height - 1) = 2^2 = 4 keys
expect(() => map.insert(8n, 19n)).toThrow('4 does not fit in 2 bits');
expect(() => map.set(8n, 19n)).toThrow('4 does not fit in 2 bits');
// check that length is as expected
expect(map.length.toBigInt()).toEqual(4n);
// check that internal nodes exactly match `MerkleTree` implementation
let keys = [0n, minus1, 1n, 4n]; // insertion order
let leafNodes = keys.map((key) => Leaf.hashNode(map._findLeaf(key).self));
let tree = new MerkleTree(3);
tree.fill(leafNodes);
let nodes = map.data.get().nodes;
for (let level = 0; level < 3; level++) {
for (let i = 0; i < 2 ** (2 - level); i++) {
expect(nodes[level][i]).toEqual(tree.nodes[level][i].toBigInt());
}
}
// check that internal `sortedLeaves` are as expected
// data sorted by key:
let sorted = [
{ key: 0n, value: 12n, index: 0 },
{ key: 1n, value: 17n, index: 2 },
{ key: 4n, value: 16n, index: 3 },
{ key: minus1, value: 15n, index: 1 },
];
let sortedLeaves = map.data.get().sortedLeaves;
for (let i = 0; i < 4; i++) {
expect(sortedLeaves[i]).toEqual({
key: sorted[i].key,
value: sorted[i].value,
nextKey: sorted[i + 1]?.key ?? 0n,
index: sorted[i].index,
});
}
}
// property tests for indexed merkle map
let uniformField = Random.map(Random(Field.random), (x) => x.toBigInt());
let { runAndCheckSync } = await synchronousRunners();
let n = 5;
test(
Random.array(uniformField, n),
Random.array(uniformField, n),
Random.array(Random.field, n),
Random.array(Random.field, n),
Random.int(6, 40),
Random.int(0, n - 1),
(initialKeys, keys, initialValues, values, height, i0) => {
class MerkleMap extends IndexedMerkleMap(height) {}
// fill a merkle map with the initial keys and values outside provable code
let map = new MerkleMap();
for (let i = 0; i < n; i++) {
map.insert(initialKeys[i], initialValues[i]);
}
const witness = (x: Field | bigint) => Provable.witness(Field, () => x);
// pass the map to a circuit
runAndCheckSync(() => {
map = Provable.witness(MerkleMap, () => map);
let initialKeysF = initialKeys.map(witness);
let keysF = keys.map(witness);
let valuesF = values.map(witness);
for (let i = 0; i < n; i++) {
// confirm we still have the same keys and values
map.getOption(initialKeysF[i]).assertSome().assertEquals(initialValues[i]);
// new keys are not in the map
map.getOption(keysF[i]).isSome.assertFalse();
}
// can't update a non-existent key
expect(() => map.update(keysF[i0], valuesF[i0])).toThrow('Key does not exist');
// set initial values at new keys, and values at initial keys
for (let i = 0; i < n; i++) {
map.set(keysF[i], initialValues[i]);
map.set(initialKeysF[i], valuesF[i]);
}
// check that the updated keys and values are in the map
for (let i = 0; i < n; i++) {
map.getOption(keysF[i]).assertSome().assertEquals(initialValues[i]);
map.get(initialKeysF[i]).assertEquals(valuesF[i]);
}
// update the new keys with the new values
for (let i = 0; i < n; i++) {
map.update(keys[i], valuesF[i]);
}
// move the map back to constants
Provable.asProver(() => {
map = Provable.toConstant(MerkleMap, map);
});
});
// check that the map is still the same
for (let i = 0; i < n; i++) {
expect(map.getOption(keys[i]).assertSome()).toEqual(Field(values[i]));
expect(map.getOption(initialKeys[i]).assertSome()).toEqual(Field(values[i]));
}
// random element is not in the map
expect(map.getOption(Field.random()).isSome).toEqual(Bool(false));
// length is as expected
expect(map.length).toEqual(Field(2 * n + 1));
// creating a new map with the same key-value pairs, where keys are inserted in the same order, gives the same root
let map2 = new MerkleMap();
for (let i = 0; i < n; i++) {
map2.insert(initialKeys[i], values[i]);
}
for (let i = 0; i < n; i++) {
map2.insert(keys[i], values[i]);
}
expect(map.root).toEqual(map2.root);
}
);
// property tests for conditional swap
test(Random.bool, Random.field, Random.field, (b, x, y) => {
let [x0, y0] = conditionalSwap(Bool(!!b), Field(x), Field(y));
// if the boolean is true, it shouldn't swap the fields; otherwise, it should
if (b) {
expect(x0.toBigInt()).toEqual(x);
expect(y0.toBigInt()).toEqual(y);
} else {
expect(x0.toBigInt()).toEqual(y);
expect(y0.toBigInt()).toEqual(x);
}
});
// property tests for merkle map
test(Random.field, (key) => {
let map = new MerkleMap();
// Check that the key fits in 254 bits, if it doesn't, we should throw an error (since the Pasta field modulus is smaller than 2^255)
if (key > 2n ** 254n) {
expect(() => {
let witness = map.getWitness(Field(key));
witness.computeRootAndKey(Field(0));
}).toThrowError();
} else {
let witness = map.getWitness(Field(key));
let [, calculatedKey] = witness.computeRootAndKey(Field(0));
expect(calculatedKey.toBigInt()).toEqual(key);
}
});