Browse Source

Merge pull request #1343 from d-yokoi/prettier

replace standard with prettier and apply format
fixTypes
Jonathan Underwood 6 years ago
committed by GitHub
parent
commit
7b8d8a1c4f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      .prettierignore
  2. 4
      .prettierrc.json
  3. 2
      .travis.yml
  4. 17
      package.json
  5. 3
      src/address.js
  6. 21
      src/block.js
  7. 2
      src/classify.js
  8. 12
      src/crypto.js
  9. 13
      src/ecpair.js
  10. 12
      src/networks.js
  11. 5
      src/payments/embed.js
  12. 4
      src/payments/lazy.js
  13. 19
      src/payments/p2ms.js
  14. 17
      src/payments/p2pk.js
  15. 14
      src/payments/p2pkh.js
  16. 26
      src/payments/p2sh.js
  17. 15
      src/payments/p2wpkh.js
  18. 33
      src/payments/p2wsh.js
  19. 14
      src/script.js
  20. 19
      src/script_number.js
  21. 9
      src/script_signature.js
  22. 7
      src/templates/nulldata.js
  23. 62
      src/transaction.js
  24. 116
      src/transaction_builder.js
  25. 8
      src/types.js
  26. 2
      test/ecpair.js
  27. 120
      ts_src/address.ts
  28. 373
      ts_src/block.ts
  29. 57
      ts_src/bufferutils.ts
  30. 96
      ts_src/classify.ts
  31. 28
      ts_src/crypto.ts
  32. 174
      ts_src/ecpair.ts
  33. 42
      ts_src/index.ts
  34. 22
      ts_src/networks.ts
  35. 91
      ts_src/payments/embed.ts
  36. 50
      ts_src/payments/index.ts
  37. 34
      ts_src/payments/lazy.ts
  38. 239
      ts_src/payments/p2ms.ts
  39. 114
      ts_src/payments/p2pk.ts
  40. 213
      ts_src/payments/p2pkh.ts
  41. 269
      ts_src/payments/p2sh.ts
  42. 212
      ts_src/payments/p2wpkh.ts
  43. 241
      ts_src/payments/p2wsh.ts
  44. 244
      ts_src/script.ts
  45. 77
      ts_src/script_number.ts
  46. 88
      ts_src/script_signature.ts
  47. 21
      ts_src/templates/nulldata.ts
  48. 712
      ts_src/transaction.ts
  49. 867
      ts_src/transaction_builder.ts
  50. 64
      ts_src/types.ts
  51. 2
      types/index.d.ts

0
.prettierignore

4
.prettierrc.json

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

2
.travis.yml

@ -7,7 +7,7 @@ node_js:
matrix: matrix:
include: include:
- node_js: "lts/*" - node_js: "lts/*"
env: TEST_SUITE=standard env: TEST_SUITE=format:ci
- node_js: "lts/*" - node_js: "lts/*"
env: TEST_SUITE=coverage env: TEST_SUITE=coverage
env: env:

17
package.json

@ -19,16 +19,17 @@
"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:ci": "npm run prettier -- --check",
"integration": "npm run build && npm run nobuild:integration", "integration": "npm run build && npm run nobuild:integration",
"nobuild:coverage-report": "nyc report --reporter=lcov", "nobuild:coverage-report": "nyc report --reporter=lcov",
"nobuild:coverage-html": "nyc report --reporter=html", "nobuild:coverage-html": "nyc report --reporter=html",
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha", "nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha",
"nobuild:integration": "mocha --timeout 50000 test/integration/", "nobuild:integration": "mocha --timeout 50000 test/integration/",
"nobuild:standard": "standard ts_src/**/*.ts",
"nobuild:unit": "mocha", "nobuild:unit": "mocha",
"prepare": "npm run build", "prepare": "npm run build",
"standard": "npm run build && npm run nobuild:standard", "prettier": "prettier ts_src/*.ts ts_src/**/*.ts --ignore-path ./.prettierignore",
"test": "npm run build && npm run nobuild:standard && npm run nobuild:coverage", "test": "npm run build && npm run format:ci && npm run nobuild:coverage",
"unit": "npm run build && npm run nobuild:unit" "unit": "npm run build && npm run nobuild:unit"
}, },
"repository": { "repository": {
@ -68,16 +69,10 @@
"minimaldata": "^1.0.2", "minimaldata": "^1.0.2",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"nyc": "^11.8.0", "nyc": "^11.8.0",
"prettier": "^1.16.4",
"proxyquire": "^2.0.1", "proxyquire": "^2.0.1",
"standard": "^11.0.1",
"typescript": "3.2.2", "typescript": "3.2.2",
"typescript-eslint-parser": "^21.0.2" "typescript-eslint-parser": "^21.0.2"
}, },
"license": "MIT", "license": "MIT"
"standard": {
"parser": "typescript-eslint-parser",
"plugins": [
"typescript"
]
}
} }

3
src/address.js

@ -25,7 +25,7 @@ function fromBech32(address) {
return { return {
version: result.words[0], version: result.words[0],
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data) data: Buffer.from(data),
}; };
} }
exports.fromBech32 = fromBech32; exports.fromBech32 = fromBech32;
@ -44,6 +44,7 @@ function toBech32(data, version, prefix) {
} }
exports.toBech32 = toBech32; exports.toBech32 = toBech32;
function fromOutputScript(output, network) { function fromOutputScript(output, network) {
//TODO: Network
network = network || networks.bitcoin; network = network || networks.bitcoin;
try { try {
return payments.p2pkh({ output, network }).address; return payments.p2pkh({ output, network }).address;

21
src/block.js

@ -10,22 +10,22 @@ const varuint = require('varuint-bitcoin');
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions');
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit 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 instanceof Array &&
transactions.some(tx => typeof tx === 'object' && 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)));
} }
class Block { class Block {
constructor() { constructor() {
@ -116,9 +116,7 @@ class Block {
// 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.
let witnessCommits = this.transactions[0].outs let witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38));
.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')))
.map(out => out.script.slice(6, 38));
if (witnessCommits.length === 0) if (witnessCommits.length === 0)
return null; 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)
@ -141,8 +139,9 @@ class Block {
byteLength(headersOnly) { byteLength(headersOnly) {
if (headersOnly || !this.transactions) if (headersOnly || !this.transactions)
return 80; return 80;
return 80 + varuint.encodingLength(this.transactions.length) + return (80 +
this.transactions.reduce((a, x) => a + x.byteLength(), 0); varuint.encodingLength(this.transactions.length) +
this.transactions.reduce((a, x) => a + x.byteLength(), 0));
} }
getHash() { getHash() {
return bcrypto.hash256(this.toBuffer(true)); return bcrypto.hash256(this.toBuffer(true));
@ -197,8 +196,8 @@ class Block {
let hasWitnessCommit = this.hasWitnessCommit(); let hasWitnessCommit = this.hasWitnessCommit();
if (!hasWitnessCommit && this.hasWitness()) if (!hasWitnessCommit && this.hasWitness())
return false; return false;
return this.__checkMerkleRoot() && return (this.__checkMerkleRoot() &&
(hasWitnessCommit ? this.__checkWitnessCommit() : true); (hasWitnessCommit ? this.__checkWitnessCommit() : true));
} }
checkMerkleRoot() { checkMerkleRoot() {
console.warn('Deprecation Warning: Block method checkMerkleRoot will be ' + console.warn('Deprecation Warning: Block method checkMerkleRoot will be ' +

2
src/classify.js

@ -18,7 +18,7 @@ const types = {
P2SH: 'scripthash', P2SH: 'scripthash',
P2WPKH: 'witnesspubkeyhash', P2WPKH: 'witnesspubkeyhash',
P2WSH: 'witnessscripthash', P2WSH: 'witnessscripthash',
WITNESS_COMMITMENT: 'witnesscommitment' WITNESS_COMMITMENT: 'witnesscommitment',
}; };
exports.types = types; exports.types = types;
function classifyOutput(script) { function classifyOutput(script) {

12
src/crypto.js

@ -2,15 +2,21 @@
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) {
return createHash('rmd160').update(buffer).digest(); return createHash('rmd160')
.update(buffer)
.digest();
} }
exports.ripemd160 = ripemd160; exports.ripemd160 = ripemd160;
function sha1(buffer) { function sha1(buffer) {
return createHash('sha1').update(buffer).digest(); return createHash('sha1')
.update(buffer)
.digest();
} }
exports.sha1 = sha1; exports.sha1 = sha1;
function sha256(buffer) { function sha256(buffer) {
return createHash('sha256').update(buffer).digest(); return createHash('sha256')
.update(buffer)
.digest();
} }
exports.sha256 = sha256; exports.sha256 = sha256;
function hash160(buffer) { function hash160(buffer) {

13
src/ecpair.js

@ -8,13 +8,14 @@ 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) {
if (options === undefined) if (options === undefined)
options = {}; options = {};
this.compressed = options.compressed === undefined ? true : options.compressed; this.compressed =
options.compressed === undefined ? true : options.compressed;
this.network = options.network || NETWORKS.bitcoin; this.network = options.network || NETWORKS.bitcoin;
this.__d = undefined; this.__d = undefined;
this.__Q = undefined; this.__Q = undefined;
@ -64,9 +65,11 @@ function fromWIF(string, network) {
const version = decoded.version; const version = decoded.version;
// list of networks? // list of networks?
if (types.Array(network)) { if (types.Array(network)) {
network = network.filter(function (x) { network = network
.filter(function (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)
@ -78,7 +81,7 @@ function fromWIF(string, network) {
} }
return fromPrivateKey(decoded.privateKey, { return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed, compressed: decoded.compressed,
network: network network: network,
}); });
} }
exports.fromWIF = fromWIF; exports.fromWIF = fromWIF;

12
src/networks.js

@ -5,31 +5,31 @@ exports.bitcoin = {
bech32: 'bc', bech32: 'bc',
bip32: { bip32: {
public: 0x0488b21e, public: 0x0488b21e,
private: 0x0488ade4 private: 0x0488ade4,
}, },
pubKeyHash: 0x00, pubKeyHash: 0x00,
scriptHash: 0x05, scriptHash: 0x05,
wif: 0x80 wif: 0x80,
}; };
exports.regtest = { exports.regtest = {
messagePrefix: '\x18Bitcoin Signed Message:\n', messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt', bech32: 'bcrt',
bip32: { bip32: {
public: 0x043587cf, public: 0x043587cf,
private: 0x04358394 private: 0x04358394,
}, },
pubKeyHash: 0x6f, pubKeyHash: 0x6f,
scriptHash: 0xc4, scriptHash: 0xc4,
wif: 0xef wif: 0xef,
}; };
exports.testnet = { exports.testnet = {
messagePrefix: '\x18Bitcoin Signed Message:\n', messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb', bech32: 'tb',
bip32: { bip32: {
public: 0x043587cf, public: 0x043587cf,
private: 0x04358394 private: 0x04358394,
}, },
pubKeyHash: 0x6f, pubKeyHash: 0x6f,
scriptHash: 0xc4, scriptHash: 0xc4,
wif: 0xef wif: 0xef,
}; };

5
src/payments/embed.js

@ -14,14 +14,13 @@ function stacksEqual(a, b) {
} }
// output: OP_RETURN ... // output: OP_RETURN ...
function p2data(a, opts) { function p2data(a, opts) {
if (!a.data && if (!a.data && !a.output)
!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 };

4
src/payments/lazy.js

@ -14,9 +14,9 @@ function prop(object, name, f) {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
value: value, value: value,
writable: true writable: true,
}); });
} },
}); });
} }
exports.prop = prop; exports.prop = prop;

19
src/payments/p2ms.js

