diff --git a/src/block.js b/src/block.js index 6f4721a..cb593ac 100644 --- a/src/block.js +++ b/src/block.js @@ -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 ' + diff --git a/ts_src/block.ts b/ts_src/block.ts index e5ea071..e3d1fc5 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -9,11 +9,9 @@ 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: Array<Transaction>): boolean { - return transactions !== undefined && - transactions instanceof Array && +function txesHaveWitnessCommit (transactions: Array<Transaction>): boolean { + return transactions instanceof Array && transactions[0] && transactions[0].ins && transactions[0].ins instanceof Array && @@ -23,6 +21,19 @@ function txesHaveWitness (transactions: Array<Transaction>): boolean { transactions[0].ins[0].witness.length > 0 } +function anyTxHasWitness (transactions: Array<Transaction>): boolean { + 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 + ) + ) +} + export class Block { version: number prevHash?: Buffer @@ -45,7 +56,7 @@ export class Block { } static fromBuffer (buffer: Buffer): Block { - if (buffer.length < 80) throw errorBufferTooSmall + if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)') let offset: number = 0 const readSlice = (n: number): Buffer => { @@ -95,19 +106,9 @@ export class Block { 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 } @@ -127,7 +128,7 @@ export class Block { static calculateMerkleRoot (transactions: Array<Transaction>, forWitness?: boolean): Buffer { typeforce([{ getHash: types.Function }], transactions) if (transactions.length === 0) throw errorMerkleNoTxes - if (forWitness && !txesHaveWitness(transactions)) throw errorWitnessNotSegwit + if (forWitness && !txesHaveWitnessCommit(transactions)) throw errorWitnessNotSegwit const hashes = transactions.map(transaction => transaction.getHash(forWitness!)) @@ -138,8 +139,33 @@ export class Block { : rootHash } + getWitnessCommit (): Buffer | null { + 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 (): boolean { - return txesHaveWitness(this.transactions!) + if (this.witnessCommit instanceof Buffer && + this.witnessCommit.length === 32) return true + if (this.getWitnessCommit() !== null) return true + return false + } + + hasWitness (): boolean { + return anyTxHasWitness(this.transactions!) } byteLength (headersOnly: boolean): number { @@ -209,8 +235,12 @@ export class Block { } checkTxRoots (): boolean { + // 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 (): boolean { diff --git a/types/block.d.ts b/types/block.d.ts index 3ff2926..8b4f2f1 100644 --- a/types/block.d.ts +++ b/types/block.d.ts @@ -14,7 +14,9 @@ export declare class Block { static fromHex(hex: string): Block; static calculateTarget(bits: number): Buffer; static calculateMerkleRoot(transactions: Array<Transaction>, forWitness?: boolean): Buffer; + getWitnessCommit(): Buffer | null; hasWitnessCommit(): boolean; + hasWitness(): boolean; byteLength(headersOnly: boolean): number; getHash(): Buffer; getId(): string;