UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

294 lines (247 loc) 9.54 kB
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); } });