@ -24,9 +24,8 @@ function p2ms(a, opts) {
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 (bscript.isCanonicalScriptSignature(x) ||
(opts.allowIncomplete && (opts.allowIncomplete && x === OPS.OP_0) !== undefined);
(x === OPS.OP_0)) !== undefined; // eslint-disable-line
} }
typef({ typef({
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
@ -35,7 +34,7 @@ function p2ms(a, opts) {
output: typef.maybe(typef.Buffer), output: typef.maybe(typef.Buffer),
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 };
@ -46,8 +45,8 @@ function p2ms(a, opts) {
return; return;
decoded = true; decoded = true;
chunks = bscript.decompile(output); chunks = bscript.decompile(output);
o.m = chunks[0] - OP_INT_BASE; // eslint-disable-line o.m = chunks[0] - OP_INT_BASE;
o.n = chunks[chunks.length - 2] - OP_INT_BASE; // eslint-disable-line o.n = chunks[chunks.length - 2] - OP_INT_BASE;
o.pubkeys = chunks.slice(1, -2); o.pubkeys = chunks.slice(1, -2);
} }
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function () {
@ -101,10 +100,7 @@ function p2ms(a, opts) {
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)
throw new TypeError('Output is invalid'); throw new TypeError('Output is invalid');
if (o.m <= 0 || // eslint-disable-line if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3)
o.n > 16 || // eslint-disable-line
o.m > o.n || // eslint-disable-line
o.n !== chunks.length - 3)
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');
@ -131,7 +127,8 @@ function p2ms(a, opts) {
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 (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) if (o.signatures.length === 0 ||
!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');

17
src/payments/p2pk.js

@ -9,11 +9,7 @@ const ecc = require('tiny-secp256k1');
// input: {signature} // input: {signature}
// output: {pubKey} OP_CHECKSIG // output: {pubKey} OP_CHECKSIG
function p2pk(a, opts) { function p2pk(a, opts) {
if (!a.input && if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature)
!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({
@ -21,18 +17,17 @@ function p2pk(a, opts) {
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(function () { return bscript.decompile(a.input); }); const _chunks = lazy.value(function () {
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', function () { lazy.prop(o, 'output', function () {
if (!a.pubkey) if (!a.pubkey)
return; return;
return bscript.compile([ return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]);
a.pubkey,
OPS.OP_CHECKSIG
]);
}); });
lazy.prop(o, 'pubkey', function () { lazy.prop(o, 'pubkey', function () {
if (!a.output) if (!a.output)

14
src/payments/p2pkh.js

@ -11,11 +11,7 @@ const bs58check = require('bs58check');
// input: {signature} {pubkey} // input: {signature} {pubkey}
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
function p2pkh(a, opts) { function p2pkh(a, opts) {
if (!a.address && if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input)
!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({
@ -25,7 +21,7 @@ function p2pkh(a, opts) {
output: typef.maybe(typef.BufferN(25)), output: typef.maybe(typef.BufferN(25)),
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(function () { const _address = lazy.value(function () {
const payload = bs58check.decode(a.address); const payload = bs58check.decode(a.address);
@ -33,7 +29,9 @@ function p2pkh(a, opts) {
const hash = payload.slice(1); const hash = payload.slice(1);
return { version, hash }; return { version, hash };
}); });
const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); const _chunks = lazy.value(function () {
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, 'address', function () { lazy.prop(o, 'address', function () {
@ -60,7 +58,7 @@ function p2pkh(a, opts) {
OPS.OP_HASH160, OPS.OP_HASH160,
o.hash, o.hash,
OPS.OP_EQUALVERIFY, OPS.OP_EQUALVERIFY,
OPS.OP_CHECKSIG OPS.OP_CHECKSIG,
]); ]);
}); });
lazy.prop(o, 'pubkey', function () { lazy.prop(o, 'pubkey', function () {

26
src/payments/p2sh.js

@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const networks_1 = require("../networks"); // eslint-disable-line const networks_1 = require("../networks");
const bscript = require("../script"); const bscript = require("../script");
const bcrypto = require("../crypto"); const bcrypto = require("../crypto");
const lazy = require("./lazy"); const lazy = require("./lazy");
@ -18,11 +18,7 @@ function stacksEqual(a, b) {
// witness: <?> // witness: <?>
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
function p2sh(a, opts) { function p2sh(a, opts) {
if (!a.address && if (!a.address && !a.hash && !a.output && !a.redeem && !a.input)
!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({
@ -34,10 +30,10 @@ function p2sh(a, opts) {
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer), output: typef.maybe(typef.Buffer),
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)) witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}), }),
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) {
@ -50,14 +46,16 @@ function p2sh(a, opts) {
const hash = payload.slice(1); const hash = payload.slice(1);
return { version, hash }; return { version, hash };
}); });
const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); const _chunks = lazy.value(function () {
return bscript.decompile(a.input);
});
const _redeem = lazy.value(function () { const _redeem = lazy.value(function () {
const chunks = _chunks(); const chunks = _chunks();
return { return {
network, network,
output: chunks[chunks.length - 1], output: chunks[chunks.length - 1],
input: bscript.compile(chunks.slice(0, -1)), input: bscript.compile(chunks.slice(0, -1)),
witness: a.witness || [] witness: a.witness || [],
}; };
}); });
// output dependents // output dependents
@ -81,11 +79,7 @@ function p2sh(a, opts) {
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function () {
if (!o.hash) if (!o.hash)
return; return;
return bscript.compile([ return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]);
OPS.OP_HASH160,
o.hash,
OPS.OP_EQUAL
]);
}); });
// input dependents // input dependents
lazy.prop(o, 'redeem', function () { lazy.prop(o, 'redeem', function () {
@ -153,7 +147,7 @@ function p2sh(a, opts) {
if (hasInput && hasWitness) if (hasInput && hasWitness)
throw new TypeError('Input and witness provided'); throw new TypeError('Input and witness provided');
if (hasInput) { if (hasInput) {
const richunks = bscript.decompile(redeem.input); const richunks = (bscript.decompile(redeem.input));
if (!bscript.isPushOnly(richunks)) if (!bscript.isPushOnly(richunks))
throw new TypeError('Non push-only scriptSig'); throw new TypeError('Non push-only scriptSig');
} }

15
src/payments/p2wpkh.js

@ -13,11 +13,7 @@ const EMPTY_BUFFER = Buffer.alloc(0);
// input: <> // input: <>
// output: OP_0 {pubKeyHash} // output: OP_0 {pubKeyHash}
function p2wpkh(a, opts) { function p2wpkh(a, opts) {
if (!a.address && if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
!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({
@ -28,7 +24,7 @@ function p2wpkh(a, opts) {
output: typef.maybe(typef.BufferN(22)), output: typef.maybe(typef.BufferN(22)),
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(function () { const _address = lazy.value(function () {
const result = bech32.decode(a.address); const result = bech32.decode(a.address);
@ -37,7 +33,7 @@ function p2wpkh(a, opts) {
return { return {
version, version,
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data) data: Buffer.from(data),
}; };
}); });
const network = a.network || networks_1.bitcoin; const network = a.network || networks_1.bitcoin;
@ -60,10 +56,7 @@ function p2wpkh(a, opts) {
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function () {
if (!o.hash) if (!o.hash)
return; return;
return bscript.compile([ return bscript.compile([OPS.OP_0, o.hash]);
OPS.OP_0,
o.hash
]);
}); });
lazy.prop(o, 'pubkey', function () { lazy.prop(o, 'pubkey', function () {
if (a.pubkey) if (a.pubkey)

33
src/payments/p2wsh.js

@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const networks_1 = require("../networks"); // eslint-disable-line const networks_1 = require("../networks");
const bscript = require("../script"); const bscript = require("../script");
const bcrypto = require("../crypto"); const bcrypto = require("../crypto");
const lazy = require("./lazy"); const lazy = require("./lazy");
@ -19,11 +19,7 @@ function stacksEqual(a, b) {
// witness: [redeemScriptSig ...] {redeemScript} // witness: [redeemScriptSig ...] {redeemScript}
// output: OP_0 {sha256(redeemScript)} // output: OP_0 {sha256(redeemScript)}
function p2wsh(a, opts) { function p2wsh(a, opts) {
if (!a.address && if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness)
!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({
@ -35,10 +31,10 @@ function p2wsh(a, opts) {
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer), output: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)) witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}), }),
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(function () { const _address = lazy.value(function () {
const result = bech32.decode(a.address); const result = bech32.decode(a.address);
@ -47,10 +43,12 @@ function p2wsh(a, opts) {
return { return {
version, version,
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data) data: Buffer.from(data),
}; };
}); });
const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input); }); const _rchunks = lazy.value(function () {
return bscript.decompile(a.redeem.input);
});
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;
@ -74,10 +72,7 @@ function p2wsh(a, opts) {
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function () {
if (!o.hash) if (!o.hash)
return; return;
return bscript.compile([ return bscript.compile([OPS.OP_0, o.hash]);
OPS.OP_0,
o.hash
]);
}); });
lazy.prop(o, 'redeem', function () { lazy.prop(o, 'redeem', function () {
if (!a.witness) if (!a.witness)
@ -85,7 +80,7 @@ function p2wsh(a, opts) {
return { return {
output: a.witness[a.witness.length - 1], output: a.witness[a.witness.length - 1],
input: EMPTY_BUFFER, input: EMPTY_BUFFER,
witness: a.witness.slice(0, -1) witness: a.witness.slice(0, -1),
}; };
}); });
lazy.prop(o, 'input', function () { lazy.prop(o, 'input', function () {
@ -165,11 +160,15 @@ function p2wsh(a, opts) {
} }
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 && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) if (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 && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) if (a.redeem &&
a.redeem.output &&
!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');
} }
} }

14
src/script.js

@ -11,10 +11,10 @@ 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);
@ -96,7 +96,7 @@ function decompile(buffer) {
while (i < buffer.length) { while (i < buffer.length) {
const opcode = buffer[i]; const opcode = buffer[i];
// data chunk // data chunk
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)
@ -129,7 +129,8 @@ function toASM(chunks) {
if (chunksIsBuffer(chunks)) { if (chunksIsBuffer(chunks)) {
chunks = decompile(chunks); chunks = decompile(chunks);
} }
return chunks.map(function (chunk) { return chunks
.map(function (chunk) {
// data? // data?
if (singleChunkIsBuffer(chunk)) { if (singleChunkIsBuffer(chunk)) {
const op = asMinimalOP(chunk); const op = asMinimalOP(chunk);
@ -139,7 +140,8 @@ function toASM(chunks) {
} }
// opcode! // opcode!
return REVERSE_OPS[chunk]; return REVERSE_OPS[chunk];
}).join(' '); })
.join(' ');
} }
exports.toASM = toASM; exports.toASM = toASM;
function fromASM(asm) { function fromASM(asm) {

19
src/script_number.js

@ -19,8 +19,8 @@ function decode(buffer, maxLength, minimal) {
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
let result = 0; let result = 0;
@ -33,11 +33,16 @@ function decode(buffer, maxLength, minimal) {
} }
exports.decode = decode; exports.decode = decode;
function scriptNumSize(i) { function scriptNumSize(i) {
return i > 0x7fffffff ? 5 return i > 0x7fffffff
: i > 0x7fffff ? 4 ? 5
: i > 0x7fff ? 3 : i > 0x7fffff
: i > 0x7f ? 2 ? 4
: i > 0x00 ? 1 : i > 0x7fff
? 3
: i > 0x7f
? 2
: i > 0x00
? 1
: 0; : 0;
} }
function encode(number) { function encode(number) {

9
src/script_signature.js

@ -34,14 +34,14 @@ function decode(buffer) {
const s = fromDER(decode.s); const s = fromDER(decode.s);
return { return {
signature: Buffer.concat([r, s], 64), signature: Buffer.concat([r, s], 64),
hashType: hashType hashType: hashType,
}; };
} }
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)
@ -50,9 +50,6 @@ function encode(signature, hashType) {
hashTypeBuffer.writeUInt8(hashType, 0); hashTypeBuffer.writeUInt8(hashType, 0);
const r = toDER(signature.slice(0, 32)); const r = toDER(signature.slice(0, 32));
const s = toDER(signature.slice(32, 64)); const s = toDER(signature.slice(32, 64));
return Buffer.concat([ return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]);
bip66.encode(r, s),
hashTypeBuffer
]);
} }
exports.encode = encode; exports.encode = encode;

7
src/templates/nulldata.js

@ -5,10 +5,11 @@ 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);
return buffer.length > 1 && return buffer.length > 1 && buffer[0] === OPS.OP_RETURN;
buffer[0] === OPS.OP_RETURN;
} }
exports.check = check; exports.check = check;
check.toJSON = function () { return 'null data output'; }; check.toJSON = function () {
return 'null data output';
};
const output = { check }; const output = { check };
exports.output = output; exports.output = output;

62
src/transaction.js

@ -14,9 +14,10 @@ function varSliceSize(someScript) {
} }
function vectorSize(someVector) { function vectorSize(someVector) {
const length = someVector.length; const length = someVector.length;
return varuint.encodingLength(length) + someVector.reduce((sum, witness) => { return (varuint.encodingLength(length) +
return sum + varSliceSize(witness); someVector.reduce((sum, witness) => {
}, 0); return sum + varSliceSize(witness);
}, 0));
} }
const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
const EMPTY_WITNESS = []; const EMPTY_WITNESS = [];
@ -25,7 +26,7 @@ const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000
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,
valueBuffer: VALUE_UINT64_MAX valueBuffer: VALUE_UINT64_MAX,
}; };
function isOutput(out) { function isOutput(out) {
return out.value !== undefined; return out.value !== undefined;
@ -90,14 +91,14 @@ class Transaction {
index: readUInt32(), index: readUInt32(),
script: readVarSlice(), script: readVarSlice(),
sequence: readUInt32(), sequence: readUInt32(),
witness: EMPTY_WITNESS witness: EMPTY_WITNESS,
}); });
} }
const voutLen = readVarInt(); const voutLen = readVarInt();
for (i = 0; i < voutLen; ++i) { for (i = 0; i < voutLen; ++i) {
tx.outs.push({ tx.outs.push({
value: readUInt64(), value: readUInt64(),
script: readVarSlice() script: readVarSlice(),
}); });
} }
if (hasWitnesses) { if (hasWitnesses) {
@ -127,7 +128,7 @@ class Transaction {
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);
@ -140,7 +141,7 @@ class Transaction {
index: index, 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) {
@ -148,11 +149,11 @@ class Transaction {
// 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 value: value,
}) - 1); }) - 1);
} }
hasWitnesses() { hasWitnesses() {
return this.ins.some((x) => { return this.ins.some(x => {
return x.witness.length !== 0; return x.witness.length !== 0;
}); });
} }
@ -178,27 +179,29 @@ class Transaction {
this.outs.reduce((sum, output) => { this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script); return sum + 8 + varSliceSize(output.script);
}, 0) + }, 0) +
(hasWitnesses ? this.ins.reduce((sum, input) => { (hasWitnesses
return sum + vectorSize(input.witness); ? this.ins.reduce((sum, input) => {
}, 0) : 0)); return sum + vectorSize(input.witness);
}, 0)
: 0));
} }
clone() { clone() {
const newTx = new Transaction(); const newTx = new Transaction();
newTx.version = this.version; newTx.version = this.version;
newTx.locktime = this.locktime; newTx.locktime = this.locktime;
newTx.ins = this.ins.map((txIn) => { newTx.ins = this.ins.map(txIn => {
return { return {
hash: txIn.hash, hash: txIn.hash,
index: txIn.index, index: txIn.index,
script: txIn.script, script: txIn.script,
sequence: txIn.sequence, sequence: txIn.sequence,
witness: txIn.witness witness: txIn.witness,
}; };
}); });
newTx.outs = this.outs.map((txOut) => { newTx.outs = this.outs.map(txOut => {
return { return {
script: txOut.script, script: txOut.script,
value: txOut.value value: txOut.value,
}; };
}); });
return newTx; return newTx;
@ -217,7 +220,7 @@ class Transaction {
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();
@ -257,7 +260,7 @@ class Transaction {
} }
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;
}); });
txTmp.ins[inIndex].script = ourScript; txTmp.ins[inIndex].script = ourScript;
@ -295,7 +298,7 @@ class Transaction {
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
tbuffer = Buffer.allocUnsafe(36 * this.ins.length); tbuffer = Buffer.allocUnsafe(36 * this.ins.length);
toffset = 0; toffset = 0;
this.ins.forEach((txIn) => { this.ins.forEach(txIn => {
writeSlice(txIn.hash); writeSlice(txIn.hash);
writeUInt32(txIn.index); writeUInt32(txIn.index);
}); });
@ -306,7 +309,7 @@ class Transaction {
(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 => {
writeUInt32(txIn.sequence); writeUInt32(txIn.sequence);
}); });
hashSequence = bcrypto.hash256(tbuffer); hashSequence = bcrypto.hash256(tbuffer);
@ -318,13 +321,14 @@ class Transaction {
}, 0); }, 0);
tbuffer = Buffer.allocUnsafe(txOutsSize); tbuffer = Buffer.allocUnsafe(txOutsSize);
toffset = 0; toffset = 0;
this.outs.forEach((out) => { this.outs.forEach(out => {
writeUInt64(out.value); writeUInt64(out.value);
writeVarSlice(out.script); writeVarSlice(out.script);
}); });
hashOutputs = bcrypto.hash256(tbuffer); hashOutputs = bcrypto.hash256(tbuffer);
} }
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
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;
@ -369,13 +373,13 @@ class Transaction {
offset += slice.copy(buffer, offset); offset += slice.copy(buffer, offset);
} }
function writeUInt8(i) { function writeUInt8(i) {
offset = (buffer).writeUInt8(i, offset); offset = buffer.writeUInt8(i, offset);
} }
function writeUInt32(i) { function writeUInt32(i) {
offset = (buffer).writeUInt32LE(i, offset); offset = buffer.writeUInt32LE(i, offset);
} }
function writeInt32(i) { function writeInt32(i) {
offset = (buffer).writeInt32LE(i, offset); offset = buffer.writeInt32LE(i, offset);
} }
function writeUInt64(i) { function writeUInt64(i) {
offset = bufferutils.writeUInt64LE(buffer, i, offset); offset = bufferutils.writeUInt64LE(buffer, i, offset);
@ -399,14 +403,14 @@ class Transaction {
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG);
} }
writeVarInt(this.ins.length); writeVarInt(this.ins.length);
this.ins.forEach((txIn) => { this.ins.forEach(txIn => {
writeSlice(txIn.hash); writeSlice(txIn.hash);
writeUInt32(txIn.index); writeUInt32(txIn.index);
writeVarSlice(txIn.script); writeVarSlice(txIn.script);
writeUInt32(txIn.sequence); writeUInt32(txIn.sequence);
}); });
writeVarInt(this.outs.length); writeVarInt(this.outs.length);
this.outs.forEach((txOut) => { this.outs.forEach(txOut => {
if (isOutput(txOut)) { if (isOutput(txOut)) {
writeUInt64(txOut.value); writeUInt64(txOut.value);
} }
@ -416,7 +420,7 @@ class Transaction {
writeVarSlice(txOut.script); writeVarSlice(txOut.script);
}); });
if (hasWitnesses) { if (hasWitnesses) {
this.ins.forEach((input) => { this.ins.forEach(input => {
writeVector(input.witness); writeVector(input.witness);
}); });
} }

116
src/transaction_builder.js

@ -43,7 +43,7 @@ class TransactionBuilder {
txb.__addInputUnsafe(txIn.hash, txIn.index, { txb.__addInputUnsafe(txIn.hash, txIn.index, {
sequence: txIn.sequence, sequence: txIn.sequence,
script: txIn.script, script: txIn.script,
witness: txIn.witness witness: txIn.witness,
}); });
}); });
// fix some things not possible through the public API // fix some things not possible through the public API
@ -89,7 +89,7 @@ class TransactionBuilder {
return this.__addInputUnsafe(txHash, vout, { return this.__addInputUnsafe(txHash, vout, {
sequence: sequence, sequence: sequence,
prevOutScript: prevOutScript, prevOutScript: prevOutScript,
value: value value: value,
}); });
} }
__addInputUnsafe(txHash, vout, options) { __addInputUnsafe(txHash, vout, options) {
@ -194,7 +194,7 @@ class TransactionBuilder {
if (!canSign(input)) { if (!canSign(input)) {
if (witnessValue !== undefined) { if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue) if (input.value !== undefined && input.value !== witnessValue)
throw new Error('Input didn\'t match witnessValue'); throw new Error("Input didn't match witnessValue");
typeforce(types.Satoshi, witnessValue); typeforce(types.Satoshi, witnessValue);
input.value = witnessValue; input.value = witnessValue;
} }
@ -251,18 +251,19 @@ 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) && this.__inputs.some((input) => { return (this.__tx.outs.length === 0 &&
if (!input.signatures) this.__inputs.some(input => {
return false; if (!input.signatures)
return input.signatures.some((signature) => { return false;
if (!signature) return input.signatures.some(signature => {
return false; // no signature, no issue if (!signature)
const hashType = signatureHashType(signature); return false; // no signature, no issue
if (hashType & transaction_1.Transaction.SIGHASH_NONE) const hashType = signatureHashType(signature);
return false; // SIGHASH_NONE doesn't care about outputs if (hashType & transaction_1.Transaction.SIGHASH_NONE)
return true; // SIGHASH_* does care return false; // SIGHASH_NONE doesn't care about outputs
}); return true; // SIGHASH_* does care
}); });
}));
} }
__canModifyOutputs() { __canModifyOutputs() {
const nInputs = this.__tx.ins.length; const nInputs = this.__tx.ins.length;
@ -313,21 +314,25 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
} }
switch (type) { switch (type) {
case SCRIPT_TYPES.P2WPKH: { case SCRIPT_TYPES.P2WPKH: {
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack }); const { output, pubkey, signature } = payments.p2wpkh({
witness: witnessStack,
});
return { return {
prevOutScript: output, prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WPKH, prevOutType: SCRIPT_TYPES.P2WPKH,
pubkeys: [pubkey], pubkeys: [pubkey],
signatures: [signature] signatures: [signature],
}; };
} }
case SCRIPT_TYPES.P2PKH: { case SCRIPT_TYPES.P2PKH: {
const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig }); const { output, pubkey, signature } = payments.p2pkh({
input: scriptSig,
});
return { return {
prevOutScript: output, prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2PKH, prevOutType: SCRIPT_TYPES.P2PKH,
pubkeys: [pubkey], pubkeys: [pubkey],
signatures: [signature] signatures: [signature],
}; };
} }
case SCRIPT_TYPES.P2PK: { case SCRIPT_TYPES.P2PK: {
@ -335,26 +340,26 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
return { return {
prevOutType: SCRIPT_TYPES.P2PK, prevOutType: SCRIPT_TYPES.P2PK,
pubkeys: [undefined], pubkeys: [undefined],
signatures: [signature] signatures: [signature],
}; };
} }
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, pubkeys: pubkeys,
signatures: signatures, signatures: signatures,
maxSignatures: m maxSignatures: m,
}; };
} }
} }
if (type === SCRIPT_TYPES.P2SH) { if (type === SCRIPT_TYPES.P2SH) {
const { output, redeem } = payments.p2sh({ const { output, redeem } = payments.p2sh({
input: scriptSig, input: scriptSig,
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(redeem.input, redeem.witness, outputType, redeem.output);
@ -368,13 +373,13 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
witnessScript: expanded.witnessScript, witnessScript: expanded.witnessScript,
witnessScriptType: expanded.witnessScriptType, witnessScriptType: expanded.witnessScriptType,
pubkeys: expanded.pubkeys, pubkeys: expanded.pubkeys,
signatures: expanded.signatures signatures: expanded.signatures,
}; };
} }
if (type === SCRIPT_TYPES.P2WSH) { if (type === SCRIPT_TYPES.P2WSH) {
const { output, redeem } = payments.p2wsh({ const { output, redeem } = payments.p2wsh({
input: scriptSig, input: scriptSig,
witness: witnessStack witness: witnessStack,
}); });
const outputType = classify.output(redeem.output); const outputType = classify.output(redeem.output);
let expanded; let expanded;
@ -392,12 +397,12 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
witnessScript: redeem.output, witnessScript: redeem.output,
witnessScriptType: expanded.prevOutType, witnessScriptType: expanded.prevOutType,
pubkeys: expanded.pubkeys, pubkeys: expanded.pubkeys,
signatures: expanded.signatures signatures: expanded.signatures,
}; };
} }
return { return {
prevOutType: SCRIPT_TYPES.NONSTANDARD, prevOutType: SCRIPT_TYPES.NONSTANDARD,
prevOutScript: scriptSig prevOutScript: scriptSig,
}; };
} }
// could be done in expandInput, but requires the original Transaction for hashForSignature // could be done in expandInput, but requires the original Transaction for hashForSignature
@ -444,7 +449,7 @@ function expandOutput(script, ourPubKey) {
return { return {
type, type,
pubkeys: [ourPubKey], pubkeys: [ourPubKey],
signatures: [undefined] signatures: [undefined],
}; };
} }
case SCRIPT_TYPES.P2WPKH: { case SCRIPT_TYPES.P2WPKH: {
@ -458,7 +463,7 @@ function expandOutput(script, ourPubKey) {
return { return {
type, type,
pubkeys: [ourPubKey], pubkeys: [ourPubKey],
signatures: [undefined] signatures: [undefined],
}; };
} }
case SCRIPT_TYPES.P2PK: { case SCRIPT_TYPES.P2PK: {
@ -466,7 +471,7 @@ function expandOutput(script, ourPubKey) {
return { return {
type, type,
pubkeys: [p2pk.pubkey], pubkeys: [p2pk.pubkey],
signatures: [undefined] signatures: [undefined],
}; };
} }
case SCRIPT_TYPES.P2MS: { case SCRIPT_TYPES.P2MS: {
@ -475,7 +480,7 @@ function expandOutput(script, ourPubKey) {
type, type,
pubkeys: p2ms.pubkeys, pubkeys: p2ms.pubkeys,
signatures: p2ms.pubkeys.map(() => undefined), signatures: p2ms.pubkeys.map(() => undefined),
maxSignatures: p2ms.m maxSignatures: p2ms.m,
}; };
} }
} }
@ -483,7 +488,7 @@ function expandOutput(script, ourPubKey) {
} }
function prepareInput(input, ourPubKey, redeemScript, witnessScript) { function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
if (redeemScript && witnessScript) { if (redeemScript && witnessScript) {
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); const p2wsh = (payments.p2wsh({ redeem: { output: witnessScript } }));
const p2wshAlt = payments.p2wsh({ output: redeemScript }); const p2wshAlt = payments.p2wsh({ output: redeemScript });
const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); const p2sh = payments.p2sh({ redeem: { output: redeemScript } });
const p2shAlt = payments.p2sh({ redeem: p2wsh }); const p2shAlt = payments.p2sh({ redeem: p2wsh });
@ -494,7 +499,10 @@ 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 + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')'); throw new Error(expanded.type +
' not supported as 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;
} }
@ -513,7 +521,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
signType: expanded.type, signType: expanded.type,
pubkeys: expanded.pubkeys, pubkeys: expanded.pubkeys,
signatures: expanded.signatures, signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures maxSignatures: expanded.maxSignatures,
}; };
} }
if (redeemScript) { if (redeemScript) {
@ -531,7 +539,10 @@ 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 + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')'); throw new Error(expanded.type +
' not supported as 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;
} }
@ -549,7 +560,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
signType: expanded.type, signType: expanded.type,
pubkeys: expanded.pubkeys, pubkeys: expanded.pubkeys,
signatures: expanded.signatures, signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures maxSignatures: expanded.maxSignatures,
}; };
} }
if (witnessScript) { if (witnessScript) {
@ -561,7 +572,10 @@ 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 + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')'); throw new Error(expanded.type +
' not supported as 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;
} }
@ -578,7 +592,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
signType: expanded.type, signType: expanded.type,
pubkeys: expanded.pubkeys, pubkeys: expanded.pubkeys,
signatures: expanded.signatures, signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures maxSignatures: expanded.maxSignatures,
}; };
} }
if (input.prevOutType && input.prevOutScript) { if (input.prevOutType && input.prevOutScript) {
@ -591,13 +605,16 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
throw new Error('PrevOutScript is missing'); 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 + ' not supported (' + bscript.toASM(input.prevOutScript) + ')'); throw new Error(expanded.type +
' not supported (' +
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] }).output; signScript = (payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output);
} }
return { return {
prevOutType: expanded.type, prevOutType: expanded.type,
@ -607,7 +624,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
signType: expanded.type, signType: expanded.type,
pubkeys: expanded.pubkeys, pubkeys: expanded.pubkeys,
signatures: expanded.signatures, signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures maxSignatures: expanded.maxSignatures,
}; };
} }
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output;
@ -618,7 +635,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
signScript: prevOutScript, signScript: prevOutScript,
signType: SCRIPT_TYPES.P2PKH, signType: SCRIPT_TYPES.P2PKH,
pubkeys: [ourPubKey], pubkeys: [ourPubKey],
signatures: [undefined] signatures: [undefined],
}; };
} }
function build(type, input, allowIncomplete) { function build(type, input, allowIncomplete) {
@ -656,7 +673,7 @@ function build(type, input, allowIncomplete) {
} }
// 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: {
@ -667,8 +684,8 @@ function build(type, input, allowIncomplete) {
redeem: { redeem: {
output: redeem.output || input.redeemScript, output: redeem.output || input.redeemScript,
input: redeem.input, input: redeem.input,
witness: redeem.witness witness: redeem.witness,
} },
}); });
} }
case SCRIPT_TYPES.P2WSH: { case SCRIPT_TYPES.P2WSH: {
@ -679,21 +696,20 @@ function build(type, input, allowIncomplete) {
redeem: { redeem: {
output: input.witnessScript, output: input.witnessScript,
input: redeem.input, input: redeem.input,
witness: redeem.witness witness: redeem.witness,
} },
}); });
} }
} }
} }
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.hasWitness === false || input.value !== undefined));
input.value !== undefined);
} }
function signatureHashType(buffer) { function signatureHashType(buffer) {
return buffer.readUInt8(buffer.length - 1); return buffer.readUInt8(buffer.length - 1);

8
src/types.js

@ -10,7 +10,9 @@ function BIP32Path(value) {
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/);
} }
exports.BIP32Path = BIP32Path; exports.BIP32Path = BIP32Path;
BIP32Path.toJSON = function () { return 'BIP32 derivation path'; }; BIP32Path.toJSON = function () {
return 'BIP32 derivation path';
};
const SATOSHI_MAX = 21 * 1e14; const SATOSHI_MAX = 21 * 1e14;
function Satoshi(value) { function Satoshi(value) {
return typeforce.UInt53(value) && value <= SATOSHI_MAX; return typeforce.UInt53(value) && value <= SATOSHI_MAX;
@ -23,11 +25,11 @@ exports.Network = typeforce.compile({
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
bip32: { bip32: {
public: typeforce.UInt32, public: typeforce.UInt32,
private: typeforce.UInt32 private: typeforce.UInt32,
}, },
pubKeyHash: typeforce.UInt8, pubKeyHash: typeforce.UInt8,
scriptHash: typeforce.UInt8, scriptHash: typeforce.UInt8,
wif: typeforce.UInt8 wif: typeforce.UInt8,
}); });
exports.Buffer256bit = typeforce.BufferN(32); exports.Buffer256bit = typeforce.BufferN(32);
exports.Hash160bit = typeforce.BufferN(20); exports.Hash160bit = typeforce.BufferN(20);

