Compare commits

...

11 Commits

Author SHA1 Message Date
Satvik Shrivas
5a25b83195
Merge a0600f308e into 2c67b48c21 2024-04-25 08:26:17 +08:00
theSatvik
a0600f308e done 2021-08-30 18:11:57 +05:30
theSatvik
9917dcfa3c Added links 2021-08-30 18:09:53 +05:30
theSatvik
8df7780bde Added Visuals and Usage 2021-08-30 18:06:35 +05:30
theSatvik
5958bcfa1a Added Documentation 2021-08-30 17:56:42 +05:30
theSatvik
dbadfe7241 added proof validation method 2021-08-30 16:10:34 +05:30
theSatvik
64ac0fd259 added proof method with commented explanation 2021-08-30 16:09:28 +05:30
theSatvik
8a88cbc455 added _toString method 2021-08-30 16:06:59 +05:30
theSatvik
1bb83c6866 added hash based methods 2021-08-30 16:05:57 +05:30
theSatvik
2992aafcc8 added buildTree method 2021-08-30 16:05:14 +05:30
theSatvik
f6f94f60cf Defined and declared class MerkleTree with constructor 2021-08-30 16:04:18 +05:30
3 changed files with 251 additions and 0 deletions

View 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

View 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB