UNPKG

@nori-zk/ethprocessor

Version:

zkApp for verifying SP1 Helios Nori proof and storing latest execution state root on Mina

229 lines 8.63 kB
import { Field, Poseidon } from 'o1js'; /** * Compute next power of two >= n */ export function nextPowerOfTwo(n) { if (n <= 1) return 1; return 1 << (32 - Math.clz32(n - 1)); } /** * Compute depth and padded size for Merkle tree */ export function computeMerkleTreeDepthAndSize(nLeaves) { const paddedSize = nextPowerOfTwo(nLeaves); const depth = Math.log2(paddedSize); return { depth, paddedSize }; } /** * Generate zero hashes array of length depth + 1 */ export function getMerkleZeros(depth) { const zeros = []; // Start with zeros[0] = Field(0) zeros.push(Field(0)); for (let i = 1; i < depth + 1; i++) { // Each next zero is hash of the previous zero with itself zeros.push(Poseidon.hash([zeros[i - 1], zeros[i - 1]])); } return zeros; } /** * Build full Merkle tree layers (root at index 0, leaves at index depth) * @param merkleLeaves Unpadded leaves as Field[] * @param paddedSize number, power of two >= merkleLeaves.length * @param depth number, log2(paddedSize) * @param zeros precomputed zero hashes per level (length depth + 1) * @returns Field[][], each element a layer of the tree, from root (0) to leaves (depth) */ /** * Build the full Merkle tree from leaves, padding and folding with zeros cache. * @param merkleLeaves Field[] initial leaves (unpadded) * @param paddedSize number padded size (power of two) * @param depth number tree depth * @param zeros Field[] array of zero hashes for each level * @returns Field[][] full Merkle tree, root at index 0, leaves at depth index */ export function buildMerkleTree(merkleLeaves, paddedSize, depth, zeros) { const nLeaves = merkleLeaves.length; const missing = paddedSize - nLeaves; // Clone and pad leaves const leaves = merkleLeaves.slice(); for (let i = 0; i < missing; i++) { leaves.push(Field(0)); } const merkleTree = new Array(depth + 1); merkleTree[depth] = leaves; let nNonDummyNodes = nLeaves; // Build tree from leaves upward for (let level = depth; level > 0; level--) { const childLevel = merkleTree[level]; const parentWidth = 1 << (level - 1); const parentLevel = new Array(parentWidth); for (let i = 0; i < parentWidth; i++) { const leftIdx = 2 * i; if (leftIdx >= nNonDummyNodes) { // Both left and right dummy nodes, use zeros cache parentLevel[i] = zeros[level]; } else { const rightIdx = leftIdx + 1; // Atleast one of left and right are real. parentLevel[i] = Poseidon.hash([ childLevel[leftIdx], childLevel[rightIdx], ]); } } merkleTree[level - 1] = parentLevel; // Shrink count of non-dummy nodes for next iteration nNonDummyNodes = Math.floor((nNonDummyNodes + 1) / 2); } return merkleTree; } /** * Fold Merkle tree bottom-up to get root; modifies leaves in-place. * @param leaves Field[], initial leaves (unpadded), will be padded in-place * @param paddedSize number, total size padded to power of two * @param depth number, tree depth * @param zeros Field[] array of zero hashes per level * @returns Field root hash */ export function foldMerkleLeft(leaves, paddedSize, depth, zeros) { if (leaves.length === 0) { return Field(0); } const nLeaves = leaves.length; // Pad leaves to paddedSize with zeros const missing = paddedSize - nLeaves; for (let i = 0; i < missing; i++) { leaves.push(Field(0)); } let nNonDummyNodes = nLeaves; // Iterate from top tree level down to leaves for (let level = depth; level > 0; level--) { const levelWidth = 1 << level; // 2^level const parentWidth = levelWidth >> 1; // half the level width for (let i = 0; i < parentWidth; i++) { const leftIdx = 2 * i; if (leftIdx >= nNonDummyNodes) { // Both left and right are dummy nodes — use cached zero for this level leaves[i] = zeros[level]; } else { // Atleast one of left and right are real. const rightIdx = leftIdx + 1; leaves[i] = Poseidon.hash([leaves[leftIdx], leaves[rightIdx]]); } } // Shrink non-dummy node count as we move up the tree nNonDummyNodes = Math.floor((nNonDummyNodes + 1) / 2); } return leaves[0]; } /** * Compute the Merkle path (sibling nodes) for a given leaf index from * a list of leaves. * * This function mutates and extends the provided `merkleLeaves` array * by padding it to `paddedSize` with zeros, then folds the tree upwards, * storing intermediate hashes back into the same array. * * It returns the vector of sibling hashes from the leaf level up to the root. * * @param merkleLeaves Mutable array of leaf node hashes (Field elements) * @param paddedSize Size to which leaves should be padded (power of two) * @param depth Depth of the Merkle tree * @param index Leaf index for which to compute the Merkle path * @param zeros Array of zero hashes per level for dummy nodes (length >= depth + 1) * @returns Array of sibling hashes (Field[]) forming the Merkle path */ export function getMerklePathFromLeaves(merkleLeaves, paddedSize, depth, index, zeros) { if (merkleLeaves.length === 0) { return []; } // Number of real leaves before padding const nLeaves = merkleLeaves.length; // Pad leaves with zeros to the padded size const missing = paddedSize - nLeaves; for (let i = 0; i < missing; i++) { merkleLeaves.push(Field(0)); } // Reuse the same array to store intermediate hashes const merkleNodes = merkleLeaves; const path = []; let position = index; let nNonDummyNodes = nLeaves; // Iterate from tree bottom (level = depth) to top (level = 1) for (let level = depth; level >= 1; level--) { const siblingIndex = position % 2 === 1 ? position - 1 : position + 1; // Sibling node on the same level const sibling = merkleNodes[siblingIndex]; path.push(sibling); const levelWidth = 1 << level; // 2^level nodes at current level // Compute parent nodes by hashing pairs of children for (let i = 0; i < levelWidth / 2; i++) { const leftIdx = 2 * i; if (leftIdx >= nNonDummyNodes) { // Both left and right are dummy nodes; use zero hash for this level merkleNodes[i] = zeros[level]; } else { const rightIdx = leftIdx + 1; // Atleast one of left and right are real. merkleNodes[i] = Poseidon.hash([ merkleNodes[leftIdx], merkleNodes[rightIdx], ]); } } // Move to the next level up position = Math.floor(position / 2); nNonDummyNodes = Math.floor((nNonDummyNodes + 1) / 2); } return path; } /** * Compute the Merkle path (sibling nodes) for a leaf index from a fully built Merkle tree. * * Assumes the Merkle tree is represented as an array of levels, * where `merkleTree[0]` is the root level (1 node), * and `merkleTree[depth]` is the leaf level. * * @param merkleTree Array of tree levels, each a Field[] array of nodes * @param index Leaf index for which to compute the Merkle path * @returns Array of sibling hashes (Field[]) forming the Merkle path */ export function getMerklePathFromTree(merkleTree, index) { const depth = merkleTree.length - 1; const path = []; let pos = index; // Traverse from leaf level up to root (levels: depth → 1) for (let level = depth; level >= 1; level--) { const sibling = pos ^ 1; // bitwise sibling index path.push(merkleTree[level][sibling]); pos = Math.floor(pos / 2); } return path; } /** * Compute Merkle root from leaf hash, leaf index, and Merkle path * @param leafHash Field * @param index number leaf index * @param path Field[] sibling nodes * @returns Field root hash */ export function computeMerkleRootFromPath(leafHash, index, path) { let acc = leafHash; let pos = index; for (const sibling of path) { if ((pos & 1) === 0) { acc = Poseidon.hash([acc, sibling]); } else { acc = Poseidon.hash([sibling, acc]); } pos >>= 1; } return acc; } //# sourceMappingURL=merkleTree.js.map