Update docs

This commit is contained in:
Miguel Mota 2020-06-06 12:07:23 -07:00
parent 9957ac545e
commit a69a927e8e
8 changed files with 1354 additions and 384 deletions

624
README.md
View File

@ -1,6 +1,6 @@
<h3 align="center">
<br />
<img src="https://user-images.githubusercontent.com/168240/39508295-ceeb1576-4d96-11e8-90aa-b2a56825567d.png" alt="logo" width="600" />
<img src="https://user-images.githubusercontent.com/168240/83951171-85f48c80-a7e4-11ea-896e-529c28ffa18e.png" alt="merkletree.js logo" width="600" />
<br />
<br />
<br />
@ -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
<img src="https://user-images.githubusercontent.com/168240/43616375-15330c32-9671-11e8-9057-6e61c312c856.png" alt="Merkle Tree" width="500">
Diagram of Merkle Tree Proof
<img src="https://user-images.githubusercontent.com/168240/43616387-27ec860a-9671-11e8-9f3f-0b871a6581a6.png" alt="Merkle Tree Proof" width="420">
Diagram of Invalid Merkle Tree Proofs
<img src="https://user-images.githubusercontent.com/168240/43616398-33e20584-9671-11e8-9f62-9f48ce412898.png" alt="Merkle Tree Proof" width="420">
Diagram of Bitcoin Merkle Tree
<img src="https://user-images.githubusercontent.com/168240/43616417-46d3293e-9671-11e8-81c3-8cdf7f8ddd77.png" alt="Merkle Tree Proof" width="420">
## Install
```bash
@ -93,6 +75,24 @@ Output
└─ 0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2
```
## Diagrams
Visualization of Merkle Tree
<img src="https://user-images.githubusercontent.com/168240/43616375-15330c32-9671-11e8-9057-6e61c312c856.png" alt="Merkle Tree" width="500">
Visualization of Merkle Tree Proof
<img src="https://user-images.githubusercontent.com/168240/43616387-27ec860a-9671-11e8-9f3f-0b871a6581a6.png" alt="Merkle Tree Proof" width="420">
Visualization of Invalid Merkle Tree Proofs
<img src="https://user-images.githubusercontent.com/168240/43616398-33e20584-9671-11e8-9f62-9f48ce412898.png" alt="Merkle Tree Proof" width="420">
Visualization of Bitcoin Merkle Tree
<img src="https://user-images.githubusercontent.com/168240/43616417-46d3293e-9671-11e8-81c3-8cdf7f8ddd77.png" alt="Merkle Tree Proof" width="420">
## Documentation
<!-- :%s/// -->
@ -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)
___
<a id="hashleaves"></a>
@ -223,14 +224,21 @@ ___
### layers
**● layers**: *`any`[]*
**● layers**: *[TLayer](api-modules-index-module.md#tlayer)[]*
___
<a id="leaves"></a>
### leaves
**● leaves**: *`any`[]*
**● leaves**: *[TLeaf](api-modules-index-module.md#tleaf)[]*
___
<a id="sort"></a>
### sort
**● sort**: *`boolean`*
___
<a id="sortleaves"></a>
@ -250,11 +258,73 @@ ___
## Methods
<a id="createhashes"></a>
<a id="_bufferindexof"></a>
### createHashes
### `<Private>` _bufferIndexOf
**createHashes**(nodes: *`any`*): `void`
**_bufferIndexOf**(arr: *`any`*, el: *`any`*): `number`
**Parameters:**
| Name | Type |
| ------ | ------ |
| arr | `any` |
| el | `any` |
**Returns:** `number`
* Index number
___
<a id="_buffertohex"></a>
### `<Private>` _bufferToHex
**_bufferToHex**(value: *`Buffer`*): `string`
**Parameters:**
| Name | Type | Description |
| ------ | ------ | ------ |
| value | `Buffer` | \- |
**Returns:** `string`
___
<a id="_bufferify"></a>
### `<Private>` _bufferify
**_bufferify**(x: *`any`*): `any`
**Parameters:**
| Name | Type |
| ------ | ------ |
| x | `any` |
**Returns:** `any`
___
<a id="_bufferifyfn"></a>
### `<Private>` _bufferifyFn
**_bufferifyFn**(f: *`any`*): `(Anonymous function)`
**Parameters:**
| Name | Type |
| ------ | ------ |
| f | `any` |
**Returns:** `(Anonymous function)`
___
<a id="_createhashes"></a>
### `<Private>` _createHashes
**_createHashes**(nodes: *`any`*): `void`
**Parameters:**
@ -264,6 +334,157 @@ ___
**Returns:** `void`
___
<a id="_getpairnode"></a>
### `<Private>` _getPairNode
**_getPairNode**(layer: *`any`*, idx: *`any`*): `any`
**Parameters:**
| Name | Type | Description |
| ------ | ------ | ------ |
| layer | `any` | Tree layer |
| idx | `any` |
**Returns:** `any`
* Node
___
<a id="_ishexstring"></a>
### `<Private>` _isHexString
**_isHexString**(v: *`any`*): `boolean`
**Parameters:**
| Name | Type |
| ------ | ------ |
| v | `any` |
**Returns:** `boolean`
___
<a id="_log2"></a>
### `<Private>` _log2
**_log2**(x: *`any`*): `any`
**Parameters:**
| Name | Type |
| ------ | ------ |
| x | `any` |
**Returns:** `any`
___
<a id="_totreestring"></a>
### `<Private>` _toTreeString
**_toTreeString**(): `any`
**Returns:** `any`
___
<a id="_zip"></a>
### `<Private>` _zip
**_zip**(a: *`any`*, b: *`any`*): `any`
**Parameters:**
| Name | Type | Description |
| ------ | ------ | ------ |
| a | `any` | first array |
| b | `any` | second array |
**Returns:** `any`
___
<a id="getdepth"></a>
### getDepth
**getDepth**(): `number`
**Returns:** `number`
___
<a id="gethexlayers"></a>
### getHexLayers
**getHexLayers**(): `any`
**Returns:** `any`
___
<a id="gethexlayersflat"></a>
### getHexLayersFlat
**getHexLayersFlat**(): `any`
**Returns:** `any`
___
<a id="gethexleaves"></a>
### getHexLeaves
**getHexLeaves**(): `string`[]
**Returns:** `string`[]
___
<a id="gethexmultiproof"></a>
### getHexMultiProof
**getHexMultiProof**(tree: *`any`*, indices: *`any`*): `string`[]
**Parameters:**
| Name | Type | Description |
| ------ | ------ | ------ |
| tree | `any` |
| indices | `any` | Tree indices. |
**Returns:** `string`[]
* Multiproofs as hex strings.
___
<a id="gethexproof"></a>
### 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.
___
<a id="gethexroot"></a>
### getHexRoot
**getHexRoot**(): `string`
**Returns:** `string`
___
<a id="getlayers"></a>
@ -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`
___
<a id="getlayersflat"></a>
### getLayersFlat
**getLayersFlat**(): `any`
**Returns:** `any`
___
<a id="getleaves"></a>
### 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`[]
___
<a id="getmultiproof"></a>
### getMultiProof
**getMultiProof**(tree: *`any`*, indices: *`any`*): `any`[]
**Parameters:**
| Name | Type | Description |
| ------ | ------ | ------ |
| tree | `any` |
| indices | `any` | Tree indices. |
**Returns:** `any`[]
* Multiproofs
___
<a id="getproof"></a>
### 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.
___
<a id="getproofflags"></a>
### getProofFlags
**getProofFlags**(els: *`any`*, proofs: *`any`*): `any`[]
**Parameters:**
| Name | Type |
| ------ | ------ |
| els | `any` |
| proofs | `any` |
**Returns:** `any`[]
* Boolean flags
___
<a id="getproofindices"></a>
### 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
___
<a id="getroot"></a>
@ -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`
___
<a id="totreestring"></a>
### toTreeString
**toTreeString**(): `any`
**Returns:** `any`
___
<a id="verify"></a>
@ -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`
___
<a id="verifymultiproof"></a>
### 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`
___
<a id="bufferify"></a>
@ -430,6 +673,38 @@ ___
**Returns:** `any`
___
<a id="getmultiproof-1"></a>
### `<Static>` 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
___
<a id="ishexstring"></a>
### `<Static>` isHexString
**isHexString**(v: *`any`*): `boolean`
**Parameters:**
| Name | Type |
| ------ | ------ |
| v | `any` |
**Returns:** `boolean`
___
<a id="print-1"></a>
@ -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
<a id="duplicateodd"></a>
### duplicateOdd
**● duplicateOdd**: *`boolean`*
If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash.
___
<a id="hashleaves"></a>
### hashLeaves
**● hashLeaves**: *`boolean`*
If set to `true`, the leaves will hashed using the set hashing algorithms.
___
<a id="isbitcointree"></a>
### 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.
___
<a id="sortleaves"></a>
### 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.
___
<a id="sortpairs"></a>
### sortPairs
**● sortPairs**: *`boolean`*
If set to `true`, the hashing pairs will be sorted.
## Test
```bash

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2020-06-06T17:56:54.181Z" agent="5.0 (X11)" etag="EqUJNqwkYz94jIl3aqxT" version="13.1.14" type="google"><diagram id="_fycCV10ZWOh--qWWdVK" name="Page-1">7Z1bc6M2FIB/TR7TQRcuftxcNp1p0+kkO233kcSKTccxXkI2zv76ykYCcyCLTDE6NjwZCRBwzocu54LP2OXz+iYJV/PbeCoWZ9SZrs/Y1RmlxPEm8mdT857V+NzPKmZJNFUHFRX30Q+hz1S1r9FUvJQOTON4kUarcuVjvFyKx7RUFyZJ/FY+7ClelK+6CmeiUnH/GC6qtX9H03Se1QauU9T/KqLZXF+ZOGrPc6gPVhUv83Aav+1UseszdpnEcZptPa8vxWIjPC2X7LzPH+zNbywRy9TkhLu724dvN1c/blcp/eOv89XNv7Pr84BmzXwPF6/qidXdpu9aBLIZKW1ZuHibR6m4X4WPmz1vUuGybp4+L2SJyM3wZZWp4ClaC3nVC9W2SFKx/vCuSS4LCZGIn0WavMtD1AkTJT2FD3NZVn4rlKEFPN/Rg64LlfpnecOFhOSGEtI+AmPIBUZcikxiHLnEKMEmMRe7xHxsb6WHXGKMYWPMxy4xdD1/gFxi3MUmsQlyibkEmcQ083gl5iPrxyYEucQ8ho0x7FN+QgFjekllTWLY5/yUY5MY9jk/8xxkEsM+5+cBNolhn/N7Dra3EvucnwRliVHXtsTQz/mpg0xi6Of8YJVEuWWJ5Rbjn4psOf20sWbLUrwSyy/zaFkWlVhH6T9y2/nFVaWvm5LavlrvFt51YSnvf+ekTfGrbm9TKE7blorzpp+jzUNuS9m9imnFjg40Ip8nfk0exU9EoV//NExmIm2cw1Z1vKNDt0aHui4RizCNvpdvuE6x6gp/xpF8lOKl42Ao9AAb2YOqswo8qg1BGwcBDWWCqDS05Sx/7P+DnsnqaRjosaNAj1QsFi3RI5Bh1jd6JsvQYaDHTdFjNtGjYODMO6990cvnwboh3jd6Juv5YaDnHgd6FRtaS/QoXPb33uuZGEYa0WuLUStkD4SeNkM2o+dbHXDBOMlbD7hgrsedvtEzsTANAz3juR4q9GjbAZc6DQ0dHD0TU90g0NOBCs3ocZvouayjuZ4b2J7rmdg8h4GefxToUWDR5H7bXg8EKXFowTs4eibG42GgF5ii56JCr/WAC9HrvdczscIPA73JUaAHTcqt0YMm5d7R0wbK0biiNdGMnmcTPR+sDipOCFP0fOhZg26Rg6M3ejNAVBxy9Dzo/4JTNGP04FIZThoPjh4dB9xy6FczeoFN9GBiUWV1YOzNgI603tHrxJtxEugZezOsose9juZ6MHy9/7neft6MZbwUZtg5rbAje2D3ImWT1rwT2/oD4Wm8FLE6KFNosmvr8WDQiNi3x4Ngj6mFgxC1HSFKCPagWhiGjEBkdRZmbyGve/EUb9+HQnbet9dY7zh/2Sa7f5IHEL5aFzvl1kz9blt50BW3ukbe6AM8StZll9PVmLQGY8d17PaO0niN0uB41qHS6myzubgNNbYVPlDadb2OOlPG5j7UVxLk6NtN1DXMHrGvnTrzZRfauTs+7VCYRWBdO7or7Vw7vx2hdmBAkX3t1BnButDO78enHUbRvTt1dqKBjjsM3ayA1plSutDOl+PTTiXJwb52qtaGu65lqBoih5GpC10t9mVat0QeaH/keuhGi7rV+EC141F07w72dFqY5E5sJ4cS2klI1Cl4aHOzUaM1OEuKtOaiBW+dx8otmBqDodmOg3YObQumnUREnQZ5ptF4gdWkH5jqyLW3ae/MC5gzGfTMHqszmAyUPVMfWGA1CJmAz1y1Zw9+KbF39saYKGi3bGbPbhQy64g92FD/7NGRPWCVbWbPru9/0hV7MLKvd/bGHG9oc25mz2q6I3M7Yg821D97nSR5nwJ7E9NFrorVscUejMOBnwVo+1UVEoCGDo5eJ0neJ4Ge6UpDxTxZG3KBf5+2DrcDy1zSd5Y36yTL+xTYyyMPm+Gjdsdc+PlAaC03hs+3Dd+Y5w1jONHD99GXGPfOeXRswzdmesMQiubFhtUcDJg64ZKWfg0XJnNAB8nB2RtTvWGASDN7E5vswfwfd9KWPQhx30413WGP7OWBNM3JPY7Vfg8m90DbiCl78G8tOOubvdGxAcOEmtn7IF6kp1ACyB5tyZ4PPwfp980eHfu98gfysRv4cta6tvDxzr4gKovFn4Fmhxd/qcqu/wM=</diagram></mxfile>

292
dist/index.d.ts vendored
View File

@ -1,9 +1,6 @@
/// <reference types="node" />
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;

364
dist/index.js vendored
View File

@ -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]]);
}

383
index.ts
View File

@ -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]])
}
}

View File

@ -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",

View File

@ -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])