mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Compare commits
11 Commits
789a14a361
...
5a25b83195
Author | SHA1 | Date | |
---|---|---|---|
|
5a25b83195 | ||
|
a0600f308e | ||
|
9917dcfa3c | ||
|
8df7780bde | ||
|
5958bcfa1a | ||
|
dbadfe7241 | ||
|
64ac0fd259 | ||
|
8a88cbc455 | ||
|
1bb83c6866 | ||
|
2992aafcc8 | ||
|
f6f94f60cf |
203
src/data-structures/tree/merkle-tree/MerkleTree.js
Normal file
203
src/data-structures/tree/merkle-tree/MerkleTree.js
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
|
class MerkleTree {
|
||||||
|
/**
|
||||||
|
* @desc Constructs a Merkle Tree.
|
||||||
|
* If the number of nodes is odd, last node is duplicated while calculating next level up:
|
||||||
|
* Hash(LN) = Hash(LN+LN)
|
||||||
|
* @param {[]} array - Array of inputs. Each value must be a string/number/object.
|
||||||
|
* @param {Object} options - Additional options: {inputHash, hash}
|
||||||
|
* inputHash: - string
|
||||||
|
* ex: 'sha256' | 'md5'
|
||||||
|
* (*listOfSupportedHashes = crypto.getHashes())
|
||||||
|
*
|
||||||
|
* - function
|
||||||
|
* ex:
|
||||||
|
* function sha256(data) {
|
||||||
|
* return crypto.createHash('sha256').update(data).digest('hex')
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* const tree = new MerkleTree([1,2,3,4,5,6], {hash: 'md5'})
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor (array, option = {}) {
|
||||||
|
if (!array) {
|
||||||
|
array = []
|
||||||
|
} else {
|
||||||
|
if (!Array.isArray(array)) {
|
||||||
|
throw new Error('Input has to be an array')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.constructor !== Object) {
|
||||||
|
throw new Error('Invalid option object: has to be {hash, inputHash}')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.option = {
|
||||||
|
hash: option.hash || 'sha256',
|
||||||
|
inputHash: option.inputHash || 'sha256'
|
||||||
|
}
|
||||||
|
this.originalArray = array
|
||||||
|
this.buildTree()
|
||||||
|
}
|
||||||
|
buildTree() {
|
||||||
|
// Hash all the inputs with inputHash()
|
||||||
|
this.hashedArray = this.originalArray.map(item => {
|
||||||
|
return this.inputHash(item)
|
||||||
|
})
|
||||||
|
// Initiate a 2D map to store all the hash values
|
||||||
|
this.fullPath = []
|
||||||
|
// Hash first row
|
||||||
|
let currentRow = this.hashedArray.map(item => {
|
||||||
|
return this.hash(item)
|
||||||
|
})
|
||||||
|
while (currentRow.length > 1) {
|
||||||
|
let nextRow = []
|
||||||
|
if ((currentRow.length % 2) !== 0) {
|
||||||
|
// Duplicate last node in the row if the node is alone
|
||||||
|
currentRow.push(currentRow[currentRow.length-1])
|
||||||
|
}
|
||||||
|
for (let i = 0; i < currentRow.length; i += 2) {
|
||||||
|
nextRow.push(this.hash(currentRow[i]+currentRow[i+1]))
|
||||||
|
}
|
||||||
|
this.fullPath.unshift(currentRow)
|
||||||
|
currentRow = nextRow
|
||||||
|
}
|
||||||
|
this.fullPath.unshift(currentRow)
|
||||||
|
// Set the final hash as root
|
||||||
|
this.root = currentRow[0]
|
||||||
|
}
|
||||||
|
inputHash(node) {
|
||||||
|
return this.hashBase(node, this.option.inputHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash(node) {
|
||||||
|
return this.hashBase(node, this.option.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashBase(node, method = this.option.hash) {
|
||||||
|
node = this._toString(node)
|
||||||
|
if (method && (typeof method === 'function')) {
|
||||||
|
return method(node)
|
||||||
|
}
|
||||||
|
const hash = crypto.createHash(method)
|
||||||
|
return hash.update(node, 'utf8').digest('hex')
|
||||||
|
}
|
||||||
|
_toString(node) {
|
||||||
|
if (node.constructor === String) return node
|
||||||
|
if (node.constructor === Number) return node.toString()
|
||||||
|
if (node.constructor === Object) return JSON.stringify(node)
|
||||||
|
throw new Error('Input object only takes string, number, object')
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(node) {
|
||||||
|
this.originalArray.push(node)
|
||||||
|
// Rebuild tree
|
||||||
|
this.buildTree()
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoot() {
|
||||||
|
return this.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getProof
|
||||||
|
* @desc Returns the proof for a node.
|
||||||
|
* @param {} node
|
||||||
|
* @param {Number} [index] - claimed node index of the input array.
|
||||||
|
* If not provided, getProof will set it default to first found index in the input array
|
||||||
|
* @return {Object} - An object provides the claim of the proof including:
|
||||||
|
* {
|
||||||
|
* node - the original input node,
|
||||||
|
* index - the original input index(if valid/applicable for the current tree), -1 otherwise,
|
||||||
|
* path - an array contains the coordinates of a bottom-up merkle path
|
||||||
|
* }
|
||||||
|
*@example
|
||||||
|
* ```js
|
||||||
|
*const tree = new MerkleTree(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
*console.log(tree.getProof('b'))
|
||||||
|
* // {node: 'b', index: 1, path: [[3,0],[2,1],[1,1]]}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
|
|
||||||
|
getProof(node, index) {
|
||||||
|
const result = {node, index: -1, path: []}
|
||||||
|
// Filter out invalid inputs
|
||||||
|
if (node === undefined || node === null) return result
|
||||||
|
if (index && (!Number.isInteger(index) || index < 0) || index >= this.originalArray.length) return result
|
||||||
|
// Set the index to real index if not provided
|
||||||
|
if (index === undefined || index === null) index = this.hashedArray.indexOf(this.inputHash(node))
|
||||||
|
if (index === -1) return result
|
||||||
|
result.index = index
|
||||||
|
|
||||||
|
// Build a path array bottom up based on the current full hash map by calculating indexes
|
||||||
|
for (let j=this.fullPath.length - 1; j>0;j--) {
|
||||||
|
// If the index indicates it is a left node, push right sibling to the path array, left otherwise.
|
||||||
|
if ((index % 2) == 0) {
|
||||||
|
result.path.push([j, index + 1])
|
||||||
|
} else {
|
||||||
|
result.path.push([j, index - 1])
|
||||||
|
}
|
||||||
|
index = Math.floor(index/2)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify
|
||||||
|
* @desc verify a proof generated by getProof()
|
||||||
|
* @param {Proof} proof - proof object generated by getProof()
|
||||||
|
* {
|
||||||
|
* node, index, path
|
||||||
|
* }
|
||||||
|
* @return {Boolean} - If true, the correct merkle root can be calculated through the path provided by input proof, false other wise.
|
||||||
|
*@example
|
||||||
|
* ```js
|
||||||
|
*const tree = new MerkleTree(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
*const proof = tree.getProof('b')) // {node: 'b', index: 1, path: [[3,0],[2,1],[1,1]]}
|
||||||
|
*console.log(tree.verify(proof)) //true
|
||||||
|
*```
|
||||||
|
*/
|
||||||
|
verify(proof) {
|
||||||
|
const node = proof.node
|
||||||
|
const index = proof.index
|
||||||
|
const path = proof.path
|
||||||
|
|
||||||
|
if (node === undefined || node === null) return false
|
||||||
|
if (index >= this.originalArray.length ) return false
|
||||||
|
let hashed = this.hash(this.inputHash(node))
|
||||||
|
// If path is [] and index == 0 means the input array contains only one node. Just compare the hash of itself with root
|
||||||
|
if (path === []) {
|
||||||
|
if(index === 0) return hashed === this.root
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild hashes bottem up by only calculating the necessary nodes, compare the final hash with root
|
||||||
|
while(path.length > 0) {
|
||||||
|
const current = path.shift()
|
||||||
|
const row = current[0]
|
||||||
|
const index = current[1]
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sibling = this.fullPath[row][index]
|
||||||
|
// If the coordinate indicates the sibling is a left node, do H(sibling + current)
|
||||||
|
// H(current + sibling) otherwise*
|
||||||
|
if ((index%2) == 0) {
|
||||||
|
hashed = this.hash(sibling + hashed)
|
||||||
|
} else {
|
||||||
|
hashed = this.hash(hashed + sibling)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashed === this.root
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MerkleTree
|
48
src/data-structures/tree/merkle-tree/README.md
Normal file
48
src/data-structures/tree/merkle-tree/README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Merkle - Tree
|
||||||
|
![MerkleVisual](https://media-exp1.licdn.com/dms/image/C5112AQHbQzs-tGzrsQ/article-cover_image-shrink_720_1280/0/1537618109250?e=1635984000&v=beta&t=cATJRvRamNOSOdEFDi9EyH1ls3aQ1r_FHD9RXJIPmKY)
|
||||||
|
|
||||||
|
## What Is a Merkle Tree?
|
||||||
|
A Merkle tree is a data structure that is used in computer science applications. In bitcoin and other cryptocurrencies, Merkle trees serve to encode blockchain data more efficiently and securely.
|
||||||
|
|
||||||
|
Ralph C. Merkle (born February 2, 1952) is a computer scientist. He is one of the inventors of public key cryptography, the inventor of hashing. He is also the inventor of MERKEL tree.
|
||||||
|
|
||||||
|
A Merkle tree is a hash-based data structure that is a generalization of the hash list. It is a tree structure in which each leaf node is a hash of a block of data, and each non-leaf node is a hash of its children. Typically, Merkle trees have a branching factor of 2, meaning that each node has up to 2 children.
|
||||||
|
|
||||||
|
Merkle trees are used in distributed systems for efficient data verification. They are efficient because they use hashes instead of full files. Hashes are ways of encoding files that are much smaller than the actual file itself. Currently, their main uses are in peer-to-peer networks such as Tor, Bitcoin, and Git.
|
||||||
|
|
||||||
|
![MerkleTree](https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Hash_Tree.svg/1920px-Hash_Tree.svg.png)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Merkle tree(Hash tree) is used to verify any kind of data stored, handled and transferred in and between computers.
|
||||||
|
|
||||||
|
Currently, the main use of Merkle tree is to make sure that data blocks received from other peers in a peer-to-peer network are received undamaged and unaltered, and even to check that the other peers do not lie and send fake blocks.
|
||||||
|
|
||||||
|
Merkle tree is used in git, Amazon's Dynamo, Cassandra as well as BitCoin.
|
||||||
|
|
||||||
|
## Role of Merkle Tree in Block Chain:
|
||||||
|
|
||||||
|
Merkle Tree is one of the core data structures which is used in the Bitcoin blockchain to verify the existence of a transaction in a way that conserves both space and time quite effectively
|
||||||
|
|
||||||
|
Merkle trees produce an overall digital fingerprint of the entire set of transactions, providing a very efficient process to verify whether a transaction is included in a block.
|
||||||
|
|
||||||
|
## Algorithm and Implementation
|
||||||
|
|
||||||
|
![Algorithm](https://media-exp1.licdn.com/dms/image/C5112AQEehgC6XD-20Q/article-inline_image-shrink_1000_1488/0/1537618459684?e=1635984000&v=beta&t=jRQinfqEPZvstqnlzklZvUXwyLIjWy6uFkCK3TsMcKQ)
|
||||||
|
|
||||||
|
A Merkle tree is constructed by recursively hashing pairs of nodes until there is only one hash.
|
||||||
|
|
||||||
|
a, b, c, and d are some data elements (files, JSON, etc) and H is a hash function.
|
||||||
|
|
||||||
|
a hash function acts as a “digital fingerprint” of some piece of data by mapping it to a simple string with a low probability that any other piece of data will map to the same string.
|
||||||
|
|
||||||
|
Each node is created by hashing the concatenation of its “parents” in the tree.
|
||||||
|
|
||||||
|
Note: Merkle tree are mostly a binary tree but there are also Trees. Platforms like Ethereum use non binary tree.
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
Merkle trees have very little overhead when compared with hash lists. Binary Merkle trees, like the one pictured above, operate similarly to binary search trees in that their depth is bounded by their branching factor, 2. Included below is worst-case analysis for a Merkle tree with a branching factor of kk.
|
||||||
|
![Complexity](https://miro.medium.com/max/875/1*AghVi4KgVsnxjXNKZBJu9Q.png)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Wikipedia](https://en.wikipedia.org/wiki/Merkle_tree)
|
BIN
src/data-structures/tree/merkle-tree/complexity-merkle-tree.jpg
Normal file
BIN
src/data-structures/tree/merkle-tree/complexity-merkle-tree.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Loading…
Reference in New Issue
Block a user