incubed
Version:
Typescript-version of the incubed client
157 lines • 8.42 kB
JavaScript
;
/***********************************************************
* This file is part of the Slock.it IoT Layer. *
* The Slock.it IoT Layer contains: *
* - USN (Universal Sharing Network) *
* - INCUBED (Trustless INcentivized remote Node Network) *
************************************************************
* Copyright (C) 2016 - 2018 Slock.it GmbH *
* All Rights Reserved. *
************************************************************
* You may use, distribute and modify this code under the *
* terms of the license contract you have concluded with *
* Slock.it GmbH. *
* For information about liability, maintenance etc. also *
* refer to the contract concluded with Slock.it GmbH. *
************************************************************
* For more information, please refer to https://slock.it *
* For questions, please contact info@slock.it *
***********************************************************/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const ethereumjs_util_1 = require("ethereumjs-util");
/**
* Verifies a Merkle Proof.
* @param rootHash the rootHash of the Trie
* @param path the path to proof
* @param proof the serialized nodes
* @param expectedValue expected value, if null, this function verifies for non existing node.
* @param errorMsg the error message that should be used in case of not verifiable.
*
* The function will return the value of the last node if it was successfull or throw otherwise.
*/
function verify(rootHash, path, proof, expectedValue, errorMsg) {
return __awaiter(this, void 0, void 0, function* () {
// prepare Error-Message
const errorPrefix = errorMsg ? errorMsg + ' : ' : '';
// create the nibbles to iterate over the path
const key = stringToNibbles(path);
// start with the root-Hash
let wantedHash = rootHash;
let lastNode = null;
// iterate through the nodes starting at root
for (let i = 0; i < proof.length; i++) {
const p = proof[i];
const hash = ethereumjs_util_1.keccak(p);
// verify the hash of the node
if (Buffer.compare(hash, wantedHash))
throw new Error('Bad proof node ' + i + ': hash mismatch');
// create the node
const node = lastNode = new Node(ethereumjs_util_1.rlp.decode(p));
switch (node.type) {
case 'empty':
if (i == 0 && expectedValue === null)
return null;
throw new Error('invalid empty node here');
case 'branch':
// we reached the end of the path
if (key.length === 0) {
if (i !== proof.length - 1)
throw new Error(errorPrefix + 'Additional nodes at end of proof (branch)');
// our value is a branch, but we can return the value
// TODO does this make sense?
return node.value;
}
// find the childHash
const childHash = node.raw[key[0]];
// remove the first item
key.splice(0, 1);
if (childHash.length === 2) {
const embeddedNode = new Node(childHash);
if (i !== proof.length - 1)
throw new Error(errorPrefix + 'Additional nodes at end of proof (embeddedNode)');
if (matchingNibbleLength(embeddedNode.key, key) !== embeddedNode.key.length)
throw new Error(errorPrefix + 'Key length does not match with the proof one (embeddedNode)');
key.splice(0, embeddedNode.key.length);
if (key.length !== 0)
throw new Error(errorPrefix + 'Key does not match with the proof one (embeddedNode)');
// all is fine we return the value
return embeddedNode.value;
}
else
wantedHash = childHash;
break;
case 'leaf':
case 'extension':
const val = node.value;
// if the relativeKey in the leaf does not math our rest key, we throw!
if (matchingNibbleLength(node.key, key) !== node.key.length) {
// so we have a wrong leaf here, if we actually expected this node to not exist,
// the last node in this path may be a different leaf or a branch with a empty hash
if (key.length === node.key.length && i === proof.length - 1 && expectedValue === null)
return val;
throw new Error(errorPrefix + 'Key does not match with the proof one (extention|leaf)');
}
// remove the items
key.splice(0, node.key.length);
if (key.length === 0) {
if (i !== proof.length - 1)
throw new Error(errorPrefix + 'Additional nodes at end of proof (extention|leaf)');
// if we are expecting a value we need to check
if (expectedValue && expectedValue.compare(val))
throw new Error(errorPrefix + ' The proven value was expected to be ' + expectedValue.toString('hex') + ' but is ' + val.toString('hex'));
// if we are proven a value which shouldn't exist this must throw an error
if (expectedValue === null)
throw new Error(errorPrefix + ' The value shouldn\'t exist, but is ' + val.toString('hex'));
return val;
}
else
// we continue with the hash
wantedHash = val;
break;
default:
throw new Error(errorPrefix + 'Invalid node type');
}
}
// if we expected this to be null and there is not further node since wantedHash is empty or we had a extension as last element, than it is ok not to find leafs
if (expectedValue === null && (lastNode === null || lastNode.type === 'extension' || wantedHash.length === 0))
return null;
// we reached the end of the proof, but not of the path
throw new Error('Unexpected end of proof');
});
}
exports.default = verify;
function matchingNibbleLength(a, b) {
const i = a.findIndex((_, i) => _ !== b[i]);
return i < 0 ? a.length : i + 1;
}
class Node {
constructor(data) {
this.raw = data;
if (data.length === 17) {
this.type = 'branch';
this.value = data[16];
}
else if (data.length === 2) {
this.type = (data[0][0] >> 4) > 1 ? 'leaf' : 'extension';
this.value = data[1];
this.key = stringToNibbles(data[0]).slice((data[0][0] >> 4) % 2 ? 1 : 2);
}
else if (data.length === 0)
this.type = 'empty';
}
}
// create the nibbles of a path
const hexToInt = { '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15 };
function stringToNibbles(bkey) {
return bkey.toString('hex').split('').map(_ => hexToInt[_]);
}
exports.stringToNibbles = stringToNibbles;
//# sourceMappingURL=merkleProof.js.map