Browse Source

Merge pull request #1319 from bitcoinjs/typeScript

Initial TypeScript implementation
fixTypes
Jonathan Underwood 6 years ago
committed by GitHub
parent
commit
22141b5a10
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      .prettierignore
  2. 4
      .prettierrc.json
  3. 9
      .travis.yml
  4. 12
      CHANGELOG.md
  5. 16
      CONTRIBUTING.md
  6. 31
      README.md
  7. 4410
      package-lock.json
  8. 40
      package.json
  9. 181
      src/address.js
  10. 393
      src/block.js
  11. 61
      src/bufferutils.js
  12. 137
      src/classify.js
  13. 55
      src/crypto.js
  14. 192
      src/ecpair.js
  15. 40
      src/index.js
  16. 37
      src/networks.js
  17. 99
      src/payments/embed.js
  18. 26
      src/payments/index.js
  19. 54
      src/payments/lazy.js
  20. 267
      src/payments/p2ms.js
  21. 147
      src/payments/p2pk.js
  22. 271
      src/payments/p2pkh.js
  23. 362
      src/payments/p2sh.js
  24. 263
      src/payments/p2wpkh.js
  25. 343
      src/payments/p2wsh.js
  26. 368
      src/script.js
  27. 124
      src/script_number.js
  28. 107
      src/script_signature.js
  29. 10
      src/templates/multisig/index.js
  30. 40
      src/templates/multisig/input.js
  31. 61
      src/templates/multisig/output.js
  32. 25
      src/templates/nulldata.js
  33. 10
      src/templates/pubkey/index.js
  34. 24
      src/templates/pubkey/input.js
  35. 26
      src/templates/pubkey/output.js
  36. 10
      src/templates/pubkeyhash/index.js
  37. 24
      src/templates/pubkeyhash/input.js
  38. 32
      src/templates/pubkeyhash/output.js
  39. 10
      src/templates/scripthash/index.js
  40. 88
      src/templates/scripthash/input.js
  41. 28
      src/templates/scripthash/output.js
  42. 7
      src/templates/witnesscommitment/index.js
  43. 66
      src/templates/witnesscommitment/output.js
  44. 10
      src/templates/witnesspubkeyhash/index.js
  45. 29
      src/templates/witnesspubkeyhash/input.js
  46. 26
      src/templates/witnesspubkeyhash/output.js
  47. 10
      src/templates/witnessscripthash/index.js
  48. 71
      src/templates/witnessscripthash/input.js
  49. 24
      src/templates/witnessscripthash/output.js
  50. 924
      src/transaction.js
  51. 1461
      src/transaction_builder.js
  52. 89
      src/types.js
  53. 15
      test/block.js
  54. 6
      test/ecpair.js
  55. 15
      test/fixtures/block.json
  56. 2
      test/fixtures/transaction_builder.json
  57. 8
      test/payments.js
  58. 2
      test/transaction.js
  59. 30
      test/transaction_builder.js
  60. 119
      ts_src/address.ts
  61. 285
      ts_src/block.ts
  62. 44
      ts_src/bufferutils.ts
  63. 71
      ts_src/classify.ts
  64. 33
      ts_src/crypto.ts
  65. 131
      ts_src/ecpair.ts
  66. 20
      ts_src/index.ts
  67. 49
      ts_src/networks.ts
  68. 58
      ts_src/payments/embed.ts
  69. 41
      ts_src/payments/index.ts
  70. 28
      ts_src/payments/lazy.ts
  71. 158
      ts_src/payments/p2ms.ts
  72. 80
      ts_src/payments/p2pk.ts
  73. 147
      ts_src/payments/p2pkh.ts
  74. 213
      ts_src/payments/p2sh.ts
  75. 142
      ts_src/payments/p2wpkh.ts
  76. 198
      ts_src/payments/p2wsh.ts
  77. 216
      ts_src/script.ts
  78. 71
      ts_src/script_number.ts
  79. 64
      ts_src/script_signature.ts
  80. 4
      ts_src/templates/multisig/index.ts
  81. 31
      ts_src/templates/multisig/input.ts
  82. 33
      ts_src/templates/multisig/output.ts
  83. 16
      ts_src/templates/nulldata.ts
  84. 4
      ts_src/templates/pubkey/index.ts
  85. 16
      ts_src/templates/pubkey/input.ts
  86. 18
      ts_src/templates/pubkey/output.ts
  87. 4
      ts_src/templates/pubkeyhash/index.ts
  88. 17
      ts_src/templates/pubkeyhash/input.ts
  89. 20
      ts_src/templates/pubkeyhash/output.ts
  90. 4
      ts_src/templates/scripthash/index.ts
  91. 61
      ts_src/templates/scripthash/input.ts
  92. 18
      ts_src/templates/scripthash/output.ts
  93. 3
      ts_src/templates/witnesscommitment/index.ts
  94. 40
      ts_src/templates/witnesscommitment/output.ts
  95. 4
      ts_src/templates/witnesspubkeyhash/index.ts
  96. 21
      ts_src/templates/witnesspubkeyhash/input.ts
  97. 13
      ts_src/templates/witnesspubkeyhash/output.ts
  98. 4
      ts_src/templates/witnessscripthash/index.ts
  99. 47
      ts_src/templates/witnessscripthash/input.ts
  100. 13
      ts_src/templates/witnessscripthash/output.ts

0
.prettierignore

4
.prettierrc.json

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

9
.travis.yml

@ -1,13 +1,16 @@
sudo: false
language: node_js
node_js:
- "8"
- "lts/*"
- "9"
- "10"
matrix:
include:
- node_js: "lts/*"
env: TEST_SUITE=standard
env: TEST_SUITE=format:ci
- node_js: "lts/*"
env: TEST_SUITE=gitdiff:ci
- node_js: "lts/*"
env: TEST_SUITE=lint
- node_js: "lts/*"
env: TEST_SUITE=coverage
env:

12
CHANGELOG.md