2
test/ecpair.js

@ -1,5 +1,3 @@
/* eslint-disable no-new */
const { describe, it, beforeEach } = require('mocha') const { describe, it, beforeEach } = require('mocha')
const assert = require('assert') const assert = require('assert')
const proxyquire = require('proxyquire') const proxyquire = require('proxyquire')

120
ts_src/address.ts

@ -1,101 +1,119 @@
import { Network } from './networks' import { Network } from './networks';
import * as types from './types' import * as types from './types';
import * as bscript from './script' import * as bscript from './script';
import * as networks from './networks' import * as networks from './networks';
import * as payments from './payments' import * as payments from './payments';
const bech32 = require('bech32') const bech32 = require('bech32');
const bs58check = require('bs58check') const bs58check = require('bs58check');
const typeforce = require('typeforce') const typeforce = require('typeforce');
export type Base58CheckResult = { export type Base58CheckResult = {
hash: Buffer; hash: Buffer;
version: number; version: number;
} };
export type Bech32Result = { export type Bech32Result = {
version: number; version: number;
prefix: string; prefix: string;
data: Buffer; data: Buffer;
} };
export function fromBase58Check (address: string): Base58CheckResult { export function fromBase58Check(address: string): Base58CheckResult {
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) throw new TypeError(address + ' is too short') if (payload.length < 21) 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: version, hash: hash } return { version: version, hash: hash };
} }
export function fromBech32 (address: string): Bech32Result { export function fromBech32(address: string): Bech32Result {
const result = bech32.decode(address) const result = bech32.decode(address);
const data = bech32.fromWords(result.words.slice(1)) const data = bech32.fromWords(result.words.slice(1));
return { return {
version: result.words[0], version: result.words[0],
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data) data: Buffer.from(data),
} };
} }
export function toBase58Check (hash: Buffer, version: number): string { export function toBase58Check(hash: Buffer, version: number): string {
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments) typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
const payload = Buffer.allocUnsafe(21) const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0) payload.writeUInt8(version, 0);
hash.copy(payload, 1) hash.copy(payload, 1);
return bs58check.encode(payload) return bs58check.encode(payload);
} }
export function toBech32 (data: Buffer, version: number, prefix: string): string { export function toBech32(
const words = bech32.toWords(data) data: Buffer,
words.unshift(version) version: number,
prefix: string,
): string {
const words = bech32.toWords(data);
words.unshift(version);
return bech32.encode(prefix, words) return bech32.encode(prefix, words);
} }
export function fromOutputScript (output: Buffer, network: Network): string { //TODO: Network export function fromOutputScript(output: Buffer, network: Network): string {
network = network || networks.bitcoin //TODO: Network
network = network || networks.bitcoin;
try { return <string>payments.p2pkh({ output, network }).address } catch (e) {} try {
try { return <string>payments.p2sh({ output, network }).address } catch (e) {} return <string>payments.p2pkh({ output, network }).address;
try { return <string>payments.p2wpkh({ output, network }).address } catch (e) {} } catch (e) {}
try { return <string>payments.p2wsh({ output, network }).address } catch (e) {} try {
return <string>payments.p2sh({ output, network }).address;
} catch (e) {}
try {
return <string>payments.p2wpkh({ output, network }).address;
} catch (e) {}
try {
return <string>payments.p2wsh({ output, network }).address;
} catch (e) {}
throw new Error(bscript.toASM(output) + ' has no matching Address') throw new Error(bscript.toASM(output) + ' has no matching Address');
} }
export function toOutputScript (address: string, network: Network): Buffer { export function toOutputScript(address: string, network: Network): Buffer {
network = network || networks.bitcoin network = network || networks.bitcoin;
let decodeBase58: Base58CheckResult | undefined = undefined let decodeBase58: Base58CheckResult | undefined = undefined;
let decodeBech32: Bech32Result | undefined = undefined let decodeBech32: Bech32Result | undefined = undefined;
try { try {
decodeBase58 = fromBase58Check(address) decodeBase58 = fromBase58Check(address);
} catch (e) {} } catch (e) {}
if (decodeBase58) { if (decodeBase58) {
if (decodeBase58.version === network.pubKeyHash) return <Buffer>payments.p2pkh({ hash: decodeBase58.hash }).output if (decodeBase58.version === network.pubKeyHash)
if (decodeBase58.version === network.scriptHash) return <Buffer>payments.p2sh({ hash: decodeBase58.hash }).output return <Buffer>payments.p2pkh({ hash: decodeBase58.hash }).output;
if (decodeBase58.version === network.scriptHash)
return <Buffer>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) throw new Error(address + ' has an invalid prefix') if (decodeBech32.prefix !== network.bech32)
throw new Error(address + ' has an invalid prefix');
if (decodeBech32.version === 0) { if (decodeBech32.version === 0) {
if (decodeBech32.data.length === 20) return <Buffer>payments.p2wpkh({ hash: decodeBech32.data }).output if (decodeBech32.data.length === 20)
if (decodeBech32.data.length === 32) return <Buffer>payments.p2wsh({ hash: decodeBech32.data }).output return <Buffer>payments.p2wpkh({ hash: decodeBech32.data }).output;
if (decodeBech32.data.length === 32)
return <Buffer>payments.p2wsh({ hash: decodeBech32.data }).output;
} }
} }
} }
throw new Error(address + ' has no matching Script') throw new Error(address + ' has no matching Script');
} }

373
ts_src/block.ts

@ -1,17 +1,22 @@
import { Transaction } from './transaction' import { Transaction } from './transaction';
import * as types from './types' import * as types from './types';
import * as bcrypto from './crypto' import * as bcrypto from './crypto';
import { reverseBuffer } from './bufferutils' import { reverseBuffer } from './bufferutils';
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',
);
function txesHaveWitnessCommit (transactions: Array<Transaction>): boolean { const errorWitnessNotSegwit = new TypeError(
return transactions instanceof Array && 'Cannot compute witness commit for non-segwit block',
);
function txesHaveWitnessCommit(transactions: Array<Transaction>): boolean {
return (
transactions instanceof Array &&
transactions[0] && transactions[0] &&
transactions[0].ins && transactions[0].ins &&
transactions[0].ins instanceof Array && transactions[0].ins instanceof Array &&
@ -19,255 +24,281 @@ function txesHaveWitnessCommit (transactions: Array<Transaction>): boolean {
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: Array<Transaction>): boolean { function anyTxHasWitness(transactions: Array<Transaction>): boolean {
return transactions instanceof Array && return (
transactions.some(tx => transactions instanceof Array &&
typeof tx === 'object' && transactions.some(
tx.ins instanceof Array && tx =>
tx.ins.some(input => typeof tx === 'object' &&
typeof input === 'object' && tx.ins instanceof Array &&
input.witness instanceof Array && tx.ins.some(
input.witness.length > 0 input =>
) typeof input === 'object' &&
input.witness instanceof Array &&
input.witness.length > 0,
),
) )
);
} }
export class Block { export class Block {
version: number version: number;
prevHash?: Buffer prevHash?: Buffer;
merkleRoot?: Buffer merkleRoot?: Buffer;
timestamp: number timestamp: number;
witnessCommit?: Buffer witnessCommit?: Buffer;
bits: number bits: number;
nonce: number nonce: number;
transactions?: Array<Transaction> transactions?: Array<Transaction>;
constructor () { constructor() {
this.version = 1 this.version = 1;
this.timestamp = 0 this.timestamp = 0;
this.bits = 0 this.bits = 0;
this.nonce = 0 this.nonce = 0;
this.prevHash = undefined this.prevHash = undefined;
this.merkleRoot = undefined this.merkleRoot = undefined;
this.witnessCommit = undefined this.witnessCommit = undefined;
this.transactions = undefined this.transactions = undefined;
} }
static fromBuffer (buffer: Buffer): Block { static fromBuffer(buffer: Buffer): Block {
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)') if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)');
let offset: number = 0 let offset: number = 0;
const readSlice = (n: number): Buffer => { const readSlice = (n: number): Buffer => {
offset += n offset += n;
return buffer.slice(offset - n, offset) return buffer.slice(offset - n, offset);
} };
const readUInt32 = (): number => { const readUInt32 = (): number => {
const i = buffer.readUInt32LE(offset) const i = buffer.readUInt32LE(offset);
offset += 4 offset += 4;
return i return i;
} };
const readInt32 = (): number => { const readInt32 = (): number => {
const i = buffer.readInt32LE(offset) const i = buffer.readInt32LE(offset);
offset += 4 offset += 4;
return i return i;
} };
const block = new Block() const block = new Block();
block.version = readInt32() block.version = readInt32();
block.prevHash = readSlice(32) block.prevHash = readSlice(32);
block.merkleRoot = readSlice(32) block.merkleRoot = readSlice(32);
block.timestamp = readUInt32() block.timestamp = readUInt32();
block.bits = readUInt32() block.bits = readUInt32();
block.nonce = readUInt32() block.nonce = readUInt32();
if (buffer.length === 80) return block if (buffer.length === 80) return block;
const readVarInt = (): number => { const readVarInt = (): number => {
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 = (): any => { const readTransaction = (): any => {
const tx = Transaction.fromBuffer(buffer.slice(offset), true) const tx = Transaction.fromBuffer(buffer.slice(offset), true);
offset += tx.byteLength() offset += tx.byteLength();
return tx return tx;
} };
const nTransactions = readVarInt() const nTransactions = readVarInt();
block.transactions = [] block.transactions = [];
for (var i = 0; i < nTransactions; ++i) { for (var i = 0; i < nTransactions; ++i) {
const tx = readTransaction() const tx = readTransaction();
block.transactions.push(tx) block.transactions.push(tx);
} }
let witnessCommit = block.getWitnessCommit() let witnessCommit = block.getWitnessCommit();
// This Block contains a witness commit // This Block contains a witness commit
if (witnessCommit) block.witnessCommit = witnessCommit if (witnessCommit) block.witnessCommit = witnessCommit;
return block return block;
} }
static fromHex (hex: string): Block { static fromHex(hex: string): Block {
return Block.fromBuffer(Buffer.from(hex, 'hex')) return Block.fromBuffer(Buffer.from(hex, 'hex'));
} }
static calculateTarget (bits: number): Buffer { static calculateTarget(bits: number): Buffer {
const exponent = ((bits & 0xff000000) >> 24) - 3 const exponent = ((bits & 0xff000000) >> 24) - 3;
const mantissa = bits & 0x007fffff const mantissa = bits & 0x007fffff;
const target = Buffer.alloc(32, 0) const target = Buffer.alloc(32, 0);
target.writeUIntBE(mantissa, 29 - exponent, 3) target.writeUIntBE(mantissa, 29 - exponent, 3);
return target return target;
} }
static calculateMerkleRoot (transactions: Array<Transaction>, forWitness?: boolean): Buffer { static calculateMerkleRoot(
typeforce([{ getHash: types.Function }], transactions) transactions: Array<Transaction>,
if (transactions.length === 0) throw errorMerkleNoTxes forWitness?: boolean,
if (forWitness && !txesHaveWitnessCommit(transactions)) throw errorWitnessNotSegwit ): Buffer {
typeforce([{ getHash: types.Function }], transactions);
if (transactions.length === 0) throw errorMerkleNoTxes;
if (forWitness && !txesHaveWitnessCommit(transactions))
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(
: rootHash Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]),
)
: rootHash;
} }
getWitnessCommit (): Buffer | null { getWitnessCommit(): Buffer | null {
if (!txesHaveWitnessCommit(this.transactions!)) return null if (!txesHaveWitnessCommit(this.transactions!)) 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.
let witnessCommits = this.transactions![0].outs let witnessCommits = this.transactions![0].outs.filter(out =>
.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))) out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')),
.map(out => out.script.slice(6, 38)) ).map(out => out.script.slice(6, 38));
if (witnessCommits.length === 0) return null 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)
let result = witnessCommits[witnessCommits.length - 1] let result = witnessCommits[witnessCommits.length - 1];
if (!(result instanceof Buffer && result.length === 32)) return null if (!(result instanceof Buffer && result.length === 32)) return null;
return result return result;
} }
hasWitnessCommit (): boolean { hasWitnessCommit(): boolean {
if (this.witnessCommit instanceof Buffer && if (
this.witnessCommit.length === 32) return true this.witnessCommit instanceof Buffer &&
if (this.getWitnessCommit() !== null) return true this.witnessCommit.length === 32
return false )
return true;
if (this.getWitnessCommit() !== null) return true;
return false;
} }
hasWitness (): boolean { hasWitness(): boolean {
return anyTxHasWitness(this.transactions!) return anyTxHasWitness(this.transactions!);
} }
byteLength (headersOnly: boolean): number { byteLength(headersOnly: boolean): number {
if (headersOnly || !this.transactions) return 80 if (headersOnly || !this.transactions) return 80;
return 80 + varuint.encodingLength(this.transactions.length) + return (
80 +
varuint.encodingLength(this.transactions.length) +
this.transactions.reduce((a, x) => a + x.byteLength(), 0) this.transactions.reduce((a, x) => a + x.byteLength(), 0)
);
} }
getHash (): Buffer { getHash(): Buffer {
return bcrypto.hash256(this.toBuffer(true)) return bcrypto.hash256(this.toBuffer(true));
} }
getId (): string { getId(): string {
return reverseBuffer(this.getHash()).toString('hex') return reverseBuffer(this.getHash()).toString('hex');
} }
getUTCDate (): Date { getUTCDate(): Date {
const date = new Date(0) // epoch const date = new Date(0); // epoch
date.setUTCSeconds(this.timestamp) date.setUTCSeconds(this.timestamp);
return date return date;
} }
// TODO: buffer, offset compatibility // TODO: buffer, offset compatibility
toBuffer (headersOnly: boolean): Buffer { toBuffer(headersOnly: boolean): Buffer {
const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)) const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
let offset: number = 0 let offset: number = 0;
const writeSlice = (slice: Buffer): void => { const writeSlice = (slice: Buffer): void => {
slice.copy(buffer, offset) slice.copy(buffer, offset);
offset += slice.length offset += slice.length;
} };
const writeInt32 = (i: number): void => { const writeInt32 = (i: number): void => {
buffer.writeInt32LE(i, offset) buffer.writeInt32LE(i, offset);
offset += 4 offset += 4;
} };
const writeUInt32 = (i: number): void => { const writeUInt32 = (i: number): void => {
buffer.writeUInt32LE(i, offset) buffer.writeUInt32LE(i, offset);
offset += 4 offset += 4;
} };
writeInt32(this.version) writeInt32(this.version);
writeSlice(this.prevHash!) writeSlice(this.prevHash!);
writeSlice(this.merkleRoot!) writeSlice(this.merkleRoot!);
writeUInt32(this.timestamp) writeUInt32(this.timestamp);
writeUInt32(this.bits) writeUInt32(this.bits);
writeUInt32(this.nonce) writeUInt32(this.nonce);
if (headersOnly || !this.transactions) return buffer if (headersOnly || !this.transactions) 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 => {
const txSize = tx.byteLength() // TODO: extract from toBuffer? const txSize = tx.byteLength(); // TODO: extract from toBuffer?
tx.toBuffer(buffer, offset) tx.toBuffer(buffer, offset);
offset += txSize offset += txSize;
}) });
return buffer return buffer;
} }
toHex (headersOnly: boolean): string { toHex(headersOnly: boolean): string {
return this.toBuffer(headersOnly).toString('hex') return this.toBuffer(headersOnly).toString('hex');
} }
checkTxRoots (): boolean { checkTxRoots(): boolean {
// 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.
let hasWitnessCommit = this.hasWitnessCommit() let hasWitnessCommit = this.hasWitnessCommit();
if (!hasWitnessCommit && this.hasWitness()) return false if (!hasWitnessCommit && this.hasWitness()) return false;
return this.__checkMerkleRoot() && return (
this.__checkMerkleRoot() &&
(hasWitnessCommit ? this.__checkWitnessCommit() : true) (hasWitnessCommit ? this.__checkWitnessCommit() : true)
);
} }
checkMerkleRoot (): boolean { checkMerkleRoot(): boolean {
console.warn('Deprecation Warning: Block method checkMerkleRoot will be ' + console.warn(
'deprecated in v5. Please use checkTxRoots instead.') 'Deprecation Warning: Block method checkMerkleRoot will be ' +
return this.checkTxRoots() 'deprecated in v5. Please use checkTxRoots instead.',
);
return this.checkTxRoots();
} }
__checkMerkleRoot (): boolean { __checkMerkleRoot(): boolean {
if (!this.transactions) throw errorMerkleNoTxes if (!this.transactions) 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 (): boolean { __checkWitnessCommit(): boolean {
if (!this.transactions) throw errorMerkleNoTxes if (!this.transactions) throw errorMerkleNoTxes;
if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit;
const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true) const actualWitnessCommit = Block.calculateMerkleRoot(
return this.witnessCommit!.compare(actualWitnessCommit) === 0 this.transactions,
true,
);
return this.witnessCommit!.compare(actualWitnessCommit) === 0;
} }
checkProofOfWork (): boolean { checkProofOfWork(): boolean {
const hash: Buffer = reverseBuffer(this.getHash()) const hash: Buffer = reverseBuffer(this.getHash());
const target = Block.calculateTarget(this.bits) const target = Block.calculateTarget(this.bits);
return hash.compare(target) <= 0 return hash.compare(target) <= 0;
} }
} }

57
ts_src/bufferutils.ts

