snarky-smt
Version:
Sparse Merkle Tree for SnarkyJS
192 lines (191 loc) • 6.91 kB
JavaScript
import { Circuit, Poseidon } from 'snarkyjs';
import { EMPTY_VALUE, SMT_DEPTH } from '../constant';
export { ProvableSMTUtils };
/**
* Collection of utility functions for sparse merkle tree in the circuit.
*
* @class ProvableSMTUtils
*/
class ProvableSMTUtils {
/**
* Returns true if the value is in the tree and it is at the index from the key
*
* @static
* @template K
* @template V
* @param {SparseMerkleProof} proof
* @param {Field} expectedRoot
* @param {K} key
* @param {Provable<K>} keyType
* @param {V} value
* @param {Provable<V>} valueType
* @param {{ hasher?: Hasher; hashKey: boolean; hashValue: boolean }} [options={
* hasher: Poseidon.hash,
* hashKey: true,
* hashValue: true,
* }] hasher: The hash function to use, defaults to Poseidon.hash; hashKey:
* whether to hash the key, the default is true; hashValue: whether to hash the value,
* the default is true.
* @return {*} {Bool}
* @memberof ProvableSMTUtils
*/
static checkMembership(proof, expectedRoot, key, keyType, value, valueType, options = {
hasher: Poseidon.hash,
hashKey: true,
hashValue: true,
}) {
let hasher = Poseidon.hash;
if (options.hasher !== undefined) {
hasher = options.hasher;
}
let keyFields = keyType.toFields(key);
let valueFields = valueType.toFields(value);
let keyHashOrKeyField = keyFields[0];
if (options.hashKey) {
keyHashOrKeyField = hasher(keyFields);
}
let valueHashOrValueField = valueFields[0];
if (options.hashValue) {
valueHashOrValueField = hasher(valueFields);
}
return verifyProofByFieldInCircuit(proof, expectedRoot, keyHashOrKeyField, valueHashOrValueField, hasher);
}
/**
* Returns true if there is no value at the index from the key
*
* @static
* @template K
* @param {SparseMerkleProof} proof
* @param {Field} expectedRoot
* @param {K} key
* @param {Provable<K>} keyType
* @param {{ hasher?: Hasher; hashKey: boolean }} [options={
* hasher: Poseidon.hash,
* hashKey: true,
* }] hasher: The hash function to use, defaults to Poseidon.hash; hashKey:
* whether to hash the key, the default is true; hashValue: whether to hash the value,
* the default is true.
* @return {*} {Bool}
* @memberof ProvableSMTUtils
*/
static checkNonMembership(proof, expectedRoot, key, keyType, options = {
hasher: Poseidon.hash,
hashKey: true,
}) {
let hasher = Poseidon.hash;
if (options.hasher !== undefined) {
hasher = options.hasher;
}
let keyFields = keyType.toFields(key);
let keyHashOrKeyField = keyFields[0];
if (options.hashKey) {
keyHashOrKeyField = hasher(keyFields);
}
return verifyProofByFieldInCircuit(proof, expectedRoot, keyHashOrKeyField, EMPTY_VALUE, hasher);
}
/**
* Calculate new root based on sideNodes, key and value
*
* @static
* @template K
* @template V
* @param {Field[]} sideNodes
* @param {K} key
* @param {Provable<K>} keyType
* @param {V} value
* @param {Provable<V>} valueType
* @param {{ hasher?: Hasher; hashKey: boolean; hashValue: boolean }} [options={
* hasher: Poseidon.hash,
* hashKey: true,
* hashValue: true,
* }]
* @return {*} {Field}
* @memberof ProvableSMTUtils
*/
static computeRoot(sideNodes, key, keyType, value, valueType, options = {
hasher: Poseidon.hash,
hashKey: true,
hashValue: true,
}) {
let hasher = Poseidon.hash;
if (options.hasher !== undefined) {
hasher = options.hasher;
}
let keyFields = keyType.toFields(key);
let valueFields = valueType.toFields(value);
let keyHashOrKeyField = keyFields[0];
if (options.hashKey) {
keyHashOrKeyField = hasher(keyFields);
}
let valueHashOrValueField = valueFields[0];
if (options.hashValue) {
valueHashOrValueField = hasher(valueFields);
}
return computeRootByFieldInCircuit(sideNodes, keyHashOrKeyField, valueHashOrValueField, options.hasher);
}
}
/**
* Empty value for sparse merkle tree
*
* @static
* @memberof ProvableSMTUtils
*/
ProvableSMTUtils.EMPTY_VALUE = EMPTY_VALUE;
/**
* Verify a merkle proof by root, keyHashOrKeyField and valueHashOrValueField
*
* @static
* @param {SparseMerkleProof} proof
* @param {Field} expectedRoot
* @param {Field} keyHashOrKeyField
* @param {Field} valueHashOrValueField
* @param {Hasher} [hasher=Poseidon.hash]
* @return {*} {Bool}
* @memberof ProvableSMTUtils
*/
ProvableSMTUtils.verifyProofByField = verifyProofByFieldInCircuit;
/**
* Calculate new root based on sideNodes, keyHashOrKeyField and valueHashOrValueField
*
* @static
* @param {Field[]} sideNodes
* @param {Field} keyHashOrKeyField
* @param {Field} valueHashOrValueField
* @param {Hasher} [hasher=Poseidon.hash]
* @return {*} {Field}
* @memberof ProvableSMTUtils
*/
ProvableSMTUtils.computeRootByField = computeRootByFieldInCircuit;
/**
* Verify a merkle proof by root, keyHashOrKeyField and valueHashOrValueField in circuit.
*
* @param {SparseMerkleProof} proof
* @param {Field} expectedRoot
* @param {Field} keyHashOrKeyField
* @param {Field} valueHashOrValueField
* @param {Hasher} [hasher=Poseidon.hash]
* @return {*} {Bool}
*/
function verifyProofByFieldInCircuit(proof, expectedRoot, keyHashOrKeyField, valueHashOrValueField, hasher = Poseidon.hash) {
const currentRoot = computeRootByFieldInCircuit(proof.sideNodes, keyHashOrKeyField, valueHashOrValueField, hasher);
return expectedRoot.equals(currentRoot);
}
/**
* Calculate new root based on sideNodes, keyHashOrKeyField and valueHashOrValueField in circuit.
*
* @param {Field[]} sideNodes
* @param {Field} keyHashOrKeyField
* @param {Field} valueHashOrValueField
* @param {Hasher} [hasher=Poseidon.hash]
* @return {*} {Field}
*/
function computeRootByFieldInCircuit(sideNodes, keyHashOrKeyField, valueHashOrValueField, hasher = Poseidon.hash) {
let currentHash = valueHashOrValueField;
const pathBits = keyHashOrKeyField.toBits(SMT_DEPTH);
for (let i = SMT_DEPTH - 1; i >= 0; i--) {
let node = sideNodes[i];
let currentValue = Circuit.if(pathBits[i], [node, currentHash], [currentHash, node]);
currentHash = hasher(currentValue);
}
return currentHash;
}