@ -1,3 +1,15 @@
# 5.0.0
__added__
- TypeScript support (#1319)
- `Block.prototype.checkTxRoots` will check the merkleRoot and witnessCommit if it exists against the transactions array. (e52abec) (0426c66)
__changed__
- `Transaction.prototype.getHash` now has `forWitness?: boolean` which when true returns the hash for wtxid (a652d04)
- `Block.calculateMerkleRoot` now has `forWitness?: boolean` which when true returns the witness commit (a652d04)
__removed__
- `Block.prototype.checkMerkleRoot` was removed, please use `checkTxRoots` (0426c66)
# 4.0.3
__fixed__
- Fixed `TransactionBuilder` to require that the Transaction has outputs before signing (#1151)

16
CONTRIBUTING.md

@ -49,6 +49,22 @@ The length of time required for peer review is unpredictable and will vary from
Refer to the [Git manual](https://git-scm.com/doc) for any information about `git`.
## Regarding TypeScript
This library is written in TypeScript with tslint, prettier, and the tsc transpiler. These tools will help during testing to notice improper logic before committing and sending a pull request.
Some rules regarding TypeScript:
* Modify the typescript source code in an IDE that will give you warnings for transpile/lint errors.
* Once you are done with the modifications, run `npm run format` then `npm test`
* Running the tests will transpile the ts files into js and d.ts files.
* Use `git diff` or other tools to verify that the ts and js are changing the same parts.
* Commit all changes to ts, js, and d.ts files.
* Add tests where necessary.
* Submit your pull request.
Using TypeScript is for preventing bugs while writing code, as well as automatically generating type definitions. However, the JS file diffs must be verified, and any unverified JS will not be published to npm.
## We adhere to Bitcoin-Core policy
Bitcoin script payment/script templates are based on community consensus, but typically adhere to bitcoin-core node policy by default.

31
README.md

@ -2,9 +2,9 @@
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib)
[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib)
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
A javascript Bitcoin library for node.js and browsers.
A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify.
Released under the terms of the [MIT LICENSE](LICENSE).
@ -23,7 +23,7 @@ Mistakes and bugs happen, but with your help in resolving and reporting [issues]
- Easy to audit and verify,
- Tested, with test coverage >95%,
- Advanced and feature rich,
- Standardized, using [standard](https://github.com/standard/standard) and Node `Buffer`'s throughout, and
- Standardized, using [prettier](https://github.com/prettier/prettier) and Node `Buffer`'s throughout, and
- Friendly, with a strong and helpful community, ready to answer questions.
@ -78,30 +78,7 @@ If you're familiar with how to use browserify, ignore this and carry on, otherwi
**WARNING**: iOS devices have [problems](https://github.com/feross/buffer/issues/136), use atleast [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater, and enforce the test suites (for `Buffer`, and any other dependency) pass before use.
### Typescript or VSCode users
Type declarations for Typescript [are available](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/0897921174860ec3d5318992d2323b3ae8100a68/types/bitcoinjs-lib) for version `^3.0.0` of the library.
``` bash
npm install @types/bitcoinjs-lib
```
For VSCode (and other editors), it is advised to install the type declarations, as Intellisense uses that information to help you code (autocompletion, static analysis).
**WARNING**: These Typescript definitions are not maintained by the maintainers of this repository, and are instead maintained at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped).
Please report any issues or problems there.
### Flow
[Flow-type](https://flowtype.org/) definitions for are available in the [flow-*typed* repository](https://github.com/flowtype/flow-typed/tree/master/definitions/npm/bitcoinjs-lib_v2.x.x) for version `^2.0.0` of the library.
You can [download them directly](https://github.com/flowtype/flow-typed/blob/master/definitions/npm/bitcoinjs-lib_v2.x.x/flow_v0.17.x-/bitcoinjs-lib_v2.x.x.js), or using the flow-typed CLI:
``` bash
npm install -g flow-typed
flow-typed install -f 0.27 bitcoinjs-lib@2.2.0
```
**WARNING**: These flow-typed definitions are not maintained by the maintainers of this repository.
Type declarations for Typescript are included in this library. Normal installation should include all the needed type information.
## Examples
The below examples are implemented as integration tests, they should be very easy to understand.

4410
package-lock.json

File diff suppressed because it is too large

40
package.json

@ -1,8 +1,9 @@
{
"name": "bitcoinjs-lib",
"version": "4.0.3",
"version": "5.0.0",
"description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js",
"types": "./types/index.d.ts",
"engines": {
"node": ">=8.0.0"
},
@ -14,24 +15,36 @@
"bitcoinjs"
],
"scripts": {
"coverage-report": "nyc report --reporter=lcov",
"coverage-html": "nyc report --reporter=html",
"coverage": "nyc --check-coverage --branches 90 --functions 90 mocha",
"integration": "mocha --timeout 50000 test/integration/",
"standard": "standard",
"test": "npm run standard && npm run coverage",
"unit": "mocha"
"build": "tsc -p ./tsconfig.json",
"coverage-report": "npm run build && npm run nobuild:coverage-report",
"coverage-html": "npm run build && npm run nobuild:coverage-html",
"coverage": "npm run build && npm run nobuild:coverage",
"format": "npm run prettier -- --write",
"format:ci": "npm run prettier -- --check",
"gitdiff:ci": "npm run build && git diff --exit-code",
"integration": "npm run build && npm run nobuild:integration",
"lint": "tslint -p tsconfig.json -c tslint.json",
"nobuild:coverage-report": "nyc report --reporter=lcov",
"nobuild:coverage-html": "nyc report --reporter=html",
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha",
"nobuild:integration": "mocha --timeout 50000 test/integration/",
"nobuild:unit": "mocha",
"prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore",
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage",
"unit": "npm run build && npm run nobuild:unit"
},
"repository": {
"type": "git",
"url": "https://github.com/bitcoinjs/bitcoinjs-lib.git"
},
"files": [
"src"
"src",
"types"
],
"dependencies": {
"@types/node": "10.12.18",
"bech32": "^1.1.2",
"bip32": "^1.0.0",
"bip32": "^2.0.0",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.4.0",
"bs58check": "^2.0.0",
@ -40,7 +53,6 @@
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1",
"safe-buffer": "^5.1.1",
"tiny-secp256k1": "^1.0.0",
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.0.4",
@ -56,9 +68,11 @@
"hoodwink": "^2.0.0",
"minimaldata": "^1.0.2",
"mocha": "^5.2.0",
"nyc": "^11.8.0",
"nyc": "^13.3.0",
"prettier": "1.16.4",
"proxyquire": "^2.0.1",
"standard": "^11.0.1"
"tslint": "5.13.1",
"typescript": "3.2.2"
},
"license": "MIT"
}

181
src/address.js

@ -1,97 +1,100 @@
const Buffer = require('safe-buffer').Buffer
const bech32 = require('bech32')
const bs58check = require('bs58check')
const bscript = require('./script')
const networks = require('./networks')
const typeforce = require('typeforce')
const types = require('./types')
const payments = require('./payments')
function fromBase58Check (address) {
const payload = bs58check.decode(address)
// 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 long')
const version = payload.readUInt8(0)
const hash = payload.slice(1)
return { version: version, hash: hash }
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const networks = require("./networks");
const payments = require("./payments");
const bscript = require("./script");
const types = require("./types");
const bech32 = require('bech32');
const bs58check = require('bs58check');
const typeforce = require('typeforce');
function fromBase58Check(address) {
const payload = bs58check.decode(address);
// 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 long');
const version = payload.readUInt8(0);
const hash = payload.slice(1);
return { version, hash };
}
function fromBech32 (address) {
const result = bech32.decode(address)
const data = bech32.fromWords(result.words.slice(1))
return {
version: result.words[0],
prefix: result.prefix,
data: Buffer.from(data)
}
exports.fromBase58Check = fromBase58Check;
function fromBech32(address) {
const result = bech32.decode(address);
const data = bech32.fromWords(result.words.slice(1));
return {
version: result.words[0],
prefix: result.prefix,
data: Buffer.from(data),
};
}
function toBase58Check (hash, version) {
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
const payload = Buffer.allocUnsafe(21)
payload.writeUInt8(version, 0)
hash.copy(payload, 1)
return bs58check.encode(payload)
exports.fromBech32 = fromBech32;
function toBase58Check(hash, version) {
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0);
hash.copy(payload, 1);
return bs58check.encode(payload);
}
function toBech32 (data, version, prefix) {
const words = bech32.toWords(data)
words.unshift(version)
return bech32.encode(prefix, words)
exports.toBase58Check = toBase58Check;
function toBech32(data, version, prefix) {
const words = bech32.toWords(data);
words.unshift(version);
return bech32.encode(prefix, words);
}
function fromOutputScript (output, network) {
network = network || networks.bitcoin
try { return payments.p2pkh({ output, network }).address } catch (e) {}
try { return payments.p2sh({ output, network }).address } catch (e) {}
try { return payments.p2wpkh({ output, network }).address } catch (e) {}
try { return payments.p2wsh({ output, network }).address } catch (e) {}
throw new Error(bscript.toASM(output) + ' has no matching Address')
}
function toOutputScript (address, network) {
network = network || networks.bitcoin
let decode
try {
decode = fromBase58Check(address)
} catch (e) {}
if (decode) {
if (decode.version === network.pubKeyHash) return payments.p2pkh({ hash: decode.hash }).output
if (decode.version === network.scriptHash) return payments.p2sh({ hash: decode.hash }).output
} else {
exports.toBech32 = toBech32;
function fromOutputScript(output, network) {
// TODO: Network
network = network || networks.bitcoin;
try {
return payments.p2pkh({ output, network }).address;
}
catch (e) { }
try {
decode = fromBech32(address)
} catch (e) {}
if (decode) {
if (decode.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix')
if (decode.version === 0) {
if (decode.data.length === 20) return payments.p2wpkh({ hash: decode.data }).output
if (decode.data.length === 32) return payments.p2wsh({ hash: decode.data }).output
}
return payments.p2sh({ output, network }).address;
}
}
throw new Error(address + ' has no matching Script')
catch (e) { }
try {
return payments.p2wpkh({ output, network }).address;
}
catch (e) { }
try {
return payments.p2wsh({ output, network }).address;
}
catch (e) { }
throw new Error(bscript.toASM(output) + ' has no matching Address');
}
module.exports = {
fromBase58Check: fromBase58Check,
fromBech32: fromBech32,
fromOutputScript: fromOutputScript,
toBase58Check: toBase58Check,
toBech32: toBech32,
toOutputScript: toOutputScript
exports.fromOutputScript = fromOutputScript;
function toOutputScript(address, network) {
network = network || networks.bitcoin;
let decodeBase58;
let decodeBech32;
try {
decodeBase58 = fromBase58Check(address);
}
catch (e) { }
if (decodeBase58) {
if (decodeBase58.version === network.pubKeyHash)
return payments.p2pkh({ hash: decodeBase58.hash }).output;
if (decodeBase58.version === network.scriptHash)
return payments.p2sh({ hash: decodeBase58.hash }).output;
}
else {
try {
decodeBech32 = fromBech32(address);
}
catch (e) { }
if (decodeBech32) {
if (decodeBech32.prefix !== network.bech32)
throw new Error(address + ' has an invalid prefix');
if (decodeBech32.version === 0) {
if (decodeBech32.data.length === 20)
return payments.p2wpkh({ hash: decodeBech32.data }).output;
if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output;
}
}
}
throw new Error(address + ' has no matching Script');
}
exports.toOutputScript = toOutputScript;

393
src/block.js

@ -1,177 +1,222 @@
const Buffer = require('safe-buffer').Buffer
const bcrypto = require('./crypto')
const fastMerkleRoot = require('merkle-lib/fastRoot')
const typeforce = require('typeforce')
const types = require('./types')
const varuint = require('varuint-bitcoin')
const Transaction = require('./transaction')
function Block () {
this.version = 1
this.prevHash = null
this.merkleRoot = null
this.timestamp = 0
this.bits = 0
this.nonce = 0
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bufferutils_1 = require("./bufferutils");
const bcrypto = require("./crypto");
const transaction_1 = require("./transaction");
const types = require("./types");
const fastMerkleRoot = require('merkle-lib/fastRoot');
const typeforce = require('typeforce');
const varuint = require('varuint-bitcoin');
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions');
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block');
class Block {
constructor() {
this.version = 1;
this.prevHash = undefined;
this.merkleRoot = undefined;
this.timestamp = 0;
this.witnessCommit = undefined;
this.bits = 0;
this.nonce = 0;
this.transactions = undefined;
}
static fromBuffer(buffer) {
if (buffer.length < 80)
throw new Error('Buffer too small (< 80 bytes)');
let offset = 0;
const readSlice = (n) => {
offset += n;
return buffer.slice(offset - n, offset);
};
const readUInt32 = () => {
const i = buffer.readUInt32LE(offset);
offset += 4;
return i;
};
const readInt32 = () => {
const i = buffer.readInt32LE(offset);
offset += 4;
return i;
};
const block = new Block();
block.version = readInt32();
block.prevHash = readSlice(32);
block.merkleRoot = readSlice(32);
block.timestamp = readUInt32();
block.bits = readUInt32();
block.nonce = readUInt32();
if (buffer.length === 80)
return block;
const readVarInt = () => {
const vi = varuint.decode(buffer, offset);
offset += varuint.decode.bytes;
return vi;
};
const readTransaction = () => {
const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true);
offset += tx.byteLength();
return tx;
};
const nTransactions = readVarInt();
block.transactions = [];
for (let i = 0; i < nTransactions; ++i) {
const tx = readTransaction();
block.transactions.push(tx);
}
const witnessCommit = block.getWitnessCommit();
// This Block contains a witness commit
if (witnessCommit)
block.witnessCommit = witnessCommit;
return block;
}
static fromHex(hex) {
return Block.fromBuffer(Buffer.from(hex, 'hex'));
}
static calculateTarget(bits) {
const exponent = ((bits & 0xff000000) >> 24) - 3;
const mantissa = bits & 0x007fffff;
const target = Buffer.alloc(32, 0);
target.writeUIntBE(mantissa, 29 - exponent, 3);
return target;
}
static calculateMerkleRoot(transactions, forWitness) {
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 rootHash = fastMerkleRoot(hashes, bcrypto.hash256);
return forWitness
? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]))
: rootHash;
}
getWitnessCommit() {
if (!txesHaveWitnessCommit(this.transactions))
return null;
// The merkle root for the witness data is in an OP_RETURN output.
// There is no rule for the index of the output, so use filter to find it.
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
// If multiple commits are found, the output with highest index is assumed.
const witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38));
if (witnessCommits.length === 0)
return null;
// Use the commit with the highest output (should only be one though)
const result = witnessCommits[witnessCommits.length - 1];
if (!(result instanceof Buffer && result.length === 32))
return null;
return result;
}
hasWitnessCommit() {
if (this.witnessCommit instanceof Buffer &&
this.witnessCommit.length === 32)
return true;
if (this.getWitnessCommit() !== null)
return true;
return false;
}
hasWitness() {
return anyTxHasWitness(this.transactions);
}
byteLength(headersOnly) {
if (headersOnly || !this.transactions)
return 80;
return (80 +
varuint.encodingLength(this.transactions.length) +
this.transactions.reduce((a, x) => a + x.byteLength(), 0));
}
getHash() {
return bcrypto.hash256(this.toBuffer(true));
}
getId() {
return bufferutils_1.reverseBuffer(this.getHash()).toString('hex');
}
getUTCDate() {
const date = new Date(0); // epoch
date.setUTCSeconds(this.timestamp);
return date;
}
// TODO: buffer, offset compatibility
toBuffer(headersOnly) {
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
let offset = 0;
const writeSlice = (slice) => {
slice.copy(buffer, offset);
offset += slice.length;
};
const writeInt32 = (i) => {
buffer.writeInt32LE(i, offset);
offset += 4;
};
const writeUInt32 = (i) => {
buffer.writeUInt32LE(i, offset);
offset += 4;
};
writeInt32(this.version);
writeSlice(this.prevHash);
writeSlice(this.merkleRoot);
writeUInt32(this.timestamp);
writeUInt32(this.bits);
writeUInt32(this.nonce);
if (headersOnly || !this.transactions)
return buffer;
varuint.encode(this.transactions.length, buffer, offset);
offset += varuint.encode.bytes;
this.transactions.forEach(tx => {
const txSize = tx.byteLength(); // TODO: extract from toBuffer?
tx.toBuffer(buffer, offset);
offset += txSize;
});
return buffer;
}
toHex(headersOnly) {
return this.toBuffer(headersOnly).toString('hex');
}
checkTxRoots() {
// If the Block has segwit transactions but no witness commit,
// there's no way it can be valid, so fail the check.
const hasWitnessCommit = this.hasWitnessCommit();
if (!hasWitnessCommit && this.hasWitness())
return false;
return (this.__checkMerkleRoot() &&
(hasWitnessCommit ? this.__checkWitnessCommit() : true));
}
checkProofOfWork() {
const hash = bufferutils_1.reverseBuffer(this.getHash());
const target = Block.calculateTarget(this.bits);
return hash.compare(target) <= 0;
}
__checkMerkleRoot() {
if (!this.transactions)
throw errorMerkleNoTxes;
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
return this.merkleRoot.compare(actualMerkleRoot) === 0;
}
__checkWitnessCommit() {
if (!this.transactions)
throw errorMerkleNoTxes;
if (!this.hasWitnessCommit())
throw errorWitnessNotSegwit;
const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true);
return this.witnessCommit.compare(actualWitnessCommit) === 0;
}
}
Block.fromBuffer = function (buffer) {
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)')
let offset = 0
function readSlice (n) {
offset += n
return buffer.slice(offset - n, offset)
}
function readUInt32 () {
const i = buffer.readUInt32LE(offset)
offset += 4
return i
}
function readInt32 () {
const i = buffer.readInt32LE(offset)
offset += 4
return i
}
const block = new Block()
block.version = readInt32()
block.prevHash = readSlice(32)
block.merkleRoot = readSlice(32)
block.timestamp = readUInt32()
block.bits = readUInt32()
block.nonce = readUInt32()
if (buffer.length === 80) return block
function readVarInt () {
const vi = varuint.decode(buffer, offset)
offset += varuint.decode.bytes
return vi
}
function readTransaction () {
const tx = Transaction.fromBuffer(buffer.slice(offset), true)
offset += tx.byteLength()
return tx
}
const nTransactions = readVarInt()
block.transactions = []
for (var i = 0; i < nTransactions; ++i) {
const tx = readTransaction()
block.transactions.push(tx)
}
return block
exports.Block = Block;
function txesHaveWitnessCommit(transactions) {
return (transactions instanceof Array &&
transactions[0] &&
transactions[0].ins &&
transactions[0].ins instanceof Array &&
transactions[0].ins[0] &&
transactions[0].ins[0].witness &&
transactions[0].ins[0].witness instanceof Array &&
transactions[0].ins[0].witness.length > 0);
}
Block.prototype.byteLength = function (headersOnly) {
if (headersOnly || !this.transactions) return 80
return 80 + varuint.encodingLength(this.transactions.length) + this.transactions.reduce(function (a, x) {
return a + x.byteLength()
}, 0)
function anyTxHasWitness(transactions) {
return (transactions instanceof Array &&
transactions.some(tx => typeof tx === 'object' &&
tx.ins instanceof Array &&
tx.ins.some(input => typeof input === 'object' &&
input.witness instanceof Array &&
input.witness.length > 0)));
}
Block.fromHex = function (hex) {
return Block.fromBuffer(Buffer.from(hex, 'hex'))
}
Block.prototype.getHash = function () {
return bcrypto.hash256(this.toBuffer(true))
}
Block.prototype.getId = function () {
return this.getHash().reverse().toString('hex')
}
Block.prototype.getUTCDate = function () {
const date = new Date(0) // epoch
date.setUTCSeconds(this.timestamp)
return date
}
// TODO: buffer, offset compatibility
Block.prototype.toBuffer = function (headersOnly) {
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly))
let offset = 0
function writeSlice (slice) {
slice.copy(buffer, offset)
offset += slice.length
}
function writeInt32 (i) {
buffer.writeInt32LE(i, offset)
offset += 4
}
function writeUInt32 (i) {
buffer.writeUInt32LE(i, offset)
offset += 4
}
writeInt32(this.version)
writeSlice(this.prevHash)
writeSlice(this.merkleRoot)
writeUInt32(this.timestamp)
writeUInt32(this.bits)
writeUInt32(this.nonce)
if (headersOnly || !this.transactions) return buffer
varuint.encode(this.transactions.length, buffer, offset)
offset += varuint.encode.bytes
this.transactions.forEach(function (tx) {
const txSize = tx.byteLength() // TODO: extract from toBuffer?
tx.toBuffer(buffer, offset)
offset += txSize
})
return buffer
}
Block.prototype.toHex = function (headersOnly) {
return this.toBuffer(headersOnly).toString('hex')
}
Block.calculateTarget = function (bits) {
const exponent = ((bits & 0xff000000) >> 24) - 3
const mantissa = bits & 0x007fffff
const target = Buffer.alloc(32, 0)
target.writeUIntBE(mantissa, 29 - exponent, 3)
return target
}
Block.calculateMerkleRoot = function (transactions) {
typeforce([{ getHash: types.Function }], transactions)
if (transactions.length === 0) throw TypeError('Cannot compute merkle root for zero transactions')
const hashes = transactions.map(function (transaction) {
return transaction.getHash()
})
return fastMerkleRoot(hashes, bcrypto.hash256)
}
Block.prototype.checkMerkleRoot = function () {
if (!this.transactions) return false
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions)
return this.merkleRoot.compare(actualMerkleRoot) === 0
}
Block.prototype.checkProofOfWork = function () {
const hash = this.getHash().reverse()
const target = Block.calculateTarget(this.bits)
return hash.compare(target) <= 0
}
module.exports = Block

61
src/bufferutils.js

@ -1,29 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// https://github.com/feross/buffer/blob/master/index.js#L1127
function verifuint (value, max) {
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number')
if (value < 0) 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')
function verifuint(value, max) {
if (typeof value !== 'number')
throw new Error('cannot write a non-number as a number');
if (value < 0)
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');
}
function readUInt64LE (buffer, offset) {
const a = buffer.readUInt32LE(offset)
let b = buffer.readUInt32LE(offset + 4)
b *= 0x100000000
verifuint(b + a, 0x001fffffffffffff)
return b + a
function readUInt64LE(buffer, offset) {
const a = buffer.readUInt32LE(offset);
let b = buffer.readUInt32LE(offset + 4);
b *= 0x100000000;
verifuint(b + a, 0x001fffffffffffff);
return b + a;
}
function writeUInt64LE (buffer, value, offset) {
verifuint(value, 0x001fffffffffffff)
buffer.writeInt32LE(value & -1, offset)
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4)
return offset + 8
exports.readUInt64LE = readUInt64LE;
function writeUInt64LE(buffer, value, offset) {
verifuint(value, 0x001fffffffffffff);
buffer.writeInt32LE(value & -1, offset);
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
return offset + 8;
}
module.exports = {
readUInt64LE: readUInt64LE,
writeUInt64LE: writeUInt64LE
exports.writeUInt64LE = writeUInt64LE;
function reverseBuffer(buffer) {
if (buffer.length < 1)
return buffer;
let j = buffer.length - 1;
let tmp = 0;
for (let i = 0; i < buffer.length / 2; i++) {
tmp = buffer[i];
buffer[i] = buffer[j];
buffer[j] = tmp;
j--;
}
return buffer;
}
exports.reverseBuffer = reverseBuffer;

137
src/classify.js

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

55
src/crypto.js

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

192
src/ecpair.js

@ -1,106 +1,98 @@
const ecc = require('tiny-secp256k1')
const randomBytes = require('randombytes')
const typeforce = require('typeforce')
const types = require('./types')
const wif = require('wif')
const NETWORKS = require('./networks')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const NETWORKS = require("./networks");
const types = require("./types");
const ecc = require('tiny-secp256k1');
const randomBytes = require('randombytes');
const typeforce = require('typeforce');
const wif = require('wif');
const isOptions = typeforce.maybe(typeforce.compile({
compressed: types.maybe(types.Boolean),
network: types.maybe(types.Network)
}))
function ECPair (d, Q, options) {
options = options || {}
this.compressed = options.compressed === undefined ? true : options.compressed
this.network = options.network || NETWORKS.bitcoin
this.__d = d || null
this.__Q = null
if (Q) this.__Q = ecc.pointCompress(Q, this.compressed)
compressed: types.maybe(types.Boolean),
network: types.maybe(types.Network),
}));
class ECPair {
constructor(__D, __Q, options) {
this.__D = __D;
this.__Q = __Q;
if (options === undefined)
options = {};
this.compressed =
options.compressed === undefined ? true : options.compressed;
this.network = options.network || NETWORKS.bitcoin;
if (__Q !== undefined)
this.__Q = ecc.pointCompress(__Q, this.compressed);
}
get privateKey() {
return this.__D;
}
get publicKey() {
if (!this.__Q)
this.__Q = ecc.pointFromScalar(this.__D, this.compressed);
return this.__Q;
}
toWIF() {
if (!this.__D)
throw new Error('Missing private key');
return wif.encode(this.network.wif, this.__D, this.compressed);
}
sign(hash) {
if (!this.__D)
throw new Error('Missing private key');
return ecc.sign(hash, this.__D);
}
verify(hash, signature) {
return ecc.verify(hash, this.publicKey, signature);
}
}
Object.defineProperty(ECPair.prototype, 'privateKey', {
enumerable: false,
get: function () { return this.__d }
})
Object.defineProperty(ECPair.prototype, 'publicKey', { get: function () {
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed)
return this.__Q
}})
ECPair.prototype.toWIF = function () {
if (!this.__d) throw new Error('Missing private key')
return wif.encode(this.network.wif, this.__d, this.compressed)
function fromPrivateKey(buffer, options) {
typeforce(types.Buffer256bit, buffer);
if (!ecc.isPrivate(buffer))
throw new TypeError('Private key not in range [1, n)');
typeforce(isOptions, options);
return new ECPair(buffer, undefined, options);
}
ECPair.prototype.sign = function (hash) {
if (!this.__d) throw new Error('Missing private key')
return ecc.sign(hash, this.__d)
exports.fromPrivateKey = fromPrivateKey;
function fromPublicKey(buffer, options) {
typeforce(ecc.isPoint, buffer);
typeforce(isOptions, options);
return new ECPair(undefined, buffer, options);
}
ECPair.prototype.verify = function (hash, signature) {
return ecc.verify(hash, this.publicKey, signature)
exports.fromPublicKey = fromPublicKey;
function fromWIF(wifString, network) {
const decoded = wif.decode(wifString);
const version = decoded.version;
// list of networks?
if (types.Array(network)) {
network = network
.filter((x) => {
return version === x.wif;
})
.pop();
if (!network)
throw new Error('Unknown network version');
// otherwise, assume a network object (or default to bitcoin)
}
else {
network = network || NETWORKS.bitcoin;
if (version !== network.wif)
throw new Error('Invalid network version');
}
return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed,
network: network,
});
}
function fromPrivateKey (buffer, options) {
typeforce(types.Buffer256bit, buffer)
if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)')
typeforce(isOptions, options)
return new ECPair(buffer, null, options)
}
function fromPublicKey (buffer, options) {
typeforce(ecc.isPoint, buffer)
typeforce(isOptions, options)
return new ECPair(null, buffer, options)
}
function fromWIF (string, network) {
const decoded = wif.decode(string)
const version = decoded.version
// list of networks?
if (types.Array(network)) {
network = network.filter(function (x) {
return version === x.wif
}).pop()
if (!network) throw new Error('Unknown network version')
// otherwise, assume a network object (or default to bitcoin)
} else {
network = network || NETWORKS.bitcoin
if (version !== network.wif) throw new Error('Invalid network version')
}
return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed,
network: network
})
}
function makeRandom (options) {
typeforce(isOptions, options)
options = options || {}
const rng = options.rng || randomBytes
let d
do {
d = rng(32)
typeforce(types.Buffer256bit, d)
} while (!ecc.isPrivate(d))
return fromPrivateKey(d, options)
}
module.exports = {
makeRandom,
fromPrivateKey,
fromPublicKey,
fromWIF
exports.fromWIF = fromWIF;
function makeRandom(options) {
typeforce(isOptions, options);
if (options === undefined)
options = {};
const rng = options.rng || randomBytes;
let d;
do {
d = rng(32);
typeforce(types.Buffer256bit, d);
} while (!ecc.isPrivate(d));
return fromPrivateKey(d, options);
}
exports.makeRandom = makeRandom;

40
src/index.js

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

37
src/networks.js

@ -1,38 +1,35 @@
// https://en.bitcoin.it/wiki/List_of_address_prefixes
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
module.exports = {
bitcoin: {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bitcoin = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80
},
regtest: {
wif: 0x80,
};
exports.regtest = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt',
bip32: {
public: 0x043587cf,
private: 0x04358394
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef
},
testnet: {
wif: 0xef,
};
exports.testnet = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bip32: {
public: 0x043587cf,
private: 0x04358394
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef
}
}
wif: 0xef,
};

99
src/payments/embed.js

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

26
src/payments/index.js

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

54
src/payments/lazy.js

@ -1,30 +1,32 @@
function prop (object, name, f) {
Object.defineProperty(object, name, {
configurable: true,
enumerable: true,
get: function () {
let value = f.call(this)
this[name] = value
return value
},
set: function (value) {
Object.defineProperty(this, name, {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function prop(object, name, f) {
Object.defineProperty(object, name, {
configurable: true,
enumerable: true,
value: value,
writable: true
})
}
})
get() {
const _value = f.call(this);
this[name] = _value;
return _value;
},
set(_value) {
Object.defineProperty(this, name, {
configurable: true,
enumerable: true,
value: _value,
writable: true,
});
},
});
}
function value (f) {
let value
return function () {
if (value !== undefined) return value
value = f()
return value
}
exports.prop = prop;
function value(f) {
let _value;
return () => {
if (_value !== undefined)
return _value;
_value = f();
return _value;
};
}
module.exports = { prop, value }
exports.value = value;

267
src/payments/p2ms.js

@ -1,140 +1,141 @@
const lazy = require('./lazy')
const typef = require('typeforce')
const OPS = require('bitcoin-ops')
const ecc = require('tiny-secp256k1')
const bscript = require('../script')
const BITCOIN_NETWORK = require('../networks').bitcoin
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
function stacksEqual (a, b) {
if (a.length !== b.length) return false
return a.every(function (x, i) {
return x.equals(b[i])
})
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const networks_1 = require("../networks");
const bscript = require("../script");
const lazy = require("./lazy");
const OPS = bscript.OPS;
const typef = require('typeforce');
const ecc = require('tiny-secp256k1');
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
function stacksEqual(a, b) {
if (a.length !== b.length)
return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: OP_0 [signatures ...]
// output: m [pubKeys ...] n OP_CHECKMULTISIG
function p2ms (a, opts) {
if (
!a.input &&
!a.output &&
!(a.pubkeys && a.m !== undefined) &&
!a.signatures
) throw new TypeError('Not enough data')
opts = Object.assign({ validate: true }, opts || {})
function isAcceptableSignature (x) {
return bscript.isCanonicalScriptSignature(x) || (opts.allowIncomplete && (x === OPS.OP_0))
}
typef({
network: typef.maybe(typef.Object),
m: typef.maybe(typef.Number),
n: typef.maybe(typef.Number),
output: typef.maybe(typef.Buffer),
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
input: typef.maybe(typef.Buffer)
}, a)
const network = a.network || BITCOIN_NETWORK
const o = { network }
let chunks
let decoded = false
function decode (output) {
if (decoded) return
decoded = true
chunks = bscript.decompile(output)
o.m = chunks[0] - OP_INT_BASE
o.n = chunks[chunks.length - 2] - OP_INT_BASE
o.pubkeys = chunks.slice(1, -2)
}
lazy.prop(o, 'output', function () {
if (!a.m) return
if (!o.n) return
if (!a.pubkeys) return
return bscript.compile([].concat(
OP_INT_BASE + a.m,
a.pubkeys,
OP_INT_BASE + o.n,
OPS.OP_CHECKMULTISIG
))
})
lazy.prop(o, 'm', function () {
if (!o.output) return
decode(o.output)
return o.m
})
lazy.prop(o, 'n', function () {
if (!o.pubkeys) return
return o.pubkeys.length
})
lazy.prop(o, 'pubkeys', function () {
if (!a.output) return
decode(a.output)
return o.pubkeys
})
lazy.prop(o, 'signatures', function () {
if (!a.input) return
return bscript.decompile(a.input).slice(1)
})
lazy.prop(o, 'input', function () {
if (!a.signatures) return
return bscript.compile([OPS.OP_0].concat(a.signatures))
})
lazy.prop(o, 'witness', function () {
if (!o.input) return
return []
})
// extended validation
if (opts.validate) {
if (a.output) {
decode(a.output)
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 (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid')
if (
o.m <= 0 ||
o.n > 16 ||
o.m > o.n ||
o.n !== chunks.length - 3) throw new TypeError('Output is invalid')
if (!o.pubkeys.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid')
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.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch')
function p2ms(a, opts) {
if (!a.input &&
!a.output &&
!(a.pubkeys && a.m !== undefined) &&
!a.signatures)
throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {});
function isAcceptableSignature(x) {
return (bscript.isCanonicalScriptSignature(x) ||
(opts.allowIncomplete && x === OPS.OP_0) !== undefined);
}
if (a.pubkeys) {
if (a.n !== undefined && a.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')
typef({
network: typef.maybe(typef.Object),
m: typef.maybe(typef.Number),
n: typef.maybe(typef.Number),
output: typef.maybe(typef.Buffer),
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
input: typef.maybe(typef.Buffer),
}, a);
const network = a.network || networks_1.bitcoin;
const o = { network };
let chunks = [];
let decoded = false;
function decode(output) {
if (decoded)
return;
decoded = true;
chunks = bscript.decompile(output);
o.m = chunks[0] - OP_INT_BASE;
o.n = chunks[chunks.length - 2] - OP_INT_BASE;
o.pubkeys = chunks.slice(1, -2);
}
if (a.signatures) {
if (a.signatures.length < o.m) throw new TypeError('Not enough signatures provided')
if (a.signatures.length > o.m) throw new TypeError('Too many signatures provided')
lazy.prop(o, 'output', () => {
if (!a.m)
return;
if (!o.n)
return;
if (!a.pubkeys)
return;
return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG));
});
lazy.prop(o, 'm', () => {
if (!o.output)
return;
decode(o.output);
return o.m;
});
lazy.prop(o, 'n', () => {
if (!o.pubkeys)
return;
return o.pubkeys.length;
});
lazy.prop(o, 'pubkeys', () => {
if (!a.output)
return;
decode(a.output);
return o.pubkeys;
});
lazy.prop(o, 'signatures', () => {
if (!a.input)
return;
return bscript.decompile(a.input).slice(1);
});
lazy.prop(o, 'input', () => {
if (!a.signatures)
return;
return bscript.compile([OPS.OP_0].concat(a.signatures));
});
lazy.prop(o, 'witness', () => {
if (!o.input)
return;
return [];
});
// extended validation
if (opts.validate) {
if (a.output) {
decode(a.output);
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 (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
throw new TypeError('Output is invalid');
if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3)
throw new TypeError('Output is invalid');
if (!o.pubkeys.every(x => ecc.isPoint(x)))
throw new TypeError('Output is invalid');
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.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys))
throw new TypeError('Pubkeys mismatch');
}
if (a.pubkeys) {
if (a.n !== undefined && a.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 (a.signatures) {
if (a.signatures.length < o.m)
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[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 (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');
}
}
if (a.input) {
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 (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);
}
module.exports = p2ms
exports.p2ms = p2ms;

147
src/payments/p2pk.js

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

271
src/payments/p2pkh.js

@ -1,137 +1,142 @@
const lazy = require('./lazy')
const typef = require('typeforce')
const OPS = require('bitcoin-ops')
const ecc = require('tiny-secp256k1')
const bcrypto = require('../crypto')
const bscript = require('../script')
const BITCOIN_NETWORK = require('../networks').bitcoin
const bs58check = require('bs58check')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bcrypto = require("../crypto");
const networks_1 = require("../networks");
const bscript = require("../script");
const lazy = require("./lazy");
const typef = require('typeforce');
const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1');
const bs58check = require('bs58check');
// input: {signature} {pubkey}
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
function p2pkh (a, opts) {
if (
!a.address &&
!a.hash &&
!a.output &&
!a.pubkey &&
!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(25)),
pubkey: typef.maybe(ecc.isPoint),
signature: typef.maybe(bscript.isCanonicalScriptSignature),
input: typef.maybe(typef.Buffer)
}, a)
const _address = lazy.value(function () {
const payload = bs58check.decode(a.address)
const version = payload.readUInt8(0)
const hash = payload.slice(1)
return { version, hash }
})
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
const network = a.network || BITCOIN_NETWORK
const o = { network }
lazy.prop(o, 'address', function () {
if (!o.hash) return
const payload = Buffer.allocUnsafe(21)
payload.writeUInt8(network.pubKeyHash, 0)
o.hash.copy(payload, 1)
return bs58check.encode(payload)
})
lazy.prop(o, 'hash', function () {
if (a.output) return a.output.slice(3, 23)
if (a.address) return _address().hash
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey)
})
lazy.prop(o, 'output', function () {
if (!o.hash) return
return bscript.compile([
OPS.OP_DUP,
OPS.OP_HASH160,
o.hash,
OPS.OP_EQUALVERIFY,
OPS.OP_CHECKSIG
])
})
lazy.prop(o, 'pubkey', function () {
if (!a.input) return
return _chunks()[1]
})
lazy.prop(o, 'signature', function () {
if (!a.input) return
return _chunks()[0]
})
lazy.prop(o, 'input', function () {
if (!a.pubkey) return
if (!a.signature) return
return bscript.compile([a.signature, a.pubkey])
})
lazy.prop(o, 'witness', function () {
if (!o.input) return
return []
})
// extended validation
if (opts.validate) {
let hash
if (a.address) {
if (_address().version !== network.pubKeyHash) throw new TypeError('Invalid version or Network mismatch')
if (_address().hash.length !== 20) throw new TypeError('Invalid address')
hash = _address().hash
function p2pkh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.pubkey && !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(25)),
pubkey: typef.maybe(ecc.isPoint),
signature: typef.maybe(bscript.isCanonicalScriptSignature),
input: typef.maybe(typef.Buffer),
}, a);
const _address = lazy.value(() => {
const payload = bs58check.decode(a.address);
const version = payload.readUInt8(0);
const hash = payload.slice(1);
return { version, hash };
});
const _chunks = lazy.value(() => {
return bscript.decompile(a.input);
});
const network = a.network || networks_1.bitcoin;
const o = { network };
lazy.prop(o, 'address', () => {
if (!o.hash)
return;
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(network.pubKeyHash, 0);
o.hash.copy(payload, 1);
return bs58check.encode(payload);
});
lazy.prop(o, 'hash', () => {
if (a.output)
return a.output.slice(3, 23);
if (a.address)
return _address().hash;
if (a.pubkey || o.pubkey)
return bcrypto.hash160(a.pubkey || o.pubkey);
});
lazy.prop(o, 'output', () => {
if (!o.hash)
return;
return bscript.compile([
OPS.OP_DUP,
OPS.OP_HASH160,
o.hash,
OPS.OP_EQUALVERIFY,
OPS.OP_CHECKSIG,
]);
});
lazy.prop(o, 'pubkey', () => {
if (!a.input)
return;
return _chunks()[1];
});
lazy.prop(o, 'signature', () => {
if (!a.input)
return;
return _chunks()[0];
});
lazy.prop(o, 'input', () => {
if (!a.pubkey)
return;
if (!a.signature)
return;
return bscript.compile([a.signature, a.pubkey]);
});
lazy.prop(o, 'witness', () => {
if (!o.input)
return;
return [];
});
// extended validation
if (opts.validate) {
let hash = Buffer.from([]);
if (a.address) {
if (_address().version !== network.pubKeyHash)
throw new TypeError('Invalid version or Network mismatch');
if (_address().hash.length !== 20)
throw new TypeError('Invalid address');
hash = _address().hash;
}
if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch');
else
hash = a.hash;
}
if (a.output) {
if (a.output.length !== 25 ||
a.output[0] !== OPS.OP_DUP ||
a.output[1] !== OPS.OP_HASH160 ||
a.output[2] !== 0x14 ||
a.output[23] !== OPS.OP_EQUALVERIFY ||
a.output[24] !== OPS.OP_CHECKSIG)
throw new TypeError('Output is invalid');
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) {
const pkh = bcrypto.hash160(a.pubkey);
if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch');
else
hash = pkh;
}
if (a.input) {
const chunks = _chunks();
if (chunks.length !== 2)
throw new TypeError('Input is invalid');
if (!bscript.isCanonicalScriptSignature(chunks[0]))
throw new TypeError('Input has invalid signature');
if (!ecc.isPoint(chunks[1]))
throw new TypeError('Input has invalid pubkey');
if (a.signature && !a.signature.equals(chunks[0]))
throw new TypeError('Signature mismatch');
if (a.pubkey && !a.pubkey.equals(chunks[1]))
throw new TypeError('Pubkey mismatch');
const pkh = bcrypto.hash160(chunks[1]);
if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch');
}
}
if (a.hash) {
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
else hash = a.hash
}
if (a.output) {
if (
a.output.length !== 25 ||
a.output[0] !== OPS.OP_DUP ||
a.output[1] !== OPS.OP_HASH160 ||
a.output[2] !== 0x14 ||
a.output[23] !== OPS.OP_EQUALVERIFY ||
a.output[24] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid')
const hash2 = a.output.slice(3, 23)
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
else hash = hash2
}
if (a.pubkey) {
const pkh = bcrypto.hash160(a.pubkey)
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
else hash = pkh
}
if (a.input) {
const chunks = _chunks()
if (chunks.length !== 2) throw new TypeError('Input is invalid')
if (!bscript.isCanonicalScriptSignature(chunks[0])) throw new TypeError('Input has invalid signature')
if (!ecc.isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey')
if (a.signature && !a.signature.equals(chunks[0])) throw new TypeError('Signature mismatch')
if (a.pubkey && !a.pubkey.equals(chunks[1])) throw new TypeError('Pubkey mismatch')
const pkh = bcrypto.hash160(chunks[1])
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
}
}
return Object.assign(o, a)
return Object.assign(o, a);
}
module.exports = p2pkh
exports.p2pkh = p2pkh;

362
src/payments/p2sh.js

@ -1,193 +1,185 @@
const lazy = require('./lazy')
const typef = require('typeforce')
const OPS = require('bitcoin-ops')
const bcrypto = require('../crypto')
const bscript = require('../script')
const BITCOIN_NETWORK = require('../networks').bitcoin
const bs58check = require('bs58check')
function stacksEqual (a, b) {
if (a.length !== b.length) return false
return a.every(function (x, i) {
return x.equals(b[i])
})
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bcrypto = require("../crypto");
const networks_1 = require("../networks");
const bscript = require("../script");
const lazy = require("./lazy");
const typef = require('typeforce');
const OPS = bscript.OPS;
const bs58check = require('bs58check');
function stacksEqual(a, b) {
if (a.length !== b.length)
return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: [redeemScriptSig ...] {redeemScript}
// witness: <?>
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
function p2sh (a, opts) {
if (
!a.address &&
!a.hash &&
!a.output &&
!a.redeem &&
!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),
output: typef.maybe(typef.Buffer),
input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer))
}),
input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer))
}, a)
let network = a.network
if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK
}
const o = { network }
const _address = lazy.value(function () {
const payload = bs58check.decode(a.address)
const version = payload.readUInt8(0)
const hash = payload.slice(1)
return { version, hash }
})
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
const _redeem = lazy.value(function () {
const chunks = _chunks()
return {
network,
output: chunks[chunks.length - 1],
input: bscript.compile(chunks.slice(0, -1)),
witness: a.witness || []
function p2sh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.redeem && !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),
output: typef.maybe(typef.Buffer),
input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}),
input: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a);
let network = a.network;
if (!network) {
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
}
})
// output dependents
lazy.prop(o, 'address', function () {
if (!o.hash) return
const payload = Buffer.allocUnsafe(21)
payload.writeUInt8(network.scriptHash, 0)
o.hash.copy(payload, 1)
return bs58check.encode(payload)
})
lazy.prop(o, 'hash', function () {
// in order of least effort
if (a.output) return a.output.slice(2, 22)
if (a.address) return _address().hash
if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output)
})
lazy.prop(o, 'output', function () {
if (!o.hash) return
return bscript.compile([
OPS.OP_HASH160,
o.hash,
OPS.OP_EQUAL
])
})
// input dependents
lazy.prop(o, 'redeem', function () {
if (!a.input) return
return _redeem()
})
lazy.prop(o, 'input', function () {
if (!a.redeem || !a.redeem.input || !a.redeem.output) return
return bscript.compile([].concat(
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 []
})
if (opts.validate) {
let hash
if (a.address) {
if (_address().version !== network.scriptHash) throw new TypeError('Invalid version or Network mismatch')
if (_address().hash.length !== 20) throw new TypeError('Invalid address')
hash = _address().hash
}
if (a.hash) {
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
else hash = a.hash
}
if (a.output) {
if (
a.output.length !== 23 ||
a.output[0] !== OPS.OP_HASH160 ||
a.output[1] !== 0x14 ||
a.output[22] !== OPS.OP_EQUAL) throw new TypeError('Output is invalid')
const hash2 = a.output.slice(2, 22)
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
else hash = hash2
}
// inlined to prevent 'no-inner-declarations' failing
const checkRedeem = function (redeem) {
// is the redeem output empty/invalid?
if (redeem.output) {
const decompile = bscript.decompile(redeem.output)
if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short')
// match hash against other sources
const hash2 = bcrypto.hash160(redeem.output)
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
else hash = hash2
}
if (redeem.input) {
const hasInput = redeem.input.length > 0
const hasWitness = redeem.witness && redeem.witness.length > 0
if (!hasInput && !hasWitness) throw new TypeError('Empty input')
if (hasInput && hasWitness) throw new TypeError('Input and witness provided')
if (hasInput) {
const richunks = bscript.decompile(redeem.input)
if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig')
const o = { network };
const _address = lazy.value(() => {
const payload = bs58check.decode(a.address);
const version = payload.readUInt8(0);
const hash = payload.slice(1);
return { version, hash };
});
const _chunks = lazy.value(() => {
return bscript.decompile(a.input);
});
const _redeem = lazy.value(() => {
const chunks = _chunks();
return {
network,
output: chunks[chunks.length - 1],
input: bscript.compile(chunks.slice(0, -1)),
witness: a.witness || [],
};
});
// output dependents
lazy.prop(o, 'address', () => {
if (!o.hash)
return;
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(o.network.scriptHash, 0);
o.hash.copy(payload, 1);
return bs58check.encode(payload);
});
lazy.prop(o, 'hash', () => {
// in order of least effort
if (a.output)
return a.output.slice(2, 22);
if (a.address)
return _address().hash;
if (o.redeem && o.redeem.output)
return bcrypto.hash160(o.redeem.output);
});
lazy.prop(o, 'output', () => {
if (!o.hash)
return;
return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]);
});
// input dependents
lazy.prop(o, 'redeem', () => {
if (!a.input)
return;
return _redeem();
});
lazy.prop(o, 'input', () => {
if (!a.redeem || !a.redeem.input || !a.redeem.output)
return;
return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output));
});
lazy.prop(o, 'witness', () => {
if (o.redeem && o.redeem.witness)
return o.redeem.witness;
if (o.input)
return [];
});
if (opts.validate) {
let hash = Buffer.from([]);
if (a.address) {
if (_address().version !== network.scriptHash)
throw new TypeError('Invalid version or Network mismatch');
if (_address().hash.length !== 20)
throw new TypeError('Invalid address');
hash = _address().hash;
}
if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch');
else
hash = a.hash;
}
if (a.output) {
if (a.output.length !== 23 ||
a.output[0] !== OPS.OP_HASH160 ||
a.output[1] !== 0x14 ||
a.output[22] !== OPS.OP_EQUAL)
throw new TypeError('Output is invalid');
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
const checkRedeem = (redeem) => {
// is the redeem output empty/invalid?
if (redeem.output) {
const decompile = bscript.decompile(redeem.output);
if (!decompile || decompile.length < 1)
throw new TypeError('Redeem.output too short');
// match hash against other sources
const hash2 = bcrypto.hash160(redeem.output);
if (hash.length > 0 && !hash.equals(hash2))
throw new TypeError('Hash mismatch');
else
hash = hash2;
}
if (redeem.input) {
const hasInput = redeem.input.length > 0;
const hasWitness = redeem.witness && redeem.witness.length > 0;
if (!hasInput && !hasWitness)
throw new TypeError('Empty input');
if (hasInput && hasWitness)
throw new TypeError('Input and witness provided');
if (hasInput) {
const richunks = bscript.decompile(redeem.input);
if (!bscript.isPushOnly(richunks))
throw new TypeError('Non push-only scriptSig');
}
}
};
if (a.input) {
const chunks = _chunks();
if (!chunks || chunks.length < 1)
throw new TypeError('Input too short');
if (!Buffer.isBuffer(_redeem().output))
throw new TypeError('Input is invalid');
checkRedeem(_redeem());
}
if (a.redeem) {
if (a.redeem.network && a.redeem.network !== network)
throw new TypeError('Network mismatch');
if (a.input) {
const redeem = _redeem();
if (a.redeem.output && !a.redeem.output.equals(redeem.output))
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);
}
if (a.witness) {
if (a.redeem &&
a.redeem.witness &&
!stacksEqual(a.redeem.witness, a.witness))
throw new TypeError('Witness and redeem.witness mismatch');
}
}
}
if (a.input) {
const chunks = _chunks()
if (!chunks || chunks.length < 1) throw new TypeError('Input too short')
if (!Buffer.isBuffer(_redeem().output)) throw new TypeError('Input is invalid')
checkRedeem(_redeem())
}
if (a.redeem) {
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch')
if (a.input) {
const redeem = _redeem()
if (a.redeem.output && !a.redeem.output.equals(redeem.output)) 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)
}
if (a.witness) {
if (
a.redeem &&
a.redeem.witness &&
!stacksEqual(a.redeem.witness, a.witness)) throw new TypeError('Witness and redeem.witness mismatch')
}
}
return Object.assign(o, a)
return Object.assign(o, a);
}
module.exports = p2sh
exports.p2sh = p2sh;

263
src/payments/p2wpkh.js

@ -1,135 +1,138 @@
const lazy = require('./lazy')
const typef = require('typeforce')
const OPS = require('bitcoin-ops')
const ecc = require('tiny-secp256k1')
const bcrypto = require('../crypto')
const bech32 = require('bech32')
const bscript = require('../script')
const BITCOIN_NETWORK = require('../networks').bitcoin
const EMPTY_BUFFER = Buffer.alloc(0)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bcrypto = require("../crypto");
const networks_1 = require("../networks");
const bscript = require("../script");
const lazy = require("./lazy");
const typef = require('typeforce');
const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1');
const bech32 = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0);
// witness: {signature} {pubKey}
// input: <>
// output: OP_0 {pubKeyHash}
function p2wpkh (a, opts) {
if (
!a.address &&
!a.hash &&
!a.output &&
!a.pubkey &&
!a.witness
) throw new TypeError('Not enough data')
opts = Object.assign({ validate: true }, opts || {})
typef({
address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)),
input: typef.maybe(typef.BufferN(0)),
network: typef.maybe(typef.Object),
output: typef.maybe(typef.BufferN(22)),
pubkey: typef.maybe(ecc.isPoint),
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 version = result.words.shift()
const data = bech32.fromWords(result.words)
return {
version,
prefix: result.prefix,
data: Buffer.from(data)
function p2wpkh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {});
typef({
address: typef.maybe(typef.String),
hash: typef.maybe(typef.BufferN(20)),
input: typef.maybe(typef.BufferN(0)),
network: typef.maybe(typef.Object),
output: typef.maybe(typef.BufferN(22)),
pubkey: typef.maybe(ecc.isPoint),
signature: typef.maybe(bscript.isCanonicalScriptSignature),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a);
const _address = lazy.value(() => {
const result = bech32.decode(a.address);
const version = result.words.shift();
const data = bech32.fromWords(result.words);
return {
version,
prefix: result.prefix,
data: Buffer.from(data),
};
});
const network = a.network || networks_1.bitcoin;
const o = { network };
lazy.prop(o, 'address', () => {
if (!o.hash)
return;
const words = bech32.toWords(o.hash);
words.unshift(0x00);
return bech32.encode(network.bech32, words);
});
lazy.prop(o, 'hash', () => {
if (a.output)
return a.output.slice(2, 22);
if (a.address)
return _address().data;
if (a.pubkey || o.pubkey)
return bcrypto.hash160(a.pubkey || o.pubkey);
});
lazy.prop(o, 'output', () => {
if (!o.hash)
return;
return bscript.compile([OPS.OP_0, o.hash]);
});
lazy.prop(o, 'pubkey', () => {
if (a.pubkey)
return a.pubkey;
if (!a.witness)
return;
return a.witness[1];
});
lazy.prop(o, 'signature', () => {
if (!a.witness)
return;
return a.witness[0];
});
lazy.prop(o, 'input', () => {
if (!o.witness)
return;
return EMPTY_BUFFER;
});
lazy.prop(o, 'witness', () => {
if (!a.pubkey)
return;
if (!a.signature)
return;
return [a.signature, a.pubkey];
});
// extended validation
if (opts.validate) {
let hash = Buffer.from([]);
if (a.address) {
if (network && network.bech32 !== _address().prefix)
throw new TypeError('Invalid prefix or Network mismatch');
if (_address().version !== 0x00)
throw new TypeError('Invalid address version');
if (_address().data.length !== 20)
throw new TypeError('Invalid address data');
hash = _address().data;
}
if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch');
else
hash = a.hash;
}
if (a.output) {
if (a.output.length !== 22 ||
a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x14)
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) {
const pkh = bcrypto.hash160(a.pubkey);
if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch');
else
hash = pkh;
}
if (a.witness) {
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 (!ecc.isPoint(a.witness[1]))
throw new TypeError('Witness has invalid pubkey');
if (a.signature && !a.signature.equals(a.witness[0]))
throw new TypeError('Signature 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');
}
}
})
const network = a.network || BITCOIN_NETWORK
const o = { network }
lazy.prop(o, 'address', function () {
if (!o.hash) return
const words = bech32.toWords(o.hash)
words.unshift(0x00)
return bech32.encode(network.bech32, words)
})
lazy.prop(o, 'hash', function () {
if (a.output) return a.output.slice(2, 22)
if (a.address) return _address().data
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey)
})
lazy.prop(o, 'output', function () {
if (!o.hash) return
return bscript.compile([
OPS.OP_0,
o.hash
])
})
lazy.prop(o, 'pubkey', function () {
if (a.pubkey) return a.pubkey
if (!a.witness) return
return a.witness[1]
})
lazy.prop(o, 'signature', function () {
if (!a.witness) return
return a.witness[0]
})
lazy.prop(o, 'input', function () {
if (!o.witness) return
return EMPTY_BUFFER
})
lazy.prop(o, 'witness', function () {
if (!a.pubkey) return
if (!a.signature) return
return [a.signature, a.pubkey]
})
// extended validation
if (opts.validate) {
let hash
if (a.address) {
if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch')
if (_address().version !== 0x00) throw new TypeError('Invalid address version')
if (_address().data.length !== 20) throw new TypeError('Invalid address data')
hash = _address().data
}
if (a.hash) {
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
else hash = a.hash
}
if (a.output) {
if (
a.output.length !== 22 ||
a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x14) throw new TypeError('Output is invalid')
if (hash && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch')
else hash = a.output.slice(2)
}
if (a.pubkey) {
const pkh = bcrypto.hash160(a.pubkey)
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
else hash = pkh
}
if (a.witness) {
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 (!ecc.isPoint(a.witness[1])) throw new TypeError('Witness has invalid pubkey')
if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch')
if (a.pubkey && !a.pubkey.equals(a.witness[1])) throw new TypeError('Pubkey mismatch')
const pkh = bcrypto.hash160(a.witness[1])
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
}
}
return Object.assign(o, a)
return Object.assign(o, a);
}
module.exports = p2wpkh
exports.p2wpkh = p2wpkh;