@ -1,37 +1,44 @@
// https://github.com/feross/buffer/blob/master/index.js#L1127 // https://github.com/feross/buffer/blob/master/index.js#L1127
function verifuint (value: number, max: number): void { function verifuint(value: number, max: number): void {
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number') if (typeof value !== 'number')
if (value < 0) throw new Error('specified a negative value for writing an unsigned value') throw new Error('cannot write a non-number as a number');
if (value > max) throw new Error('RangeError: value out of range') if (value < 0)
if (Math.floor(value) !== value) throw new Error('value has a fractional component') throw new Error('specified a negative value for writing an unsigned value');
if (value > max) throw new Error('RangeError: value out of range');
if (Math.floor(value) !== value)
throw new Error('value has a fractional component');
} }
export function readUInt64LE (buffer: Buffer, offset: number): number { export function readUInt64LE(buffer: Buffer, offset: number): number {
const a = buffer.readUInt32LE(offset) const a = buffer.readUInt32LE(offset);
let b = buffer.readUInt32LE(offset + 4) let b = buffer.readUInt32LE(offset + 4);
b *= 0x100000000 b *= 0x100000000;
verifuint(b + a, 0x001fffffffffffff) verifuint(b + a, 0x001fffffffffffff);
return b + a return b + a;
} }
export function writeUInt64LE (buffer: Buffer, value: number, offset: number): number { export function writeUInt64LE(
verifuint(value, 0x001fffffffffffff) buffer: Buffer,
value: number,
offset: number,
): number {
verifuint(value, 0x001fffffffffffff);
buffer.writeInt32LE(value & -1, offset) buffer.writeInt32LE(value & -1, offset);
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4) buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
return offset + 8 return offset + 8;
} }
export function reverseBuffer (buffer: Buffer): Buffer { export function reverseBuffer(buffer: Buffer): Buffer {
if (buffer.length < 1) return buffer if (buffer.length < 1) 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++) {
tmp = buffer[i] tmp = buffer[i];
buffer[i] = buffer[j] buffer[i] = buffer[j];
buffer[j] = tmp buffer[j] = tmp;
j-- j--;
} }
return buffer return buffer;
} }

96
ts_src/classify.ts

@ -1,65 +1,69 @@
import { decompile } from './script' import { decompile } from './script';
import * as multisig from './templates/multisig' import * as multisig from './templates/multisig';
import * as nullData from './templates/nulldata' import * as nullData from './templates/nulldata';
import * as pubKey from './templates/pubkey' import * as pubKey from './templates/pubkey';
import * as pubKeyHash from './templates/pubkeyhash' import * as pubKeyHash from './templates/pubkeyhash';
import * as scriptHash from './templates/scripthash' import * as scriptHash from './templates/scripthash';
import * as witnessPubKeyHash from './templates/witnesspubkeyhash' import * as witnessPubKeyHash from './templates/witnesspubkeyhash';
import * as witnessScriptHash from './templates/witnessscripthash' import * as witnessScriptHash from './templates/witnessscripthash';
import * as witnessCommitment from './templates/witnesscommitment' import * as witnessCommitment from './templates/witnesscommitment';
const types = { const types = {
P2MS: <string> 'multisig', P2MS: <string>'multisig',
NONSTANDARD: <string> 'nonstandard', NONSTANDARD: <string>'nonstandard',
NULLDATA: <string> 'nulldata', NULLDATA: <string>'nulldata',
P2PK: <string> 'pubkey', P2PK: <string>'pubkey',
P2PKH: <string> 'pubkeyhash', P2PKH: <string>'pubkeyhash',
P2SH: <string> 'scripthash', P2SH: <string>'scripthash',
P2WPKH: <string> 'witnesspubkeyhash', P2WPKH: <string>'witnesspubkeyhash',
P2WSH: <string> 'witnessscripthash', P2WSH: <string>'witnessscripthash',
WITNESS_COMMITMENT: <string> 'witnesscommitment' WITNESS_COMMITMENT: <string>'witnesscommitment',
} };
function classifyOutput (script: Buffer): string { function classifyOutput(script: Buffer): string {
if (witnessPubKeyHash.output.check(script)) return types.P2WPKH if (witnessPubKeyHash.output.check(script)) return types.P2WPKH;
if (witnessScriptHash.output.check(script)) return types.P2WSH if (witnessScriptHash.output.check(script)) return types.P2WSH;
if (pubKeyHash.output.check(script)) return types.P2PKH if (pubKeyHash.output.check(script)) return types.P2PKH;
if (scriptHash.output.check(script)) return types.P2SH if (scriptHash.output.check(script)) return types.P2SH;
// XXX: optimization, below functions .decompile before use // XXX: optimization, below functions .decompile before use
const chunks = decompile(script) const chunks = decompile(script);
if (!chunks) throw new TypeError('Invalid script') if (!chunks) throw new TypeError('Invalid script');
if (multisig.output.check(chunks)) return types.P2MS if (multisig.output.check(chunks)) return types.P2MS;
if (pubKey.output.check(chunks)) return types.P2PK if (pubKey.output.check(chunks)) return types.P2PK;
if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT;
if (nullData.output.check(chunks)) return types.NULLDATA if (nullData.output.check(chunks)) return types.NULLDATA;
return types.NONSTANDARD return types.NONSTANDARD;
} }
function classifyInput (script: Buffer, allowIncomplete: boolean): string { function classifyInput(script: Buffer, allowIncomplete: boolean): string {
// XXX: optimization, below functions .decompile before use // XXX: optimization, below functions .decompile before use
const chunks = decompile(script) const chunks = decompile(script);
if (!chunks) throw new TypeError('Invalid script') if (!chunks) throw new TypeError('Invalid script');
if (pubKeyHash.input.check(chunks)) return types.P2PKH if (pubKeyHash.input.check(chunks)) return types.P2PKH;
if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH;
if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS;
if (pubKey.input.check(chunks)) return types.P2PK if (pubKey.input.check(chunks)) return types.P2PK;
return types.NONSTANDARD return types.NONSTANDARD;
} }
function classifyWitness (script: Array<Buffer>, allowIncomplete: boolean): string { function classifyWitness(
script: Array<Buffer>,
allowIncomplete: boolean,
): string {
// XXX: optimization, below functions .decompile before use // XXX: optimization, below functions .decompile before use
const chunks = decompile(script) const chunks = decompile(script);
if (!chunks) throw new TypeError('Invalid script') if (!chunks) 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(<Array<Buffer>>chunks, allowIncomplete)) return types.P2WSH if (witnessScriptHash.input.check(<Array<Buffer>>chunks, allowIncomplete))
return types.P2WSH;
return types.NONSTANDARD return types.NONSTANDARD;
} }
export { export {
@ -67,4 +71,4 @@ export {
classifyOutput as output, classifyOutput as output,
classifyWitness as witness, classifyWitness as witness,
types, types,
} };

28
ts_src/crypto.ts

@ -1,21 +1,27 @@
const createHash = require('create-hash') const createHash = require('create-hash');
export function ripemd160 (buffer: Buffer): Buffer { export function ripemd160(buffer: Buffer): Buffer {
return createHash('rmd160').update(buffer).digest() return createHash('rmd160')
.update(buffer)
.digest();
} }
export function sha1 (buffer: Buffer): Buffer { export function sha1(buffer: Buffer): Buffer {
return createHash('sha1').update(buffer).digest() return createHash('sha1')
.update(buffer)
.digest();
} }
export function sha256 (buffer: Buffer): Buffer { export function sha256(buffer: Buffer): Buffer {
return createHash('sha256').update(buffer).digest() return createHash('sha256')
.update(buffer)
.digest();
} }
export function hash160 (buffer: Buffer): Buffer { export function hash160(buffer: Buffer): Buffer {
return ripemd160(sha256(buffer)) return ripemd160(sha256(buffer));
} }
export function hash256 (buffer: Buffer): Buffer { export function hash256(buffer: Buffer): Buffer {
return sha256(sha256(buffer)) return sha256(sha256(buffer));
} }

174
ts_src/ecpair.ts

@ -1,130 +1,132 @@
import { Network } from './networks' import { Network } from './networks';
import * as NETWORKS from './networks' import * as NETWORKS from './networks';
import * as types from './types' import * as types from './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(
compressed: types.maybe(types.Boolean), typeforce.compile({
network: types.maybe(types.Network) compressed: types.maybe(types.Boolean),
})) network: types.maybe(types.Network),
}),
);
interface ECPairOptions { interface ECPairOptions {
compressed?: boolean compressed?: boolean;
network?: Network network?: Network;
rng?(arg0: Buffer): Buffer rng?(arg0: Buffer): Buffer;
} }
export interface ECPairInterface { export interface ECPairInterface {
compressed: boolean compressed: boolean;
network: Network network: Network;
privateKey?: Buffer privateKey?: Buffer;
publicKey?: Buffer publicKey?: Buffer;
toWIF(): string toWIF(): string;
sign(hash: Buffer): Buffer sign(hash: Buffer): Buffer;
verify(hash: Buffer, signature: Buffer): Buffer verify(hash: Buffer, signature: Buffer): Buffer;
getPublicKey?(): Buffer getPublicKey?(): Buffer;
} }
class ECPair implements ECPairInterface { class ECPair implements ECPairInterface {
compressed: boolean compressed: boolean;
network: Network network: Network;
private __d?: Buffer private __d?: Buffer;
private __Q?: Buffer private __Q?: Buffer;
constructor (d?: Buffer, Q?: Buffer, options?: ECPairOptions) { constructor(d?: Buffer, Q?: Buffer, options?: ECPairOptions) {
if (options === undefined) options = {} if (options === undefined) options = {};
this.compressed = options.compressed === undefined ? true : options.compressed this.compressed =
this.network = options.network || NETWORKS.bitcoin options.compressed === undefined ? true : options.compressed;
this.network = options.network || NETWORKS.bitcoin;
this.__d = undefined
this.__Q = undefined this.__d = undefined;
if (d !== undefined) this.__d = d this.__Q = undefined;
if (Q !== undefined) this.__Q = ecc.pointCompress(Q, this.compressed) if (d !== undefined) this.__d = d;
if (Q !== undefined) this.__Q = ecc.pointCompress(Q, this.compressed);
} }
get privateKey (): Buffer | undefined { get privateKey(): Buffer | undefined {
return this.__d return this.__d;
} }
get publicKey (): Buffer | undefined { get publicKey(): Buffer | undefined {
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed) if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed);
return this.__Q return this.__Q;
} }
toWIF (): string { toWIF(): string {
if (!this.__d) throw new Error('Missing private key') if (!this.__d) 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: Buffer): Buffer { sign(hash: Buffer): Buffer {
if (!this.__d) throw new Error('Missing private key') if (!this.__d) throw new Error('Missing private key');
return ecc.sign(hash, this.__d) return ecc.sign(hash, this.__d);
} }
verify (hash: Buffer, signature: Buffer): Buffer { verify(hash: Buffer, signature: Buffer): Buffer {
return ecc.verify(hash, this.publicKey, signature) return ecc.verify(hash, this.publicKey, signature);
} }
} }
function fromPrivateKey (buffer: Buffer, options?: ECPairOptions): ECPair { function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair {
typeforce(types.Buffer256bit, buffer) typeforce(types.Buffer256bit, buffer);
if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)') if (!ecc.isPrivate(buffer))
typeforce(isOptions, options) throw new TypeError('Private key not in range [1, n)');
typeforce(isOptions, options);
return new ECPair(buffer, undefined, <ECPairOptions>options) return new ECPair(buffer, undefined, <ECPairOptions>options);
} }
function fromPublicKey (buffer: Buffer, options?: ECPairOptions): ECPair { function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair {
typeforce(ecc.isPoint, buffer) typeforce(ecc.isPoint, buffer);
typeforce(isOptions, options) typeforce(isOptions, options);
return new ECPair(undefined, buffer, <ECPairOptions>options) return new ECPair(undefined, buffer, <ECPairOptions>options);
} }
function fromWIF (string: string, network?: Network | Array<Network>): ECPair { function fromWIF(string: string, network?: Network | Array<Network>): ECPair {
const decoded = wif.decode(string) const decoded = wif.decode(string);
const version = decoded.version const version = decoded.version;
// list of networks? // list of networks?
if (types.Array(network)) { if (types.Array(network)) {
network = <Network>(<Array<Network>>network).filter(function (x: Network) { network = <Network>(<Array<Network>>network)
return version === x.wif .filter(function(x: Network) {
}).pop() return version === x.wif;
})
.pop();
if (!network) throw new Error('Unknown network version') if (!network) 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>network).wif) throw new Error('Invalid network version') if (version !== (<Network>network).wif)
throw new Error('Invalid network version');
} }
return fromPrivateKey(decoded.privateKey, { return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed, compressed: decoded.compressed,
network: <Network>network network: <Network>network,
}) });
} }
function makeRandom (options?: ECPairOptions): ECPair { function makeRandom(options?: ECPairOptions): ECPair {
typeforce(isOptions, options) typeforce(isOptions, options);
if (options === undefined) options = {} if (options === undefined) options = {};
const rng = options.rng || randomBytes const rng = options.rng || randomBytes;
let d let d;
do { do {
d = rng(32) d = rng(32);
typeforce(types.Buffer256bit, d) typeforce(types.Buffer256bit, d);
} while (!ecc.isPrivate(d)) } while (!ecc.isPrivate(d));
return fromPrivateKey(d, options) return fromPrivateKey(d, options);
} }
export { export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF };
makeRandom,
fromPrivateKey,
fromPublicKey,
fromWIF
}

42
ts_src/index.ts

@ -1,28 +1,20 @@
import * as bip32 from 'bip32' import * as bip32 from 'bip32';
import * as ECPair from './ecpair' import * as ECPair from './ecpair';
import * as address from './address' import * as address from './address';
import * as crypto from './crypto' import * as crypto from './crypto';
import * as networks from './networks' import * as networks from './networks';
import * as payments from './payments' import * as payments from './payments';
import * as script from './script' import * as script from './script';
export { export { ECPair, address, bip32, crypto, networks, payments, script };
ECPair,
address,
bip32,
crypto,
networks,
payments,
script,
}
export { Block } from './block' export { Block } from './block';
export { Transaction } from './transaction' export { Transaction } from './transaction';
export { TransactionBuilder } from './transaction_builder' export { TransactionBuilder } from './transaction_builder';
export { OPS as opcodes } from './script' export { OPS as opcodes } from './script';
export { Payment, PaymentOpts } from './payments' export { Payment, PaymentOpts } from './payments';
export { Input as TxInput, Output as TxOutput } from './transaction' export { Input as TxInput, Output as TxOutput } from './transaction';
export { Network } from './networks' export { Network } from './networks';
export { OpCode } from './script' export { OpCode } from './script';
export { BIP32Interface } from 'bip32' export { BIP32Interface } from 'bip32';

22
ts_src/networks.ts

@ -7,43 +7,43 @@ export type Network = {
pubKeyHash: number; pubKeyHash: number;
scriptHash: number; scriptHash: number;
wif: number; wif: number;
} };
type bip32 = { type bip32 = {
public: number; public: number;
private: number; private: number;
} };
export const bitcoin: Network = { export const bitcoin: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n', messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc', bech32: 'bc',
bip32: { bip32: {
public: 0x0488b21e, public: 0x0488b21e,
private: 0x0488ade4 private: 0x0488ade4,
}, },
pubKeyHash: 0x00, pubKeyHash: 0x00,
scriptHash: 0x05, scriptHash: 0x05,
wif: 0x80 wif: 0x80,
} };
export const regtest: Network = { export const regtest: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n', messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt', bech32: 'bcrt',
bip32: { bip32: {
public: 0x043587cf, public: 0x043587cf,
private: 0x04358394 private: 0x04358394,
}, },
pubKeyHash: 0x6f, pubKeyHash: 0x6f,
scriptHash: 0xc4, scriptHash: 0xc4,
wif: 0xef wif: 0xef,
} };
export const testnet: Network = { export const testnet: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n', messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb', bech32: 'tb',
bip32: { bip32: {
public: 0x043587cf, public: 0x043587cf,
private: 0x04358394 private: 0x04358394,
}, },
pubKeyHash: 0x6f, pubKeyHash: 0x6f,
scriptHash: 0xc4, scriptHash: 0xc4,
wif: 0xef wif: 0xef,
} };

91
ts_src/payments/embed.ts

@ -1,54 +1,59 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import * as bscript from '../script' import * as bscript from '../script';
import * as lazy from './lazy' import * as lazy from './lazy';
import { bitcoin as BITCOIN_NETWORK } from '../networks' import { bitcoin as BITCOIN_NETWORK } from '../networks';
const typef = require('typeforce') const typef = require('typeforce');
const OPS = bscript.OPS const OPS = bscript.OPS;
function stacksEqual (a: Array<Buffer>, b: Array<Buffer>): boolean { function stacksEqual(a: Array<Buffer>, b: Array<Buffer>): boolean {
if (a.length !== b.length) return false if (a.length !== b.length) return false;
return a.every(function (x, i) { return a.every(function(x, i) {
return x.equals(b[i]) return x.equals(b[i]);
}) });
} }
// output: OP_RETURN ... // output: OP_RETURN ...
export function p2data (a: Payment, opts?: PaymentOpts): Payment { export function p2data(a: Payment, opts?: PaymentOpts): Payment {
if ( if (!a.data && !a.output) throw new TypeError('Not enough data');
!a.data && opts = Object.assign({ validate: true }, opts || {});
!a.output
) throw new TypeError('Not enough data') typef(
opts = Object.assign({ validate: true }, opts || {}) {
network: typef.maybe(typef.Object),
typef({ output: typef.maybe(typef.Buffer),
network: typef.maybe(typef.Object), data: typef.maybe(typef.arrayOf(typef.Buffer)),
output: typef.maybe(typef.Buffer), },
data: typef.maybe(typef.arrayOf(typef.Buffer)) a,
}, a) );
const network = a.network || BITCOIN_NETWORK const network = a.network || BITCOIN_NETWORK;
const o = <Payment>{ network } const o = <Payment>{ network };
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!a.data) return if (!a.data) return;
return bscript.compile((<Array<Buffer | number>>[OPS.OP_RETURN]).concat(a.data)) return bscript.compile(
}) (<Array<Buffer | number>>[OPS.OP_RETURN]).concat(a.data),
lazy.prop(o, 'data', function () { );
if (!a.output) return });
return bscript.decompile(a.output)!.slice(1) lazy.prop(o, 'data', function() {
}) if (!a.output) return;
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) throw new TypeError('Output is invalid') if (chunks![0] !== OPS.OP_RETURN)
if (!chunks!.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid') throw new TypeError('Output is invalid');
if (!chunks!.slice(1).every(typef.Buffer))
if (a.data && !stacksEqual(a.data, <Array<Buffer>>o.data)) throw new TypeError('Data mismatch') throw new TypeError('Output is invalid');
if (a.data && !stacksEqual(a.data, <Array<Buffer>>o.data))
throw new TypeError('Data mismatch');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

50
ts_src/payments/index.ts

@ -1,35 +1,35 @@
import { Network } from '../networks' // eslint-disable-line import { Network } from '../networks';
import { p2data as embed } from './embed' import { p2data as embed } from './embed';
import { p2ms } from './p2ms' import { p2ms } from './p2ms';
import { p2pk } from './p2pk' import { p2pk } from './p2pk';
import { p2pkh } from './p2pkh' import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh' import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh' import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh' import { p2wsh } from './p2wsh';
export interface Payment { export interface Payment {
network?: Network, network?: Network;
output?: Buffer, output?: Buffer;
data?: Array<Buffer>, data?: Array<Buffer>;
m?: number, m?: number;
n?: number, n?: number;
pubkeys?: Array<Buffer>, pubkeys?: Array<Buffer>;
input?: Buffer, input?: Buffer;
signatures?: Array<Buffer>, signatures?: Array<Buffer>;
pubkey?: Buffer, pubkey?: Buffer;
signature?: Buffer, signature?: Buffer;
address?: string, address?: string;
hash?: Buffer, hash?: Buffer;
redeem?: Payment, redeem?: Payment;
witness?: Array<Buffer>, witness?: Array<Buffer>;
} }
export interface PaymentOpts { export interface PaymentOpts {
validate?: boolean, validate?: boolean;
allowIncomplete?: boolean, allowIncomplete?: boolean;
} }
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh } export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
// TODO // TODO
// witness commitment // witness commitment

