@nori-zk/o1js-zk-utils
Version:
o1js-zk-utils supporting Nori Bridge
142 lines • 7.51 kB
JavaScript
import { UInt64 } from 'o1js';
import { merkleLeafAttestorGenerator } from './merkleLeafAttestor.js';
import { Bytes32 } from '../types.js';
import { Logger, LogPrinter } from 'esm-iso-logger';
import { wordToBytes } from '@nori-zk/proof-conversion';
import { computeMerkleTreeDepthAndSize, foldMerkleLeft, getMerkleZeros, } from './merkleTree.js';
import { buildLeavesNonProvable, dummyCodeChallenge, dummyValue, nonProvableStorageSlotLeafHash, provableLeafContentsHash, ProvableLeafObject, } from './testUtils.js';
const logger = new Logger('TestMerkle');
new LogPrinter('TestO1JsZkUtils');
const { MerkleTreeLeafAttestorInput, MerkleTreeLeafAttestor, buildLeaves, getMerklePathFromLeaves, } = merkleLeafAttestorGenerator(16, 'MyMerkleVerifier', ProvableLeafObject, provableLeafContentsHash);
describe('Merkle Attestor Test', () => {
test('compute_non_provable_storage_slot_leaf_hash', () => {
const slot = {
slot_key_code_challenge: '0x2f000000000000000000000000000000000000000000000000038d7ec293e52f',
value: '0xe8d4a51000',
};
console.log(slot);
// FIXME probably all need to be padded
const codeChallenge = Bytes32.fromHex(slot.slot_key_code_challenge.slice(2).padStart(64, '0'));
const valuePad = slot.value.slice(2).padStart(64, '0');
console.log('padded value', valuePad);
const value = Bytes32.fromHex(valuePad);
const hash = nonProvableStorageSlotLeafHash(codeChallenge, value);
console.log(`Hash result big int: ${hash.toBigInt()}`);
console.log(`Hash result bytes: ${wordToBytes(hash, 32).map((byte) => byte.toNumber())}`);
console.log(`Hash result hex: ${wordToBytes(hash, 32)
.map((byte) => byte.toNumber().toString(16).padStart(2, '0'))
.join('')}`);
const hash2 = provableLeafContentsHash(new ProvableLeafObject({ codeChallenge, value }));
console.log('Provable hash result', hash2.toBigInt().toString());
console.log(`Provable hash result hex: ${wordToBytes(hash2, 32)
.map((byte) => byte.toNumber().toString(16).padStart(2, '0'))
.join('')}`);
});
test('test_all_leaf_counts_and_indices_with_pipeline', async () => {
// Analyse zk program
const merkleTreeLeafAttestorAnalysis = await MerkleTreeLeafAttestor.analyzeMethods();
logger.log(`MerkleTreeLeafAttestor analyze methods gates length '${merkleTreeLeafAttestorAnalysis.compute.gates.length}'.`);
// Build zk program
const { verificationKey } = await MerkleTreeLeafAttestor.compile({
forceRecompile: true,
});
logger.log(`MerkleTreeLeafAttestor contract compiled vk: '${verificationKey.hash}'.`);
const maxLeaves = 10;
const maxDepth = Math.ceil(Math.log2(maxLeaves)) || 1;
const zeros = getMerkleZeros(maxDepth);
console.log('Testing all leaf counts and indices with both fold and circuit...');
for (let nLeaves = 0; nLeaves <= maxLeaves; nLeaves++) {
console.log(`→ Testing with ${nLeaves} leaves`);
const pairs = [];
for (let i = 0; i < nLeaves; i++) {
pairs.push([
dummyCodeChallenge(i),
dummyValue(i),
]);
}
const leafObjects = [];
for (let i = 0; i < nLeaves; i++) {
leafObjects.push(new ProvableLeafObject({
codeChallenge: pairs[i][0],
value: pairs[i][1],
}));
}
const leaves = buildLeaves(leafObjects);
console.log(` leaves ${leaves.map((l) => l.toJSON().split('\n').join(' ,'))}`);
const rustLeaves = buildLeavesNonProvable(pairs);
const { depth, paddedSize } = computeMerkleTreeDepthAndSize(nLeaves);
console.log(` depth=${depth}, paddedSize=${paddedSize}`);
/*console.log(
'LEAVES COMPARISON',
JSON.stringify(leaves),
JSON.stringify(rustLeaves)
);*/
expect(leaves).toEqual(rustLeaves);
const rootViaFold = foldMerkleLeft(rustLeaves, paddedSize, depth, zeros);
console.log(` rootViaFold = ${rootViaFold}`);
for (let index = 0; index < nLeaves; index++) {
const pathFold = getMerklePathFromLeaves(leaves.slice(), index);
const slotToFind = leafObjects[index];
const input = new MerkleTreeLeafAttestorInput({
path: pathFold,
index: UInt64.from(index),
value: slotToFind,
});
const output = await MerkleTreeLeafAttestor.compute(input);
expect(output.proof.publicOutput.toBigInt()).toBe(rootViaFold.toBigInt());
console.log(` ✅ [nLeaves=${nLeaves}, index=${index}] OK`);
}
}
}, 1000000000);
test('huge_2pow16_leaves_provable_test', async () => {
const merkleTreeLeafAttestorAnalysis = await MerkleTreeLeafAttestor.analyzeMethods();
logger.log(`MerkleTreeLeafAttestor analyze methods gates length '${merkleTreeLeafAttestorAnalysis.compute.gates.length}'.`);
const { verificationKey } = await MerkleTreeLeafAttestor.compile({
forceRecompile: true,
});
logger.log(`MerkleTreeLeafAttestor contract compiled vk: '${verificationKey.hash}'.`);
const nLeaves = 2 ** 16; // 65536
console.log(`Building ${nLeaves} provable leaves (this may use significant memory)...`);
const pairs = new Array(nLeaves);
for (let i = 0; i < nLeaves; i++) {
pairs[i] = [dummyCodeChallenge(i), dummyValue(i)];
}
const leafObjects = new Array(nLeaves);
for (let i = 0; i < nLeaves; i++) {
leafObjects[i] = new ProvableLeafObject({
codeChallenge: pairs[i][0],
value: pairs[i][1],
});
}
const leaves = buildLeaves(leafObjects);
const { depth, paddedSize } = computeMerkleTreeDepthAndSize(nLeaves);
const zeros = getMerkleZeros(depth);
console.log(` depth=${depth}, paddedSize=${paddedSize}`);
const rustLeaves = buildLeavesNonProvable(pairs);
const rootViaFold = foldMerkleLeft(rustLeaves, paddedSize, depth, zeros);
console.log(` rootViaFold = ${rootViaFold.toBigInt()}`);
const indicesToCheck = [
0,
1,
Math.floor(nLeaves / 2),
nLeaves - 2,
nLeaves - 1,
];
for (const index of indicesToCheck) {
console.log(`Verifying index ${index} / ${nLeaves}`);
const pathFold = getMerklePathFromLeaves(leaves.slice(), index);
const slotToFind = leafObjects[index];
const input = new MerkleTreeLeafAttestorInput({
path: pathFold,
index: UInt64.from(index),
value: slotToFind,
});
const t0 = Date.now();
const output = await MerkleTreeLeafAttestor.compute(input);
const t1 = Date.now();
expect(output.proof.publicOutput.toBigInt()).toBe(rootViaFold.toBigInt());
console.log(` ✅ [nLeaves=${nLeaves}, index=${index}] OK (took ${t1 - t0} ms)`);
}
}, 1000000000);
});
//# sourceMappingURL=merkleLeafAttestor.spec.js.map