incubed
Version:
Typescript-version of the incubed client
657 lines • 40.2 kB
JavaScript
"use strict";
/***********************************************************
* 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 EthChainContext_1 = require("./EthChainContext");
const util = require("ethereumjs-util");
const serialize_1 = require("./serialize");
const util_1 = require("../../util/util");
const call_1 = require("./call");
const serverList_1 = require("../../client/serverList");
const merkleProof_1 = require("../../util/merkleProof");
const storage_1 = require("./storage");
const Trie = require("merkle-patricia-tree");
const ethUtil = require("ethereumjs-util");
const ipfs_1 = require("../ipfs/ipfs");
const header_1 = require("./header");
const Client_1 = require("../../client/Client");
const BN = require("bn.js");
// these method are accepted without proof
const allowedWithoutProof = ['ipfs_get', 'ipfs_put', 'eth_blockNumber', 'web3_clientVersion', 'web3_sha3', 'net_version', 'net_peerCount', 'net_listening', 'eth_protocolVersion', 'eth_syncing', 'eth_coinbase', 'eth_mining', 'eth_hashrate', 'eth_gasPrice', 'eth_accounts', 'eth_sign', 'eth_sendRawTransaction', 'eth_estimateGas', 'eth_getCompilers', 'eth_compileLLL', 'eth_compileSolidity', 'eth_compileSerpent', 'eth_getWork', 'eth_submitWork', 'eth_submitHashrate'];
const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16);
/** verify the signatures of a blockhash */
function verifyBlock(b, proof, ctx) {
return __awaiter(this, void 0, void 0, function* () {
// calculate the blockHash
const blockHash = b.hash();
if (proof.expectedBlockHash && !blockHash.equals(proof.expectedBlockHash))
throw new Error('The BlockHash is not the expected one!');
// if we don't expect signatures
if (!proof.expectedSigners || proof.expectedSigners.length === 0) {
// for proof of authorities we can verify the signatures
if (ctx && ctx.chainSpec && (ctx.chainSpec.engine === 'authorityRound' || ctx.chainSpec.engine === 'clique')) {
const finality = yield header_1.checkBlockSignatures([b, ...(proof.proof && proof.proof.finalityBlocks || [])], _ => header_1.getChainSpec(_, ctx));
if (proof.finality && proof.finality > finality)
throw new Error('we have only a finality of ' + finality + ' but expected was ' + proof.finality);
}
// no expected signatures - no need to verify here
return;
}
// we are not allowing block verification without signature
if (!proof.proof.signatures)
throw new Error('No signatures found ');
const existing = ctx && ctx instanceof EthChainContext_1.default && ctx.getBlockHeaderByHash(blockHash);
// filter valid signatures for the current block
const signaturesForBlock = proof.proof.signatures.filter(_ => _ && util_1.toNumber(_.block) === util_1.toNumber(b.number) && (!_.blockHash || blockHash.equals(serialize_1.bytes32(_.blockHash))));
if (signaturesForBlock.length === 0) {
// if the blockhash is already verified, we don't need a signature
if (existing)
return;
throw new Client_1.BlackListError('No signatures found for block ', proof.expectedSigners.map(_ => ethUtil.toChecksumAddress(util_1.toHex(_))));
}
// verify the signatures for only the blocks matching the given
const messageHash = util.keccak(Buffer.concat([blockHash, serialize_1.bytes32(b.number)]));
if (!signaturesForBlock.reduce((p, signature, i) => {
if (!messageHash.equals(serialize_1.bytes32(signature.msgHash)))
throw new Client_1.BlackListError('The signature signed the wrong message!', proof.expectedSigners.map(_ => ethUtil.toChecksumAddress(util_1.toHex(_))));
// recover the signer from the signature
const signer = util.pubToAddress(util.ecrecover(messageHash, util_1.toNumber(signature.v), serialize_1.bytes(signature.r), serialize_1.bytes(signature.s)));
// make sure the signer is the expected one
if (!signer.equals(proof.expectedSigners[i]))
throw new Error('The signature was not signed by ' + proof.expectedSigners[i]);
// we have at least one valid signature, so we can try to cache it.
if (ctx && ctx instanceof EthChainContext_1.default && ctx.client.defConfig.maxBlockCache)
ctx.addBlockHeader(util_1.toNumber(b.number), b.serializeHeader());
// looks good ;-)
return true;
}, true))
throw new Error('No valid signature');
});
}
exports.verifyBlock = verifyBlock;
/** verifies a TransactionProof */
function verifyTransactionProof(txHash, headerProof, txData, ctx) {
return __awaiter(this, void 0, void 0, function* () {
if (!txData)
throw new Error('No TransactionData!');
// decode the blockheader
const block = serialize_1.blockFromHex(headerProof.proof.block);
// verify the blockhash and the signatures
yield verifyBlock(block, Object.assign({}, headerProof, { expectedBlockHash: serialize_1.bytes32(txData.blockHash) }), ctx);
verifyTransaction(txData);
const tx = serialize_1.toTransaction(txData);
const txHashofData = serialize_1.hash(tx);
if (util_1.toNumber(block.number) != util_1.toNumber(txData.blockNumber))
throw new Error('invalid blockNumber');
if (!serialize_1.bytes32(txData.hash).equals(txHashofData))
throw new Error('invalid txhash');
if (headerProof.proof.txIndex != util_1.toNumber(txData.transactionIndex))
throw new Error('invalid txIndex');
if (!txHashofData.equals(txHash))
throw new Error('The transactiondata were manipulated');
// verifiy the proof
yield merkleProof_1.default(block.transactionsTrie, // expected merkle root
util.rlp.encode(util_1.toNumber(headerProof.proof.txIndex)), // path, which is the transsactionIndex
headerProof.proof.merkleProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
serialize_1.serialize(tx), 'The Transaction can not be verified');
});
}
exports.verifyTransactionProof = verifyTransactionProof;
/** verifies a TransactionProof */
function verifyTransactionByBlockProof(request, headerProof, txData, ctx) {
return __awaiter(this, void 0, void 0, function* () {
// decode the blockheader
const block = serialize_1.blockFromHex(headerProof.proof.block);
const txIndex = serialize_1.bytes32(request.params[1]);
if (!txData) {
yield merkleProof_1.default(block.transactionsTrie, // expected merkle root
util.rlp.encode(util_1.toNumber(txIndex)), // path, which is the transsactionIndex
headerProof.proof.merkleProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
null, 'The Transaction can not be verified');
}
else {
// verify the blockhash and the signatures
verifyTransaction(txData);
const tx = serialize_1.toTransaction(txData);
const txHashofData = serialize_1.hash(tx);
if (request.method == "eth_getTransactionByBlockHashAndIndex") {
if (!serialize_1.bytes32(txData.blockHash).equals(serialize_1.bytes32(request.params[0])))
throw new Error('invalid blockHash in transaction data');
yield verifyBlock(block, Object.assign({}, headerProof, { expectedBlockHash: serialize_1.bytes32(request.params[0]) }), ctx);
}
else if (request.method == "eth_getTransactionByBlockNumberAndIndex") {
if (util_1.toNumber(serialize_1.bytes32(request.params[0])) != util_1.toNumber(block.number))
throw new Error('invalid blockNumber in request');
yield verifyBlock(block, Object.assign({}, headerProof, { expectedBlockHash: serialize_1.bytes32(txData.blockHash) }), ctx);
}
if (util_1.toNumber(block.number) != util_1.toNumber(txData.blockNumber))
throw new Error('invalid blockNumber');
if (!serialize_1.bytes32(txData.hash).equals(txHashofData))
throw new Error('invalid txhash');
if (util_1.toNumber(txIndex) != util_1.toNumber(headerProof.proof.txIndex))
throw new Error('invalid txIndex in request');
if (util_1.toNumber(txIndex) != util_1.toNumber(txData.transactionIndex))
throw new Error('invalid txIndex in transaction data');
// verifiy the proof
yield merkleProof_1.default(block.transactionsTrie, // expected merkle root
util.rlp.encode(util_1.toNumber(txIndex)), // path, which is the transsactionIndex
headerProof.proof.merkleProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
serialize_1.serialize(tx), 'The Transaction can not be verified');
}
});
}
exports.verifyTransactionByBlockProof = verifyTransactionByBlockProof;
function verifyLog(l, block, blockHash, index, txIndex, txHash) {
if (l.blockHash !== blockHash)
throw new Error('invalid blockhash');
if (util_1.toNumber(l.blockNumber) !== util_1.toNumber(block.number))
throw new Error('invalid blocknumber');
if (util_1.toNumber(l.logIndex) !== index)
throw new Error('invalid logIndex');
if (l.transactionHash != txHash)
throw new Error('invalid txHash');
if (util_1.toNumber(l.transactionIndex) != txIndex)
throw new Error('invalid txIndex');
}
/** verifies a TransactionProof */
function verifyTransactionReceiptProof(txHash, headerProof, receipt, ctx, useFullProof) {
return __awaiter(this, void 0, void 0, function* () {
if (!receipt)
throw new Error('No ReceiptData!');
if (useFullProof && headerProof.proof.txIndex > 0 && !headerProof.proof.merkleProofPrev)
throw new Error('For Fullproof we expect the merkleProofPrev, which is missing!');
// decode the blockheader
const block = serialize_1.blockFromHex(headerProof.proof.block);
// verify the blockhash and the signatures
yield verifyBlock(block, Object.assign({}, headerProof, { expectedBlockHash: serialize_1.bytes32(receipt.blockHash) }), ctx);
if (headerProof.proof.txIndex === 0 && receipt.cumulativeGasUsed !== receipt.gasUsed)
throw new Error('gasUsed must match cumulativeGasUsed');
// since the blockhash is verified, we have the correct transaction root
// we use the txIndex, so only if both (the transaction matches the hash and ther receiptproof is verified, we know it is the right receipt)
if (util_1.toNumber(receipt.blockNumber) != util_1.toNumber(block.number))
throw new Error('Invalid BlockNumber');
if (!serialize_1.bytes32(receipt.transactionHash).equals(txHash))
throw new Error('Invalid txHash');
if (util_1.toNumber(receipt.transactionIndex) !== headerProof.proof.txIndex)
throw new Error('Invalid txIndex');
// make sure the data in the receipts are correct
receipt.logs.forEach((t, i) => verifyLog(t, block, receipt.blockHash, i, util_1.toNumber(receipt.transactionIndex), receipt.transactionHash));
// verifiy the proof
return Promise.all([
merkleProof_1.default(block.receiptTrie, // expected merkle root
util.rlp.encode(util_1.toNumber(headerProof.proof.txIndex)), // path, which is the transsactionIndex
headerProof.proof.merkleProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
serialize_1.serialize(serialize_1.toReceipt(receipt)), 'The TransactionReceipt can not be verified'),
// prev
useFullProof && headerProof.proof.txIndex > 0 && merkleProof_1.default(block.receiptTrie, // expected merkle root
util.rlp.encode(util_1.toNumber(headerProof.proof.txIndex - 1)), // path, which is the transsactionIndex
headerProof.proof.merkleProof.map(serialize_1.bytes), undefined)
.then(r => {
const prevReceipt = serialize_1.rlp.decode(r);
const gasUsed = util_1.toNumber(receipt.cumulativeGasUsed) - util_1.toNumber(prevReceipt[prevReceipt.length - 3]);
if (util_1.toNumber(receipt.gasUsed) != gasUsed)
throw new Error('The Transaction did consumed ' + gasUsed);
}),
merkleProof_1.default(block.transactionsTrie, // expected merkle root
util.rlp.encode(util_1.toNumber(headerProof.proof.txIndex)), // path, which is the transsactionIndex
headerProof.proof.txProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
undefined, 'The TransactionIndex can not be verified').then(val => {
if (!serialize_1.hash(val).equals(txHash))
throw new Error('The TransactionHash does not match the prooved one');
})
]);
});
}
exports.verifyTransactionReceiptProof = verifyTransactionReceiptProof;
/** verifies a TransactionProof */
function verifyLogProof(headerProof, logs, ctx) {
return __awaiter(this, void 0, void 0, function* () {
if (!logs)
throw new Error('No Logs!');
if (!logs.length)
return;
if (!headerProof.proof.logProof)
throw new Error('Missing LogProof');
const receiptData = {};
const blockHashes = {};
yield Promise.all(Object.keys(headerProof.proof.logProof).map((bn) => __awaiter(this, void 0, void 0, function* () {
const blockProof = headerProof.proof.logProof[bn];
// decode the blockheader
const block = serialize_1.blockFromHex(blockProof.block);
blockHashes[bn] = block.hash();
if (util_1.toHex(blockProof.number) !== bn)
throw new Error('wrong blocknumber');
// verify the blockhash and the signatures
yield verifyBlock(block, headerProof, ctx);
// verifiy all merkle-Trees of the receipts
yield Promise.all(Object.keys(blockProof.receipts).map(txHash => merkleProof_1.default(block.receiptTrie, // expected merkle root
util.rlp.encode(blockProof.receipts[txHash].txIndex), // path, which is the transsactionIndex
blockProof.receipts[txHash].proof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
undefined // we don't want to check, but use the found value in the next step
).then(value => receiptData[txHash] = util.rlp.decode(value)))),
// verifiy all merkle-Trees of the receipts
yield Promise.all(Object.keys(blockProof.receipts).map(txHash => merkleProof_1.default(block.transactionsTrie, // expected merkle root
util.rlp.encode(blockProof.receipts[txHash].txIndex), // path, which is the transsactionIndex
blockProof.receipts[txHash].txProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
undefined // we don't want to check, but use the found value in the next step
).then(value => serialize_1.bytes32(txHash).equals(serialize_1.hash(value)) && serialize_1.bytes32(txHash).equals(serialize_1.bytes32(blockProof.receipts[txHash].txHash)) || Promise.reject(new Error('wrong txhash')))));
})));
// now verify the logdata
logs.forEach(l => {
const receipt = receiptData[l.transactionHash];
if (!receipt)
throw new Error('The receipt ' + l.transactionHash + 'is missing in the proof');
const logData = receipt[receipt.length - 1][util_1.toNumber(l.transactionLogIndex)];
if (!logData)
throw new Error('Log not found in Transaction');
if (!logData[0].equals(serialize_1.address(l.address)))
throw new Error('Wrong address in log ');
if (logData[1].map(util_1.toHex).join() !== l.topics.join())
throw new Error('Wrong Topics in log ');
if (!logData[2].equals(serialize_1.bytes(l.data)))
throw new Error('Wrong data in log ');
const bp = headerProof.proof.logProof[util_1.toHex(l.blockNumber)];
if (!bp)
throw new Error('wrong blockNumber');
if (!blockHashes[util_1.toHex(l.blockNumber)].equals(serialize_1.bytes32(l.blockHash)))
throw new Error('wrong blockhash');
if (!bp.receipts[l.transactionHash])
throw new Error('wrong transactionHash');
if (util_1.toNumber(bp.receipts[l.transactionHash].txIndex) !== util_1.toNumber(l.transactionIndex))
throw new Error('wrong transactionIndex');
});
});
}
exports.verifyLogProof = verifyLogProof;
/** verifies a TransactionProof */
function verifyBlockProof(request, data, headerProof, ctx) {
return __awaiter(this, void 0, void 0, function* () {
// decode the blockheader
const block = new serialize_1.Block(headerProof.proof.block || data);
if (headerProof.proof.transactions)
block.transactions = headerProof.proof.transactions.map(serialize_1.createTx);
let requiredHash = null;
if (request.method.endsWith('ByHash'))
requiredHash = serialize_1.bytes32(request.params[0]);
else if (parseInt(request.params[0]) && util_1.toNumber(request.params[0]) !== util_1.toNumber(block.number))
throw new Error('The Block does not contain the required blocknumber');
if (!requiredHash && request.method.indexOf('Count') < 0 && data)
requiredHash = serialize_1.bytes32(data.hash);
// we only need to verify the uncles, if they are actually part of the data
if (data && data.uncles) {
const bd = data;
if (bd.uncles.length === 0) {
if (!serialize_1.bytes32(bd.sha3Uncles).equals(serialize_1.bytes('0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347')))
throw new Error('Wrong uncle-hash');
}
else if (request.in3.useFullProof) {
if (!headerProof.proof.uncles || headerProof.proof.uncles.length != bd.uncles.length)
throw new Error('The Uncles are missing or wrong size!');
// we only verify uncles for full proof
const trie = new Trie();
yield Promise.all(headerProof.proof.uncles.map((b, i) => {
const header = util_1.toBuffer(b);
if (!serialize_1.hash(header).equals(util_1.toBuffer(bd.uncles[i])))
throw new Error('The uncle hash of uncle ' + i + ' is wrong');
return util_1.promisify(trie, trie.put, util.rlp.encode(i), header);
}));
if (!trie.root.equals(block.uncleHash))
throw new Error('The UncleRoot do not match uncles!');
}
}
// verify the blockhash and the signatures
yield verifyBlock(block, Object.assign({}, headerProof, { expectedBlockHash: requiredHash }), ctx);
// verify additional fields
if (data && request.method.indexOf('Count') < 0) {
const bd = data;
const d = data;
if (d.author && d.author !== bd.miner)
throw new Error('Invalid author');
if (d.hash && !serialize_1.bytes32(d.hash).equals(requiredHash || block.hash()))
throw new Error('Invalid hash');
if (d.mixHash && !serialize_1.bytes32(d.mixHash).equals(block.sealedFields[0]))
throw new Error('Invalid mixHash');
if (d.nonce && !serialize_1.bytes8(d.nonce).equals(block.sealedFields[1]))
throw new Error('Invalid nonce');
}
// verify the transactions
if (block.transactions) {
const trie = new Trie();
yield Promise.all(block.transactions.map((tx, i) => util_1.promisify(trie, trie.put, util.rlp.encode(i), tx.serialize())));
const thash = block.transactions.length ? trie.root : util.KECCAK256_RLP;
if (!thash.equals(block.transactionsTrie))
throw new Error('The Transactions do not match transactionRoot!');
}
if (data && data.transactions) {
const rtransactions = data.transactions;
if (rtransactions.length != block.transactions.length)
throw new Error('wrong number of transactions in block');
if (request.params.length == 2 && request.params[1])
rtransactions.forEach((t, i) => {
if (t.blockHash && !serialize_1.bytes32(t.blockHash).equals(requiredHash || block.hash()))
throw new Error('Invalid hash in tx');
if (t.blockNumber && util_1.toNumber(t.blockNumber) != util_1.toNumber(block.number))
throw new Error('Invalid blocknumber');
if (util_1.toNumber(t.transactionIndex) != i)
throw new Error('Wrong transactionIndex');
verifyTransaction(t);
});
else
rtransactions.forEach((t, i) => {
if (!block.transactions[i].hash().equals(serialize_1.bytes32(t)))
throw new Error('Invalid TransactionHash');
});
}
if (request.method.indexOf('Count') > 0 && util_1.toHex(block.transactions.length) != util_1.toHex(data))
throw new Error('The number of transaction does not match');
});
}
exports.verifyBlockProof = verifyBlockProof;
function verifyTransaction(t) {
const raw = serialize_1.toTransaction(t);
let rawHash, v = ethUtil.bufferToInt(serialize_1.bytes(t.v));
if (t.chainId) {
rawHash = serialize_1.hash([...raw.slice(0, 6), serialize_1.uint(t.chainId), Buffer.allocUnsafe(0), Buffer.allocUnsafe(0)]);
v -= util_1.toNumber(t.chainId) * 2 + 8;
}
else
rawHash = serialize_1.hash(raw.slice(0, 6));
if (new BN(t.s).cmp(N_DIV_2) === 1)
throw new Error('Invalid signature');
const senderPubKey = ethUtil.ecrecover(rawHash, v, serialize_1.bytes(t.r), serialize_1.bytes(t.s));
if (!serialize_1.bytes(t.publicKey).equals(senderPubKey))
throw new Error('Invalid public key');
if (!serialize_1.address(t.from).equals(ethUtil.publicToAddress(senderPubKey)))
throw new Error('Invalid from');
if (t.raw && !serialize_1.bytes(t.raw).equals(ethUtil.rlp.encode(raw)))
throw new Error('Invalid Raw data');
if (t.standardV && util_1.toNumber(t.standardV) != v - 27)
throw new Error('Invalid stanardV ');
}
exports.verifyTransaction = verifyTransaction;
/** verifies a TransactionProof */
function verifyAccountProof(request, value, headerProof, ctx) {
return __awaiter(this, void 0, void 0, function* () {
if (!value)
throw new Error('No Accountdata!');
// get the account this proof is based on
const account = serialize_1.address(request.method === 'in3_nodeList' ? value.contract : request.params[0]);
// verify the blockhash and the signatures
const block = new serialize_1.Block(headerProof.proof.block);
// TODO if we expect a specific block in the request, we should also check if the block is the one requested
yield verifyBlock(block, headerProof, ctx);
// get the account-proof
const accountProof = headerProof.proof.accounts[Object.keys(headerProof.proof.accounts)[0]];
if (!accountProof)
throw new Error('Missing Account in Account-Proof');
// verify the result
if (!account.equals(serialize_1.address(accountProof.address)))
throw new Error('The Account does not match the account in the proof');
switch (request.method) {
case 'eth_getBalance':
if (!util_1.toBN(value).eq(util_1.toBN(accountProof.balance)))
throw new Error('The Balance does not match the one in the proof');
break;
case 'eth_getStorageAt':
checkStorage(accountProof, serialize_1.bytes32(request.params[1]), serialize_1.bytes32(value));
break;
case 'eth_getCode':
if (!serialize_1.bytes32(accountProof.codeHash).equals(util.keccak(value)))
throw new Error('The codehash in the proof does not match the code');
break;
case 'eth_getTransactionCount':
if (!util_1.toBN(accountProof.nonce).eq(util_1.toBN(value)))
throw new Error('The nonce in the proof does not match the returned');
break;
case 'in3_nodeList':
verifyNodeListData(value, headerProof.proof, block, request);
// the contract must be checked later in the updateList -function
break;
default:
throw new Error('Unsupported Account-Proof for ' + request.method);
}
// verify the merkle tree of the account proof
yield verifyAccount(accountProof, block);
});
}
exports.verifyAccountProof = verifyAccountProof;
function verifyNodeListData(nl, proof, block, request) {
// get the one account to check with
const accountProof = proof.accounts[Object.keys(proof.accounts)[0]];
if (!accountProof)
throw new Error('Missing Account in Account-Proof');
// check the total servercount
checkStorage(accountProof, storage_1.getStorageArrayKey(0), serialize_1.bytes32(nl.totalServers), 'wrong number of servers ');
// check blocknumber
if (util_1.toNumber(block.number) < nl.lastBlockNumber)
throw new Error('The signature is based on older block!');
// if we requested a limit, we need to find out if the correct nodes where send.
const limit = request.params[0];
if (limit && limit < nl.totalServers) {
if (limit !== nl.nodes.length)
throw new Error('The number of returned nodes must be ' + limit + ', since this was required and there are ' + nl.totalServers + ' servers');
// try to find the addresses in the node list
const idxs = (request.params[2] || []).map(adr => {
const a = nl.nodes.find(_ => _.address === adr);
if (!a)
throw new Error('The required address ' + adr + ' is not part of the list!');
return a.index;
});
// create the index the same way the server should
serverList_1.createRandomIndexes(nl.totalServers, limit, serialize_1.bytes32(request.params[1]), idxs);
// veryfy the index is in the same order
if (idxs.length !== limit)
throw new Error('wrong number of index');
idxs.forEach((index, i) => {
if (nl.nodes[i].index !== index)
throw new Error('the index of node nr. ' + (i + 1) + ' needs to be ' + index);
});
}
else {
// check server count
if (nl.nodes.length !== nl.totalServers)
throw new Error('Wrong number of nodes!');
// check the index of the result
const failedNode = nl.nodes.find((n, i) => n.index !== i);
if (failedNode)
throw new Error('The node ' + failedNode.url + ' has the wrong index!');
}
// verify the values of the proof
for (const n of nl.nodes) {
checkStorage(accountProof, storage_1.getStorageArrayKey(0, n.index, 6, 1), serialize_1.bytes32(Buffer.concat([serialize_1.uint64(n.timeout ? n.timeout : 0), serialize_1.address(n.address)])), 'wrong owner ');
// when checking the deposit we have to take into account the fact, that anumber only support 53bits and may not be able to hit the exact ammount, but it should always be equals
const deposit = getStorageValue(accountProof, storage_1.getStorageArrayKey(0, n.index, 6, 2));
if (parseInt(util_1.toBN(deposit).toString()) != parseInt(n.deposit))
throw new Error('wrong deposit ');
// checkStorage(accountProof, getStorageArrayKey(0, n.index, 6, 2), bytes32(n.deposit), 'wrong deposit ')
const props = serialize_1.bytes32(n.props);
if (n.capacity)
props.writeUInt32BE(n.capacity, 12);
checkStorage(accountProof, storage_1.getStorageArrayKey(0, n.index, 6, 3), props, 'wrong props ');
const urlKey = storage_1.getStorageArrayKey(0, n.index, 6, 0);
const urlVal = storage_1.getStringValue(getStorageValue(accountProof, urlKey), urlKey);
if (typeof urlVal === 'string') {
if (urlVal !== n.url)
throw new Error('Wrong url in proof ' + n.url);
}
else {
const url = Buffer.concat(urlVal.storageKeys.map(_ => getStorageValue(accountProof, _))).slice(0, urlVal.len).toString('utf8');
if (url !== n.url)
throw new Error('Wrong url in proof ' + n.url);
}
}
}
function checkStorage(ap, key, value, msg) {
if (!getStorageValue(ap, key).equals(value))
throw new Error(msg + ('The key has the wrong value (expected: ' + util_1.toMinHex(value) + ' proven:' + util_1.toMinHex(getStorageValue(ap, key))));
}
function getStorageValue(ap, storageKey) {
let key = util_1.toMinHex(storageKey);
let entry = ap.storageProof.find(_ => _.key === key);
if (!entry && key.length % 2) {
key = '0x0' + key.substr(2);
entry = ap.storageProof.find(_ => _.key === key);
}
if (!entry)
throw new Error(' There is no storrage key ' + key + ' in the storage proof!');
return serialize_1.bytes32(entry.value);
}
exports.getStorageValue = getStorageValue;
/** verifies a TransactionProof */
function verifyCallProof(request, value, headerProof, ctx) {
return __awaiter(this, void 0, void 0, function* () {
// verify the blockhash and the signatures
const block = new serialize_1.Block(headerProof.proof.block);
// TODO if we expect a specific block in the request, we should also check if the block is the one requested
yield verifyBlock(block, headerProof, ctx);
if (!headerProof.proof.accounts)
throw new Error('No Accounts to verify');
// make sure, we have all codes
const missingCode = Object.keys(headerProof.proof.accounts)
.filter(ac => !headerProof.proof.accounts[ac].code && headerProof.proof.accounts[ac].codeHash !== '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470');
// in case there are some missing codes, we fetch them with one unproved request through the cache, since they will be verified later anyway.
if (missingCode.length && ctx && ctx instanceof EthChainContext_1.default)
yield ctx.getCodeFor(missingCode.map(serialize_1.address), util_1.toHex(block.number)).then(_ => _.forEach((c, i) => headerProof.proof.accounts[missingCode[i]].code = c));
// verify all accounts
yield Promise.all(Object.keys(headerProof.proof.accounts).map(adr => verifyAccount(headerProof.proof.accounts[adr], block)));
// now create a vm and run the transaction
const result = yield call_1.executeCall(request.params[0], headerProof.proof.accounts, new serialize_1.Block({ parentHash: block.parentHash, sha3Uncles: block.uncleHash, miner: block.coinbase, stateRoot: block.stateRoot, transactionsRoot: block.transactionsTrie, receiptRoot: block.receiptTrie, logsBloom: block.bloom, difficulty: block.difficulty, number: block.number, gasLimit: block.gasLimit, gasUsed: block.gasUsed, timestamp: block.timestamp, extraData: block.extra }).serializeHeader());
if (!result.equals(value))
throw new Error('The result does not match the execution !');
});
}
exports.verifyCallProof = verifyCallProof;
/** verify a an account */
function verifyAccount(accountProof, block) {
return __awaiter(this, void 0, void 0, function* () {
// if we received the code, make sure the codeHash is correct!
if (accountProof.code && !util.keccak(accountProof.code).equals(serialize_1.bytes32(accountProof.codeHash)))
throw new Error('The code does not math the correct codehash! ');
const emptyAccount = isNotExistend(accountProof);
if (emptyAccount && accountProof.storageHash !== '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421')
throw new Error('Invalid storageHash');
return Promise.all([
merkleProof_1.default(block.stateRoot, // expected merkle root
util.keccak(accountProof.address), // path, which is the transsactionIndex
accountProof.accountProof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
emptyAccount ? null : serialize_1.serialize(serialize_1.toAccount(accountProof)), 'The Account could not be verified'),
// and all storage proofs
...accountProof.storageProof.map(s => merkleProof_1.default(serialize_1.bytes32(accountProof.storageHash), // the storageRoot of the account
util.keccak(serialize_1.bytes32(s.key)), // the path, which is the hash of the key
s.proof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data
util_1.toNumber(s.value) === 0 ? null : util.rlp.encode(s.value), 'The Storage could not be verified'))
]);
});
}
function isNotExistend(account) {
// TODO how do I determine the default nonce? It is in the chain-config
return util_1.toNumber(account.balance) === 0 && account.codeHash == '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' && util_1.toNumber(account.nonce) === 0;
}
function checkBlock(block, ctx, blockNumber) {
if (!block)
return block;
if (typeof block === 'string' && !block.startsWith('0x') && ctx instanceof EthChainContext_1.default) {
const bh = ctx.getBlockHeader(util_1.toNumber(block));
if (!bh)
throw new Error('The server returned a not supported blockheader : ' + block);
return bh;
}
return block;
}
function handleBlockCache(proof, ctx) {
if (!ctx || !ctx.client.defConfig.maxBlockCache)
return;
if (proof.block)
proof.block = checkBlock(proof.block, ctx);
if (proof.logProof)
Object.keys(proof.logProof).forEach(bn => {
const v = proof.logProof[bn];
v.block = checkBlock(v.block, ctx, util_1.toNumber(bn));
});
}
/** general verification-function which handles it according to its given type. */
function verifyProof(request, response, allowWithoutProof = true, ctx) {
return __awaiter(this, void 0, void 0, function* () {
// make sure we ignore errors caused by sending a trnasaction to multiple servers.
if (request.method === 'eth_sendRawTransaction' && response.error && (response.error.code === -32010 || response.error.toString().indexOf('already imported') >= 0)) {
delete response.error;
response.result = util_1.toHex(serialize_1.hash(serialize_1.bytes(request.params[0])), 20);
}
// handle verification with implicit proof (like ipfs)
if (request.method === 'ipfs_get' && response.result)
return ipfs_1.verifyIPFSHash(response.result, request.params[1] || 'base64', request.params[0]);
// make sure we only throw an exception for missing proof, if the proof is possible
const proof = response && response.in3 && response.in3.proof;
if (!proof) {
if (allowedWithoutProof.indexOf(request.method) >= 0)
return true;
// exceptions
if (request.method === 'eth_getLogs' && response.result && response.result.length === 0)
return true;
if (request.method.startsWith('eth_getTransaction') && !response.result)
return true;
if (!allowWithoutProof && !response.error)
throw new Error('the response does not contain any proof!');
return !!response.error || allowWithoutProof;
}
//attach the lastValidatorChange to the chain context
if ((response.in3.lastValidatorChange || 0) > ctx.lastValidatorChange)
ctx.lastValidatorChange = response.in3.lastValidatorChange;
// check BlockCache and convert all blockheaders to buffer
handleBlockCache(proof, ctx);
// convert all signatures into buffer
const headerProof = { proof, expectedSigners: request.in3 && request.in3.signatures && request.in3.signatures.map(serialize_1.address), finality: request.in3 && request.in3.finality };
switch (proof.type) {
case 'transactionProof':
if (request.method == "eth_getTransactionByBlockHashAndIndex" || request.method == "eth_getTransactionByBlockNumberAndIndex")
yield verifyTransactionByBlockProof(request, headerProof, response.result, ctx);
else
yield verifyTransactionProof(serialize_1.bytes32(request.params[0]), headerProof, response.result, ctx);
break;
case 'logProof':
yield verifyLogProof(headerProof, response.result && response.result, ctx);
break;
case 'receiptProof':
yield verifyTransactionReceiptProof(serialize_1.bytes32(request.params[0]), headerProof, response.result && response.result, ctx, request.in3.useFullProof);
break;
case 'blockProof':
yield verifyBlockProof(request, response.result, headerProof, ctx);
break;
case 'accountProof':
yield verifyAccountProof(request, response.result, headerProof, ctx);
break;
case 'callProof':
yield verifyCallProof(request, serialize_1.bytes(response.result), headerProof, ctx);
break;
default:
throw new Error('Unsupported proof-type : ' + proof.type);
}
return true;
});
}
exports.verifyProof = verifyProof;
//# sourceMappingURL=verify.js.map