34
ts_src/payments/lazy.ts

@ -1,28 +1,28 @@
export function prop (object: Object, name: string, f: ()=>any): void { export function prop(object: Object, name: string, f: () => any): void {
Object.defineProperty(object, name, { Object.defineProperty(object, name, {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
get: function () { get: function() {
let value = f.call(this) let value = f.call(this);
this[name] = value this[name] = value;
return value return value;
}, },
set: function (value) { set: function(value) {
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
value: value, value: value,
writable: true writable: true,
}) });
} },
}) });
} }
export function value <T> (f: ()=>T): ()=>T { export function value<T>(f: () => T): () => T {
let value: T let value: T;
return function (): T { return function(): T {
if (value !== undefined) return value if (value !== undefined) return value;
value = f() value = f();
return value return value;
} };
} }

239
ts_src/payments/p2ms.ts

@ -1,141 +1,160 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import * as bscript from '../script' import * as bscript from '../script';
import * as lazy from './lazy' import * as lazy from './lazy';
import { bitcoin as BITCOIN_NETWORK } from '../networks' import { bitcoin as BITCOIN_NETWORK } from '../networks';
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: Array<Buffer>, b: Array<Buffer>): boolean { function stacksEqual(a: Array<Buffer>, b: Array<Buffer>): boolean {
if (a.length !== b.length) return false if (a.length !== b.length) return false;
return a.every(function (x, i) { return a.every(function(x, i) {
return x.equals(b[i]) return x.equals(b[i]);
}) });
} }
// input: OP_0 [signatures ...] // input: OP_0 [signatures ...]
// output: m [pubKeys ...] n OP_CHECKMULTISIG // output: m [pubKeys ...] n OP_CHECKMULTISIG
export function p2ms (a: Payment, opts?: PaymentOpts): Payment { export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
if ( if (
!a.input && !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') )
opts = Object.assign({ validate: true }, opts || {}) throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {});
function isAcceptableSignature (x: Buffer | number) {
return bscript.isCanonicalScriptSignature(<Buffer>x) || function isAcceptableSignature(x: Buffer | number) {
(opts!.allowIncomplete && return (
(<number> x === OPS.OP_0)) !== undefined // eslint-disable-line bscript.isCanonicalScriptSignature(<Buffer>x) ||
(opts!.allowIncomplete && <number>x === OPS.OP_0) !== undefined
);
} }
typef({ typef(
network: typef.maybe(typef.Object), {
m: typef.maybe(typef.Number), network: typef.maybe(typef.Object),
n: typef.maybe(typef.Number), m: typef.maybe(typef.Number),
output: typef.maybe(typef.Buffer), n: typef.maybe(typef.Number),
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), output: typef.maybe(typef.Buffer),
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
input: typef.maybe(typef.Buffer) signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
}, a) input: typef.maybe(typef.Buffer),
},
const network = a.network || BITCOIN_NETWORK a,
const o: Payment = { network } );
let chunks: Array<Buffer | number> = [] const network = a.network || BITCOIN_NETWORK;
let decoded = false const o: Payment = { network };
function decode (output: Buffer | Array<Buffer | number>): void {
if (decoded) return let chunks: Array<Buffer | number> = [];
decoded = true let decoded = false;
chunks = <Array<Buffer | number>>bscript.decompile(output) function decode(output: Buffer | Array<Buffer | number>): void {
o.m = <number> chunks[0] - OP_INT_BASE // eslint-disable-line if (decoded) return;
o.n = <number> chunks[chunks.length - 2] - OP_INT_BASE // eslint-disable-line decoded = true;
o.pubkeys = <Array<Buffer>>chunks.slice(1, -2) chunks = <Array<Buffer | number>>bscript.decompile(output);
o.m = <number>chunks[0] - OP_INT_BASE;
o.n = <number>chunks[chunks.length - 2] - OP_INT_BASE;
o.pubkeys = <Array<Buffer>>chunks.slice(1, -2);
} }
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!a.m) return if (!a.m) return;
if (!o.n) return if (!o.n) return;
if (!a.pubkeys) return if (!a.pubkeys) return;
return bscript.compile((<Array<Buffer | number>>[]).concat( return bscript.compile(
OP_INT_BASE + a.m, (<Array<Buffer | number>>[]).concat(
a.pubkeys, OP_INT_BASE + a.m,
OP_INT_BASE + o.n, a.pubkeys,
OPS.OP_CHECKMULTISIG OP_INT_BASE + o.n,
)) OPS.OP_CHECKMULTISIG,
}) ),
lazy.prop(o, 'm', function () { );
if (!o.output) return });
decode(o.output) lazy.prop(o, 'm', function() {
return o.m if (!o.output) return;
}) decode(o.output);
lazy.prop(o, 'n', function () { return o.m;
if (!o.pubkeys) return });
return o.pubkeys.length lazy.prop(o, 'n', function() {
}) if (!o.pubkeys) return;
lazy.prop(o, 'pubkeys', function () { return o.pubkeys.length;
if (!a.output) return });
decode(a.output) lazy.prop(o, 'pubkeys', function() {
return o.pubkeys if (!a.output) return;
}) decode(a.output);
lazy.prop(o, 'signatures', function () { return o.pubkeys;
if (!a.input) return });
return bscript.decompile(a.input)!.slice(1) lazy.prop(o, 'signatures', function() {
}) if (!a.input) return;
lazy.prop(o, 'input', function () { return bscript.decompile(a.input)!.slice(1);
if (!a.signatures) return });
return bscript.compile((<Array<Buffer | number>>[OPS.OP_0]).concat(a.signatures)) lazy.prop(o, 'input', function() {
}) if (!a.signatures) return;
lazy.prop(o, 'witness', function () { return bscript.compile(
if (!o.input) return (<Array<Buffer | number>>[OPS.OP_0]).concat(a.signatures),
return [] );
}) });
lazy.prop(o, 'witness', function() {
if (!o.input) 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])) throw new TypeError('Output is invalid') if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid');
if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid') if (!typef.Number(chunks[chunks.length - 2]))
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid') throw new TypeError('Output is invalid');
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
if ( throw new TypeError('Output is invalid');
o.m! <= 0 || // eslint-disable-line
o.n! > 16 || // eslint-disable-line if (o.m! <= 0 || o.n! > 16 || o.m! > o.n! || o.n !== chunks.length - 3)
o.m! > o.n! || // eslint-disable-line throw new TypeError('Output is invalid');
o.n !== chunks.length - 3) 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) throw new TypeError('m mismatch') if (a.m !== undefined && a.m !== o.m) 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!)) throw new TypeError('Pubkeys mismatch') if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys!))
throw new TypeError('Pubkeys mismatch');
} }
if (a.pubkeys) { if (a.pubkeys) {
if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch') if (a.n !== undefined && a.n !== a.pubkeys.length)
o.n = a.pubkeys.length throw new TypeError('Pubkey count mismatch');
o.n = a.pubkeys.length;
if (o.n < o.m!) throw new TypeError('Pubkey count cannot be less than m') if (o.n < o.m!) throw new TypeError('Pubkey count cannot be less than m');
} }
if (a.signatures) { if (a.signatures) {
if (a.signatures.length < o.m!) throw new TypeError('Not enough signatures provided') if (a.signatures.length < o.m!)
if (a.signatures.length > o.m!) throw new TypeError('Too many signatures provided') throw new TypeError('Not enough signatures provided');
if (a.signatures.length > o.m!)
throw new TypeError('Too many signatures provided');
} }
if (a.input) { if (a.input) {
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid') if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid');
if (o.signatures!.length === 0 || !o.signatures!.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)') if (
o.signatures!.length === 0 ||
if (a.signatures && !stacksEqual(a.signatures, o.signatures!)) throw new TypeError('Signature mismatch') !o.signatures!.every(isAcceptableSignature)
if (a.m !== undefined && a.m !== a.signatures!.length) throw new TypeError('Signature count mismatch') )
throw new TypeError('Input has invalid signature(s)');
if (a.signatures && !stacksEqual(a.signatures, o.signatures!))
throw new TypeError('Signature mismatch');
if (a.m !== undefined && a.m !== a.signatures!.length)
throw new TypeError('Signature count mismatch');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

114
ts_src/payments/p2pk.ts

