From a69a927e8e6b2a1e9fe00a9c9e272712a9e919ee Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sat, 6 Jun 2020 12:07:23 -0700 Subject: [PATCH] Update docs --- README.md | 624 ++++++++++++++++++++++------------ diagrams/merkle-tree-logo.png | Bin 0 -> 29865 bytes diagrams/merkle-tree-logo.xml | 2 + dist/index.d.ts | 292 ++++++++++++++-- dist/index.js | 364 ++++++++++++++++---- index.ts | 383 ++++++++++++++++----- package.json | 4 +- test/merkletree.test.js | 69 +++- 8 files changed, 1354 insertions(+), 384 deletions(-) create mode 100644 diagrams/merkle-tree-logo.png create mode 100644 diagrams/merkle-tree-logo.xml diff --git a/README.md b/README.md index 93ff4e3..be777ad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@


- logo + merkletree.js logo


@@ -18,9 +18,9 @@ ## Contents -- [Diagrams](#diagrams) - [Install](#install) - [Getting started](#Getting-started) +- [Diagrams](#diagrams) - [Documentation](#documentation) - [Test](#test) - [FAQ](#faq) @@ -29,24 +29,6 @@ - [Contributing](#contributing) - [License](#license) -## Diagrams - -Diagram of Merkle Tree - -Merkle Tree - -Diagram of Merkle Tree Proof - -Merkle Tree Proof - -Diagram of Invalid Merkle Tree Proofs - -Merkle Tree Proof - -Diagram of Bitcoin Merkle Tree - -Merkle Tree Proof - ## Install ```bash @@ -93,6 +75,24 @@ Output └─ 0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2 ``` +## Diagrams + +Visualization of Merkle Tree + +Merkle Tree + +Visualization of Merkle Tree Proof + +Merkle Tree Proof + +Visualization of Invalid Merkle Tree Proofs + +Merkle Tree Proof + +Visualization of Bitcoin Merkle Tree + +Merkle Tree Proof + ## Documentation @@ -100,44 +100,62 @@ Output ## Class -Class reprensenting a Merkle Tree - -*__namespace__*: MerkleTree - ## Hierarchy **MerkleTree** ### Constructors -* [constructor](#constructor) +* [constructor](api-classes-index-merkletree.md#constructor) ### Properties -* [duplicateOdd](#duplicateodd) -* [hashAlgo](#hashalgo) -* [hashLeaves](#hashleaves) -* [isBitcoinTree](#isbitcointree) -* [layers](#layers) -* [leaves](#leaves) -* [sort](#sort) -* [sortLeaves](#sortleaves) -* [sortPairs](#sortpairs) +* [duplicateOdd](api-classes-index-merkletree.md#duplicateodd) +* [hashAlgo](api-classes-index-merkletree.md#hashalgo) +* [hashLeaves](api-classes-index-merkletree.md#hashleaves) +* [isBitcoinTree](api-classes-index-merkletree.md#isbitcointree) +* [layers](api-classes-index-merkletree.md#layers) +* [leaves](api-classes-index-merkletree.md#leaves) +* [sort](api-classes-index-merkletree.md#sort) +* [sortLeaves](api-classes-index-merkletree.md#sortleaves) +* [sortPairs](api-classes-index-merkletree.md#sortpairs) ### Methods -* [createHashes](#createhashes) -* [getLayers](#getlayers) -* [getLayersAsObject](#getlayersasobject) -* [getLeaves](#getleaves) -* [getProof](#getproof) -* [getRoot](#getroot) -* [print](#print) -* [toString](#tostring) -* [toTreeString](#totreestring) -* [verify](#verify) -* [bufferify](#bufferify) -* [print](#print-1) +* [_bufferIndexOf](api-classes-index-merkletree.md#_bufferindexof) +* [_bufferToHex](api-classes-index-merkletree.md#_buffertohex) +* [_bufferify](api-classes-index-merkletree.md#_bufferify) +* [_bufferifyFn](api-classes-index-merkletree.md#_bufferifyfn) +* [_createHashes](api-classes-index-merkletree.md#_createhashes) +* [_getPairNode](api-classes-index-merkletree.md#_getpairnode) +* [_isHexString](api-classes-index-merkletree.md#_ishexstring) +* [_log2](api-classes-index-merkletree.md#_log2) +* [_toTreeString](api-classes-index-merkletree.md#_totreestring) +* [_zip](api-classes-index-merkletree.md#_zip) +* [getDepth](api-classes-index-merkletree.md#getdepth) +* [getHexLayers](api-classes-index-merkletree.md#gethexlayers) +* [getHexLayersFlat](api-classes-index-merkletree.md#gethexlayersflat) +* [getHexLeaves](api-classes-index-merkletree.md#gethexleaves) +* [getHexMultiProof](api-classes-index-merkletree.md#gethexmultiproof) +* [getHexProof](api-classes-index-merkletree.md#gethexproof) +* [getHexRoot](api-classes-index-merkletree.md#gethexroot) +* [getLayers](api-classes-index-merkletree.md#getlayers) +* [getLayersAsObject](api-classes-index-merkletree.md#getlayersasobject) +* [getLayersFlat](api-classes-index-merkletree.md#getlayersflat) +* [getLeaves](api-classes-index-merkletree.md#getleaves) +* [getMultiProof](api-classes-index-merkletree.md#getmultiproof) +* [getProof](api-classes-index-merkletree.md#getproof) +* [getProofFlags](api-classes-index-merkletree.md#getproofflags) +* [getProofIndices](api-classes-index-merkletree.md#getproofindices) +* [getRoot](api-classes-index-merkletree.md#getroot) +* [print](api-classes-index-merkletree.md#print) +* [toString](api-classes-index-merkletree.md#tostring) +* [verify](api-classes-index-merkletree.md#verify) +* [verifyMultiProof](api-classes-index-merkletree.md#verifymultiproof) +* [bufferify](api-classes-index-merkletree.md#bufferify) +* [getMultiProof](api-classes-index-merkletree.md#getmultiproof-1) +* [isHexString](api-classes-index-merkletree.md#ishexstring) +* [print](api-classes-index-merkletree.md#print-1) --- @@ -147,34 +165,17 @@ Class reprensenting a Merkle Tree ### constructor -⊕ **new MerkleTree**(leaves: *`any`*, hashAlgorithm: *`any`*, options?: *[Options]() - -*__desc__*: Constructs a Merkle Tree. All nodes and leaves are stored as Buffers. Lonely leaf nodes are promoted to the next level up without being hashed again. - -*__example__*: - ```js -const MerkleTree = require('merkletreejs') -const crypto = require('crypto') - -function sha256(data) { - // returns Buffer - return crypto.createHash('sha256').update(data).digest() -} - -const leaves = ['a', 'b', 'c'].map(x => keccak(x)) - -const tree = new MerkleTree(leaves, sha256) -``` +⊕ **new MerkleTree**(leaves: *`any`*, hashAlgorithm?: *`any`*, options?: *[Options](api-interfaces-index-options.md)*): [MerkleTree](api-classes-index-merkletree.md) **Parameters:** | Name | Type | Default value | Description | | ------ | ------ | ------ | ------ | | leaves | `any` | - | Array of hashed leaves. Each leaf must be a Buffer. | -| hashAlgorithm | `any` | - | Algorithm used for hashing leaves and nodes | -| `Default value` options | [Options]() | {} as any | Additional options | +| `Default value` hashAlgorithm | `any` | SHA256 | Algorithm used for hashing leaves and nodes | +| `Default value` options | [Options](api-interfaces-index-options.md) | {} | Additional options | -**Returns:** [MerkleTree]() +**Returns:** [MerkleTree](api-classes-index-merkletree.md) ___ @@ -194,15 +195,15 @@ ___ **● hashAlgo**: *`function`* #### Type declaration -▸(value: *`any`*): `any` +▸(value: *[TValue](api-modules-index-module.md#tvalue)*): [THashAlgo](api-modules-index-module.md#thashalgo) **Parameters:** | Name | Type | | ------ | ------ | -| value | `any` | +| value | [TValue](api-modules-index-module.md#tvalue) | -**Returns:** `any` +**Returns:** [THashAlgo](api-modules-index-module.md#thashalgo) ___ @@ -223,14 +224,21 @@ ___ ### layers -**● layers**: *`any`[]* +**● layers**: *[TLayer](api-modules-index-module.md#tlayer)[]* ___ ### leaves -**● leaves**: *`any`[]* +**● leaves**: *[TLeaf](api-modules-index-module.md#tleaf)[]* + +___ + + +### sort + +**● sort**: *`boolean`* ___ @@ -250,11 +258,73 @@ ___ ## Methods - + -### createHashes +### `` _bufferIndexOf -▸ **createHashes**(nodes: *`any`*): `void` +▸ **_bufferIndexOf**(arr: *`any`*, el: *`any`*): `number` + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| arr | `any` | +| el | `any` | + +**Returns:** `number` +* Index number + +___ + + +### `` _bufferToHex + +▸ **_bufferToHex**(value: *`Buffer`*): `string` + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| value | `Buffer` | \- | + +**Returns:** `string` + +___ + + +### `` _bufferify + +▸ **_bufferify**(x: *`any`*): `any` + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| x | `any` | + +**Returns:** `any` + +___ + + +### `` _bufferifyFn + +▸ **_bufferifyFn**(f: *`any`*): `(Anonymous function)` + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| f | `any` | + +**Returns:** `(Anonymous function)` + +___ + + +### `` _createHashes + +▸ **_createHashes**(nodes: *`any`*): `void` **Parameters:** @@ -264,6 +334,157 @@ ___ **Returns:** `void` +___ + + +### `` _getPairNode + +▸ **_getPairNode**(layer: *`any`*, idx: *`any`*): `any` + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| layer | `any` | Tree layer | +| idx | `any` | + +**Returns:** `any` +* Node + +___ + + +### `` _isHexString + +▸ **_isHexString**(v: *`any`*): `boolean` + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| v | `any` | + +**Returns:** `boolean` + +___ + + +### `` _log2 + +▸ **_log2**(x: *`any`*): `any` + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| x | `any` | + +**Returns:** `any` + +___ + + +### `` _toTreeString + +▸ **_toTreeString**(): `any` + +**Returns:** `any` + +___ + + +### `` _zip + +▸ **_zip**(a: *`any`*, b: *`any`*): `any` + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| a | `any` | first array | +| b | `any` | second array | + +**Returns:** `any` + +___ + + +### getDepth + +▸ **getDepth**(): `number` + +**Returns:** `number` + +___ + + +### getHexLayers + +▸ **getHexLayers**(): `any` + +**Returns:** `any` + +___ + + +### getHexLayersFlat + +▸ **getHexLayersFlat**(): `any` + +**Returns:** `any` + +___ + + +### getHexLeaves + +▸ **getHexLeaves**(): `string`[] + +**Returns:** `string`[] + +___ + + +### getHexMultiProof + +▸ **getHexMultiProof**(tree: *`any`*, indices: *`any`*): `string`[] + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| tree | `any` | +| indices | `any` | Tree indices. | + +**Returns:** `string`[] +* Multiproofs as hex strings. + +___ + + +### getHexProof + +▸ **getHexProof**(leaf: *`any`*, index: *`any`*): `string`[] + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| leaf | `any` | Target leaf | +| `Optional` index | `any` | + +**Returns:** `string`[] +* Proof array as hex strings. + +___ + + +### getHexRoot + +▸ **getHexRoot**(): `string` + +**Returns:** `string` + ___ @@ -271,15 +492,6 @@ ___ ▸ **getLayers**(): `any`[] -getLayers - -*__desc__*: Returns array of all layers of Merkle Tree, including leaves and root. - -*__example__*: - ```js -const layers = tree.getLayers() -``` - **Returns:** `any`[] ___ @@ -291,46 +503,53 @@ ___ **Returns:** `any` +___ + + +### getLayersFlat + +▸ **getLayersFlat**(): `any` + +**Returns:** `any` + ___ ### getLeaves -▸ **getLeaves**(): `any`[] +▸ **getLeaves**(data: *`any`[]*): `any`[] -getLeaves +**Parameters:** -*__desc__*: Returns array of leaves of Merkle Tree. - -*__example__*: - ```js -const leaves = tree.getLeaves() -``` +| Name | Type | +| ------ | ------ | +| `Optional` data | `any`[] | **Returns:** `any`[] +___ + + +### getMultiProof + +▸ **getMultiProof**(tree: *`any`*, indices: *`any`*): `any`[] + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| tree | `any` | +| indices | `any` | Tree indices. | + +**Returns:** `any`[] +* Multiproofs + ___ ### getProof -▸ **getProof**(leaf: *`any`*, index?: *`any`*): `any`[] - -getProof - -*__desc__*: Returns the proof for a target leaf. - -*__example__*: - ```js -const proof = tree.getProof(leaves[2]) -``` - -*__example__*: - ```js -const leaves = ['a', 'b', 'a'].map(x => keccak(x)) -const tree = new MerkleTree(leaves, keccak) -const proof = tree.getProof(leaves[2], 2) -``` +▸ **getProof**(leaf: *`any`*, index: *`any`*): `any`[] **Parameters:** @@ -342,6 +561,40 @@ const proof = tree.getProof(leaves[2], 2) **Returns:** `any`[] * Array of objects containing a position property of type string with values of 'left' or 'right' and a data property of type Buffer. +___ + + +### getProofFlags + +▸ **getProofFlags**(els: *`any`*, proofs: *`any`*): `any`[] + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| els | `any` | +| proofs | `any` | + +**Returns:** `any`[] +* Boolean flags + +___ + + +### getProofIndices + +▸ **getProofIndices**(treeIndices: *`any`*, depth: *`any`*): `any`[] + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| treeIndices | `any` | Tree indices | +| depth | `any` | Tree depth; number of layers. | + +**Returns:** `any`[] +* Proof indices + ___ @@ -349,15 +602,6 @@ ___ ▸ **getRoot**(): `any` -getRoot - -*__desc__*: Returns the Merkle root hash as a Buffer. - -*__example__*: - ```js -const root = tree.getRoot() -``` - **Returns:** `any` ___ @@ -378,15 +622,6 @@ ___ **Returns:** `any` -___ - - -### toTreeString - -▸ **toTreeString**(): `any` - -**Returns:** `any` - ___ @@ -394,17 +629,6 @@ ___ ▸ **verify**(proof: *`any`*, targetNode: *`any`*, root: *`any`*): `boolean` -verify - -*__desc__*: Returns true if the proof path (array of hashes) can connect the target node to the Merkle root. - -*__example__*: - ```js -const root = tree.getRoot() -const proof = tree.getProof(leaves[2]) -const verified = tree.verify(proof, leaves[2], root) -``` - **Parameters:** | Name | Type | Description | @@ -415,6 +639,25 @@ const verified = tree.verify(proof, leaves[2], root) **Returns:** `boolean` +___ + + +### verifyMultiProof + +▸ **verifyMultiProof**(root: *`any`*, indices: *`any`*, leaves: *`any`*, depth: *`any`*, proof: *`any`*): `any` + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| root | `any` | Merkle tree root | +| indices | `any` | Leave indices | +| leaves | `any` | Leaf values at indices. | +| depth | `any` | Tree depth | +| proof | `any` | Multiproofs given indices | + +**Returns:** `any` + ___ @@ -430,6 +673,38 @@ ___ **Returns:** `any` +___ + + +### `` getMultiProof + +▸ **getMultiProof**(tree: *`any`*, indices: *`any`*): `any`[] + +**Parameters:** + +| Name | Type | Description | +| ------ | ------ | ------ | +| tree | `any` | Tree as a flat array. | +| indices | `any` | Tree indices. | + +**Returns:** `any`[] +* Multiproofs + +___ + + +### `` isHexString + +▸ **isHexString**(v: *`any`*): `boolean` + +**Parameters:** + +| Name | Type | +| ------ | ------ | +| v | `any` | + +**Returns:** `boolean` + ___ @@ -439,79 +714,12 @@ ___ **Parameters:** -| Name | Type | -| ------ | ------ | -| tree | `any` | +| Name | Type | Description | +| ------ | ------ | ------ | +| tree | `any` | Merkle tree instance. | **Returns:** `void` -## Interface - -## Options - -### Properties - -* [duplicateOdd](#duplicateodd) -* [hashLeaves](#hashleaves) -* [isBitcoinTree](#isbitcointree) -* [sort](#sort) -* [sortLeaves](#sortleaves) -* [sortPairs](#sortpairs) - ---- - -## Properties - - - -### duplicateOdd - -**● duplicateOdd**: *`boolean`* - -If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash. - -___ - - -### hashLeaves - -**● hashLeaves**: *`boolean`* - -If set to `true`, the leaves will hashed using the set hashing algorithms. - -___ - - -### isBitcoinTree - -**● isBitcoinTree**: *`boolean`* - -If set to `true`, constructs the Merkle Tree using the [Bitcoin Merkle Tree implementation](http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html). Enable it when you need to replicate Bitcoin constructed Merkle Trees. In Bitcoin Merkle Trees, single nodes are combined with themselves, and each output hash is hashed again. - -___ - - -### sort - -**● sort**: *`boolean`* - -If set to `true`, the leaves and hashing pairs will be sorted. - -### sortLeaves - -**● sortLeaves**: *`boolean`* - -If set to `true`, the leaves will be sorted. - -___ - - -### sortPairs - -**● sortPairs**: *`boolean`* - -If set to `true`, the hashing pairs will be sorted. - ## Test ```bash diff --git a/diagrams/merkle-tree-logo.png b/diagrams/merkle-tree-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d08853f24da66c3217b907c5f80e2f89fb118ee7 GIT binary patch literal 29865 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 \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index 92b289d..56711ff 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,9 +1,6 @@ -/// interface Options { /** If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash. */ duplicateOdd?: boolean; - /** If set to `true`, an odd node will not have a pair generating the layer hash. */ - singleOdd?: boolean; /** If set to `true`, the leaves will hashed using the set hashing algorithms. */ hashLeaves?: boolean; /** If set to `true`, constructs the Merkle Tree using the [Bitcoin Merkle Tree implementation](http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html). Enable it when you need to replicate Bitcoin constructed Merkle Trees. In Bitcoin Merkle Trees, single nodes are combined with themselves, and each output hash is hashed again. */ @@ -25,7 +22,6 @@ declare type TLayer = any; */ export declare class MerkleTree { duplicateOdd: boolean; - singleOdd: boolean; hashAlgo: (value: TValue) => THashAlgo; hashLeaves: boolean; isBitcoinTree: boolean; @@ -56,8 +52,8 @@ export declare class MerkleTree { *const tree = new MerkleTree(leaves, sha256) *``` */ - constructor(leaves: any, hashAlgorithm: any, options?: Options); - createHashes(nodes: any): void; + constructor(leaves: any, hashAlgorithm?: any, options?: Options); + private _createHashes; /** * getLeaves * @desc Returns array of leaves of Merkle Tree. @@ -146,7 +142,7 @@ export declare class MerkleTree { * Use if there are leaves containing duplicate data in order to distinguish it. * @return {Object[]} - Array of objects containing a position property of type string * with values of 'left' or 'right' and a data property of type Buffer. - *@example + * @example * ```js *const proof = tree.getProof(leaves[2]) *``` @@ -159,13 +155,70 @@ export declare class MerkleTree { *``` */ getProof(leaf: any, index?: any): any[]; - getProofIndices(treeIndices: any, depth: any): any[]; - getMultiProof(tree: any, indices: any): any[]; - getHexMultiProof(tree: any, indices: any): string[]; - bufIndexOf(arr: any, el: any): number; - getProofFlags(els: any, proofs: any): any[]; - getPairElement(idx: any, layer: any): any; + /** + * getHexProof + * @desc Returns the proof for a target leaf as hex strings. + * @param {Buffer} leaf - Target leaf + * @param {Number} [index] - Target leaf index in leaves array. + * Use if there are leaves containing duplicate data in order to distinguish it. + * @return {String[]} - Proof array as hex strings. + * @example + * ```js + *const proof = tree.getHexProof(leaves[2]) + *``` + */ getHexProof(leaf: any, index?: any): string[]; + /** + * getProofIndices + * @desc Returns the proof indices for given tree indices. + * @param {Number[]} treeIndices - Tree indices + * @param {Number} depth - Tree depth; number of layers. + * @return {Number[]} - Proof indices + * @example + * ```js + *const proofIndices = tree.getProofIndices([2,5,6], 4) + *console.log(proofIndices) // [ 23, 20, 19, 8, 3 ] + *``` + */ + getProofIndices(treeIndices: any, depth: any): any[]; + /** + * getMultiProof + * @desc Returns the multiproof for given tree indices. + * @param {Number[]} indices - Tree indices. + * @return {Buffer[]} - Multiproofs + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getMultiProof(indices) + *``` + */ + getMultiProof(tree: any, indices: any): any[]; + /** + * getHexMultiProof + * @desc Returns the multiproof for given tree indices as hex strings. + * @param {Number[]} indices - Tree indices. + * @return {String[]} - Multiproofs as hex strings. + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getHexMultiProof(indices) + *``` + */ + getHexMultiProof(tree: any, indices: any): string[]; + /** + * getProofFlags + * @desc Returns list of booleans where proofs should be used instead of hashing. + * Proof flags are used in the Solidity multiproof verifiers. + * @param {Number[]} indices - Tree indices. + * @return {Boolean[]} - Boolean flags + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getMultiProof(indices) + *const proofFlags = tree.getProofFlags(leaves, proof) + *``` + */ + getProofFlags(els: any, proofs: any): any[]; /** * verify * @desc Returns true if the proof path (array of hashes) can connect the target node @@ -183,20 +236,217 @@ export declare class MerkleTree { *``` */ verify(proof: any, targetNode: any, root: any): boolean; + /** + * verifyMultiProof + * @desc Returns true if the multiproofs can connect the leaves to the Merkle root. + * @param {Buffer} root - Merkle tree root + * @param {Number[]} indices - Leave indices + * @param {Buffer[]} leaves - Leaf values at indices. + * @param {Number} depth - Tree depth + * @param {Buffer[]} proof - Multiproofs given indices + * @return {Boolean} + * @example + *```js + *const root = tree.getRoot() + *const treeFlat = tree.getLayersFlat() + *const depth = tree.getDepth() + *const indices = [2, 5, 6] + *const proofLeaves = indices.map(i => leaves[i]) + *const proof = tree.getMultiProof(treeFlat, indices) + *const verified = tree.verifyMultiProof(root, indices, proofLeaves, depth, proof) + *``` + */ verifyMultiProof(root: any, indices: any, leaves: any, depth: any, proof: any): any; + /** + * getDepth + * @desc Returns the tree depth (number of layers) + * @return {Number} + * @example + *```js + *const depth = tree.getDepth() + *``` + */ getDepth(): number; + /** + * getLayersAsObject + * @desc Returns the layers as nested objects instead of an array. + * @example + *```js + *const layersObj = tree.getLayersAsObject() + *``` + */ getLayersAsObject(): any; + /** + * print + * @desc Prints out a visual representation of the merkle tree. + * @example + *```js + *tree.print() + *``` + */ print(): void; - toTreeString(): any; + /** + * toTreeString + * @desc Returns a visual representation of the merkle tree as a string. + * @return {String} + * @example + *```js + *console.log(tree.toTreeString()) + *``` + */ + private _toTreeString; + /** + * toString + * @desc Returns a visual representation of the merkle tree as a string. + * @example + *```js + *console.log(tree.toString()) + *``` + */ toString(): any; + /** + * getMultiProof + * @desc Returns the multiproof for given tree indices. + * @param {Buffer[]} tree - Tree as a flat array. + * @param {Number[]} indices - Tree indices. + * @return {Buffer[]} - Multiproofs + * + *@example + * ```js + *const flatTree = tree.getLayersFlat() + *const indices = [2, 5, 6] + *const proof = MerkleTree.getMultiProof(flatTree, indices) + *``` + */ + static getMultiProof(tree: any, indices: any): any[]; + /** + * getPairNode + * @desc Returns the node at the index for given layer. + * @param {Buffer[]} layer - Tree layer + * @param {Number} index - Index at layer. + * @return {Buffer} - Node + * + *@example + * ```js + *const node = tree.getPairNode(layer, index) + *``` + */ + private _getPairNode; + /** + * bufferIndexOf + * @desc Returns the first index of which given buffer is found in array. + * @param {Buffer[]} haystack - Array of buffers. + * @param {Buffer} needle - Buffer to find. + * @return {Number} - Index number + * + * @example + * ```js + *const index = tree.bufferIndexOf(haystack, needle) + *``` + */ + private _bufferIndexOf; + /** + * bufferify + * @desc Returns a buffer type for the given value. + * @param {String|Number|Object|Buffer} value + * @return {Buffer} + * + * @example + * ```js + *const buf = MerkleTree.bufferify('0x1234') + *``` + */ static bufferify(x: any): any; - static isHexStr(v: any): boolean; + /** + * isHexString + * @desc Returns true if value is a hex string. + * @param {String} value + * @return {Boolean} + * + * @example + * ```js + *console.log(MerkleTree.isHexString('0x1234')) + *``` + */ + static isHexString(v: any): boolean; + /** + * print + * @desc Prints out a visual representation of the given merkle tree. + * @param {Object} tree - Merkle tree instance. + * @return {String} + * @example + *```js + *MerkleTree.print(tree) + *``` + */ static print(tree: any): void; - _bufferToHex(value: Buffer): string; - _bufferify(x: any): any; - _bufferifyFn(f: any): (x: any) => Buffer; - _isHexStr(v: any): boolean; - _log2(x: any): any; - _zip(a: any, b: any): any; + /** + * bufferToHex + * @desc Returns a hex string with 0x prefix for given buffer. + * @param {Buffer} value + * @return {String} + * @example + *```js + *const hexStr = tree.bufferToHex(Buffer.from('A')) + *``` + */ + private _bufferToHex; + /** + * bufferify + * @desc Returns a buffer type for the given value. + * @param {String|Number|Object|Buffer} value + * @return {Buffer} + * + * @example + * ```js + *const buf = MerkleTree.bufferify('0x1234') + *``` + */ + private _bufferify; + /** + * bufferifyFn + * @desc Returns a function that will bufferify the return value. + * @param {Function} + * @return {Function} + * + * @example + * ```js + *const fn = tree.bufferifyFn((value) => sha256(value)) + *``` + */ + private _bufferifyFn; + /** + * isHexString + * @desc Returns true if value is a hex string. + * @param {String} value + * @return {Boolean} + * + * @example + * ```js + *console.log(MerkleTree.isHexString('0x1234')) + *``` + */ + private _isHexString; + /** + * log2 + * @desc Returns the log2 of number. + * @param {Number} value + * @return {Number} + */ + private _log2; + /** + * zip + * @desc Returns true if value is a hex string. + * @param {String[]|Number[]|Buffer[]} a - first array + * @param {String[]|Number[]|Buffer[]} b - second array + * @return {String[][]|Number[][]|Buffer[][]} + * + * @example + * ```js + *const zipped = tree.zip(['a', 'b'],['A', 'B']) + *console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ] + *``` + */ + private _zip; } export default MerkleTree; diff --git a/dist/index.js b/dist/index.js index 263f5e3..2620a28 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const buffer_reverse_1 = __importDefault(require("buffer-reverse")); const crypto_js_1 = __importDefault(require("crypto-js")); +const sha256_1 = __importDefault(require("crypto-js/sha256")); const treeify_1 = __importDefault(require("treeify")); /** * Class reprensenting a Merkle Tree @@ -33,7 +34,7 @@ class MerkleTree { *const tree = new MerkleTree(leaves, sha256) *``` */ - constructor(leaves, hashAlgorithm, options = {}) { + constructor(leaves, hashAlgorithm = sha256_1.default, options = {}) { this.isBitcoinTree = !!options.isBitcoinTree; this.hashLeaves = !!options.hashLeaves; this.sortLeaves = !!options.sortLeaves; @@ -44,7 +45,6 @@ class MerkleTree { this.sortPairs = true; } this.duplicateOdd = !!options.duplicateOdd; - this.singleOdd = !!options.singleOdd; this.hashAlgo = this._bufferifyFn(hashAlgorithm); if (this.hashLeaves) { leaves = leaves.map(this.hashAlgo); @@ -54,10 +54,9 @@ class MerkleTree { this.leaves = this.leaves.sort(Buffer.compare); } this.layers = [this.leaves]; - this.createHashes(this.leaves); + this._createHashes(this.leaves); } - // TODO: documentation - createHashes(nodes) { + _createHashes(nodes) { while (nodes.length > 1) { const layerIndex = this.layers.length; this.layers.push([]); @@ -76,7 +75,7 @@ class MerkleTree { continue; } else { - if (!this.duplicateOdd && !this.singleOdd) { + if (!this.duplicateOdd) { this.layers[layerIndex].push(nodes[i]); continue; } @@ -91,21 +90,7 @@ class MerkleTree { combined = [buffer_reverse_1.default(left), buffer_reverse_1.default(right)]; } else { - if (this.singleOdd) { - right = nodes[i + 1]; - if (!left) { - combined = [right]; - } - else if (!right) { - combined = [left]; - } - else { - combined = [left, right]; - } - } - else { - combined = [left, right]; - } + combined = [left, right]; } if (this.sortPairs) { combined.sort(Buffer.compare); @@ -138,7 +123,7 @@ class MerkleTree { data = data.sort(Buffer.compare); } } - return this.leaves.filter(x => this.bufIndexOf(data, x) !== -1); + return this.leaves.filter(x => this._bufferIndexOf(data, x) !== -1); } return this.leaves; } @@ -252,7 +237,7 @@ class MerkleTree { * Use if there are leaves containing duplicate data in order to distinguish it. * @return {Object[]} - Array of objects containing a position property of type string * with values of 'left' or 'right' and a data property of type Buffer. - *@example + * @example * ```js *const proof = tree.getProof(leaves[2]) *``` @@ -312,7 +297,33 @@ class MerkleTree { return proof; } } - // TODO: documentation + /** + * getHexProof + * @desc Returns the proof for a target leaf as hex strings. + * @param {Buffer} leaf - Target leaf + * @param {Number} [index] - Target leaf index in leaves array. + * Use if there are leaves containing duplicate data in order to distinguish it. + * @return {String[]} - Proof array as hex strings. + * @example + * ```js + *const proof = tree.getHexProof(leaves[2]) + *``` + */ + getHexProof(leaf, index) { + return this.getProof(leaf, index).map(x => this._bufferToHex(x.data)); + } + /** + * getProofIndices + * @desc Returns the proof indices for given tree indices. + * @param {Number[]} treeIndices - Tree indices + * @param {Number} depth - Tree depth; number of layers. + * @return {Number[]} - Proof indices + * @example + * ```js + *const proofIndices = tree.getProofIndices([2,5,6], 4) + *console.log(proofIndices) // [ 23, 20, 19, 8, 3 ] + *``` + */ getProofIndices(treeIndices, depth) { const leafCount = Math.pow(2, depth); let maximalIndices = new Set(); @@ -343,7 +354,17 @@ class MerkleTree { return !treeIndices.includes(index - leafCount); }); } - // TODO: documentation + /** + * getMultiProof + * @desc Returns the multiproof for given tree indices. + * @param {Number[]} indices - Tree indices. + * @return {Buffer[]} - Multiproofs + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getMultiProof(indices) + *``` + */ getMultiProof(tree, indices) { if (!indices) { indices = tree; @@ -353,7 +374,7 @@ class MerkleTree { if (this.sortPairs) { els = els.sort(Buffer.compare); } - let ids = els.map((el) => this.bufIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1); + let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1); if (!ids.every((idx) => idx !== -1)) { throw new Error('Element does not exist in Merkle tree'); } @@ -364,7 +385,7 @@ class MerkleTree { const layer = this.layers[i]; for (let j = 0; j < ids.length; j++) { const idx = ids[j]; - const pairElement = this.getPairElement(idx, layer); + const pairElement = this._getPairNode(layer, idx); hashes.push(layer[idx]); if (pairElement) { proof.push(pairElement); @@ -379,22 +400,35 @@ class MerkleTree { } return this.getProofIndices(indices, this._log2((tree.length / 2) | 0)).map(index => tree[index]); } - // TODO: documentation + /** + * getHexMultiProof + * @desc Returns the multiproof for given tree indices as hex strings. + * @param {Number[]} indices - Tree indices. + * @return {String[]} - Multiproofs as hex strings. + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getHexMultiProof(indices) + *``` + */ getHexMultiProof(tree, indices) { return this.getMultiProof(tree, indices).map(this._bufferToHex); } - // TODO: documentation - bufIndexOf(arr, el) { - for (let i = 0; i < arr.length; i++) { - if (el.equals(arr[i])) { - return i; - } - } - return -1; - } - // TODO: documentation + /** + * getProofFlags + * @desc Returns list of booleans where proofs should be used instead of hashing. + * Proof flags are used in the Solidity multiproof verifiers. + * @param {Number[]} indices - Tree indices. + * @return {Boolean[]} - Boolean flags + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getMultiProof(indices) + *const proofFlags = tree.getProofFlags(leaves, proof) + *``` + */ getProofFlags(els, proofs) { - let ids = els.map((el) => this.bufIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1); + let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1); if (!ids.every((idx) => idx !== -1)) { throw new Error('Element does not exist in Merkle tree'); } @@ -405,7 +439,7 @@ class MerkleTree { ids = ids.reduce((ids, idx) => { const skipped = tested.includes(layer[idx]); if (!skipped) { - const pairElement = this.getPairElement(idx, layer); + const pairElement = this._getPairNode(layer, idx); const proofUsed = proofs.includes(layer[idx]) || proofs.includes(pairElement); pairElement && flags.push(!proofUsed); tested.push(layer[idx]); @@ -417,19 +451,6 @@ class MerkleTree { } return flags; } - getPairElement(idx, layer) { - const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; - if (pairIdx < layer.length) { - return layer[pairIdx]; - } - else { - return null; - } - } - // TODO: documentation - getHexProof(leaf, index) { - return this.getProof(leaf, index).map(x => this._bufferToHex(x.data)); - } /** * verify * @desc Returns true if the proof path (array of hashes) can connect the target node @@ -495,7 +516,26 @@ class MerkleTree { } return Buffer.compare(hash, root) === 0; } - // TODO: documentation + /** + * verifyMultiProof + * @desc Returns true if the multiproofs can connect the leaves to the Merkle root. + * @param {Buffer} root - Merkle tree root + * @param {Number[]} indices - Leave indices + * @param {Buffer[]} leaves - Leaf values at indices. + * @param {Number} depth - Tree depth + * @param {Buffer[]} proof - Multiproofs given indices + * @return {Boolean} + * @example + *```js + *const root = tree.getRoot() + *const treeFlat = tree.getLayersFlat() + *const depth = tree.getDepth() + *const indices = [2, 5, 6] + *const proofLeaves = indices.map(i => leaves[i]) + *const proof = tree.getMultiProof(treeFlat, indices) + *const verified = tree.verifyMultiProof(root, indices, proofLeaves, depth, proof) + *``` + */ verifyMultiProof(root, indices, leaves, depth, proof) { root = this._bufferify(root); leaves = leaves.map(this._bufferify); @@ -520,11 +560,26 @@ class MerkleTree { } return !indices.length || (({}).hasOwnProperty.call(tree, 1) && tree[1].equals(root)); } - // TODO: documentation + /** + * getDepth + * @desc Returns the tree depth (number of layers) + * @return {Number} + * @example + *```js + *const depth = tree.getDepth() + *``` + */ getDepth() { return this.getLayers().length - 1; } - // TODO: documentation + /** + * getLayersAsObject + * @desc Returns the layers as nested objects instead of an array. + * @example + *```js + *const layersObj = tree.getLayersAsObject() + *``` + */ getLayersAsObject() { const layers = this.getLayers().map(x => x.map(x => x.toString('hex'))); const objs = []; @@ -549,27 +604,118 @@ class MerkleTree { } return objs[0]; } - // TODO: documentation + /** + * print + * @desc Prints out a visual representation of the merkle tree. + * @example + *```js + *tree.print() + *``` + */ print() { MerkleTree.print(this); } - // TODO: documentation - toTreeString() { + /** + * toTreeString + * @desc Returns a visual representation of the merkle tree as a string. + * @return {String} + * @example + *```js + *console.log(tree.toTreeString()) + *``` + */ + _toTreeString() { const obj = this.getLayersAsObject(); return treeify_1.default.asTree(obj, true); } - // TODO: documentation + /** + * toString + * @desc Returns a visual representation of the merkle tree as a string. + * @example + *```js + *console.log(tree.toString()) + *``` + */ toString() { - return this.toTreeString(); + return this._toTreeString(); } - // TODO: documentation + /** + * getMultiProof + * @desc Returns the multiproof for given tree indices. + * @param {Buffer[]} tree - Tree as a flat array. + * @param {Number[]} indices - Tree indices. + * @return {Buffer[]} - Multiproofs + * + *@example + * ```js + *const flatTree = tree.getLayersFlat() + *const indices = [2, 5, 6] + *const proof = MerkleTree.getMultiProof(flatTree, indices) + *``` + */ + static getMultiProof(tree, indices) { + const t = new MerkleTree([]); + return t.getMultiProof(tree.getLayersFlat(), indices); + } + /** + * getPairNode + * @desc Returns the node at the index for given layer. + * @param {Buffer[]} layer - Tree layer + * @param {Number} index - Index at layer. + * @return {Buffer} - Node + * + *@example + * ```js + *const node = tree.getPairNode(layer, index) + *``` + */ + _getPairNode(layer, idx) { + const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; + if (pairIdx < layer.length) { + return layer[pairIdx]; + } + else { + return null; + } + } + /** + * bufferIndexOf + * @desc Returns the first index of which given buffer is found in array. + * @param {Buffer[]} haystack - Array of buffers. + * @param {Buffer} needle - Buffer to find. + * @return {Number} - Index number + * + * @example + * ```js + *const index = tree.bufferIndexOf(haystack, needle) + *``` + */ + _bufferIndexOf(arr, el) { + for (let i = 0; i < arr.length; i++) { + if (el.equals(arr[i])) { + return i; + } + } + return -1; + } + /** + * bufferify + * @desc Returns a buffer type for the given value. + * @param {String|Number|Object|Buffer} value + * @return {Buffer} + * + * @example + * ```js + *const buf = MerkleTree.bufferify('0x1234') + *``` + */ static bufferify(x) { if (!Buffer.isBuffer(x)) { // crypto-js support if (typeof x === 'object' && x.words) { return Buffer.from(x.toString(crypto_js_1.default.enc.Hex), 'hex'); } - else if (MerkleTree.isHexStr(x)) { + else if (MerkleTree.isHexString(x)) { return Buffer.from(x.replace(/^0x/, ''), 'hex'); } else if (typeof x === 'string') { @@ -578,38 +724,120 @@ class MerkleTree { } return x; } - static isHexStr(v) { + /** + * isHexString + * @desc Returns true if value is a hex string. + * @param {String} value + * @return {Boolean} + * + * @example + * ```js + *console.log(MerkleTree.isHexString('0x1234')) + *``` + */ + static isHexString(v) { return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v)); } - // TODO: documentation + /** + * print + * @desc Prints out a visual representation of the given merkle tree. + * @param {Object} tree - Merkle tree instance. + * @return {String} + * @example + *```js + *MerkleTree.print(tree) + *``` + */ static print(tree) { console.log(tree.toString()); } + /** + * bufferToHex + * @desc Returns a hex string with 0x prefix for given buffer. + * @param {Buffer} value + * @return {String} + * @example + *```js + *const hexStr = tree.bufferToHex(Buffer.from('A')) + *``` + */ _bufferToHex(value) { return '0x' + value.toString('hex'); } + /** + * bufferify + * @desc Returns a buffer type for the given value. + * @param {String|Number|Object|Buffer} value + * @return {Buffer} + * + * @example + * ```js + *const buf = MerkleTree.bufferify('0x1234') + *``` + */ _bufferify(x) { return MerkleTree.bufferify(x); } + /** + * bufferifyFn + * @desc Returns a function that will bufferify the return value. + * @param {Function} + * @return {Function} + * + * @example + * ```js + *const fn = tree.bufferifyFn((value) => sha256(value)) + *``` + */ _bufferifyFn(f) { return function (x) { const v = f(x); if (Buffer.isBuffer(v)) { return v; } - if (this._isHexStr(v)) { + if (this._isHexString(v)) { return Buffer.from(v, 'hex'); } // crypto-js support return Buffer.from(f(crypto_js_1.default.enc.Hex.parse(x.toString('hex'))).toString(crypto_js_1.default.enc.Hex), 'hex'); }; } - _isHexStr(v) { - return MerkleTree.isHexStr(v); + /** + * isHexString + * @desc Returns true if value is a hex string. + * @param {String} value + * @return {Boolean} + * + * @example + * ```js + *console.log(MerkleTree.isHexString('0x1234')) + *``` + */ + _isHexString(v) { + return MerkleTree.isHexString(v); } + /** + * log2 + * @desc Returns the log2 of number. + * @param {Number} value + * @return {Number} + */ _log2(x) { return x === 1 ? 0 : 1 + this._log2((x / 2) | 0); } + /** + * zip + * @desc Returns true if value is a hex string. + * @param {String[]|Number[]|Buffer[]} a - first array + * @param {String[]|Number[]|Buffer[]} b - second array + * @return {String[][]|Number[][]|Buffer[][]} + * + * @example + * ```js + *const zipped = tree.zip(['a', 'b'],['A', 'B']) + *console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ] + *``` + */ _zip(a, b) { return a.map((e, i) => [e, b[i]]); } diff --git a/index.ts b/index.ts index 62e30b5..7365d05 100644 --- a/index.ts +++ b/index.ts @@ -1,12 +1,11 @@ import reverse from 'buffer-reverse' import CryptoJS from 'crypto-js' +import SHA256 from 'crypto-js/sha256' import treeify from 'treeify' interface Options { /** If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash. */ duplicateOdd?: boolean - /** If set to `true`, an odd node will not have a pair generating the layer hash. */ - singleOdd?: boolean /** If set to `true`, the leaves will hashed using the set hashing algorithms. */ hashLeaves?: boolean /** If set to `true`, constructs the Merkle Tree using the [Bitcoin Merkle Tree implementation](http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html). Enable it when you need to replicate Bitcoin constructed Merkle Trees. In Bitcoin Merkle Trees, single nodes are combined with themselves, and each output hash is hashed again. */ @@ -30,7 +29,6 @@ type TLayer = any */ export class MerkleTree { duplicateOdd: boolean - singleOdd: boolean hashAlgo: (value: TValue) => THashAlgo hashLeaves: boolean isBitcoinTree: boolean @@ -62,7 +60,7 @@ export class MerkleTree { *const tree = new MerkleTree(leaves, sha256) *``` */ - constructor (leaves, hashAlgorithm, options: Options = {}) { + constructor (leaves, hashAlgorithm=SHA256, options: Options = {}) { this.isBitcoinTree = !!options.isBitcoinTree this.hashLeaves = !!options.hashLeaves this.sortLeaves = !!options.sortLeaves @@ -75,7 +73,6 @@ export class MerkleTree { } this.duplicateOdd = !!options.duplicateOdd - this.singleOdd = !!options.singleOdd this.hashAlgo = this._bufferifyFn(hashAlgorithm) if (this.hashLeaves) { @@ -88,11 +85,10 @@ export class MerkleTree { } this.layers = [this.leaves] - this.createHashes(this.leaves) + this._createHashes(this.leaves) } - // TODO: documentation - createHashes (nodes) { + private _createHashes (nodes) { while (nodes.length > 1) { const layerIndex = this.layers.length @@ -114,7 +110,7 @@ export class MerkleTree { this.layers[layerIndex].push(hash) continue } else { - if (!this.duplicateOdd && !this.singleOdd) { + if (!this.duplicateOdd) { this.layers[layerIndex].push(nodes[i]) continue } @@ -130,18 +126,7 @@ export class MerkleTree { if (this.isBitcoinTree) { combined = [reverse(left), reverse(right)] } else { - if (this.singleOdd) { - right = nodes[i + 1] - if (!left) { - combined = [right] - } else if (!right) { - combined = [left] - } else { - combined = [left, right] - } - } else { - combined = [left, right] - } + combined = [left, right] } if (this.sortPairs) { @@ -182,7 +167,7 @@ export class MerkleTree { } } - return this.leaves.filter(x => this.bufIndexOf(data, x) !== -1) + return this.leaves.filter(x => this._bufferIndexOf(data, x) !== -1) } return this.leaves @@ -307,7 +292,7 @@ export class MerkleTree { * Use if there are leaves containing duplicate data in order to distinguish it. * @return {Object[]} - Array of objects containing a position property of type string * with values of 'left' or 'right' and a data property of type Buffer. - *@example + * @example * ```js *const proof = tree.getProof(leaves[2]) *``` @@ -379,7 +364,34 @@ export class MerkleTree { } } - // TODO: documentation + /** + * getHexProof + * @desc Returns the proof for a target leaf as hex strings. + * @param {Buffer} leaf - Target leaf + * @param {Number} [index] - Target leaf index in leaves array. + * Use if there are leaves containing duplicate data in order to distinguish it. + * @return {String[]} - Proof array as hex strings. + * @example + * ```js + *const proof = tree.getHexProof(leaves[2]) + *``` + */ + getHexProof (leaf, index?) { + return this.getProof(leaf, index).map(x => this._bufferToHex(x.data)) + } + + /** + * getProofIndices + * @desc Returns the proof indices for given tree indices. + * @param {Number[]} treeIndices - Tree indices + * @param {Number} depth - Tree depth; number of layers. + * @return {Number[]} - Proof indices + * @example + * ```js + *const proofIndices = tree.getProofIndices([2,5,6], 4) + *console.log(proofIndices) // [ 23, 20, 19, 8, 3 ] + *``` + */ getProofIndices (treeIndices, depth) { const leafCount = 2 ** depth let maximalIndices :any = new Set() @@ -414,7 +426,17 @@ export class MerkleTree { }) } - // TODO: documentation + /** + * getMultiProof + * @desc Returns the multiproof for given tree indices. + * @param {Number[]} indices - Tree indices. + * @return {Buffer[]} - Multiproofs + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getMultiProof(indices) + *``` + */ getMultiProof (tree, indices) { if (!indices) { indices = tree @@ -426,7 +448,7 @@ export class MerkleTree { els = els.sort(Buffer.compare) } - let ids = els.map((el) => this.bufIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) + let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) if (!ids.every((idx) => idx !== -1)) { throw new Error('Element does not exist in Merkle tree') } @@ -439,7 +461,7 @@ export class MerkleTree { const layer = this.layers[i] for (let j = 0; j < ids.length; j++) { const idx = ids[j] - const pairElement = this.getPairElement(idx, layer) + const pairElement = this._getPairNode(layer, idx) hashes.push(layer[idx]) if (pairElement) { @@ -460,25 +482,36 @@ export class MerkleTree { return this.getProofIndices(indices, this._log2((tree.length / 2) | 0)).map(index => tree[index]) } - // TODO: documentation + /** + * getHexMultiProof + * @desc Returns the multiproof for given tree indices as hex strings. + * @param {Number[]} indices - Tree indices. + * @return {String[]} - Multiproofs as hex strings. + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getHexMultiProof(indices) + *``` + */ getHexMultiProof (tree, indices) { return this.getMultiProof(tree, indices).map(this._bufferToHex) } - // TODO: documentation - bufIndexOf (arr, el) { - for (let i = 0; i < arr.length; i++) { - if (el.equals(arr[i])) { - return i - } - } - - return -1 - } - - // TODO: documentation + /** + * getProofFlags + * @desc Returns list of booleans where proofs should be used instead of hashing. + * Proof flags are used in the Solidity multiproof verifiers. + * @param {Number[]} indices - Tree indices. + * @return {Boolean[]} - Boolean flags + * @example + * ```js + *const indices = [2, 5, 6] + *const proof = tree.getMultiProof(indices) + *const proofFlags = tree.getProofFlags(leaves, proof) + *``` + */ getProofFlags (els, proofs) { - let ids = els.map((el) => this.bufIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) + let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) if (!ids.every((idx) => idx !== -1)) { throw new Error('Element does not exist in Merkle tree') } @@ -490,7 +523,7 @@ export class MerkleTree { ids = ids.reduce((ids, idx) => { const skipped = tested.includes(layer[idx]) if (!skipped) { - const pairElement = this.getPairElement(idx, layer) + const pairElement = this._getPairNode(layer, idx) const proofUsed = proofs.includes(layer[idx]) || proofs.includes(pairElement) pairElement && flags.push(!proofUsed) tested.push(layer[idx]) @@ -504,21 +537,6 @@ export class MerkleTree { return flags } - getPairElement (idx, layer) { - const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1 - - if (pairIdx < layer.length) { - return layer[pairIdx] - } else { - return null - } - } - - // TODO: documentation - getHexProof (leaf, index?) { - return this.getProof(leaf, index).map(x => this._bufferToHex(x.data)) - } - /** * verify * @desc Returns true if the proof path (array of hashes) can connect the target node @@ -589,7 +607,26 @@ export class MerkleTree { return Buffer.compare(hash, root) === 0 } - // TODO: documentation + /** + * verifyMultiProof + * @desc Returns true if the multiproofs can connect the leaves to the Merkle root. + * @param {Buffer} root - Merkle tree root + * @param {Number[]} indices - Leave indices + * @param {Buffer[]} leaves - Leaf values at indices. + * @param {Number} depth - Tree depth + * @param {Buffer[]} proof - Multiproofs given indices + * @return {Boolean} + * @example + *```js + *const root = tree.getRoot() + *const treeFlat = tree.getLayersFlat() + *const depth = tree.getDepth() + *const indices = [2, 5, 6] + *const proofLeaves = indices.map(i => leaves[i]) + *const proof = tree.getMultiProof(treeFlat, indices) + *const verified = tree.verifyMultiProof(root, indices, proofLeaves, depth, proof) + *``` + */ verifyMultiProof (root, indices, leaves, depth, proof) { root = this._bufferify(root) leaves = leaves.map(this._bufferify) @@ -616,12 +653,27 @@ export class MerkleTree { return !indices.length || (({}).hasOwnProperty.call(tree, 1) && tree[1].equals(root)) } - // TODO: documentation + /** + * getDepth + * @desc Returns the tree depth (number of layers) + * @return {Number} + * @example + *```js + *const depth = tree.getDepth() + *``` + */ getDepth () { return this.getLayers().length - 1 } - // TODO: documentation + /** + * getLayersAsObject + * @desc Returns the layers as nested objects instead of an array. + * @example + *```js + *const layersObj = tree.getLayersAsObject() + *``` + */ getLayersAsObject () { const layers = this.getLayers().map(x => x.map(x => x.toString('hex'))) const objs = [] @@ -650,29 +702,124 @@ export class MerkleTree { return objs[0] } - // TODO: documentation + /** + * print + * @desc Prints out a visual representation of the merkle tree. + * @example + *```js + *tree.print() + *``` + */ print () { MerkleTree.print(this) } - // TODO: documentation - toTreeString () { + /** + * toTreeString + * @desc Returns a visual representation of the merkle tree as a string. + * @return {String} + * @example + *```js + *console.log(tree.toTreeString()) + *``` + */ + private _toTreeString () { const obj = this.getLayersAsObject() return treeify.asTree(obj, true) } - // TODO: documentation + /** + * toString + * @desc Returns a visual representation of the merkle tree as a string. + * @example + *```js + *console.log(tree.toString()) + *``` + */ toString () { - return this.toTreeString() + return this._toTreeString() } - // TODO: documentation + /** + * getMultiProof + * @desc Returns the multiproof for given tree indices. + * @param {Buffer[]} tree - Tree as a flat array. + * @param {Number[]} indices - Tree indices. + * @return {Buffer[]} - Multiproofs + * + *@example + * ```js + *const flatTree = tree.getLayersFlat() + *const indices = [2, 5, 6] + *const proof = MerkleTree.getMultiProof(flatTree, indices) + *``` + */ + static getMultiProof (tree, indices) { + const t = new MerkleTree([]) + return t.getMultiProof(tree.getLayersFlat(), indices) + } + + /** + * getPairNode + * @desc Returns the node at the index for given layer. + * @param {Buffer[]} layer - Tree layer + * @param {Number} index - Index at layer. + * @return {Buffer} - Node + * + *@example + * ```js + *const node = tree.getPairNode(layer, index) + *``` + */ + private _getPairNode(layer, idx) { + const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1 + + if (pairIdx < layer.length) { + return layer[pairIdx] + } else { + return null + } + } + + /** + * bufferIndexOf + * @desc Returns the first index of which given buffer is found in array. + * @param {Buffer[]} haystack - Array of buffers. + * @param {Buffer} needle - Buffer to find. + * @return {Number} - Index number + * + * @example + * ```js + *const index = tree.bufferIndexOf(haystack, needle) + *``` + */ + private _bufferIndexOf (arr, el) { + for (let i = 0; i < arr.length; i++) { + if (el.equals(arr[i])) { + return i + } + } + + return -1 + } + + /** + * bufferify + * @desc Returns a buffer type for the given value. + * @param {String|Number|Object|Buffer} value + * @return {Buffer} + * + * @example + * ```js + *const buf = MerkleTree.bufferify('0x1234') + *``` + */ static bufferify (x) { if (!Buffer.isBuffer(x)) { // crypto-js support if (typeof x === 'object' && x.words) { return Buffer.from(x.toString(CryptoJS.enc.Hex), 'hex') - } else if (MerkleTree.isHexStr(x)) { + } else if (MerkleTree.isHexString(x)) { return Buffer.from(x.replace(/^0x/, ''), 'hex') } else if (typeof x === 'string') { return Buffer.from(x) @@ -682,31 +829,83 @@ export class MerkleTree { return x } - static isHexStr (v) { + /** + * isHexString + * @desc Returns true if value is a hex string. + * @param {String} value + * @return {Boolean} + * + * @example + * ```js + *console.log(MerkleTree.isHexString('0x1234')) + *``` + */ + static isHexString (v) { return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v)) } - // TODO: documentation + /** + * print + * @desc Prints out a visual representation of the given merkle tree. + * @param {Object} tree - Merkle tree instance. + * @return {String} + * @example + *```js + *MerkleTree.print(tree) + *``` + */ static print (tree) { console.log(tree.toString()) } - _bufferToHex (value: Buffer) { + /** + * bufferToHex + * @desc Returns a hex string with 0x prefix for given buffer. + * @param {Buffer} value + * @return {String} + * @example + *```js + *const hexStr = tree.bufferToHex(Buffer.from('A')) + *``` + */ + private _bufferToHex (value: Buffer) { return '0x' + value.toString('hex') } - _bufferify (x) { + /** + * bufferify + * @desc Returns a buffer type for the given value. + * @param {String|Number|Object|Buffer} value + * @return {Buffer} + * + * @example + * ```js + *const buf = MerkleTree.bufferify('0x1234') + *``` + */ + private _bufferify (x) { return MerkleTree.bufferify(x) } - _bufferifyFn (f) { + /** + * bufferifyFn + * @desc Returns a function that will bufferify the return value. + * @param {Function} + * @return {Function} + * + * @example + * ```js + *const fn = tree.bufferifyFn((value) => sha256(value)) + *``` + */ + private _bufferifyFn (f) { return function (x) { const v = f(x) if (Buffer.isBuffer(v)) { return v } - if (this._isHexStr(v)) { + if (this._isHexString(v)) { return Buffer.from(v, 'hex') } @@ -715,15 +914,45 @@ export class MerkleTree { } } - _isHexStr (v) { - return MerkleTree.isHexStr(v) + /** + * isHexString + * @desc Returns true if value is a hex string. + * @param {String} value + * @return {Boolean} + * + * @example + * ```js + *console.log(MerkleTree.isHexString('0x1234')) + *``` + */ + private _isHexString (v) { + return MerkleTree.isHexString(v) } - _log2 (x) { + /** + * log2 + * @desc Returns the log2 of number. + * @param {Number} value + * @return {Number} + */ + private _log2 (x) { return x === 1 ? 0 : 1 + this._log2((x / 2) | 0) } - _zip (a, b) { + /** + * zip + * @desc Returns true if value is a hex string. + * @param {String[]|Number[]|Buffer[]} a - first array + * @param {String[]|Number[]|Buffer[]} b - second array + * @return {String[][]|Number[][]|Buffer[][]} + * + * @example + * ```js + *const zipped = tree.zip(['a', 'b'],['A', 'B']) + *console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ] + *``` + */ + private _zip (a, b) { return a.map((e, i) => [e, b[i]]) } } diff --git a/package.json b/package.json index d045fb6..0553344 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "merkletreejs", - "version": "0.2.1", + "version": "0.2.2", "description": "Construct Merkle Trees and verify proofs", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -11,7 +11,7 @@ "test": "tape test/*.js", "build": "rm -rf dist/ && tsc", "lint": "standardx --fix index.ts test/*.js", - "docs:md": "rm -rf docs/ && typedoc --theme markdown index.ts --out docs --mdEngine github --mdDocusaurus --mdHideSources" + "docs": "rm -rf docs/ && typedoc --theme markdown index.ts --out docs --mdEngine github --mdDocusaurus --mdHideSources" }, "repository": { "type": "git", diff --git a/test/merkletree.test.js b/test/merkletree.test.js index 5701b52..2307f5d 100644 --- a/test/merkletree.test.js +++ b/test/merkletree.test.js @@ -13,23 +13,59 @@ const { MerkleTree } = require('../') const sha256 = (data) => crypto.createHash('sha256').update(data).digest() test('sha256 with keccak leaves', t => { - t.plan(1) + t.plan(3) const leaves = ['a', 'b', 'c'].map(x => keccak(x)) const tree = new MerkleTree(leaves, sha256) - const root = '311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae' - t.equal(tree.getRoot().toString('hex'), root) + t.equal(tree.getHexRoot(), '0x311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae') + t.deepEqual(tree.getHexLeaves(), [ + '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510', + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2' + ]) + t.deepEqual(tree.getHexLayers(), [ + [ + '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510', + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2' + ], + [ + '0x176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1', + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2' + ], + [ + '0x311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae' + ] + ]) }) test('sha256 with keccak leaves with duplicate odd option', t => { - t.plan(1) + t.plan(3) const leaves = ['a', 'b', 'c'].map(x => keccak(x)) const tree = new MerkleTree(leaves, sha256, { duplicateOdd: true }) - const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1' - t.equal(tree.getRoot().toString('hex'), root) + t.equal(tree.getHexRoot(), '0xbcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1') + t.deepEqual(tree.getHexLeaves(), [ + '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510', + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2' + ]) + t.deepEqual(tree.getHexLayers(), [ + [ + '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510', + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2' + ], + [ + '0x176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1', + '0x43e061172b1177f25d0f156b2d2ed11728006fade8e167ff3d1b9dbc979a3358' + ], + [ + '0xbcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1' + ] + ]) }) test('crypto-js - sha256', t => { @@ -462,9 +498,21 @@ test('crypto-js bufferify', t => { t.deepEqual(leaves.map(MerkleTree.bufferify), leaves.map(bufferifyCryptoJS)) }) -test('sha1', t => { +test('bufferify', t => { t.plan(1) + t.deepEqual(MerkleTree.bufferify('0x1234'), Buffer.from('1234', 'hex')) +}) + +test('isHexString', t => { + t.plan(1) + + t.deepEqual(MerkleTree.isHexString('0x1234'), true) +}) + +test('sha1', t => { + t.plan(2) + const leaves = [ 'd89f84d948796605a413e196f40bce1d6294175d', '32f04c7f572bf75a266268c6f4d8c92731dc3b7f', @@ -481,6 +529,11 @@ test('sha1', t => { const leaf = 'd89f84d948796605a413e196f40bce1d6294175d' const proof = tree.getHexProof(leaf) + t.deepEqual(proof, [ + '0xb80b52d80f5fe940ac2c987044bc439e4218ac94', + '0x59f544ee5de8d761b124ccd4e1285d3b02a2a539' + ]) + t.equal(tree.verify(proof, leaf, root), true) }) @@ -691,7 +744,7 @@ test('sha256 getMultiProof using tree array', t => { ]) const depth = tree.getDepth() - t.equal(depth, Math.log2((treeFlat.length/2)|0)) + t.equal(depth, Math.log2((treeFlat.length / 2) | 0)) const tRoot = treeFlat[1] const tLeaves = indices.map(i => leaves[i])