@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
147 lines (137 loc) • 4.02 kB
JavaScript
/*
* ISC License (ISC)
* Copyright (c) 2018 aeternity developers
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
import { rlp, hash } from './crypto'
const NODE_TYPES = {
branch: 1,
extension: 2,
leaf: 3
}
function nodeType (node) {
if (node.length === 17) {
return NODE_TYPES.branch
}
if (node.length === 2) {
const nibble = node[0].toString('hex')[0]
if (nibble === '0' || nibble === '1') {
return NODE_TYPES.extension
}
if (nibble === '2' || nibble === '3') {
return NODE_TYPES.leaf
}
}
}
function decodePath (path) {
if (path[0] === '0' || path[0] === '2') {
return path.slice(2)
}
if (path[0] === '1' || path[0] === '3') {
return path.slice(1)
}
}
/**
* Deserialize Merkle Patricia Tree
* @rtype (binary: Array) => Object
* @param {Array} binary - Binary
* @return {Object} Merkle Patricia Tree
*/
export function deserialize (binary) {
return {
rootHash: binary[0].toString('hex'),
nodes: binary[1].reduce((prev, node) => ({
...prev,
[node[0].toString('hex')]: node[1]
}), {})
}
}
/**
* Serialize Merkle Patricia Tree
* @rtype (tree: Object) => Array
* @param {Object} tree - Merkle Patricia Tree
* @return {Array} Binary
*/
export function serialize (tree) {
return [
Buffer.from(tree.rootHash, 'hex'),
Object.entries(tree.nodes).map(([mptHash, value]) => ([
Buffer.from(mptHash, 'hex'),
value
]))
]
}
/**
* Retrieve value from Merkle Patricia Tree
* @rtype (tree: Object, key: String) => Buffer
* @param {Object} tree - Merkle Patricia Tree
* @param {String} key - The key of the element to retrieve
* @return {Buffer} Value associated to the specified key
*/
export function get (tree, key, hash) {
const node = hash ? tree.nodes[hash] : tree.nodes[tree.rootHash]
const type = nodeType(node)
if (type === NODE_TYPES.branch) {
if (key.length) {
const nextHash = node[parseInt(key[0], 16)].toString('hex')
return get(tree, key.substr(1), nextHash)
}
return node[16]
}
if (type === NODE_TYPES.extension) {
const path = decodePath(node[0].toString('hex'))
if (key.substr(0, path.length) === path) {
return get(tree, key.substr(path.length), node[1].toString('hex'))
}
}
if (type === NODE_TYPES.leaf) {
if (node[0].toString('hex').substr(1) === key) {
return node[1]
}
}
}
function nodeHash (node) {
return Buffer.from(hash(rlp.encode(node))).toString('hex')
}
/**
* Verify if rootHash of Merkle Patricia Tree is correct
* @rtype (tree: Object) => Boolean
* @param {Object} tree - Merkle Patricia Tree
* @return {Boolean} Boolean indicating whether or not rootHash is correct
*/
export function verify (tree, key, verified = []) {
const hash = key || tree.rootHash
if (verified.includes(hash)) {
return true
}
const node = tree.nodes[hash]
const type = nodeType(node)
if (nodeHash(node) !== hash) {
return false
}
verified.push(hash)
if (type === NODE_TYPES.branch) {
return !node.some((n, i) => {
const nextKey = n.toString('hex')
if (i < 16) {
return !verify(tree, nextKey, verified)
}
return false
})
}
if (type === NODE_TYPES.extension) {
return verify(tree, node[1].toString('hex'), verified)
}
return true
}