@ -1,78 +1,80 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import * as bscript from '../script' import * as bscript from '../script';
import * as lazy from './lazy' import * as lazy from './lazy';
import { bitcoin as BITCOIN_NETWORK } from '../networks' import { bitcoin as BITCOIN_NETWORK } from '../networks';
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');
// input: {signature} // input: {signature}
// output: {pubKey} OP_CHECKSIG // output: {pubKey} OP_CHECKSIG
export function p2pk (a: Payment, opts?: PaymentOpts): Payment { export function p2pk(a: Payment, opts?: PaymentOpts): Payment {
if ( if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature)
!a.input && throw new TypeError('Not enough data');
!a.output && opts = Object.assign({ validate: true }, opts || {});
!a.pubkey &&
!a.input &&
!a.signature
) throw new TypeError('Not enough data')
opts = Object.assign({ validate: true }, opts || {})
typef({ typef(
network: typef.maybe(typef.Object), {
output: typef.maybe(typef.Buffer), network: typef.maybe(typef.Object),
pubkey: typef.maybe(ecc.isPoint), output: typef.maybe(typef.Buffer),
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 = <()=>Array<Buffer | number>>lazy.value(function () { return bscript.decompile(a.input!) }) const _chunks = <() => Array<Buffer | number>>lazy.value(function() {
return bscript.decompile(a.input!);
});
const network = a.network || BITCOIN_NETWORK const network = a.network || BITCOIN_NETWORK;
const o: Payment = { network } const o: Payment = { network };
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!a.pubkey) return if (!a.pubkey) return;
return bscript.compile([ return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]);
a.pubkey, });
OPS.OP_CHECKSIG lazy.prop(o, 'pubkey', function() {
]) if (!a.output) return;
}) return a.output.slice(1, -1);
lazy.prop(o, 'pubkey', function () { });
if (!a.output) return lazy.prop(o, 'signature', function() {
return a.output.slice(1, -1) if (!a.input) return;
}) return <Buffer>_chunks()[0];
lazy.prop(o, 'signature', function () { });
if (!a.input) return lazy.prop(o, 'input', function() {
return <Buffer>_chunks()[0] if (!a.signature) return;
}) return bscript.compile([a.signature]);
lazy.prop(o, 'input', function () { });
if (!a.signature) return lazy.prop(o, 'witness', function() {
return bscript.compile([a.signature]) if (!o.input) return;
}) return [];
lazy.prop(o, 'witness', function () { });
if (!o.input) return
return []
})
// extended validation // extended validation
if (opts.validate) { if (opts.validate) {
if (a.output) { if (a.output) {
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid') if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG)
if (!ecc.isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid') throw new TypeError('Output is invalid');
if (a.pubkey && !a.pubkey.equals(o.pubkey!)) throw new TypeError('Pubkey mismatch') if (!ecc.isPoint(o.pubkey))
throw new TypeError('Output pubkey is invalid');
if (a.pubkey && !a.pubkey.equals(o.pubkey!))
throw new TypeError('Pubkey mismatch');
} }
if (a.signature) { if (a.signature) {
if (a.input && !a.input.equals(o.input!)) throw new TypeError('Signature mismatch') if (a.input && !a.input.equals(o.input!))
throw new TypeError('Signature mismatch');
} }
if (a.input) { if (a.input) {
if (_chunks().length !== 1) throw new TypeError('Input is invalid') if (_chunks().length !== 1) throw new TypeError('Input is invalid');
if (!bscript.isCanonicalScriptSignature(o.signature!)) throw new TypeError('Input has invalid signature') if (!bscript.isCanonicalScriptSignature(o.signature!))
throw new TypeError('Input has invalid signature');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

213
ts_src/payments/p2pkh.ts

@ -1,101 +1,103 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import * as bscript from '../script' import * as bscript from '../script';
import * as bcrypto from '../crypto' import * as bcrypto from '../crypto';
import * as lazy from './lazy' import * as lazy from './lazy';
import { bitcoin as BITCOIN_NETWORK } from '../networks' import { bitcoin as BITCOIN_NETWORK } from '../networks';
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');
const bs58check = require('bs58check') const bs58check = require('bs58check');
// input: {signature} {pubkey} // input: {signature} {pubkey}
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
export function p2pkh (a: Payment, opts?: PaymentOpts): Payment { export function p2pkh(a: Payment, opts?: PaymentOpts): Payment {
if ( if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input)
!a.address && throw new TypeError('Not enough data');
!a.hash && opts = Object.assign({ validate: true }, opts || {});
!a.output &&
!a.pubkey && typef(
!a.input {
) throw new TypeError('Not enough data') network: typef.maybe(typef.Object),
opts = Object.assign({ validate: true }, opts || {}) address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)),
typef({ output: typef.maybe(typef.BufferN(25)),
network: typef.maybe(typef.Object),
address: typef.maybe(typef.String), pubkey: typef.maybe(ecc.isPoint),
hash: typef.maybe(typef.BufferN(20)), signature: typef.maybe(bscript.isCanonicalScriptSignature),
output: typef.maybe(typef.BufferN(25)), input: typef.maybe(typef.Buffer),
},
pubkey: typef.maybe(ecc.isPoint), a,
signature: typef.maybe(bscript.isCanonicalScriptSignature), );
input: typef.maybe(typef.Buffer)
}, a) const _address = lazy.value(function() {
const payload = bs58check.decode(a.address);
const _address = lazy.value(function () { const version = payload.readUInt8(0);
const payload = bs58check.decode(a.address) const hash = payload.slice(1);
const version = payload.readUInt8(0) return { version, hash };
const hash = payload.slice(1) });
return { version, hash } const _chunks = <() => Array<Buffer | number>>lazy.value(function() {
}) return bscript.decompile(a.input!);
const _chunks = <()=>Array<Buffer | number>>lazy.value(function () { return bscript.decompile(a.input!) }) });
const network = a.network || BITCOIN_NETWORK const network = a.network || BITCOIN_NETWORK;
const o: Payment = { network } const o: Payment = { network };
lazy.prop(o, 'address', function () { lazy.prop(o, 'address', function() {
if (!o.hash) return if (!o.hash) 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', function () { lazy.prop(o, 'hash', function() {
if (a.output) return a.output.slice(3, 23) if (a.output) return a.output.slice(3, 23);
if (a.address) return _address().hash if (a.address) return _address().hash;
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!) if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!);
}) });
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!o.hash) return if (!o.hash) return;
return bscript.compile([ return bscript.compile([
OPS.OP_DUP, OPS.OP_DUP,
OPS.OP_HASH160, OPS.OP_HASH160,
o.hash, o.hash,
OPS.OP_EQUALVERIFY, OPS.OP_EQUALVERIFY,
OPS.OP_CHECKSIG OPS.OP_CHECKSIG,
]) ]);
}) });
lazy.prop(o, 'pubkey', function () { lazy.prop(o, 'pubkey', function() {
if (!a.input) return if (!a.input) return;
return <Buffer>_chunks()[1] return <Buffer>_chunks()[1];
}) });
lazy.prop(o, 'signature', function () { lazy.prop(o, 'signature', function() {
if (!a.input) return if (!a.input) return;
return <Buffer>_chunks()[0] return <Buffer>_chunks()[0];
}) });
lazy.prop(o, 'input', function () { lazy.prop(o, 'input', function() {
if (!a.pubkey) return if (!a.pubkey) 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', function () { lazy.prop(o, 'witness', function() {
if (!o.input) return if (!o.input) return;
return [] return [];
}) });
// extended validation // extended validation
if (opts.validate) { if (opts.validate) {
let hash: Buffer = Buffer.from([]) let hash: Buffer = Buffer.from([]);
if (a.address) { if (a.address) {
if (_address().version !== network.pubKeyHash) throw new TypeError('Invalid version or Network mismatch') if (_address().version !== network.pubKeyHash)
if (_address().hash.length !== 20) throw new TypeError('Invalid address') throw new TypeError('Invalid version or Network mismatch');
hash = _address().hash if (_address().hash.length !== 20) throw new TypeError('Invalid address');
hash = _address().hash;
} }
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(a.hash))
else hash = a.hash throw new TypeError('Hash mismatch');
else hash = a.hash;
} }
if (a.output) { if (a.output) {
@ -105,32 +107,41 @@ export function p2pkh (a: Payment, opts?: PaymentOpts): Payment {
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) throw new TypeError('Output is invalid') a.output[24] !== OPS.OP_CHECKSIG
)
const hash2 = a.output.slice(3, 23) throw new TypeError('Output is invalid');
if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
else hash = hash2 const hash2 = a.output.slice(3, 23);
if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch');
else 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)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(pkh))
else hash = pkh throw new TypeError('Hash mismatch');
else hash = pkh;
} }
if (a.input) { if (a.input) {
const chunks = _chunks() const chunks = _chunks();
if (chunks.length !== 2) throw new TypeError('Input is invalid') if (chunks.length !== 2) throw new TypeError('Input is invalid');
if (!bscript.isCanonicalScriptSignature(<Buffer>chunks[0])) throw new TypeError('Input has invalid signature') if (!bscript.isCanonicalScriptSignature(<Buffer>chunks[0]))
if (!ecc.isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey') throw new TypeError('Input has invalid signature');
if (!ecc.isPoint(chunks[1]))
if (a.signature && !a.signature.equals(<Buffer>chunks[0])) throw new TypeError('Signature mismatch') throw new TypeError('Input has invalid pubkey');
if (a.pubkey && !a.pubkey.equals(<Buffer>chunks[1])) throw new TypeError('Pubkey mismatch')
if (a.signature && !a.signature.equals(<Buffer>chunks[0]))
const pkh = bcrypto.hash160(<Buffer>chunks[1]) throw new TypeError('Signature mismatch');
if (hash.length > 0 && !hash.equals(pkh)) throw new TypeError('Hash mismatch') if (a.pubkey && !a.pubkey.equals(<Buffer>chunks[1]))
throw new TypeError('Pubkey mismatch');
const pkh = bcrypto.hash160(<Buffer>chunks[1]);
if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

269
ts_src/payments/p2sh.ts

@ -1,127 +1,127 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import { bitcoin as BITCOIN_NETWORK } from '../networks' // eslint-disable-line import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script' import * as bscript from '../script';
import * as bcrypto from '../crypto' import * as bcrypto from '../crypto';
import * as lazy from './lazy' import * as lazy from './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: Array<Buffer>, b: Array<Buffer>): boolean { function stacksEqual(a: Array<Buffer>, b: Array<Buffer>): boolean {
if (a.length !== b.length) return false if (a.length !== b.length) return false;
return a.every(function (x, i) { return a.every(function(x, i) {
return x.equals(b[i]) return x.equals(b[i]);
}) });
} }
// input: [redeemScriptSig ...] {redeemScript} // input: [redeemScriptSig ...] {redeemScript}
// witness: <?> // witness: <?>
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
export function p2sh (a: Payment, opts?: PaymentOpts): Payment { export function p2sh(a: Payment, opts?: PaymentOpts): Payment {
if ( if (!a.address && !a.hash && !a.output && !a.redeem && !a.input)
!a.address && throw new TypeError('Not enough data');
!a.hash && opts = Object.assign({ validate: true }, opts || {});
!a.output &&
!a.redeem && typef(
!a.input {
) throw new TypeError('Not enough data')
opts = Object.assign({ validate: true }, opts || {})
typef({
network: typef.maybe(typef.Object),
address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)),
output: typef.maybe(typef.BufferN(23)),
redeem: typef.maybe({
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer),
address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)),
output: typef.maybe(typef.BufferN(23)),
redeem: typef.maybe({
network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer),
input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}),
input: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)) witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}), },
input: typef.maybe(typef.Buffer), a,
witness: typef.maybe(typef.arrayOf(typef.Buffer)) );
}, a)
let network = a.network let network = a.network;
if (!network) { if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
} }
const o: Payment = { network } const o: Payment = { network };
const _address = lazy.value(function () { const _address = lazy.value(function() {
const payload = bs58check.decode(a.address) const payload = bs58check.decode(a.address);
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 };
}) });
const _chunks = <()=>Array<Buffer | number>>lazy.value(function () { return bscript.decompile(a.input!) }) const _chunks = <() => Array<Buffer | number>>lazy.value(function() {
const _redeem = lazy.value(function (): Payment { return bscript.decompile(a.input!);
const chunks = _chunks() });
const _redeem = lazy.value(function(): Payment {
const chunks = _chunks();
return { return {
network, network,
output: <Buffer>chunks[chunks.length - 1], output: <Buffer>chunks[chunks.length - 1],
input: bscript.compile(chunks.slice(0, -1)), input: bscript.compile(chunks.slice(0, -1)),
witness: a.witness || [] witness: a.witness || [],
} };
}) });
// output dependents // output dependents
lazy.prop(o, 'address', function () { lazy.prop(o, 'address', function() {
if (!o.hash) return if (!o.hash) 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);
return bs58check.encode(payload) return bs58check.encode(payload);
}) });
lazy.prop(o, 'hash', function () { lazy.prop(o, 'hash', function() {
// in order of least effort // in order of least effort
if (a.output) return a.output.slice(2, 22) if (a.output) return a.output.slice(2, 22);
if (a.address) return _address().hash if (a.address) return _address().hash;
if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output) if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output);
}) });
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!o.hash) return if (!o.hash) return;
return bscript.compile([ return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]);
OPS.OP_HASH160, });
o.hash,
OPS.OP_EQUAL
])
})
// input dependents // input dependents
lazy.prop(o, 'redeem', function () { lazy.prop(o, 'redeem', function() {
if (!a.input) return if (!a.input) return;
return _redeem() return _redeem();
}) });
lazy.prop(o, 'input', function () { lazy.prop(o, 'input', function() {
if (!a.redeem || !a.redeem.input || !a.redeem.output) return if (!a.redeem || !a.redeem.input || !a.redeem.output) return;
return bscript.compile((<Array<Buffer | number>>[]).concat( return bscript.compile(
<Array<Buffer | number>>bscript.decompile(a.redeem.input), (<Array<Buffer | number>>[]).concat(
a.redeem.output <Array<Buffer | number>>bscript.decompile(a.redeem.input),
)) a.redeem.output,
}) ),
lazy.prop(o, 'witness', function () { );
if (o.redeem && o.redeem.witness) return o.redeem.witness });
if (o.input) return [] lazy.prop(o, 'witness', function() {
}) if (o.redeem && o.redeem.witness) return o.redeem.witness;
if (o.input) return [];
});
if (opts.validate) { if (opts.validate) {
let hash: Buffer = Buffer.from([]) let hash: Buffer = Buffer.from([]);
if (a.address) { if (a.address) {
if (_address().version !== network.scriptHash) throw new TypeError('Invalid version or Network mismatch') if (_address().version !== network.scriptHash)
if (_address().hash.length !== 20) throw new TypeError('Invalid address') throw new TypeError('Invalid version or Network mismatch');
hash = _address().hash if (_address().hash.length !== 20) throw new TypeError('Invalid address');
hash = _address().hash;
} }
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(a.hash))
else hash = a.hash throw new TypeError('Hash mismatch');
else hash = a.hash;
} }
if (a.output) { if (a.output) {
@ -129,64 +129,79 @@ export function p2sh (a: Payment, opts?: PaymentOpts): Payment {
a.output.length !== 23 || 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) throw new TypeError('Output is invalid') a.output[22] !== OPS.OP_EQUAL
)
const hash2 = a.output.slice(2, 22) throw new TypeError('Output is invalid');
if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
else hash = hash2 const hash2 = a.output.slice(2, 22);
if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch');
else hash = hash2;
} }
// inlined to prevent 'no-inner-declarations' failing // inlined to prevent 'no-inner-declarations' failing
const checkRedeem = function (redeem: Payment): void { const checkRedeem = function(redeem: Payment): void {
// 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);
if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short') if (!decompile || decompile.length < 1)
throw new TypeError('Redeem.output too short');
// match hash against other sources // match hash against other sources
const hash2 = bcrypto.hash160(redeem.output) const hash2 = bcrypto.hash160(redeem.output);
if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(hash2))
else hash = hash2 throw new TypeError('Hash mismatch');
else 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) throw new TypeError('Empty input') if (!hasInput && !hasWitness) throw new TypeError('Empty input');
if (hasInput && hasWitness) throw new TypeError('Input and witness provided') if (hasInput && hasWitness)
throw new TypeError('Input and witness provided');
if (hasInput) { if (hasInput) {
const richunks = <Array<Buffer | number>>bscript.decompile(redeem.input) const richunks = <Array<Buffer | number>>(
if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig') bscript.decompile(redeem.input)
);
if (!bscript.isPushOnly(richunks))
throw new TypeError('Non push-only scriptSig');
} }
} }
} };
if (a.input) { if (a.input) {
const chunks = _chunks() const chunks = _chunks();
if (!chunks || chunks.length < 1) throw new TypeError('Input too short') if (!chunks || chunks.length < 1) throw new TypeError('Input too short');
if (!Buffer.isBuffer(_redeem().output)) throw new TypeError('Input is invalid') if (!Buffer.isBuffer(_redeem().output))
throw new TypeError('Input is invalid');
checkRedeem(_redeem()) checkRedeem(_redeem());
} }
if (a.redeem) { if (a.redeem) {
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') if (a.redeem.network && a.redeem.network !== network)
throw new TypeError('Network mismatch');
if (a.input) { if (a.input) {
const redeem = _redeem() const redeem = _redeem();
if (a.redeem.output && !a.redeem.output.equals(redeem.output!)) throw new TypeError('Redeem.output mismatch') if (a.redeem.output && !a.redeem.output.equals(redeem.output!))
if (a.redeem.input && !a.redeem.input.equals(redeem.input!)) throw new TypeError('Redeem.input mismatch') throw new TypeError('Redeem.output mismatch');
if (a.redeem.input && !a.redeem.input.equals(redeem.input!))
throw new TypeError('Redeem.input mismatch');
} }
checkRedeem(a.redeem) checkRedeem(a.redeem);
} }
if (a.witness) { if (a.witness) {
if ( if (
a.redeem && a.redeem &&
a.redeem.witness && a.redeem.witness &&
!stacksEqual(a.redeem.witness, a.witness)) throw new TypeError('Witness and redeem.witness mismatch') !stacksEqual(a.redeem.witness, a.witness)
)
throw new TypeError('Witness and redeem.witness mismatch');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

212
ts_src/payments/p2wpkh.ts

@ -1,134 +1,142 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import * as bscript from '../script' import * as bscript from '../script';
import * as bcrypto from '../crypto' import * as bcrypto from '../crypto';
import * as lazy from './lazy' import * as lazy from './lazy';
import { bitcoin as BITCOIN_NETWORK } from '../networks' import { bitcoin as BITCOIN_NETWORK } from '../networks';
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');
const bech32 = require('bech32') const bech32 = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0) const EMPTY_BUFFER = Buffer.alloc(0);
// witness: {signature} {pubKey} // witness: {signature} {pubKey}
// input: <> // input: <>
// output: OP_0 {pubKeyHash} // output: OP_0 {pubKeyHash}
export function p2wpkh (a: Payment, opts?: PaymentOpts): Payment { export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment {
if ( if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
!a.address && throw new TypeError('Not enough data');
!a.hash && opts = Object.assign({ validate: true }, opts || {});
!a.output &&
!a.pubkey && typef(
!a.witness {
) throw new TypeError('Not enough data') address: typef.maybe(typef.String),
opts = Object.assign({ validate: true }, opts || {}) hash: typef.maybe(typef.BufferN(20)),
input: typef.maybe(typef.BufferN(0)),
typef({ network: typef.maybe(typef.Object),
address: typef.maybe(typef.String), output: typef.maybe(typef.BufferN(22)),
hash: typef.maybe(typef.BufferN(20)), pubkey: typef.maybe(ecc.isPoint),
input: typef.maybe(typef.BufferN(0)), signature: typef.maybe(bscript.isCanonicalScriptSignature),
network: typef.maybe(typef.Object), witness: typef.maybe(typef.arrayOf(typef.Buffer)),
output: typef.maybe(typef.BufferN(22)), },
pubkey: typef.maybe(ecc.isPoint), a,
signature: typef.maybe(bscript.isCanonicalScriptSignature), );
witness: typef.maybe(typef.arrayOf(typef.Buffer))
}, a) const _address = lazy.value(function() {
const result = bech32.decode(a.address);
const _address = lazy.value(function () { const version = result.words.shift();
const result = bech32.decode(a.address) const data = bech32.fromWords(result.words);
const version = result.words.shift()
const data = bech32.fromWords(result.words)
return { return {
version, version,
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data) data: Buffer.from(data),
} };
}) });
const network = a.network || BITCOIN_NETWORK const network = a.network || BITCOIN_NETWORK;
const o: Payment = { network } const o: Payment = { network };
lazy.prop(o, 'address', function () { lazy.prop(o, 'address', function() {
if (!o.hash) return if (!o.hash) 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', function () { lazy.prop(o, 'hash', function() {
if (a.output) return a.output.slice(2, 22) if (a.output) return a.output.slice(2, 22);
if (a.address) return _address().data if (a.address) return _address().data;
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!) if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!);
}) });
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!o.hash) return if (!o.hash) return;
return bscript.compile([ return bscript.compile([OPS.OP_0, o.hash]);
OPS.OP_0, });
o.hash lazy.prop(o, 'pubkey', function() {
]) if (a.pubkey) return a.pubkey;
}) if (!a.witness) return;
lazy.prop(o, 'pubkey', function () { return a.witness[1];
if (a.pubkey) return a.pubkey });
if (!a.witness) return lazy.prop(o, 'signature', function() {
return a.witness[1] if (!a.witness) return;
}) return a.witness[0];
lazy.prop(o, 'signature', function () { });
if (!a.witness) return lazy.prop(o, 'input', function() {
return a.witness[0] if (!o.witness) return;
}) return EMPTY_BUFFER;
lazy.prop(o, 'input', function () { });
if (!o.witness) return lazy.prop(o, 'witness', function() {
return EMPTY_BUFFER if (!a.pubkey) return;
}) if (!a.signature) return;
lazy.prop(o, 'witness', function () { return [a.signature, a.pubkey];
if (!a.pubkey) return });
if (!a.signature) return
return [a.signature, a.pubkey]
})
// extended validation // extended validation
if (opts.validate) { if (opts.validate) {
let hash: Buffer = Buffer.from([]) let hash: Buffer = Buffer.from([]);
if (a.address) { if (a.address) {
if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch') if (network && network.bech32 !== _address().prefix)
if (_address().version !== 0x00) throw new TypeError('Invalid address version') throw new TypeError('Invalid prefix or Network mismatch');
if (_address().data.length !== 20) throw new TypeError('Invalid address data') if (_address().version !== 0x00)
hash = _address().data throw new TypeError('Invalid address version');
if (_address().data.length !== 20)
throw new TypeError('Invalid address data');
hash = _address().data;
} }
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(a.hash))
else hash = a.hash throw new TypeError('Hash mismatch');
else hash = a.hash;
} }
if (a.output) { if (a.output) {
if ( if (
a.output.length !== 22 || a.output.length !== 22 ||
a.output[0] !== OPS.OP_0 || a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x14) throw new TypeError('Output is invalid') a.output[1] !== 0x14
if (hash.length > 0 && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch') )
else hash = a.output.slice(2) throw new TypeError('Output is invalid');
if (hash.length > 0 && !hash.equals(a.output.slice(2)))
throw new TypeError('Hash mismatch');
else 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)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(pkh))
else hash = pkh throw new TypeError('Hash mismatch');
else hash = pkh;
} }
if (a.witness) { if (a.witness) {
if (a.witness.length !== 2) throw new TypeError('Witness is invalid') if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature') if (!bscript.isCanonicalScriptSignature(a.witness[0]))
if (!ecc.isPoint(a.witness[1])) throw new TypeError('Witness has invalid pubkey') throw new TypeError('Witness has invalid signature');
if (!ecc.isPoint(a.witness[1]))
if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch') throw new TypeError('Witness has invalid pubkey');
if (a.pubkey && !a.pubkey.equals(a.witness[1])) throw new TypeError('Pubkey mismatch')
if (a.signature && !a.signature.equals(a.witness[0]))
const pkh = bcrypto.hash160(a.witness[1]) throw new TypeError('Signature mismatch');
if (hash.length > 0 && !hash.equals(pkh)) throw new TypeError('Hash mismatch') if (a.pubkey && !a.pubkey.equals(a.witness[1]))
throw new TypeError('Pubkey mismatch');
const pkh = bcrypto.hash160(a.witness[1]);
if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

241
ts_src/payments/p2wsh.ts

@ -1,103 +1,100 @@
import { Payment, PaymentOpts } from './index' // eslint-disable-line import { Payment, PaymentOpts } from './index';
import { bitcoin as BITCOIN_NETWORK } from '../networks' // eslint-disable-line import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script' import * as bscript from '../script';
import * as bcrypto from '../crypto' import * as bcrypto from '../crypto';
import * as lazy from './lazy' import * as lazy from './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: Array<Buffer>, b: Array<Buffer>): boolean { function stacksEqual(a: Array<Buffer>, b: Array<Buffer>): boolean {
if (a.length !== b.length) return false if (a.length !== b.length) return false;
return a.every(function (x, i) { return a.every(function(x, i) {
return x.equals(b[i]) return x.equals(b[i]);
}) });
} }
// input: <> // input: <>
// witness: [redeemScriptSig ...] {redeemScript} // witness: [redeemScriptSig ...] {redeemScript}
// output: OP_0 {sha256(redeemScript)} // output: OP_0 {sha256(redeemScript)}
export function p2wsh (a: Payment, opts?: PaymentOpts): Payment { export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
if ( if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness)
!a.address && throw new TypeError('Not enough data');
!a.hash && opts = Object.assign({ validate: true }, opts || {});
!a.output &&
!a.redeem && typef(
!a.witness {
) throw new TypeError('Not enough data')
opts = Object.assign({ validate: true }, opts || {})
typef({
network: typef.maybe(typef.Object),
address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(32)),
output: typef.maybe(typef.BufferN(34)),
redeem: typef.maybe({
input: typef.maybe(typef.Buffer),
network: typef.maybe(typef.Object), network: typef.maybe(typef.Object),
output: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)) address: typef.maybe(typef.String),
}), hash: typef.maybe(typef.BufferN(32)),
input: typef.maybe(typef.BufferN(0)), output: typef.maybe(typef.BufferN(34)),
witness: typef.maybe(typef.arrayOf(typef.Buffer))
}, a) redeem: typef.maybe({
input: typef.maybe(typef.Buffer),
const _address = lazy.value(function () { network: typef.maybe(typef.Object),
const result = bech32.decode(a.address) output: typef.maybe(typef.Buffer),
const version = result.words.shift() witness: typef.maybe(typef.arrayOf(typef.Buffer)),
const data = bech32.fromWords(result.words) }),
input: typef.maybe(typef.BufferN(0)),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
},
a,
);
const _address = lazy.value(function() {
const result = bech32.decode(a.address);
const version = result.words.shift();
const data = bech32.fromWords(result.words);
return { return {
version, version,
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data) data: Buffer.from(data),
} };
}) });
const _rchunks = <()=>Array<Buffer | number>>lazy.value(function () { return bscript.decompile(a.redeem!.input!) }) const _rchunks = <() => Array<Buffer | number>>lazy.value(function() {
return bscript.decompile(a.redeem!.input!);
let network = a.network });
let network = a.network;
if (!network) { if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
} }
const o: Payment = { network } const o: Payment = { network };
lazy.prop(o, 'address', function () { lazy.prop(o, 'address', function() {
if (!o.hash) return if (!o.hash) 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', function () { lazy.prop(o, 'hash', function() {
if (a.output) return a.output.slice(2) if (a.output) return a.output.slice(2);
if (a.address) return _address().data if (a.address) return _address().data;
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output) if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output);
}) });
lazy.prop(o, 'output', function () { lazy.prop(o, 'output', function() {
if (!o.hash) return if (!o.hash) return;
return bscript.compile([ return bscript.compile([OPS.OP_0, o.hash]);
OPS.OP_0, });
o.hash lazy.prop(o, 'redeem', function() {
]) if (!a.witness) return;
})
lazy.prop(o, 'redeem', function () {
if (!a.witness) return
return { return {
output: a.witness[a.witness.length - 1], output: a.witness[a.witness.length - 1],
input: EMPTY_BUFFER, input: EMPTY_BUFFER,
witness: a.witness.slice(0, -1) witness: a.witness.slice(0, -1),
} };
}) });
lazy.prop(o, 'input', function () { lazy.prop(o, 'input', function() {
if (!o.witness) return if (!o.witness) return;
return EMPTY_BUFFER return EMPTY_BUFFER;
}) });
lazy.prop(o, 'witness', function () { lazy.prop(o, 'witness', function() {
// transform redeem input to witness stack? // transform redeem input to witness stack?
if ( if (
a.redeem && a.redeem &&
@ -106,47 +103,55 @@ export function p2wsh (a: Payment, opts?: PaymentOpts): Payment {
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 (<Array<Buffer>>[]).concat(stack, a.redeem.output) return (<Array<Buffer>>[]).concat(stack, a.redeem.output);
} }
if (!a.redeem) return if (!a.redeem) return;
if (!a.redeem.output) return if (!a.redeem.output) return;
if (!a.redeem.witness) return if (!a.redeem.witness) return;
return (<Array<Buffer>>[]).concat(a.redeem.witness, a.redeem.output) return (<Array<Buffer>>[]).concat(a.redeem.witness, a.redeem.output);
}) });
// extended validation // extended validation
if (opts.validate) { if (opts.validate) {
let hash: Buffer = Buffer.from([]) let hash: Buffer = Buffer.from([]);
if (a.address) { if (a.address) {
if (_address().prefix !== network.bech32) throw new TypeError('Invalid prefix or Network mismatch') if (_address().prefix !== network.bech32)
if (_address().version !== 0x00) throw new TypeError('Invalid address version') throw new TypeError('Invalid prefix or Network mismatch');
if (_address().data.length !== 32) throw new TypeError('Invalid address data') if (_address().version !== 0x00)
hash = _address().data throw new TypeError('Invalid address version');
if (_address().data.length !== 32)
throw new TypeError('Invalid address data');
hash = _address().data;
} }
if (a.hash) { if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(a.hash))
else hash = a.hash throw new TypeError('Hash mismatch');
else hash = a.hash;
} }
if (a.output) { if (a.output) {
if ( if (
a.output.length !== 34 || a.output.length !== 34 ||
a.output[0] !== OPS.OP_0 || a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x20) throw new TypeError('Output is invalid') a.output[1] !== 0x20
const hash2 = a.output.slice(2) )
if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch') throw new TypeError('Output is invalid');
else hash = hash2 const hash2 = a.output.slice(2);
if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch');
else hash = hash2;
} }
if (a.redeem) { if (a.redeem) {
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') if (a.redeem.network && a.redeem.network !== network)
throw new TypeError('Network mismatch');
// is there two redeem sources? // is there two redeem sources?
if ( if (
@ -154,26 +159,40 @@ export function p2wsh (a: Payment, opts?: PaymentOpts): Payment {
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) {
if (bscript.decompile(a.redeem.output)!.length === 0) throw new TypeError('Redeem.output is invalid') if (bscript.decompile(a.redeem.output)!.length === 0)
throw new TypeError('Redeem.output is invalid');
// match hash against other sources // match hash against other sources
const hash2 = bcrypto.sha256(a.redeem.output) const hash2 = bcrypto.sha256(a.redeem.output);
if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch') if (hash.length > 0 && !hash.equals(hash2))
else hash = hash2 throw new TypeError('Hash mismatch');
else hash = hash2;
} }
if (a.redeem.input && !bscript.isPushOnly(_rchunks())) throw new TypeError('Non push-only scriptSig') if (a.redeem.input && !bscript.isPushOnly(_rchunks()))
if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch') throw new TypeError('Non push-only scriptSig');
if (
a.witness &&
a.redeem.witness &&
!stacksEqual(a.witness, a.redeem.witness)
)
throw new TypeError('Witness and redeem.witness mismatch');
} }
if (a.witness) { if (a.witness) {
if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch') if (
a.redeem &&
a.redeem.output &&
!a.redeem.output.equals(a.witness[a.witness.length - 1])
)
throw new TypeError('Witness and redeem.output mismatch');
} }
} }
return Object.assign(o, a) return Object.assign(o, a);
} }

