Skip to content

Commit

Permalink
add fromJSON toJSON
Browse files Browse the repository at this point in the history
  • Loading branch information
vmidyllic committed Oct 18, 2023
1 parent efbe122 commit 183d326
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 52 deletions.
60 changes: 41 additions & 19 deletions src/lib/merkletree/merkletree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Hash, ZERO_HASH, circomSiblingsFromSiblings, newHashFromBigInt } from '
import { Node } from '../../types';
import { NODE_TYPE_EMPTY, NODE_TYPE_LEAF, NODE_TYPE_MIDDLE } from '../../constants';
import { NodeEmpty, NodeLeaf, NodeMiddle } from '../node/node';
import { bytesEqual, getPath, setBitBigEndian } from '../utils';
import { Siblings } from '../../types/merkletree';
import { bytesEqual, getPath } from '../utils';
import { NodeAux, Siblings } from '../../types/merkletree';
import { checkBigIntInField } from '../utils/crypto';
import { CircomProcessorProof, CircomVerifierProof } from './circom';
import {
Expand Down Expand Up @@ -442,8 +442,8 @@ export class Merkletree {
break;
case NODE_TYPE_MIDDLE:
await f(n);
await this.walk((n as NodeMiddle).childL, f);
await this.walk((n as NodeMiddle).childR, f);
await this.recWalk((n as NodeMiddle).childL, f);
await this.recWalk((n as NodeMiddle).childR, f);
break;
default:
throw ErrInvalidNodeFound;
Expand All @@ -454,7 +454,7 @@ export class Merkletree {
if (bytesEqual(rootKey.value, ZERO_HASH.value)) {
rootKey = await this.root();
}
await this.walk(rootKey, f);
await this.recWalk(rootKey, f);
}

async generateCircomVerifierProof(k: bigint, rootKey: Hash): Promise<CircomVerifierProof> {
Expand All @@ -471,7 +471,7 @@ export class Merkletree {
const { proof, value } = await this.generateProof(k, rootKey);
const cp = new CircomVerifierProof();
cp.root = rootKey;
cp.siblings = proof.siblings;
cp.siblings = proof.allSiblings();
if (typeof proof.nodeAux !== 'undefined') {
cp.oldKey = proof.nodeAux.key;
cp.oldValue = proof.nodeAux.value;
Expand All @@ -492,7 +492,6 @@ export class Merkletree {
}

async generateProof(k: bigint, rootKey?: Hash): Promise<{ proof: Proof; value: bigint }> {
const p = new Proof();
let siblingKey: Hash;

const kHash = newHashFromBigInt(k);
Expand All @@ -502,26 +501,53 @@ export class Merkletree {
}
let nextKey = rootKey;

for (p.depth = 0; p.depth < this.maxLevels; p.depth += 1) {
let depth = 0;
let existence = false;
const siblings: Siblings = [];
let nodeAux: NodeAux | undefined;

for (depth = 0; depth < this.maxLevels; depth += 1) {
const n = await this.getNode(nextKey);
if (typeof n === 'undefined') {
throw ErrNotFound;
}
switch (n.type) {
case NODE_TYPE_EMPTY:
return { proof: p, value: BigInt('0') };
return {
proof: Proof.fromJSON({
existence,
siblings,
nodeAux
}),
value: BigInt('0')
};
case NODE_TYPE_LEAF:
if (bytesEqual(kHash.value, (n as NodeLeaf).entry[0].value)) {
p.existence = true;
return { proof: p, value: (n as NodeLeaf).entry[1].bigInt() };
existence = true;

return {
proof: Proof.fromJSON({
existence,
siblings,
nodeAux
}),
value: (n as NodeLeaf).entry[1].bigInt()
};
}
p.nodeAux = {
nodeAux = {
key: (n as NodeLeaf).entry[0],
value: (n as NodeLeaf).entry[1]
};
return { proof: p, value: (n as NodeLeaf).entry[1].bigInt() };
return {
proof: Proof.fromJSON({
existence,
siblings,
nodeAux
}),
value: (n as NodeLeaf).entry[1].bigInt()
};
case NODE_TYPE_MIDDLE:
if (path[p.depth]) {
if (path[depth]) {
nextKey = (n as NodeMiddle).childR;
siblingKey = (n as NodeMiddle).childL;
} else {
Expand All @@ -532,11 +558,7 @@ export class Merkletree {
default:
throw ErrInvalidNodeFound;
}

if (!bytesEqual(siblingKey.value, ZERO_HASH.value)) {
setBitBigEndian(p.notEmpties, p.depth);
p.siblings.push(siblingKey);
}
siblings.push(siblingKey);
}
throw ErrKeyNotFound;
}
Expand Down
88 changes: 58 additions & 30 deletions src/lib/merkletree/proof.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { NodeAux, Siblings } from '../../types/merkletree';
import { ELEM_BYTES_LEN, NOT_EMPTIES_LEN, PROOF_FLAG_LEN } from '../../constants';
import { bytesEqual, getPath, siblings2Bytes, testBitBigEndian } from '../utils';
import { bytesEqual, getPath, setBitBigEndian, siblings2Bytes, testBitBigEndian } from '../utils';
import { Hash, ZERO_HASH, newHashFromBigInt } from '../hash/hash';
import { NodeMiddle } from '../node/node';
import { leafKey } from '../utils/node';
import { ErrNodeAuxNonExistAgainstHIndex } from '../errors/proof';
import { Bytes } from '../../types';

export interface ProofJSON {
existence: boolean;
siblings: Siblings;
nodeAux: NodeAux | undefined;
}

export class Proof {
existence: boolean;
depth: number;
private depth: number;
// notempties is a bitmap of non-empty siblings found in siblings
notEmpties: Bytes;
siblings: Siblings;
private notEmpties: Bytes;
private siblings: Siblings;
nodeAux: NodeAux | undefined;

constructor() {
Expand Down Expand Up @@ -50,25 +56,54 @@ export class Proof {
return bs;
}

allSiblings(): Siblings {
return siblignsFroomProof(this);
toJSON(): ProofJSON {
return {
existence: this.existence,
siblings: this.allSiblings(),
nodeAux: this.nodeAux
};
}
}

export const siblignsFroomProof = (proof: Proof): Siblings => {
let sibIdx = 0;
const siblings: Siblings = [];
public static fromJSON(obj: ProofJSON): Proof {
const proof = new Proof();

for (let i = 0; i < proof.depth; i += 1) {
if (testBitBigEndian(proof.notEmpties, i)) {
siblings.push(proof.siblings[sibIdx]);
sibIdx += 1;
} else {
siblings.push(ZERO_HASH);
proof.existence = obj.existence;
proof.nodeAux = obj.nodeAux;
proof.depth = obj.siblings.length;

for (let i = 0; i < obj.siblings.length; i++) {
const sibling = obj.siblings[i];
if (JSON.stringify(obj.siblings[i]) !== JSON.stringify(ZERO_HASH)) {
setBitBigEndian(proof.notEmpties, i);
proof.siblings.push(sibling);
}
}
return proof;
}

return siblings;
allSiblings(): Siblings {
let sibIdx = 0;
const siblings: Siblings = [];

for (let i = 0; i < this.depth; i += 1) {
if (testBitBigEndian(this.notEmpties, i)) {
siblings.push(this.siblings[sibIdx]);
sibIdx += 1;
} else {
siblings.push(ZERO_HASH);
}
}

return siblings;
}
}

/**
* @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: Proof): Siblings => {
return proof.allSiblings();
};

export const verifyProof = async (
Expand All @@ -91,8 +126,6 @@ export const verifyProof = async (
export const rootFromProof = async (proof: Proof, k: bigint, v: bigint): Promise<Hash> => {
const kHash = newHashFromBigInt(k);
const vHash = newHashFromBigInt(v);

let sibIdx = proof.siblings.length - 1;
let midKey: Hash;

if (proof.existence) {
Expand All @@ -109,20 +142,15 @@ export const rootFromProof = async (proof: Proof, k: bigint, v: bigint): Promise
}
}

const path = getPath(proof.depth, kHash.value);
let siblingKey: Hash;
const siblings = proof.allSiblings();

for (let i = proof.depth - 1; i >= 0; i -= 1) {
if (testBitBigEndian(proof.notEmpties, i)) {
siblingKey = proof.siblings[sibIdx];
sibIdx -= 1;
} else {
siblingKey = ZERO_HASH;
}
const path = getPath(siblings.length, kHash.value);

for (let i = siblings.length - 1; i >= 0; i -= 1) {
if (path[i]) {
midKey = await new NodeMiddle(siblingKey, midKey).getKey();
midKey = await new NodeMiddle(siblings[i], midKey).getKey();
} else {
midKey = await new NodeMiddle(midKey, siblingKey).getKey();
midKey = await new NodeMiddle(midKey, siblings[i]).getKey();
}
}

Expand Down
48 changes: 45 additions & 3 deletions tests/full.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { UseStore, createStore, clear } from 'idb-keyval';
import { HASH_BYTES_LENGTH, MAX_NUM_IN_FIELD } from '../src/constants';
import { NodeMiddle } from '../src/lib/node/node';
import { NodeLeaf, NodeMiddle } from '../src/lib/node/node';
import { InMemoryDB, LocalStorageDB, IndexedDBStorage } from '../src/lib/db';
import { bytes2Hex, bytesEqual, str2Bytes } from '../src/lib/utils';
import { bigint2Array, bytes2BinaryString, bytes2Hex, bytesEqual, str2Bytes } from '../src/lib/utils';
import { Hash, ZERO_HASH, newHashFromBigInt } from '../src/lib/hash/hash';
import { Merkletree, siblignsFroomProof, verifyProof } from '../src/lib/merkletree';
import { Merkletree, Proof, siblignsFroomProof, verifyProof } from '../src/lib/merkletree';
import { ErrEntryIndexAlreadyExists, ErrKeyNotFound, ErrReachedMaxLevel } from '../src/lib/errors';

import { expect } from 'chai';
import { poseidon } from '@iden3/js-crypto';

import 'mock-local-storage';
import 'fake-indexeddb/auto';
import { Node } from '../src/types';
import { ffUtils } from '@iden3/js-crypto';
import { nodeValue } from '../src/lib/utils/node';

enum TreeStorageType {
LocalStorageDB = 'localStorage',
Expand Down Expand Up @@ -634,6 +637,44 @@ for (let index = 0; index < storages.length; index++) {
});
});

it('expect tree.walk does not produce infinite loop', async () => {

const f = async (node: Node): Promise<void> => {

return Promise.resolve()
}
let tree = new Merkletree(new InMemoryDB(str2Bytes('')), true, 40);

for (let i = 0; i < 5; i++) {
await tree.add(BigInt(i), BigInt(i))
}

await tree.walk(await tree.root(), (node: Node) => f(node));
});

it('proof stringify', async () => {


let tree = new Merkletree(new InMemoryDB(str2Bytes('')), true, 40);

for (let i = 0; i < 5; i++) {
await tree.add(BigInt(i), BigInt(i))
}

const {proof, value} = await tree.generateProof(BigInt(9))

const proofModel = JSON.stringify(proof);

const proofFromJSON = Proof.fromJSON(JSON.parse(proofModel))

expect(JSON.stringify(proof.allSiblings())).to.equal(JSON.stringify((proofFromJSON.allSiblings())));
expect(proof.existence).to.eq(proofFromJSON.existence);
expect(proof.existence).to.eq(false);
expect(JSON.stringify(proof.nodeAux)).to.eq(JSON.stringify(proofFromJSON.nodeAux));


});

it('test smt verifier', async () => {
const sto = getTreeStorage();
const mt = new Merkletree(sto, true, 4);
Expand Down Expand Up @@ -680,3 +721,4 @@ for (let index = 0; index < storages.length; index++) {
});
});
}

0 comments on commit 183d326

Please sign in to comment.