From cab4baa7f3b5771d8496a9e76fdb53f4900a29ef Mon Sep 17 00:00:00 2001 From: cedoor Date: Wed, 19 Jan 2022 11:41:36 +0100 Subject: [PATCH] refactor: replace sidenodes with siblings --- packages/sparse-merkle-tree/README.md | 4 +- packages/sparse-merkle-tree/src/index.ts | 1 + .../src/sparse-merkle-tree.ts | 129 ++++++++---------- .../sparse-merkle-tree/src/types/index.ts | 21 +++ .../sparse-merkle-tree/tests/index.test.ts | 5 +- 5 files changed, 80 insertions(+), 80 deletions(-) create mode 100644 packages/sparse-merkle-tree/src/types/index.ts diff --git a/packages/sparse-merkle-tree/README.md b/packages/sparse-merkle-tree/README.md index 8cb2925..90b9f92 100644 --- a/packages/sparse-merkle-tree/README.md +++ b/packages/sparse-merkle-tree/README.md @@ -144,7 +144,7 @@ console.log(membershipProof) { entry: [ '2b', '44', '1' ], matchingEntry: undefined, - sidenodes: [ + siblings: [ '006a0ab15a212e0e0126b81e056b11576628b1ad80792403dbb3a90be2e71d64', 'f786ce5a843614d7da216d95c0087c1eb29244927feeeeeb658aa60cf124cd5e' ], @@ -158,7 +158,7 @@ console.log(nonMembershipProof) { entry: [ '16' ], matchingEntry: undefined, - sidenodes: [ + siblings: [ '960f23d9fbb44241be53efb7c4d69ac129bb1cb9482dcb6789d3cc7e6de2de2b', '2a1aef839e68d1bdf43c1b3b1ed9ef16c27162e8a175898c9ac64a679b0fc825' ], diff --git a/packages/sparse-merkle-tree/src/index.ts b/packages/sparse-merkle-tree/src/index.ts index f1d4f3b..eb2bc5a 100644 --- a/packages/sparse-merkle-tree/src/index.ts +++ b/packages/sparse-merkle-tree/src/index.ts @@ -2,3 +2,4 @@ import SparseMerkleTree from "./sparse-merkle-tree" export { SparseMerkleTree } export * from "./utils" +export * from "./types" diff --git a/packages/sparse-merkle-tree/src/sparse-merkle-tree.ts b/packages/sparse-merkle-tree/src/sparse-merkle-tree.ts index 07a31d6..8620123 100644 --- a/packages/sparse-merkle-tree/src/sparse-merkle-tree.ts +++ b/packages/sparse-merkle-tree/src/sparse-merkle-tree.ts @@ -1,4 +1,5 @@ import { checkHex, getFirstCommonElements, getIndexOfLastNonZeroElement, keyToPath } from "../src/utils" +import { ChildNodes, EntryMark, HashFunction, Key, Value, Node, Siblings, EntryResponse, MerkleProof } from "./types" /** * SparseMerkleTree class provides all the functions to create a sparse Merkle tree @@ -13,7 +14,7 @@ import { checkHex, getFirstCommonElements, getIndexOfLastNonZeroElement, keyToPa * value to mark the node as leaf node (`H(x, y, 1)`); * * **entry**: a tree entry is a key/value pair used to create the leaf nodes; * * **zero nodes**: a zero node is an hash of zeros and in this implementation `H(0,0) = 0`; - * * **side node**: if you take one of the two child nodes, the other one is its side node; + * * **siblings**: the children of a parent node are siblings; * * **path**: every entry key is a number < 2^256 that can be converted in a binary number, * and this binary number is the path used to place the entry in the tree (1 or 0 define the * child node to choose); @@ -93,7 +94,7 @@ export default class SparseMerkleTree { this.checkParameterType(key) this.checkParameterType(value) - const { entry, matchingEntry, sidenodes } = this.retrieveEntry(key) + const { entry, matchingEntry, siblings } = this.retrieveEntry(key) if (entry[1] !== undefined) { throw new Error(`Key "${key}" already exists`) @@ -108,23 +109,23 @@ export default class SparseMerkleTree { // If there are side nodes it deletes all the nodes of the path. // These nodes will be re-created below with the new hashes. - if (sidenodes.length > 0) { - this.deleteOldNodes(node, path, sidenodes) + if (siblings.length > 0) { + this.deleteOldNodes(node, path, siblings) } // If there is a matching entry, further N zero side nodes are added - // in the `sidenodes` array, followed by the matching node itself. + // in the `siblings` array, followed by the matching node itself. // N is the number of the first matching bits of the paths. // This is helpful in the non-membership proof verification // as explained in the function below. if (matchingEntry) { const matchingPath = keyToPath(matchingEntry[0]) - for (let i = sidenodes.length; matchingPath[i] === path[i]; i++) { - sidenodes.push(this.zeroNode) + for (let i = siblings.length; matchingPath[i] === path[i]; i++) { + siblings.push(this.zeroNode) } - sidenodes.push(node) + siblings.push(node) } // Adds the new entry and re-creates the nodes of the path with the new hashes @@ -132,7 +133,7 @@ export default class SparseMerkleTree { // added, which is the root node. const newNode = this.hash([key, value, this.entryMark]) this.nodes.set(newNode, [key, value, this.entryMark]) - this.root = this.addNewNodes(newNode, path, sidenodes) + this.root = this.addNewNodes(newNode, path, siblings) } /** @@ -146,7 +147,7 @@ export default class SparseMerkleTree { this.checkParameterType(key) this.checkParameterType(value) - const { entry, sidenodes } = this.retrieveEntry(key) + const { entry, siblings } = this.retrieveEntry(key) if (entry[1] === undefined) { throw new Error(`Key "${key}" does not exist`) @@ -157,13 +158,13 @@ export default class SparseMerkleTree { // Deletes the old entry and all the nodes in its path. const oldNode = this.hash(entry) this.nodes.delete(oldNode) - this.deleteOldNodes(oldNode, path, sidenodes) + this.deleteOldNodes(oldNode, path, siblings) // Adds the new entry and re-creates the nodes of the path // with the new hashes. const newNode = this.hash([key, value, this.entryMark]) this.nodes.set(newNode, [key, value, this.entryMark]) - this.root = this.addNewNodes(newNode, path, sidenodes) + this.root = this.addNewNodes(newNode, path, siblings) } /** @@ -174,7 +175,7 @@ export default class SparseMerkleTree { delete(key: Key) { this.checkParameterType(key) - const { entry, sidenodes } = this.retrieveEntry(key) + const { entry, siblings } = this.retrieveEntry(key) if (entry[1] === undefined) { throw new Error(`Key "${key}" does not exist`) @@ -190,20 +191,20 @@ export default class SparseMerkleTree { // If there are side nodes it deletes the nodes of the path and // re-creates them with the new hashes. - if (sidenodes.length > 0) { - this.deleteOldNodes(node, path, sidenodes) + if (siblings.length > 0) { + this.deleteOldNodes(node, path, siblings) // If the last side node is not a leaf node, it adds all the // nodes of the path starting from a zero node, otherwise - // it removes the last non-zero side node from the `sidenodes` + // it removes the last non-zero side node from the `siblings` // array and it starts from it by skipping the last zero nodes. - if (!this.isLeaf(sidenodes[sidenodes.length - 1])) { - this.root = this.addNewNodes(this.zeroNode, path, sidenodes) + if (!this.isLeaf(siblings[siblings.length - 1])) { + this.root = this.addNewNodes(this.zeroNode, path, siblings) } else { - const firstSidenode = sidenodes.pop() as Node - const i = getIndexOfLastNonZeroElement(sidenodes) + const firstSidenode = siblings.pop() as Node + const i = getIndexOfLastNonZeroElement(siblings) - this.root = this.addNewNodes(firstSidenode, path, sidenodes, i) + this.root = this.addNewNodes(firstSidenode, path, siblings, i) } } } @@ -214,17 +215,17 @@ export default class SparseMerkleTree { * @param key A key of an existing or a non-existing entry. * @returns The membership or the non-membership proof. */ - createProof(key: Key): Proof { + createProof(key: Key): MerkleProof { this.checkParameterType(key) - const { entry, matchingEntry, sidenodes } = this.retrieveEntry(key) + const { entry, matchingEntry, siblings } = this.retrieveEntry(key) // If the key exists the function returns a membership proof, otherwise it // returns a non-membership proof with the matching entry. return { entry, matchingEntry, - sidenodes, + siblings: siblings, root: this.root, membership: !!entry[1] } @@ -232,34 +233,34 @@ export default class SparseMerkleTree { /** * Verifies a membership or a non-membership proof. - * @param proof The proof to verify. + * @param merkleProof The proof to verify. * @returns True if the proof is valid, false otherwise. */ - verifyProof(proof: Proof): boolean { + verifyProof(merkleProof: MerkleProof): boolean { // If there is not a matching entry it simply obtains the root // hash by using the side nodes and the path of the key. - if (!proof.matchingEntry) { - const path = keyToPath(proof.entry[0]) + if (!merkleProof.matchingEntry) { + const path = keyToPath(merkleProof.entry[0]) // If there is not an entry value the proof is a non-membership proof, // and in this case, since there is not a matching entry, the node // is a zero node. If there is an entry value the proof is a // membership proof and the node is the hash of the entry. - const node = proof.entry[1] !== undefined ? this.hash(proof.entry) : this.zeroNode - const root = this.calculateRoot(node, path, proof.sidenodes) + const node = merkleProof.entry[1] !== undefined ? this.hash(merkleProof.entry) : this.zeroNode + const root = this.calculateRoot(node, path, merkleProof.siblings) // If the obtained root is equal to the proof root, then the proof is valid. - return root === proof.root + return root === merkleProof.root } else { // If there is a matching entry the proof is definitely a non-membership // proof. In this case it checks if the matching node belongs to the tree // and then it checks if the number of the first matching bits of the keys // is greater than or equal to the number of the side nodes. - const matchingPath = keyToPath(proof.matchingEntry[0]) - const node = this.hash(proof.matchingEntry) - const root = this.calculateRoot(node, matchingPath, proof.sidenodes) + const matchingPath = keyToPath(merkleProof.matchingEntry[0]) + const node = this.hash(merkleProof.matchingEntry) + const root = this.calculateRoot(node, matchingPath, merkleProof.siblings) - if (root === proof.root) { - const path = keyToPath(proof.entry[0]) + if (root === merkleProof.root) { + const path = keyToPath(merkleProof.entry[0]) // Returns the first common bits of the two keys: the // non-member key and the matching key. const firstMatchingBits = getFirstCommonElements(path, matchingPath) @@ -267,7 +268,7 @@ export default class SparseMerkleTree { // matching node should be greater than the number of the first common // bits of the keys. The depth of a node can be defined by the number // of its side nodes. - return proof.sidenodes.length <= firstMatchingBits.length + return merkleProof.siblings.length <= firstMatchingBits.length } return false @@ -285,7 +286,7 @@ export default class SparseMerkleTree { */ private retrieveEntry(key: Key): EntryResponse { const path = keyToPath(key) - const sidenodes: SideNodes = [] + const siblings: Siblings = [] // Starts from the root and goes down into the tree until it finds // the entry, a zero node or a matching entry. @@ -299,35 +300,35 @@ export default class SparseMerkleTree { if (childNodes[0] === key) { // An entry with the same key was found and // it returns it with the side nodes. - return { entry: childNodes, sidenodes } + return { entry: childNodes, siblings: siblings } } // The entry found does not have the same key. But the key of this // particular entry matches the first 'i' bits of the key passed // as parameter and it can be useful in several functions. - return { entry: [key], matchingEntry: childNodes, sidenodes } + return { entry: [key], matchingEntry: childNodes, siblings: siblings } } // When it goes down into the tree and follows the path, in every step // a node is chosen between the left and the right child nodes, and the // opposite node is saved as side node. node = childNodes[direction] as Node - sidenodes.push(childNodes[Number(!direction)] as Node) + siblings.push(childNodes[Number(!direction)] as Node) } // The path led to a zero node. - return { entry: [key], sidenodes } + return { entry: [key], siblings: siblings } } /** * Calculates nodes with a bottom-up approach until it reaches the root node. * @param node The node to start from. * @param path The path of the key. - * @param sidenodes The side nodes of the path. + * @param siblings The side nodes of the path. * @returns The root node. */ - private calculateRoot(node: Node, path: number[], sidenodes: SideNodes): Node { - for (let i = sidenodes.length - 1; i >= 0; i--) { - const childNodes: ChildNodes = path[i] ? [sidenodes[i], node] : [node, sidenodes[i]] + private calculateRoot(node: Node, path: number[], siblings: Siblings): Node { + for (let i = siblings.length - 1; i >= 0; i--) { + const childNodes: ChildNodes = path[i] ? [siblings[i], node] : [node, siblings[i]] node = this.hash(childNodes) } @@ -338,13 +339,13 @@ export default class SparseMerkleTree { * Adds new nodes in the tree with a bottom-up approach until it reaches the root node. * @param node The node to start from. * @param path The path of the key. - * @param sidenodes The side nodes of the path. + * @param siblings The side nodes of the path. * @param i The index to start from. * @returns The root node. */ - private addNewNodes(node: Node, path: number[], sidenodes: SideNodes, i = sidenodes.length - 1): Node { + private addNewNodes(node: Node, path: number[], siblings: Siblings, i = siblings.length - 1): Node { for (; i >= 0; i--) { - const childNodes: ChildNodes = path[i] ? [sidenodes[i], node] : [node, sidenodes[i]] + const childNodes: ChildNodes = path[i] ? [siblings[i], node] : [node, siblings[i]] node = this.hash(childNodes) this.nodes.set(node, childNodes) @@ -357,12 +358,12 @@ export default class SparseMerkleTree { * Deletes nodes in the tree with a bottom-up approach until it reaches the root node. * @param node The node to start from. * @param path The path of the key. - * @param sidenodes The side nodes of the path. + * @param siblings The side nodes of the path. * @param i The index to start from. */ - private deleteOldNodes(node: Node, path: number[], sidenodes: SideNodes) { - for (let i = sidenodes.length - 1; i >= 0; i--) { - const childNodes: ChildNodes = path[i] ? [sidenodes[i], node] : [node, sidenodes[i]] + private deleteOldNodes(node: Node, path: number[], siblings: Siblings) { + for (let i = siblings.length - 1; i >= 0; i--) { + const childNodes: ChildNodes = path[i] ? [siblings[i], node] : [node, siblings[i]] node = this.hash(childNodes) this.nodes.delete(node) @@ -394,25 +395,3 @@ export default class SparseMerkleTree { } } } - -export type Node = string | bigint -export type Key = Node -export type Value = Node -export type EntryMark = Node - -export type Entry = [Key, Value, EntryMark] -export type ChildNodes = Node[] -export type SideNodes = Node[] - -export type HashFunction = (childNodes: ChildNodes) => Node - -export interface EntryResponse { - entry: Entry | Node[] - matchingEntry?: Entry | Node[] - sidenodes: SideNodes -} - -export interface Proof extends EntryResponse { - root: Node - membership: boolean -} diff --git a/packages/sparse-merkle-tree/src/types/index.ts b/packages/sparse-merkle-tree/src/types/index.ts new file mode 100644 index 0000000..b5ec2f0 --- /dev/null +++ b/packages/sparse-merkle-tree/src/types/index.ts @@ -0,0 +1,21 @@ +export type Node = string | bigint +export type Key = Node +export type Value = Node +export type EntryMark = Node + +export type Entry = [Key, Value, EntryMark] +export type ChildNodes = Node[] +export type Siblings = Node[] + +export type HashFunction = (childNodes: ChildNodes) => Node + +export interface EntryResponse { + entry: Entry | Node[] + matchingEntry?: Entry | Node[] + siblings: Siblings +} + +export interface MerkleProof extends EntryResponse { + root: Node + membership: boolean +} diff --git a/packages/sparse-merkle-tree/tests/index.test.ts b/packages/sparse-merkle-tree/tests/index.test.ts index f8464e4..0b12adf 100644 --- a/packages/sparse-merkle-tree/tests/index.test.ts +++ b/packages/sparse-merkle-tree/tests/index.test.ts @@ -1,7 +1,6 @@ -import { SparseMerkleTree } from "../src" -import { ChildNodes } from "../src/sparse-merkle-tree" -import { sha256 } from "js-sha256" import { poseidon, smt } from "circomlibjs" +import { sha256 } from "js-sha256" +import { ChildNodes, SparseMerkleTree } from "../src" describe("Sparse Merkle tree", () => { const hash = (childNodes: ChildNodes) => sha256(childNodes.join(""))