244
ts_src/script.ts

@ -1,206 +1,218 @@
import * as types from './types' import * as types from './types';
import * as scriptNumber from './script_number' import * as scriptNumber from './script_number';
import * as scriptSignature from './script_signature' import * as scriptSignature from './script_signature';
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');
const typeforce = require('typeforce') const typeforce = require('typeforce');
export type OpCode = number export type OpCode = number;
export const OPS = <{[index:string]: OpCode}> require('bitcoin-ops') export const OPS = <{ [index: string]: OpCode }>require('bitcoin-ops');
const REVERSE_OPS = <{[index:number]: string}> require('bitcoin-ops/map') const REVERSE_OPS = <{ [index: number]: string }>require('bitcoin-ops/map');
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
function isOPInt (value:number): boolean { function isOPInt(value: number): boolean {
return types.Number(value) && return (
((value === OPS.OP_0) || types.Number(value) &&
(value >= OPS.OP_1 && value <= OPS.OP_16) || (value === OPS.OP_0 ||
(value === OPS.OP_1NEGATE)) (value >= OPS.OP_1 && value <= OPS.OP_16) ||
value === OPS.OP_1NEGATE)
);
} }
function isPushOnlyChunk (value: number | Buffer): boolean { function isPushOnlyChunk(value: number | Buffer): boolean {
return types.Buffer(value) || isOPInt(<number>value) return types.Buffer(value) || isOPInt(<number>value);
} }
export function isPushOnly (value: Array<number | Buffer>) { export function isPushOnly(value: Array<number | Buffer>) {
return types.Array(value) && value.every(isPushOnlyChunk) return types.Array(value) && value.every(isPushOnlyChunk);
} }
function asMinimalOP (buffer: Buffer): number | void { function asMinimalOP(buffer: Buffer): number | void {
if (buffer.length === 0) return OPS.OP_0 if (buffer.length === 0) return OPS.OP_0;
if (buffer.length !== 1) return if (buffer.length !== 1) return;
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0] if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
if (buffer[0] === 0x81) return OPS.OP_1NEGATE if (buffer[0] === 0x81) return OPS.OP_1NEGATE;
} }
function chunksIsBuffer(buf: Buffer | Array<number | Buffer>): buf is Buffer { function chunksIsBuffer(buf: Buffer | Array<number | Buffer>): buf is Buffer {
return Buffer.isBuffer(buf) return Buffer.isBuffer(buf);
} }
function chunksIsArray(buf: Buffer | Array<number | Buffer>): buf is Array<number | Buffer> { function chunksIsArray(
return types.Array(buf) buf: Buffer | Array<number | Buffer>,
): buf is Array<number | Buffer> {
return types.Array(buf);
} }
function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer {
return Buffer.isBuffer(buf) return Buffer.isBuffer(buf);
} }
export function compile (chunks: Buffer | Array<number | Buffer>): Buffer { export function compile(chunks: Buffer | Array<number | Buffer>): Buffer {
// TODO: remove me // TODO: remove me
if (chunksIsBuffer(chunks)) return chunks if (chunksIsBuffer(chunks)) return chunks;
typeforce(types.Array, chunks) typeforce(types.Array, chunks);
const bufferSize = chunks.reduce(function (accum: number, chunk) { const bufferSize = chunks.reduce(function(accum: number, chunk) {
// data chunk // data chunk
if (singleChunkIsBuffer(chunk)) { if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy // adhere to BIP62.3, minimal push policy
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
return accum + 1 return accum + 1;
} }
return accum + pushdata.encodingLength(chunk.length) + chunk.length return accum + pushdata.encodingLength(chunk.length) + chunk.length;
} }
// opcode // opcode
return accum + 1 return accum + 1;
}, 0.0) }, 0.0);
const buffer = Buffer.allocUnsafe(bufferSize) const buffer = Buffer.allocUnsafe(bufferSize);
let offset = 0 let offset = 0;
chunks.forEach(function (chunk) { chunks.forEach(function(chunk) {
// data chunk // data chunk
if (singleChunkIsBuffer(chunk)) { if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy // adhere to BIP62.3, minimal push policy
const opcode = asMinimalOP(chunk) const opcode = asMinimalOP(chunk);
if (opcode !== undefined) { if (opcode !== undefined) {
buffer.writeUInt8(opcode, offset) buffer.writeUInt8(opcode, offset);
offset += 1 offset += 1;
return return;
} }
offset += pushdata.encode(buffer, chunk.length, offset) offset += pushdata.encode(buffer, chunk.length, offset);
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) throw new Error('Could not decode chunks') if (offset !== buffer.length) throw new Error('Could not decode chunks');
return buffer return buffer;
} }
export function decompile (buffer: Buffer | Array<number | Buffer>): Array<number | Buffer> | null { export function decompile(
buffer: Buffer | Array<number | Buffer>,
): Array<number | Buffer> | null {
// TODO: remove me // TODO: remove me
if (chunksIsArray(buffer)) return buffer if (chunksIsArray(buffer)) return buffer;
typeforce(types.Buffer, buffer) typeforce(types.Buffer, buffer);
const chunks: Array<number | Buffer> = [] const chunks: Array<number | Buffer> = [];
let i = 0 let i = 0;
while (i < buffer.length) { while (i < buffer.length) {
const opcode = buffer[i] const opcode = buffer[i];
// data chunk // data chunk
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { if (opcode > OPS.OP_0 && opcode <= 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) return null if (d === 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) return null if (i + d.number > buffer.length) 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;
} }
} }
return chunks return chunks;
} }
export function toASM (chunks: Buffer | Array<number | Buffer>): string { export function toASM(chunks: Buffer | Array<number | Buffer>): string {
if (chunksIsBuffer(chunks)) { if (chunksIsBuffer(chunks)) {
chunks = <Array<number | Buffer>>decompile(chunks) chunks = <Array<number | Buffer>>decompile(chunks);
} }
return chunks.map(function (chunk) { return chunks
// data? .map(function(chunk) {
if (singleChunkIsBuffer(chunk)) { // data?
const op = asMinimalOP(chunk) if (singleChunkIsBuffer(chunk)) {
if (op === undefined) return chunk.toString('hex') const op = asMinimalOP(chunk);
chunk = <number>op if (op === undefined) return chunk.toString('hex');
} chunk = <number>op;
}
// opcode! // opcode!
return REVERSE_OPS[chunk] return REVERSE_OPS[chunk];
}).join(' ') })
.join(' ');
} }
export function fromASM (asm: string): Buffer { export function fromASM(asm: string): Buffer {
typeforce(types.String, asm) typeforce(types.String, asm);
return compile(asm.split(' ').map(function (chunkStr) { return compile(
// opcode? asm.split(' ').map(function(chunkStr) {
if (OPS[chunkStr] !== undefined) return OPS[chunkStr] // opcode?
typeforce(types.Hex, chunkStr) if (OPS[chunkStr] !== undefined) return OPS[chunkStr];
typeforce(types.Hex, chunkStr);
// data! // data!
return Buffer.from(chunkStr, 'hex') return Buffer.from(chunkStr, 'hex');
})) }),
);
} }
export function toStack (chunks: Buffer | Array<number | Buffer>): Array<Buffer> { export function toStack(
chunks = <Array<number | Buffer>>decompile(chunks) chunks: Buffer | Array<number | Buffer>,
typeforce(isPushOnly, chunks) ): Array<Buffer> {
chunks = <Array<number | Buffer>>decompile(chunks);
typeforce(isPushOnly, chunks);
return chunks.map(function (op) { return chunks.map(function(op) {
if (singleChunkIsBuffer(op)) return op if (singleChunkIsBuffer(op)) return op;
if (op === OPS.OP_0) return Buffer.allocUnsafe(0) if (op === OPS.OP_0) return Buffer.allocUnsafe(0);
return scriptNumber.encode(op - OP_INT_BASE) return scriptNumber.encode(op - OP_INT_BASE);
}) });
} }
export function isCanonicalPubKey (buffer: Buffer): boolean { export function isCanonicalPubKey(buffer: Buffer): boolean {
return ecc.isPoint(buffer) return ecc.isPoint(buffer);
} }
export function isDefinedHashType (hashType: number): boolean { export function isDefinedHashType(hashType: number): boolean {
const hashTypeMod = hashType & ~0x80 const hashTypeMod = hashType & ~0x80;
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
return hashTypeMod > 0x00 && hashTypeMod < 0x04 return hashTypeMod > 0x00 && hashTypeMod < 0x04;
} }
export function isCanonicalScriptSignature (buffer: Buffer): boolean { export function isCanonicalScriptSignature(buffer: Buffer): boolean {
if (!Buffer.isBuffer(buffer)) return false if (!Buffer.isBuffer(buffer)) 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));
} }
export const number = scriptNumber export const number = scriptNumber;
export const signature = scriptSignature export const signature = scriptSignature;

77
ts_src/script_number.ts

@ -1,62 +1,71 @@
export function decode(
buffer: Buffer,
maxLength?: number,
minimal?: boolean,
): number {
maxLength = maxLength || 4;
minimal = minimal === undefined ? true : minimal;
const length = buffer.length;
export function decode (buffer: Buffer, maxLength?: number, minimal?: boolean): number { if (length === 0) return 0;
maxLength = maxLength || 4 if (length > maxLength) throw new TypeError('Script number overflow');
minimal = minimal === undefined ? true : minimal
const length = buffer.length
if (length === 0) return 0
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) throw new Error('Non-minimally encoded script number') if (length <= 1 || (buffer[length - 2] & 0x80) === 0)
throw new Error('Non-minimally encoded script number');
} }
} }
// 40-bit // 40-bit
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) return -(((b & ~0x80) * 0x100000000) + a) if (b & 0x80) 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
let result = 0 let result = 0;
for (var i = 0; i < length; ++i) { for (var i = 0; i < length; ++i) {
result |= buffer[i] << (8 * i) result |= buffer[i] << (8 * i);
} }
if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) if (buffer[length - 1] & 0x80)
return result return -(result & ~(0x80 << (8 * (length - 1))));
return result;
} }
function scriptNumSize (i: number): number { function scriptNumSize(i: number): number {
return i > 0x7fffffff ? 5 return i > 0x7fffffff
: i > 0x7fffff ? 4 ? 5
: i > 0x7fff ? 3 : i > 0x7fffff
: i > 0x7f ? 2 ? 4
: i > 0x00 ? 1 : i > 0x7fff
: 0 ? 3
: i > 0x7f
? 2
: i > 0x00
? 1
: 0;
} }
export function encode (number: number): Buffer { export function encode(number: number): Buffer {
let value = Math.abs(number) let value = Math.abs(number);
const size = scriptNumSize(value) const size = scriptNumSize(value);
const buffer = Buffer.allocUnsafe(size) const buffer = Buffer.allocUnsafe(size);
const negative = number < 0 const negative = number < 0;
for (var i = 0; i < size; ++i) { for (var i = 0; i < size; ++i) {
buffer.writeUInt8(value & 0xff, i) buffer.writeUInt8(value & 0xff, i);
value >>= 8 value >>= 8;
} }
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;
} }

88
ts_src/script_signature.ts

@ -1,64 +1,66 @@
import * as types from './types' import * as types from './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: Buffer): Buffer { function toDER(x: Buffer): Buffer {
let i = 0 let i = 0;
while (x[i] === 0) ++i while (x[i] === 0) ++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) return Buffer.concat([ZERO, x], 1 + x.length) if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length);
return x return x;
} }
function fromDER (x: Buffer): Buffer { function fromDER(x: Buffer): Buffer {
if (x[0] === 0x00) x = x.slice(1) if (x[0] === 0x00) 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);
return buffer return buffer;
} }
interface ScriptSignature { interface ScriptSignature {
signature: Buffer signature: Buffer;
hashType: number hashType: number;
} }
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
export function decode (buffer: Buffer): ScriptSignature { export function decode(buffer: Buffer): ScriptSignature {
const hashType = buffer.readUInt8(buffer.length - 1) const hashType = buffer.readUInt8(buffer.length - 1);
const hashTypeMod = hashType & ~0x80 const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) if (hashTypeMod <= 0 || hashTypeMod >= 4)
throw new Error('Invalid hashType ' + hashType);
const decode = bip66.decode(buffer.slice(0, -1)) const decode = bip66.decode(buffer.slice(0, -1));
const r = fromDER(decode.r) const r = fromDER(decode.r);
const s = fromDER(decode.s) const s = fromDER(decode.s);
return { return {
signature: Buffer.concat([r, s], 64), signature: Buffer.concat([r, s], 64),
hashType: hashType hashType: hashType,
} };
} }
export function encode (signature: Buffer, hashType: number): Buffer { export function encode(signature: Buffer, hashType: number): Buffer {
typeforce({ typeforce(
signature: types.BufferN(64), {
hashType: types.UInt8 signature: types.BufferN(64),
}, { signature, hashType }) hashType: types.UInt8,
},
{ signature, hashType },
);
const hashTypeMod = hashType & ~0x80 const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) if (hashTypeMod <= 0 || hashTypeMod >= 4)
throw new Error('Invalid hashType ' + hashType);
const hashTypeBuffer = Buffer.allocUnsafe(1) const hashTypeBuffer = Buffer.allocUnsafe(1);
hashTypeBuffer.writeUInt8(hashType, 0) hashTypeBuffer.writeUInt8(hashType, 0);
const r = toDER(signature.slice(0, 32)) const r = toDER(signature.slice(0, 32));
const s = toDER(signature.slice(32, 64)) const s = toDER(signature.slice(32, 64));
return Buffer.concat([ return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]);
bip66.encode(r, s),
hashTypeBuffer
])
} }

21
ts_src/templates/nulldata.ts

@ -1,17 +1,16 @@
// OP_RETURN {data} // OP_RETURN {data}
import * as bscript from '../script' import * as bscript from '../script';
const OPS = bscript.OPS const OPS = bscript.OPS;
export function check (script: Buffer | Array<number | Buffer>): boolean { export function check(script: Buffer | Array<number | Buffer>): boolean {
const buffer = bscript.compile(script) const buffer = bscript.compile(script);
return buffer.length > 1 && return buffer.length > 1 && buffer[0] === OPS.OP_RETURN;
buffer[0] === OPS.OP_RETURN
} }
check.toJSON = function () { return 'null data output' } check.toJSON = function() {
return 'null data output';
};
const output = { check } const output = { check };
export { export { output };
output
}

712
ts_src/transaction.ts

