Browse Source

Use Prettier to make JS easier to read/audit

trimSpace
junderw 6 years ago
parent
commit
e28e04427e
No known key found for this signature in database GPG Key ID: B256185D3A971908
  1. 5
      package.json
  2. 39
      src/address.js
  3. 134
      src/block.js
  4. 10
      src/bufferutils.js
  5. 70
      src/classify.js
  6. 7
      src/crypto.js
  7. 43
      src/ecpair.js
  8. 26
      src/index.js
  9. 4
      src/networks.js
  10. 32
      src/payments/embed.js
  11. 18
      src/payments/index.js
  12. 7
      src/payments/lazy.js
  13. 92
      src/payments/p2ms.js
  14. 35
      src/payments/p2pk.js
  15. 70
      src/payments/p2pkh.js
  16. 85
      src/payments/p2sh.js
  17. 70
      src/payments/p2wpkh.js
  18. 91
      src/payments/p2wsh.js
  19. 76
      src/script.js
  20. 16
      src/script_number.js
  21. 25
      src/script_signature.js
  22. 8
      src/templates/multisig/index.js
  23. 18
      src/templates/multisig/input.js
  24. 37
      src/templates/multisig/output.js
  25. 6
      src/templates/nulldata.js
  26. 8
      src/templates/pubkey/index.js
  27. 9
      src/templates/pubkey/input.js
  28. 14
      src/templates/pubkey/output.js
  29. 8
      src/templates/pubkeyhash/index.js
  30. 12
      src/templates/pubkeyhash/input.js
  31. 14
      src/templates/pubkeyhash/output.js
  32. 8
      src/templates/scripthash/index.js
  33. 54
      src/templates/scripthash/input.js
  34. 14
      src/templates/scripthash/output.js
  35. 6
      src/templates/witnesscommitment/index.js
  36. 16
      src/templates/witnesscommitment/output.js
  37. 8
      src/templates/witnesspubkeyhash/index.js
  38. 12
      src/templates/witnesspubkeyhash/input.js
  39. 14
      src/templates/witnesspubkeyhash/output.js
  40. 8
      src/templates/witnessscripthash/index.js
  41. 39
      src/templates/witnessscripthash/input.js
  42. 14
      src/templates/witnessscripthash/output.js
  43. 144
      src/transaction.js
  44. 287
      src/transaction_builder.js
  45. 4
      src/types.js

5
package.json

@ -15,11 +15,13 @@
"bitcoinjs" "bitcoinjs"
], ],
"scripts": { "scripts": {
"build": "tsc -p ./tsconfig.json", "build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs",
"clean": "rm -rf src/",
"coverage-report": "npm run build && npm run nobuild:coverage-report", "coverage-report": "npm run build && npm run nobuild:coverage-report",
"coverage-html": "npm run build && npm run nobuild:coverage-html", "coverage-html": "npm run build && npm run nobuild:coverage-html",
"coverage": "npm run build && npm run nobuild:coverage", "coverage": "npm run build && npm run nobuild:coverage",
"format": "npm run prettier -- --write", "format": "npm run prettier -- --write",
"formatjs": "npm run prettierjs -- --write > /dev/null 2>&1",
"format:ci": "npm run prettier -- --check", "format:ci": "npm run prettier -- --check",
"gitdiff:ci": "npm run build && git diff --exit-code", "gitdiff:ci": "npm run build && git diff --exit-code",
"integration": "npm run build && npm run nobuild:integration", "integration": "npm run build && npm run nobuild:integration",
@ -30,6 +32,7 @@
"nobuild:integration": "mocha --timeout 50000 test/integration/", "nobuild:integration": "mocha --timeout 50000 test/integration/",
"nobuild:unit": "mocha", "nobuild:unit": "mocha",
"prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore", "prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore",
"prettierjs": "prettier 'src/**/*.js' --ignore-path ./.prettierignore",
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage",
"unit": "npm run build && npm run nobuild:unit" "unit": "npm run build && npm run nobuild:unit"
}, },

39
src/address.js