343
src/payments/p2wsh.js

@ -1,180 +1,177 @@
const lazy = require('./lazy')
const typef = require('typeforce')
const OPS = require('bitcoin-ops')
const bech32 = require('bech32')
const bcrypto = require('../crypto')
const bscript = require('../script')
const BITCOIN_NETWORK = require('../networks').bitcoin
const EMPTY_BUFFER = Buffer.alloc(0)
function stacksEqual (a, b) {
if (a.length !== b.length) return false
return a.every(function (x, i) {
return x.equals(b[i])
})
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bcrypto = require("../crypto");
const networks_1 = require("../networks");
const bscript = require("../script");
const lazy = require("./lazy");
const typef = require('typeforce');
const OPS = bscript.OPS;
const bech32 = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0);
function stacksEqual(a, b) {
if (a.length !== b.length)
return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: <>
// witness: [redeemScriptSig ...] {redeemScript}
// output: OP_0 {sha256(redeemScript)}
function p2wsh (a, opts) {
if (
!a.address &&
!a.hash &&
!a.output &&
!a.redeem &&
!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),
output: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer))
}),
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 {
version,
prefix: result.prefix,
data: Buffer.from(data)
function p2wsh(a, opts) {
if (!a.address && !a.hash && !a.output && !a.redeem && !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),
output: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}),
input: typef.maybe(typef.BufferN(0)),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}, a);
const _address = lazy.value(() => {
const result = bech32.decode(a.address);
const version = result.words.shift();
const data = bech32.fromWords(result.words);
return {
version,
prefix: result.prefix,
data: Buffer.from(data),
};
});
const _rchunks = lazy.value(() => {
return bscript.decompile(a.redeem.input);
});
let network = a.network;
if (!network) {
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
}
})
const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input) })
let network = a.network
if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK
}
const o = { network }
lazy.prop(o, 'address', function () {
if (!o.hash) return
const words = bech32.toWords(o.hash)
words.unshift(0x00)
return bech32.encode(network.bech32, words)
})
lazy.prop(o, 'hash', function () {
if (a.output) return a.output.slice(2)
if (a.address) return _address().data
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output)
})
lazy.prop(o, 'output', function () {
if (!o.hash) return
return bscript.compile([
OPS.OP_0,
o.hash
])
})
lazy.prop(o, 'redeem', function () {
if (!a.witness) return
return {
output: a.witness[a.witness.length - 1],
input: EMPTY_BUFFER,
witness: a.witness.slice(0, -1)
const o = { network };
lazy.prop(o, 'address', () => {
if (!o.hash)
return;
const words = bech32.toWords(o.hash);
words.unshift(0x00);
return bech32.encode(network.bech32, words);
});
lazy.prop(o, 'hash', () => {
if (a.output)
return a.output.slice(2);
if (a.address)
return _address().data;
if (o.redeem && o.redeem.output)
return bcrypto.sha256(o.redeem.output);
});
lazy.prop(o, 'output', () => {
if (!o.hash)
return;
return bscript.compile([OPS.OP_0, o.hash]);
});
lazy.prop(o, 'redeem', () => {
if (!a.witness)
return;
return {
output: a.witness[a.witness.length - 1],
input: EMPTY_BUFFER,
witness: a.witness.slice(0, -1),
};
});
lazy.prop(o, 'input', () => {
if (!o.witness)
return;
return EMPTY_BUFFER;
});
lazy.prop(o, 'witness', () => {
// transform redeem input to witness stack?
if (a.redeem &&
a.redeem.input &&
a.redeem.input.length > 0 &&
a.redeem.output &&
a.redeem.output.length > 0) {
const stack = bscript.toStack(_rchunks());
// assign, and blank the existing input
o.redeem = Object.assign({ witness: stack }, a.redeem);
o.redeem.input = EMPTY_BUFFER;
return [].concat(stack, a.redeem.output);
}
if (!a.redeem)
return;
if (!a.redeem.output)
return;
if (!a.redeem.witness)
return;
return [].concat(a.redeem.witness, a.redeem.output);
});
// extended validation
if (opts.validate) {
let hash = Buffer.from([]);
if (a.address) {
if (_address().prefix !== network.bech32)
throw new TypeError('Invalid prefix or Network mismatch');
if (_address().version !== 0x00)
throw new TypeError('Invalid address version');
if (_address().data.length !== 32)
throw new TypeError('Invalid address data');
hash = _address().data;
}
if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch');
else
hash = a.hash;
}
if (a.output) {
if (a.output.length !== 34 ||
a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x20)
throw new TypeError('Output is invalid');
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.network && a.redeem.network !== network)
throw new TypeError('Network mismatch');
// is there two redeem sources?
if (a.redeem.input &&
a.redeem.input.length > 0 &&
a.redeem.witness &&
a.redeem.witness.length > 0)
throw new TypeError('Ambiguous witness source');
// is the redeem output non-empty?
if (a.redeem.output) {
if (bscript.decompile(a.redeem.output).length === 0)
throw new TypeError('Redeem.output is invalid');
// match hash against other sources
const hash2 = bcrypto.sha256(a.redeem.output);
if (hash.length > 0 && !hash.equals(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.witness &&
a.redeem.witness &&
!stacksEqual(a.witness, a.redeem.witness))
throw new TypeError('Witness and redeem.witness mismatch');
}
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');
}
}
})
lazy.prop(o, 'input', function () {
if (!o.witness) return
return EMPTY_BUFFER
})
lazy.prop(o, 'witness', function () {
// transform redeem input to witness stack?
if (
a.redeem &&
a.redeem.input &&
a.redeem.input.length > 0 &&
a.redeem.output &&
a.redeem.output.length > 0
) {
const stack = bscript.toStack(_rchunks())
// assign, and blank the existing input
o.redeem = Object.assign({ witness: stack }, a.redeem)
o.redeem.input = EMPTY_BUFFER
return [].concat(stack, a.redeem.output)
}
if (!a.redeem) return
if (!a.redeem.output) return
if (!a.redeem.witness) return
return [].concat(a.redeem.witness, a.redeem.output)
})
// extended validation
if (opts.validate) {
let hash
if (a.address) {
if (_address().prefix !== network.bech32) throw new TypeError('Invalid prefix or Network mismatch')
if (_address().version !== 0x00) throw new TypeError('Invalid address version')
if (_address().data.length !== 32) throw new TypeError('Invalid address data')
hash = _address().data
}
if (a.hash) {
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
else hash = a.hash
}
if (a.output) {
if (
a.output.length !== 34 ||
a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x20) throw new TypeError('Output is invalid')
const hash2 = a.output.slice(2)
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
else hash = hash2
}
if (a.redeem) {
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch')
// is there two redeem sources?
if (
a.redeem.input &&
a.redeem.input.length > 0 &&
a.redeem.witness &&
a.redeem.witness.length > 0
) throw new TypeError('Ambiguous witness source')
// is the redeem output non-empty?
if (a.redeem.output) {
if (bscript.decompile(a.redeem.output).length === 0) throw new TypeError('Redeem.output is invalid')
// match hash against other sources
const hash2 = bcrypto.sha256(a.redeem.output)
if (hash && !hash.equals(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.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch')
}
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')
}
}
return Object.assign(o, a)
return Object.assign(o, a);
}
module.exports = p2wsh
exports.p2wsh = p2wsh;

368
src/script.js

@ -1,205 +1,191 @@
const Buffer = require('safe-buffer').Buffer
const bip66 = require('bip66')
const ecc = require('tiny-secp256k1')
const pushdata = require('pushdata-bitcoin')
const typeforce = require('typeforce')
const types = require('./types')
const scriptNumber = require('./script_number')
const OPS = require('bitcoin-ops')
const REVERSE_OPS = require('bitcoin-ops/map')
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
function isOPInt (value) {
return types.Number(value) &&
((value === OPS.OP_0) ||
(value >= OPS.OP_1 && value <= OPS.OP_16) ||
(value === OPS.OP_1NEGATE))
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const scriptNumber = require("./script_number");
const scriptSignature = require("./script_signature");
const types = require("./types");
const bip66 = require('bip66');
const ecc = require('tiny-secp256k1');
const pushdata = require('pushdata-bitcoin');
const typeforce = require('typeforce');
exports.OPS = require('bitcoin-ops');
const REVERSE_OPS = require('bitcoin-ops/map');
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1
function isOPInt(value) {
return (types.Number(value) &&
(value === exports.OPS.OP_0 ||
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) ||
value === exports.OPS.OP_1NEGATE));
}
function isPushOnlyChunk (value) {
return types.Buffer(value) || isOPInt(value)
function isPushOnlyChunk(value) {
return types.Buffer(value) || isOPInt(value);
}
function isPushOnly (value) {
return types.Array(value) && value.every(isPushOnlyChunk)
function isPushOnly(value) {
return types.Array(value) && value.every(isPushOnlyChunk);
}
function asMinimalOP (buffer) {
if (buffer.length === 0) return OPS.OP_0
if (buffer.length !== 1) return
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]
if (buffer[0] === 0x81) return OPS.OP_1NEGATE
exports.isPushOnly = isPushOnly;
function asMinimalOP(buffer) {
if (buffer.length === 0)
return exports.OPS.OP_0;
if (buffer.length !== 1)
return;
if (buffer[0] >= 1 && buffer[0] <= 16)
return OP_INT_BASE + buffer[0];
if (buffer[0] === 0x81)
return exports.OPS.OP_1NEGATE;
}
function compile (chunks) {
// TODO: remove me
if (Buffer.isBuffer(chunks)) return chunks
typeforce(types.Array, chunks)
const bufferSize = chunks.reduce(function (accum, chunk) {
// data chunk
if (Buffer.isBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
return accum + 1
}
return accum + pushdata.encodingLength(chunk.length) + chunk.length
}
// opcode
return accum + 1
}, 0.0)
const buffer = Buffer.allocUnsafe(bufferSize)
let offset = 0
chunks.forEach(function (chunk) {
// data chunk
if (Buffer.isBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
const opcode = asMinimalOP(chunk)
if (opcode !== undefined) {
buffer.writeUInt8(opcode, offset)
offset += 1
return
}
offset += pushdata.encode(buffer, chunk.length, offset)
chunk.copy(buffer, offset)
offset += chunk.length
// opcode
} else {
buffer.writeUInt8(chunk, offset)
offset += 1
}
})
if (offset !== buffer.length) throw new Error('Could not decode chunks')
return buffer
function chunksIsBuffer(buf) {
return Buffer.isBuffer(buf);
}
function decompile (buffer) {
// TODO: remove me
if (types.Array(buffer)) return buffer
typeforce(types.Buffer, buffer)
const chunks = []
let i = 0
while (i < buffer.length) {
const opcode = buffer[i]
// data chunk
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) {
const d = pushdata.decode(buffer, i)
// did reading a pushDataInt fail?
if (d === null) return null
i += d.size
// attempt to read too much data?
if (i + d.number > buffer.length) return null
const data = buffer.slice(i, i + d.number)
i += d.number
// decompile minimally
const op = asMinimalOP(data)
if (op !== undefined) {
chunks.push(op)
} else {
chunks.push(data)
}
// opcode
} else {
chunks.push(opcode)
i += 1
}
}
return chunks
function chunksIsArray(buf) {
return types.Array(buf);
}
function toASM (chunks) {
if (Buffer.isBuffer(chunks)) {
chunks = decompile(chunks)
}
return chunks.map(function (chunk) {
// data?
if (Buffer.isBuffer(chunk)) {
const op = asMinimalOP(chunk)
if (op === undefined) return chunk.toString('hex')
chunk = op
function singleChunkIsBuffer(buf) {
return Buffer.isBuffer(buf);
}
function compile(chunks) {
// TODO: remove me
if (chunksIsBuffer(chunks))
return chunks;
typeforce(types.Array, chunks);
const bufferSize = chunks.reduce((accum, chunk) => {
// data chunk
if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
return accum + 1;
}
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
}
// opcode
return accum + 1;
}, 0.0);
const buffer = Buffer.allocUnsafe(bufferSize);
let offset = 0;
chunks.forEach(chunk => {
// data chunk
if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
const opcode = asMinimalOP(chunk);
if (opcode !== undefined) {
buffer.writeUInt8(opcode, offset);
offset += 1;
return;
}
offset += pushdata.encode(buffer, chunk.length, offset);
chunk.copy(buffer, offset);
offset += chunk.length;
// opcode
}
else {
buffer.writeUInt8(chunk, offset);
offset += 1;
}
});
if (offset !== buffer.length)
throw new Error('Could not decode chunks');
return buffer;
}
exports.compile = compile;
function decompile(buffer) {
// TODO: remove me
if (chunksIsArray(buffer))
return buffer;
typeforce(types.Buffer, buffer);
const chunks = [];
let i = 0;
while (i < buffer.length) {
const opcode = buffer[i];
// data chunk
if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) {
const d = pushdata.decode(buffer, i);
// did reading a pushDataInt fail?
if (d === null)
return null;
i += d.size;
// attempt to read too much data?
if (i + d.number > buffer.length)
return null;
const data = buffer.slice(i, i + d.number);
i += d.number;
// decompile minimally
const op = asMinimalOP(data);
if (op !== undefined) {
chunks.push(op);
}
else {
chunks.push(data);
}
// opcode
}
else {
chunks.push(opcode);
i += 1;
}
}
// opcode!
return REVERSE_OPS[chunk]
}).join(' ')
return chunks;
}
function fromASM (asm) {
typeforce(types.String, asm)
return compile(asm.split(' ').map(function (chunkStr) {
// opcode?
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
typeforce(types.Hex, chunkStr)
// data!
return Buffer.from(chunkStr, 'hex')
}))
exports.decompile = decompile;
function toASM(chunks) {
if (chunksIsBuffer(chunks)) {
chunks = decompile(chunks);
}
return chunks
.map(chunk => {
// data?
if (singleChunkIsBuffer(chunk)) {
const op = asMinimalOP(chunk);
if (op === undefined)
return chunk.toString('hex');
chunk = op;
}
// opcode!
return REVERSE_OPS[chunk];
})
.join(' ');
}
function toStack (chunks) {
chunks = decompile(chunks)
typeforce(isPushOnly, chunks)
return chunks.map(function (op) {
if (Buffer.isBuffer(op)) return op
if (op === OPS.OP_0) return Buffer.allocUnsafe(0)
return scriptNumber.encode(op - OP_INT_BASE)
})
exports.toASM = toASM;
function fromASM(asm) {
typeforce(types.String, asm);
return compile(asm.split(' ').map(chunkStr => {
// opcode?
if (exports.OPS[chunkStr] !== undefined)
return exports.OPS[chunkStr];
typeforce(types.Hex, chunkStr);
// data!
return Buffer.from(chunkStr, 'hex');
}));
}
function isCanonicalPubKey (buffer) {
return ecc.isPoint(buffer)
exports.fromASM = fromASM;
function toStack(chunks) {
chunks = decompile(chunks);
typeforce(isPushOnly, chunks);
return chunks.map(op => {
if (singleChunkIsBuffer(op))
return op;
if (op === exports.OPS.OP_0)
return Buffer.allocUnsafe(0);
return scriptNumber.encode(op - OP_INT_BASE);
});
}
function isDefinedHashType (hashType) {
const hashTypeMod = hashType & ~0x80
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
return hashTypeMod > 0x00 && hashTypeMod < 0x04
exports.toStack = toStack;
function isCanonicalPubKey(buffer) {
return ecc.isPoint(buffer);
}
function isCanonicalScriptSignature (buffer) {
if (!Buffer.isBuffer(buffer)) return false
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
return bip66.check(buffer.slice(0, -1))
exports.isCanonicalPubKey = isCanonicalPubKey;
function isDefinedHashType(hashType) {
const hashTypeMod = hashType & ~0x80;
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
}
module.exports = {
compile: compile,
decompile: decompile,
fromASM: fromASM,
toASM: toASM,
toStack: toStack,
number: require('./script_number'),
signature: require('./script_signature'),
isCanonicalPubKey: isCanonicalPubKey,
isCanonicalScriptSignature: isCanonicalScriptSignature,
isPushOnly: isPushOnly,
isDefinedHashType: isDefinedHashType
exports.isDefinedHashType = isDefinedHashType;
function isCanonicalScriptSignature(buffer) {
if (!Buffer.isBuffer(buffer))
return false;
if (!isDefinedHashType(buffer[buffer.length - 1]))
return false;
return bip66.check(buffer.slice(0, -1));
}
exports.isCanonicalScriptSignature = isCanonicalScriptSignature;
// tslint:disable-next-line variable-name
exports.number = scriptNumber;
exports.signature = scriptSignature;

124
src/script_number.js

@ -1,67 +1,65 @@
const Buffer = require('safe-buffer').Buffer
function decode (buffer, maxLength, minimal) {
maxLength = maxLength || 4
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 ((buffer[length - 1] & 0x7f) === 0) {
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) throw new Error('Non-minimally encoded script number')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function decode(buffer, maxLength, minimal) {
maxLength = maxLength || 4;
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 ((buffer[length - 1] & 0x7f) === 0) {
if (length <= 1 || (buffer[length - 2] & 0x80) === 0)
throw new Error('Non-minimally encoded script number');
}
}
}
// 40-bit
if (length === 5) {
const a = buffer.readUInt32LE(0)
const b = buffer.readUInt8(4)
if (b & 0x80) return -(((b & ~0x80) * 0x100000000) + a)
return (b * 0x100000000) + a
}
// 32-bit / 24-bit / 16-bit / 8-bit
let result = 0
for (var i = 0; i < length; ++i) {
result |= buffer[i] << (8 * i)
}
if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1))))
return result
}
function scriptNumSize (i) {
return i > 0x7fffffff ? 5
: i > 0x7fffff ? 4
: i > 0x7fff ? 3
: i > 0x7f ? 2
: i > 0x00 ? 1
: 0
// 40-bit
if (length === 5) {
const a = buffer.readUInt32LE(0);
const b = buffer.readUInt8(4);
if (b & 0x80)
return -((b & ~0x80) * 0x100000000 + a);
return b * 0x100000000 + a;
}
// 32-bit / 24-bit / 16-bit / 8-bit
let result = 0;
for (let i = 0; i < length; ++i) {
result |= buffer[i] << (8 * i);
}
if (buffer[length - 1] & 0x80)
return -(result & ~(0x80 << (8 * (length - 1))));
return result;
}
function encode (number) {
let value = Math.abs(number)
const size = scriptNumSize(value)
const buffer = Buffer.allocUnsafe(size)
const negative = number < 0
for (var i = 0; i < size; ++i) {
buffer.writeUInt8(value & 0xff, i)
value >>= 8
}
if (buffer[size - 1] & 0x80) {
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1)
} else if (negative) {
buffer[size - 1] |= 0x80
}
return buffer
exports.decode = decode;
function scriptNumSize(i) {
return i > 0x7fffffff
? 5
: i > 0x7fffff
? 4
: i > 0x7fff
? 3
: i > 0x7f
? 2
: i > 0x00
? 1
: 0;
}
module.exports = {
decode: decode,
encode: encode
function encode(_number) {
let value = Math.abs(_number);
const size = scriptNumSize(value);
const buffer = Buffer.allocUnsafe(size);
const negative = _number < 0;
for (let i = 0; i < size; ++i) {
buffer.writeUInt8(value & 0xff, i);
value >>= 8;
}
if (buffer[size - 1] & 0x80) {
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1);
}
else if (negative) {
buffer[size - 1] |= 0x80;
}
return buffer;
}
exports.encode = encode;

107
src/script_signature.js

@ -1,64 +1,53 @@
const bip66 = require('bip66')
const Buffer = require('safe-buffer').Buffer
const typeforce = require('typeforce')
const types = require('./types')
const ZERO = Buffer.alloc(1, 0)
function toDER (x) {
let i = 0
while (x[i] === 0) ++i
if (i === x.length) return ZERO
x = x.slice(i)
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length)
return x
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types = require("./types");
const bip66 = require('bip66');
const typeforce = require('typeforce');
const ZERO = Buffer.alloc(1, 0);
function toDER(x) {
let i = 0;
while (x[i] === 0)
++i;
if (i === x.length)
return ZERO;
x = x.slice(i);
if (x[0] & 0x80)
return Buffer.concat([ZERO, x], 1 + x.length);
return x;
}
function fromDER (x) {
if (x[0] === 0x00) x = x.slice(1)
const buffer = Buffer.alloc(32, 0)
const bstart = Math.max(0, 32 - x.length)
x.copy(buffer, bstart)
return buffer
function fromDER(x) {
if (x[0] === 0x00)
x = x.slice(1);
const buffer = Buffer.alloc(32, 0);
const bstart = Math.max(0, 32 - x.length);
x.copy(buffer, bstart);
return buffer;
}
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
function decode (buffer) {
const hashType = buffer.readUInt8(buffer.length - 1)
const hashTypeMod = hashType & ~0x80
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
const decode = bip66.decode(buffer.slice(0, -1))
const r = fromDER(decode.r)
const s = fromDER(decode.s)
return {
signature: Buffer.concat([r, s], 64),
hashType: hashType
}
function decode(buffer) {
const hashType = buffer.readUInt8(buffer.length - 1);
const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4)
throw new Error('Invalid hashType ' + hashType);
const decoded = bip66.decode(buffer.slice(0, -1));
const r = fromDER(decoded.r);
const s = fromDER(decoded.s);
const signature = Buffer.concat([r, s], 64);
return { signature, hashType };
}
function encode (signature, hashType) {
typeforce({
signature: types.BufferN(64),
hashType: types.UInt8
}, { signature, hashType })
const hashTypeMod = hashType & ~0x80
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
const hashTypeBuffer = Buffer.allocUnsafe(1)
hashTypeBuffer.writeUInt8(hashType, 0)
const r = toDER(signature.slice(0, 32))
const s = toDER(signature.slice(32, 64))
return Buffer.concat([
bip66.encode(r, s),
hashTypeBuffer
])
}
module.exports = {
decode: decode,
encode: encode
exports.decode = decode;
function encode(signature, hashType) {
typeforce({
signature: types.BufferN(64),
hashType: types.UInt8,
}, { signature, hashType });
const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4)
throw new Error('Invalid hashType ' + hashType);
const hashTypeBuffer = Buffer.allocUnsafe(1);
hashTypeBuffer.writeUInt8(hashType, 0);
const r = toDER(signature.slice(0, 32));
const s = toDER(signature.slice(32, 64));
return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]);
}
exports.encode = encode;

10
src/templates/multisig/index.js

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

40
src/templates/multisig/input.js

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

61
src/templates/multisig/output.js

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

25
src/templates/nulldata.js

@ -1,14 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// OP_RETURN {data}
const bscript = require('../script')
const OPS = require('bitcoin-ops')
function check (script) {
const buffer = bscript.compile(script)
return buffer.length > 1 &&
buffer[0] === OPS.OP_RETURN
const bscript = require("../script");
const OPS = bscript.OPS;
function check(script) {
const buffer = bscript.compile(script);
return buffer.length > 1 && buffer[0] === OPS.OP_RETURN;
}
check.toJSON = function () { return 'null data output' }
module.exports = { output: { check: check } }
exports.check = check;
check.toJSON = () => {
return 'null data output';
};
const output = { check };
exports.output = output;

10
src/templates/pubkey/index.js

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

24
src/templates/pubkey/input.js

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

26
src/templates/pubkey/output.js

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

10
src/templates/pubkeyhash/index.js

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

24
src/templates/pubkeyhash/input.js

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

32
src/templates/pubkeyhash/output.js

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

10
src/templates/scripthash/index.js

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

88
src/templates/scripthash/input.js

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

28
src/templates/scripthash/output.js

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

7
src/templates/witnesscommitment/index.js

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

66
src/templates/witnesscommitment/output.js

@ -1,42 +1,32 @@
"use strict";
// OP_RETURN {aa21a9ed} {commitment}
const Buffer = require('safe-buffer').Buffer
const bscript = require('../../script')
const types = require('../../types')
const typeforce = require('typeforce')
const OPS = require('bitcoin-ops')
const HEADER = Buffer.from('aa21a9ed', 'hex')
function check (script) {
const buffer = bscript.compile(script)
return buffer.length > 37 &&
buffer[0] === OPS.OP_RETURN &&
buffer[1] === 0x24 &&
buffer.slice(2, 6).equals(HEADER)
Object.defineProperty(exports, "__esModule", { value: true });
const bscript = require("../../script");
const script_1 = require("../../script");
const types = require("../../types");
const typeforce = require('typeforce');
const HEADER = Buffer.from('aa21a9ed', 'hex');
function check(script) {
const buffer = bscript.compile(script);
return (buffer.length > 37 &&
buffer[0] === script_1.OPS.OP_RETURN &&
buffer[1] === 0x24 &&
buffer.slice(2, 6).equals(HEADER));
}
check.toJSON = function () { return 'Witness commitment output' }
function encode (commitment) {
typeforce(types.Hash256bit, commitment)
const buffer = Buffer.allocUnsafe(36)
HEADER.copy(buffer, 0)
commitment.copy(buffer, 4)
return bscript.compile([OPS.OP_RETURN, buffer])
exports.check = check;
check.toJSON = () => {
return 'Witness commitment output';
};
function encode(commitment) {
typeforce(types.Hash256bit, commitment);
const buffer = Buffer.allocUnsafe(36);
HEADER.copy(buffer, 0);
commitment.copy(buffer, 4);
return bscript.compile([script_1.OPS.OP_RETURN, buffer]);
}
function decode (buffer) {
typeforce(check, buffer)
return bscript.decompile(buffer)[1].slice(4, 36)
}
module.exports = {
check: check,
decode: decode,
encode: encode
exports.encode = encode;
function decode(buffer) {
typeforce(check, buffer);
return bscript.decompile(buffer)[1].slice(4, 36);
}
exports.decode = decode;

10
src/templates/witnesspubkeyhash/index.js

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

29
src/templates/witnesspubkeyhash/input.js

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

26
src/templates/witnesspubkeyhash/output.js

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

10
src/templates/witnessscripthash/index.js

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

71
src/templates/witnessscripthash/input.js

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

24
src/templates/witnessscripthash/output.js

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

924
src/transaction.js

@ -1,492 +1,452 @@
const Buffer = require('safe-buffer').Buffer
const bcrypto = require('./crypto')
const bscript = require('./script')
const bufferutils = require('./bufferutils')
const opcodes = require('bitcoin-ops')
const typeforce = require('typeforce')
const types = require('./types')
const varuint = require('varuint-bitcoin')
function varSliceSize (someScript) {
const length = someScript.length
return varuint.encodingLength(length) + length
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bufferutils = require("./bufferutils");
const bufferutils_1 = require("./bufferutils");
const bcrypto = require("./crypto");
const bscript = require("./script");
const script_1 = require("./script");
const types = require("./types");
const typeforce = require('typeforce');
const varuint = require('varuint-bitcoin');
function varSliceSize(someScript) {
const length = someScript.length;
return varuint.encodingLength(length) + length;
}
function vectorSize (someVector) {
const length = someVector.length
return varuint.encodingLength(length) + someVector.reduce(function (sum, witness) {
return sum + varSliceSize(witness)
}, 0)
function vectorSize(someVector) {
const length = someVector.length;
return (varuint.encodingLength(length) +
someVector.reduce((sum, witness) => {
return sum + varSliceSize(witness);
}, 0));
}
function Transaction () {
this.version = 1
this.locktime = 0
this.ins = []
this.outs = []
}
Transaction.DEFAULT_SEQUENCE = 0xffffffff
Transaction.SIGHASH_ALL = 0x01
Transaction.SIGHASH_NONE = 0x02
Transaction.SIGHASH_SINGLE = 0x03
Transaction.SIGHASH_ANYONECANPAY = 0x80
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01
const EMPTY_SCRIPT = Buffer.allocUnsafe(0)
const EMPTY_WITNESS = []
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex')
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
const EMPTY_WITNESS = [];
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex');
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT = {
script: EMPTY_SCRIPT,
valueBuffer: VALUE_UINT64_MAX
script: EMPTY_SCRIPT,
valueBuffer: VALUE_UINT64_MAX,
};
function isOutput(out) {
return out.value !== undefined;
}
Transaction.fromBuffer = function (buffer, __noStrict) {
let offset = 0
function readSlice (n) {
offset += n
return buffer.slice(offset - n, offset)
}
function readUInt32 () {
const i = buffer.readUInt32LE(offset)
offset += 4
return i
}
function readInt32 () {
const i = buffer.readInt32LE(offset)
offset += 4
return i
}
function readUInt64 () {
const i = bufferutils.readUInt64LE(buffer, offset)
offset += 8
return i
}
function readVarInt () {
const vi = varuint.decode(buffer, offset)
offset += varuint.decode.bytes
return vi
}
function readVarSlice () {
return readSlice(readVarInt())
}
function readVector () {
const count = readVarInt()
const vector = []
for (var i = 0; i < count; i++) vector.push(readVarSlice())
return vector
}
const tx = new Transaction()
tx.version = readInt32()
const marker = buffer.readUInt8(offset)
const flag = buffer.readUInt8(offset + 1)
let hasWitnesses = false
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
flag === Transaction.ADVANCED_TRANSACTION_FLAG) {
offset += 2
hasWitnesses = true
}
const vinLen = readVarInt()
for (var i = 0; i < vinLen; ++i) {
tx.ins.push({
hash: readSlice(32),
index: readUInt32(),
script: readVarSlice(),
sequence: readUInt32(),
witness: EMPTY_WITNESS
})
}
const voutLen = readVarInt()
for (i = 0; i < voutLen; ++i) {
tx.outs.push({
value: readUInt64(),
script: readVarSlice()
})
}
if (hasWitnesses) {
for (i = 0; i < vinLen; ++i) {
tx.ins[i].witness = readVector()
class Transaction {
constructor() {
this.version = 1;
this.locktime = 0;
this.ins = [];
this.outs = [];
}
// was this pointless?
if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data')
}
tx.locktime = readUInt32()
if (__noStrict) return tx
if (offset !== buffer.length) throw new Error('Transaction has unexpected data')
return tx
}
Transaction.fromHex = function (hex) {
return Transaction.fromBuffer(Buffer.from(hex, 'hex'))
}
Transaction.isCoinbaseHash = function (buffer) {
typeforce(types.Hash256bit, buffer)
for (var i = 0; i < 32; ++i) {
if (buffer[i] !== 0) return false
}
return true
}
Transaction.prototype.isCoinbase = function () {
return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
}
Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) {
typeforce(types.tuple(
types.Hash256bit,
types.UInt32,
types.maybe(types.UInt32),
types.maybe(types.Buffer)
), arguments)
if (types.Null(sequence)) {
sequence = Transaction.DEFAULT_SEQUENCE
}
// Add the input and return the input's index
return (this.ins.push({
hash: hash,
index: index,
script: scriptSig || EMPTY_SCRIPT,
sequence: sequence,
witness: EMPTY_WITNESS
}) - 1)
}
Transaction.prototype.addOutput = function (scriptPubKey, value) {
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments)
// Add the output and return the output's index
return (this.outs.push({
script: scriptPubKey,
value: value
}) - 1)
}
Transaction.prototype.hasWitnesses = function () {
return this.ins.some(function (x) {
return x.witness.length !== 0
})
}
Transaction.prototype.weight = function () {
const base = this.__byteLength(false)
const total = this.__byteLength(true)
return base * 3 + total
}
Transaction.prototype.virtualSize = function () {
return Math.ceil(this.weight() / 4)
}
Transaction.prototype.byteLength = function () {
return this.__byteLength(true)
}
Transaction.prototype.__byteLength = function (__allowWitness) {
const hasWitnesses = __allowWitness && this.hasWitnesses()
return (
(hasWitnesses ? 10 : 8) +
varuint.encodingLength(this.ins.length) +
varuint.encodingLength(this.outs.length) +
this.ins.reduce(function (sum, input) { return sum + 40 + varSliceSize(input.script) }, 0) +
this.outs.reduce(function (sum, output) { return sum + 8 + varSliceSize(output.script) }, 0) +
(hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0)
)
}
Transaction.prototype.clone = function () {
const newTx = new Transaction()
newTx.version = this.version
newTx.locktime = this.locktime
newTx.ins = this.ins.map(function (txIn) {
return {
hash: txIn.hash,
index: txIn.index,
script: txIn.script,
sequence: txIn.sequence,
witness: txIn.witness
static fromBuffer(buffer, _NO_STRICT) {
let offset = 0;
function readSlice(n) {
offset += n;
return buffer.slice(offset - n, offset);
}
function readUInt32() {
const i = buffer.readUInt32LE(offset);
offset += 4;
return i;
}
function readInt32() {
const i = buffer.readInt32LE(offset);
offset += 4;
return i;
}
function readUInt64() {
const i = bufferutils.readUInt64LE(buffer, offset);
offset += 8;
return i;
}
function readVarInt() {
const vi = varuint.decode(buffer, offset);
offset += varuint.decode.bytes;
return vi;
}
function readVarSlice() {
return readSlice(readVarInt());
}
function readVector() {
const count = readVarInt();
const vector = [];
for (let i = 0; i < count; i++)
vector.push(readVarSlice());
return vector;
}
const tx = new Transaction();
tx.version = readInt32();
const marker = buffer.readUInt8(offset);
const flag = buffer.readUInt8(offset + 1);
let hasWitnesses = false;
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
flag === Transaction.ADVANCED_TRANSACTION_FLAG) {
offset += 2;
hasWitnesses = true;
}
const vinLen = readVarInt();
for (let i = 0; i < vinLen; ++i) {
tx.ins.push({
hash: readSlice(32),
index: readUInt32(),
script: readVarSlice(),
sequence: readUInt32(),
witness: EMPTY_WITNESS,
});
}
const voutLen = readVarInt();
for (let i = 0; i < voutLen; ++i) {
tx.outs.push({
value: readUInt64(),
script: readVarSlice(),
});
}
if (hasWitnesses) {
for (let i = 0; i < vinLen; ++i) {
tx.ins[i].witness = readVector();
}
// was this pointless?
if (!tx.hasWitnesses())
throw new Error('Transaction has superfluous witness data');
}
tx.locktime = readUInt32();
if (_NO_STRICT)
return tx;
if (offset !== buffer.length)
throw new Error('Transaction has unexpected data');
return tx;
}
})
newTx.outs = this.outs.map(function (txOut) {
return {
script: txOut.script,
value: txOut.value
static fromHex(hex) {
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
}
})
return newTx
}
/**
* Hash transaction for signing a specific input.
*
* Bitcoin uses a different hash for each signed transaction input.
* This method copies the transaction, makes the necessary changes based on the
* hashType, and then hashes the result.
* This hash can then be used to sign the provided transaction input.
*/
Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
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
if (inIndex >= this.ins.length) return ONE
// ignore OP_CODESEPARATOR
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) {
return x !== opcodes.OP_CODESEPARATOR
}))
const txTmp = this.clone()
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
txTmp.outs = []
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach(function (input, i) {
if (i === inIndex) return
input.sequence = 0
})
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
if (inIndex >= this.outs.length) return ONE
// truncate outputs after
txTmp.outs.length = inIndex + 1
// "blank" outputs before
for (var i = 0; i < inIndex; i++) {
txTmp.outs[i] = BLANK_OUTPUT
static isCoinbaseHash(buffer) {
typeforce(types.Hash256bit, buffer);
for (let i = 0; i < 32; ++i) {
if (buffer[i] !== 0)
return false;
}
return true;
}
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach(function (input, y) {
if (y === inIndex) return
input.sequence = 0
})
}
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
txTmp.ins = [txTmp.ins[inIndex]]
txTmp.ins[0].script = ourScript
// SIGHASH_ALL: only ignore input scripts
} else {
// "blank" others input scripts
txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
txTmp.ins[inIndex].script = ourScript
}
// serialize and hash
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4)
buffer.writeInt32LE(hashType, buffer.length - 4)
txTmp.__toBuffer(buffer, 0, false)
return bcrypto.hash256(buffer)
}
Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) {
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments)
let tbuffer, toffset
function writeSlice (slice) { toffset += slice.copy(tbuffer, toffset) }
function writeUInt32 (i) { toffset = tbuffer.writeUInt32LE(i, toffset) }
function writeUInt64 (i) { toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) }
function writeVarInt (i) {
varuint.encode(i, tbuffer, toffset)
toffset += varuint.encode.bytes
}
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) }
let hashOutputs = ZERO
let hashPrevouts = ZERO
let hashSequence = ZERO
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
tbuffer = Buffer.allocUnsafe(36 * this.ins.length)
toffset = 0
this.ins.forEach(function (txIn) {
writeSlice(txIn.hash)
writeUInt32(txIn.index)
})
hashPrevouts = bcrypto.hash256(tbuffer)
}
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
tbuffer = Buffer.allocUnsafe(4 * this.ins.length)
toffset = 0
this.ins.forEach(function (txIn) {
writeUInt32(txIn.sequence)
})
hashSequence = bcrypto.hash256(tbuffer)
}
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
const txOutsSize = this.outs.reduce(function (sum, output) {
return sum + 8 + varSliceSize(output.script)
}, 0)
tbuffer = Buffer.allocUnsafe(txOutsSize)
toffset = 0
this.outs.forEach(function (out) {
writeUInt64(out.value)
writeVarSlice(out.script)
})
hashOutputs = bcrypto.hash256(tbuffer)
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) {
const output = this.outs[inIndex]
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script))
toffset = 0
writeUInt64(output.value)
writeVarSlice(output.script)
hashOutputs = bcrypto.hash256(tbuffer)
}
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript))
toffset = 0
const input = this.ins[inIndex]
writeUInt32(this.version)
writeSlice(hashPrevouts)
writeSlice(hashSequence)
writeSlice(input.hash)
writeUInt32(input.index)
writeVarSlice(prevOutScript)
writeUInt64(value)
writeUInt32(input.sequence)
writeSlice(hashOutputs)
writeUInt32(this.locktime)
writeUInt32(hashType)
return bcrypto.hash256(tbuffer)
}
Transaction.prototype.getHash = function () {
return bcrypto.hash256(this.__toBuffer(undefined, undefined, false))
}
Transaction.prototype.getId = function () {
// transaction hash's are displayed in reverse order
return this.getHash().reverse().toString('hex')
}
Transaction.prototype.toBuffer = function (buffer, initialOffset) {
return this.__toBuffer(buffer, initialOffset, true)
}
Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitness) {
if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness))
let offset = initialOffset || 0
function writeSlice (slice) { offset += slice.copy(buffer, offset) }
function writeUInt8 (i) { offset = buffer.writeUInt8(i, offset) }
function writeUInt32 (i) { offset = buffer.writeUInt32LE(i, offset) }
function writeInt32 (i) { offset = buffer.writeInt32LE(i, offset) }
function writeUInt64 (i) { offset = bufferutils.writeUInt64LE(buffer, i, offset) }
function writeVarInt (i) {
varuint.encode(i, buffer, offset)
offset += varuint.encode.bytes
}
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) }
function writeVector (vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice) }
writeInt32(this.version)
const hasWitnesses = __allowWitness && this.hasWitnesses()
if (hasWitnesses) {
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER)
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG)
}
writeVarInt(this.ins.length)
this.ins.forEach(function (txIn) {
writeSlice(txIn.hash)
writeUInt32(txIn.index)
writeVarSlice(txIn.script)
writeUInt32(txIn.sequence)
})
writeVarInt(this.outs.length)
this.outs.forEach(function (txOut) {
if (!txOut.valueBuffer) {
writeUInt64(txOut.value)
} else {
writeSlice(txOut.valueBuffer)
isCoinbase() {
return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash));
}
addInput(hash, index, sequence, scriptSig) {
typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer)), arguments);
if (types.Null(sequence)) {
sequence = Transaction.DEFAULT_SEQUENCE;
}
// Add the input and return the input's index
return (this.ins.push({
hash,
index,
script: scriptSig || EMPTY_SCRIPT,
sequence: sequence,
witness: EMPTY_WITNESS,
}) - 1);
}
addOutput(scriptPubKey, value) {
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments);
// Add the output and return the output's index
return (this.outs.push({
script: scriptPubKey,
value,
}) - 1);
}
hasWitnesses() {
return this.ins.some(x => {
return x.witness.length !== 0;
});
}
weight() {
const base = this.__byteLength(false);
const total = this.__byteLength(true);
return base * 3 + total;
}
virtualSize() {
return Math.ceil(this.weight() / 4);
}
byteLength() {
return this.__byteLength(true);
}
clone() {
const newTx = new Transaction();
newTx.version = this.version;
newTx.locktime = this.locktime;
newTx.ins = this.ins.map(txIn => {
return {
hash: txIn.hash,
index: txIn.index,
script: txIn.script,
sequence: txIn.sequence,
witness: txIn.witness,
};
});
newTx.outs = this.outs.map(txOut => {
return {
script: txOut.script,
value: txOut.value,
};
});
return newTx;
}
/**
* Hash transaction for signing a specific input.
*
* Bitcoin uses a different hash for each signed transaction input.
* This method copies the transaction, makes the necessary changes based on the
* hashType, and then hashes the result.
* This hash can then be used to sign the provided transaction input.
*/
hashForSignature(inIndex, prevOutScript, hashType) {
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
if (inIndex >= this.ins.length)
return ONE;
// ignore OP_CODESEPARATOR
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => {
return x !== script_1.OPS.OP_CODESEPARATOR;
}));
const txTmp = this.clone();
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
txTmp.outs = [];
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, i) => {
if (i === inIndex)
return;
input.sequence = 0;
});
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
}
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
if (inIndex >= this.outs.length)
return ONE;
// truncate outputs after
txTmp.outs.length = inIndex + 1;
// "blank" outputs before
for (let i = 0; i < inIndex; i++) {
txTmp.outs[i] = BLANK_OUTPUT;
}
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, y) => {
if (y === inIndex)
return;
input.sequence = 0;
});
}
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
txTmp.ins = [txTmp.ins[inIndex]];
txTmp.ins[0].script = ourScript;
// SIGHASH_ALL: only ignore input scripts
}
else {
// "blank" others input scripts
txTmp.ins.forEach(input => {
input.script = EMPTY_SCRIPT;
});
txTmp.ins[inIndex].script = ourScript;
}
// serialize and hash
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4);
buffer.writeInt32LE(hashType, buffer.length - 4);
txTmp.__toBuffer(buffer, 0, false);
return bcrypto.hash256(buffer);
}
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments);
let tbuffer = Buffer.from([]);
let toffset = 0;
function writeSlice(slice) {
toffset += slice.copy(tbuffer, toffset);
}
function writeUInt32(i) {
toffset = tbuffer.writeUInt32LE(i, toffset);
}
function writeUInt64(i) {
toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset);
}
function writeVarInt(i) {
varuint.encode(i, tbuffer, toffset);
toffset += varuint.encode.bytes;
}
function writeVarSlice(slice) {
writeVarInt(slice.length);
writeSlice(slice);
}
let hashOutputs = ZERO;
let hashPrevouts = ZERO;
let hashSequence = ZERO;
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
tbuffer = Buffer.allocUnsafe(36 * this.ins.length);
toffset = 0;
this.ins.forEach(txIn => {
writeSlice(txIn.hash);
writeUInt32(txIn.index);
});
hashPrevouts = bcrypto.hash256(tbuffer);
}
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
tbuffer = Buffer.allocUnsafe(4 * this.ins.length);
toffset = 0;
this.ins.forEach(txIn => {
writeUInt32(txIn.sequence);
});
hashSequence = bcrypto.hash256(tbuffer);
}
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
const txOutsSize = this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script);
}, 0);
tbuffer = Buffer.allocUnsafe(txOutsSize);
toffset = 0;
this.outs.forEach(out => {
writeUInt64(out.value);
writeVarSlice(out.script);
});
hashOutputs = bcrypto.hash256(tbuffer);
}
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
inIndex < this.outs.length) {
const output = this.outs[inIndex];
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script));
toffset = 0;
writeUInt64(output.value);
writeVarSlice(output.script);
hashOutputs = bcrypto.hash256(tbuffer);
}
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript));
toffset = 0;
const input = this.ins[inIndex];
writeUInt32(this.version);
writeSlice(hashPrevouts);
writeSlice(hashSequence);
writeSlice(input.hash);
writeUInt32(input.index);
writeVarSlice(prevOutScript);
writeUInt64(value);
writeUInt32(input.sequence);
writeSlice(hashOutputs);
writeUInt32(this.locktime);
writeUInt32(hashType);
return bcrypto.hash256(tbuffer);
}
getHash(forWitness) {
// wtxid for coinbase is always 32 bytes of 0x00
if (forWitness && this.isCoinbase())
return Buffer.alloc(32, 0);
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness));
}
getId() {
// transaction hash's are displayed in reverse order
return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex');
}
toBuffer(buffer, initialOffset) {
return this.__toBuffer(buffer, initialOffset, true);
}
toHex() {
return this.toBuffer(undefined, undefined).toString('hex');
}
setInputScript(index, scriptSig) {
typeforce(types.tuple(types.Number, types.Buffer), arguments);
this.ins[index].script = scriptSig;
}
setWitness(index, witness) {
typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
this.ins[index].witness = witness;
}
__byteLength(_ALLOW_WITNESS) {
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
return ((hasWitnesses ? 10 : 8) +
varuint.encodingLength(this.ins.length) +
varuint.encodingLength(this.outs.length) +
this.ins.reduce((sum, input) => {
return sum + 40 + varSliceSize(input.script);
}, 0) +
this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script);
}, 0) +
(hasWitnesses
? this.ins.reduce((sum, input) => {
return sum + vectorSize(input.witness);
}, 0)
: 0));
}
__toBuffer(buffer, initialOffset, _ALLOW_WITNESS) {
if (!buffer)
buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS));
let offset = initialOffset || 0;
function writeSlice(slice) {
offset += slice.copy(buffer, offset);
}
function writeUInt8(i) {
offset = buffer.writeUInt8(i, offset);
}
function writeUInt32(i) {
offset = buffer.writeUInt32LE(i, offset);
}
function writeInt32(i) {
offset = buffer.writeInt32LE(i, offset);
}
function writeUInt64(i) {
offset = bufferutils.writeUInt64LE(buffer, i, offset);
}
function writeVarInt(i) {
varuint.encode(i, buffer, offset);
offset += varuint.encode.bytes;
}
function writeVarSlice(slice) {
writeVarInt(slice.length);
writeSlice(slice);
}
function writeVector(vector) {
writeVarInt(vector.length);
vector.forEach(writeVarSlice);
}
writeInt32(this.version);
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
if (hasWitnesses) {
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER);
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG);
}
writeVarInt(this.ins.length);
this.ins.forEach(txIn => {
writeSlice(txIn.hash);
writeUInt32(txIn.index);
writeVarSlice(txIn.script);
writeUInt32(txIn.sequence);
});
writeVarInt(this.outs.length);
this.outs.forEach(txOut => {
if (isOutput(txOut)) {
writeUInt64(txOut.value);
}
else {
writeSlice(txOut.valueBuffer);
}
writeVarSlice(txOut.script);
});
if (hasWitnesses) {
this.ins.forEach(input => {
writeVector(input.witness);
});
}
writeUInt32(this.locktime);
// avoid slicing unless necessary
if (initialOffset !== undefined)
return buffer.slice(initialOffset, offset);
return buffer;
}
writeVarSlice(txOut.script)
})
if (hasWitnesses) {
this.ins.forEach(function (input) {
writeVector(input.witness)
})
}
writeUInt32(this.locktime)
// avoid slicing unless necessary
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset)
return buffer
}
Transaction.prototype.toHex = function () {
return this.toBuffer().toString('hex')
}
Transaction.prototype.setInputScript = function (index, scriptSig) {
typeforce(types.tuple(types.Number, types.Buffer), arguments)
this.ins[index].script = scriptSig
}
Transaction.prototype.setWitness = function (index, witness) {
typeforce(types.tuple(types.Number, [types.Buffer]), arguments)
this.ins[index].witness = witness
}
module.exports = Transaction
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
Transaction.SIGHASH_ALL = 0x01;
Transaction.SIGHASH_NONE = 0x02;
Transaction.SIGHASH_SINGLE = 0x03;
Transaction.SIGHASH_ANYONECANPAY = 0x80;
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
exports.Transaction = Transaction;

1461
src/transaction_builder.js

File diff suppressed because it is too large

89
src/types.js

@ -1,49 +1,50 @@
const typeforce = require('typeforce')
const UINT31_MAX = Math.pow(2, 31) - 1
function UInt31 (value) {
return typeforce.UInt32(value) && value <= UINT31_MAX
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const typeforce = require('typeforce');
const UINT31_MAX = Math.pow(2, 31) - 1;
function UInt31(value) {
return typeforce.UInt32(value) && value <= UINT31_MAX;
}
function BIP32Path (value) {
return typeforce.String(value) && value.match(/^(m\/)?(\d+'?\/)*\d+'?$/)
exports.UInt31 = UInt31;
function BIP32Path(value) {
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/);
}
BIP32Path.toJSON = function () { return 'BIP32 derivation path' }
const SATOSHI_MAX = 21 * 1e14
function Satoshi (value) {
return typeforce.UInt53(value) && value <= SATOSHI_MAX
exports.BIP32Path = BIP32Path;
BIP32Path.toJSON = () => {
return 'BIP32 derivation path';
};
const SATOSHI_MAX = 21 * 1e14;
function Satoshi(value) {
return typeforce.UInt53(value) && value <= SATOSHI_MAX;
}
exports.Satoshi = Satoshi;
// external dependent types
const ECPoint = typeforce.quacksLike('Point')
exports.ECPoint = typeforce.quacksLike('Point');
// exposed, external API
const Network = typeforce.compile({
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
bip32: {
public: typeforce.UInt32,
private: typeforce.UInt32
},
pubKeyHash: typeforce.UInt8,
scriptHash: typeforce.UInt8,
wif: typeforce.UInt8
})
// extend typeforce types with ours
const types = {
BIP32Path: BIP32Path,
Buffer256bit: typeforce.BufferN(32),
ECPoint: ECPoint,
Hash160bit: typeforce.BufferN(20),
Hash256bit: typeforce.BufferN(32),
Network: Network,
Satoshi: Satoshi,
UInt31: UInt31
}
for (var typeName in typeforce) {
types[typeName] = typeforce[typeName]
}
module.exports = types
exports.Network = typeforce.compile({
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
bip32: {
public: typeforce.UInt32,
private: typeforce.UInt32,
},
pubKeyHash: typeforce.UInt8,
scriptHash: typeforce.UInt8,
wif: typeforce.UInt8,
});
exports.Buffer256bit = typeforce.BufferN(32);
exports.Hash160bit = typeforce.BufferN(20);
exports.Hash256bit = typeforce.BufferN(32);
exports.Number = typeforce.Number; // tslint:disable-line variable-name
exports.Array = typeforce.Array;
exports.Boolean = typeforce.Boolean; // tslint:disable-line variable-name
exports.String = typeforce.String; // tslint:disable-line variable-name
exports.Buffer = typeforce.Buffer;
exports.Hex = typeforce.Hex;
exports.maybe = typeforce.maybe;
exports.tuple = typeforce.tuple;
exports.UInt8 = typeforce.UInt8;
exports.UInt32 = typeforce.UInt32;
exports.Function = typeforce.Function;
exports.BufferN = typeforce.BufferN;
exports.Null = typeforce.Null;
exports.oneOf = typeforce.oneOf;

15
test/block.js

@ -1,6 +1,6 @@
const { describe, it, beforeEach } = require('mocha')
const assert = require('assert')
const Block = require('../src/block')
const Block = require('..').Block
const fixtures = require('./fixtures/block')
@ -32,6 +32,9 @@ describe('Block', function () {
assert.strictEqual(block.version, f.version)
assert.strictEqual(block.prevHash.toString('hex'), f.prevHash)
assert.strictEqual(block.merkleRoot.toString('hex'), f.merkleRoot)
if (block.witnessCommit) {
assert.strictEqual(block.witnessCommit.toString('hex'), f.witnessCommit)
}
assert.strictEqual(block.timestamp, f.timestamp)
assert.strictEqual(block.bits, f.bits)
assert.strictEqual(block.nonce, f.nonce)
@ -113,10 +116,16 @@ describe('Block', function () {
it('returns ' + f.merkleRoot + ' for ' + f.id, function () {
assert.strictEqual(Block.calculateMerkleRoot(block.transactions).toString('hex'), f.merkleRoot)
})
if (f.witnessCommit) {
it('returns witness commit ' + f.witnessCommit + ' for ' + f.id, function () {
assert.strictEqual(Block.calculateMerkleRoot(block.transactions, true).toString('hex'), f.witnessCommit)
})
}
})
})
describe('checkMerkleRoot', function () {
describe('checkTxRoots', function () {
fixtures.valid.forEach(function (f) {
if (f.hex.length === 160) return
@ -127,7 +136,7 @@ describe('Block', function () {
})
it('returns ' + f.valid + ' for ' + f.id, function () {
assert.strictEqual(block.checkMerkleRoot(), true)
assert.strictEqual(block.checkTxRoots(), true)
})
})
})

6
test/ecpair.js

@ -1,5 +1,3 @@
/* eslint-disable no-new */
const { describe, it, beforeEach } = require('mocha')
const assert = require('assert')
const proxyquire = require('proxyquire')
@ -30,7 +28,7 @@ describe('ECPair', function () {
})
it('calls pointFromScalar lazily', hoodwink(function () {
assert.strictEqual(keyPair.__Q, null)
assert.strictEqual(keyPair.__Q, undefined)
// .publicKey forces the memoization
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
@ -240,7 +238,7 @@ describe('ECPair', function () {
}))
it('throws if no private key is found', function () {
delete keyPair.__d
delete keyPair.__D
assert.throws(function () {
keyPair.sign(hash)

15
test/fixtures/block.json

@ -119,6 +119,21 @@
"timestamp": 1231006505,
"valid": true,
"version": 1
},
{
"description": "Block with witness commit",
"bits": 388503969,
"hash": "ec61d8d62a4945034a1df40dd3dfda364221c0562c3a14000000000000000000",
"height": 542213,
"hex": "000000208980ebb11236bacc66c447d5ad961bc546c0f9cc385a08000000000000000000135e9b653aee9a70028aff2db53d4f43f4484ad52558c3ae300410b79cb6a864df50a35ba11928171396e30104010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5003054608174d696e656420627920416e74506f6f6c393b205ba350dffabe6d6d3a3e92d9efff857664de632e89fa3182f1e793d00be2e71a117b273145945a810400000000000000db250000acba0500ffffffff028a8f814a000000001976a914edf10a7fac6b32e24daa5305c723f3de58db1bc888ac0000000000000000266a24aa21a9ed4a657fcaa2149342376247e2e283a55a6b92dcc35d4d89e4ac7b74488cb63be201200000000000000000000000000000000000000000000000000000000000000000000000000100000001b898273f98d49399ecb5194ffdb1ed15c2fb37cf6d7696b4389bc7d1b76b63db010000006b483045022100e2e9bc1f6bae2deed086e935bb49fd6ac1e13dc3a44c36cd8b9a6f4257efb70d022076537c7021f12d761e1202796029f13798503bc22ab8c2ee8cb98207cbfeb414012102071c2c88e4560b47a03c033c736149a2ddd6071aea54ab85c5169cee156712f8ffffffff02b0710b00000000001976a914849a95fc65eeaa2ac47b6b6fc1f1883edb2c6c9788ace6b62501000000001976a9142964198f7ae9f7b920a2ab7c0b96b90e4ec9b14d88ac000000000100000000010152405e2660055b3540f63424a1b0b3b7bb9bbef10ceec970cd18f6f86f84a7880000000017160014dde2f1a9a4bfda011ba9ec4062990c7e1a531585ffffffff0266d418000000000017a914adc5ec550548f087371e645047170864d5fbdc03877e0400000000000016001441f6746110cc0fec102e83053d4c0ae56fab1bdd02483045022100f93bd5d3529418f60dddd477f169c32ea5ab340c99573438ee7c40d705db9ba20220323f1b4d8840098b1271c95365cdc7e8f81d0bf1fcc568c68fc7de8b871b4bee012103211d047d92547bca4aa116bc51ec4b09188e5991f69f0432fe1b5dfd8947859700000000010000000ac1a19854e5b92792ae96d08eb9f7fc016fb57c51a9047161c342e6b8de8ce721000000006b483045022100fa00a6651015ac807b03d8d54559831db40d80329f46a886b93ca6b3996daf9b02201d0fafccc88c4654a9c3d205ef0828e32376ecb42f79587615203f23fc8f2d42012102a2cda42f6954605e40cbc5601f65673621c057f8c12f16149fbbf632e8be8ed8ffffffffda16ff4a7324f78f16d5e5d4a740168de9df426cf53df569c9a33d1b94e2c25f000000006a4730440220167b4777a23db304f50ac75febfae5db0b1578c90d85769bc76e0f5458484319022023f763e95ea7771c15ea0df91c17519014b2223cd726a3b0a8b1a6f67f99b2bf012103b9492a823f03b70a1750e0fac44f58f5c81e09f4c18d0b28755b44c911ffab5ffffffffff1533f2ea34b859d2be05bbb6859d6105ad4389c911545487187437acae21b70000000006b483045022100be0b8dc174f4136e3fb4f3ccf1a2be67a44d2086179c5726c61a1af010d5232f02205c0fb4cdd7c9cb8698ceed05e688e10a0cbdd0ee5db1a0a2ef92f322e1a60e9f0121020b9f404317cc6ab5a699f607a9bb0acd0bf5588777672f8f7f4c1a13304e9f76ffffffff1395191837aa5b1cfa4cc2a79d6d6bda7d198e8a795a0f7a85f556c446119572000000006b483045022100ef3c61b5ba155b94fd02a96bf788e9a9be2de0ee53e3fe1fa6c24dd9d9aaee12022044c07be54a9c59fed96dfb778da0079f6753ab634d1920bf0fac25ebf7ac49d0012103f93e29be8b393773228f704151964662a91df4c1a3e15364e4cfb38a8cacbda9ffffffff55120f684d5fbb845fd0198cd734e46fc29f40dc8f906bf36743d7f740f5fd7c000000006a4730440220421aef290bbd39a18d1281ecfbb420b43daf2cc1c315b6bb44c895f88cb3cfcc022007a512c65b7768b1505f789b6d78479063d04ffb243072f2061eeb66fb1ade81012102712eea19c72fd644b2698c5480ebe77ce6face0bed64238a34a32085567f2f8efffffffff3553d19ce3156464e9cfa06a5260f9d9d01b16ccad6e2ebaab233ba10472ba6000000006b483045022100a29aea775d2c46028f40dceb1707b23e720bb314cef455854313569200c83162022009d0cdcef2e1f0a9217794991fa838fe6ce2bdc6e3c04ad490343c7e4a0ee7d3012102ed59ec6d98f9c2a4dd1324d46d74c56ff7e15935925f69799e547969a523ea98ffffffff530ab6d95f0d83669bfb12cbe983febe6ca638255139ce2ba0e35887f2fe3db6000000006b483045022100fad9f10989ab4ab019da4f6e430f9fe9ebe76941a9200d7346447358e93b1dce0220756c864b029a64ed9d6eedc13cf8b7d602572fcd7a3d3cb528350bb032d7e3ec012102e3e0c78d034627b1616cf196aa69bfaf20009ba9ebf09cf069453cc0423239e2ffffffff4b8e70824941d965df99703cacadfabcf79bc040029e528ff931e9ba4a7ee4d8000000006b48304502210095f37fb2700c9f96d5e5c02d4b043a7cd804f79dd071d8b221b7ae781f0f5b4e02200ae3135b8bebf813449956ae19e7c02db5eff2472da023afa8b8d4e9baba0cdd01210226cc53dfc0a41cc0cf7117dfd406db2b87161e89e2bab9908a2382173fc6dbe1ffffffff5262129e9881722b217f7e4882ed2b83ee53d9e23b9bc647494c9487ae9fabdf000000006a473044022032361f724fe006079cf37b3df61cb6c51cb0c5fd77f29b61a318134ca4948d0e02202fbe6d484a78730899230244f3662fd5578b87e9eaaccdf9bf8f05d23917de8301210254e84223b3d7f7cfd14315be8fa0c7d7eb1a1a34a672f08e2b8ec134472a66d4ffffffff067040e03288282ebe5db7b705130849c9984458b13ea7ca3c755f07b9d1b9f4000000006b483045022100b78b9c24f5f3e950aba637b827d5b11615c17ae2f43105941d172cc4a8b73c80022064998c17becd06abbcfde47c91b9d87da5ac67873ed9a412010b08b954e0b5cd01210329a0acc2b0d60dd243eef46073a672ed0caf467c92f63ef7293f2036a3851a1effffffff02027f0000000000001976a914c6a396ae979670eeaa6929df3dd1c2d8fba31c3d88ac85a19b020000000017a914409dbd0e9a1ab27853186367130e6aab2509e47f8700000000",
"id": "000000000000000000143a2c56c0214236dadfd30df41d4a0345492ad6d861ec",
"merkleRoot": "135e9b653aee9a70028aff2db53d4f43f4484ad52558c3ae300410b79cb6a864",
"witnessCommit": "4a657fcaa2149342376247e2e283a55a6b92dcc35d4d89e4ac7b74488cb63be2",
"nonce": 31692307,
"prevHash": "8980ebb11236bacc66c447d5ad961bc546c0f9cc385a08000000000000000000",
"timestamp": 1537429727,
"valid": true,
"version": 536870912
}
],
"invalid": [

2
test/fixtures/transaction_builder.json

@ -1976,7 +1976,7 @@
"sign": [
{
"description": "Transaction w/ witness value mismatch",
"exception": "Input didn\\'t match witnessValue",
"exception": "Input did not match witnessValue",
"network": "testnet",
"inputs": [
{

8
test/payments.js

@ -4,7 +4,13 @@ const u = require('./payments.utils')
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) {
describe(p, function () {
const fn = require('../src/payments/' + p)
let fn
let payment = require('../src/payments/' + p)
if (p === 'embed') {
fn = payment.p2data
} else {
fn = payment[p]
}
const fixtures = require('./fixtures/' + p)
fixtures.valid.forEach(function (f, i) {

2
test/transaction.js

@ -2,7 +2,7 @@ const { describe, it, beforeEach } = require('mocha')
const assert = require('assert')
const bscript = require('../src/script')
const fixtures = require('./fixtures/transaction')
const Transaction = require('../src/transaction')
const Transaction = require('..').Transaction
describe('Transaction', function () {
function fromRaw (raw, noWitness) {

30
test/transaction_builder.js

@ -5,8 +5,8 @@ const bscript = require('../src/script')
const payments = require('../src/payments')
const ECPair = require('../src/ecpair')
const Transaction = require('../src/transaction')
const TransactionBuilder = require('../src/transaction_builder')
const Transaction = require('..').Transaction
const TransactionBuilder = require('..').TransactionBuilder
const NETWORKS = require('../src/networks')
const fixtures = require('./fixtures/transaction_builder')
@ -164,7 +164,7 @@ describe('TransactionBuilder', function () {
const tx = Transaction.fromHex(fixtures.valid.classification.hex)
const txb = TransactionBuilder.fromTransaction(tx)
txb.__inputs.forEach(function (i) {
txb.__INPUTS.forEach(function (i) {
assert.strictEqual(i.prevOutType, 'scripthash')
assert.strictEqual(i.redeemScriptType, 'multisig')
})
@ -191,22 +191,22 @@ describe('TransactionBuilder', function () {
const vin = txb.addInput(txHash, 1, 54)
assert.strictEqual(vin, 0)
const txIn = txb.__tx.ins[0]
const txIn = txb.__TX.ins[0]
assert.strictEqual(txIn.hash, txHash)
assert.strictEqual(txIn.index, 1)
assert.strictEqual(txIn.sequence, 54)
assert.strictEqual(txb.__inputs[0].prevOutScript, undefined)
assert.strictEqual(txb.__INPUTS[0].prevOutScript, undefined)
})
it('accepts a txHash, index [, sequence number and scriptPubKey]', function () {
const vin = txb.addInput(txHash, 1, 54, scripts[1])
assert.strictEqual(vin, 0)
const txIn = txb.__tx.ins[0]
const txIn = txb.__TX.ins[0]
assert.strictEqual(txIn.hash, txHash)
assert.strictEqual(txIn.index, 1)
assert.strictEqual(txIn.sequence, 54)
assert.strictEqual(txb.__inputs[0].prevOutScript, scripts[1])
assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1])
})
it('accepts a prevTx, index [and sequence number]', function () {
@ -217,11 +217,11 @@ describe('TransactionBuilder', function () {
const vin = txb.addInput(prevTx, 1, 54)
assert.strictEqual(vin, 0)
const txIn = txb.__tx.ins[0]
const txIn = txb.__TX.ins[0]
assert.deepEqual(txIn.hash, prevTx.getHash())
assert.strictEqual(txIn.index, 1)
assert.strictEqual(txIn.sequence, 54)
assert.strictEqual(txb.__inputs[0].prevOutScript, scripts[1])
assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1])
})
it('returns the input index', function () {
@ -251,7 +251,7 @@ describe('TransactionBuilder', function () {
const vout = txb.addOutput(address, 1000)
assert.strictEqual(vout, 0)
const txout = txb.__tx.outs[0]
const txout = txb.__TX.outs[0]
assert.deepEqual(txout.script, scripts[0])
assert.strictEqual(txout.value, 1000)
})
@ -260,7 +260,7 @@ describe('TransactionBuilder', function () {
const vout = txb.addOutput(scripts[0], 1000)
assert.strictEqual(vout, 0)
const txout = txb.__tx.outs[0]
const txout = txb.__TX.outs[0]
assert.deepEqual(txout.script, scripts[0])
assert.strictEqual(txout.value, 1000)
})
@ -533,10 +533,10 @@ describe('TransactionBuilder', function () {
'194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' +
'0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000'
const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx))
txb.__inputs[0].value = 241530
txb.__inputs[1].value = 241530
txb.__inputs[2].value = 248920
txb.__inputs[3].value = 248920
txb.__INPUTS[0].value = 241530
txb.__INPUTS[1].value = 241530
txb.__INPUTS[2].value = 248920
txb.__INPUTS[3].value = 248920
assert.throws(function () {
txb.build()

119
ts_src/address.ts

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

285
ts_src/block.ts

@ -0,0 +1,285 @@
import { reverseBuffer } from './bufferutils';
import * as bcrypto from './crypto';
import { Transaction } from './transaction';
import * as types from './types';
const fastMerkleRoot = require('merkle-lib/fastRoot');
const typeforce = require('typeforce');
const varuint = require('varuint-bitcoin');
const errorMerkleNoTxes = new TypeError(
'Cannot compute merkle root for zero transactions',
);
const errorWitnessNotSegwit = new TypeError(
'Cannot compute witness commit for non-segwit block',
);
export class Block {
static fromBuffer(buffer: Buffer): Block {
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)');
let offset: number = 0;
const readSlice = (n: number): Buffer => {
offset += n;
return buffer.slice(offset - n, offset);
};
const readUInt32 = (): number => {
const i = buffer.readUInt32LE(offset);
offset += 4;
return i;
};
const readInt32 = (): number => {
const i = buffer.readInt32LE(offset);
offset += 4;
return i;
};
const block = new Block();
block.version = readInt32();
block.prevHash = readSlice(32);
block.merkleRoot = readSlice(32);
block.timestamp = readUInt32();
block.bits = readUInt32();
block.nonce = readUInt32();
if (buffer.length === 80) return block;
const readVarInt = (): number => {
const vi = varuint.decode(buffer, offset);
offset += varuint.decode.bytes;
return vi;
};
const readTransaction = (): any => {
const tx = Transaction.fromBuffer(buffer.slice(offset), true);
offset += tx.byteLength();
return tx;
};
const nTransactions = readVarInt();
block.transactions = [];
for (let i = 0; i < nTransactions; ++i) {
const tx = readTransaction();
block.transactions.push(tx);
}
const witnessCommit = block.getWitnessCommit();
// This Block contains a witness commit
if (witnessCommit) block.witnessCommit = witnessCommit;
return block;
}
static fromHex(hex: string): Block {
return Block.fromBuffer(Buffer.from(hex, 'hex'));
}
static calculateTarget(bits: number): Buffer {
const exponent = ((bits & 0xff000000) >> 24) - 3;
const mantissa = bits & 0x007fffff;
const target = Buffer.alloc(32, 0);
target.writeUIntBE(mantissa, 29 - exponent, 3);
return target;
}
static calculateMerkleRoot(
transactions: Transaction[],
forWitness?: boolean,
): 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 rootHash = fastMerkleRoot(hashes, bcrypto.hash256);
return forWitness
? bcrypto.hash256(
Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]),
)
: rootHash;
}
version: number = 1;
prevHash?: Buffer = undefined;
merkleRoot?: Buffer = undefined;
timestamp: number = 0;
witnessCommit?: Buffer = undefined;
bits: number = 0;
nonce: number = 0;
transactions?: Transaction[] = undefined;
getWitnessCommit(): Buffer | null {
if (!txesHaveWitnessCommit(this.transactions!)) return null;
// The merkle root for the witness data is in an OP_RETURN output.
// There is no rule for the index of the output, so use filter to find it.
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
// If multiple commits are found, the output with highest index is assumed.
const witnessCommits = this.transactions![0].outs.filter(out =>
out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')),
).map(out => out.script.slice(6, 38));
if (witnessCommits.length === 0) return null;
// Use the commit with the highest output (should only be one though)
const result = witnessCommits[witnessCommits.length - 1];
if (!(result instanceof Buffer && result.length === 32)) return null;
return result;
}
hasWitnessCommit(): boolean {
if (
this.witnessCommit instanceof Buffer &&
this.witnessCommit.length === 32
)
return true;
if (this.getWitnessCommit() !== null) return true;
return false;
}
hasWitness(): boolean {
return anyTxHasWitness(this.transactions!);
}
byteLength(headersOnly: boolean): number {
if (headersOnly || !this.transactions) return 80;
return (
80 +
varuint.encodingLength(this.transactions.length) +
this.transactions.reduce((a, x) => a + x.byteLength(), 0)
);
}
getHash(): Buffer {
return bcrypto.hash256(this.toBuffer(true));
}
getId(): string {
return reverseBuffer(this.getHash()).toString('hex');
}
getUTCDate(): Date {
const date = new Date(0); // epoch
date.setUTCSeconds(this.timestamp);
return date;
}
// TODO: buffer, offset compatibility
toBuffer(headersOnly: boolean): Buffer {
const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
let offset: number = 0;
const writeSlice = (slice: Buffer): void => {
slice.copy(buffer, offset);
offset += slice.length;
};
const writeInt32 = (i: number): void => {
buffer.writeInt32LE(i, offset);
offset += 4;
};
const writeUInt32 = (i: number): void => {
buffer.writeUInt32LE(i, offset);
offset += 4;
};
writeInt32(this.version);
writeSlice(this.prevHash!);
writeSlice(this.merkleRoot!);
writeUInt32(this.timestamp);
writeUInt32(this.bits);
writeUInt32(this.nonce);
if (headersOnly || !this.transactions) return buffer;
varuint.encode(this.transactions.length, buffer, offset);
offset += varuint.encode.bytes;
this.transactions.forEach(tx => {
const txSize = tx.byteLength(); // TODO: extract from toBuffer?
tx.toBuffer(buffer, offset);
offset += txSize;
});
return buffer;
}
toHex(headersOnly: boolean): string {
return this.toBuffer(headersOnly).toString('hex');
}
checkTxRoots(): boolean {
// If the Block has segwit transactions but no witness commit,
// there's no way it can be valid, so fail the check.
const hasWitnessCommit = this.hasWitnessCommit();
if (!hasWitnessCommit && this.hasWitness()) return false;
return (
this.__checkMerkleRoot() &&
(hasWitnessCommit ? this.__checkWitnessCommit() : true)
);
}
checkProofOfWork(): boolean {
const hash: Buffer = reverseBuffer(this.getHash());
const target = Block.calculateTarget(this.bits);
return hash.compare(target) <= 0;
}
private __checkMerkleRoot(): boolean {
if (!this.transactions) throw errorMerkleNoTxes;
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
return this.merkleRoot!.compare(actualMerkleRoot) === 0;
}
private __checkWitnessCommit(): boolean {
if (!this.transactions) throw errorMerkleNoTxes;
if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit;
const actualWitnessCommit = Block.calculateMerkleRoot(
this.transactions,
true,
);
return this.witnessCommit!.compare(actualWitnessCommit) === 0;
}
}
function txesHaveWitnessCommit(transactions: Transaction[]): boolean {
return (
transactions instanceof Array &&
transactions[0] &&
transactions[0].ins &&
transactions[0].ins instanceof Array &&
transactions[0].ins[0] &&
transactions[0].ins[0].witness &&
transactions[0].ins[0].witness instanceof Array &&
transactions[0].ins[0].witness.length > 0
);
}
function anyTxHasWitness(transactions: Transaction[]): boolean {
return (
transactions instanceof Array &&
transactions.some(
tx =>
typeof tx === 'object' &&
tx.ins instanceof Array &&
tx.ins.some(
input =>
typeof input === 'object' &&
input.witness instanceof Array &&
input.witness.length > 0,
),
)
);
}

44
ts_src/bufferutils.ts

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

71
ts_src/classify.ts

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

33
ts_src/crypto.ts

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

131
ts_src/ecpair.ts

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

20
ts_src/index.ts

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

49
ts_src/networks.ts

@ -0,0 +1,49 @@
// https://en.bitcoin.it/wiki/List_of_address_prefixes
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
export interface Network {
messagePrefix: string;
bech32: string;
bip32: Bip32;
pubKeyHash: number;
scriptHash: number;
wif: number;
}
interface Bip32 {
public: number;
private: number;
}
export const bitcoin: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
};
export const regtest: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
};
export const testnet: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
};

58
ts_src/payments/embed.ts

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

41
ts_src/payments/index.ts

@ -0,0 +1,41 @@
import { Network } from '../networks';
import { p2data as embed } from './embed';
import { p2ms } from './p2ms';
import { p2pk } from './p2pk';
import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh';
export interface Payment {
network?: Network;
output?: Buffer;
data?: Buffer[];
m?: number;
n?: number;
pubkeys?: Buffer[];
input?: Buffer;
signatures?: Buffer[];
pubkey?: Buffer;
signature?: Buffer;
address?: string;
hash?: Buffer;
redeem?: Payment;
witness?: Buffer[];
}
export type PaymentFunction = () => Payment;
export interface PaymentOpts {
validate?: boolean;
allowIncomplete?: boolean;
}
export type StackElement = Buffer | number;
export type Stack = StackElement[];
export type StackFunction = () => Stack;
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
// TODO
// witness commitment

28
ts_src/payments/lazy.ts

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

158
ts_src/payments/p2ms.ts

@ -0,0 +1,158 @@
import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script';
import { Payment, PaymentOpts, Stack } from './index';
import * as lazy from './lazy';
const OPS = bscript.OPS;
const typef = require('typeforce');
const ecc = require('tiny-secp256k1');
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: OP_0 [signatures ...]
// output: m [pubKeys ...] n OP_CHECKMULTISIG
export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
if (
!a.input &&
!a.output &&
!(a.pubkeys && a.m !== undefined) &&
!a.signatures
)
throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {});
function isAcceptableSignature(x: Buffer | number): boolean {
return (
bscript.isCanonicalScriptSignature(x as Buffer) ||
(opts!.allowIncomplete && (x as number) === OPS.OP_0) !== undefined
);
}
typef(
{
network: typef.maybe(typef.Object),
m: typef.maybe(typef.Number),
n: typef.maybe(typef.Number),
output: typef.maybe(typef.Buffer),
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
input: typef.maybe(typef.Buffer),
},
a,
);
const network = a.network || BITCOIN_NETWORK;
const o: Payment = { network };
let chunks: Stack = [];
let decoded = false;
function decode(output: Buffer | Stack): void {
if (decoded) return;
decoded = true;
chunks = bscript.decompile(output) as Stack;
o.m = (chunks[0] as number) - OP_INT_BASE;
o.n = (chunks[chunks.length - 2] as number) - OP_INT_BASE;
o.pubkeys = chunks.slice(1, -2) as Buffer[];
}
lazy.prop(o, 'output', () => {
if (!a.m) return;
if (!o.n) return;
if (!a.pubkeys) return;
return bscript.compile(
([] as Stack).concat(
OP_INT_BASE + a.m,
a.pubkeys,
OP_INT_BASE + o.n,
OPS.OP_CHECKMULTISIG,
),
);
});
lazy.prop(o, 'm', () => {
if (!o.output) return;
decode(o.output);
return o.m;
});
lazy.prop(o, 'n', () => {
if (!o.pubkeys) return;
return o.pubkeys.length;
});
lazy.prop(o, 'pubkeys', () => {
if (!a.output) return;
decode(a.output);
return o.pubkeys;
});
lazy.prop(o, 'signatures', () => {
if (!a.input) return;
return bscript.decompile(a.input)!.slice(1);
});
lazy.prop(o, 'input', () => {
if (!a.signatures) return;
return bscript.compile(([OPS.OP_0] as Stack).concat(a.signatures));
});
lazy.prop(o, 'witness', () => {
if (!o.input) return;
return [];
});
// extended validation
if (opts.validate) {
if (a.output) {
decode(a.output);
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 (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
throw new TypeError('Output is invalid');
if (o.m! <= 0 || o.n! > 16 || o.m! > o.n! || o.n !== chunks.length - 3)
throw new TypeError('Output is invalid');
if (!o.pubkeys!.every(x => ecc.isPoint(x)))
throw new TypeError('Output is invalid');
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.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys!))
throw new TypeError('Pubkeys mismatch');
}
if (a.pubkeys) {
if (a.n !== undefined && a.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 (a.signatures) {
if (a.signatures.length < o.m!)
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[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 (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);
}

80
ts_src/payments/p2pk.ts

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

147
ts_src/payments/p2pkh.ts

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

213
ts_src/payments/p2sh.ts

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

142
ts_src/payments/p2wpkh.ts

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

198
ts_src/payments/p2wsh.ts

@ -0,0 +1,198 @@
import * as bcrypto from '../crypto';
import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script';
import { Payment, PaymentOpts, StackFunction } from './index';
import * as lazy from './lazy';
const typef = require('typeforce');
const OPS = bscript.OPS;
const bech32 = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0);
function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: <>
// witness: [redeemScriptSig ...] {redeemScript}
// output: OP_0 {sha256(redeemScript)}
export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
if (!a.address && !a.hash && !a.output && !a.redeem && !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),
output: typef.maybe(typef.Buffer),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
}),
input: typef.maybe(typef.BufferN(0)),
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
},
a,
);
const _address = lazy.value(() => {
const result = bech32.decode(a.address);
const version = result.words.shift();
const data = bech32.fromWords(result.words);
return {
version,
prefix: result.prefix,
data: Buffer.from(data),
};
});
const _rchunks = lazy.value(() => {
return bscript.decompile(a.redeem!.input!);
}) as StackFunction;
let network = a.network;
if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
}
const o: Payment = { network };
lazy.prop(o, 'address', () => {
if (!o.hash) return;
const words = bech32.toWords(o.hash);
words.unshift(0x00);
return bech32.encode(network!.bech32, words);
});
lazy.prop(o, 'hash', () => {
if (a.output) return a.output.slice(2);
if (a.address) return _address().data;
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output);
});
lazy.prop(o, 'output', () => {
if (!o.hash) return;
return bscript.compile([OPS.OP_0, o.hash]);
});
lazy.prop(o, 'redeem', () => {
if (!a.witness) return;
return {
output: a.witness[a.witness.length - 1],
input: EMPTY_BUFFER,
witness: a.witness.slice(0, -1),
};
});
lazy.prop(o, 'input', () => {
if (!o.witness) return;
return EMPTY_BUFFER;
});
lazy.prop(o, 'witness', () => {
// transform redeem input to witness stack?
if (
a.redeem &&
a.redeem.input &&
a.redeem.input.length > 0 &&
a.redeem.output &&
a.redeem.output.length > 0
) {
const stack = bscript.toStack(_rchunks());
// assign, and blank the existing input
o.redeem = Object.assign({ witness: stack }, a.redeem);
o.redeem.input = EMPTY_BUFFER;
return ([] as Buffer[]).concat(stack, a.redeem.output);
}
if (!a.redeem) return;
if (!a.redeem.output) return;
if (!a.redeem.witness) return;
return ([] as Buffer[]).concat(a.redeem.witness, a.redeem.output);
});
// extended validation
if (opts.validate) {
let hash: Buffer = Buffer.from([]);
if (a.address) {
if (_address().prefix !== network.bech32)
throw new TypeError('Invalid prefix or Network mismatch');
if (_address().version !== 0x00)
throw new TypeError('Invalid address version');
if (_address().data.length !== 32)
throw new TypeError('Invalid address data');
hash = _address().data;
}
if (a.hash) {
if (hash.length > 0 && !hash.equals(a.hash))
throw new TypeError('Hash mismatch');
else hash = a.hash;
}
if (a.output) {
if (
a.output.length !== 34 ||
a.output[0] !== OPS.OP_0 ||
a.output[1] !== 0x20
)
throw new TypeError('Output is invalid');
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.network && a.redeem.network !== network)
throw new TypeError('Network mismatch');
// is there two redeem sources?
if (
a.redeem.input &&
a.redeem.input.length > 0 &&
a.redeem.witness &&
a.redeem.witness.length > 0
)
throw new TypeError('Ambiguous witness source');
// is the redeem output non-empty?
if (a.redeem.output) {
if (bscript.decompile(a.redeem.output)!.length === 0)
throw new TypeError('Redeem.output is invalid');
// match hash against other sources
const hash2 = bcrypto.sha256(a.redeem.output);
if (hash.length > 0 && !hash.equals(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.witness &&
a.redeem.witness &&
!stacksEqual(a.witness, a.redeem.witness)
)
throw new TypeError('Witness and redeem.witness mismatch');
}
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');
}
}
return Object.assign(o, a);
}

216
ts_src/script.ts

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

71
ts_src/script_number.ts

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

64
ts_src/script_signature.ts

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

4
ts_src/templates/multisig/index.ts

@ -0,0 +1,4 @@
import * as input from './input';
import * as output from './output';
export { input, output };

31
ts_src/templates/multisig/input.ts

@ -0,0 +1,31 @@
// OP_0 [signatures ...]
import { Stack } from '../../payments';
import * as bscript from '../../script';
import { OPS } from '../../script';
function partialSignature(value: number | Buffer): boolean {
return (
value === OPS.OP_0 || bscript.isCanonicalScriptSignature(value as Buffer)
);
}
export function check(
script: Buffer | Stack,
allowIncomplete?: boolean,
): boolean {
const chunks = bscript.decompile(script) as Stack;
if (chunks.length < 2) return false;
if (chunks[0] !== OPS.OP_0) return false;
if (allowIncomplete) {
return chunks.slice(1).every(partialSignature);
}
return (chunks.slice(1) as Buffer[]).every(
bscript.isCanonicalScriptSignature,
);
}
check.toJSON = (): string => {
return 'multisig input';
};

33
ts_src/templates/multisig/output.ts

@ -0,0 +1,33 @@
// m [pubKeys ...] n OP_CHECKMULTISIG
import { Stack } from '../../payments';
import * as bscript from '../../script';
import { OPS } from '../../script';
import * as types from '../../types';
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
export function check(
script: Buffer | Stack,
allowIncomplete?: boolean,
): boolean {
const chunks = bscript.decompile(script) as Stack;
if (chunks.length < 4) return false;
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false;
if (!types.Number(chunks[0])) return false;
if (!types.Number(chunks[chunks.length - 2])) return false;
const m = (chunks[0] as number) - OP_INT_BASE;
const n = (chunks[chunks.length - 2] as number) - OP_INT_BASE;
if (m <= 0) return false;
if (n > 16) return false;
if (m > n) return false;
if (n !== chunks.length - 3) return false;
if (allowIncomplete) return true;
const keys = chunks.slice(1, -2) as Buffer[];
return keys.every(bscript.isCanonicalPubKey);
}
check.toJSON = (): string => {
return 'multi-sig output';
};

16
ts_src/templates/nulldata.ts

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

4
ts_src/templates/pubkey/index.ts

@ -0,0 +1,4 @@
import * as input from './input';
import * as output from './output';
export { input, output };

16
ts_src/templates/pubkey/input.ts

@ -0,0 +1,16 @@
// {signature}
import { Stack } from '../../payments';
import * as bscript from '../../script';
export function check(script: Buffer | Stack): boolean {
const chunks = bscript.decompile(script) as Stack;
return (
chunks.length === 1 &&
bscript.isCanonicalScriptSignature(chunks[0] as Buffer)
);
}
check.toJSON = (): string => {
return 'pubKey input';
};

18
ts_src/templates/pubkey/output.ts

@ -0,0 +1,18 @@
// {pubKey} OP_CHECKSIG
import { Stack } from '../../payments';
import * as bscript from '../../script';
import { OPS } from '../../script';
export function check(script: Buffer | Stack): boolean {
const chunks = bscript.decompile(script) as Stack;
return (
chunks.length === 2 &&
bscript.isCanonicalPubKey(chunks[0] as Buffer) &&
chunks[1] === OPS.OP_CHECKSIG
);
}
check.toJSON = (): string => {
return 'pubKey output';
};

4
ts_src/templates/pubkeyhash/index.ts

@ -0,0 +1,4 @@
import * as input from './input';
import * as output from './output';
export { input, output };

17
ts_src/templates/pubkeyhash/input.ts

@ -0,0 +1,17 @@
// {signature} {pubKey}
import { Stack } from '../../payments';
import * as bscript from '../../script';
export function check(script: Buffer | Stack): boolean {
const chunks = bscript.decompile(script) as Stack;
return (
chunks.length === 2 &&
bscript.isCanonicalScriptSignature(chunks[0] as Buffer) &&
bscript.isCanonicalPubKey(chunks[1] as Buffer)
);
}
check.toJSON = (): string => {
return 'pubKeyHash input';
};

20
ts_src/templates/pubkeyhash/output.ts

@ -0,0 +1,20 @@
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
import * as bscript from '../../script';
import { OPS } from '../../script';
export function check(script: Buffer | Array<number | Buffer>): boolean {
const buffer = bscript.compile(script);
return (
buffer.length === 25 &&
buffer[0] === OPS.OP_DUP &&
buffer[1] === OPS.OP_HASH160 &&
buffer[2] === 0x14 &&
buffer[23] === OPS.OP_EQUALVERIFY &&
buffer[24] === OPS.OP_CHECKSIG
);
}
check.toJSON = (): string => {
return 'pubKeyHash output';
};

4
ts_src/templates/scripthash/index.ts

@ -0,0 +1,4 @@
import * as input from './input';
import * as output from './output';
export { input, output };

61
ts_src/templates/scripthash/input.ts

@ -0,0 +1,61 @@
// <scriptSig> {serialized scriptPubKey script}
import * as bscript from '../../script';
import * as p2ms from '../multisig';
import * as p2pk from '../pubkey';
import * as p2pkh from '../pubkeyhash';
import * as p2wpkho from '../witnesspubkeyhash/output';
import * as p2wsho from '../witnessscripthash/output';
export function check(
script: Buffer | Array<number | Buffer>,
allowIncomplete?: boolean,
): boolean {
const chunks = bscript.decompile(script)!;
if (chunks.length < 1) return false;
const lastChunk = chunks[chunks.length - 1];
if (!Buffer.isBuffer(lastChunk)) return false;
const scriptSigChunks = bscript.decompile(
bscript.compile(chunks.slice(0, -1)),
)!;
const redeemScriptChunks = bscript.decompile(lastChunk);
// is redeemScript a valid script?
if (!redeemScriptChunks) return false;
// is redeemScriptSig push only?
if (!bscript.isPushOnly(scriptSigChunks)) return false;
// is witness?
if (chunks.length === 1) {
return (
p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)
);
}
// match types
if (
p2pkh.input.check(scriptSigChunks) &&
p2pkh.output.check(redeemScriptChunks)
)
return true;
if (
p2ms.input.check(scriptSigChunks, allowIncomplete) &&
p2ms.output.check(redeemScriptChunks)
)
return true;
if (
p2pk.input.check(scriptSigChunks) &&
p2pk.output.check(redeemScriptChunks)
)
return true;
return false;
}
check.toJSON = (): string => {
return 'scriptHash input';
};

18
ts_src/templates/scripthash/output.ts

@ -0,0 +1,18 @@
// OP_HASH160 {scriptHash} OP_EQUAL
import * as bscript from '../../script';
import { OPS } from '../../script';
export function check(script: Buffer | Array<number | Buffer>): boolean {
const buffer = bscript.compile(script);
return (
buffer.length === 23 &&
buffer[0] === OPS.OP_HASH160 &&
buffer[1] === 0x14 &&
buffer[22] === OPS.OP_EQUAL
);
}
check.toJSON = (): string => {
return 'scriptHash output';
};

3
ts_src/templates/witnesscommitment/index.ts

@ -0,0 +1,3 @@
import * as output from './output';
export { output };

40
ts_src/templates/witnesscommitment/output.ts

@ -0,0 +1,40 @@
// OP_RETURN {aa21a9ed} {commitment}
import * as bscript from '../../script';
import { OPS } from '../../script';
import * as types from '../../types';
const typeforce = require('typeforce');
const HEADER: Buffer = Buffer.from('aa21a9ed', 'hex');
export function check(script: Buffer | Array<number | Buffer>): boolean {
const buffer = bscript.compile(script);
return (
buffer.length > 37 &&
buffer[0] === OPS.OP_RETURN &&
buffer[1] === 0x24 &&
buffer.slice(2, 6).equals(HEADER)
);
}
check.toJSON = (): string => {
return 'Witness commitment output';
};
export function encode(commitment: Buffer): Buffer {
typeforce(types.Hash256bit, commitment);
const buffer = Buffer.allocUnsafe(36);
HEADER.copy(buffer, 0);
commitment.copy(buffer, 4);
return bscript.compile([OPS.OP_RETURN, buffer]);
}
export function decode(buffer: Buffer): Buffer {
typeforce(check, buffer);
return (bscript.decompile(buffer)![1] as Buffer).slice(4, 36);
}

4
ts_src/templates/witnesspubkeyhash/index.ts

@ -0,0 +1,4 @@
import * as input from './input';
import * as output from './output';
export { input, output };

21
ts_src/templates/witnesspubkeyhash/input.ts

@ -0,0 +1,21 @@
// {signature} {pubKey}
import { Stack } from '../../payments';
import * as bscript from '../../script';
function isCompressedCanonicalPubKey(pubKey: Buffer): boolean {
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33;
}
export function check(script: Buffer | Stack): boolean {
const chunks = bscript.decompile(script) as Stack;
return (
chunks.length === 2 &&
bscript.isCanonicalScriptSignature(chunks[0] as Buffer) &&
isCompressedCanonicalPubKey(chunks[1] as Buffer)
);
}
check.toJSON = (): string => {
return 'witnessPubKeyHash input';
};

13
ts_src/templates/witnesspubkeyhash/output.ts

@ -0,0 +1,13 @@
// OP_0 {pubKeyHash}
import * as bscript from '../../script';
import { OPS } from '../../script';
export function check(script: Buffer | Array<number | Buffer>): boolean {
const buffer = bscript.compile(script);
return buffer.length === 22 && buffer[0] === OPS.OP_0 && buffer[1] === 0x14;
}
check.toJSON = (): string => {
return 'Witness pubKeyHash output';
};

4
ts_src/templates/witnessscripthash/index.ts

@ -0,0 +1,4 @@
import * as input from './input';
import * as output from './output';
export { input, output };

47
ts_src/templates/witnessscripthash/input.ts

@ -0,0 +1,47 @@
// <scriptSig> {serialized scriptPubKey script}
import * as bscript from '../../script';
const typeforce = require('typeforce');
import * as p2ms from '../multisig';
import * as p2pk from '../pubkey';
import * as p2pkh from '../pubkeyhash';
export function check(chunks: Buffer[], allowIncomplete?: boolean): boolean {
typeforce(typeforce.Array, chunks);
if (chunks.length < 1) return false;
const witnessScript = chunks[chunks.length - 1];
if (!Buffer.isBuffer(witnessScript)) return false;
const witnessScriptChunks = bscript.decompile(witnessScript);
// is witnessScript a valid script?
if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false;
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1));
// match types
if (
p2pkh.input.check(witnessRawScriptSig) &&
p2pkh.output.check(witnessScriptChunks)
)
return true;
if (
p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
p2ms.output.check(witnessScriptChunks)
)
return true;
if (
p2pk.input.check(witnessRawScriptSig) &&
p2pk.output.check(witnessScriptChunks)
)
return true;
return false;
}
check.toJSON = (): string => {
return 'witnessScriptHash input';
};

13
ts_src/templates/witnessscripthash/output.ts

@ -0,0 +1,13 @@
// OP_0 {scriptHash}
import * as bscript from '../../script';
import { OPS } from '../../script';
export function check(script: Buffer | Array<number | Buffer>): boolean {
const buffer = bscript.compile(script);
return buffer.length === 34 && buffer[0] === OPS.OP_0 && buffer[1] === 0x20;
}
check.toJSON = (): string => {
return 'Witness scriptHash output';
};

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save