@ -1,282 +1,311 @@
import * as bcrypto from './crypto' import * as bcrypto from './crypto';
import * as bscript from './script' import * as bscript from './script';
import * as types from './types' import * as types from './types';
import * as bufferutils from './bufferutils' import * as bufferutils from './bufferutils';
import { reverseBuffer } from './bufferutils' import { reverseBuffer } from './bufferutils';
import { OPS as opcodes } from './script' import { OPS as opcodes } from './script';
const typeforce = require('typeforce') const typeforce = require('typeforce');
const varuint = require('varuint-bitcoin') const varuint = require('varuint-bitcoin');
function varSliceSize (someScript: Buffer): number { function varSliceSize(someScript: Buffer): number {
const length = someScript.length const length = someScript.length;
return varuint.encodingLength(length) + length return varuint.encodingLength(length) + length;
} }
function vectorSize (someVector: Array<Buffer>): number { function vectorSize(someVector: Array<Buffer>): number {
const length = someVector.length const length = someVector.length;
return varuint.encodingLength(length) + someVector.reduce((sum, witness) => { return (
return sum + varSliceSize(witness) varuint.encodingLength(length) +
}, 0) someVector.reduce((sum, witness) => {
return sum + varSliceSize(witness);
}, 0)
);
} }
const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0) const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0);
const EMPTY_WITNESS: Array<Buffer> = [] const EMPTY_WITNESS: Array<Buffer> = [];
const ZERO: Buffer = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') const ZERO: Buffer = Buffer.from(
const ONE: Buffer = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex') '0000000000000000000000000000000000000000000000000000000000000000',
const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex') 'hex',
);
const ONE: Buffer = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex',
);
const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT: BlankOutput = { const BLANK_OUTPUT: BlankOutput = {
script: EMPTY_SCRIPT, script: EMPTY_SCRIPT,
valueBuffer: VALUE_UINT64_MAX valueBuffer: VALUE_UINT64_MAX,
} };
function isOutput(out: Output | BlankOutput): out is Output { function isOutput(out: Output | BlankOutput): out is Output {
return (<Output>out).value !== undefined return (<Output>out).value !== undefined;
} }
export type BlankOutput = { export type BlankOutput = {
script: Buffer script: Buffer;
valueBuffer: Buffer valueBuffer: Buffer;
} };
export type Output = { export type Output = {
script: Buffer script: Buffer;
value: number value: number;
} };
export type Input = { export type Input = {
hash: Buffer hash: Buffer;
index: number index: number;
script: Buffer script: Buffer;
sequence: number sequence: number;
witness: Array<Buffer> witness: Array<Buffer>;
} };
export class Transaction { export class Transaction {
version: number version: number;
locktime: number locktime: number;
ins: Array<Input> ins: Array<Input>;
outs: Array<Output | BlankOutput> outs: Array<Output | BlankOutput>;
static readonly DEFAULT_SEQUENCE = 0xffffffff static readonly DEFAULT_SEQUENCE = 0xffffffff;
static readonly SIGHASH_ALL = 0x01 static readonly SIGHASH_ALL = 0x01;
static readonly SIGHASH_NONE = 0x02 static readonly SIGHASH_NONE = 0x02;
static readonly SIGHASH_SINGLE = 0x03 static readonly SIGHASH_SINGLE = 0x03;
static readonly SIGHASH_ANYONECANPAY = 0x80 static readonly SIGHASH_ANYONECANPAY = 0x80;
static readonly ADVANCED_TRANSACTION_MARKER = 0x00 static readonly ADVANCED_TRANSACTION_MARKER = 0x00;
static readonly ADVANCED_TRANSACTION_FLAG = 0x01 static readonly ADVANCED_TRANSACTION_FLAG = 0x01;
constructor () { constructor() {
this.version = 1 this.version = 1;
this.locktime = 0 this.locktime = 0;
this.ins = [] this.ins = [];
this.outs = [] this.outs = [];
} }
static fromBuffer (buffer: Buffer, __noStrict?: boolean): Transaction { static fromBuffer(buffer: Buffer, __noStrict?: boolean): Transaction {
let offset: number = 0 let offset: number = 0;
function readSlice (n: number): Buffer { function readSlice(n: number): Buffer {
offset += n offset += n;
return buffer.slice(offset - n, offset) return buffer.slice(offset - n, offset);
} }
function readUInt32 (): number { function readUInt32(): number {
const i = buffer.readUInt32LE(offset) const i = buffer.readUInt32LE(offset);
offset += 4 offset += 4;
return i return i;
} }
function readInt32 (): number { function readInt32(): number {
const i = buffer.readInt32LE(offset) const i = buffer.readInt32LE(offset);
offset += 4 offset += 4;
return i return i;
} }
function readUInt64 (): number { function readUInt64(): number {
const i = bufferutils.readUInt64LE(buffer, offset) const i = bufferutils.readUInt64LE(buffer, offset);
offset += 8 offset += 8;
return i return i;
} }
function readVarInt (): number { function readVarInt(): number {
const vi = varuint.decode(buffer, offset) const vi = varuint.decode(buffer, offset);
offset += varuint.decode.bytes offset += varuint.decode.bytes;
return vi return vi;
} }
function readVarSlice (): Buffer { function readVarSlice(): Buffer {
return readSlice(readVarInt()) return readSlice(readVarInt());
} }
function readVector (): Array<Buffer> { function readVector(): Array<Buffer> {
const count = readVarInt() const count = readVarInt();
const vector: Array<Buffer> = [] const vector: Array<Buffer> = [];
for (var i = 0; i < count; i++) vector.push(readVarSlice()) for (var i = 0; i < count; i++) vector.push(readVarSlice());
return vector return vector;
} }
const tx = new Transaction() const tx = new Transaction();
tx.version = readInt32() tx.version = readInt32();
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 &&
offset += 2 flag === Transaction.ADVANCED_TRANSACTION_FLAG
hasWitnesses = true ) {
offset += 2;
hasWitnesses = true;
} }
const vinLen = readVarInt() const vinLen = readVarInt();
for (var i = 0; i < vinLen; ++i) { for (var i = 0; i < vinLen; ++i) {
tx.ins.push({ tx.ins.push({
hash: readSlice(32), hash: readSlice(32),
index: readUInt32(), index: readUInt32(),
script: readVarSlice(), script: readVarSlice(),
sequence: readUInt32(), sequence: readUInt32(),
witness: EMPTY_WITNESS witness: EMPTY_WITNESS,
}) });
} }
const voutLen = readVarInt() const voutLen = readVarInt();
for (i = 0; i < voutLen; ++i) { for (i = 0; i < voutLen; ++i) {
tx.outs.push({ tx.outs.push({
value: readUInt64(), value: readUInt64(),
script: readVarSlice() script: readVarSlice(),
}) });
} }
if (hasWitnesses) { if (hasWitnesses) {
for (i = 0; i < vinLen; ++i) { for (i = 0; i < vinLen; ++i) {
tx.ins[i].witness = readVector() tx.ins[i].witness = readVector();
} }
// was this pointless? // was this pointless?
if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data') if (!tx.hasWitnesses())
throw new Error('Transaction has superfluous witness data');
} }
tx.locktime = readUInt32() tx.locktime = readUInt32();
if (__noStrict) return tx if (__noStrict) return tx;
if (offset !== buffer.length) throw new Error('Transaction has unexpected data') if (offset !== buffer.length)
throw new Error('Transaction has unexpected data');
return tx return tx;
} }
static fromHex (hex: string): Transaction { static fromHex(hex: string): Transaction {
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false) return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
} }
static isCoinbaseHash (buffer: Buffer): boolean { static isCoinbaseHash(buffer: Buffer): boolean {
typeforce(types.Hash256bit, buffer) typeforce(types.Hash256bit, buffer);
for (var i = 0; i < 32; ++i) { for (var i = 0; i < 32; ++i) {
if (buffer[i] !== 0) return false if (buffer[i] !== 0) return false;
} }
return true return true;
} }
isCoinbase (): boolean { isCoinbase(): boolean {
return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) return (
this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
);
} }
addInput (hash: Buffer, index: number, sequence?: number, scriptSig?: Buffer): number { addInput(
typeforce(types.tuple( hash: Buffer,
types.Hash256bit, index: number,
types.UInt32, sequence?: number,
types.maybe(types.UInt32), scriptSig?: Buffer,
types.maybe(types.Buffer) ): number {
), 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 (
hash: hash, this.ins.push({
index: index, hash: hash,
script: scriptSig || EMPTY_SCRIPT, index: index,
sequence: <number>sequence, script: scriptSig || EMPTY_SCRIPT,
witness: EMPTY_WITNESS sequence: <number>sequence,
}) - 1) witness: EMPTY_WITNESS,
}) - 1
);
} }
addOutput (scriptPubKey: Buffer, value: number): number { addOutput(scriptPubKey: Buffer, value: number): number {
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 (
script: scriptPubKey, this.outs.push({
value: value script: scriptPubKey,
}) - 1) value: value,
}) - 1
);
} }
hasWitnesses (): boolean { hasWitnesses(): boolean {
return this.ins.some((x) => { return this.ins.some(x => {
return x.witness.length !== 0 return x.witness.length !== 0;
}) });
} }
weight (): number { weight(): number {
const base = this.__byteLength(false) const base = this.__byteLength(false);
const total = this.__byteLength(true) const total = this.__byteLength(true);
return base * 3 + total return base * 3 + total;
} }
virtualSize (): number { virtualSize(): number {
return Math.ceil(this.weight() / 4) return Math.ceil(this.weight() / 4);
} }
byteLength (): number { byteLength(): number {
return this.__byteLength(true) return this.__byteLength(true);
} }
private __byteLength (__allowWitness: boolean): number { private __byteLength(__allowWitness: boolean): number {
const hasWitnesses = __allowWitness && this.hasWitnesses() const hasWitnesses = __allowWitness && this.hasWitnesses();
return ( return (
(hasWitnesses ? 10 : 8) + (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) => {
return sum + 40 + varSliceSize(input.script) return sum + 40 + varSliceSize(input.script);
}, 0) + }, 0) +
this.outs.reduce((sum, output) => { this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script) return sum + 8 + varSliceSize(output.script);
}, 0) + }, 0) +
(hasWitnesses ? this.ins.reduce((sum, input) => { (hasWitnesses
return sum + vectorSize(input.witness) ? this.ins.reduce((sum, input) => {
}, 0) : 0) return sum + vectorSize(input.witness);
) }, 0)
: 0)
);
} }
clone (): Transaction { clone(): Transaction {
const newTx = new Transaction() const newTx = new Transaction();
newTx.version = this.version newTx.version = this.version;
newTx.locktime = this.locktime newTx.locktime = this.locktime;
newTx.ins = this.ins.map((txIn) => { newTx.ins = this.ins.map(txIn => {
return { return {
hash: txIn.hash, hash: txIn.hash,
index: txIn.index, index: txIn.index,
script: txIn.script, script: txIn.script,
sequence: txIn.sequence, sequence: txIn.sequence,
witness: txIn.witness witness: txIn.witness,
} };
}) });
newTx.outs = this.outs.map((txOut) => { newTx.outs = this.outs.map(txOut => {
return { return {
script: txOut.script, script: txOut.script,
value: (<Output>txOut).value value: (<Output>txOut).value,
} };
}) });
return newTx return newTx;
} }
/** /**
@ -287,284 +316,313 @@ export class Transaction {
* hashType, and then hashes the result. * hashType, and then hashes the result.
* 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: number, prevOutScript: Buffer, hashType: number): Buffer { hashForSignature(
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments) inIndex: number,
prevOutScript: Buffer,
hashType: number,
): Buffer {
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) return ONE if (inIndex >= this.ins.length) return ONE;
// ignore OP_CODESEPARATOR // ignore OP_CODESEPARATOR
const ourScript = bscript.compile(bscript.decompile(prevOutScript)!.filter((x) => { const ourScript = bscript.compile(
return x !== opcodes.OP_CODESEPARATOR bscript.decompile(prevOutScript)!.filter(x => {
})) return x !== opcodes.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) return if (i === inIndex) 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) return ONE if (inIndex >= this.outs.length) return ONE;
// truncate outputs after // truncate outputs after
txTmp.outs.length = inIndex + 1 txTmp.outs.length = inIndex + 1;
// "blank" outputs before // "blank" outputs before
for (var i = 0; i < inIndex; i++) { for (var i = 0; i < inIndex; i++) {
txTmp.outs[i] = BLANK_OUTPUT txTmp.outs[i] = BLANK_OUTPUT;
} }
// 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) return if (y === inIndex) return;
input.sequence = 0 input.sequence = 0;
}) });
} }
// SIGHASH_ANYONECANPAY: ignore inputs entirely? // SIGHASH_ANYONECANPAY: ignore inputs entirely?
if (hashType & Transaction.SIGHASH_ANYONECANPAY) { if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
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;
}) });
txTmp.ins[inIndex].script = ourScript txTmp.ins[inIndex].script = ourScript;
} }
// serialize and hash // serialize and hash
const buffer: Buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4) const buffer: Buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4);
buffer.writeInt32LE(hashType, buffer.length - 4) buffer.writeInt32LE(hashType, buffer.length - 4);
txTmp.__toBuffer(buffer, 0, false) txTmp.__toBuffer(buffer, 0, false);
return bcrypto.hash256(buffer) return bcrypto.hash256(buffer);
} }
hashForWitnessV0 (inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer { hashForWitnessV0(
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments) inIndex: number,
prevOutScript: Buffer,
value: number,
hashType: number,
): Buffer {
typeforce(
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
arguments,
);
let tbuffer: Buffer = Buffer.from([]) let tbuffer: Buffer = Buffer.from([]);
let toffset: number = 0 let toffset: number = 0;
function writeSlice (slice: Buffer): void { function writeSlice(slice: Buffer): void {
toffset += slice.copy(tbuffer, toffset) toffset += slice.copy(tbuffer, toffset);
} }
function writeUInt32 (i: number): void { function writeUInt32(i: number): void {
toffset = tbuffer.writeUInt32LE(i, toffset) toffset = tbuffer.writeUInt32LE(i, toffset);
} }
function writeUInt64 (i: number): void { function writeUInt64(i: number): void {
toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset);
} }
function writeVarInt (i: number): void { function writeVarInt(i: number): void {
varuint.encode(i, tbuffer, toffset) varuint.encode(i, tbuffer, toffset);
toffset += varuint.encode.bytes toffset += varuint.encode.bytes;
} }
function writeVarSlice (slice: Buffer): void { function writeVarSlice(slice: Buffer): void {
writeVarInt(slice.length) writeVarInt(slice.length);
writeSlice(slice) writeSlice(slice);
} }
let hashOutputs = ZERO let hashOutputs = ZERO;
let hashPrevouts = ZERO let hashPrevouts = ZERO;
let hashSequence = ZERO let hashSequence = ZERO;
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
tbuffer = Buffer.allocUnsafe(36 * this.ins.length) tbuffer = Buffer.allocUnsafe(36 * this.ins.length);
toffset = 0 toffset = 0;
this.ins.forEach((txIn) => { this.ins.forEach(txIn => {
writeSlice(txIn.hash) writeSlice(txIn.hash);
writeUInt32(txIn.index) writeUInt32(txIn.index);
}) });
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) ) {
toffset = 0 tbuffer = Buffer.allocUnsafe(4 * this.ins.length);
toffset = 0;
this.ins.forEach((txIn) => { this.ins.forEach(txIn => {
writeUInt32(txIn.sequence) writeUInt32(txIn.sequence);
}) });
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);
tbuffer = Buffer.allocUnsafe(txOutsSize) tbuffer = Buffer.allocUnsafe(txOutsSize);
toffset = 0 toffset = 0;
this.outs.forEach((out) => { this.outs.forEach(out => {
writeUInt64((<Output>out).value) writeUInt64((<Output>out).value);
writeVarSlice(out.script) writeVarSlice(out.script);
}) });
hashOutputs = bcrypto.hash256(tbuffer) hashOutputs = bcrypto.hash256(tbuffer);
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { } else if (
const output = this.outs[inIndex] (hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
inIndex < this.outs.length
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)) ) {
toffset = 0 const output = this.outs[inIndex];
writeUInt64((<Output>output).value)
writeVarSlice(output.script) tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script));
toffset = 0;
hashOutputs = bcrypto.hash256(tbuffer) writeUInt64((<Output>output).value);
} writeVarSlice(output.script);
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)) hashOutputs = bcrypto.hash256(tbuffer);
toffset = 0 }
const input = this.ins[inIndex] tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript));
writeUInt32(this.version) toffset = 0;
writeSlice(hashPrevouts)
writeSlice(hashSequence) const input = this.ins[inIndex];
writeSlice(input.hash) writeUInt32(this.version);
writeUInt32(input.index) writeSlice(hashPrevouts);
writeVarSlice(prevOutScript) writeSlice(hashSequence);
writeUInt64(value) writeSlice(input.hash);
writeUInt32(input.sequence) writeUInt32(input.index);
writeSlice(hashOutputs) writeVarSlice(prevOutScript);
writeUInt32(this.locktime) writeUInt64(value);
writeUInt32(hashType) writeUInt32(input.sequence);
return bcrypto.hash256(tbuffer) writeSlice(hashOutputs);
writeUInt32(this.locktime);
writeUInt32(hashType);
return bcrypto.hash256(tbuffer);
} }
getHash (forWitness?: boolean): Buffer { getHash(forWitness?: boolean): Buffer {
// wtxid for coinbase is always 32 bytes of 0x00 // wtxid for coinbase is always 32 bytes of 0x00
if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0) if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0);
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)) return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness));
} }
getId (): string { getId(): string {
// transaction hash's are displayed in reverse order // transaction hash's are displayed in reverse order
return reverseBuffer(this.getHash(false)).toString('hex') return reverseBuffer(this.getHash(false)).toString('hex');
} }
toBuffer (buffer?: Buffer, initialOffset?: number): Buffer { toBuffer(buffer?: Buffer, initialOffset?: number): Buffer {
return this.__toBuffer(buffer, initialOffset, true) return this.__toBuffer(buffer, initialOffset, true);
} }
private __toBuffer (buffer?: Buffer, initialOffset?: number, __allowWitness?: boolean): Buffer { private __toBuffer(
if (!buffer) buffer = <Buffer> Buffer.allocUnsafe(this.__byteLength(__allowWitness!)) buffer?: Buffer,
initialOffset?: number,
__allowWitness?: boolean,
): Buffer {
if (!buffer)
buffer = <Buffer>Buffer.allocUnsafe(this.__byteLength(__allowWitness!));
let offset = initialOffset || 0 let offset = initialOffset || 0;
function writeSlice (slice: Buffer): void { function writeSlice(slice: Buffer): void {
offset += slice.copy(buffer!, offset) offset += slice.copy(buffer!, offset);
} }
function writeUInt8 (i: number) { function writeUInt8(i: number) {
offset = (buffer!).writeUInt8(i, offset) offset = buffer!.writeUInt8(i, offset);
} }
function writeUInt32 (i: number) { function writeUInt32(i: number) {
offset = (buffer!).writeUInt32LE(i, offset) offset = buffer!.writeUInt32LE(i, offset);
} }
function writeInt32 (i: number) { function writeInt32(i: number) {
offset = (buffer!).writeInt32LE(i, offset) offset = buffer!.writeInt32LE(i, offset);
} }
function writeUInt64 (i: number) { function writeUInt64(i: number) {
offset = bufferutils.writeUInt64LE(buffer!, i, offset) offset = bufferutils.writeUInt64LE(buffer!, i, offset);
} }
function writeVarInt (i: number) { function writeVarInt(i: number) {
varuint.encode(i, buffer, offset) varuint.encode(i, buffer, offset);
offset += varuint.encode.bytes offset += varuint.encode.bytes;
} }
function writeVarSlice (slice: Buffer) { function writeVarSlice(slice: Buffer) {
writeVarInt(slice.length) writeVarInt(slice.length);
writeSlice(slice) writeSlice(slice);
} }
function writeVector (vector: Array<Buffer>) { function writeVector(vector: Array<Buffer>) {
writeVarInt(vector.length) writeVarInt(vector.length);
vector.forEach(writeVarSlice) vector.forEach(writeVarSlice);
} }
writeInt32(this.version) writeInt32(this.version);
const hasWitnesses = __allowWitness && this.hasWitnesses() const hasWitnesses = __allowWitness && this.hasWitnesses();
if (hasWitnesses) { if (hasWitnesses) {
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER) writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER);
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG);
} }
writeVarInt(this.ins.length) writeVarInt(this.ins.length);
this.ins.forEach((txIn) => { this.ins.forEach(txIn => {
writeSlice(txIn.hash) writeSlice(txIn.hash);
writeUInt32(txIn.index) writeUInt32(txIn.index);
writeVarSlice(txIn.script) writeVarSlice(txIn.script);
writeUInt32(txIn.sequence) writeUInt32(txIn.sequence);
}) });
writeVarInt(this.outs.length) writeVarInt(this.outs.length);
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);
}) });
if (hasWitnesses) { if (hasWitnesses) {
this.ins.forEach((input) => { this.ins.forEach(input => {
writeVector(input.witness) writeVector(input.witness);
}) });
} }
writeUInt32(this.locktime) writeUInt32(this.locktime);
// avoid slicing unless necessary // avoid slicing unless necessary
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) if (initialOffset !== undefined) return buffer.slice(initialOffset, offset);
return buffer return buffer;
} }
toHex () { toHex() {
return this.toBuffer(undefined, undefined).toString('hex') return this.toBuffer(undefined, undefined).toString('hex');
} }
setInputScript (index: number, scriptSig: Buffer) { setInputScript(index: number, scriptSig: Buffer) {
typeforce(types.tuple(types.Number, types.Buffer), arguments) typeforce(types.tuple(types.Number, types.Buffer), arguments);
this.ins[index].script = scriptSig this.ins[index].script = scriptSig;
} }
setWitness (index: number, witness: Array<Buffer>) { setWitness(index: number, witness: Array<Buffer>) {
typeforce(types.tuple(types.Number, [types.Buffer]), arguments) typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
this.ins[index].witness = witness this.ins[index].witness = witness;
} }
} }

867
ts_src/transaction_builder.ts

File diff suppressed because it is too large

64
ts_src/types.ts

@ -1,49 +1,51 @@
const typeforce = require('typeforce') const typeforce = require('typeforce');
const UINT31_MAX: number = Math.pow(2, 31) - 1 const UINT31_MAX: number = Math.pow(2, 31) - 1;
export function UInt31 (value: number): boolean { export function UInt31(value: number): boolean {
return typeforce.UInt32(value) && value <= UINT31_MAX return typeforce.UInt32(value) && value <= UINT31_MAX;
} }
export function BIP32Path (value: string): boolean { export function BIP32Path(value: string): boolean {
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/);
} }
BIP32Path.toJSON = function () { return 'BIP32 derivation path' } BIP32Path.toJSON = function() {
return 'BIP32 derivation path';
};
const SATOSHI_MAX: number = 21 * 1e14 const SATOSHI_MAX: number = 21 * 1e14;
export function Satoshi (value: number): boolean { export function Satoshi(value: number): boolean {
return typeforce.UInt53(value) && value <= SATOSHI_MAX return typeforce.UInt53(value) && value <= SATOSHI_MAX;
} }
// external dependent types // external dependent types
export const ECPoint = typeforce.quacksLike('Point') export const ECPoint = typeforce.quacksLike('Point');
// exposed, external API // exposed, external API
export const Network = typeforce.compile({ export const Network = typeforce.compile({
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
bip32: { bip32: {
public: typeforce.UInt32, public: typeforce.UInt32,
private: typeforce.UInt32 private: typeforce.UInt32,
}, },
pubKeyHash: typeforce.UInt8, pubKeyHash: typeforce.UInt8,
scriptHash: typeforce.UInt8, scriptHash: typeforce.UInt8,
wif: typeforce.UInt8 wif: typeforce.UInt8,
}) });
export const Buffer256bit = typeforce.BufferN(32) export const Buffer256bit = typeforce.BufferN(32);
export const Hash160bit = typeforce.BufferN(20) export const Hash160bit = typeforce.BufferN(20);
export const Hash256bit = typeforce.BufferN(32) export const Hash256bit = typeforce.BufferN(32);
export const Number = typeforce.Number export const Number = typeforce.Number;
export const Array = typeforce.Array export const Array = typeforce.Array;
export const Boolean = typeforce.Boolean export const Boolean = typeforce.Boolean;
export const String = typeforce.String export const String = typeforce.String;
export const Buffer = typeforce.Buffer export const Buffer = typeforce.Buffer;
export const Hex = typeforce.Hex export const Hex = typeforce.Hex;
export const maybe = typeforce.maybe export const maybe = typeforce.maybe;
export const tuple = typeforce.tuple export const tuple = typeforce.tuple;
export const UInt8 = typeforce.UInt8 export const UInt8 = typeforce.UInt8;
export const UInt32 = typeforce.UInt32 export const UInt32 = typeforce.UInt32;
export const Function = typeforce.Function export const Function = typeforce.Function;
export const BufferN = typeforce.BufferN export const BufferN = typeforce.BufferN;
export const Null = typeforce.Null export const Null = typeforce.Null;
export const oneOf = typeforce.oneOf export const oneOf = typeforce.oneOf;

2
types/index.d.ts

@ -5,7 +5,7 @@ import * as crypto from './crypto';
import * as networks from './networks'; import * as networks from './networks';
import * as payments from './payments'; import * as payments from './payments';
import * as script from './script'; import * as script from './script';
export { ECPair, address, bip32, crypto, networks, payments, script, }; export { ECPair, address, bip32, crypto, networks, payments, script };
export { Block } from './block'; export { Block } from './block';
export { Transaction } from './transaction'; export { Transaction } from './transaction';
export { TransactionBuilder } from './transaction_builder'; export { TransactionBuilder } from './transaction_builder';

Loading…
Cancel
Save