Add duplicateOdd and sort option
This commit is contained in:
parent
c1fcf89f2c
commit
49ef70eccd
|
@ -103,7 +103,11 @@ tmp
|
|||
.netrwhist
|
||||
|
||||
# Debug files to ignore #
|
||||
#########################
|
||||
example.js
|
||||
|
||||
# Build directories #
|
||||
#####################
|
||||
build
|
||||
#dist
|
||||
docs
|
||||
|
|
343
README.md
343
README.md
|
@ -380,6 +380,349 @@ const verified = tree.verify(proof, leaves[2], root)
|
|||
|
||||
* * *
|
||||
|
||||
# Class: MerkleTree
|
||||
|
||||
Class reprensenting a Merkle Tree
|
||||
|
||||
*__namespace__*: MerkleTree
|
||||
|
||||
## Hierarchy
|
||||
|
||||
**MerkleTree**
|
||||
|
||||
## Index
|
||||
|
||||
### Constructors
|
||||
|
||||
* [constructor](_index_.merkletree.md#constructor)
|
||||
|
||||
### Properties
|
||||
|
||||
* [_sort](_index_.merkletree.md#_sort)
|
||||
* [duplicateOdd](_index_.merkletree.md#duplicateodd)
|
||||
* [hashAlgo](_index_.merkletree.md#hashalgo)
|
||||
* [hashLeaves](_index_.merkletree.md#hashleaves)
|
||||
* [isBitcoinTree](_index_.merkletree.md#isbitcointree)
|
||||
* [layers](_index_.merkletree.md#layers)
|
||||
* [leaves](_index_.merkletree.md#leaves)
|
||||
|
||||
### Methods
|
||||
|
||||
* [createHashes](_index_.merkletree.md#createhashes)
|
||||
* [getLayers](_index_.merkletree.md#getlayers)
|
||||
* [getLayersAsObject](_index_.merkletree.md#getlayersasobject)
|
||||
* [getLeaves](_index_.merkletree.md#getleaves)
|
||||
* [getProof](_index_.merkletree.md#getproof)
|
||||
* [getRoot](_index_.merkletree.md#getroot)
|
||||
* [print](_index_.merkletree.md#print)
|
||||
* [toString](_index_.merkletree.md#tostring)
|
||||
* [toTreeString](_index_.merkletree.md#totreestring)
|
||||
* [verify](_index_.merkletree.md#verify)
|
||||
* [bufferify](_index_.merkletree.md#bufferify)
|
||||
* [print](_index_.merkletree.md#print-1)
|
||||
|
||||
---
|
||||
|
||||
## Constructors
|
||||
|
||||
<a id="constructor"></a>
|
||||
|
||||
### constructor
|
||||
|
||||
⊕ **new MerkleTree**(leaves: *`any`*, hashAlgorithm: *`any`*, options?: *`any`*): [MerkleTree](_index_.merkletree.md)
|
||||
|
||||
*Defined in [index.ts:16](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L16)*
|
||||
|
||||
*__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__*: 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 => sha3(x))
|
||||
|
||||
const tree = new MerkleTree(leaves, sha256)
|
||||
|
||||
**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 | `any` | {} as any | Additional options |
|
||||
|
||||
**Returns:** [MerkleTree](_index_.merkletree.md)
|
||||
|
||||
___
|
||||
|
||||
## Properties
|
||||
|
||||
<a id="_sort"></a>
|
||||
|
||||
### _sort
|
||||
|
||||
**● _sort**: *`boolean`*
|
||||
|
||||
*Defined in [index.ts:15](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L15)*
|
||||
|
||||
___
|
||||
<a id="duplicateodd"></a>
|
||||
|
||||
### duplicateOdd
|
||||
|
||||
**● duplicateOdd**: *`boolean`*
|
||||
|
||||
*Defined in [index.ts:16](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L16)*
|
||||
|
||||
___
|
||||
<a id="hashalgo"></a>
|
||||
|
||||
### hashAlgo
|
||||
|
||||
**● hashAlgo**: *`any`*
|
||||
|
||||
*Defined in [index.ts:10](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L10)*
|
||||
|
||||
___
|
||||
<a id="hashleaves"></a>
|
||||
|
||||
### hashLeaves
|
||||
|
||||
**● hashLeaves**: *`boolean`*
|
||||
|
||||
*Defined in [index.ts:11](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L11)*
|
||||
|
||||
___
|
||||
<a id="isbitcointree"></a>
|
||||
|
||||
### isBitcoinTree
|
||||
|
||||
**● isBitcoinTree**: *`boolean`*
|
||||
|
||||
*Defined in [index.ts:14](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L14)*
|
||||
|
||||
___
|
||||
<a id="layers"></a>
|
||||
|
||||
### layers
|
||||
|
||||
**● layers**: *`any`*
|
||||
|
||||
*Defined in [index.ts:13](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L13)*
|
||||
|
||||
___
|
||||
<a id="leaves"></a>
|
||||
|
||||
### leaves
|
||||
|
||||
**● leaves**: *`any`*
|
||||
|
||||
*Defined in [index.ts:12](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L12)*
|
||||
|
||||
___
|
||||
|
||||
## Methods
|
||||
|
||||
<a id="createhashes"></a>
|
||||
|
||||
### createHashes
|
||||
|
||||
▸ **createHashes**(nodes: *`any`*): `void`
|
||||
|
||||
*Defined in [index.ts:60](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L60)*
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type |
|
||||
| ------ | ------ |
|
||||
| nodes | `any` |
|
||||
|
||||
**Returns:** `void`
|
||||
|
||||
___
|
||||
<a id="getlayers"></a>
|
||||
|
||||
### getLayers
|
||||
|
||||
▸ **getLayers**(): `any`
|
||||
|
||||
*Defined in [index.ts:145](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L145)*
|
||||
|
||||
getLayers
|
||||
|
||||
*__desc__*: Returns array of all layers of Merkle Tree, including leaves and root.
|
||||
|
||||
*__example__*: const layers = tree.getLayers()
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="getlayersasobject"></a>
|
||||
|
||||
### getLayersAsObject
|
||||
|
||||
▸ **getLayersAsObject**(): `any`
|
||||
|
||||
*Defined in [index.ts:295](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L295)*
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="getleaves"></a>
|
||||
|
||||
### getLeaves
|
||||
|
||||
▸ **getLeaves**(): `any`
|
||||
|
||||
*Defined in [index.ts:134](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L134)*
|
||||
|
||||
getLeaves
|
||||
|
||||
*__desc__*: Returns array of leaves of Merkle Tree.
|
||||
|
||||
*__example__*: const leaves = tree.getLeaves()
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="getproof"></a>
|
||||
|
||||
### getProof
|
||||
|
||||
▸ **getProof**(leaf: *`any`*, index?: *`any`*): `any`[]
|
||||
|
||||
*Defined in [index.ts:176](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L176)*
|
||||
|
||||
getProof
|
||||
|
||||
*__desc__*: Returns the proof for a target leaf.
|
||||
|
||||
*__example__*: const proof = tree.getProof(leaves\[2\])
|
||||
|
||||
*__example__*: const leaves = \['a', 'b', 'a'\].map(x => sha3(x)) const tree = new MerkleTree(leaves, sha3) const proof = tree.getProof(leaves\[2\], 2)
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------ | ------ |
|
||||
| leaf | `any` | Target leaf |
|
||||
| `Optional` index | `any` |
|
||||
|
||||
**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="getroot"></a>
|
||||
|
||||
### getRoot
|
||||
|
||||
▸ **getRoot**(): `any`
|
||||
|
||||
*Defined in [index.ts:156](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L156)*
|
||||
|
||||
getRoot
|
||||
|
||||
*__desc__*: Returns the Merkle root hash as a Buffer.
|
||||
|
||||
*__example__*: const root = tree.getRoot()
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="print"></a>
|
||||
|
||||
### print
|
||||
|
||||
▸ **print**(): `void`
|
||||
|
||||
*Defined in [index.ts:324](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L324)*
|
||||
|
||||
**Returns:** `void`
|
||||
|
||||
___
|
||||
<a id="tostring"></a>
|
||||
|
||||
### toString
|
||||
|
||||
▸ **toString**(): `any`
|
||||
|
||||
*Defined in [index.ts:335](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L335)*
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="totreestring"></a>
|
||||
|
||||
### toTreeString
|
||||
|
||||
▸ **toTreeString**(): `any`
|
||||
|
||||
*Defined in [index.ts:329](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L329)*
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="verify"></a>
|
||||
|
||||
### verify
|
||||
|
||||
▸ **verify**(proof: *`any`*, targetNode: *`any`*, root: *`any`*): `boolean`
|
||||
|
||||
*Defined in [index.ts:258](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L258)*
|
||||
|
||||
verify
|
||||
|
||||
*__desc__*: Returns true if the proof path (array of hashes) can connect the target node to the Merkle root.
|
||||
|
||||
*__example__*: const root = tree.getRoot() const proof = tree.getProof(leaves\[2\]) const verified = tree.verify(proof, leaves\[2\], root)
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------ | ------ |
|
||||
| proof | `any` | Array of proof objects that should connect target node to Merkle root. |
|
||||
| targetNode | `any` | Target node Buffer |
|
||||
| root | `any` | Merkle root Buffer |
|
||||
|
||||
**Returns:** `boolean`
|
||||
|
||||
___
|
||||
<a id="bufferify"></a>
|
||||
|
||||
### `<Static>` bufferify
|
||||
|
||||
▸ **bufferify**(x: *`any`*): `any`
|
||||
|
||||
*Defined in [index.ts:340](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L340)*
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type |
|
||||
| ------ | ------ |
|
||||
| x | `any` |
|
||||
|
||||
**Returns:** `any`
|
||||
|
||||
___
|
||||
<a id="print-1"></a>
|
||||
|
||||
### `<Static>` print
|
||||
|
||||
▸ **print**(tree: *`any`*): `void`
|
||||
|
||||
*Defined in [index.ts:345](https://github.com/miguelmota/merkletreejs/blob/c1fcf89/index.ts#L345)*
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type |
|
||||
| ------ | ------ |
|
||||
| tree | `any` |
|
||||
|
||||
**Returns:** `void`
|
||||
|
||||
___
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
*/
|
||||
export declare class MerkleTree {
|
||||
hashAlgo: any;
|
||||
hashLeaves: boolean;
|
||||
leaves: any;
|
||||
layers: any;
|
||||
isBitcoinTree: boolean;
|
||||
_sort: boolean;
|
||||
duplicateOdd: boolean;
|
||||
/**
|
||||
* @desc Constructs a Merkle Tree.
|
||||
* All nodes and leaves are stored as Buffers.
|
||||
|
@ -14,9 +17,12 @@ export declare class MerkleTree {
|
|||
* @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer.
|
||||
* @param {Function} hashAlgorithm - Algorithm used for hashing leaves and nodes
|
||||
* @param {Object} options - Additional options
|
||||
* @param {Boolean} options.sort - If set to `true`, the leaves and hashing pairs will be sorted.
|
||||
* @param {Boolean} options.hashLeaves - If set to `true`, the leaves will hashed using the set hashing algorithms.
|
||||
* @param {Boolean} options.isBitcoinTree - 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.
|
||||
* @param {Boolean} options.duplicateOdd - If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash.
|
||||
* @example
|
||||
* const MerkleTree = require('merkletreejs')
|
||||
* const crypto = require('crypto')
|
||||
|
|
|
@ -15,9 +15,12 @@ var MerkleTree = /** @class */ (function () {
|
|||
* @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer.
|
||||
* @param {Function} hashAlgorithm - Algorithm used for hashing leaves and nodes
|
||||
* @param {Object} options - Additional options
|
||||
* @param {Boolean} options.sort - If set to `true`, the leaves and hashing pairs will be sorted.
|
||||
* @param {Boolean} options.hashLeaves - If set to `true`, the leaves will hashed using the set hashing algorithms.
|
||||
* @param {Boolean} options.isBitcoinTree - 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.
|
||||
* @param {Boolean} options.duplicateOdd - If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash.
|
||||
* @example
|
||||
* const MerkleTree = require('merkletreejs')
|
||||
* const crypto = require('crypto')
|
||||
|
@ -33,10 +36,16 @@ var MerkleTree = /** @class */ (function () {
|
|||
*/
|
||||
function MerkleTree(leaves, hashAlgorithm, options) {
|
||||
if (options === void 0) { options = {}; }
|
||||
this.isBitcoinTree = !!options.isBitcoinTree;
|
||||
this.hashLeaves = !!options.hashLeaves;
|
||||
this._sort = !!options.sort;
|
||||
this.duplicateOdd = !!options.duplicateOdd;
|
||||
this.hashAlgo = bufferifyFn(hashAlgorithm);
|
||||
if (this.hashLeaves) {
|
||||
leaves = leaves.map(this.hashAlgo);
|
||||
}
|
||||
this.leaves = leaves.map(bufferify);
|
||||
this.layers = [this.leaves];
|
||||
this.isBitcoinTree = !!options.isBitcoinTree;
|
||||
this.createHashes(this.leaves);
|
||||
}
|
||||
// TODO: documentation
|
||||
|
@ -44,15 +53,44 @@ var MerkleTree = /** @class */ (function () {
|
|||
while (nodes.length > 1) {
|
||||
var layerIndex = this.layers.length;
|
||||
this.layers.push([]);
|
||||
for (var i = 0; i < nodes.length - 1; i += 2) {
|
||||
for (var i = 0; i < nodes.length; i += 2) {
|
||||
if (i + 1 === nodes.length) {
|
||||
if (nodes.length % 2 === 1) {
|
||||
var data_1 = nodes[nodes.length - 1];
|
||||
var hash_1 = data_1;
|
||||
// is bitcoin tree
|
||||
if (this.isBitcoinTree) {
|
||||
// Bitcoin method of duplicating the odd ending nodes
|
||||
data_1 = Buffer.concat([reverse(data_1), reverse(data_1)]);
|
||||
hash_1 = this.hashAlgo(data_1);
|
||||
hash_1 = reverse(this.hashAlgo(hash_1));
|
||||
this.layers[layerIndex].push(hash_1);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
if (!this.duplicateOdd) {
|
||||
this.layers[layerIndex].push(nodes[i]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var left = nodes[i];
|
||||
var right = nodes[i + 1];
|
||||
var right = i + 1 == nodes.length ? left : nodes[i + 1];
|
||||
var data = null;
|
||||
if (this.isBitcoinTree) {
|
||||
data = Buffer.concat([reverse(left), reverse(right)]);
|
||||
var combined = [reverse(left), reverse(right)];
|
||||
if (this._sort) {
|
||||
combined.sort(Buffer.compare);
|
||||
}
|
||||
data = Buffer.concat(combined);
|
||||
}
|
||||
else {
|
||||
data = Buffer.concat([left, right]);
|
||||
var combined = [left, right];
|
||||
if (this._sort) {
|
||||
combined.sort(Buffer.compare);
|
||||
}
|
||||
data = Buffer.concat(combined);
|
||||
}
|
||||
var hash = this.hashAlgo(data);
|
||||
// double hash if bitcoin tree
|
||||
|
@ -61,19 +99,6 @@ var MerkleTree = /** @class */ (function () {
|
|||
}
|
||||
this.layers[layerIndex].push(hash);
|
||||
}
|
||||
// is odd number of nodes
|
||||
if (nodes.length % 2 === 1) {
|
||||
var data = nodes[nodes.length - 1];
|
||||
var hash = data;
|
||||
// is bitcoin tree
|
||||
if (this.isBitcoinTree) {
|
||||
// Bitcoin method of duplicating the odd ending nodes
|
||||
data = Buffer.concat([reverse(data), reverse(data)]);
|
||||
hash = this.hashAlgo(data);
|
||||
hash = reverse(this.hashAlgo(hash));
|
||||
}
|
||||
this.layers[layerIndex].push(hash);
|
||||
}
|
||||
nodes = this.layers[layerIndex];
|
||||
}
|
||||
};
|
||||
|
|
138
index.ts
138
index.ts
|
@ -8,9 +8,12 @@ import * as treeify from 'treeify'
|
|||
*/
|
||||
export class MerkleTree {
|
||||
hashAlgo: any
|
||||
hashLeaves: boolean
|
||||
leaves: any
|
||||
layers: any
|
||||
isBitcoinTree: boolean
|
||||
_sort: boolean
|
||||
duplicateOdd: boolean
|
||||
|
||||
/**
|
||||
* @desc Constructs a Merkle Tree.
|
||||
|
@ -19,9 +22,12 @@ export class MerkleTree {
|
|||
* @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer.
|
||||
* @param {Function} hashAlgorithm - Algorithm used for hashing leaves and nodes
|
||||
* @param {Object} options - Additional options
|
||||
* @param {Boolean} options.sort - If set to `true`, the leaves and hashing pairs will be sorted.
|
||||
* @param {Boolean} options.hashLeaves - If set to `true`, the leaves will hashed using the set hashing algorithms.
|
||||
* @param {Boolean} options.isBitcoinTree - 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.
|
||||
* @param {Boolean} options.duplicateOdd - If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash.
|
||||
* @example
|
||||
* const MerkleTree = require('merkletreejs')
|
||||
* const crypto = require('crypto')
|
||||
|
@ -36,11 +42,17 @@ export class MerkleTree {
|
|||
* const tree = new MerkleTree(leaves, sha256)
|
||||
*/
|
||||
constructor(leaves, hashAlgorithm, options={} as any) {
|
||||
this.isBitcoinTree = !!options.isBitcoinTree
|
||||
this.hashLeaves = !!options.hashLeaves
|
||||
this._sort = !!options.sort
|
||||
this.duplicateOdd = !!options.duplicateOdd
|
||||
this.hashAlgo = bufferifyFn(hashAlgorithm)
|
||||
if (this.hashLeaves) {
|
||||
leaves = leaves.map(this.hashAlgo)
|
||||
}
|
||||
|
||||
this.leaves = leaves.map(bufferify)
|
||||
this.layers = [this.leaves]
|
||||
this.isBitcoinTree = !!options.isBitcoinTree
|
||||
|
||||
this.createHashes(this.leaves)
|
||||
}
|
||||
|
||||
|
@ -52,15 +64,50 @@ export class MerkleTree {
|
|||
|
||||
this.layers.push([])
|
||||
|
||||
for (let i = 0; i < nodes.length - 1; i += 2) {
|
||||
for (let i = 0; i < nodes.length; i += 2) {
|
||||
|
||||
if (i+1 === nodes.length) {
|
||||
if (nodes.length % 2 === 1) {
|
||||
let data = nodes[nodes.length-1]
|
||||
let hash = data
|
||||
|
||||
// is bitcoin tree
|
||||
if (this.isBitcoinTree) {
|
||||
// Bitcoin method of duplicating the odd ending nodes
|
||||
data = Buffer.concat([reverse(data), reverse(data)])
|
||||
hash = this.hashAlgo(data)
|
||||
hash = reverse(this.hashAlgo(hash))
|
||||
|
||||
this.layers[layerIndex].push(hash)
|
||||
continue
|
||||
} else {
|
||||
if (!this.duplicateOdd) {
|
||||
this.layers[layerIndex].push(nodes[i])
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const left = nodes[i]
|
||||
const right = nodes[i+1]
|
||||
const right = i + 1 == nodes.length ? left : nodes[i + 1];
|
||||
let data = null
|
||||
|
||||
if (this.isBitcoinTree) {
|
||||
data = Buffer.concat([reverse(left), reverse(right)])
|
||||
let combined = [reverse(left), reverse(right)]
|
||||
if (this._sort) {
|
||||
combined.sort(Buffer.compare)
|
||||
}
|
||||
|
||||
data = Buffer.concat(combined)
|
||||
} else {
|
||||
data = Buffer.concat([left, right])
|
||||
let combined = [left, right]
|
||||
if (this._sort) {
|
||||
combined.sort(Buffer.compare)
|
||||
}
|
||||
|
||||
data = Buffer.concat(combined)
|
||||
}
|
||||
|
||||
let hash = this.hashAlgo(data)
|
||||
|
@ -73,25 +120,8 @@ export class MerkleTree {
|
|||
this.layers[layerIndex].push(hash)
|
||||
}
|
||||
|
||||
// is odd number of nodes
|
||||
if (nodes.length % 2 === 1) {
|
||||
let data = nodes[nodes.length-1]
|
||||
let hash = data
|
||||
|
||||
// is bitcoin tree
|
||||
if (this.isBitcoinTree) {
|
||||
// Bitcoin method of duplicating the odd ending nodes
|
||||
data = Buffer.concat([reverse(data), reverse(data)])
|
||||
hash = this.hashAlgo(data)
|
||||
hash = reverse(this.hashAlgo(hash))
|
||||
}
|
||||
|
||||
this.layers[layerIndex].push(hash)
|
||||
}
|
||||
|
||||
nodes = this.layers[layerIndex]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,36 +210,36 @@ export class MerkleTree {
|
|||
// set index to parent index
|
||||
index = (index / 2)|0
|
||||
|
||||
}
|
||||
|
||||
return proof
|
||||
|
||||
} else {
|
||||
|
||||
// Proof Generation for Non-Bitcoin Trees
|
||||
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layer = this.layers[i]
|
||||
const isRightNode = index % 2
|
||||
const pairIndex = (isRightNode ? index - 1 : index + 1)
|
||||
|
||||
if (pairIndex < layer.length) {
|
||||
proof.push({
|
||||
position: isRightNode ? 'left': 'right',
|
||||
data: layer[pairIndex]
|
||||
})
|
||||
}
|
||||
|
||||
// set index to parent index
|
||||
index = (index / 2)|0
|
||||
|
||||
}
|
||||
|
||||
return proof
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return proof
|
||||
|
||||
} else {
|
||||
|
||||
// Proof Generation for Non-Bitcoin Trees
|
||||
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layer = this.layers[i]
|
||||
const isRightNode = index % 2
|
||||
const pairIndex = (isRightNode ? index - 1 : index + 1)
|
||||
|
||||
if (pairIndex < layer.length) {
|
||||
proof.push({
|
||||
position: isRightNode ? 'left': 'right',
|
||||
data: layer[pairIndex]
|
||||
})
|
||||
}
|
||||
|
||||
// set index to parent index
|
||||
index = (index / 2)|0
|
||||
|
||||
}
|
||||
|
||||
return proof
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* verify
|
||||
* @desc Returns true if the proof path (array of hashes) can connect the target node
|
||||
|
@ -230,9 +260,9 @@ export class MerkleTree {
|
|||
root = bufferify(root)
|
||||
|
||||
if (!Array.isArray(proof) ||
|
||||
!proof.length ||
|
||||
!targetNode ||
|
||||
!root) {
|
||||
!proof.length ||
|
||||
!targetNode ||
|
||||
!root) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
9
jsdoc.js
9
jsdoc.js
|
@ -1,9 +0,0 @@
|
|||
const jsdoc2md = require('jsdoc-to-markdown')
|
||||
|
||||
jsdoc2md.render({
|
||||
files: 'index.js',
|
||||
separators: true
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "merkletreejs",
|
||||
"version": "0.0.25",
|
||||
"version": "0.1.0",
|
||||
"description": "Construct Merkle Trees and verify proofs",
|
||||
"main": "dist/index.js",
|
||||
"types": "typings/merkletreejs/*",
|
||||
"scripts": {
|
||||
"test": "tape test/*.js",
|
||||
"build": "rm -rf dist/ && tsc",
|
||||
"docs:md": "node jsdoc.js"
|
||||
"docs:md": "typedoc --theme markdown index.ts --out docs"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -40,8 +40,9 @@
|
|||
"@types/node": "^11.12.1",
|
||||
"crypto": "0.0.3",
|
||||
"ethereumjs-util": "^5.1.2",
|
||||
"jsdoc-to-markdown": "^3.0.0",
|
||||
"tape": "^4.9.2",
|
||||
"typedoc": "^0.14.2",
|
||||
"typedoc-plugin-markdown": "^1.2.1",
|
||||
"typescript": "^3.4.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -51,6 +52,7 @@
|
|||
"buffer-reverse": "^1.0.1",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"is-buffer": "^2.0.3",
|
||||
"merkle-lib": "^2.0.10",
|
||||
"treeify": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,25 @@ const SHA3 = require('crypto-js/sha3')
|
|||
|
||||
const { MerkleTree } = require('../')
|
||||
|
||||
function sha256(data) {
|
||||
return crypto.createHash('sha256').update(data).digest()
|
||||
}
|
||||
const sha256 = (data) => crypto.createHash('sha256').update(data).digest()
|
||||
|
||||
test('sha256', t => {
|
||||
test('sha256 with sha3 leaves', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
|
||||
const tree = new MerkleTree(leaves, sha256)
|
||||
|
||||
const root = '311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('sha256 with sha3 leaves with duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
const tree = new MerkleTree(leaves, sha256, {duplicateOdd: true})
|
||||
const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
|
@ -26,32 +33,100 @@ test('crypto-js - sha256', t => {
|
|||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
|
||||
const root = '311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('crypto-js - SHA256 leaves', t => {
|
||||
test('sha256 with sort option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(x => sha256(x))
|
||||
const tree = new MerkleTree(leaves, sha256, {sort: true})
|
||||
const root = 'a30ba95a1a5dc397fe45ea20105363b08d682b864a28f4940419a29349a28325'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('sha256 with sha256 leaves and sort option and duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(x => sha256(x))
|
||||
const tree = new MerkleTree(leaves, sha256, {sort: true, duplicateOdd: true})
|
||||
const root = 'a5260b2a7ec31584e5d5689a5628c2b3d949e2397334fd71c107478e5f887eaf'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('sha256 with hash leaves option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
const tree = new MerkleTree(leaves, sha256, {hashLeaves: true})
|
||||
const root = '1f7379539707bcaea00564168d1d4d626b09b73f8a2a365234c62d763f854da2'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('sha256 with hash leaves option and duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
const tree = new MerkleTree(leaves, sha256, {hashLeaves: true, duplicateOdd: true})
|
||||
const root = '44205acec5156114821f1f71d87c72e0de395633cd1589def6d4444cc79f8103'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('crypto-js - sha256 with sha3 leaves', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(SHA256)
|
||||
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
|
||||
const root = '7075152d03a5cd92104887b476862778ec0c87be5c2fa1c0a90f87c49fad6eff'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('crypto-js - SHA3 leaves', t => {
|
||||
test('crypto-js - sha256 with sha3 leaves and duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(SHA3)
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
const tree = new MerkleTree(leaves, SHA256, {duplicateOdd: true})
|
||||
const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('crypto-js - SHA256 with SHA256 leaves and duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(SHA256)
|
||||
const tree = new MerkleTree(leaves, SHA256, {duplicateOdd: true})
|
||||
const root = 'd31a37ef6ac14a2db1470c4316beb5592e6afd4465022339adafda76a18ffabe'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('crypto-js - SHA256 with SHA3 leaves', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => SHA3(x))
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
|
||||
const root = '57e9ee696a291f8a51d224a6d64ba4a0693920a63f1e0329efe96c02a5f28849'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test('crypto-js - SHA256 with sha3 leaves and duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
const tree = new MerkleTree(leaves, SHA256, {duplicateOdd: true})
|
||||
const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
|
@ -71,6 +146,7 @@ test('solidity sha3 [keccak-256]', t => {
|
|||
const layers = tree.getLayers().slice(1) // no leaves
|
||||
|
||||
const layer_1 = sha3(Buffer.concat([leaves[0], leaves[1]])).toString('hex')
|
||||
|
||||
t.equal(layers[0][0].toString('hex'), layer_1)
|
||||
t.equal(layers[0][1].toString('hex'), c_hash)
|
||||
|
||||
|
@ -103,6 +179,53 @@ test('solidity sha3 [keccak-256]', t => {
|
|||
t.equal(tree.verify(proof_2, leaves[2], root), true)
|
||||
})
|
||||
|
||||
test('solidity sha3 [keccak-256] with duplicate odd option', t => {
|
||||
t.plan(20)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
|
||||
const a_hash = '3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb'
|
||||
const b_hash = 'b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510'
|
||||
const c_hash = '0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2'
|
||||
|
||||
t.deepEqual(leaves.map(x => x.toString('hex')), [a_hash, b_hash, c_hash])
|
||||
|
||||
const tree = new MerkleTree(leaves, sha3, {duplicateOdd: true})
|
||||
const layers = tree.getLayers().slice(1) // no leaves
|
||||
const layer_1 = sha3(Buffer.concat([leaves[0], leaves[1]])).toString('hex')
|
||||
const layer_2 = sha3(Buffer.concat([leaves[2], leaves[2]])).toString('hex')
|
||||
t.equal(layers[0][0].toString('hex'), layer_1)
|
||||
t.equal(layers[0][1].toString('hex'), layer_2)
|
||||
|
||||
const root = Buffer.from('905b17edcf8b6fb1415b32cdbab3e02c2c93f80a345de80ea2bbf9feba9f5a55', 'hex')
|
||||
t.equal(tree.getRoot().toString('hex'), root.toString('hex'))
|
||||
|
||||
const proof_0 = tree.getProof(leaves[0])
|
||||
t.equal(proof_0.length, 2)
|
||||
t.equal(proof_0[0].position, 'right')
|
||||
t.equal(proof_0[0].data.toString('hex'), b_hash)
|
||||
t.equal(proof_0[1].position, 'right')
|
||||
t.equal(proof_0[1].data.toString('hex'), layer_2)
|
||||
|
||||
t.equal(tree.verify(proof_0, leaves[0], root), true)
|
||||
|
||||
const proof_1 = tree.getProof(leaves[1])
|
||||
t.equal(proof_1.length, 2)
|
||||
t.equal(proof_1[0].position, 'left')
|
||||
t.equal(proof_1[0].data.toString('hex'), a_hash)
|
||||
t.equal(proof_1[1].position, 'right')
|
||||
t.equal(proof_1[1].data.toString('hex'), layer_2)
|
||||
|
||||
t.equal(tree.verify(proof_1, leaves[1], root), true)
|
||||
|
||||
const proof_2 = tree.getProof(leaves[2])
|
||||
t.equal(proof_2.length, 1)
|
||||
t.equal(proof_2[0].position, 'left')
|
||||
t.equal(proof_2[0].data.toString('hex'), layer_1)
|
||||
|
||||
t.equal(tree.verify(proof_2, layer_2, root), true)
|
||||
})
|
||||
|
||||
test('solidity sha3 [keccak-256] with duplicate leaves', t => {
|
||||
t.plan(5)
|
||||
|
||||
|
@ -126,7 +249,6 @@ test('solidity sha3 [keccak-256] with duplicate leaves', t => {
|
|||
t.equal(proof_0[0].data.toString('hex'), layer_1)
|
||||
})
|
||||
|
||||
|
||||
test('sha-256 with option.isBitcoinTree', t => {
|
||||
t.plan(2)
|
||||
|
||||
|
@ -235,10 +357,9 @@ test('sha-256 with option.isBitcoinTree', t => {
|
|||
"27a0797cc5b042ba4c11e72a9555d13a67f00161550b32ede0511718b22dbc2c",
|
||||
]
|
||||
|
||||
var leaves = txHashes.map(x => Buffer.from(x, 'hex'))
|
||||
const leaves = txHashes.map(x => Buffer.from(x, 'hex'))
|
||||
|
||||
const tree = new MerkleTree(leaves, sha256, {isBitcoinTree: true})
|
||||
|
||||
const root = Buffer.from('871714dcbae6c8193a2bb9b2a69fe1c0440399f38d94b3a0f1b447275a29978a', 'hex')
|
||||
t.equal(tree.getRoot().toString('hex'), root.toString('hex'))
|
||||
|
||||
|
@ -249,7 +370,7 @@ test('sha-256 with option.isBitcoinTree', t => {
|
|||
|
||||
test('sha3 - hex strings', t => {
|
||||
t.plan(1)
|
||||
let leaves = ['a', 'b', 'c'].map(x => sha3(x).toString('hex'))
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x).toString('hex'))
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
const root = '311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae'
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
|
@ -265,19 +386,18 @@ test('sha256 - no leaves', t => {
|
|||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
test.skip('sha256 - 1,000,000 leaves', t => {
|
||||
test('sha256 - 1,000,000 leaves', t => {
|
||||
t.plan(1)
|
||||
|
||||
let values = []
|
||||
const values = []
|
||||
for (let i = 0; i < 1e6; i++) {
|
||||
values.push(`${i}`)
|
||||
}
|
||||
|
||||
const leaves = values.map(x => sha256(x))
|
||||
|
||||
const tree = new MerkleTree(leaves, sha256)
|
||||
|
||||
const root = '101dd357df60384d254330fe118e3046871767c2748ebd62ce031c117df483da'
|
||||
|
||||
t.equal(tree.getRoot().toString('hex'), root)
|
||||
})
|
||||
|
||||
|
@ -285,11 +405,8 @@ test('crypto-js SHA3 leaves SHA256 hash algo', t => {
|
|||
t.plan(2)
|
||||
|
||||
const leaves = ['a', 'b', 'c', 'd'].map(SHA3)
|
||||
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
|
||||
t.deepEqual(tree.getLeaves(), leaves.map(MerkleTree.bufferify))
|
||||
|
||||
const root = tree.getRoot()
|
||||
|
||||
const verifications = leaves.map(leaf => {
|
||||
|
@ -329,6 +446,26 @@ test('getLayersAsObject', t => {
|
|||
})
|
||||
})
|
||||
|
||||
test('getLayersAsObject with duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
const tree = new MerkleTree(leaves, sha256, {duplicateOdd: true})
|
||||
const obj = tree.getLayersAsObject()
|
||||
|
||||
t.deepEqual(obj, {
|
||||
"bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1": {
|
||||
"176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1": {
|
||||
"3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb": null,
|
||||
"b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510": null
|
||||
},
|
||||
"43e061172b1177f25d0f156b2d2ed11728006fade8e167ff3d1b9dbc979a3358": {
|
||||
"0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2": null
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('print', t => {
|
||||
t.plan(1)
|
||||
|
||||
|
@ -344,3 +481,19 @@ test('print', t => {
|
|||
└─ 0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2
|
||||
`)
|
||||
})
|
||||
|
||||
test('print with duplicate odd option', t => {
|
||||
t.plan(1)
|
||||
|
||||
const leaves = ['a', 'b', 'c'].map(x => sha3(x))
|
||||
const tree = new MerkleTree(leaves, sha256, {duplicateOdd: true})
|
||||
|
||||
t.equal(tree.toString(),
|
||||
`└─ bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1
|
||||
├─ 176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1
|
||||
│ ├─ 3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb
|
||||
│ └─ b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510
|
||||
└─ 43e061172b1177f25d0f156b2d2ed11728006fade8e167ff3d1b9dbc979a3358
|
||||
└─ 0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2
|
||||
`)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue