Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
🌱 Introduce two new interfaces for verification
Browse files Browse the repository at this point in the history
- Also introduce a new function `isInclusion` to check inclusion & non-inclusion
- Update JS unit test to check two new interfaces
  • Loading branch information
hrmhatef committed Jul 7, 2023
1 parent 237c9de commit 270e2fc
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 30 deletions.
37 changes: 31 additions & 6 deletions sparse_merkle_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const {
in_memory_smt_verify,
in_memory_smt_calculate_root,
} = require("./bin-package/index.node");
const { isInclusion } = require('./utils');

const DEFAULT_KEY_LENGTH = 38;

Expand Down Expand Up @@ -68,12 +69,36 @@ class SparseMerkleTree {
}

async verify(root, queries, proof) {
return new Promise((resolve, reject) => {
in_memory_smt_verify.call(null, root, queries, proof, this._keyLength, (err, result) => {
if (err) {
reject(err);
return;
return new Promise((resolve) => {
in_memory_smt_verify.call(null, root, queries, proof, this._keyLength, (result) => {
resolve(result);
});
});
}

async verifyInclusionProof(root, queries, proof) {
return new Promise((resolve) => {
for (let i = 0; i < queries.length; i++) {
if (!isInclusion(queries[i], proof.queries[i])) {
resolve(false);
}
}

in_memory_smt_verify.call(null, root, queries, proof, this._keyLength, (_, result) => {
resolve(result);
});
});
}

async verifyNonInclusionProof(root, queries, proof) {
return new Promise((resolve) => {
for (let i = 0; i < queries.length; i++) {
if (isInclusion(queries[i], proof.queries[i])) {
resolve(false);
}
}

in_memory_smt_verify.call(null, root, queries, proof, this._keyLength, (result) => {
resolve(result);
});
});
Expand All @@ -94,4 +119,4 @@ class SparseMerkleTree {

module.exports = {
SparseMerkleTree,
};
};
35 changes: 31 additions & 4 deletions state_db.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const {
const { NotFoundError } = require('./error');
const { Iterator } = require("./iterator");
const { getOptionsWithDefault } = require('./options');
const { isInclusion } = require('./utils');

class StateReader {
constructor(db) {
Expand Down Expand Up @@ -303,11 +304,37 @@ class StateDB {
}

async verify(root, queries, proof) {
return new Promise((resolve, reject) => {
state_db_verify.call(this._db, root, queries, proof, (err, result) => {
if (err) {
return reject(err);
return new Promise((resolve) => {
state_db_verify.call(this._db, root, queries, proof, (_, result) => {
resolve(result);
});
});
}


async verifyInclusionProof(root, queries, proof) {
return new Promise((resolve) => {
for (let i = 0; i < queries.length; i++) {
if (!isInclusion(queries[i], proof.queries[i])) {
resolve(false);
}
}

state_db_verify.call(this._db, root, queries, proof, (_, result) => {
resolve(result);
});
});
}

async verifyNonInclusionProof(root, queries, proof) {
return new Promise((resolve) => {
for (let i = 0; i < queries.length; i++) {
if (isInclusion(queries[i], proof.queries[i])) {
resolve(false);
}
}

state_db_verify.call(this._db, root, queries, proof, (_, result) => {
resolve(result);
});
});
Expand Down
44 changes: 37 additions & 7 deletions test/sparse_merkle_tree.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
* Removal or modification of this copyright notice is prohibited.
*/
const os = require('os');
const path = require('path');
const fs = require('fs');
const { StateDB, SparseMerkleTree } = require('../main');
const { SparseMerkleTree } = require('../main');
const { getRandomBytes } = require('./utils');
const { isInclusion } = require('../utils');

const SMTFixtures = require('./fixtures/smt_fixtures.json');
const FixturesInclusionProof = require('./fixtures/fixtures_no_delete_inclusion_proof.json');
Expand Down Expand Up @@ -89,6 +88,37 @@ describe('SparseMerkleTree', () => {
});
}

/*
* We do not know which testcase is inclusion or non-inclusion so define the two
* following functions to call correct verification function based on the result
* of these functions.
*/
const isNonInclusionProof = (queriesKeys, proofQueries) => {
for (let i = 0; i < queriesKeys.length; i++) {
if (isInclusion(queriesKeys[i], proofQueries[i])) {
return false;
}
}

return true;
}
const isInclusionProof = (queriesKeys, proofQueries) => {
for (let i = 0; i < queriesKeys.length; i++) {
if (!isInclusion(queriesKeys[i], proofQueries[i])) {
return false;
}
}

return true;
}
if (isNonInclusionProof(queryKeys, proof.queries)) {
await expect(smt.verifyNonInclusionProof(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, proof)).resolves.toEqual(true);
await expect(smt.verifyInclusionProof(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, proof)).resolves.toEqual(false);
} else if (isInclusionProof(queryKeys, proof.queries)) {
await expect(smt.verifyInclusionProof(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, proof)).resolves.toEqual(true);
await expect(smt.verifyNonInclusionProof(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, proof)).resolves.toEqual(false);
}

expect(siblingHashesString).toEqual(outputProof.siblingHashes);
expect(queriesString).toEqual(outputProof.queries);
await expect(smt.verify(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, proof)).resolves.toEqual(true);
Expand All @@ -115,6 +145,8 @@ describe('SparseMerkleTree', () => {
const randomSiblingPrependedProof = { ...proof };
randomSiblingPrependedProof.siblingHashes = [getRandomBytes(), ...randomSiblingPrependedProof.siblingHashes];
await expect(smt.verify(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, randomSiblingPrependedProof)).resolves.toEqual(false);
await expect(smt.verifyInclusionProof(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, randomSiblingPrependedProof)).resolves.toEqual(false);
await expect(smt.verifyNonInclusionProof(Buffer.from(outputMerkleRoot, 'hex'), queryKeys, randomSiblingPrependedProof)).resolves.toEqual(false);

const randomSiblingAppendedProof = { ...proof };
randomSiblingAppendedProof.siblingHashes = [...randomSiblingAppendedProof.siblingHashes, getRandomBytes()];
Expand Down Expand Up @@ -182,17 +214,15 @@ describe('SparseMerkleTree', () => {
const rootAfterDelete = await smt.update(rootHash, deletingKVPair);
const deletedKeyProof = await smt.prove(rootAfterDelete, queryKeys);

const isInclusion = (proofQuery, queryKey) =>
queryKey.equals(proofQuery.key) && !proofQuery.value.equals(Buffer.alloc(0));
await expect(smt.verify(rootAfterDelete, queryKeys, deletedKeyProof)).resolves.toEqual(true);
for (let i = 0; i < queryKeys.length; i += 1) {
const query = queryKeys[i];
const proofQuery = deletedKeyProof.queries[i];

if (inputKeys.find(k => Buffer.from(k, 'hex').equals(query)) !== undefined && [...deleteQueryKeys, ...deletedKeys.map(keyHex => Buffer.from(keyHex, 'hex'))].find(k => k.equals(query)) === undefined) {
expect(isInclusion(proofQuery, query)).toEqual(true);
expect(isInclusion(query, proofQuery)).toEqual(true);
} else {
expect(isInclusion(proofQuery, query)).toEqual(false);
expect(isInclusion(query, proofQuery)).toEqual(false);
}
}
});
Expand Down
24 changes: 12 additions & 12 deletions test/statedb.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,9 @@ describe('statedb', () => {
const queries = [getRandomBytes(38), getRandomBytes(38)];
const proof = await db.prove(root, queries);

const result = await db.verify(root, queries, proof);

expect(result).toEqual(true);
await expect(db.verify(root, queries, proof)).resolves.toEqual(true);
await expect(db.verifyNonInclusionProof(root, queries, proof)).resolves.toEqual(true);
await expect(db.verifyInclusionProof(root, queries, proof)).resolves.toEqual(false);
});

it('should generate wrong non-inclusion proof and verify that a result is not correct', async () => {
Expand All @@ -599,9 +599,9 @@ describe('statedb', () => {
// change sibling hash in proof to make it wrong
proof.siblingHashes[0] = getRandomBytes(32);

const result = await db.verify(root, queries, proof);

expect(result).toEqual(false);
await expect(db.verify(root, queries, proof)).resolves.toEqual(false);
await expect(db.verifyNonInclusionProof(root, queries, proof)).resolves.toEqual(false);
await expect(db.verifyInclusionProof(root, queries, proof)).resolves.toEqual(false);
});

it('should generate inclusion proof and verify that a result is correct', async () => {
Expand All @@ -612,9 +612,9 @@ describe('statedb', () => {
];
const proof = await db.prove(root, queries);

const result = await db.verify(root, queries, proof);

expect(result).toEqual(true);
await expect(db.verify(root, queries, proof)).resolves.toEqual(true);
await expect(db.verifyNonInclusionProof(root, queries, proof)).resolves.toEqual(false);
await expect(db.verifyInclusionProof(root, queries, proof)).resolves.toEqual(true);
});

it('should generate wrong inclusion proof and verify that a result is not correct', async () => {
Expand All @@ -628,9 +628,9 @@ describe('statedb', () => {
// change sibling hash in proof to make it wrong
proof.siblingHashes[0] = getRandomBytes(32);

const result = await db.verify(root, queries, proof);

expect(result).toEqual(false);
await expect(db.verify(root, queries, proof)).resolves.toEqual(false);
await expect(db.verifyNonInclusionProof(root, queries, proof)).resolves.toEqual(false);
await expect(db.verifyInclusionProof(root, queries, proof)).resolves.toEqual(false);
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ const getRandomBytes = (size = 32) => crypto.randomBytes(size);

module.exports = {
getRandomBytes,
};
};
4 changes: 4 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export class StateDB {
commit(readWriter: StateReadWriter, height: number, prevRoot: Buffer, options?: StateCommitOption): Promise<Buffer>;
prove(root: Buffer, queries: Buffer[]): Promise<Proof>;
verify(root: Buffer, queries: Buffer[], proof: Proof): Promise<boolean>;
verifyInclusionProof(root: Buffer, queries: Buffer[], proof: Proof): Promise<boolean>;
verifyNonInclusionProof(root: Buffer, queries: Buffer[], proof: Proof): Promise<boolean>;
finalize(height: number): Promise<void>;
newReader(): StateReader;
newReadWriter(): StateReadWriter;
Expand All @@ -134,5 +136,7 @@ export class SparseMerkleTree {
update(root: Buffer, kvpair: { key: Buffer, value: Buffer }[]): Promise<Buffer>;
prove(root: Buffer, queries: Buffer[]): Promise<Proof>;
verify(root: Buffer, queries: Buffer[], proof: Proof): Promise<boolean>;
verifyInclusionProof(root: Buffer, queries: Buffer[], proof: Proof): Promise<boolean>;
verifyNonInclusionProof(root: Buffer, queries: Buffer[], proof: Proof): Promise<boolean>;
calculateRoot(proof: Proof): Promise<Buffer>;
}
21 changes: 21 additions & 0 deletions utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright © 2022 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/

const isInclusion = (queryKey, proofQuery) =>
queryKey.equals(proofQuery.key) && !proofQuery.value.equals(Buffer.alloc(0));

module.exports = {
isInclusion,
};

0 comments on commit 270e2fc

Please sign in to comment.