UNPKG

@iden3/js-merkletree

Version:

javascript sparse merkle tree library

146 lines (145 loc) 5.15 kB
import { ELEM_BYTES_LEN, NOT_EMPTIES_LEN, PROOF_FLAG_LEN } from '../../constants'; import { bytesEqual, getPath, setBitBigEndian, siblings2Bytes, testBitBigEndian } from '../utils'; import { Hash, ZERO_HASH } from '../hash/hash'; import { NodeMiddle } from '../node/node'; import { leafKey } from '../utils/node'; import { ErrNodeAuxNonExistAgainstHIndex } from '../errors/proof'; export class Proof { constructor(obj) { this.existence = obj?.existence ?? false; this.depth = 0; this.nodeAux = obj?.nodeAux; const { siblings, notEmpties } = this.reduceSiblings(obj?.siblings); this.siblings = siblings; this.notEmpties = notEmpties; } bytes() { let bsLen = PROOF_FLAG_LEN + this.notEmpties.length + ELEM_BYTES_LEN * this.siblings.length; if (typeof this.nodeAux !== 'undefined') { bsLen += 2 * ELEM_BYTES_LEN; } const arrBuff = new ArrayBuffer(bsLen); const bs = new Uint8Array(arrBuff); if (!this.existence) { bs[0] |= 1; } bs[1] = this.depth; bs.set(this.notEmpties, PROOF_FLAG_LEN); const siblingBytes = siblings2Bytes(this.siblings); bs.set(siblingBytes, this.notEmpties.length + PROOF_FLAG_LEN); if (typeof this.nodeAux !== 'undefined') { bs[0] |= 2; bs.set(this.nodeAux.key.value, bs.length - 2 * ELEM_BYTES_LEN); bs.set(this.nodeAux.value.value, bs.length - 1 * ELEM_BYTES_LEN); } return bs; } toJSON() { return { existence: this.existence, siblings: this.allSiblings().map((s) => s.toJSON()), node_aux: this.nodeAux ? { key: this.nodeAux.key.toJSON(), value: this.nodeAux.value.toJSON() } : undefined }; } reduceSiblings(siblings) { const reducedSiblings = []; const notEmpties = new Uint8Array(NOT_EMPTIES_LEN); if (!siblings) { return { siblings: reducedSiblings, notEmpties }; } for (let i = 0; i < siblings.length; i++) { const sibling = siblings[i]; if (JSON.stringify(siblings[i]) !== JSON.stringify(ZERO_HASH)) { setBitBigEndian(notEmpties, i); reducedSiblings.push(sibling); this.depth = i + 1; } } return { notEmpties, siblings: reducedSiblings }; } static fromJSON(obj) { let nodeAux = undefined; const nodeAuxJson = obj.node_aux ?? obj.nodeAux; // we keep backward compatibility and support both representations if (nodeAuxJson) { nodeAux = { key: Hash.fromString(nodeAuxJson.key), value: Hash.fromString(nodeAuxJson.value) }; } const existence = obj.existence ?? false; const siblings = obj.siblings.map((s) => Hash.fromString(s)); return new Proof({ existence, nodeAux, siblings }); } allSiblings() { return Proof.buildAllSiblings(this.depth, this.notEmpties, this.siblings); } static buildAllSiblings(depth, notEmpties, siblings) { let sibIdx = 0; const allSiblings = []; for (let i = 0; i < depth; i += 1) { if (testBitBigEndian(notEmpties, i)) { allSiblings.push(siblings[sibIdx]); sibIdx += 1; } else { allSiblings.push(ZERO_HASH); } } return allSiblings; } } /** * @deprecated The method should not be used and will be removed in the next major version, * please use proof.allSiblings instead */ export const siblignsFroomProof = (proof) => { return proof.allSiblings(); }; export const verifyProof = async (rootKey, proof, k, v) => { try { const rFromProof = await rootFromProof(proof, k, v); return bytesEqual(rootKey.value, rFromProof.value); } catch (err) { if (err === ErrNodeAuxNonExistAgainstHIndex) { return false; } throw err; } }; export const rootFromProof = async (proof, k, v) => { const kHash = Hash.fromBigInt(k); const vHash = Hash.fromBigInt(v); let midKey; if (proof.existence) { midKey = await leafKey(kHash, vHash); } else { if (typeof proof.nodeAux === 'undefined') { midKey = ZERO_HASH; } else { const nodeAux = proof.nodeAux; if (bytesEqual(kHash.value, nodeAux.key.value)) { throw ErrNodeAuxNonExistAgainstHIndex; } midKey = await leafKey(nodeAux.key, nodeAux.value); } } const siblings = proof.allSiblings(); const path = getPath(siblings.length, kHash.value); for (let i = siblings.length - 1; i >= 0; i -= 1) { if (path[i]) { midKey = await new NodeMiddle(siblings[i], midKey).getKey(); } else { midKey = await new NodeMiddle(midKey, siblings[i]).getKey(); } } return midKey; };