snarky-smt
Version:
Sparse Merkle Tree for SnarkyJS
113 lines (112 loc) • 4.5 kB
JavaScript
import { Circuit } from 'snarkyjs';
import { CP_PADD_VALUE, CSMT_DEPTH, PLACEHOLDER } from './constant';
import { TreeHasher } from './tree_hasher';
export { ProvableCSMTUtils };
/**
* Collection of utility functions for compact sparse merkle tree in the circuit.
*
* @class ProvableCSMTUtils
*/
class ProvableCSMTUtils {
/**
* Returns true if the value is in the tree and it is at the index from the key
*
* @static
* @template K
* @template V
* @param {CompactSparseMerkleProof} proof
* @param {Field} expectedRoot
* @param {K} key
* @param {Provable<K>} keyType
* @param {V} value
* @param {Provable<V>} valueType
* @param {{
* treeHasher: TreeHasher<K, V>;
* hashKey: boolean;
* hashValue: boolean;
* }} [options={
* treeHasher: TreeHasher.poseidon(),
* hashKey: true,
* hashValue: true,
* }] treeHasher: The tree hasher function to use, defaults to TreeHasher.poseidon; hashKey:
* whether to hash the key, the default is true; hashValue: whether to hash the value,
* the default is true.
* @return {*} {Bool}
* @memberof ProvableCSMTUtils
*/
static checkMembership(proof, expectedRoot, key, keyType, value, valueType, options = {
treeHasher: TreeHasher.poseidon(keyType, valueType),
hashKey: true,
hashValue: true,
}) {
let th = options.treeHasher;
let keyHashOrKeyField = null;
if (options.hashKey) {
keyHashOrKeyField = th.path(key);
}
else {
keyHashOrKeyField = keyType.toFields(key)[0];
}
let valueHashOrValueField = null;
if (options.hashValue) {
valueHashOrValueField = th.digestValue(value);
}
else {
valueHashOrValueField = valueType.toFields(value)[0];
}
const path = keyHashOrKeyField;
let currentHash = th.digestLeaf(path, valueHashOrValueField).hash;
const currentRoot = computeRootInCircuit(proof.sideNodes, path, currentHash, th);
return expectedRoot.equals(currentRoot);
}
/**
* Returns true if there is no value at the index from the key
*
* @static
* @template K
* @template V
* @param {CompactSparseMerkleProof} proof
* @param {Field} expectedRoot
* @param {K} key
* @param {Provable<K>} keyType
* @param {{ treeHasher: TreeHasher<K, V>; hashKey: boolean }} [options={
* treeHasher: TreeHasher.poseidon(),
* hashKey: true,
* }] treeHasher: The tree hasher function to use, defaults to TreeHasher.poseidon;
* hashKey: whether to hash the key, the default is true
* @return {*} {Bool}
* @memberof ProvableCSMTUtils
*/
static checkNonMembership(proof, expectedRoot, key, keyType, options = {
treeHasher: TreeHasher.poseidon(keyType),
hashKey: true,
}) {
let th = options.treeHasher;
let keyHashOrKeyField = null;
if (options.hashKey) {
keyHashOrKeyField = th.path(key);
}
else {
keyHashOrKeyField = keyType.toFields(key)[0];
}
const path = keyHashOrKeyField;
const { path: actualPath, leaf: leafData } = th.parseLeaf(proof.nonMembershipLeafData);
let currentHash = Circuit.if(th.isEmptyDataInCircuit(proof.nonMembershipLeafData), PLACEHOLDER, th.digestLeaf(actualPath, leafData).hash);
const currentRoot = computeRootInCircuit(proof.sideNodes, path, currentHash, th);
return expectedRoot.equals(currentRoot);
}
}
function computeRootInCircuit(sideNodes, keyHashOrKeyField, valueHashOrValueField, th = TreeHasher.poseidon()) {
const path = keyHashOrKeyField;
let currentHash = valueHashOrValueField;
const pathBits = path.toBits(CSMT_DEPTH);
//Recompute root
for (let i = 0, len = sideNodes.length; i < len; i++) {
let node = sideNodes[i];
// right node
currentHash = Circuit.if(pathBits[len - 1 - i].and(node.equals(CP_PADD_VALUE).not()), th.digestNode(node, currentHash).hash, currentHash);
// left node
currentHash = Circuit.if(pathBits[len - 1 - i].not().and(node.equals(CP_PADD_VALUE).not()), th.digestNode(currentHash, node).hash, currentHash);
}
return currentHash;
}