@ -1,19 +1,17 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const networks = require("./networks"); const networks = require('./networks');
const payments = require("./payments"); const payments = require('./payments');
const bscript = require("./script"); const bscript = require('./script');
const types = require("./types"); const types = require('./types');
const bech32 = require('bech32'); const bech32 = require('bech32');
const bs58check = require('bs58check'); const bs58check = require('bs58check');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
function fromBase58Check(address) { function fromBase58Check(address) {
const payload = bs58check.decode(address); const payload = bs58check.decode(address);
// TODO: 4.0.0, move to "toOutputScript" // TODO: 4.0.0, move to "toOutputScript"
if (payload.length < 21) if (payload.length < 21) throw new TypeError(address + ' is too short');
throw new TypeError(address + ' is too short'); if (payload.length > 21) throw new TypeError(address + ' is too long');
if (payload.length > 21)
throw new TypeError(address + ' is too long');
const version = payload.readUInt8(0); const version = payload.readUInt8(0);
const hash = payload.slice(1); const hash = payload.slice(1);
return { version, hash }; return { version, hash };
@ -48,20 +46,16 @@ function fromOutputScript(output, network) {
network = network || networks.bitcoin; network = network || networks.bitcoin;
try { try {
return payments.p2pkh({ output, network }).address; return payments.p2pkh({ output, network }).address;
} } catch (e) {}
catch (e) { }
try { try {
return payments.p2sh({ output, network }).address; return payments.p2sh({ output, network }).address;
} } catch (e) {}
catch (e) { }
try { try {
return payments.p2wpkh({ output, network }).address; return payments.p2wpkh({ output, network }).address;
} } catch (e) {}
catch (e) { }
try { try {
return payments.p2wsh({ output, network }).address; return payments.p2wsh({ output, network }).address;
} } catch (e) {}
catch (e) { }
throw new Error(bscript.toASM(output) + ' has no matching Address'); throw new Error(bscript.toASM(output) + ' has no matching Address');
} }
exports.fromOutputScript = fromOutputScript; exports.fromOutputScript = fromOutputScript;
@ -71,19 +65,16 @@ function toOutputScript(address, network) {
let decodeBech32; let decodeBech32;
try { try {
decodeBase58 = fromBase58Check(address); decodeBase58 = fromBase58Check(address);
} } catch (e) {}
catch (e) { }
if (decodeBase58) { if (decodeBase58) {
if (decodeBase58.version === network.pubKeyHash) if (decodeBase58.version === network.pubKeyHash)
return payments.p2pkh({ hash: decodeBase58.hash }).output; return payments.p2pkh({ hash: decodeBase58.hash }).output;
if (decodeBase58.version === network.scriptHash) if (decodeBase58.version === network.scriptHash)
return payments.p2sh({ hash: decodeBase58.hash }).output; return payments.p2sh({ hash: decodeBase58.hash }).output;
} } else {
else {
try { try {
decodeBech32 = fromBech32(address); decodeBech32 = fromBech32(address);
} } catch (e) {}
catch (e) { }
if (decodeBech32) { if (decodeBech32) {
if (decodeBech32.prefix !== network.bech32) if (decodeBech32.prefix !== network.bech32)
throw new Error(address + ' has an invalid prefix'); throw new Error(address + ' has an invalid prefix');

134
src/block.js

@ -1,14 +1,18 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bufferutils_1 = require("./bufferutils"); const bufferutils_1 = require('./bufferutils');
const bcrypto = require("./crypto"); const bcrypto = require('./crypto');
const transaction_1 = require("./transaction"); const transaction_1 = require('./transaction');
const types = require("./types"); const types = require('./types');
const fastMerkleRoot = require('merkle-lib/fastRoot'); const fastMerkleRoot = require('merkle-lib/fastRoot');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const varuint = require('varuint-bitcoin'); const varuint = require('varuint-bitcoin');
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); const errorMerkleNoTxes = new TypeError(
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); 'Cannot compute merkle root for zero transactions',
);
const errorWitnessNotSegwit = new TypeError(
'Cannot compute witness commit for non-segwit block',
);
class Block { class Block {
constructor() { constructor() {
this.version = 1; this.version = 1;
@ -21,10 +25,9 @@ class Block {
this.transactions = undefined; this.transactions = undefined;
} }
static fromBuffer(buffer) { static fromBuffer(buffer) {
if (buffer.length < 80) if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)');
throw new Error('Buffer too small (< 80 bytes)');
let offset = 0; let offset = 0;
const readSlice = (n) => { const readSlice = n => {
offset += n; offset += n;
return buffer.slice(offset - n, offset); return buffer.slice(offset - n, offset);
}; };
@ -45,15 +48,17 @@ class Block {
block.timestamp = readUInt32(); block.timestamp = readUInt32();
block.bits = readUInt32(); block.bits = readUInt32();
block.nonce = readUInt32(); block.nonce = readUInt32();
if (buffer.length === 80) if (buffer.length === 80) return block;
return block;
const readVarInt = () => { const readVarInt = () => {
const vi = varuint.decode(buffer, offset); const vi = varuint.decode(buffer, offset);
offset += varuint.decode.bytes; offset += varuint.decode.bytes;
return vi; return vi;
}; };
const readTransaction = () => { const readTransaction = () => {
const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true); const tx = transaction_1.Transaction.fromBuffer(
buffer.slice(offset),
true,
);
offset += tx.byteLength(); offset += tx.byteLength();
return tx; return tx;
}; };
@ -65,8 +70,7 @@ class Block {
} }
const witnessCommit = block.getWitnessCommit(); const witnessCommit = block.getWitnessCommit();
// This Block contains a witness commit // This Block contains a witness commit
if (witnessCommit) if (witnessCommit) block.witnessCommit = witnessCommit;
block.witnessCommit = witnessCommit;
return block; return block;
} }
static fromHex(hex) { static fromHex(hex) {
@ -81,49 +85,55 @@ class Block {
} }
static calculateMerkleRoot(transactions, forWitness) { static calculateMerkleRoot(transactions, forWitness) {
typeforce([{ getHash: types.Function }], transactions); typeforce([{ getHash: types.Function }], transactions);
if (transactions.length === 0) if (transactions.length === 0) throw errorMerkleNoTxes;
throw errorMerkleNoTxes;
if (forWitness && !txesHaveWitnessCommit(transactions)) if (forWitness && !txesHaveWitnessCommit(transactions))
throw errorWitnessNotSegwit; throw errorWitnessNotSegwit;
const hashes = transactions.map(transaction => transaction.getHash(forWitness)); const hashes = transactions.map(transaction =>
transaction.getHash(forWitness),
);
const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); const rootHash = fastMerkleRoot(hashes, bcrypto.hash256);
return forWitness return forWitness
? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) ? bcrypto.hash256(
Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]),
)
: rootHash; : rootHash;
} }
getWitnessCommit() { getWitnessCommit() {
if (!txesHaveWitnessCommit(this.transactions)) if (!txesHaveWitnessCommit(this.transactions)) return null;
return null;
// The merkle root for the witness data is in an OP_RETURN output. // 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. // 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 // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
// If multiple commits are found, the output with highest index is assumed. // If multiple commits are found, the output with highest index is assumed.
const witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38)); const witnessCommits = this.transactions[0].outs
if (witnessCommits.length === 0) .filter(out =>
return null; 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) // Use the commit with the highest output (should only be one though)
const result = witnessCommits[witnessCommits.length - 1]; const result = witnessCommits[witnessCommits.length - 1];
if (!(result instanceof Buffer && result.length === 32)) if (!(result instanceof Buffer && result.length === 32)) return null;
return null;
return result; return result;
} }
hasWitnessCommit() { hasWitnessCommit() {
if (this.witnessCommit instanceof Buffer && if (
this.witnessCommit.length === 32) this.witnessCommit instanceof Buffer &&
return true; this.witnessCommit.length === 32
if (this.getWitnessCommit() !== null) )
return true; return true;
if (this.getWitnessCommit() !== null) return true;
return false; return false;
} }
hasWitness() { hasWitness() {
return anyTxHasWitness(this.transactions); return anyTxHasWitness(this.transactions);
} }
byteLength(headersOnly) { byteLength(headersOnly) {
if (headersOnly || !this.transactions) if (headersOnly || !this.transactions) return 80;
return 80; return (
return (80 + 80 +
varuint.encodingLength(this.transactions.length) + varuint.encodingLength(this.transactions.length) +
this.transactions.reduce((a, x) => a + x.byteLength(), 0)); this.transactions.reduce((a, x) => a + x.byteLength(), 0)
);
} }
getHash() { getHash() {
return bcrypto.hash256(this.toBuffer(true)); return bcrypto.hash256(this.toBuffer(true));
@ -140,15 +150,15 @@ class Block {
toBuffer(headersOnly) { toBuffer(headersOnly) {
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
let offset = 0; let offset = 0;
const writeSlice = (slice) => { const writeSlice = slice => {
slice.copy(buffer, offset); slice.copy(buffer, offset);
offset += slice.length; offset += slice.length;
}; };
const writeInt32 = (i) => { const writeInt32 = i => {
buffer.writeInt32LE(i, offset); buffer.writeInt32LE(i, offset);
offset += 4; offset += 4;
}; };
const writeUInt32 = (i) => { const writeUInt32 = i => {
buffer.writeUInt32LE(i, offset); buffer.writeUInt32LE(i, offset);
offset += 4; offset += 4;
}; };
@ -158,8 +168,7 @@ class Block {
writeUInt32(this.timestamp); writeUInt32(this.timestamp);
writeUInt32(this.bits); writeUInt32(this.bits);
writeUInt32(this.nonce); writeUInt32(this.nonce);
if (headersOnly || !this.transactions) if (headersOnly || !this.transactions) return buffer;
return buffer;
varuint.encode(this.transactions.length, buffer, offset); varuint.encode(this.transactions.length, buffer, offset);
offset += varuint.encode.bytes; offset += varuint.encode.bytes;
this.transactions.forEach(tx => { this.transactions.forEach(tx => {
@ -176,10 +185,11 @@ class Block {
// If the Block has segwit transactions but no witness commit, // If the Block has segwit transactions but no witness commit,
// there's no way it can be valid, so fail the check. // there's no way it can be valid, so fail the check.
const hasWitnessCommit = this.hasWitnessCommit(); const hasWitnessCommit = this.hasWitnessCommit();
if (!hasWitnessCommit && this.hasWitness()) if (!hasWitnessCommit && this.hasWitness()) return false;
return false; return (
return (this.__checkMerkleRoot() && this.__checkMerkleRoot() &&
(hasWitnessCommit ? this.__checkWitnessCommit() : true)); (hasWitnessCommit ? this.__checkWitnessCommit() : true)
);
} }
checkProofOfWork() { checkProofOfWork() {
const hash = bufferutils_1.reverseBuffer(this.getHash()); const hash = bufferutils_1.reverseBuffer(this.getHash());
@ -187,36 +197,46 @@ class Block {
return hash.compare(target) <= 0; return hash.compare(target) <= 0;
} }
__checkMerkleRoot() { __checkMerkleRoot() {
if (!this.transactions) if (!this.transactions) throw errorMerkleNoTxes;
throw errorMerkleNoTxes;
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
return this.merkleRoot.compare(actualMerkleRoot) === 0; return this.merkleRoot.compare(actualMerkleRoot) === 0;
} }
__checkWitnessCommit() { __checkWitnessCommit() {
if (!this.transactions) if (!this.transactions) throw errorMerkleNoTxes;
throw errorMerkleNoTxes; if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit;
if (!this.hasWitnessCommit()) const actualWitnessCommit = Block.calculateMerkleRoot(
throw errorWitnessNotSegwit; this.transactions,
const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true); true,
);
return this.witnessCommit.compare(actualWitnessCommit) === 0; return this.witnessCommit.compare(actualWitnessCommit) === 0;
} }
} }
exports.Block = Block; exports.Block = Block;
function txesHaveWitnessCommit(transactions) { function txesHaveWitnessCommit(transactions) {
return (transactions instanceof Array && return (
transactions instanceof Array &&
transactions[0] && transactions[0] &&
transactions[0].ins && transactions[0].ins &&
transactions[0].ins instanceof Array && transactions[0].ins instanceof Array &&
transactions[0].ins[0] && transactions[0].ins[0] &&
transactions[0].ins[0].witness && transactions[0].ins[0].witness &&
transactions[0].ins[0].witness instanceof Array && transactions[0].ins[0].witness instanceof Array &&
transactions[0].ins[0].witness.length > 0); transactions[0].ins[0].witness.length > 0
);
} }
function anyTxHasWitness(transactions) { function anyTxHasWitness(transactions) {
return (transactions instanceof Array && return (
transactions.some(tx => typeof tx === 'object' && transactions instanceof Array &&
transactions.some(
tx =>
typeof tx === 'object' &&
tx.ins instanceof Array && tx.ins instanceof Array &&
tx.ins.some(input => typeof input === 'object' && tx.ins.some(
input =>
typeof input === 'object' &&
input.witness instanceof Array && input.witness instanceof Array &&
input.witness.length > 0))); input.witness.length > 0,
),
)
);
} }

10
src/bufferutils.js

@ -1,13 +1,12 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
// https://github.com/feross/buffer/blob/master/index.js#L1127 // https://github.com/feross/buffer/blob/master/index.js#L1127
function verifuint(value, max) { function verifuint(value, max) {
if (typeof value !== 'number') if (typeof value !== 'number')
throw new Error('cannot write a non-number as a number'); throw new Error('cannot write a non-number as a number');
if (value < 0) if (value < 0)
throw new Error('specified a negative value for writing an unsigned value'); throw new Error('specified a negative value for writing an unsigned value');
if (value > max) if (value > max) throw new Error('RangeError: value out of range');
throw new Error('RangeError: value out of range');
if (Math.floor(value) !== value) if (Math.floor(value) !== value)
throw new Error('value has a fractional component'); throw new Error('value has a fractional component');
} }
@ -27,8 +26,7 @@ function writeUInt64LE(buffer, value, offset) {
} }
exports.writeUInt64LE = writeUInt64LE; exports.writeUInt64LE = writeUInt64LE;
function reverseBuffer(buffer) { function reverseBuffer(buffer) {
if (buffer.length < 1) if (buffer.length < 1) return buffer;
return buffer;
let j = buffer.length - 1; let j = buffer.length - 1;
let tmp = 0; let tmp = 0;
for (let i = 0; i < buffer.length / 2; i++) { for (let i = 0; i < buffer.length / 2; i++) {

70
src/classify.js

@ -1,14 +1,14 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const script_1 = require("./script"); const script_1 = require('./script');
const multisig = require("./templates/multisig"); const multisig = require('./templates/multisig');
const nullData = require("./templates/nulldata"); const nullData = require('./templates/nulldata');
const pubKey = require("./templates/pubkey"); const pubKey = require('./templates/pubkey');
const pubKeyHash = require("./templates/pubkeyhash"); const pubKeyHash = require('./templates/pubkeyhash');
const scriptHash = require("./templates/scripthash"); const scriptHash = require('./templates/scripthash');
const witnessCommitment = require("./templates/witnesscommitment"); const witnessCommitment = require('./templates/witnesscommitment');
const witnessPubKeyHash = require("./templates/witnesspubkeyhash"); const witnessPubKeyHash = require('./templates/witnesspubkeyhash');
const witnessScriptHash = require("./templates/witnessscripthash"); const witnessScriptHash = require('./templates/witnessscripthash');
const types = { const types = {
P2MS: 'multisig', P2MS: 'multisig',
NONSTANDARD: 'nonstandard', NONSTANDARD: 'nonstandard',
@ -22,52 +22,36 @@ const types = {
}; };
exports.types = types; exports.types = types;
function classifyOutput(script) { function classifyOutput(script) {
if (witnessPubKeyHash.output.check(script)) if (witnessPubKeyHash.output.check(script)) return types.P2WPKH;
return types.P2WPKH; if (witnessScriptHash.output.check(script)) return types.P2WSH;
if (witnessScriptHash.output.check(script)) if (pubKeyHash.output.check(script)) return types.P2PKH;
return types.P2WSH; if (scriptHash.output.check(script)) return types.P2SH;
if (pubKeyHash.output.check(script))
return types.P2PKH;
if (scriptHash.output.check(script))
return types.P2SH;
// XXX: optimization, below functions .decompile before use // XXX: optimization, below functions .decompile before use
const chunks = script_1.decompile(script); const chunks = script_1.decompile(script);
if (!chunks) if (!chunks) throw new TypeError('Invalid script');
throw new TypeError('Invalid script'); if (multisig.output.check(chunks)) return types.P2MS;
if (multisig.output.check(chunks)) if (pubKey.output.check(chunks)) return types.P2PK;
return types.P2MS; if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT;
if (pubKey.output.check(chunks)) if (nullData.output.check(chunks)) return types.NULLDATA;
return types.P2PK;
if (witnessCommitment.output.check(chunks))
return types.WITNESS_COMMITMENT;
if (nullData.output.check(chunks))
return types.NULLDATA;
return types.NONSTANDARD; return types.NONSTANDARD;
} }
exports.output = classifyOutput; exports.output = classifyOutput;
function classifyInput(script, allowIncomplete) { function classifyInput(script, allowIncomplete) {
// XXX: optimization, below functions .decompile before use // XXX: optimization, below functions .decompile before use
const chunks = script_1.decompile(script); const chunks = script_1.decompile(script);
if (!chunks) if (!chunks) throw new TypeError('Invalid script');
throw new TypeError('Invalid script'); if (pubKeyHash.input.check(chunks)) return types.P2PKH;
if (pubKeyHash.input.check(chunks)) if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH;
return types.P2PKH; if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS;
if (scriptHash.input.check(chunks, allowIncomplete)) if (pubKey.input.check(chunks)) return types.P2PK;
return types.P2SH;
if (multisig.input.check(chunks, allowIncomplete))
return types.P2MS;
if (pubKey.input.check(chunks))
return types.P2PK;
return types.NONSTANDARD; return types.NONSTANDARD;
} }
exports.input = classifyInput; exports.input = classifyInput;
function classifyWitness(script, allowIncomplete) { function classifyWitness(script, allowIncomplete) {
// XXX: optimization, below functions .decompile before use // XXX: optimization, below functions .decompile before use
const chunks = script_1.decompile(script); const chunks = script_1.decompile(script);
if (!chunks) if (!chunks) throw new TypeError('Invalid script');
throw new TypeError('Invalid script'); if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH;
if (witnessPubKeyHash.input.check(chunks))
return types.P2WPKH;
if (witnessScriptHash.input.check(chunks, allowIncomplete)) if (witnessScriptHash.input.check(chunks, allowIncomplete))
return types.P2WSH; return types.P2WSH;
return types.NONSTANDARD; return types.NONSTANDARD;

7
src/crypto.js

@ -1,13 +1,12 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const createHash = require('create-hash'); const createHash = require('create-hash');
function ripemd160(buffer) { function ripemd160(buffer) {
try { try {
return createHash('rmd160') return createHash('rmd160')
.update(buffer) .update(buffer)
.digest(); .digest();
} } catch (err) {
catch (err) {
return createHash('ripemd160') return createHash('ripemd160')
.update(buffer) .update(buffer)
.digest(); .digest();

43
src/ecpair.js

@ -1,43 +1,40 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const NETWORKS = require("./networks"); const NETWORKS = require('./networks');
const types = require("./types"); const types = require('./types');
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const randomBytes = require('randombytes'); const randomBytes = require('randombytes');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const wif = require('wif'); const wif = require('wif');
const isOptions = typeforce.maybe(typeforce.compile({ const isOptions = typeforce.maybe(
typeforce.compile({
compressed: types.maybe(types.Boolean), compressed: types.maybe(types.Boolean),
network: types.maybe(types.Network), network: types.maybe(types.Network),
})); }),
);
class ECPair { class ECPair {
constructor(__D, __Q, options) { constructor(__D, __Q, options) {
this.__D = __D; this.__D = __D;
this.__Q = __Q; this.__Q = __Q;
if (options === undefined) if (options === undefined) options = {};
options = {};
this.compressed = this.compressed =
options.compressed === undefined ? true : options.compressed; options.compressed === undefined ? true : options.compressed;
this.network = options.network || NETWORKS.bitcoin; this.network = options.network || NETWORKS.bitcoin;
if (__Q !== undefined) if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed);
this.__Q = ecc.pointCompress(__Q, this.compressed);
} }
get privateKey() { get privateKey() {
return this.__D; return this.__D;
} }
get publicKey() { get publicKey() {
if (!this.__Q) if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__D, this.compressed);
this.__Q = ecc.pointFromScalar(this.__D, this.compressed);
return this.__Q; return this.__Q;
} }
toWIF() { toWIF() {
if (!this.__D) if (!this.__D) throw new Error('Missing private key');
throw new Error('Missing private key');
return wif.encode(this.network.wif, this.__D, this.compressed); return wif.encode(this.network.wif, this.__D, this.compressed);
} }
sign(hash) { sign(hash) {
if (!this.__D) if (!this.__D) throw new Error('Missing private key');
throw new Error('Missing private key');
return ecc.sign(hash, this.__D); return ecc.sign(hash, this.__D);
} }
verify(hash, signature) { verify(hash, signature) {
@ -64,18 +61,15 @@ function fromWIF(wifString, network) {
// list of networks? // list of networks?
if (types.Array(network)) { if (types.Array(network)) {
network = network network = network
.filter((x) => { .filter(x => {
return version === x.wif; return version === x.wif;
}) })
.pop(); .pop();
if (!network) if (!network) throw new Error('Unknown network version');
throw new Error('Unknown network version');
// otherwise, assume a network object (or default to bitcoin) // otherwise, assume a network object (or default to bitcoin)
} } else {
else {
network = network || NETWORKS.bitcoin; network = network || NETWORKS.bitcoin;
if (version !== network.wif) if (version !== network.wif) throw new Error('Invalid network version');
throw new Error('Invalid network version');
} }
return fromPrivateKey(decoded.privateKey, { return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed, compressed: decoded.compressed,
@ -85,8 +79,7 @@ function fromWIF(wifString, network) {
exports.fromWIF = fromWIF; exports.fromWIF = fromWIF;
function makeRandom(options) { function makeRandom(options) {
typeforce(isOptions, options); typeforce(isOptions, options);
if (options === undefined) if (options === undefined) options = {};
options = {};
const rng = options.rng || randomBytes; const rng = options.rng || randomBytes;
let d; let d;
do { do {

26
src/index.js

@ -1,24 +1,24 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bip32 = require("bip32"); const bip32 = require('bip32');
exports.bip32 = bip32; exports.bip32 = bip32;
const address = require("./address"); const address = require('./address');
exports.address = address; exports.address = address;
const crypto = require("./crypto"); const crypto = require('./crypto');
exports.crypto = crypto; exports.crypto = crypto;
const ECPair = require("./ecpair"); const ECPair = require('./ecpair');
exports.ECPair = ECPair; exports.ECPair = ECPair;
const networks = require("./networks"); const networks = require('./networks');
exports.networks = networks; exports.networks = networks;
const payments = require("./payments"); const payments = require('./payments');
exports.payments = payments; exports.payments = payments;
const script = require("./script"); const script = require('./script');
exports.script = script; exports.script = script;
var block_1 = require("./block"); var block_1 = require('./block');
exports.Block = block_1.Block; exports.Block = block_1.Block;
var script_1 = require("./script"); var script_1 = require('./script');
exports.opcodes = script_1.OPS; exports.opcodes = script_1.OPS;
var transaction_1 = require("./transaction"); var transaction_1 = require('./transaction');
exports.Transaction = transaction_1.Transaction; exports.Transaction = transaction_1.Transaction;
var transaction_builder_1 = require("./transaction_builder"); var transaction_builder_1 = require('./transaction_builder');
exports.TransactionBuilder = transaction_builder_1.TransactionBuilder; exports.TransactionBuilder = transaction_builder_1.TransactionBuilder;

4
src/networks.js

@ -1,5 +1,5 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
exports.bitcoin = { exports.bitcoin = {
messagePrefix: '\x18Bitcoin Signed Message:\n', messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc', bech32: 'bc',

32
src/payments/embed.js

@ -1,45 +1,43 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
function stacksEqual(a, b) { function stacksEqual(a, b) {
if (a.length !== b.length) if (a.length !== b.length) return false;
return false;
return a.every((x, i) => { return a.every((x, i) => {
return x.equals(b[i]); return x.equals(b[i]);
}); });
} }
// output: OP_RETURN ... // output: OP_RETURN ...
function p2data(a, opts) { function p2data(a, opts) {
if (!a.data && !a.output) if (!a.data && !a.output) throw new TypeError('Not enough data');
throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
typef({ typef(
{
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer), output: typef.maybe(typef.Buffer),
data: typef.maybe(typef.arrayOf(typef.Buffer)), data: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a); },
a,
);
const network = a.network || networks_1.bitcoin; const network = a.network || networks_1.bitcoin;
const o = { network }; const o = { network };
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!a.data) if (!a.data) return;
return;
return bscript.compile([OPS.OP_RETURN].concat(a.data)); return bscript.compile([OPS.OP_RETURN].concat(a.data));
}); });
lazy.prop(o, 'data', () => { lazy.prop(o, 'data', () => {
if (!a.output) if (!a.output) return;
return;
return bscript.decompile(a.output).slice(1); return bscript.decompile(a.output).slice(1);
}); });
// extended validation // extended validation
if (opts.validate) { if (opts.validate) {
if (a.output) { if (a.output) {
const chunks = bscript.decompile(a.output); const chunks = bscript.decompile(a.output);
if (chunks[0] !== OPS.OP_RETURN) if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid');
throw new TypeError('Output is invalid');
if (!chunks.slice(1).every(typef.Buffer)) if (!chunks.slice(1).every(typef.Buffer))
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
if (a.data && !stacksEqual(a.data, o.data)) if (a.data && !stacksEqual(a.data, o.data))

18
src/payments/index.js

@ -1,18 +1,18 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const embed_1 = require("./embed"); const embed_1 = require('./embed');
exports.embed = embed_1.p2data; exports.embed = embed_1.p2data;
const p2ms_1 = require("./p2ms"); const p2ms_1 = require('./p2ms');
exports.p2ms = p2ms_1.p2ms; exports.p2ms = p2ms_1.p2ms;
const p2pk_1 = require("./p2pk"); const p2pk_1 = require('./p2pk');
exports.p2pk = p2pk_1.p2pk; exports.p2pk = p2pk_1.p2pk;
const p2pkh_1 = require("./p2pkh"); const p2pkh_1 = require('./p2pkh');
exports.p2pkh = p2pkh_1.p2pkh; exports.p2pkh = p2pkh_1.p2pkh;
const p2sh_1 = require("./p2sh"); const p2sh_1 = require('./p2sh');
exports.p2sh = p2sh_1.p2sh; exports.p2sh = p2sh_1.p2sh;
const p2wpkh_1 = require("./p2wpkh"); const p2wpkh_1 = require('./p2wpkh');
exports.p2wpkh = p2wpkh_1.p2wpkh; exports.p2wpkh = p2wpkh_1.p2wpkh;
const p2wsh_1 = require("./p2wsh"); const p2wsh_1 = require('./p2wsh');
exports.p2wsh = p2wsh_1.p2wsh; exports.p2wsh = p2wsh_1.p2wsh;
// TODO // TODO
// witness commitment // witness commitment

7
src/payments/lazy.js

@ -1,5 +1,5 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
function prop(object, name, f) { function prop(object, name, f) {
Object.defineProperty(object, name, { Object.defineProperty(object, name, {
configurable: true, configurable: true,
@ -23,8 +23,7 @@ exports.prop = prop;
function value(f) { function value(f) {
let _value; let _value;
return () => { return () => {
if (_value !== undefined) if (_value !== undefined) return _value;
return _value;
_value = f(); _value = f();
return _value; return _value;
}; };

92
src/payments/p2ms.js

@ -1,15 +1,14 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const typef = require('typeforce'); const typef = require('typeforce');
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
function stacksEqual(a, b) { function stacksEqual(a, b) {
if (a.length !== b.length) if (a.length !== b.length) return false;
return false;
return a.every((x, i) => { return a.every((x, i) => {
return x.equals(b[i]); return x.equals(b[i]);
}); });
@ -17,17 +16,22 @@ function stacksEqual(a, b) {
// input: OP_0 [signatures ...] // input: OP_0 [signatures ...]
// output: m [pubKeys ...] n OP_CHECKMULTISIG // output: m [pubKeys ...] n OP_CHECKMULTISIG
function p2ms(a, opts) { function p2ms(a, opts) {
if (!a.input && if (
!a.input &&
!a.output && !a.output &&
!(a.pubkeys && a.m !== undefined) && !(a.pubkeys && a.m !== undefined) &&
!a.signatures) !a.signatures
)
throw new TypeError('Not enough data'); throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
function isAcceptableSignature(x) { function isAcceptableSignature(x) {
return (bscript.isCanonicalScriptSignature(x) || return (
(opts.allowIncomplete && x === OPS.OP_0) !== undefined); bscript.isCanonicalScriptSignature(x) ||
(opts.allowIncomplete && x === OPS.OP_0) !== undefined
);
} }
typef({ typef(
{
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
m: typef.maybe(typef.Number), m: typef.maybe(typef.Number),
n: typef.maybe(typef.Number), n: typef.maybe(typef.Number),
@ -35,14 +39,15 @@ function p2ms(a, opts) {
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
}, a); },
a,
);
const network = a.network || networks_1.bitcoin; const network = a.network || networks_1.bitcoin;
const o = { network }; const o = { network };
let chunks = []; let chunks = [];
let decoded = false; let decoded = false;
function decode(output) { function decode(output) {
if (decoded) if (decoded) return;
return;
decoded = true; decoded = true;
chunks = bscript.decompile(output); chunks = bscript.decompile(output);
o.m = chunks[0] - OP_INT_BASE; o.m = chunks[0] - OP_INT_BASE;
@ -50,52 +55,49 @@ function p2ms(a, opts) {
o.pubkeys = chunks.slice(1, -2); o.pubkeys = chunks.slice(1, -2);
} }
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!a.m) if (!a.m) return;
return; if (!o.n) return;
if (!o.n) if (!a.pubkeys) return;
return; return bscript.compile(
if (!a.pubkeys) [].concat(
return; OP_INT_BASE + a.m,
return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG)); a.pubkeys,
OP_INT_BASE + o.n,
OPS.OP_CHECKMULTISIG,
),
);
}); });
lazy.prop(o, 'm', () => { lazy.prop(o, 'm', () => {
if (!o.output) if (!o.output) return;
return;
decode(o.output); decode(o.output);
return o.m; return o.m;
}); });
lazy.prop(o, 'n', () => { lazy.prop(o, 'n', () => {
if (!o.pubkeys) if (!o.pubkeys) return;
return;
return o.pubkeys.length; return o.pubkeys.length;
}); });
lazy.prop(o, 'pubkeys', () => { lazy.prop(o, 'pubkeys', () => {
if (!a.output) if (!a.output) return;
return;
decode(a.output); decode(a.output);
return o.pubkeys; return o.pubkeys;
}); });
lazy.prop(o, 'signatures', () => { lazy.prop(o, 'signatures', () => {
if (!a.input) if (!a.input) return;
return;
return bscript.decompile(a.input).slice(1); return bscript.decompile(a.input).slice(1);
}); });
lazy.prop(o, 'input', () => { lazy.prop(o, 'input', () => {
if (!a.signatures) if (!a.signatures) return;
return;
return bscript.compile([OPS.OP_0].concat(a.signatures)); return bscript.compile([OPS.OP_0].concat(a.signatures));
}); });
lazy.prop(o, 'witness', () => { lazy.prop(o, 'witness', () => {
if (!o.input) if (!o.input) return;
return;
return []; return [];
}); });
// extended validation // extended validation
if (opts.validate) { if (opts.validate) {
if (a.output) { if (a.output) {
decode(a.output); decode(a.output);
if (!typef.Number(chunks[0])) if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid');
throw new TypeError('Output is invalid');
if (!typef.Number(chunks[chunks.length - 2])) if (!typef.Number(chunks[chunks.length - 2]))
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
@ -104,10 +106,8 @@ function p2ms(a, opts) {
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
if (!o.pubkeys.every(x => ecc.isPoint(x))) if (!o.pubkeys.every(x => ecc.isPoint(x)))
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
if (a.m !== undefined && a.m !== o.m) if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch');
throw new TypeError('m mismatch'); if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch');
if (a.n !== undefined && a.n !== o.n)
throw new TypeError('n mismatch');
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys))
throw new TypeError('Pubkeys mismatch'); throw new TypeError('Pubkeys mismatch');
} }
@ -115,8 +115,7 @@ function p2ms(a, opts) {
if (a.n !== undefined && a.n !== a.pubkeys.length) if (a.n !== undefined && a.n !== a.pubkeys.length)
throw new TypeError('Pubkey count mismatch'); throw new TypeError('Pubkey count mismatch');
o.n = a.pubkeys.length; o.n = a.pubkeys.length;
if (o.n < o.m) if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m');
throw new TypeError('Pubkey count cannot be less than m');
} }
if (a.signatures) { if (a.signatures) {
if (a.signatures.length < o.m) if (a.signatures.length < o.m)
@ -125,10 +124,11 @@ function p2ms(a, opts) {
throw new TypeError('Too many signatures provided'); throw new TypeError('Too many signatures provided');
} }
if (a.input) { if (a.input) {
if (a.input[0] !== OPS.OP_0) if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid');
throw new TypeError('Input is invalid'); if (
if (o.signatures.length === 0 || o.signatures.length === 0 ||
!o.signatures.every(isAcceptableSignature)) !o.signatures.every(isAcceptableSignature)
)
throw new TypeError('Input has invalid signature(s)'); throw new TypeError('Input has invalid signature(s)');
if (a.signatures && !stacksEqual(a.signatures, o.signatures)) if (a.signatures && !stacksEqual(a.signatures, o.signatures))
throw new TypeError('Signature mismatch'); throw new TypeError('Signature mismatch');

35
src/payments/p2pk.js

@ -1,8 +1,8 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
@ -12,41 +12,39 @@ function p2pk(a, opts) {
if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature)
throw new TypeError('Not enough data'); throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
typef({ typef(
{
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer), output: typef.maybe(typef.Buffer),
pubkey: typef.maybe(ecc.isPoint), pubkey: typef.maybe(ecc.isPoint),
signature: typef.maybe(bscript.isCanonicalScriptSignature), signature: typef.maybe(bscript.isCanonicalScriptSignature),
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
}, a); },
a,
);
const _chunks = lazy.value(() => { const _chunks = lazy.value(() => {
return bscript.decompile(a.input); return bscript.decompile(a.input);
}); });
const network = a.network || networks_1.bitcoin; const network = a.network || networks_1.bitcoin;
const o = { network }; const o = { network };
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!a.pubkey) if (!a.pubkey) return;
return;
return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]);
}); });
lazy.prop(o, 'pubkey', () => { lazy.prop(o, 'pubkey', () => {
if (!a.output) if (!a.output) return;
return;
return a.output.slice(1, -1); return a.output.slice(1, -1);
}); });
lazy.prop(o, 'signature', () => { lazy.prop(o, 'signature', () => {
if (!a.input) if (!a.input) return;
return;
return _chunks()[0]; return _chunks()[0];
}); });
lazy.prop(o, 'input', () => { lazy.prop(o, 'input', () => {
if (!a.signature) if (!a.signature) return;
return;
return bscript.compile([a.signature]); return bscript.compile([a.signature]);
}); });
lazy.prop(o, 'witness', () => { lazy.prop(o, 'witness', () => {
if (!o.input) if (!o.input) return;
return;
return []; return [];
}); });
// extended validation // extended validation
@ -64,8 +62,7 @@ function p2pk(a, opts) {
throw new TypeError('Signature mismatch'); throw new TypeError('Signature mismatch');
} }
if (a.input) { if (a.input) {
if (_chunks().length !== 1) if (_chunks().length !== 1) throw new TypeError('Input is invalid');
throw new TypeError('Input is invalid');
if (!bscript.isCanonicalScriptSignature(o.signature)) if (!bscript.isCanonicalScriptSignature(o.signature))
throw new TypeError('Input has invalid signature'); throw new TypeError('Input has invalid signature');
} }

70
src/payments/p2pkh.js

@ -1,9 +1,9 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bcrypto = require("../crypto"); const bcrypto = require('../crypto');
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
@ -14,7 +14,8 @@ function p2pkh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input)
throw new TypeError('Not enough data'); throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
typef({ typef(
{
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
address: typef.maybe(typef.String), address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)), hash: typef.maybe(typef.BufferN(20)),
@ -22,7 +23,9 @@ function p2pkh(a, opts) {
pubkey: typef.maybe(ecc.isPoint), pubkey: typef.maybe(ecc.isPoint),
signature: typef.maybe(bscript.isCanonicalScriptSignature), signature: typef.maybe(bscript.isCanonicalScriptSignature),
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
}, a); },
a,
);
const _address = lazy.value(() => { const _address = lazy.value(() => {
const payload = bs58check.decode(a.address); const payload = bs58check.decode(a.address);
const version = payload.readUInt8(0); const version = payload.readUInt8(0);
@ -35,24 +38,19 @@ function p2pkh(a, opts) {
const network = a.network || networks_1.bitcoin; const network = a.network || networks_1.bitcoin;
const o = { network }; const o = { network };
lazy.prop(o, 'address', () => { lazy.prop(o, 'address', () => {
if (!o.hash) if (!o.hash) return;
return;
const payload = Buffer.allocUnsafe(21); const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(network.pubKeyHash, 0); payload.writeUInt8(network.pubKeyHash, 0);
o.hash.copy(payload, 1); o.hash.copy(payload, 1);
return bs58check.encode(payload); return bs58check.encode(payload);
}); });
lazy.prop(o, 'hash', () => { lazy.prop(o, 'hash', () => {
if (a.output) if (a.output) return a.output.slice(3, 23);
return a.output.slice(3, 23); if (a.address) return _address().hash;
if (a.address) if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey);
return _address().hash;
if (a.pubkey || o.pubkey)
return bcrypto.hash160(a.pubkey || o.pubkey);
}); });
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!o.hash) if (!o.hash) return;
return;
return bscript.compile([ return bscript.compile([
OPS.OP_DUP, OPS.OP_DUP,
OPS.OP_HASH160, OPS.OP_HASH160,
@ -62,25 +60,20 @@ function p2pkh(a, opts) {
]); ]);
}); });
lazy.prop(o, 'pubkey', () => { lazy.prop(o, 'pubkey', () => {
if (!a.input) if (!a.input) return;
return;
return _chunks()[1]; return _chunks()[1];
}); });
lazy.prop(o, 'signature', () => { lazy.prop(o, 'signature', () => {
if (!a.input) if (!a.input) return;
return;
return _chunks()[0]; return _chunks()[0];
}); });
lazy.prop(o, 'input', () => { lazy.prop(o, 'input', () => {
if (!a.pubkey) if (!a.pubkey) return;
return; if (!a.signature) return;
if (!a.signature)
return;
return bscript.compile([a.signature, a.pubkey]); return bscript.compile([a.signature, a.pubkey]);
}); });
lazy.prop(o, 'witness', () => { lazy.prop(o, 'witness', () => {
if (!o.input) if (!o.input) return;
return;
return []; return [];
}); });
// extended validation // extended validation
@ -89,41 +82,38 @@ function p2pkh(a, opts) {
if (a.address) { if (a.address) {
if (_address().version !== network.pubKeyHash) if (_address().version !== network.pubKeyHash)
throw new TypeError('Invalid version or Network mismatch'); throw new TypeError('Invalid version or Network mismatch');
if (_address().hash.length !== 20) if (_address().hash.length !== 20) throw new TypeError('Invalid address');
throw new TypeError('Invalid address');
hash = _address().hash; hash = _address().hash;
} }
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = a.hash;
hash = a.hash;
} }
if (a.output) { if (a.output) {
if (a.output.length !== 25 || if (
a.output.length !== 25 ||
a.output[0] !== OPS.OP_DUP || a.output[0] !== OPS.OP_DUP ||
a.output[1] !== OPS.OP_HASH160 || a.output[1] !== OPS.OP_HASH160 ||
a.output[2] !== 0x14 || a.output[2] !== 0x14 ||
a.output[23] !== OPS.OP_EQUALVERIFY || a.output[23] !== OPS.OP_EQUALVERIFY ||
a.output[24] !== OPS.OP_CHECKSIG) a.output[24] !== OPS.OP_CHECKSIG
)
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
const hash2 = a.output.slice(3, 23); const hash2 = a.output.slice(3, 23);
if (hash.length > 0 && !hash.equals(hash2)) if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = hash2;
hash = hash2;
} }
if (a.pubkey) { if (a.pubkey) {
const pkh = bcrypto.hash160(a.pubkey); const pkh = bcrypto.hash160(a.pubkey);
if (hash.length > 0 && !hash.equals(pkh)) if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = pkh;
hash = pkh;
} }
if (a.input) { if (a.input) {
const chunks = _chunks(); const chunks = _chunks();
if (chunks.length !== 2) if (chunks.length !== 2) throw new TypeError('Input is invalid');
throw new TypeError('Input is invalid');
if (!bscript.isCanonicalScriptSignature(chunks[0])) if (!bscript.isCanonicalScriptSignature(chunks[0]))
throw new TypeError('Input has invalid signature'); throw new TypeError('Input has invalid signature');
if (!ecc.isPoint(chunks[1])) if (!ecc.isPoint(chunks[1]))

85
src/payments/p2sh.js

@ -1,15 +1,14 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bcrypto = require("../crypto"); const bcrypto = require('../crypto');
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const bs58check = require('bs58check'); const bs58check = require('bs58check');
function stacksEqual(a, b) { function stacksEqual(a, b) {
if (a.length !== b.length) if (a.length !== b.length) return false;
return false;
return a.every((x, i) => { return a.every((x, i) => {
return x.equals(b[i]); return x.equals(b[i]);
}); });
@ -21,7 +20,8 @@ function p2sh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) if (!a.address && !a.hash && !a.output && !a.redeem && !a.input)
throw new TypeError('Not enough data'); throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
typef({ typef(
{
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
address: typef.maybe(typef.String), address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)), hash: typef.maybe(typef.BufferN(20)),
@ -34,7 +34,9 @@ function p2sh(a, opts) {
}), }),
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)), witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a); },
a,
);
let network = a.network; let network = a.network;
if (!network) { if (!network) {
network = (a.redeem && a.redeem.network) || networks_1.bitcoin; network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
@ -60,8 +62,7 @@ function p2sh(a, opts) {
}); });
// output dependents // output dependents
lazy.prop(o, 'address', () => { lazy.prop(o, 'address', () => {
if (!o.hash) if (!o.hash) return;
return;
const payload = Buffer.allocUnsafe(21); const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(o.network.scriptHash, 0); payload.writeUInt8(o.network.scriptHash, 0);
o.hash.copy(payload, 1); o.hash.copy(payload, 1);
@ -69,64 +70,57 @@ function p2sh(a, opts) {
}); });
lazy.prop(o, 'hash', () => { lazy.prop(o, 'hash', () => {
// in order of least effort // in order of least effort
if (a.output) if (a.output) return a.output.slice(2, 22);
return a.output.slice(2, 22); if (a.address) return _address().hash;
if (a.address) if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output);
return _address().hash;
if (o.redeem && o.redeem.output)
return bcrypto.hash160(o.redeem.output);
}); });
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!o.hash) if (!o.hash) return;
return;
return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]);
}); });
// input dependents // input dependents
lazy.prop(o, 'redeem', () => { lazy.prop(o, 'redeem', () => {
if (!a.input) if (!a.input) return;
return;
return _redeem(); return _redeem();
}); });
lazy.prop(o, 'input', () => { lazy.prop(o, 'input', () => {
if (!a.redeem || !a.redeem.input || !a.redeem.output) if (!a.redeem || !a.redeem.input || !a.redeem.output) return;
return; return bscript.compile(
return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output)); [].concat(bscript.decompile(a.redeem.input), a.redeem.output),
);
}); });
lazy.prop(o, 'witness', () => { lazy.prop(o, 'witness', () => {
if (o.redeem && o.redeem.witness) if (o.redeem && o.redeem.witness) return o.redeem.witness;
return o.redeem.witness; if (o.input) return [];
if (o.input)
return [];
}); });
if (opts.validate) { if (opts.validate) {
let hash = Buffer.from([]); let hash = Buffer.from([]);
if (a.address) { if (a.address) {
if (_address().version !== network.scriptHash) if (_address().version !== network.scriptHash)
throw new TypeError('Invalid version or Network mismatch'); throw new TypeError('Invalid version or Network mismatch');
if (_address().hash.length !== 20) if (_address().hash.length !== 20) throw new TypeError('Invalid address');
throw new TypeError('Invalid address');
hash = _address().hash; hash = _address().hash;
} }
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = a.hash;
hash = a.hash;
} }
if (a.output) { if (a.output) {
if (a.output.length !== 23 || if (
a.output.length !== 23 ||
a.output[0] !== OPS.OP_HASH160 || a.output[0] !== OPS.OP_HASH160 ||
a.output[1] !== 0x14 || a.output[1] !== 0x14 ||
a.output[22] !== OPS.OP_EQUAL) a.output[22] !== OPS.OP_EQUAL
)
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
const hash2 = a.output.slice(2, 22); const hash2 = a.output.slice(2, 22);
if (hash.length > 0 && !hash.equals(hash2)) if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = hash2;
hash = hash2;
} }
// inlined to prevent 'no-inner-declarations' failing // inlined to prevent 'no-inner-declarations' failing
const checkRedeem = (redeem) => { const checkRedeem = redeem => {
// is the redeem output empty/invalid? // is the redeem output empty/invalid?
if (redeem.output) { if (redeem.output) {
const decompile = bscript.decompile(redeem.output); const decompile = bscript.decompile(redeem.output);
@ -136,14 +130,12 @@ function p2sh(a, opts) {
const hash2 = bcrypto.hash160(redeem.output); const hash2 = bcrypto.hash160(redeem.output);
if (hash.length > 0 && !hash.equals(hash2)) if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = hash2;
hash = hash2;
} }
if (redeem.input) { if (redeem.input) {
const hasInput = redeem.input.length > 0; const hasInput = redeem.input.length > 0;
const hasWitness = redeem.witness && redeem.witness.length > 0; const hasWitness = redeem.witness && redeem.witness.length > 0;
if (!hasInput && !hasWitness) if (!hasInput && !hasWitness) throw new TypeError('Empty input');
throw new TypeError('Empty input');
if (hasInput && hasWitness) if (hasInput && hasWitness)
throw new TypeError('Input and witness provided'); throw new TypeError('Input and witness provided');
if (hasInput) { if (hasInput) {
@ -155,8 +147,7 @@ function p2sh(a, opts) {
}; };
if (a.input) { if (a.input) {
const chunks = _chunks(); const chunks = _chunks();
if (!chunks || chunks.length < 1) if (!chunks || chunks.length < 1) throw new TypeError('Input too short');
throw new TypeError('Input too short');
if (!Buffer.isBuffer(_redeem().output)) if (!Buffer.isBuffer(_redeem().output))
throw new TypeError('Input is invalid'); throw new TypeError('Input is invalid');
checkRedeem(_redeem()); checkRedeem(_redeem());
@ -174,9 +165,11 @@ function p2sh(a, opts) {
checkRedeem(a.redeem); checkRedeem(a.redeem);
} }
if (a.witness) { if (a.witness) {
if (a.redeem && if (
a.redeem &&
a.redeem.witness && a.redeem.witness &&
!stacksEqual(a.redeem.witness, a.witness)) !stacksEqual(a.redeem.witness, a.witness)
)
throw new TypeError('Witness and redeem.witness mismatch'); throw new TypeError('Witness and redeem.witness mismatch');
} }
} }

70
src/payments/p2wpkh.js

@ -1,9 +1,9 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bcrypto = require("../crypto"); const bcrypto = require('../crypto');
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
@ -16,7 +16,8 @@ function p2wpkh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
throw new TypeError('Not enough data'); throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
typef({ typef(
{
address: typef.maybe(typef.String), address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)), hash: typef.maybe(typef.BufferN(20)),
input: typef.maybe(typef.BufferN(0)), input: typef.maybe(typef.BufferN(0)),
@ -25,7 +26,9 @@ function p2wpkh(a, opts) {
pubkey: typef.maybe(ecc.isPoint), pubkey: typef.maybe(ecc.isPoint),
signature: typef.maybe(bscript.isCanonicalScriptSignature), signature: typef.maybe(bscript.isCanonicalScriptSignature),
witness: typef.maybe(typef.arrayOf(typef.Buffer)), witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a); },
a,
);
const _address = lazy.value(() => { const _address = lazy.value(() => {
const result = bech32.decode(a.address); const result = bech32.decode(a.address);
const version = result.words.shift(); const version = result.words.shift();
@ -39,47 +42,36 @@ function p2wpkh(a, opts) {
const network = a.network || networks_1.bitcoin; const network = a.network || networks_1.bitcoin;
const o = { network }; const o = { network };
lazy.prop(o, 'address', () => { lazy.prop(o, 'address', () => {
if (!o.hash) if (!o.hash) return;
return;
const words = bech32.toWords(o.hash); const words = bech32.toWords(o.hash);
words.unshift(0x00); words.unshift(0x00);
return bech32.encode(network.bech32, words); return bech32.encode(network.bech32, words);
}); });
lazy.prop(o, 'hash', () => { lazy.prop(o, 'hash', () => {
if (a.output) if (a.output) return a.output.slice(2, 22);
return a.output.slice(2, 22); if (a.address) return _address().data;
if (a.address) if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey);
return _address().data;
if (a.pubkey || o.pubkey)
return bcrypto.hash160(a.pubkey || o.pubkey);
}); });
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!o.hash) if (!o.hash) return;
return;
return bscript.compile([OPS.OP_0, o.hash]); return bscript.compile([OPS.OP_0, o.hash]);
}); });
lazy.prop(o, 'pubkey', () => { lazy.prop(o, 'pubkey', () => {
if (a.pubkey) if (a.pubkey) return a.pubkey;
return a.pubkey; if (!a.witness) return;
if (!a.witness)
return;
return a.witness[1]; return a.witness[1];
}); });
lazy.prop(o, 'signature', () => { lazy.prop(o, 'signature', () => {
if (!a.witness) if (!a.witness) return;
return;
return a.witness[0]; return a.witness[0];
}); });
lazy.prop(o, 'input', () => { lazy.prop(o, 'input', () => {
if (!o.witness) if (!o.witness) return;
return;
return EMPTY_BUFFER; return EMPTY_BUFFER;
}); });
lazy.prop(o, 'witness', () => { lazy.prop(o, 'witness', () => {
if (!a.pubkey) if (!a.pubkey) return;
return; if (!a.signature) return;
if (!a.signature)
return;
return [a.signature, a.pubkey]; return [a.signature, a.pubkey];
}); });
// extended validation // extended validation
@ -97,29 +89,27 @@ function p2wpkh(a, opts) {
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = a.hash;
hash = a.hash;
} }
if (a.output) { if (a.output) {
if (a.output.length !== 22 || if (
a.output.length !== 22 ||
a.output[0] !== OPS.OP_0 || a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x14) a.output[1] !== 0x14
)
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
if (hash.length > 0 && !hash.equals(a.output.slice(2))) if (hash.length > 0 && !hash.equals(a.output.slice(2)))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = a.output.slice(2);
hash = a.output.slice(2);
} }
if (a.pubkey) { if (a.pubkey) {
const pkh = bcrypto.hash160(a.pubkey); const pkh = bcrypto.hash160(a.pubkey);
if (hash.length > 0 && !hash.equals(pkh)) if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = pkh;
hash = pkh;
} }
if (a.witness) { if (a.witness) {
if (a.witness.length !== 2) if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
throw new TypeError('Witness is invalid');
if (!bscript.isCanonicalScriptSignature(a.witness[0])) if (!bscript.isCanonicalScriptSignature(a.witness[0]))
throw new TypeError('Witness has invalid signature'); throw new TypeError('Witness has invalid signature');
if (!ecc.isPoint(a.witness[1])) if (!ecc.isPoint(a.witness[1]))

91
src/payments/p2wsh.js

@ -1,16 +1,15 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bcrypto = require("../crypto"); const bcrypto = require('../crypto');
const networks_1 = require("../networks"); const networks_1 = require('../networks');
const bscript = require("../script"); const bscript = require('../script');
const lazy = require("./lazy"); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const bech32 = require('bech32'); const bech32 = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);
function stacksEqual(a, b) { function stacksEqual(a, b) {
if (a.length !== b.length) if (a.length !== b.length) return false;
return false;
return a.every((x, i) => { return a.every((x, i) => {
return x.equals(b[i]); return x.equals(b[i]);
}); });
@ -22,7 +21,8 @@ function p2wsh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness)
throw new TypeError('Not enough data'); throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {}); opts = Object.assign({ validate: true }, opts || {});
typef({ typef(
{
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
address: typef.maybe(typef.String), address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(32)), hash: typef.maybe(typef.BufferN(32)),
@ -35,7 +35,9 @@ function p2wsh(a, opts) {
}), }),
input: typef.maybe(typef.BufferN(0)), input: typef.maybe(typef.BufferN(0)),
witness: typef.maybe(typef.arrayOf(typef.Buffer)), witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a); },
a,
);
const _address = lazy.value(() => { const _address = lazy.value(() => {
const result = bech32.decode(a.address); const result = bech32.decode(a.address);
const version = result.words.shift(); const version = result.words.shift();
@ -55,28 +57,22 @@ function p2wsh(a, opts) {
} }
const o = { network }; const o = { network };
lazy.prop(o, 'address', () => { lazy.prop(o, 'address', () => {
if (!o.hash) if (!o.hash) return;
return;
const words = bech32.toWords(o.hash); const words = bech32.toWords(o.hash);
words.unshift(0x00); words.unshift(0x00);
return bech32.encode(network.bech32, words); return bech32.encode(network.bech32, words);
}); });
lazy.prop(o, 'hash', () => { lazy.prop(o, 'hash', () => {
if (a.output) if (a.output) return a.output.slice(2);
return a.output.slice(2); if (a.address) return _address().data;
if (a.address) if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output);
return _address().data;
if (o.redeem && o.redeem.output)
return bcrypto.sha256(o.redeem.output);
}); });
lazy.prop(o, 'output', () => { lazy.prop(o, 'output', () => {
if (!o.hash) if (!o.hash) return;
return;
return bscript.compile([OPS.OP_0, o.hash]); return bscript.compile([OPS.OP_0, o.hash]);
}); });
lazy.prop(o, 'redeem', () => { lazy.prop(o, 'redeem', () => {
if (!a.witness) if (!a.witness) return;
return;
return { return {
output: a.witness[a.witness.length - 1], output: a.witness[a.witness.length - 1],
input: EMPTY_BUFFER, input: EMPTY_BUFFER,
@ -84,29 +80,27 @@ function p2wsh(a, opts) {
}; };
}); });
lazy.prop(o, 'input', () => { lazy.prop(o, 'input', () => {
if (!o.witness) if (!o.witness) return;
return;
return EMPTY_BUFFER; return EMPTY_BUFFER;
}); });
lazy.prop(o, 'witness', () => { lazy.prop(o, 'witness', () => {
// transform redeem input to witness stack? // transform redeem input to witness stack?
if (a.redeem && if (
a.redeem &&
a.redeem.input && a.redeem.input &&
a.redeem.input.length > 0 && a.redeem.input.length > 0 &&
a.redeem.output && a.redeem.output &&
a.redeem.output.length > 0) { a.redeem.output.length > 0
) {
const stack = bscript.toStack(_rchunks()); const stack = bscript.toStack(_rchunks());
// assign, and blank the existing input // assign, and blank the existing input
o.redeem = Object.assign({ witness: stack }, a.redeem); o.redeem = Object.assign({ witness: stack }, a.redeem);
o.redeem.input = EMPTY_BUFFER; o.redeem.input = EMPTY_BUFFER;
return [].concat(stack, a.redeem.output); return [].concat(stack, a.redeem.output);
} }
if (!a.redeem) if (!a.redeem) return;
return; if (!a.redeem.output) return;
if (!a.redeem.output) if (!a.redeem.witness) return;
return;
if (!a.redeem.witness)
return;
return [].concat(a.redeem.witness, a.redeem.output); return [].concat(a.redeem.witness, a.redeem.output);
}); });
// extended validation // extended validation
@ -124,28 +118,30 @@ function p2wsh(a, opts) {
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = a.hash;
hash = a.hash;
} }
if (a.output) { if (a.output) {
if (a.output.length !== 34 || if (
a.output.length !== 34 ||
a.output[0] !== OPS.OP_0 || a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x20) a.output[1] !== 0x20
)
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
const hash2 = a.output.slice(2); const hash2 = a.output.slice(2);
if (hash.length > 0 && !hash.equals(hash2)) if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = hash2;
hash = hash2;
} }
if (a.redeem) { if (a.redeem) {
if (a.redeem.network && a.redeem.network !== network) if (a.redeem.network && a.redeem.network !== network)
throw new TypeError('Network mismatch'); throw new TypeError('Network mismatch');
// is there two redeem sources? // is there two redeem sources?
if (a.redeem.input && if (
a.redeem.input &&
a.redeem.input.length > 0 && a.redeem.input.length > 0 &&
a.redeem.witness && a.redeem.witness &&
a.redeem.witness.length > 0) a.redeem.witness.length > 0
)
throw new TypeError('Ambiguous witness source'); throw new TypeError('Ambiguous witness source');
// is the redeem output non-empty? // is the redeem output non-empty?
if (a.redeem.output) { if (a.redeem.output) {
@ -155,20 +151,23 @@ function p2wsh(a, opts) {
const hash2 = bcrypto.sha256(a.redeem.output); const hash2 = bcrypto.sha256(a.redeem.output);
if (hash.length > 0 && !hash.equals(hash2)) if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else else hash = hash2;
hash = hash2;
} }
if (a.redeem.input && !bscript.isPushOnly(_rchunks())) if (a.redeem.input && !bscript.isPushOnly(_rchunks()))
throw new TypeError('Non push-only scriptSig'); throw new TypeError('Non push-only scriptSig');
if (a.witness && if (
a.witness &&
a.redeem.witness && a.redeem.witness &&
!stacksEqual(a.witness, a.redeem.witness)) !stacksEqual(a.witness, a.redeem.witness)
)
throw new TypeError('Witness and redeem.witness mismatch'); throw new TypeError('Witness and redeem.witness mismatch');
} }
if (a.witness) { if (a.witness) {
if (a.redeem && if (
a.redeem &&
a.redeem.output && a.redeem.output &&
!a.redeem.output.equals(a.witness[a.witness.length - 1])) !a.redeem.output.equals(a.witness[a.witness.length - 1])
)
throw new TypeError('Witness and redeem.output mismatch'); throw new TypeError('Witness and redeem.output mismatch');
} }
} }

76
src/script.js

@ -1,8 +1,8 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const scriptNumber = require("./script_number"); const scriptNumber = require('./script_number');
const scriptSignature = require("./script_signature"); const scriptSignature = require('./script_signature');
const types = require("./types"); const types = require('./types');
const bip66 = require('bip66'); const bip66 = require('bip66');
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const pushdata = require('pushdata-bitcoin'); const pushdata = require('pushdata-bitcoin');
@ -11,10 +11,12 @@ exports.OPS = require('bitcoin-ops');
const REVERSE_OPS = require('bitcoin-ops/map'); const REVERSE_OPS = require('bitcoin-ops/map');
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1 const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1
function isOPInt(value) { function isOPInt(value) {
return (types.Number(value) && return (
types.Number(value) &&
(value === exports.OPS.OP_0 || (value === exports.OPS.OP_0 ||
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) ||
value === exports.OPS.OP_1NEGATE)); value === exports.OPS.OP_1NEGATE)
);
} }
function isPushOnlyChunk(value) { function isPushOnlyChunk(value) {
return types.Buffer(value) || isOPInt(value); return types.Buffer(value) || isOPInt(value);
@ -24,14 +26,10 @@ function isPushOnly(value) {
} }
exports.isPushOnly = isPushOnly; exports.isPushOnly = isPushOnly;
function asMinimalOP(buffer) { function asMinimalOP(buffer) {
if (buffer.length === 0) if (buffer.length === 0) return exports.OPS.OP_0;
return exports.OPS.OP_0; if (buffer.length !== 1) return;
if (buffer.length !== 1) if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
return; if (buffer[0] === 0x81) return exports.OPS.OP_1NEGATE;
if (buffer[0] >= 1 && buffer[0] <= 16)
return OP_INT_BASE + buffer[0];
if (buffer[0] === 0x81)
return exports.OPS.OP_1NEGATE;
} }
function chunksIsBuffer(buf) { function chunksIsBuffer(buf) {
return Buffer.isBuffer(buf); return Buffer.isBuffer(buf);
@ -44,8 +42,7 @@ function singleChunkIsBuffer(buf) {
} }
function compile(chunks) { function compile(chunks) {
// TODO: remove me // TODO: remove me
if (chunksIsBuffer(chunks)) if (chunksIsBuffer(chunks)) return chunks;
return chunks;
typeforce(types.Array, chunks); typeforce(types.Array, chunks);
const bufferSize = chunks.reduce((accum, chunk) => { const bufferSize = chunks.reduce((accum, chunk) => {
// data chunk // data chunk
@ -75,21 +72,18 @@ function compile(chunks) {
chunk.copy(buffer, offset); chunk.copy(buffer, offset);
offset += chunk.length; offset += chunk.length;
// opcode // opcode
} } else {
else {
buffer.writeUInt8(chunk, offset); buffer.writeUInt8(chunk, offset);
offset += 1; offset += 1;
} }
}); });
if (offset !== buffer.length) if (offset !== buffer.length) throw new Error('Could not decode chunks');
throw new Error('Could not decode chunks');
return buffer; return buffer;
} }
exports.compile = compile; exports.compile = compile;
function decompile(buffer) { function decompile(buffer) {
// TODO: remove me // TODO: remove me
if (chunksIsArray(buffer)) if (chunksIsArray(buffer)) return buffer;
return buffer;
typeforce(types.Buffer, buffer); typeforce(types.Buffer, buffer);
const chunks = []; const chunks = [];
let i = 0; let i = 0;
@ -99,25 +93,21 @@ function decompile(buffer) {
if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) {
const d = pushdata.decode(buffer, i); const d = pushdata.decode(buffer, i);
// did reading a pushDataInt fail? // did reading a pushDataInt fail?
if (d === null) if (d === null) return null;
return null;
i += d.size; i += d.size;
// attempt to read too much data? // attempt to read too much data?
if (i + d.number > buffer.length) if (i + d.number > buffer.length) return null;
return null;
const data = buffer.slice(i, i + d.number); const data = buffer.slice(i, i + d.number);
i += d.number; i += d.number;
// decompile minimally // decompile minimally
const op = asMinimalOP(data); const op = asMinimalOP(data);
if (op !== undefined) { if (op !== undefined) {
chunks.push(op); chunks.push(op);
} } else {
else {
chunks.push(data); chunks.push(data);
} }
// opcode // opcode
} } else {
else {
chunks.push(opcode); chunks.push(opcode);
i += 1; i += 1;
} }
@ -134,8 +124,7 @@ function toASM(chunks) {
// data? // data?
if (singleChunkIsBuffer(chunk)) { if (singleChunkIsBuffer(chunk)) {
const op = asMinimalOP(chunk); const op = asMinimalOP(chunk);
if (op === undefined) if (op === undefined) return chunk.toString('hex');
return chunk.toString('hex');
chunk = op; chunk = op;
} }
// opcode! // opcode!
@ -146,24 +135,23 @@ function toASM(chunks) {
exports.toASM = toASM; exports.toASM = toASM;
function fromASM(asm) { function fromASM(asm) {
typeforce(types.String, asm); typeforce(types.String, asm);
return compile(asm.split(' ').map(chunkStr => { return compile(
asm.split(' ').map(chunkStr => {
// opcode? // opcode?
if (exports.OPS[chunkStr] !== undefined) if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr];
return exports.OPS[chunkStr];
typeforce(types.Hex, chunkStr); typeforce(types.Hex, chunkStr);
// data! // data!
return Buffer.from(chunkStr, 'hex'); return Buffer.from(chunkStr, 'hex');
})); }),
);
} }
exports.fromASM = fromASM; exports.fromASM = fromASM;
function toStack(chunks) { function toStack(chunks) {
chunks = decompile(chunks); chunks = decompile(chunks);
typeforce(isPushOnly, chunks); typeforce(isPushOnly, chunks);
return chunks.map(op => { return chunks.map(op => {
if (singleChunkIsBuffer(op)) if (singleChunkIsBuffer(op)) return op;
return op; if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0);
if (op === exports.OPS.OP_0)
return Buffer.allocUnsafe(0);
return scriptNumber.encode(op - OP_INT_BASE); return scriptNumber.encode(op - OP_INT_BASE);
}); });
} }
@ -179,10 +167,8 @@ function isDefinedHashType(hashType) {
} }
exports.isDefinedHashType = isDefinedHashType; exports.isDefinedHashType = isDefinedHashType;
function isCanonicalScriptSignature(buffer) { function isCanonicalScriptSignature(buffer) {
if (!Buffer.isBuffer(buffer)) if (!Buffer.isBuffer(buffer)) return false;
return false; if (!isDefinedHashType(buffer[buffer.length - 1])) return false;
if (!isDefinedHashType(buffer[buffer.length - 1]))
return false;
return bip66.check(buffer.slice(0, -1)); return bip66.check(buffer.slice(0, -1));
} }
exports.isCanonicalScriptSignature = isCanonicalScriptSignature; exports.isCanonicalScriptSignature = isCanonicalScriptSignature;

16
src/script_number.js

@ -1,13 +1,11 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
function decode(buffer, maxLength, minimal) { function decode(buffer, maxLength, minimal) {
maxLength = maxLength || 4; maxLength = maxLength || 4;
minimal = minimal === undefined ? true : minimal; minimal = minimal === undefined ? true : minimal;
const length = buffer.length; const length = buffer.length;
if (length === 0) if (length === 0) return 0;
return 0; if (length > maxLength) throw new TypeError('Script number overflow');
if (length > maxLength)
throw new TypeError('Script number overflow');
if (minimal) { if (minimal) {
if ((buffer[length - 1] & 0x7f) === 0) { if ((buffer[length - 1] & 0x7f) === 0) {
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) if (length <= 1 || (buffer[length - 2] & 0x80) === 0)
@ -18,8 +16,7 @@ function decode(buffer, maxLength, minimal) {
if (length === 5) { if (length === 5) {
const a = buffer.readUInt32LE(0); const a = buffer.readUInt32LE(0);
const b = buffer.readUInt8(4); const b = buffer.readUInt8(4);
if (b & 0x80) if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a);
return -((b & ~0x80) * 0x100000000 + a);
return b * 0x100000000 + a; return b * 0x100000000 + a;
} }
// 32-bit / 24-bit / 16-bit / 8-bit // 32-bit / 24-bit / 16-bit / 8-bit
@ -56,8 +53,7 @@ function encode(_number) {
} }
if (buffer[size - 1] & 0x80) { if (buffer[size - 1] & 0x80) {
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1);
} } else if (negative) {
else if (negative) {
buffer[size - 1] |= 0x80; buffer[size - 1] |= 0x80;
} }
return buffer; return buffer;

25
src/script_signature.js

@ -1,23 +1,19 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const types = require("./types"); const types = require('./types');
const bip66 = require('bip66'); const bip66 = require('bip66');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const ZERO = Buffer.alloc(1, 0); const ZERO = Buffer.alloc(1, 0);
function toDER(x) { function toDER(x) {
let i = 0; let i = 0;
while (x[i] === 0) while (x[i] === 0) ++i;
++i; if (i === x.length) return ZERO;
if (i === x.length)
return ZERO;
x = x.slice(i); x = x.slice(i);
if (x[0] & 0x80) if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length);
return Buffer.concat([ZERO, x], 1 + x.length);
return x; return x;
} }
function fromDER(x) { function fromDER(x) {
if (x[0] === 0x00) if (x[0] === 0x00) x = x.slice(1);
x = x.slice(1);
const buffer = Buffer.alloc(32, 0); const buffer = Buffer.alloc(32, 0);
const bstart = Math.max(0, 32 - x.length); const bstart = Math.max(0, 32 - x.length);
x.copy(buffer, bstart); x.copy(buffer, bstart);
@ -37,10 +33,13 @@ function decode(buffer) {
} }
exports.decode = decode; exports.decode = decode;
function encode(signature, hashType) { function encode(signature, hashType) {
typeforce({ typeforce(
{
signature: types.BufferN(64), signature: types.BufferN(64),
hashType: types.UInt8, hashType: types.UInt8,
}, { signature, hashType }); },
{ signature, hashType },
);
const hashTypeMod = hashType & ~0x80; const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4) if (hashTypeMod <= 0 || hashTypeMod >= 4)
throw new Error('Invalid hashType ' + hashType); throw new Error('Invalid hashType ' + hashType);

8
src/templates/multisig/index.js

@ -1,6 +1,6 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const input = require("./input"); const input = require('./input');
exports.input = input; exports.input = input;
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

18
src/templates/multisig/input.js

@ -1,17 +1,17 @@
"use strict"; 'use strict';
// OP_0 [signatures ...] // OP_0 [signatures ...]
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
function partialSignature(value) { function partialSignature(value) {
return (value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)); return (
value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)
);
} }
function check(script, allowIncomplete) { function check(script, allowIncomplete) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
if (chunks.length < 2) if (chunks.length < 2) return false;
return false; if (chunks[0] !== script_1.OPS.OP_0) return false;
if (chunks[0] !== script_1.OPS.OP_0)
return false;
if (allowIncomplete) { if (allowIncomplete) {
return chunks.slice(1).every(partialSignature); return chunks.slice(1).every(partialSignature);
} }

37
src/templates/multisig/output.js

@ -1,32 +1,23 @@
"use strict"; 'use strict';
// m [pubKeys ...] n OP_CHECKMULTISIG // m [pubKeys ...] n OP_CHECKMULTISIG
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
const types = require("../../types"); const types = require('../../types');
const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1
function check(script, allowIncomplete) { function check(script, allowIncomplete) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
if (chunks.length < 4) if (chunks.length < 4) return false;
return false; if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) return false;
if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) if (!types.Number(chunks[0])) return false;
return false; if (!types.Number(chunks[chunks.length - 2])) return false;
if (!types.Number(chunks[0]))
return false;
if (!types.Number(chunks[chunks.length - 2]))
return false;
const m = chunks[0] - OP_INT_BASE; const m = chunks[0] - OP_INT_BASE;
const n = chunks[chunks.length - 2] - OP_INT_BASE; const n = chunks[chunks.length - 2] - OP_INT_BASE;
if (m <= 0) if (m <= 0) return false;
return false; if (n > 16) return false;
if (n > 16) if (m > n) return false;
return false; if (n !== chunks.length - 3) return false;
if (m > n) if (allowIncomplete) return true;
return false;
if (n !== chunks.length - 3)
return false;
if (allowIncomplete)
return true;
const keys = chunks.slice(1, -2); const keys = chunks.slice(1, -2);
return keys.every(bscript.isCanonicalPubKey); return keys.every(bscript.isCanonicalPubKey);
} }

6
src/templates/nulldata.js

@ -1,7 +1,7 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
// OP_RETURN {data} // OP_RETURN {data}
const bscript = require("../script"); const bscript = require('../script');
const OPS = bscript.OPS; const OPS = bscript.OPS;
function check(script) { function check(script) {
const buffer = bscript.compile(script); const buffer = bscript.compile(script);

8
src/templates/pubkey/index.js

@ -1,6 +1,6 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const input = require("./input"); const input = require('./input');
exports.input = input; exports.input = input;
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

9
src/templates/pubkey/input.js

@ -1,11 +1,10 @@
"use strict"; 'use strict';
// {signature} // {signature}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
function check(script) { function check(script) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
return (chunks.length === 1 && return chunks.length === 1 && bscript.isCanonicalScriptSignature(chunks[0]);
bscript.isCanonicalScriptSignature(chunks[0]));
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

14
src/templates/pubkey/output.js

@ -1,13 +1,15 @@
"use strict"; 'use strict';
// {pubKey} OP_CHECKSIG // {pubKey} OP_CHECKSIG
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
function check(script) { function check(script) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
return (chunks.length === 2 && return (
chunks.length === 2 &&
bscript.isCanonicalPubKey(chunks[0]) && bscript.isCanonicalPubKey(chunks[0]) &&
chunks[1] === script_1.OPS.OP_CHECKSIG); chunks[1] === script_1.OPS.OP_CHECKSIG
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

8
src/templates/pubkeyhash/index.js

@ -1,6 +1,6 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const input = require("./input"); const input = require('./input');
exports.input = input; exports.input = input;
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

12
src/templates/pubkeyhash/input.js

@ -1,12 +1,14 @@
"use strict"; 'use strict';
// {signature} {pubKey} // {signature} {pubKey}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
function check(script) { function check(script) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
return (chunks.length === 2 && return (
chunks.length === 2 &&
bscript.isCanonicalScriptSignature(chunks[0]) && bscript.isCanonicalScriptSignature(chunks[0]) &&
bscript.isCanonicalPubKey(chunks[1])); bscript.isCanonicalPubKey(chunks[1])
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

14
src/templates/pubkeyhash/output.js

@ -1,16 +1,18 @@
"use strict"; 'use strict';
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
function check(script) { function check(script) {
const buffer = bscript.compile(script); const buffer = bscript.compile(script);
return (buffer.length === 25 && return (
buffer.length === 25 &&
buffer[0] === script_1.OPS.OP_DUP && buffer[0] === script_1.OPS.OP_DUP &&
buffer[1] === script_1.OPS.OP_HASH160 && buffer[1] === script_1.OPS.OP_HASH160 &&
buffer[2] === 0x14 && buffer[2] === 0x14 &&
buffer[23] === script_1.OPS.OP_EQUALVERIFY && buffer[23] === script_1.OPS.OP_EQUALVERIFY &&
buffer[24] === script_1.OPS.OP_CHECKSIG); buffer[24] === script_1.OPS.OP_CHECKSIG
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

8
src/templates/scripthash/index.js

@ -1,6 +1,6 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const input = require("./input"); const input = require('./input');
exports.input = input; exports.input = input;
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

54
src/templates/scripthash/input.js

@ -1,40 +1,46 @@
"use strict"; 'use strict';
// <scriptSig> {serialized scriptPubKey script} // <scriptSig> {serialized scriptPubKey script}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const p2ms = require("../multisig"); const p2ms = require('../multisig');
const p2pk = require("../pubkey"); const p2pk = require('../pubkey');
const p2pkh = require("../pubkeyhash"); const p2pkh = require('../pubkeyhash');
const p2wpkho = require("../witnesspubkeyhash/output"); const p2wpkho = require('../witnesspubkeyhash/output');
const p2wsho = require("../witnessscripthash/output"); const p2wsho = require('../witnessscripthash/output');
function check(script, allowIncomplete) { function check(script, allowIncomplete) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
if (chunks.length < 1) if (chunks.length < 1) return false;
return false;
const lastChunk = chunks[chunks.length - 1]; const lastChunk = chunks[chunks.length - 1];
if (!Buffer.isBuffer(lastChunk)) if (!Buffer.isBuffer(lastChunk)) return false;
return false; const scriptSigChunks = bscript.decompile(
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))); bscript.compile(chunks.slice(0, -1)),
);
const redeemScriptChunks = bscript.decompile(lastChunk); const redeemScriptChunks = bscript.decompile(lastChunk);
// is redeemScript a valid script? // is redeemScript a valid script?
if (!redeemScriptChunks) if (!redeemScriptChunks) return false;
return false;
// is redeemScriptSig push only? // is redeemScriptSig push only?
if (!bscript.isPushOnly(scriptSigChunks)) if (!bscript.isPushOnly(scriptSigChunks)) return false;
return false;
// is witness? // is witness?
if (chunks.length === 1) { if (chunks.length === 1) {
return (p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)); return (
p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)
);
} }
// match types // match types
if (p2pkh.input.check(scriptSigChunks) && if (
p2pkh.output.check(redeemScriptChunks)) p2pkh.input.check(scriptSigChunks) &&
p2pkh.output.check(redeemScriptChunks)
)
return true; return true;
if (p2ms.input.check(scriptSigChunks, allowIncomplete) && if (
p2ms.output.check(redeemScriptChunks)) p2ms.input.check(scriptSigChunks, allowIncomplete) &&
p2ms.output.check(redeemScriptChunks)
)
return true; return true;
if (p2pk.input.check(scriptSigChunks) && if (
p2pk.output.check(redeemScriptChunks)) p2pk.input.check(scriptSigChunks) &&
p2pk.output.check(redeemScriptChunks)
)
return true; return true;
return false; return false;
} }

14
src/templates/scripthash/output.js

@ -1,14 +1,16 @@
"use strict"; 'use strict';
// OP_HASH160 {scriptHash} OP_EQUAL // OP_HASH160 {scriptHash} OP_EQUAL
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
function check(script) { function check(script) {
const buffer = bscript.compile(script); const buffer = bscript.compile(script);
return (buffer.length === 23 && return (
buffer.length === 23 &&
buffer[0] === script_1.OPS.OP_HASH160 && buffer[0] === script_1.OPS.OP_HASH160 &&
buffer[1] === 0x14 && buffer[1] === 0x14 &&
buffer[22] === script_1.OPS.OP_EQUAL); buffer[22] === script_1.OPS.OP_EQUAL
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

6
src/templates/witnesscommitment/index.js

@ -1,4 +1,4 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

16
src/templates/witnesscommitment/output.js

@ -1,17 +1,19 @@
"use strict"; 'use strict';
// OP_RETURN {aa21a9ed} {commitment} // OP_RETURN {aa21a9ed} {commitment}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
const types = require("../../types"); const types = require('../../types');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const HEADER = Buffer.from('aa21a9ed', 'hex'); const HEADER = Buffer.from('aa21a9ed', 'hex');
function check(script) { function check(script) {
const buffer = bscript.compile(script); const buffer = bscript.compile(script);
return (buffer.length > 37 && return (
buffer.length > 37 &&
buffer[0] === script_1.OPS.OP_RETURN && buffer[0] === script_1.OPS.OP_RETURN &&
buffer[1] === 0x24 && buffer[1] === 0x24 &&
buffer.slice(2, 6).equals(HEADER)); buffer.slice(2, 6).equals(HEADER)
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

8
src/templates/witnesspubkeyhash/index.js

@ -1,6 +1,6 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const input = require("./input"); const input = require('./input');
exports.input = input; exports.input = input;
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

12
src/templates/witnesspubkeyhash/input.js

@ -1,15 +1,17 @@
"use strict"; 'use strict';
// {signature} {pubKey} // {signature} {pubKey}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
function isCompressedCanonicalPubKey(pubKey) { function isCompressedCanonicalPubKey(pubKey) {
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33;
} }
function check(script) { function check(script) {
const chunks = bscript.decompile(script); const chunks = bscript.decompile(script);
return (chunks.length === 2 && return (
chunks.length === 2 &&
bscript.isCanonicalScriptSignature(chunks[0]) && bscript.isCanonicalScriptSignature(chunks[0]) &&
isCompressedCanonicalPubKey(chunks[1])); isCompressedCanonicalPubKey(chunks[1])
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

14
src/templates/witnesspubkeyhash/output.js

@ -1,11 +1,15 @@
"use strict"; 'use strict';
// OP_0 {pubKeyHash} // OP_0 {pubKeyHash}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
function check(script) { function check(script) {
const buffer = bscript.compile(script); const buffer = bscript.compile(script);
return buffer.length === 22 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x14; return (
buffer.length === 22 &&
buffer[0] === script_1.OPS.OP_0 &&
buffer[1] === 0x14
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

8
src/templates/witnessscripthash/index.js

@ -1,6 +1,6 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const input = require("./input"); const input = require('./input');
exports.input = input; exports.input = input;
const output = require("./output"); const output = require('./output');
exports.output = output; exports.output = output;

39
src/templates/witnessscripthash/input.js

@ -1,32 +1,35 @@
"use strict"; 'use strict';
// <scriptSig> {serialized scriptPubKey script} // <scriptSig> {serialized scriptPubKey script}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const p2ms = require("../multisig"); const p2ms = require('../multisig');
const p2pk = require("../pubkey"); const p2pk = require('../pubkey');
const p2pkh = require("../pubkeyhash"); const p2pkh = require('../pubkeyhash');
function check(chunks, allowIncomplete) { function check(chunks, allowIncomplete) {
typeforce(typeforce.Array, chunks); typeforce(typeforce.Array, chunks);
if (chunks.length < 1) if (chunks.length < 1) return false;
return false;
const witnessScript = chunks[chunks.length - 1]; const witnessScript = chunks[chunks.length - 1];
if (!Buffer.isBuffer(witnessScript)) if (!Buffer.isBuffer(witnessScript)) return false;
return false;
const witnessScriptChunks = bscript.decompile(witnessScript); const witnessScriptChunks = bscript.decompile(witnessScript);
// is witnessScript a valid script? // is witnessScript a valid script?
if (!witnessScriptChunks || witnessScriptChunks.length === 0) if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false;
return false;
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1));
// match types // match types
if (p2pkh.input.check(witnessRawScriptSig) && if (
p2pkh.output.check(witnessScriptChunks)) p2pkh.input.check(witnessRawScriptSig) &&
p2pkh.output.check(witnessScriptChunks)
)
return true; return true;
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && if (
p2ms.output.check(witnessScriptChunks)) p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
p2ms.output.check(witnessScriptChunks)
)
return true; return true;
if (p2pk.input.check(witnessRawScriptSig) && if (
p2pk.output.check(witnessScriptChunks)) p2pk.input.check(witnessRawScriptSig) &&
p2pk.output.check(witnessScriptChunks)
)
return true; return true;
return false; return false;
} }

14
src/templates/witnessscripthash/output.js

@ -1,11 +1,15 @@
"use strict"; 'use strict';
// OP_0 {scriptHash} // OP_0 {scriptHash}
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bscript = require("../../script"); const bscript = require('../../script');
const script_1 = require("../../script"); const script_1 = require('../../script');
function check(script) { function check(script) {
const buffer = bscript.compile(script); const buffer = bscript.compile(script);
return buffer.length === 34 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x20; return (
buffer.length === 34 &&
buffer[0] === script_1.OPS.OP_0 &&
buffer[1] === 0x20
);
} }
exports.check = check; exports.check = check;
check.toJSON = () => { check.toJSON = () => {

144
src/transaction.js

@ -1,11 +1,11 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bufferutils = require("./bufferutils"); const bufferutils = require('./bufferutils');
const bufferutils_1 = require("./bufferutils"); const bufferutils_1 = require('./bufferutils');
const bcrypto = require("./crypto"); const bcrypto = require('./crypto');
const bscript = require("./script"); const bscript = require('./script');
const script_1 = require("./script"); const script_1 = require('./script');
const types = require("./types"); const types = require('./types');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const varuint = require('varuint-bitcoin'); const varuint = require('varuint-bitcoin');
function varSliceSize(someScript) { function varSliceSize(someScript) {
@ -14,15 +14,23 @@ function varSliceSize(someScript) {
} }
function vectorSize(someVector) { function vectorSize(someVector) {
const length = someVector.length; const length = someVector.length;
return (varuint.encodingLength(length) + return (
varuint.encodingLength(length) +
someVector.reduce((sum, witness) => { someVector.reduce((sum, witness) => {
return sum + varSliceSize(witness); return sum + varSliceSize(witness);
}, 0)); }, 0)
);
} }
const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
const EMPTY_WITNESS = []; const EMPTY_WITNESS = [];
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); const ZERO = Buffer.from(
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); '0000000000000000000000000000000000000000000000000000000000000000',
'hex',
);
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex',
);
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT = { const BLANK_OUTPUT = {
script: EMPTY_SCRIPT, script: EMPTY_SCRIPT,
@ -70,8 +78,7 @@ class Transaction {
function readVector() { function readVector() {
const count = readVarInt(); const count = readVarInt();
const vector = []; const vector = [];
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++) vector.push(readVarSlice());
vector.push(readVarSlice());
return vector; return vector;
} }
const tx = new Transaction(); const tx = new Transaction();
@ -79,8 +86,10 @@ class Transaction {
const marker = buffer.readUInt8(offset); const marker = buffer.readUInt8(offset);
const flag = buffer.readUInt8(offset + 1); const flag = buffer.readUInt8(offset + 1);
let hasWitnesses = false; let hasWitnesses = false;
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && if (
flag === Transaction.ADVANCED_TRANSACTION_FLAG) { marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
flag === Transaction.ADVANCED_TRANSACTION_FLAG
) {
offset += 2; offset += 2;
hasWitnesses = true; hasWitnesses = true;
} }
@ -110,8 +119,7 @@ class Transaction {
throw new Error('Transaction has superfluous witness data'); throw new Error('Transaction has superfluous witness data');
} }
tx.locktime = readUInt32(); tx.locktime = readUInt32();
if (_NO_STRICT) if (_NO_STRICT) return tx;
return tx;
if (offset !== buffer.length) if (offset !== buffer.length)
throw new Error('Transaction has unexpected data'); throw new Error('Transaction has unexpected data');
return tx; return tx;
@ -122,35 +130,48 @@ class Transaction {
static isCoinbaseHash(buffer) { static isCoinbaseHash(buffer) {
typeforce(types.Hash256bit, buffer); typeforce(types.Hash256bit, buffer);
for (let i = 0; i < 32; ++i) { for (let i = 0; i < 32; ++i) {
if (buffer[i] !== 0) if (buffer[i] !== 0) return false;
return false;
} }
return true; return true;
} }
isCoinbase() { isCoinbase() {
return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)); return (
this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
);
} }
addInput(hash, index, sequence, scriptSig) { addInput(hash, index, sequence, scriptSig) {
typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer)), arguments); typeforce(
types.tuple(
types.Hash256bit,
types.UInt32,
types.maybe(types.UInt32),
types.maybe(types.Buffer),
),
arguments,
);
if (types.Null(sequence)) { if (types.Null(sequence)) {
sequence = Transaction.DEFAULT_SEQUENCE; sequence = Transaction.DEFAULT_SEQUENCE;
} }
// Add the input and return the input's index // Add the input and return the input's index
return (this.ins.push({ return (
this.ins.push({
hash, hash,
index, index,
script: scriptSig || EMPTY_SCRIPT, script: scriptSig || EMPTY_SCRIPT,
sequence: sequence, sequence: sequence,
witness: EMPTY_WITNESS, witness: EMPTY_WITNESS,
}) - 1); }) - 1
);
} }
addOutput(scriptPubKey, value) { addOutput(scriptPubKey, value) {
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); typeforce(types.tuple(types.Buffer, types.Satoshi), arguments);
// Add the output and return the output's index // Add the output and return the output's index
return (this.outs.push({ return (
this.outs.push({
script: scriptPubKey, script: scriptPubKey,
value, value,
}) - 1); }) - 1
);
} }
hasWitnesses() { hasWitnesses() {
return this.ins.some(x => { return this.ins.some(x => {
@ -198,30 +219,31 @@ class Transaction {
* This hash can then be used to sign the provided transaction input. * This hash can then be used to sign the provided transaction input.
*/ */
hashForSignature(inIndex, prevOutScript, hashType) { hashForSignature(inIndex, prevOutScript, hashType) {
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments); typeforce(
types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number),
arguments,
);
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
if (inIndex >= this.ins.length) if (inIndex >= this.ins.length) return ONE;
return ONE;
// ignore OP_CODESEPARATOR // ignore OP_CODESEPARATOR
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => { const ourScript = bscript.compile(
bscript.decompile(prevOutScript).filter(x => {
return x !== script_1.OPS.OP_CODESEPARATOR; return x !== script_1.OPS.OP_CODESEPARATOR;
})); }),
);
const txTmp = this.clone(); const txTmp = this.clone();
// SIGHASH_NONE: ignore all outputs? (wildcard payee) // SIGHASH_NONE: ignore all outputs? (wildcard payee)
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
txTmp.outs = []; txTmp.outs = [];
// ignore sequence numbers (except at inIndex) // ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, i) => { txTmp.ins.forEach((input, i) => {
if (i === inIndex) if (i === inIndex) return;
return;
input.sequence = 0; input.sequence = 0;
}); });
// SIGHASH_SINGLE: ignore all outputs, except at the same index? // SIGHASH_SINGLE: ignore all outputs, except at the same index?
} } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
if (inIndex >= this.outs.length) if (inIndex >= this.outs.length) return ONE;
return ONE;
// truncate outputs after // truncate outputs after
txTmp.outs.length = inIndex + 1; txTmp.outs.length = inIndex + 1;
// "blank" outputs before // "blank" outputs before
@ -230,8 +252,7 @@ class Transaction {
} }
// ignore sequence numbers (except at inIndex) // ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, y) => { txTmp.ins.forEach((input, y) => {
if (y === inIndex) if (y === inIndex) return;
return;
input.sequence = 0; input.sequence = 0;
}); });
} }
@ -240,8 +261,7 @@ class Transaction {
txTmp.ins = [txTmp.ins[inIndex]]; txTmp.ins = [txTmp.ins[inIndex]];
txTmp.ins[0].script = ourScript; txTmp.ins[0].script = ourScript;
// SIGHASH_ALL: only ignore input scripts // SIGHASH_ALL: only ignore input scripts
} } else {
else {
// "blank" others input scripts // "blank" others input scripts
txTmp.ins.forEach(input => { txTmp.ins.forEach(input => {
input.script = EMPTY_SCRIPT; input.script = EMPTY_SCRIPT;
@ -255,7 +275,10 @@ class Transaction {
return bcrypto.hash256(buffer); return bcrypto.hash256(buffer);
} }
hashForWitnessV0(inIndex, prevOutScript, value, hashType) { hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments); typeforce(
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
arguments,
);
let tbuffer = Buffer.from([]); let tbuffer = Buffer.from([]);
let toffset = 0; let toffset = 0;
function writeSlice(slice) { function writeSlice(slice) {
@ -287,9 +310,11 @@ class Transaction {
}); });
hashPrevouts = bcrypto.hash256(tbuffer); hashPrevouts = bcrypto.hash256(tbuffer);
} }
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && if (
!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) { (hashType & 0x1f) !== Transaction.SIGHASH_NONE
) {
tbuffer = Buffer.allocUnsafe(4 * this.ins.length); tbuffer = Buffer.allocUnsafe(4 * this.ins.length);
toffset = 0; toffset = 0;
this.ins.forEach(txIn => { this.ins.forEach(txIn => {
@ -297,8 +322,10 @@ class Transaction {
}); });
hashSequence = bcrypto.hash256(tbuffer); hashSequence = bcrypto.hash256(tbuffer);
} }
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && if (
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) { (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE
) {
const txOutsSize = this.outs.reduce((sum, output) => { const txOutsSize = this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script); return sum + 8 + varSliceSize(output.script);
}, 0); }, 0);
@ -309,9 +336,10 @@ class Transaction {
writeVarSlice(out.script); writeVarSlice(out.script);
}); });
hashOutputs = bcrypto.hash256(tbuffer); hashOutputs = bcrypto.hash256(tbuffer);
} } else if (
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && (hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
inIndex < this.outs.length) { inIndex < this.outs.length
) {
const output = this.outs[inIndex]; const output = this.outs[inIndex];
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script));
toffset = 0; toffset = 0;
@ -337,8 +365,7 @@ class Transaction {
} }
getHash(forWitness) { getHash(forWitness) {
// wtxid for coinbase is always 32 bytes of 0x00 // wtxid for coinbase is always 32 bytes of 0x00
if (forWitness && this.isCoinbase()) if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0);
return Buffer.alloc(32, 0);
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness));
} }
getId() { getId() {
@ -361,7 +388,8 @@ class Transaction {
} }
__byteLength(_ALLOW_WITNESS) { __byteLength(_ALLOW_WITNESS) {
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
return ((hasWitnesses ? 10 : 8) + return (
(hasWitnesses ? 10 : 8) +
varuint.encodingLength(this.ins.length) + varuint.encodingLength(this.ins.length) +
varuint.encodingLength(this.outs.length) + varuint.encodingLength(this.outs.length) +
this.ins.reduce((sum, input) => { this.ins.reduce((sum, input) => {
@ -374,11 +402,11 @@ class Transaction {
? this.ins.reduce((sum, input) => { ? this.ins.reduce((sum, input) => {
return sum + vectorSize(input.witness); return sum + vectorSize(input.witness);
}, 0) }, 0)
: 0)); : 0)
);
} }
__toBuffer(buffer, initialOffset, _ALLOW_WITNESS) { __toBuffer(buffer, initialOffset, _ALLOW_WITNESS) {
if (!buffer) if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS));
buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS));
let offset = initialOffset || 0; let offset = initialOffset || 0;
function writeSlice(slice) { function writeSlice(slice) {
offset += slice.copy(buffer, offset); offset += slice.copy(buffer, offset);
@ -424,8 +452,7 @@ class Transaction {
this.outs.forEach(txOut => { this.outs.forEach(txOut => {
if (isOutput(txOut)) { if (isOutput(txOut)) {
writeUInt64(txOut.value); writeUInt64(txOut.value);
} } else {
else {
writeSlice(txOut.valueBuffer); writeSlice(txOut.valueBuffer);
} }
writeVarSlice(txOut.script); writeVarSlice(txOut.script);
@ -437,8 +464,7 @@ class Transaction {
} }
writeUInt32(this.locktime); writeUInt32(this.locktime);
// avoid slicing unless necessary // avoid slicing unless necessary
if (initialOffset !== undefined) if (initialOffset !== undefined) return buffer.slice(initialOffset, offset);
return buffer.slice(initialOffset, offset);
return buffer; return buffer;
} }
} }

287
src/transaction_builder.js

@ -1,16 +1,16 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const baddress = require("./address"); const baddress = require('./address');
const bufferutils_1 = require("./bufferutils"); const bufferutils_1 = require('./bufferutils');
const classify = require("./classify"); const classify = require('./classify');
const bcrypto = require("./crypto"); const bcrypto = require('./crypto');
const ECPair = require("./ecpair"); const ECPair = require('./ecpair');
const networks = require("./networks"); const networks = require('./networks');
const payments = require("./payments"); const payments = require('./payments');
const bscript = require("./script"); const bscript = require('./script');
const script_1 = require("./script"); const script_1 = require('./script');
const transaction_1 = require("./transaction"); const transaction_1 = require('./transaction');
const types = require("./types"); const types = require('./types');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const SCRIPT_TYPES = classify.types; const SCRIPT_TYPES = classify.types;
function txIsString(tx) { function txIsString(tx) {
@ -56,11 +56,12 @@ class TransactionBuilder {
setLockTime(locktime) { setLockTime(locktime) {
typeforce(types.UInt32, locktime); typeforce(types.UInt32, locktime);
// if any signatures exist, throw // if any signatures exist, throw
if (this.__INPUTS.some(input => { if (
if (!input.signatures) this.__INPUTS.some(input => {
return false; if (!input.signatures) return false;
return input.signatures.some(s => s !== undefined); return input.signatures.some(s => s !== undefined);
})) { })
) {
throw new Error('No, this would invalidate signatures'); throw new Error('No, this would invalidate signatures');
} }
this.__TX.locktime = locktime; this.__TX.locktime = locktime;
@ -80,8 +81,7 @@ class TransactionBuilder {
// transaction hashs's are displayed in reverse order, un-reverse it // transaction hashs's are displayed in reverse order, un-reverse it
txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex'));
// is it a Transaction object? // is it a Transaction object?
} } else if (txIsTransaction(txHash)) {
else if (txIsTransaction(txHash)) {
const txOut = txHash.outs[vout]; const txOut = txHash.outs[vout];
prevOutScript = txOut.script; prevOutScript = txOut.script;
value = txOut.value; value = txOut.value;
@ -113,16 +113,17 @@ class TransactionBuilder {
// TODO: remove keyPair.network matching in 4.0.0 // TODO: remove keyPair.network matching in 4.0.0
if (keyPair.network && keyPair.network !== this.network) if (keyPair.network && keyPair.network !== this.network)
throw new TypeError('Inconsistent network'); throw new TypeError('Inconsistent network');
if (!this.__INPUTS[vin]) if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin);
throw new Error('No input at index: ' + vin);
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; hashType = hashType || transaction_1.Transaction.SIGHASH_ALL;
if (this.__needsOutputs(hashType)) if (this.__needsOutputs(hashType))
throw new Error('Transaction needs outputs'); throw new Error('Transaction needs outputs');
const input = this.__INPUTS[vin]; const input = this.__INPUTS[vin];
// if redeemScript was previously provided, enforce consistency // if redeemScript was previously provided, enforce consistency
if (input.redeemScript !== undefined && if (
input.redeemScript !== undefined &&
redeemScript && redeemScript &&
!input.redeemScript.equals(redeemScript)) { !input.redeemScript.equals(redeemScript)
) {
throw new Error('Inconsistent redeemScript'); throw new Error('Inconsistent redeemScript');
} }
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); const ourPubKey = keyPair.publicKey || keyPair.getPublicKey();
@ -134,37 +135,48 @@ class TransactionBuilder {
input.value = witnessValue; input.value = witnessValue;
} }
if (!canSign(input)) { if (!canSign(input)) {
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript); const prepared = prepareInput(
input,
ourPubKey,
redeemScript,
witnessScript,
);
// updates inline // updates inline
Object.assign(input, prepared); Object.assign(input, prepared);
} }
if (!canSign(input)) if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
throw Error(input.prevOutType + ' not supported');
} }
// ready to sign // ready to sign
let signatureHash; let signatureHash;
if (input.hasWitness) { if (input.hasWitness) {
signatureHash = this.__TX.hashForWitnessV0(vin, input.signScript, input.value, hashType); signatureHash = this.__TX.hashForWitnessV0(
} vin,
else { input.signScript,
signatureHash = this.__TX.hashForSignature(vin, input.signScript, hashType); input.value,
hashType,
);
} else {
signatureHash = this.__TX.hashForSignature(
vin,
input.signScript,
hashType,
);
} }
// enforce in order signing of public keys // enforce in order signing of public keys
const signed = input.pubkeys.some((pubKey, i) => { const signed = input.pubkeys.some((pubKey, i) => {
if (!ourPubKey.equals(pubKey)) if (!ourPubKey.equals(pubKey)) return false;
return false; if (input.signatures[i]) throw new Error('Signature already exists');
if (input.signatures[i])
throw new Error('Signature already exists');
// TODO: add tests // TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) { if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); throw new Error(
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
);
} }
const signature = keyPair.sign(signatureHash); const signature = keyPair.sign(signatureHash);
input.signatures[i] = bscript.signature.encode(signature, hashType); input.signatures[i] = bscript.signature.encode(signature, hashType);
return true; return true;
}); });
if (!signed) if (!signed) throw new Error('Key pair cannot sign for this input');
throw new Error('Key pair cannot sign for this input');
} }
__addInputUnsafe(txHash, vout, options) { __addInputUnsafe(txHash, vout, options) {
if (transaction_1.Transaction.isCoinbaseHash(txHash)) { if (transaction_1.Transaction.isCoinbaseHash(txHash)) {
@ -196,17 +208,20 @@ class TransactionBuilder {
input.prevOutScript = options.prevOutScript; input.prevOutScript = options.prevOutScript;
input.prevOutType = prevOutType || classify.output(options.prevOutScript); input.prevOutType = prevOutType || classify.output(options.prevOutScript);
} }
const vin = this.__TX.addInput(txHash, vout, options.sequence, options.scriptSig); const vin = this.__TX.addInput(
txHash,
vout,
options.sequence,
options.scriptSig,
);
this.__INPUTS[vin] = input; this.__INPUTS[vin] = input;
this.__PREV_TX_SET[prevTxOut] = true; this.__PREV_TX_SET[prevTxOut] = true;
return vin; return vin;
} }
__build(allowIncomplete) { __build(allowIncomplete) {
if (!allowIncomplete) { if (!allowIncomplete) {
if (!this.__TX.ins.length) if (!this.__TX.ins.length) throw new Error('Transaction has no inputs');
throw new Error('Transaction has no inputs'); if (!this.__TX.outs.length) throw new Error('Transaction has no outputs');
if (!this.__TX.outs.length)
throw new Error('Transaction has no outputs');
} }
const tx = this.__TX.clone(); const tx = this.__TX.clone();
// create script signatures from inputs // create script signatures from inputs
@ -217,8 +232,7 @@ class TransactionBuilder {
if (!result) { if (!result) {
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD)
throw new Error('Unknown input type'); throw new Error('Unknown input type');
if (!allowIncomplete) if (!allowIncomplete) throw new Error('Not enough information');
throw new Error('Not enough information');
return; return;
} }
tx.setInputScript(i, result.input); tx.setInputScript(i, result.input);
@ -234,15 +248,15 @@ class TransactionBuilder {
} }
__canModifyInputs() { __canModifyInputs() {
return this.__INPUTS.every(input => { return this.__INPUTS.every(input => {
if (!input.signatures) if (!input.signatures) return true;
return true;
return input.signatures.every(signature => { return input.signatures.every(signature => {
if (!signature) if (!signature) return true;
return true;
const hashType = signatureHashType(signature); const hashType = signatureHashType(signature);
// if SIGHASH_ANYONECANPAY is set, signatures would not // if SIGHASH_ANYONECANPAY is set, signatures would not
// be invalidated by more inputs // be invalidated by more inputs
return (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0; return (
(hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0
);
}); });
}); });
} }
@ -252,33 +266,29 @@ class TransactionBuilder {
} }
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
// .build() will fail, but .buildIncomplete() is OK // .build() will fail, but .buildIncomplete() is OK
return (this.__TX.outs.length === 0 && return (
this.__TX.outs.length === 0 &&
this.__INPUTS.some(input => { this.__INPUTS.some(input => {
if (!input.signatures) if (!input.signatures) return false;
return false;
return input.signatures.some(signature => { return input.signatures.some(signature => {
if (!signature) if (!signature) return false; // no signature, no issue
return false; // no signature, no issue
const hashType = signatureHashType(signature); const hashType = signatureHashType(signature);
if (hashType & transaction_1.Transaction.SIGHASH_NONE) if (hashType & transaction_1.Transaction.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs
return false; // SIGHASH_NONE doesn't care about outputs
return true; // SIGHASH_* does care return true; // SIGHASH_* does care
}); });
})); })
);
} }
__canModifyOutputs() { __canModifyOutputs() {
const nInputs = this.__TX.ins.length; const nInputs = this.__TX.ins.length;
const nOutputs = this.__TX.outs.length; const nOutputs = this.__TX.outs.length;
return this.__INPUTS.every(input => { return this.__INPUTS.every(input => {
if (input.signatures === undefined) if (input.signatures === undefined) return true;
return true;
return input.signatures.every(signature => { return input.signatures.every(signature => {
if (!signature) if (!signature) return true;
return true;
const hashType = signatureHashType(signature); const hashType = signatureHashType(signature);
const hashTypeMod = hashType & 0x1f; const hashTypeMod = hashType & 0x1f;
if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) return true;
return true;
if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) {
// if SIGHASH_SINGLE is set, and nInputs > nOutputs // if SIGHASH_SINGLE is set, and nInputs > nOutputs
// some signatures would be invalidated by the addition // some signatures would be invalidated by the addition
@ -302,15 +312,12 @@ class TransactionBuilder {
} }
exports.TransactionBuilder = TransactionBuilder; exports.TransactionBuilder = TransactionBuilder;
function expandInput(scriptSig, witnessStack, type, scriptPubKey) { function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
if (scriptSig.length === 0 && witnessStack.length === 0) if (scriptSig.length === 0 && witnessStack.length === 0) return {};
return {};
if (!type) { if (!type) {
let ssType = classify.input(scriptSig, true); let ssType = classify.input(scriptSig, true);
let wsType = classify.witness(witnessStack, true); let wsType = classify.witness(witnessStack, true);
if (ssType === SCRIPT_TYPES.NONSTANDARD) if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined;
ssType = undefined; if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined;
if (wsType === SCRIPT_TYPES.NONSTANDARD)
wsType = undefined;
type = ssType || wsType; type = ssType || wsType;
} }
switch (type) { switch (type) {
@ -345,10 +352,13 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
}; };
} }
case SCRIPT_TYPES.P2MS: { case SCRIPT_TYPES.P2MS: {
const { m, pubkeys, signatures } = payments.p2ms({ const { m, pubkeys, signatures } = payments.p2ms(
{
input: scriptSig, input: scriptSig,
output: scriptPubKey, output: scriptPubKey,
}, { allowIncomplete: true }); },
{ allowIncomplete: true },
);
return { return {
prevOutType: SCRIPT_TYPES.P2MS, prevOutType: SCRIPT_TYPES.P2MS,
pubkeys, pubkeys,
@ -363,9 +373,13 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
witness: witnessStack, witness: witnessStack,
}); });
const outputType = classify.output(redeem.output); const outputType = classify.output(redeem.output);
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output); const expanded = expandInput(
if (!expanded.prevOutType) redeem.input,
return {}; redeem.witness,
outputType,
redeem.output,
);
if (!expanded.prevOutType) return {};
return { return {
prevOutScript: output, prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2SH, prevOutType: SCRIPT_TYPES.P2SH,
@ -386,12 +400,15 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
let expanded; let expanded;
if (outputType === SCRIPT_TYPES.P2WPKH) { if (outputType === SCRIPT_TYPES.P2WPKH) {
expanded = expandInput(redeem.input, redeem.witness, outputType); expanded = expandInput(redeem.input, redeem.witness, outputType);
} } else {
else { expanded = expandInput(
expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output); bscript.compile(redeem.witness),
} [],
if (!expanded.prevOutType) outputType,
return {}; redeem.output,
);
}
if (!expanded.prevOutType) return {};
return { return {
prevOutScript: output, prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WSH, prevOutType: SCRIPT_TYPES.P2WSH,
@ -410,8 +427,7 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
function fixMultisigOrder(input, transaction, vin) { function fixMultisigOrder(input, transaction, vin) {
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript)
return; return;
if (input.pubkeys.length === input.signatures.length) if (input.pubkeys.length === input.signatures.length) return;
return;
const unmatched = input.signatures.concat(); const unmatched = input.signatures.concat();
input.signatures = input.pubkeys.map(pubKey => { input.signatures = input.pubkeys.map(pubKey => {
const keyPair = ECPair.fromPublicKey(pubKey); const keyPair = ECPair.fromPublicKey(pubKey);
@ -419,14 +435,16 @@ function fixMultisigOrder(input, transaction, vin) {
// check for a signature // check for a signature
unmatched.some((signature, i) => { unmatched.some((signature, i) => {
// skip if undefined || OP_0 // skip if undefined || OP_0
if (!signature) if (!signature) return false;
return false;
// TODO: avoid O(n) hashForSignature // TODO: avoid O(n) hashForSignature
const parsed = bscript.signature.decode(signature); const parsed = bscript.signature.decode(signature);
const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType); const hash = transaction.hashForSignature(
vin,
input.redeemScript,
parsed.hashType,
);
// skip if signature does not match pubKey // skip if signature does not match pubKey
if (!keyPair.verify(hash, parsed.signature)) if (!keyPair.verify(hash, parsed.signature)) return false;
return false;
// remove matched signature from unmatched // remove matched signature from unmatched
unmatched[i] = undefined; unmatched[i] = undefined;
match = signature; match = signature;
@ -440,13 +458,11 @@ function expandOutput(script, ourPubKey) {
const type = classify.output(script); const type = classify.output(script);
switch (type) { switch (type) {
case SCRIPT_TYPES.P2PKH: { case SCRIPT_TYPES.P2PKH: {
if (!ourPubKey) if (!ourPubKey) return { type };
return { type };
// does our hash160(pubKey) match the output scripts? // does our hash160(pubKey) match the output scripts?
const pkh1 = payments.p2pkh({ output: script }).hash; const pkh1 = payments.p2pkh({ output: script }).hash;
const pkh2 = bcrypto.hash160(ourPubKey); const pkh2 = bcrypto.hash160(ourPubKey);
if (!pkh1.equals(pkh2)) if (!pkh1.equals(pkh2)) return { type };
return { type };
return { return {
type, type,
pubkeys: [ourPubKey], pubkeys: [ourPubKey],
@ -454,13 +470,11 @@ function expandOutput(script, ourPubKey) {
}; };
} }
case SCRIPT_TYPES.P2WPKH: { case SCRIPT_TYPES.P2WPKH: {
if (!ourPubKey) if (!ourPubKey) return { type };
return { type };
// does our hash160(pubKey) match the output scripts? // does our hash160(pubKey) match the output scripts?
const wpkh1 = payments.p2wpkh({ output: script }).hash; const wpkh1 = payments.p2wpkh({ output: script }).hash;
const wpkh2 = bcrypto.hash160(ourPubKey); const wpkh2 = bcrypto.hash160(ourPubKey);
if (!wpkh1.equals(wpkh2)) if (!wpkh1.equals(wpkh2)) return { type };
return { type };
return { return {
type, type,
pubkeys: [ourPubKey], pubkeys: [ourPubKey],
@ -502,10 +516,12 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
throw new Error('Redeem script inconsistent with prevOutScript'); throw new Error('Redeem script inconsistent with prevOutScript');
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); const expanded = expandOutput(p2wsh.redeem.output, ourPubKey);
if (!expanded.pubkeys) if (!expanded.pubkeys)
throw new Error(expanded.type + throw new Error(
expanded.type +
' not supported as witnessScript (' + ' not supported as witnessScript (' +
bscript.toASM(witnessScript) + bscript.toASM(witnessScript) +
')'); ')',
);
if (input.signatures && input.signatures.some(x => x !== undefined)) { if (input.signatures && input.signatures.some(x => x !== undefined)) {
expanded.signatures = input.signatures; expanded.signatures = input.signatures;
} }
@ -533,8 +549,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
let p2shAlt; let p2shAlt;
try { try {
p2shAlt = payments.p2sh({ output: input.prevOutScript }); p2shAlt = payments.p2sh({ output: input.prevOutScript });
} } catch (e) {
catch (e) {
throw new Error('PrevOutScript must be P2SH'); throw new Error('PrevOutScript must be P2SH');
} }
if (!p2sh.hash.equals(p2shAlt.hash)) if (!p2sh.hash.equals(p2shAlt.hash))
@ -542,10 +557,12 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
} }
const expanded = expandOutput(p2sh.redeem.output, ourPubKey); const expanded = expandOutput(p2sh.redeem.output, ourPubKey);
if (!expanded.pubkeys) if (!expanded.pubkeys)
throw new Error(expanded.type + throw new Error(
expanded.type +
' not supported as redeemScript (' + ' not supported as redeemScript (' +
bscript.toASM(redeemScript) + bscript.toASM(redeemScript) +
')'); ')',
);
if (input.signatures && input.signatures.some(x => x !== undefined)) { if (input.signatures && input.signatures.some(x => x !== undefined)) {
expanded.signatures = input.signatures; expanded.signatures = input.signatures;
} }
@ -575,10 +592,12 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
} }
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); const expanded = expandOutput(p2wsh.redeem.output, ourPubKey);
if (!expanded.pubkeys) if (!expanded.pubkeys)
throw new Error(expanded.type + throw new Error(
expanded.type +
' not supported as witnessScript (' + ' not supported as witnessScript (' +
bscript.toASM(witnessScript) + bscript.toASM(witnessScript) +
')'); ')',
);
if (input.signatures && input.signatures.some(x => x !== undefined)) { if (input.signatures && input.signatures.some(x => x !== undefined)) {
expanded.signatures = input.signatures; expanded.signatures = input.signatures;
} }
@ -601,24 +620,28 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
if (input.prevOutType && input.prevOutScript) { if (input.prevOutType && input.prevOutScript) {
// embedded scripts are not possible without extra information // embedded scripts are not possible without extra information
if (input.prevOutType === SCRIPT_TYPES.P2SH) if (input.prevOutType === SCRIPT_TYPES.P2SH)
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript'); throw new Error(
'PrevOutScript is ' + input.prevOutType + ', requires redeemScript',
);
if (input.prevOutType === SCRIPT_TYPES.P2WSH) if (input.prevOutType === SCRIPT_TYPES.P2WSH)
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript'); throw new Error(
if (!input.prevOutScript) 'PrevOutScript is ' + input.prevOutType + ', requires witnessScript',
throw new Error('PrevOutScript is missing'); );
if (!input.prevOutScript) throw new Error('PrevOutScript is missing');
const expanded = expandOutput(input.prevOutScript, ourPubKey); const expanded = expandOutput(input.prevOutScript, ourPubKey);
if (!expanded.pubkeys) if (!expanded.pubkeys)
throw new Error(expanded.type + throw new Error(
expanded.type +
' not supported (' + ' not supported (' +
bscript.toASM(input.prevOutScript) + bscript.toASM(input.prevOutScript) +
')'); ')',
);
if (input.signatures && input.signatures.some(x => x !== undefined)) { if (input.signatures && input.signatures.some(x => x !== undefined)) {
expanded.signatures = input.signatures; expanded.signatures = input.signatures;
} }
let signScript = input.prevOutScript; let signScript = input.prevOutScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH) { if (expanded.type === SCRIPT_TYPES.P2WPKH) {
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }) signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output;
.output;
} }
return { return {
prevOutType: expanded.type, prevOutType: expanded.type,
@ -643,47 +666,42 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
}; };
} }
function build(type, input, allowIncomplete) { function build(type, input, allowIncomplete) {
const pubkeys = (input.pubkeys || []); const pubkeys = input.pubkeys || [];
let signatures = (input.signatures || []); let signatures = input.signatures || [];
switch (type) { switch (type) {
case SCRIPT_TYPES.P2PKH: { case SCRIPT_TYPES.P2PKH: {
if (pubkeys.length === 0) if (pubkeys.length === 0) break;
break; if (signatures.length === 0) break;
if (signatures.length === 0)
break;
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] });
} }
case SCRIPT_TYPES.P2WPKH: { case SCRIPT_TYPES.P2WPKH: {
if (pubkeys.length === 0) if (pubkeys.length === 0) break;
break; if (signatures.length === 0) break;
if (signatures.length === 0)
break;
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] });
} }
case SCRIPT_TYPES.P2PK: { case SCRIPT_TYPES.P2PK: {
if (pubkeys.length === 0) if (pubkeys.length === 0) break;
break; if (signatures.length === 0) break;
if (signatures.length === 0)
break;
return payments.p2pk({ signature: signatures[0] }); return payments.p2pk({ signature: signatures[0] });
} }
case SCRIPT_TYPES.P2MS: { case SCRIPT_TYPES.P2MS: {
const m = input.maxSignatures; const m = input.maxSignatures;
if (allowIncomplete) { if (allowIncomplete) {
signatures = signatures.map(x => x || script_1.OPS.OP_0); signatures = signatures.map(x => x || script_1.OPS.OP_0);
} } else {
else {
signatures = signatures.filter(x => x); signatures = signatures.filter(x => x);
} }
// if the transaction is not not complete (complete), or if signatures.length === m, validate // if the transaction is not not complete (complete), or if signatures.length === m, validate
// otherwise, the number of OP_0's may be >= m, so don't validate (boo) // otherwise, the number of OP_0's may be >= m, so don't validate (boo)
const validate = !allowIncomplete || m === signatures.length; const validate = !allowIncomplete || m === signatures.length;
return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }); return payments.p2ms(
{ m, pubkeys, signatures },
{ allowIncomplete, validate },
);
} }
case SCRIPT_TYPES.P2SH: { case SCRIPT_TYPES.P2SH: {
const redeem = build(input.redeemScriptType, input, allowIncomplete); const redeem = build(input.redeemScriptType, input, allowIncomplete);
if (!redeem) if (!redeem) return;
return;
return payments.p2sh({ return payments.p2sh({
redeem: { redeem: {
output: redeem.output || input.redeemScript, output: redeem.output || input.redeemScript,
@ -694,8 +712,7 @@ function build(type, input, allowIncomplete) {
} }
case SCRIPT_TYPES.P2WSH: { case SCRIPT_TYPES.P2WSH: {
const redeem = build(input.witnessScriptType, input, allowIncomplete); const redeem = build(input.witnessScriptType, input, allowIncomplete);
if (!redeem) if (!redeem) return;
return;
return payments.p2wsh({ return payments.p2wsh({
redeem: { redeem: {
output: input.witnessScript, output: input.witnessScript,
@ -707,13 +724,15 @@ function build(type, input, allowIncomplete) {
} }
} }
function canSign(input) { function canSign(input) {
return (input.signScript !== undefined && return (
input.signScript !== undefined &&
input.signType !== undefined && input.signType !== undefined &&
input.pubkeys !== undefined && input.pubkeys !== undefined &&
input.signatures !== undefined && input.signatures !== undefined &&
input.signatures.length === input.pubkeys.length && input.signatures.length === input.pubkeys.length &&
input.pubkeys.length > 0 && input.pubkeys.length > 0 &&
(input.hasWitness === false || input.value !== undefined)); (input.hasWitness === false || input.value !== undefined)
);
} }
function signatureHashType(buffer) { function signatureHashType(buffer) {
return buffer.readUInt8(buffer.length - 1); return buffer.readUInt8(buffer.length - 1);

4
src/types.js

@ -1,5 +1,5 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const UINT31_MAX = Math.pow(2, 31) - 1; const UINT31_MAX = Math.pow(2, 31) - 1;
function UInt31(value) { function UInt31(value) {

Loading…
Cancel
Save