Last active
March 28, 2018 15:53
-
-
Save tgoldenberg/73524940965d967da84275686f8a5178 to your computer and use it in GitHub Desktop.
Validate that blocks follow Bitcoin protocol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BlockModel from 'models/Block'; | |
import find from 'lodash/find'; | |
import { verifyUnlock } from 'utils/verifySignature'; | |
const COIN = 100000000; | |
const COINBASE_REWARD = 50 * COIN; | |
export async function areBlocksValid(blocks) { | |
if (!blocks.length) { | |
return false; | |
} | |
// loop through each block and check that is valid | |
for (let i = 0; i < blocks.length; i++) { | |
const isValid = await isBlockValid(blocks[i], blocks[i-1], i === 0); | |
if (!isValid) { | |
return false; | |
} | |
} | |
return true; | |
} | |
export async function isBlockValid(block, prevBlock, isGenesis) { | |
// check that all transactions are valid | |
const { txs } = block; | |
for (let i = 0; i < txs.length; i++) { | |
const isValid = await isTxValid(txs[i]); | |
if (!isValid) { | |
return false; | |
} | |
} | |
// check mining - that nonce is correctly calculated | |
const target = Math.pow(2, 256 - block.difficulty); | |
if (parseInt(block.hash, 16) > target) { | |
console.log('> Incorrect nonce: ', block.hash); | |
return false; | |
} | |
// check that previousHash is correct (special case for genesis block) | |
if (!isGenesis && block.previousHash !== prevBlock.hash) { | |
return false; | |
} | |
return true; | |
} | |
export async function isTxValid(tx) { | |
// verify inputs and outputs ("vin" and "vout") | |
if (!tx.vin || !tx.vin.length || !tx.vout || !tx.vout.length) { | |
return false; | |
} | |
let txinValue = 0; | |
let txoutValue = 0; | |
// check inputs | |
for (let i = 0; i < tx.vin.length; i++) { | |
const txin = tx.vin[i]; | |
if (txin.prevout === 'COINBASE') { | |
// make sure only one coinbase tx | |
if (tx.vout.length > 1 || tx.vin.length > 1 || tx.vout[i].nValue > COINBASE_REWARD) { | |
return false; | |
} | |
return true; | |
} | |
// validate regular input | |
let prevTxBlock = await BlockModel.findOne({ 'txs.hash': txin.prevout }); | |
if (!prevTxBlock) { | |
return false; | |
} | |
let prevTx = find(prevTxBlock.txs, ({ hash }) => hash === txin.prevout); | |
if (!prevTx) { | |
return false; | |
} | |
txinValue += prevTx.vout[txin.n].nValue; | |
// ensure that is not double spent in multiple txs | |
let alreadySpentTxs = await BlockModel.find({ 'txs.vin.prevout': txin.prevout }); | |
if (alreadySpentTxs.length > 1) { | |
return false; | |
} | |
// verify signature | |
let publicKeyScript = prevTx.vout[txin.n].scriptPubKey; | |
let txid = prevTx.hash; | |
let [ publicKey, scriptSig ] = txin.scriptSig.split(' '); | |
let isVerified = verifyUnlock(txid, prevTx.vout[txin.n].scriptPubKey, publicKey, scriptSig); | |
if (!isVerified) { | |
return false; | |
} | |
} | |
// check transaction outputs | |
for (let i = 0; i < tx.vout.length; i++) { | |
let txout = tx.vout[i]; | |
if (typeof txout.nValue !== 'number' || typeof txout.scriptPubKey !== 'string') { | |
return false; | |
} | |
txoutValue += txout.nValue; | |
} | |
// check that input value is not greater than the output value | |
let totalFees = txinValue - txoutValue; | |
if (totalFees < 0) { | |
return false; | |
} | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment