|
|
@ -9,10 +9,8 @@ const typeforce = require('typeforce'); |
|
|
|
const varuint = require('varuint-bitcoin'); |
|
|
|
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); |
|
|
|
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); |
|
|
|
const errorBufferTooSmall = new Error('Buffer too small (< 80 bytes)'); |
|
|
|
function txesHaveWitness(transactions) { |
|
|
|
return transactions !== undefined && |
|
|
|
transactions instanceof Array && |
|
|
|
function txesHaveWitnessCommit(transactions) { |
|
|
|
return transactions instanceof Array && |
|
|
|
transactions[0] && |
|
|
|
transactions[0].ins && |
|
|
|
transactions[0].ins instanceof Array && |
|
|
@ -21,6 +19,14 @@ function txesHaveWitness(transactions) { |
|
|
|
transactions[0].ins[0].witness instanceof Array && |
|
|
|
transactions[0].ins[0].witness.length > 0; |
|
|
|
} |
|
|
|
function anyTxHasWitness(transactions) { |
|
|
|
return transactions instanceof Array && |
|
|
|
transactions.some(tx => typeof tx === 'object' && |
|
|
|
tx.ins instanceof Array && |
|
|
|
tx.ins.some(input => typeof input === 'object' && |
|
|
|
input.witness instanceof Array && |
|
|
|
input.witness.length > 0)); |
|
|
|
} |
|
|
|
class Block { |
|
|
|
constructor() { |
|
|
|
this.version = 1; |
|
|
@ -34,7 +40,7 @@ class Block { |
|
|
|
} |
|
|
|
static fromBuffer(buffer) { |
|
|
|
if (buffer.length < 80) |
|
|
|
throw errorBufferTooSmall; |
|
|
|
throw new Error('Buffer too small (< 80 bytes)'); |
|
|
|
let offset = 0; |
|
|
|
const readSlice = (n) => { |
|
|
|
offset += n; |
|
|
@ -75,18 +81,10 @@ class Block { |
|
|
|
const tx = readTransaction(); |
|
|
|
block.transactions.push(tx); |
|
|
|
} |
|
|
|
let witnessCommit = block.getWitnessCommit(); |
|
|
|
// This Block contains a witness commit
|
|
|
|
if (block.hasWitnessCommit()) { |
|
|
|
// The merkle root for the witness data is in an OP_RETURN output.
|
|
|
|
// There is no rule for the index of the output, so use filter to find it.
|
|
|
|
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
|
|
|
|
// If multiple commits are found, the output with highest index is assumed.
|
|
|
|
let witnessCommits = block.transactions[0].outs |
|
|
|
.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))) |
|
|
|
.map(out => out.script.slice(6, 38)); |
|
|
|
// Use the commit with the highest output (should only be one though)
|
|
|
|
block.witnessCommit = witnessCommits[witnessCommits.length - 1]; |
|
|
|
} |
|
|
|
if (witnessCommit) |
|
|
|
block.witnessCommit = witnessCommit; |
|
|
|
return block; |
|
|
|
} |
|
|
|
static fromHex(hex) { |
|
|
@ -103,7 +101,7 @@ class Block { |
|
|
|
typeforce([{ getHash: types.Function }], transactions); |
|
|
|
if (transactions.length === 0) |
|
|
|
throw errorMerkleNoTxes; |
|
|
|
if (forWitness && !txesHaveWitness(transactions)) |
|
|
|
if (forWitness && !txesHaveWitnessCommit(transactions)) |
|
|
|
throw errorWitnessNotSegwit; |
|
|
|
const hashes = transactions.map(transaction => transaction.getHash(forWitness)); |
|
|
|
const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); |
|
|
@ -111,8 +109,34 @@ class Block { |
|
|
|
? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) |
|
|
|
: rootHash; |
|
|
|
} |
|
|
|
getWitnessCommit() { |
|
|
|
if (!txesHaveWitnessCommit(this.transactions)) |
|
|
|
return null; |
|
|
|
// The merkle root for the witness data is in an OP_RETURN output.
|
|
|
|
// There is no rule for the index of the output, so use filter to find it.
|
|
|
|
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
|
|
|
|
// If multiple commits are found, the output with highest index is assumed.
|
|
|
|
let witnessCommits = this.transactions[0].outs |
|
|
|
.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))) |
|
|
|
.map(out => out.script.slice(6, 38)); |
|
|
|
if (witnessCommits.length === 0) |
|
|
|
return null; |
|
|
|
// Use the commit with the highest output (should only be one though)
|
|
|
|
let result = witnessCommits[witnessCommits.length - 1]; |
|
|
|
if (!(result instanceof Buffer && result.length === 32)) |
|
|
|
return null; |
|
|
|
return result; |
|
|
|
} |
|
|
|
hasWitnessCommit() { |
|
|
|
return txesHaveWitness(this.transactions); |
|
|
|
if (this.witnessCommit instanceof Buffer && |
|
|
|
this.witnessCommit.length === 32) |
|
|
|
return true; |
|
|
|
if (this.getWitnessCommit() !== null) |
|
|
|
return true; |
|
|
|
return false; |
|
|
|
} |
|
|
|
hasWitness() { |
|
|
|
return anyTxHasWitness(this.transactions); |
|
|
|
} |
|
|
|
byteLength(headersOnly) { |
|
|
|
if (headersOnly || !this.transactions) |
|
|
@ -168,8 +192,13 @@ class Block { |
|
|
|
return this.toBuffer(headersOnly).toString('hex'); |
|
|
|
} |
|
|
|
checkTxRoots() { |
|
|
|
// If the Block has segwit transactions but no witness commit,
|
|
|
|
// there's no way it can be valid, so fail the check.
|
|
|
|
let hasWitnessCommit = this.hasWitnessCommit(); |
|
|
|
if (!hasWitnessCommit && this.hasWitness()) |
|
|
|
return false; |
|
|
|
return this.__checkMerkleRoot() && |
|
|
|
(this.hasWitnessCommit() ? this.__checkWitnessCommit() : true); |
|
|
|
(hasWitnessCommit ? this.__checkWitnessCommit() : true); |
|
|
|
} |
|
|
|
checkMerkleRoot() { |
|
|
|
console.warn('Deprecation Warning: Block method checkMerkleRoot will be ' + |
|
|
|