Merge pull request #1319 from bitcoinjs/typeScript
Initial TypeScript implementation
This commit is contained in:
commit
22141b5a10
149 changed files with 8669 additions and 6705 deletions
0
.prettierignore
Normal file
0
.prettierignore
Normal file
4
.prettierrc.json
Normal file
4
.prettierrc.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
|
- "8"
|
||||||
- "lts/*"
|
- "lts/*"
|
||||||
- "9"
|
|
||||||
- "10"
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- node_js: "lts/*"
|
- 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/*"
|
- node_js: "lts/*"
|
||||||
env: TEST_SUITE=coverage
|
env: TEST_SUITE=coverage
|
||||||
env:
|
env:
|
||||||
|
|
12
CHANGELOG.md
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
|
# 4.0.3
|
||||||
__fixed__
|
__fixed__
|
||||||
- Fixed `TransactionBuilder` to require that the Transaction has outputs before signing (#1151)
|
- Fixed `TransactionBuilder` to require that the Transaction has outputs before signing (#1151)
|
||||||
|
|
|
@ -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`.
|
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
|
## 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.
|
Bitcoin script payment/script templates are based on community consensus, but typically adhere to bitcoin-core node policy by default.
|
||||||
|
|
||||||
|
|
31
README.md
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)
|
[![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)
|
[![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).
|
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,
|
- Easy to audit and verify,
|
||||||
- Tested, with test coverage >95%,
|
- Tested, with test coverage >95%,
|
||||||
- Advanced and feature rich,
|
- 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.
|
- 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.
|
**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
|
### 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.
|
Type declarations for Typescript are included in this library. Normal installation should include all the needed type information.
|
||||||
|
|
||||||
``` 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.
|
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
The below examples are implemented as integration tests, they should be very easy to understand.
|
The below examples are implemented as integration tests, they should be very easy to understand.
|
||||||
|
|
3792
package-lock.json
generated
3792
package-lock.json
generated
File diff suppressed because it is too large
Load diff
40
package.json
40
package.json
|
@ -1,8 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "bitcoinjs-lib",
|
"name": "bitcoinjs-lib",
|
||||||
"version": "4.0.3",
|
"version": "5.0.0",
|
||||||
"description": "Client-side Bitcoin JavaScript library",
|
"description": "Client-side Bitcoin JavaScript library",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
},
|
},
|
||||||
|
@ -14,24 +15,36 @@
|
||||||
"bitcoinjs"
|
"bitcoinjs"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"coverage-report": "nyc report --reporter=lcov",
|
"build": "tsc -p ./tsconfig.json",
|
||||||
"coverage-html": "nyc report --reporter=html",
|
"coverage-report": "npm run build && npm run nobuild:coverage-report",
|
||||||
"coverage": "nyc --check-coverage --branches 90 --functions 90 mocha",
|
"coverage-html": "npm run build && npm run nobuild:coverage-html",
|
||||||
"integration": "mocha --timeout 50000 test/integration/",
|
"coverage": "npm run build && npm run nobuild:coverage",
|
||||||
"standard": "standard",
|
"format": "npm run prettier -- --write",
|
||||||
"test": "npm run standard && npm run coverage",
|
"format:ci": "npm run prettier -- --check",
|
||||||
"unit": "mocha"
|
"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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/bitcoinjs/bitcoinjs-lib.git"
|
"url": "https://github.com/bitcoinjs/bitcoinjs-lib.git"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src"
|
"src",
|
||||||
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/node": "10.12.18",
|
||||||
"bech32": "^1.1.2",
|
"bech32": "^1.1.2",
|
||||||
"bip32": "^1.0.0",
|
"bip32": "^2.0.0",
|
||||||
"bip66": "^1.1.0",
|
"bip66": "^1.1.0",
|
||||||
"bitcoin-ops": "^1.4.0",
|
"bitcoin-ops": "^1.4.0",
|
||||||
"bs58check": "^2.0.0",
|
"bs58check": "^2.0.0",
|
||||||
|
@ -40,7 +53,6 @@
|
||||||
"merkle-lib": "^2.0.10",
|
"merkle-lib": "^2.0.10",
|
||||||
"pushdata-bitcoin": "^1.0.1",
|
"pushdata-bitcoin": "^1.0.1",
|
||||||
"randombytes": "^2.0.1",
|
"randombytes": "^2.0.1",
|
||||||
"safe-buffer": "^5.1.1",
|
|
||||||
"tiny-secp256k1": "^1.0.0",
|
"tiny-secp256k1": "^1.0.0",
|
||||||
"typeforce": "^1.11.3",
|
"typeforce": "^1.11.3",
|
||||||
"varuint-bitcoin": "^1.0.4",
|
"varuint-bitcoin": "^1.0.4",
|
||||||
|
@ -56,9 +68,11 @@
|
||||||
"hoodwink": "^2.0.0",
|
"hoodwink": "^2.0.0",
|
||||||
"minimaldata": "^1.0.2",
|
"minimaldata": "^1.0.2",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"nyc": "^11.8.0",
|
"nyc": "^13.3.0",
|
||||||
|
"prettier": "1.16.4",
|
||||||
"proxyquire": "^2.0.1",
|
"proxyquire": "^2.0.1",
|
||||||
"standard": "^11.0.1"
|
"tslint": "5.13.1",
|
||||||
|
"typescript": "3.2.2"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
167
src/address.js
167
src/address.js
|
@ -1,97 +1,100 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
"use strict";
|
||||||
const bech32 = require('bech32')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bs58check = require('bs58check')
|
const networks = require("./networks");
|
||||||
const bscript = require('./script')
|
const payments = require("./payments");
|
||||||
const networks = require('./networks')
|
const bscript = require("./script");
|
||||||
const typeforce = require('typeforce')
|
const types = require("./types");
|
||||||
const types = require('./types')
|
const bech32 = require('bech32');
|
||||||
const payments = require('./payments')
|
const bs58check = require('bs58check');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
function fromBase58Check (address) {
|
function fromBase58Check(address) {
|
||||||
const payload = bs58check.decode(address)
|
const payload = bs58check.decode(address);
|
||||||
|
|
||||||
// TODO: 4.0.0, move to "toOutputScript"
|
// TODO: 4.0.0, move to "toOutputScript"
|
||||||
if (payload.length < 21) throw new TypeError(address + ' is too short')
|
if (payload.length < 21)
|
||||||
if (payload.length > 21) throw new TypeError(address + ' is too long')
|
throw new TypeError(address + ' is too short');
|
||||||
|
if (payload.length > 21)
|
||||||
const version = payload.readUInt8(0)
|
throw new TypeError(address + ' is too long');
|
||||||
const hash = payload.slice(1)
|
const version = payload.readUInt8(0);
|
||||||
|
const hash = payload.slice(1);
|
||||||
return { version: version, hash: hash }
|
return { version, hash };
|
||||||
}
|
}
|
||||||
|
exports.fromBase58Check = fromBase58Check;
|
||||||
function fromBech32 (address) {
|
function fromBech32(address) {
|
||||||
const result = bech32.decode(address)
|
const result = bech32.decode(address);
|
||||||
const data = bech32.fromWords(result.words.slice(1))
|
const data = bech32.fromWords(result.words.slice(1));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: result.words[0],
|
version: result.words[0],
|
||||||
prefix: result.prefix,
|
prefix: result.prefix,
|
||||||
data: Buffer.from(data)
|
data: Buffer.from(data),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
exports.fromBech32 = fromBech32;
|
||||||
function toBase58Check (hash, version) {
|
function toBase58Check(hash, version) {
|
||||||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
|
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
const payload = Buffer.allocUnsafe(21)
|
payload.writeUInt8(version, 0);
|
||||||
payload.writeUInt8(version, 0)
|
hash.copy(payload, 1);
|
||||||
hash.copy(payload, 1)
|
return bs58check.encode(payload);
|
||||||
|
|
||||||
return bs58check.encode(payload)
|
|
||||||
}
|
}
|
||||||
|
exports.toBase58Check = toBase58Check;
|
||||||
function toBech32 (data, version, prefix) {
|
function toBech32(data, version, prefix) {
|
||||||
const words = bech32.toWords(data)
|
const words = bech32.toWords(data);
|
||||||
words.unshift(version)
|
words.unshift(version);
|
||||||
|
return bech32.encode(prefix, words);
|
||||||
return bech32.encode(prefix, words)
|
|
||||||
}
|
}
|
||||||
|
exports.toBech32 = toBech32;
|
||||||
function fromOutputScript (output, network) {
|
function fromOutputScript(output, network) {
|
||||||
network = network || networks.bitcoin
|
// TODO: 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 {
|
try {
|
||||||
decode = fromBase58Check(address)
|
return payments.p2pkh({ output, network }).address;
|
||||||
} catch (e) {}
|
}
|
||||||
|
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 {
|
|
||||||
try {
|
try {
|
||||||
decode = fromBech32(address)
|
return payments.p2sh({ output, network }).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
|
|
||||||
}
|
}
|
||||||
|
catch (e) { }
|
||||||
|
try {
|
||||||
|
return payments.p2wpkh({ output, network }).address;
|
||||||
}
|
}
|
||||||
|
catch (e) { }
|
||||||
|
try {
|
||||||
|
return payments.p2wsh({ output, network }).address;
|
||||||
}
|
}
|
||||||
|
catch (e) { }
|
||||||
throw new Error(address + ' has no matching Script')
|
throw new Error(bscript.toASM(output) + ' has no matching Address');
|
||||||
}
|
}
|
||||||
|
exports.fromOutputScript = fromOutputScript;
|
||||||
module.exports = {
|
function toOutputScript(address, network) {
|
||||||
fromBase58Check: fromBase58Check,
|
network = network || networks.bitcoin;
|
||||||
fromBech32: fromBech32,
|
let decodeBase58;
|
||||||
fromOutputScript: fromOutputScript,
|
let decodeBech32;
|
||||||
toBase58Check: toBase58Check,
|
try {
|
||||||
toBech32: toBech32,
|
decodeBase58 = fromBase58Check(address);
|
||||||
toOutputScript: toOutputScript
|
}
|
||||||
|
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;
|
||||||
|
|
375
src/block.js
375
src/block.js
|
@ -1,177 +1,222 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
"use strict";
|
||||||
const bcrypto = require('./crypto')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const fastMerkleRoot = require('merkle-lib/fastRoot')
|
const bufferutils_1 = require("./bufferutils");
|
||||||
const typeforce = require('typeforce')
|
const bcrypto = require("./crypto");
|
||||||
const types = require('./types')
|
const transaction_1 = require("./transaction");
|
||||||
const varuint = require('varuint-bitcoin')
|
const types = require("./types");
|
||||||
|
const fastMerkleRoot = require('merkle-lib/fastRoot');
|
||||||
const Transaction = require('./transaction')
|
const typeforce = require('typeforce');
|
||||||
|
const varuint = require('varuint-bitcoin');
|
||||||
function Block () {
|
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions');
|
||||||
this.version = 1
|
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block');
|
||||||
this.prevHash = null
|
class Block {
|
||||||
this.merkleRoot = null
|
constructor() {
|
||||||
this.timestamp = 0
|
this.version = 1;
|
||||||
this.bits = 0
|
this.prevHash = undefined;
|
||||||
this.nonce = 0
|
this.merkleRoot = undefined;
|
||||||
}
|
this.timestamp = 0;
|
||||||
|
this.witnessCommit = undefined;
|
||||||
Block.fromBuffer = function (buffer) {
|
this.bits = 0;
|
||||||
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)')
|
this.nonce = 0;
|
||||||
|
this.transactions = undefined;
|
||||||
let offset = 0
|
|
||||||
function readSlice (n) {
|
|
||||||
offset += n
|
|
||||||
return buffer.slice(offset - n, offset)
|
|
||||||
}
|
}
|
||||||
|
static fromBuffer(buffer) {
|
||||||
function readUInt32 () {
|
if (buffer.length < 80)
|
||||||
const i = buffer.readUInt32LE(offset)
|
throw new Error('Buffer too small (< 80 bytes)');
|
||||||
offset += 4
|
let offset = 0;
|
||||||
return i
|
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();
|
||||||
function readInt32 () {
|
// This Block contains a witness commit
|
||||||
const i = buffer.readInt32LE(offset)
|
if (witnessCommit)
|
||||||
offset += 4
|
block.witnessCommit = witnessCommit;
|
||||||
return i
|
return block;
|
||||||
}
|
}
|
||||||
|
static fromHex(hex) {
|
||||||
const block = new Block()
|
return Block.fromBuffer(Buffer.from(hex, 'hex'));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
static calculateTarget(bits) {
|
||||||
function readTransaction () {
|
const exponent = ((bits & 0xff000000) >> 24) - 3;
|
||||||
const tx = Transaction.fromBuffer(buffer.slice(offset), true)
|
const mantissa = bits & 0x007fffff;
|
||||||
offset += tx.byteLength()
|
const target = Buffer.alloc(32, 0);
|
||||||
return tx
|
target.writeUIntBE(mantissa, 29 - exponent, 3);
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
static calculateMerkleRoot(transactions, forWitness) {
|
||||||
const nTransactions = readVarInt()
|
typeforce([{ getHash: types.Function }], transactions);
|
||||||
block.transactions = []
|
if (transactions.length === 0)
|
||||||
|
throw errorMerkleNoTxes;
|
||||||
for (var i = 0; i < nTransactions; ++i) {
|
if (forWitness && !txesHaveWitnessCommit(transactions))
|
||||||
const tx = readTransaction()
|
throw errorWitnessNotSegwit;
|
||||||
block.transactions.push(tx)
|
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() {
|
||||||
return block
|
if (!txesHaveWitnessCommit(this.transactions))
|
||||||
}
|
return null;
|
||||||
|
// The merkle root for the witness data is in an OP_RETURN output.
|
||||||
Block.prototype.byteLength = function (headersOnly) {
|
// There is no rule for the index of the output, so use filter to find it.
|
||||||
if (headersOnly || !this.transactions) return 80
|
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
|
||||||
|
// If multiple commits are found, the output with highest index is assumed.
|
||||||
return 80 + varuint.encodingLength(this.transactions.length) + this.transactions.reduce(function (a, x) {
|
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));
|
||||||
return a + x.byteLength()
|
if (witnessCommits.length === 0)
|
||||||
}, 0)
|
return null;
|
||||||
}
|
// Use the commit with the highest output (should only be one though)
|
||||||
|
const result = witnessCommits[witnessCommits.length - 1];
|
||||||
Block.fromHex = function (hex) {
|
if (!(result instanceof Buffer && result.length === 32))
|
||||||
return Block.fromBuffer(Buffer.from(hex, 'hex'))
|
return null;
|
||||||
}
|
return result;
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
hasWitnessCommit() {
|
||||||
function writeInt32 (i) {
|
if (this.witnessCommit instanceof Buffer &&
|
||||||
buffer.writeInt32LE(i, offset)
|
this.witnessCommit.length === 32)
|
||||||
offset += 4
|
return true;
|
||||||
|
if (this.getWitnessCommit() !== null)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
function writeUInt32 (i) {
|
hasWitness() {
|
||||||
buffer.writeUInt32LE(i, offset)
|
return anyTxHasWitness(this.transactions);
|
||||||
offset += 4
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
exports.Block = Block;
|
||||||
Block.prototype.toHex = function (headersOnly) {
|
function txesHaveWitnessCommit(transactions) {
|
||||||
return this.toBuffer(headersOnly).toString('hex')
|
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) {
|
||||||
Block.calculateTarget = function (bits) {
|
return (transactions instanceof Array &&
|
||||||
const exponent = ((bits & 0xff000000) >> 24) - 3
|
transactions.some(tx => typeof tx === 'object' &&
|
||||||
const mantissa = bits & 0x007fffff
|
tx.ins instanceof Array &&
|
||||||
const target = Buffer.alloc(32, 0)
|
tx.ins.some(input => typeof input === 'object' &&
|
||||||
target.writeUIntBE(mantissa, 29 - exponent, 3)
|
input.witness instanceof Array &&
|
||||||
return target
|
input.witness.length > 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
|
@ -1,29 +1,42 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
||||||
function verifuint (value, max) {
|
function verifuint(value, max) {
|
||||||
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number')
|
if (typeof value !== 'number')
|
||||||
if (value < 0) throw new Error('specified a negative value for writing an unsigned value')
|
throw new Error('cannot write a non-number as a number');
|
||||||
if (value > max) throw new Error('RangeError: value out of range')
|
if (value < 0)
|
||||||
if (Math.floor(value) !== value) throw new Error('value has a fractional component')
|
throw new Error('specified a negative value for writing an unsigned value');
|
||||||
|
if (value > max)
|
||||||
|
throw new Error('RangeError: value out of range');
|
||||||
|
if (Math.floor(value) !== value)
|
||||||
|
throw new Error('value has a fractional component');
|
||||||
}
|
}
|
||||||
|
function readUInt64LE(buffer, offset) {
|
||||||
function readUInt64LE (buffer, offset) {
|
const a = buffer.readUInt32LE(offset);
|
||||||
const a = buffer.readUInt32LE(offset)
|
let b = buffer.readUInt32LE(offset + 4);
|
||||||
let b = buffer.readUInt32LE(offset + 4)
|
b *= 0x100000000;
|
||||||
b *= 0x100000000
|
verifuint(b + a, 0x001fffffffffffff);
|
||||||
|
return b + a;
|
||||||
verifuint(b + a, 0x001fffffffffffff)
|
|
||||||
return b + a
|
|
||||||
}
|
}
|
||||||
|
exports.readUInt64LE = readUInt64LE;
|
||||||
function writeUInt64LE (buffer, value, offset) {
|
function writeUInt64LE(buffer, value, offset) {
|
||||||
verifuint(value, 0x001fffffffffffff)
|
verifuint(value, 0x001fffffffffffff);
|
||||||
|
buffer.writeInt32LE(value & -1, offset);
|
||||||
buffer.writeInt32LE(value & -1, offset)
|
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
|
||||||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4)
|
return offset + 8;
|
||||||
return offset + 8
|
|
||||||
}
|
}
|
||||||
|
exports.writeUInt64LE = writeUInt64LE;
|
||||||
module.exports = {
|
function reverseBuffer(buffer) {
|
||||||
readUInt64LE: readUInt64LE,
|
if (buffer.length < 1)
|
||||||
writeUInt64LE: writeUInt64LE
|
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;
|
||||||
|
|
115
src/classify.js
115
src/classify.js
|
@ -1,13 +1,14 @@
|
||||||
const decompile = require('./script').decompile
|
"use strict";
|
||||||
const multisig = require('./templates/multisig')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const nullData = require('./templates/nulldata')
|
const script_1 = require("./script");
|
||||||
const pubKey = require('./templates/pubkey')
|
const multisig = require("./templates/multisig");
|
||||||
const pubKeyHash = require('./templates/pubkeyhash')
|
const nullData = require("./templates/nulldata");
|
||||||
const scriptHash = require('./templates/scripthash')
|
const pubKey = require("./templates/pubkey");
|
||||||
const witnessPubKeyHash = require('./templates/witnesspubkeyhash')
|
const pubKeyHash = require("./templates/pubkeyhash");
|
||||||
const witnessScriptHash = require('./templates/witnessscripthash')
|
const scriptHash = require("./templates/scripthash");
|
||||||
const witnessCommitment = require('./templates/witnesscommitment')
|
const witnessCommitment = require("./templates/witnesscommitment");
|
||||||
|
const witnessPubKeyHash = require("./templates/witnesspubkeyhash");
|
||||||
|
const witnessScriptHash = require("./templates/witnessscripthash");
|
||||||
const types = {
|
const types = {
|
||||||
P2MS: 'multisig',
|
P2MS: 'multisig',
|
||||||
NONSTANDARD: 'nonstandard',
|
NONSTANDARD: 'nonstandard',
|
||||||
|
@ -17,54 +18,58 @@ const types = {
|
||||||
P2SH: 'scripthash',
|
P2SH: 'scripthash',
|
||||||
P2WPKH: 'witnesspubkeyhash',
|
P2WPKH: 'witnesspubkeyhash',
|
||||||
P2WSH: 'witnessscripthash',
|
P2WSH: 'witnessscripthash',
|
||||||
WITNESS_COMMITMENT: 'witnesscommitment'
|
WITNESS_COMMITMENT: 'witnesscommitment',
|
||||||
}
|
};
|
||||||
|
exports.types = types;
|
||||||
function classifyOutput (script) {
|
function classifyOutput(script) {
|
||||||
if (witnessPubKeyHash.output.check(script)) return types.P2WPKH
|
if (witnessPubKeyHash.output.check(script))
|
||||||
if (witnessScriptHash.output.check(script)) return types.P2WSH
|
return types.P2WPKH;
|
||||||
if (pubKeyHash.output.check(script)) return types.P2PKH
|
if (witnessScriptHash.output.check(script))
|
||||||
if (scriptHash.output.check(script)) return types.P2SH
|
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
|
// XXX: optimization, below functions .decompile before use
|
||||||
const chunks = decompile(script)
|
const chunks = script_1.decompile(script);
|
||||||
if (!chunks) throw new TypeError('Invalid script')
|
if (!chunks)
|
||||||
|
throw new TypeError('Invalid script');
|
||||||
if (multisig.output.check(chunks)) return types.P2MS
|
if (multisig.output.check(chunks))
|
||||||
if (pubKey.output.check(chunks)) return types.P2PK
|
return types.P2MS;
|
||||||
if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT
|
if (pubKey.output.check(chunks))
|
||||||
if (nullData.output.check(chunks)) return types.NULLDATA
|
return types.P2PK;
|
||||||
|
if (witnessCommitment.output.check(chunks))
|
||||||
return types.NONSTANDARD
|
return types.WITNESS_COMMITMENT;
|
||||||
|
if (nullData.output.check(chunks))
|
||||||
|
return types.NULLDATA;
|
||||||
|
return types.NONSTANDARD;
|
||||||
}
|
}
|
||||||
|
exports.output = classifyOutput;
|
||||||
function classifyInput (script, allowIncomplete) {
|
function classifyInput(script, allowIncomplete) {
|
||||||
// XXX: optimization, below functions .decompile before use
|
// XXX: optimization, below functions .decompile before use
|
||||||
const chunks = decompile(script)
|
const chunks = script_1.decompile(script);
|
||||||
if (!chunks) throw new TypeError('Invalid script')
|
if (!chunks)
|
||||||
|
throw new TypeError('Invalid script');
|
||||||
if (pubKeyHash.input.check(chunks)) return types.P2PKH
|
if (pubKeyHash.input.check(chunks))
|
||||||
if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH
|
return types.P2PKH;
|
||||||
if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS
|
if (scriptHash.input.check(chunks, allowIncomplete))
|
||||||
if (pubKey.input.check(chunks)) return types.P2PK
|
return types.P2SH;
|
||||||
|
if (multisig.input.check(chunks, allowIncomplete))
|
||||||
return types.NONSTANDARD
|
return types.P2MS;
|
||||||
|
if (pubKey.input.check(chunks))
|
||||||
|
return types.P2PK;
|
||||||
|
return types.NONSTANDARD;
|
||||||
}
|
}
|
||||||
|
exports.input = classifyInput;
|
||||||
function classifyWitness (script, allowIncomplete) {
|
function classifyWitness(script, allowIncomplete) {
|
||||||
// XXX: optimization, below functions .decompile before use
|
// XXX: optimization, below functions .decompile before use
|
||||||
const chunks = decompile(script)
|
const chunks = script_1.decompile(script);
|
||||||
if (!chunks) throw new TypeError('Invalid script')
|
if (!chunks)
|
||||||
|
throw new TypeError('Invalid script');
|
||||||
if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH
|
if (witnessPubKeyHash.input.check(chunks))
|
||||||
if (witnessScriptHash.input.check(chunks, allowIncomplete)) return types.P2WSH
|
return types.P2WPKH;
|
||||||
|
if (witnessScriptHash.input.check(chunks, allowIncomplete))
|
||||||
return types.NONSTANDARD
|
return types.P2WSH;
|
||||||
}
|
return types.NONSTANDARD;
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
input: classifyInput,
|
|
||||||
output: classifyOutput,
|
|
||||||
witness: classifyWitness,
|
|
||||||
types: types
|
|
||||||
}
|
}
|
||||||
|
exports.witness = classifyWitness;
|
||||||
|
|
|
@ -1,29 +1,36 @@
|
||||||
const createHash = require('create-hash')
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
function ripemd160 (buffer) {
|
const createHash = require('create-hash');
|
||||||
return createHash('rmd160').update(buffer).digest()
|
function ripemd160(buffer) {
|
||||||
|
try {
|
||||||
|
return createHash('rmd160')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return createHash('ripemd160')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
exports.ripemd160 = ripemd160;
|
||||||
function sha1 (buffer) {
|
function sha1(buffer) {
|
||||||
return createHash('sha1').update(buffer).digest()
|
return createHash('sha1')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
}
|
}
|
||||||
|
exports.sha1 = sha1;
|
||||||
function sha256 (buffer) {
|
function sha256(buffer) {
|
||||||
return createHash('sha256').update(buffer).digest()
|
return createHash('sha256')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
}
|
}
|
||||||
|
exports.sha256 = sha256;
|
||||||
function hash160 (buffer) {
|
function hash160(buffer) {
|
||||||
return ripemd160(sha256(buffer))
|
return ripemd160(sha256(buffer));
|
||||||
}
|
}
|
||||||
|
exports.hash160 = hash160;
|
||||||
function hash256 (buffer) {
|
function hash256(buffer) {
|
||||||
return sha256(sha256(buffer))
|
return sha256(sha256(buffer));
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
hash160: hash160,
|
|
||||||
hash256: hash256,
|
|
||||||
ripemd160: ripemd160,
|
|
||||||
sha1: sha1,
|
|
||||||
sha256: sha256
|
|
||||||
}
|
}
|
||||||
|
exports.hash256 = hash256;
|
||||||
|
|
176
src/ecpair.js
176
src/ecpair.js
|
@ -1,106 +1,98 @@
|
||||||
const ecc = require('tiny-secp256k1')
|
"use strict";
|
||||||
const randomBytes = require('randombytes')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const typeforce = require('typeforce')
|
const NETWORKS = require("./networks");
|
||||||
const types = require('./types')
|
const types = require("./types");
|
||||||
const wif = require('wif')
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const randomBytes = require('randombytes');
|
||||||
const NETWORKS = require('./networks')
|
const typeforce = require('typeforce');
|
||||||
|
const wif = require('wif');
|
||||||
const isOptions = typeforce.maybe(typeforce.compile({
|
const isOptions = typeforce.maybe(typeforce.compile({
|
||||||
compressed: types.maybe(types.Boolean),
|
compressed: types.maybe(types.Boolean),
|
||||||
network: types.maybe(types.Network)
|
network: types.maybe(types.Network),
|
||||||
}))
|
}));
|
||||||
|
class ECPair {
|
||||||
function ECPair (d, Q, options) {
|
constructor(__D, __Q, options) {
|
||||||
options = options || {}
|
this.__D = __D;
|
||||||
|
this.__Q = __Q;
|
||||||
this.compressed = options.compressed === undefined ? true : options.compressed
|
if (options === undefined)
|
||||||
this.network = options.network || NETWORKS.bitcoin
|
options = {};
|
||||||
|
this.compressed =
|
||||||
this.__d = d || null
|
options.compressed === undefined ? true : options.compressed;
|
||||||
this.__Q = null
|
this.network = options.network || NETWORKS.bitcoin;
|
||||||
if (Q) this.__Q = ecc.pointCompress(Q, this.compressed)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
function fromPrivateKey(buffer, options) {
|
||||||
Object.defineProperty(ECPair.prototype, 'privateKey', {
|
typeforce(types.Buffer256bit, buffer);
|
||||||
enumerable: false,
|
if (!ecc.isPrivate(buffer))
|
||||||
get: function () { return this.__d }
|
throw new TypeError('Private key not in range [1, n)');
|
||||||
})
|
typeforce(isOptions, options);
|
||||||
|
return new ECPair(buffer, undefined, options);
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
exports.fromPrivateKey = fromPrivateKey;
|
||||||
ECPair.prototype.sign = function (hash) {
|
function fromPublicKey(buffer, options) {
|
||||||
if (!this.__d) throw new Error('Missing private key')
|
typeforce(ecc.isPoint, buffer);
|
||||||
return ecc.sign(hash, this.__d)
|
typeforce(isOptions, options);
|
||||||
|
return new ECPair(undefined, buffer, options);
|
||||||
}
|
}
|
||||||
|
exports.fromPublicKey = fromPublicKey;
|
||||||
ECPair.prototype.verify = function (hash, signature) {
|
function fromWIF(wifString, network) {
|
||||||
return ecc.verify(hash, this.publicKey, signature)
|
const decoded = wif.decode(wifString);
|
||||||
}
|
const version = decoded.version;
|
||||||
|
|
||||||
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?
|
// list of networks?
|
||||||
if (types.Array(network)) {
|
if (types.Array(network)) {
|
||||||
network = network.filter(function (x) {
|
network = network
|
||||||
return version === x.wif
|
.filter((x) => {
|
||||||
}).pop()
|
return version === x.wif;
|
||||||
|
})
|
||||||
if (!network) throw new Error('Unknown network version')
|
.pop();
|
||||||
|
if (!network)
|
||||||
|
throw new Error('Unknown network version');
|
||||||
// otherwise, assume a network object (or default to bitcoin)
|
// otherwise, assume a network object (or default to bitcoin)
|
||||||
} else {
|
|
||||||
network = network || NETWORKS.bitcoin
|
|
||||||
|
|
||||||
if (version !== network.wif) throw new Error('Invalid network version')
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
network = network || NETWORKS.bitcoin;
|
||||||
|
if (version !== network.wif)
|
||||||
|
throw new Error('Invalid network version');
|
||||||
|
}
|
||||||
return fromPrivateKey(decoded.privateKey, {
|
return fromPrivateKey(decoded.privateKey, {
|
||||||
compressed: decoded.compressed,
|
compressed: decoded.compressed,
|
||||||
network: network
|
network: network,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
exports.fromWIF = fromWIF;
|
||||||
function makeRandom (options) {
|
function makeRandom(options) {
|
||||||
typeforce(isOptions, options)
|
typeforce(isOptions, options);
|
||||||
options = options || {}
|
if (options === undefined)
|
||||||
const rng = options.rng || randomBytes
|
options = {};
|
||||||
|
const rng = options.rng || randomBytes;
|
||||||
let d
|
let d;
|
||||||
do {
|
do {
|
||||||
d = rng(32)
|
d = rng(32);
|
||||||
typeforce(types.Buffer256bit, d)
|
typeforce(types.Buffer256bit, d);
|
||||||
} while (!ecc.isPrivate(d))
|
} while (!ecc.isPrivate(d));
|
||||||
|
return fromPrivateKey(d, options);
|
||||||
return fromPrivateKey(d, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
makeRandom,
|
|
||||||
fromPrivateKey,
|
|
||||||
fromPublicKey,
|
|
||||||
fromWIF
|
|
||||||
}
|
}
|
||||||
|
exports.makeRandom = makeRandom;
|
||||||
|
|
40
src/index.js
40
src/index.js
|
@ -1,16 +1,24 @@
|
||||||
const script = require('./script')
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
module.exports = {
|
const bip32 = require("bip32");
|
||||||
Block: require('./block'),
|
exports.bip32 = bip32;
|
||||||
ECPair: require('./ecpair'),
|
const address = require("./address");
|
||||||
Transaction: require('./transaction'),
|
exports.address = address;
|
||||||
TransactionBuilder: require('./transaction_builder'),
|
const crypto = require("./crypto");
|
||||||
|
exports.crypto = crypto;
|
||||||
address: require('./address'),
|
const ECPair = require("./ecpair");
|
||||||
bip32: require('bip32'),
|
exports.ECPair = ECPair;
|
||||||
crypto: require('./crypto'),
|
const networks = require("./networks");
|
||||||
networks: require('./networks'),
|
exports.networks = networks;
|
||||||
opcodes: require('bitcoin-ops'),
|
const payments = require("./payments");
|
||||||
payments: require('./payments'),
|
exports.payments = payments;
|
||||||
script: script
|
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;
|
||||||
|
|
|
@ -1,38 +1,35 @@
|
||||||
// https://en.bitcoin.it/wiki/List_of_address_prefixes
|
"use strict";
|
||||||
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.bitcoin = {
|
||||||
module.exports = {
|
|
||||||
bitcoin: {
|
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'bc',
|
bech32: 'bc',
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x0488b21e,
|
public: 0x0488b21e,
|
||||||
private: 0x0488ade4
|
private: 0x0488ade4,
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x00,
|
pubKeyHash: 0x00,
|
||||||
scriptHash: 0x05,
|
scriptHash: 0x05,
|
||||||
wif: 0x80
|
wif: 0x80,
|
||||||
},
|
};
|
||||||
regtest: {
|
exports.regtest = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'bcrt',
|
bech32: 'bcrt',
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x043587cf,
|
public: 0x043587cf,
|
||||||
private: 0x04358394
|
private: 0x04358394,
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x6f,
|
pubKeyHash: 0x6f,
|
||||||
scriptHash: 0xc4,
|
scriptHash: 0xc4,
|
||||||
wif: 0xef
|
wif: 0xef,
|
||||||
},
|
};
|
||||||
testnet: {
|
exports.testnet = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'tb',
|
bech32: 'tb',
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x043587cf,
|
public: 0x043587cf,
|
||||||
private: 0x04358394
|
private: 0x04358394,
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x6f,
|
pubKeyHash: 0x6f,
|
||||||
scriptHash: 0xc4,
|
scriptHash: 0xc4,
|
||||||
wif: 0xef
|
wif: 0xef,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
|
@ -1,56 +1,51 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const networks_1 = require("../networks");
|
||||||
|
const bscript = require("../script");
|
||||||
const bscript = require('../script')
|
const lazy = require("./lazy");
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const typef = require('typeforce');
|
||||||
|
const OPS = bscript.OPS;
|
||||||
function stacksEqual (a, b) {
|
function stacksEqual(a, b) {
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length)
|
||||||
|
return false;
|
||||||
return a.every(function (x, i) {
|
return a.every((x, i) => {
|
||||||
return x.equals(b[i])
|
return x.equals(b[i]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// output: OP_RETURN ...
|
// output: OP_RETURN ...
|
||||||
function p2data (a, opts) {
|
function p2data(a, opts) {
|
||||||
if (
|
if (!a.data && !a.output)
|
||||||
!a.data &&
|
throw new TypeError('Not enough data');
|
||||||
!a.output
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
output: typef.maybe(typef.Buffer),
|
output: typef.maybe(typef.Buffer),
|
||||||
data: typef.maybe(typef.arrayOf(typef.Buffer))
|
data: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}, a)
|
}, a);
|
||||||
|
const network = a.network || networks_1.bitcoin;
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const o = { network };
|
||||||
const o = { network }
|
lazy.prop(o, 'output', () => {
|
||||||
|
if (!a.data)
|
||||||
lazy.prop(o, 'output', function () {
|
return;
|
||||||
if (!a.data) return
|
return bscript.compile([OPS.OP_RETURN].concat(a.data));
|
||||||
return bscript.compile([OPS.OP_RETURN].concat(a.data))
|
});
|
||||||
})
|
lazy.prop(o, 'data', () => {
|
||||||
lazy.prop(o, 'data', function () {
|
if (!a.output)
|
||||||
if (!a.output) return
|
return;
|
||||||
return bscript.decompile(a.output).slice(1)
|
return bscript.decompile(a.output).slice(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
const chunks = bscript.decompile(a.output)
|
const chunks = bscript.decompile(a.output);
|
||||||
if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid')
|
if (chunks[0] !== OPS.OP_RETURN)
|
||||||
if (!chunks.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
|
if (!chunks.slice(1).every(typef.Buffer))
|
||||||
if (a.data && !stacksEqual(a.data, o.data)) throw new TypeError('Data mismatch')
|
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)
|
|
||||||
}
|
}
|
||||||
|
exports.p2data = p2data;
|
||||||
module.exports = p2data
|
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
const embed = require('./embed')
|
"use strict";
|
||||||
const p2ms = require('./p2ms')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const p2pk = require('./p2pk')
|
const embed_1 = require("./embed");
|
||||||
const p2pkh = require('./p2pkh')
|
exports.embed = embed_1.p2data;
|
||||||
const p2sh = require('./p2sh')
|
const p2ms_1 = require("./p2ms");
|
||||||
const p2wpkh = require('./p2wpkh')
|
exports.p2ms = p2ms_1.p2ms;
|
||||||
const p2wsh = require('./p2wsh')
|
const p2pk_1 = require("./p2pk");
|
||||||
|
exports.p2pk = p2pk_1.p2pk;
|
||||||
module.exports = { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }
|
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
|
// TODO
|
||||||
// witness commitment
|
// witness commitment
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
function prop (object, name, f) {
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
function prop(object, name, f) {
|
||||||
Object.defineProperty(object, name, {
|
Object.defineProperty(object, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get() {
|
||||||
let value = f.call(this)
|
const _value = f.call(this);
|
||||||
this[name] = value
|
this[name] = _value;
|
||||||
return value
|
return _value;
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set(_value) {
|
||||||
Object.defineProperty(this, name, {
|
Object.defineProperty(this, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
value: value,
|
value: _value,
|
||||||
writable: true
|
writable: true,
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
exports.prop = prop;
|
||||||
function value (f) {
|
function value(f) {
|
||||||
let value
|
let _value;
|
||||||
return function () {
|
return () => {
|
||||||
if (value !== undefined) return value
|
if (_value !== undefined)
|
||||||
value = f()
|
return _value;
|
||||||
return value
|
_value = f();
|
||||||
}
|
return _value;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
exports.value = value;
|
||||||
module.exports = { prop, value }
|
|
||||||
|
|
|
@ -1,140 +1,141 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const networks_1 = require("../networks");
|
||||||
const ecc = require('tiny-secp256k1')
|
const bscript = require("../script");
|
||||||
|
const lazy = require("./lazy");
|
||||||
const bscript = require('../script')
|
const OPS = bscript.OPS;
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const typef = require('typeforce');
|
||||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
|
||||||
function stacksEqual (a, b) {
|
function stacksEqual(a, b) {
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length)
|
||||||
|
return false;
|
||||||
return a.every(function (x, i) {
|
return a.every((x, i) => {
|
||||||
return x.equals(b[i])
|
return x.equals(b[i]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// input: OP_0 [signatures ...]
|
// input: OP_0 [signatures ...]
|
||||||
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
||||||
function p2ms (a, opts) {
|
function p2ms(a, opts) {
|
||||||
if (
|
if (!a.input &&
|
||||||
!a.input &&
|
|
||||||
!a.output &&
|
!a.output &&
|
||||||
!(a.pubkeys && a.m !== undefined) &&
|
!(a.pubkeys && a.m !== undefined) &&
|
||||||
!a.signatures
|
!a.signatures)
|
||||||
) throw new TypeError('Not enough data')
|
throw new TypeError('Not enough data');
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
|
function isAcceptableSignature(x) {
|
||||||
function isAcceptableSignature (x) {
|
return (bscript.isCanonicalScriptSignature(x) ||
|
||||||
return bscript.isCanonicalScriptSignature(x) || (opts.allowIncomplete && (x === OPS.OP_0))
|
(opts.allowIncomplete && x === OPS.OP_0) !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
m: typef.maybe(typef.Number),
|
m: typef.maybe(typef.Number),
|
||||||
n: typef.maybe(typef.Number),
|
n: typef.maybe(typef.Number),
|
||||||
output: typef.maybe(typef.Buffer),
|
output: typef.maybe(typef.Buffer),
|
||||||
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
|
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
|
||||||
|
|
||||||
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
|
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
|
||||||
input: typef.maybe(typef.Buffer)
|
input: typef.maybe(typef.Buffer),
|
||||||
}, a)
|
}, a);
|
||||||
|
const network = a.network || networks_1.bitcoin;
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const o = { network };
|
||||||
const o = { network }
|
let chunks = [];
|
||||||
|
let decoded = false;
|
||||||
let chunks
|
function decode(output) {
|
||||||
let decoded = false
|
if (decoded)
|
||||||
function decode (output) {
|
return;
|
||||||
if (decoded) return
|
decoded = true;
|
||||||
decoded = true
|
chunks = bscript.decompile(output);
|
||||||
chunks = bscript.decompile(output)
|
o.m = chunks[0] - OP_INT_BASE;
|
||||||
o.m = chunks[0] - OP_INT_BASE
|
o.n = chunks[chunks.length - 2] - OP_INT_BASE;
|
||||||
o.n = chunks[chunks.length - 2] - OP_INT_BASE
|
o.pubkeys = chunks.slice(1, -2);
|
||||||
o.pubkeys = chunks.slice(1, -2)
|
|
||||||
}
|
}
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
lazy.prop(o, 'output', function () {
|
if (!a.m)
|
||||||
if (!a.m) return
|
return;
|
||||||
if (!o.n) return
|
if (!o.n)
|
||||||
if (!a.pubkeys) return
|
return;
|
||||||
return bscript.compile([].concat(
|
if (!a.pubkeys)
|
||||||
OP_INT_BASE + a.m,
|
return;
|
||||||
a.pubkeys,
|
return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG));
|
||||||
OP_INT_BASE + o.n,
|
});
|
||||||
OPS.OP_CHECKMULTISIG
|
lazy.prop(o, 'm', () => {
|
||||||
))
|
if (!o.output)
|
||||||
})
|
return;
|
||||||
lazy.prop(o, 'm', function () {
|
decode(o.output);
|
||||||
if (!o.output) return
|
return o.m;
|
||||||
decode(o.output)
|
});
|
||||||
return o.m
|
lazy.prop(o, 'n', () => {
|
||||||
})
|
if (!o.pubkeys)
|
||||||
lazy.prop(o, 'n', function () {
|
return;
|
||||||
if (!o.pubkeys) return
|
return o.pubkeys.length;
|
||||||
return o.pubkeys.length
|
});
|
||||||
})
|
lazy.prop(o, 'pubkeys', () => {
|
||||||
lazy.prop(o, 'pubkeys', function () {
|
if (!a.output)
|
||||||
if (!a.output) return
|
return;
|
||||||
decode(a.output)
|
decode(a.output);
|
||||||
return o.pubkeys
|
return o.pubkeys;
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'signatures', function () {
|
lazy.prop(o, 'signatures', () => {
|
||||||
if (!a.input) return
|
if (!a.input)
|
||||||
return bscript.decompile(a.input).slice(1)
|
return;
|
||||||
})
|
return bscript.decompile(a.input).slice(1);
|
||||||
lazy.prop(o, 'input', function () {
|
});
|
||||||
if (!a.signatures) return
|
lazy.prop(o, 'input', () => {
|
||||||
return bscript.compile([OPS.OP_0].concat(a.signatures))
|
if (!a.signatures)
|
||||||
})
|
return;
|
||||||
lazy.prop(o, 'witness', function () {
|
return bscript.compile([OPS.OP_0].concat(a.signatures));
|
||||||
if (!o.input) return
|
});
|
||||||
return []
|
lazy.prop(o, 'witness', () => {
|
||||||
})
|
if (!o.input)
|
||||||
|
return;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
decode(a.output)
|
decode(a.output);
|
||||||
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid')
|
if (!typef.Number(chunks[0]))
|
||||||
if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid')
|
if (!typef.Number(chunks[chunks.length - 2]))
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
if (
|
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
|
||||||
o.m <= 0 ||
|
throw new TypeError('Output is invalid');
|
||||||
o.n > 16 ||
|
if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3)
|
||||||
o.m > o.n ||
|
throw new TypeError('Output is invalid');
|
||||||
o.n !== chunks.length - 3) throw new TypeError('Output is invalid')
|
if (!o.pubkeys.every(x => ecc.isPoint(x)))
|
||||||
if (!o.pubkeys.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
|
if (a.m !== undefined && a.m !== o.m)
|
||||||
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch')
|
throw new TypeError('m mismatch');
|
||||||
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch')
|
if (a.n !== undefined && a.n !== o.n)
|
||||||
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch')
|
throw new TypeError('n mismatch');
|
||||||
|
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys))
|
||||||
|
throw new TypeError('Pubkeys mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.pubkeys) {
|
if (a.pubkeys) {
|
||||||
if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch')
|
if (a.n !== undefined && a.n !== a.pubkeys.length)
|
||||||
o.n = a.pubkeys.length
|
throw new TypeError('Pubkey count mismatch');
|
||||||
|
o.n = a.pubkeys.length;
|
||||||
if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m')
|
if (o.n < o.m)
|
||||||
|
throw new TypeError('Pubkey count cannot be less than m');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.signatures) {
|
if (a.signatures) {
|
||||||
if (a.signatures.length < o.m) throw new TypeError('Not enough signatures provided')
|
if (a.signatures.length < o.m)
|
||||||
if (a.signatures.length > o.m) throw new TypeError('Too many signatures provided')
|
throw new TypeError('Not enough signatures provided');
|
||||||
|
if (a.signatures.length > o.m)
|
||||||
|
throw new TypeError('Too many signatures provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid')
|
if (a.input[0] !== OPS.OP_0)
|
||||||
if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)')
|
throw new TypeError('Input is invalid');
|
||||||
|
if (o.signatures.length === 0 ||
|
||||||
if (a.signatures && !stacksEqual(a.signatures, o.signatures)) throw new TypeError('Signature mismatch')
|
!o.signatures.every(isAcceptableSignature))
|
||||||
if (a.m !== undefined && a.m !== a.signatures.length) throw new TypeError('Signature count mismatch')
|
throw new TypeError('Input has invalid signature(s)');
|
||||||
|
if (a.signatures && !stacksEqual(a.signatures, o.signatures))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
|
if (a.m !== undefined && a.m !== a.signatures.length)
|
||||||
|
throw new TypeError('Signature count mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2ms = p2ms;
|
||||||
module.exports = p2ms
|
|
||||||
|
|
|
@ -1,80 +1,75 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const networks_1 = require("../networks");
|
||||||
const ecc = require('tiny-secp256k1')
|
const bscript = require("../script");
|
||||||
|
const lazy = require("./lazy");
|
||||||
const bscript = require('../script')
|
const typef = require('typeforce');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const OPS = bscript.OPS;
|
||||||
|
const ecc = require('tiny-secp256k1');
|
||||||
// input: {signature}
|
// input: {signature}
|
||||||
// output: {pubKey} OP_CHECKSIG
|
// output: {pubKey} OP_CHECKSIG
|
||||||
function p2pk (a, opts) {
|
function p2pk(a, opts) {
|
||||||
if (
|
if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature)
|
||||||
!a.input &&
|
throw new TypeError('Not enough data');
|
||||||
!a.output &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.pubkey &&
|
|
||||||
!a.input &&
|
|
||||||
!a.signature
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
output: typef.maybe(typef.Buffer),
|
output: typef.maybe(typef.Buffer),
|
||||||
pubkey: typef.maybe(ecc.isPoint),
|
pubkey: typef.maybe(ecc.isPoint),
|
||||||
|
|
||||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
input: typef.maybe(typef.Buffer)
|
input: typef.maybe(typef.Buffer),
|
||||||
}, a)
|
}, a);
|
||||||
|
const _chunks = lazy.value(() => {
|
||||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
|
return bscript.decompile(a.input);
|
||||||
|
});
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const network = a.network || networks_1.bitcoin;
|
||||||
const o = { network }
|
const o = { network };
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
lazy.prop(o, 'output', function () {
|
if (!a.pubkey)
|
||||||
if (!a.pubkey) return
|
return;
|
||||||
return bscript.compile([
|
return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]);
|
||||||
a.pubkey,
|
});
|
||||||
OPS.OP_CHECKSIG
|
lazy.prop(o, 'pubkey', () => {
|
||||||
])
|
if (!a.output)
|
||||||
})
|
return;
|
||||||
lazy.prop(o, 'pubkey', function () {
|
return a.output.slice(1, -1);
|
||||||
if (!a.output) return
|
});
|
||||||
return a.output.slice(1, -1)
|
lazy.prop(o, 'signature', () => {
|
||||||
})
|
if (!a.input)
|
||||||
lazy.prop(o, 'signature', function () {
|
return;
|
||||||
if (!a.input) return
|
return _chunks()[0];
|
||||||
return _chunks()[0]
|
});
|
||||||
})
|
lazy.prop(o, 'input', () => {
|
||||||
lazy.prop(o, 'input', function () {
|
if (!a.signature)
|
||||||
if (!a.signature) return
|
return;
|
||||||
return bscript.compile([a.signature])
|
return bscript.compile([a.signature]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'witness', function () {
|
lazy.prop(o, 'witness', () => {
|
||||||
if (!o.input) return
|
if (!o.input)
|
||||||
return []
|
return;
|
||||||
})
|
return [];
|
||||||
|
});
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid')
|
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG)
|
||||||
if (!ecc.isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
if (a.pubkey && !a.pubkey.equals(o.pubkey)) throw new TypeError('Pubkey mismatch')
|
if (!ecc.isPoint(o.pubkey))
|
||||||
|
throw new TypeError('Output pubkey is invalid');
|
||||||
|
if (a.pubkey && !a.pubkey.equals(o.pubkey))
|
||||||
|
throw new TypeError('Pubkey mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.signature) {
|
if (a.signature) {
|
||||||
if (a.input && !a.input.equals(o.input)) throw new TypeError('Signature mismatch')
|
if (a.input && !a.input.equals(o.input))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
if (_chunks().length !== 1) throw new TypeError('Input is invalid')
|
if (_chunks().length !== 1)
|
||||||
if (!bscript.isCanonicalScriptSignature(o.signature)) throw new TypeError('Input has invalid signature')
|
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)
|
|
||||||
}
|
}
|
||||||
|
exports.p2pk = p2pk;
|
||||||
module.exports = p2pk
|
|
||||||
|
|
|
@ -1,137 +1,142 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require("../crypto");
|
||||||
const ecc = require('tiny-secp256k1')
|
const networks_1 = require("../networks");
|
||||||
|
const bscript = require("../script");
|
||||||
const bcrypto = require('../crypto')
|
const lazy = require("./lazy");
|
||||||
const bscript = require('../script')
|
const typef = require('typeforce');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const OPS = bscript.OPS;
|
||||||
const bs58check = require('bs58check')
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const bs58check = require('bs58check');
|
||||||
// input: {signature} {pubkey}
|
// input: {signature} {pubkey}
|
||||||
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
function p2pkh (a, opts) {
|
function p2pkh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
|
||||||
!a.pubkey &&
|
|
||||||
!a.input
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
address: typef.maybe(typef.String),
|
address: typef.maybe(typef.String),
|
||||||
hash: typef.maybe(typef.BufferN(20)),
|
hash: typef.maybe(typef.BufferN(20)),
|
||||||
output: typef.maybe(typef.BufferN(25)),
|
output: typef.maybe(typef.BufferN(25)),
|
||||||
|
|
||||||
pubkey: typef.maybe(ecc.isPoint),
|
pubkey: typef.maybe(ecc.isPoint),
|
||||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
input: typef.maybe(typef.Buffer)
|
input: typef.maybe(typef.Buffer),
|
||||||
}, a)
|
}, a);
|
||||||
|
const _address = lazy.value(() => {
|
||||||
const _address = lazy.value(function () {
|
const payload = bs58check.decode(a.address);
|
||||||
const payload = bs58check.decode(a.address)
|
const version = payload.readUInt8(0);
|
||||||
const version = payload.readUInt8(0)
|
const hash = payload.slice(1);
|
||||||
const hash = payload.slice(1)
|
return { version, hash };
|
||||||
return { version, hash }
|
});
|
||||||
})
|
const _chunks = lazy.value(() => {
|
||||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
|
return bscript.decompile(a.input);
|
||||||
|
});
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const network = a.network || networks_1.bitcoin;
|
||||||
const o = { network }
|
const o = { network };
|
||||||
|
lazy.prop(o, 'address', () => {
|
||||||
lazy.prop(o, 'address', function () {
|
if (!o.hash)
|
||||||
if (!o.hash) return
|
return;
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
const payload = Buffer.allocUnsafe(21)
|
payload.writeUInt8(network.pubKeyHash, 0);
|
||||||
payload.writeUInt8(network.pubKeyHash, 0)
|
o.hash.copy(payload, 1);
|
||||||
o.hash.copy(payload, 1)
|
return bs58check.encode(payload);
|
||||||
return bs58check.encode(payload)
|
});
|
||||||
})
|
lazy.prop(o, 'hash', () => {
|
||||||
lazy.prop(o, 'hash', function () {
|
if (a.output)
|
||||||
if (a.output) return a.output.slice(3, 23)
|
return a.output.slice(3, 23);
|
||||||
if (a.address) return _address().hash
|
if (a.address)
|
||||||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey)
|
return _address().hash;
|
||||||
})
|
if (a.pubkey || o.pubkey)
|
||||||
lazy.prop(o, 'output', function () {
|
return bcrypto.hash160(a.pubkey || o.pubkey);
|
||||||
if (!o.hash) return
|
});
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
|
if (!o.hash)
|
||||||
|
return;
|
||||||
return bscript.compile([
|
return bscript.compile([
|
||||||
OPS.OP_DUP,
|
OPS.OP_DUP,
|
||||||
OPS.OP_HASH160,
|
OPS.OP_HASH160,
|
||||||
o.hash,
|
o.hash,
|
||||||
OPS.OP_EQUALVERIFY,
|
OPS.OP_EQUALVERIFY,
|
||||||
OPS.OP_CHECKSIG
|
OPS.OP_CHECKSIG,
|
||||||
])
|
]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'pubkey', function () {
|
lazy.prop(o, 'pubkey', () => {
|
||||||
if (!a.input) return
|
if (!a.input)
|
||||||
return _chunks()[1]
|
return;
|
||||||
})
|
return _chunks()[1];
|
||||||
lazy.prop(o, 'signature', function () {
|
});
|
||||||
if (!a.input) return
|
lazy.prop(o, 'signature', () => {
|
||||||
return _chunks()[0]
|
if (!a.input)
|
||||||
})
|
return;
|
||||||
lazy.prop(o, 'input', function () {
|
return _chunks()[0];
|
||||||
if (!a.pubkey) return
|
});
|
||||||
if (!a.signature) return
|
lazy.prop(o, 'input', () => {
|
||||||
return bscript.compile([a.signature, a.pubkey])
|
if (!a.pubkey)
|
||||||
})
|
return;
|
||||||
lazy.prop(o, 'witness', function () {
|
if (!a.signature)
|
||||||
if (!o.input) return
|
return;
|
||||||
return []
|
return bscript.compile([a.signature, a.pubkey]);
|
||||||
})
|
});
|
||||||
|
lazy.prop(o, 'witness', () => {
|
||||||
|
if (!o.input)
|
||||||
|
return;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (_address().version !== network.pubKeyHash) throw new TypeError('Invalid version or Network mismatch')
|
if (_address().version !== network.pubKeyHash)
|
||||||
if (_address().hash.length !== 20) throw new TypeError('Invalid address')
|
throw new TypeError('Invalid version or Network mismatch');
|
||||||
hash = _address().hash
|
if (_address().hash.length !== 20)
|
||||||
|
throw new TypeError('Invalid address');
|
||||||
|
hash = _address().hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (a.output.length !== 25 ||
|
||||||
a.output.length !== 25 ||
|
|
||||||
a.output[0] !== OPS.OP_DUP ||
|
a.output[0] !== OPS.OP_DUP ||
|
||||||
a.output[1] !== OPS.OP_HASH160 ||
|
a.output[1] !== OPS.OP_HASH160 ||
|
||||||
a.output[2] !== 0x14 ||
|
a.output[2] !== 0x14 ||
|
||||||
a.output[23] !== OPS.OP_EQUALVERIFY ||
|
a.output[23] !== OPS.OP_EQUALVERIFY ||
|
||||||
a.output[24] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid')
|
a.output[24] !== OPS.OP_CHECKSIG)
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
const hash2 = a.output.slice(3, 23)
|
const hash2 = a.output.slice(3, 23);
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
else hash = hash2
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.pubkey) {
|
if (a.pubkey) {
|
||||||
const pkh = bcrypto.hash160(a.pubkey)
|
const pkh = bcrypto.hash160(a.pubkey);
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
else hash = pkh
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = pkh;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
const chunks = _chunks()
|
const chunks = _chunks();
|
||||||
if (chunks.length !== 2) throw new TypeError('Input is invalid')
|
if (chunks.length !== 2)
|
||||||
if (!bscript.isCanonicalScriptSignature(chunks[0])) throw new TypeError('Input has invalid signature')
|
throw new TypeError('Input is invalid');
|
||||||
if (!ecc.isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey')
|
if (!bscript.isCanonicalScriptSignature(chunks[0]))
|
||||||
|
throw new TypeError('Input has invalid signature');
|
||||||
if (a.signature && !a.signature.equals(chunks[0])) throw new TypeError('Signature mismatch')
|
if (!ecc.isPoint(chunks[1]))
|
||||||
if (a.pubkey && !a.pubkey.equals(chunks[1])) throw new TypeError('Pubkey mismatch')
|
throw new TypeError('Input has invalid pubkey');
|
||||||
|
if (a.signature && !a.signature.equals(chunks[0]))
|
||||||
const pkh = bcrypto.hash160(chunks[1])
|
throw new TypeError('Signature mismatch');
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash 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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2pkh = p2pkh;
|
||||||
module.exports = p2pkh
|
|
||||||
|
|
|
@ -1,193 +1,185 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require("../crypto");
|
||||||
|
const networks_1 = require("../networks");
|
||||||
const bcrypto = require('../crypto')
|
const bscript = require("../script");
|
||||||
const bscript = require('../script')
|
const lazy = require("./lazy");
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const typef = require('typeforce');
|
||||||
const bs58check = require('bs58check')
|
const OPS = bscript.OPS;
|
||||||
|
const bs58check = require('bs58check');
|
||||||
function stacksEqual (a, b) {
|
function stacksEqual(a, b) {
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length)
|
||||||
|
return false;
|
||||||
return a.every(function (x, i) {
|
return a.every((x, i) => {
|
||||||
return x.equals(b[i])
|
return x.equals(b[i]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// input: [redeemScriptSig ...] {redeemScript}
|
// input: [redeemScriptSig ...] {redeemScript}
|
||||||
// witness: <?>
|
// witness: <?>
|
||||||
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
|
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
|
||||||
function p2sh (a, opts) {
|
function p2sh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.redeem && !a.input)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
|
||||||
!a.redeem &&
|
|
||||||
!a.input
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
|
|
||||||
address: typef.maybe(typef.String),
|
address: typef.maybe(typef.String),
|
||||||
hash: typef.maybe(typef.BufferN(20)),
|
hash: typef.maybe(typef.BufferN(20)),
|
||||||
output: typef.maybe(typef.BufferN(23)),
|
output: typef.maybe(typef.BufferN(23)),
|
||||||
|
|
||||||
redeem: typef.maybe({
|
redeem: typef.maybe({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
output: typef.maybe(typef.Buffer),
|
output: typef.maybe(typef.Buffer),
|
||||||
input: typef.maybe(typef.Buffer),
|
input: typef.maybe(typef.Buffer),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}),
|
}),
|
||||||
input: typef.maybe(typef.Buffer),
|
input: typef.maybe(typef.Buffer),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}, a)
|
}, a);
|
||||||
|
let network = a.network;
|
||||||
let network = a.network
|
|
||||||
if (!network) {
|
if (!network) {
|
||||||
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK
|
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
||||||
}
|
}
|
||||||
|
const o = { network };
|
||||||
const o = { network }
|
const _address = lazy.value(() => {
|
||||||
|
const payload = bs58check.decode(a.address);
|
||||||
const _address = lazy.value(function () {
|
const version = payload.readUInt8(0);
|
||||||
const payload = bs58check.decode(a.address)
|
const hash = payload.slice(1);
|
||||||
const version = payload.readUInt8(0)
|
return { version, hash };
|
||||||
const hash = payload.slice(1)
|
});
|
||||||
return { version, hash }
|
const _chunks = lazy.value(() => {
|
||||||
})
|
return bscript.decompile(a.input);
|
||||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
|
});
|
||||||
const _redeem = lazy.value(function () {
|
const _redeem = lazy.value(() => {
|
||||||
const chunks = _chunks()
|
const chunks = _chunks();
|
||||||
return {
|
return {
|
||||||
network,
|
network,
|
||||||
output: chunks[chunks.length - 1],
|
output: chunks[chunks.length - 1],
|
||||||
input: bscript.compile(chunks.slice(0, -1)),
|
input: bscript.compile(chunks.slice(0, -1)),
|
||||||
witness: a.witness || []
|
witness: a.witness || [],
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
// output dependents
|
// output dependents
|
||||||
lazy.prop(o, 'address', function () {
|
lazy.prop(o, 'address', () => {
|
||||||
if (!o.hash) return
|
if (!o.hash)
|
||||||
|
return;
|
||||||
const payload = Buffer.allocUnsafe(21)
|
const payload = Buffer.allocUnsafe(21);
|
||||||
payload.writeUInt8(network.scriptHash, 0)
|
payload.writeUInt8(o.network.scriptHash, 0);
|
||||||
o.hash.copy(payload, 1)
|
o.hash.copy(payload, 1);
|
||||||
return bs58check.encode(payload)
|
return bs58check.encode(payload);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'hash', function () {
|
lazy.prop(o, 'hash', () => {
|
||||||
// in order of least effort
|
// in order of least effort
|
||||||
if (a.output) return a.output.slice(2, 22)
|
if (a.output)
|
||||||
if (a.address) return _address().hash
|
return a.output.slice(2, 22);
|
||||||
if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output)
|
if (a.address)
|
||||||
})
|
return _address().hash;
|
||||||
lazy.prop(o, 'output', function () {
|
if (o.redeem && o.redeem.output)
|
||||||
if (!o.hash) return
|
return bcrypto.hash160(o.redeem.output);
|
||||||
return bscript.compile([
|
});
|
||||||
OPS.OP_HASH160,
|
lazy.prop(o, 'output', () => {
|
||||||
o.hash,
|
if (!o.hash)
|
||||||
OPS.OP_EQUAL
|
return;
|
||||||
])
|
return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]);
|
||||||
})
|
});
|
||||||
|
|
||||||
// input dependents
|
// input dependents
|
||||||
lazy.prop(o, 'redeem', function () {
|
lazy.prop(o, 'redeem', () => {
|
||||||
if (!a.input) return
|
if (!a.input)
|
||||||
return _redeem()
|
return;
|
||||||
})
|
return _redeem();
|
||||||
lazy.prop(o, 'input', function () {
|
});
|
||||||
if (!a.redeem || !a.redeem.input || !a.redeem.output) return
|
lazy.prop(o, 'input', () => {
|
||||||
return bscript.compile([].concat(
|
if (!a.redeem || !a.redeem.input || !a.redeem.output)
|
||||||
bscript.decompile(a.redeem.input),
|
return;
|
||||||
a.redeem.output
|
return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output));
|
||||||
))
|
});
|
||||||
})
|
lazy.prop(o, 'witness', () => {
|
||||||
lazy.prop(o, 'witness', function () {
|
if (o.redeem && o.redeem.witness)
|
||||||
if (o.redeem && o.redeem.witness) return o.redeem.witness
|
return o.redeem.witness;
|
||||||
if (o.input) return []
|
if (o.input)
|
||||||
})
|
return [];
|
||||||
|
});
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (_address().version !== network.scriptHash) throw new TypeError('Invalid version or Network mismatch')
|
if (_address().version !== network.scriptHash)
|
||||||
if (_address().hash.length !== 20) throw new TypeError('Invalid address')
|
throw new TypeError('Invalid version or Network mismatch');
|
||||||
hash = _address().hash
|
if (_address().hash.length !== 20)
|
||||||
|
throw new TypeError('Invalid address');
|
||||||
|
hash = _address().hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (a.output.length !== 23 ||
|
||||||
a.output.length !== 23 ||
|
|
||||||
a.output[0] !== OPS.OP_HASH160 ||
|
a.output[0] !== OPS.OP_HASH160 ||
|
||||||
a.output[1] !== 0x14 ||
|
a.output[1] !== 0x14 ||
|
||||||
a.output[22] !== OPS.OP_EQUAL) throw new TypeError('Output is invalid')
|
a.output[22] !== OPS.OP_EQUAL)
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
const hash2 = a.output.slice(2, 22)
|
const hash2 = a.output.slice(2, 22);
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
else hash = hash2
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// inlined to prevent 'no-inner-declarations' failing
|
// inlined to prevent 'no-inner-declarations' failing
|
||||||
const checkRedeem = function (redeem) {
|
const checkRedeem = (redeem) => {
|
||||||
// is the redeem output empty/invalid?
|
// is the redeem output empty/invalid?
|
||||||
if (redeem.output) {
|
if (redeem.output) {
|
||||||
const decompile = bscript.decompile(redeem.output)
|
const decompile = bscript.decompile(redeem.output);
|
||||||
if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short')
|
if (!decompile || decompile.length < 1)
|
||||||
|
throw new TypeError('Redeem.output too short');
|
||||||
// match hash against other sources
|
// match hash against other sources
|
||||||
const hash2 = bcrypto.hash160(redeem.output)
|
const hash2 = bcrypto.hash160(redeem.output);
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
else hash = hash2
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redeem.input) {
|
if (redeem.input) {
|
||||||
const hasInput = redeem.input.length > 0
|
const hasInput = redeem.input.length > 0;
|
||||||
const hasWitness = redeem.witness && redeem.witness.length > 0
|
const hasWitness = redeem.witness && redeem.witness.length > 0;
|
||||||
if (!hasInput && !hasWitness) throw new TypeError('Empty input')
|
if (!hasInput && !hasWitness)
|
||||||
if (hasInput && hasWitness) throw new TypeError('Input and witness provided')
|
throw new TypeError('Empty input');
|
||||||
|
if (hasInput && hasWitness)
|
||||||
|
throw new TypeError('Input and witness provided');
|
||||||
if (hasInput) {
|
if (hasInput) {
|
||||||
const richunks = bscript.decompile(redeem.input)
|
const richunks = bscript.decompile(redeem.input);
|
||||||
if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig')
|
if (!bscript.isPushOnly(richunks))
|
||||||
|
throw new TypeError('Non push-only scriptSig');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
const chunks = _chunks()
|
const chunks = _chunks();
|
||||||
if (!chunks || chunks.length < 1) throw new TypeError('Input too short')
|
if (!chunks || chunks.length < 1)
|
||||||
if (!Buffer.isBuffer(_redeem().output)) throw new TypeError('Input is invalid')
|
throw new TypeError('Input too short');
|
||||||
|
if (!Buffer.isBuffer(_redeem().output))
|
||||||
checkRedeem(_redeem())
|
throw new TypeError('Input is invalid');
|
||||||
|
checkRedeem(_redeem());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.redeem) {
|
if (a.redeem) {
|
||||||
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch')
|
if (a.redeem.network && a.redeem.network !== network)
|
||||||
|
throw new TypeError('Network mismatch');
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
const redeem = _redeem()
|
const redeem = _redeem();
|
||||||
if (a.redeem.output && !a.redeem.output.equals(redeem.output)) throw new TypeError('Redeem.output mismatch')
|
if (a.redeem.output && !a.redeem.output.equals(redeem.output))
|
||||||
if (a.redeem.input && !a.redeem.input.equals(redeem.input)) throw new TypeError('Redeem.input mismatch')
|
throw new TypeError('Redeem.output mismatch');
|
||||||
|
if (a.redeem.input && !a.redeem.input.equals(redeem.input))
|
||||||
|
throw new TypeError('Redeem.input mismatch');
|
||||||
}
|
}
|
||||||
|
checkRedeem(a.redeem);
|
||||||
checkRedeem(a.redeem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.witness) {
|
if (a.witness) {
|
||||||
if (
|
if (a.redeem &&
|
||||||
a.redeem &&
|
|
||||||
a.redeem.witness &&
|
a.redeem.witness &&
|
||||||
!stacksEqual(a.redeem.witness, a.witness)) throw new TypeError('Witness and redeem.witness mismatch')
|
!stacksEqual(a.redeem.witness, a.witness))
|
||||||
|
throw new TypeError('Witness and redeem.witness mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2sh = p2sh;
|
||||||
module.exports = p2sh
|
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require("../crypto");
|
||||||
const ecc = require('tiny-secp256k1')
|
const networks_1 = require("../networks");
|
||||||
|
const bscript = require("../script");
|
||||||
const bcrypto = require('../crypto')
|
const lazy = require("./lazy");
|
||||||
const bech32 = require('bech32')
|
const typef = require('typeforce');
|
||||||
const bscript = require('../script')
|
const OPS = bscript.OPS;
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const bech32 = require('bech32');
|
||||||
const EMPTY_BUFFER = Buffer.alloc(0)
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
|
||||||
// witness: {signature} {pubKey}
|
// witness: {signature} {pubKey}
|
||||||
// input: <>
|
// input: <>
|
||||||
// output: OP_0 {pubKeyHash}
|
// output: OP_0 {pubKeyHash}
|
||||||
function p2wpkh (a, opts) {
|
function p2wpkh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
|
||||||
!a.pubkey &&
|
|
||||||
!a.witness
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
address: typef.maybe(typef.String),
|
address: typef.maybe(typef.String),
|
||||||
hash: typef.maybe(typef.BufferN(20)),
|
hash: typef.maybe(typef.BufferN(20)),
|
||||||
|
@ -31,105 +24,115 @@ function p2wpkh (a, opts) {
|
||||||
output: typef.maybe(typef.BufferN(22)),
|
output: typef.maybe(typef.BufferN(22)),
|
||||||
pubkey: typef.maybe(ecc.isPoint),
|
pubkey: typef.maybe(ecc.isPoint),
|
||||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}, a)
|
}, a);
|
||||||
|
const _address = lazy.value(() => {
|
||||||
const _address = lazy.value(function () {
|
const result = bech32.decode(a.address);
|
||||||
const result = bech32.decode(a.address)
|
const version = result.words.shift();
|
||||||
const version = result.words.shift()
|
const data = bech32.fromWords(result.words);
|
||||||
const data = bech32.fromWords(result.words)
|
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
prefix: result.prefix,
|
prefix: result.prefix,
|
||||||
data: Buffer.from(data)
|
data: Buffer.from(data),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
const network = a.network || networks_1.bitcoin;
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const o = { network };
|
||||||
const o = { network }
|
lazy.prop(o, 'address', () => {
|
||||||
|
if (!o.hash)
|
||||||
lazy.prop(o, 'address', function () {
|
return;
|
||||||
if (!o.hash) return
|
const words = bech32.toWords(o.hash);
|
||||||
|
words.unshift(0x00);
|
||||||
const words = bech32.toWords(o.hash)
|
return bech32.encode(network.bech32, words);
|
||||||
words.unshift(0x00)
|
});
|
||||||
return bech32.encode(network.bech32, words)
|
lazy.prop(o, 'hash', () => {
|
||||||
})
|
if (a.output)
|
||||||
lazy.prop(o, 'hash', function () {
|
return a.output.slice(2, 22);
|
||||||
if (a.output) return a.output.slice(2, 22)
|
if (a.address)
|
||||||
if (a.address) return _address().data
|
return _address().data;
|
||||||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey)
|
if (a.pubkey || o.pubkey)
|
||||||
})
|
return bcrypto.hash160(a.pubkey || o.pubkey);
|
||||||
lazy.prop(o, 'output', function () {
|
});
|
||||||
if (!o.hash) return
|
lazy.prop(o, 'output', () => {
|
||||||
return bscript.compile([
|
if (!o.hash)
|
||||||
OPS.OP_0,
|
return;
|
||||||
o.hash
|
return bscript.compile([OPS.OP_0, o.hash]);
|
||||||
])
|
});
|
||||||
})
|
lazy.prop(o, 'pubkey', () => {
|
||||||
lazy.prop(o, 'pubkey', function () {
|
if (a.pubkey)
|
||||||
if (a.pubkey) return a.pubkey
|
return a.pubkey;
|
||||||
if (!a.witness) return
|
if (!a.witness)
|
||||||
return a.witness[1]
|
return;
|
||||||
})
|
return a.witness[1];
|
||||||
lazy.prop(o, 'signature', function () {
|
});
|
||||||
if (!a.witness) return
|
lazy.prop(o, 'signature', () => {
|
||||||
return a.witness[0]
|
if (!a.witness)
|
||||||
})
|
return;
|
||||||
lazy.prop(o, 'input', function () {
|
return a.witness[0];
|
||||||
if (!o.witness) return
|
});
|
||||||
return EMPTY_BUFFER
|
lazy.prop(o, 'input', () => {
|
||||||
})
|
if (!o.witness)
|
||||||
lazy.prop(o, 'witness', function () {
|
return;
|
||||||
if (!a.pubkey) return
|
return EMPTY_BUFFER;
|
||||||
if (!a.signature) return
|
});
|
||||||
return [a.signature, a.pubkey]
|
lazy.prop(o, 'witness', () => {
|
||||||
})
|
if (!a.pubkey)
|
||||||
|
return;
|
||||||
|
if (!a.signature)
|
||||||
|
return;
|
||||||
|
return [a.signature, a.pubkey];
|
||||||
|
});
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch')
|
if (network && network.bech32 !== _address().prefix)
|
||||||
if (_address().version !== 0x00) throw new TypeError('Invalid address version')
|
throw new TypeError('Invalid prefix or Network mismatch');
|
||||||
if (_address().data.length !== 20) throw new TypeError('Invalid address data')
|
if (_address().version !== 0x00)
|
||||||
hash = _address().data
|
throw new TypeError('Invalid address version');
|
||||||
|
if (_address().data.length !== 20)
|
||||||
|
throw new TypeError('Invalid address data');
|
||||||
|
hash = _address().data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (a.output.length !== 22 ||
|
||||||
a.output.length !== 22 ||
|
|
||||||
a.output[0] !== OPS.OP_0 ||
|
a.output[0] !== OPS.OP_0 ||
|
||||||
a.output[1] !== 0x14) throw new TypeError('Output is invalid')
|
a.output[1] !== 0x14)
|
||||||
if (hash && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch')
|
throw new TypeError('Output is invalid');
|
||||||
else hash = a.output.slice(2)
|
if (hash.length > 0 && !hash.equals(a.output.slice(2)))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = a.output.slice(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.pubkey) {
|
if (a.pubkey) {
|
||||||
const pkh = bcrypto.hash160(a.pubkey)
|
const pkh = bcrypto.hash160(a.pubkey);
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
else hash = pkh
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = pkh;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.witness) {
|
if (a.witness) {
|
||||||
if (a.witness.length !== 2) throw new TypeError('Witness is invalid')
|
if (a.witness.length !== 2)
|
||||||
if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature')
|
throw new TypeError('Witness is invalid');
|
||||||
if (!ecc.isPoint(a.witness[1])) throw new TypeError('Witness has invalid pubkey')
|
if (!bscript.isCanonicalScriptSignature(a.witness[0]))
|
||||||
|
throw new TypeError('Witness has invalid signature');
|
||||||
if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch')
|
if (!ecc.isPoint(a.witness[1]))
|
||||||
if (a.pubkey && !a.pubkey.equals(a.witness[1])) throw new TypeError('Pubkey mismatch')
|
throw new TypeError('Witness has invalid pubkey');
|
||||||
|
if (a.signature && !a.signature.equals(a.witness[0]))
|
||||||
const pkh = bcrypto.hash160(a.witness[1])
|
throw new TypeError('Signature mismatch');
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
if (a.pubkey && !a.pubkey.equals(a.witness[1]))
|
||||||
|
throw new TypeError('Pubkey mismatch');
|
||||||
|
const pkh = bcrypto.hash160(a.witness[1]);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2wpkh = p2wpkh;
|
||||||
module.exports = p2wpkh
|
|
||||||
|
|
|
@ -1,180 +1,177 @@
|
||||||
const lazy = require('./lazy')
|
"use strict";
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require("../crypto");
|
||||||
|
const networks_1 = require("../networks");
|
||||||
const bech32 = require('bech32')
|
const bscript = require("../script");
|
||||||
const bcrypto = require('../crypto')
|
const lazy = require("./lazy");
|
||||||
const bscript = require('../script')
|
const typef = require('typeforce');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const OPS = bscript.OPS;
|
||||||
|
const bech32 = require('bech32');
|
||||||
const EMPTY_BUFFER = Buffer.alloc(0)
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
function stacksEqual(a, b) {
|
||||||
function stacksEqual (a, b) {
|
if (a.length !== b.length)
|
||||||
if (a.length !== b.length) return false
|
return false;
|
||||||
|
return a.every((x, i) => {
|
||||||
return a.every(function (x, i) {
|
return x.equals(b[i]);
|
||||||
return x.equals(b[i])
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// input: <>
|
// input: <>
|
||||||
// witness: [redeemScriptSig ...] {redeemScript}
|
// witness: [redeemScriptSig ...] {redeemScript}
|
||||||
// output: OP_0 {sha256(redeemScript)}
|
// output: OP_0 {sha256(redeemScript)}
|
||||||
function p2wsh (a, opts) {
|
function p2wsh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
|
||||||
!a.redeem &&
|
|
||||||
!a.witness
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = Object.assign({ validate: true }, opts || {})
|
|
||||||
|
|
||||||
typef({
|
typef({
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
|
|
||||||
address: typef.maybe(typef.String),
|
address: typef.maybe(typef.String),
|
||||||
hash: typef.maybe(typef.BufferN(32)),
|
hash: typef.maybe(typef.BufferN(32)),
|
||||||
output: typef.maybe(typef.BufferN(34)),
|
output: typef.maybe(typef.BufferN(34)),
|
||||||
|
|
||||||
redeem: typef.maybe({
|
redeem: typef.maybe({
|
||||||
input: typef.maybe(typef.Buffer),
|
input: typef.maybe(typef.Buffer),
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
output: typef.maybe(typef.Buffer),
|
output: typef.maybe(typef.Buffer),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}),
|
}),
|
||||||
input: typef.maybe(typef.BufferN(0)),
|
input: typef.maybe(typef.BufferN(0)),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}, a)
|
}, a);
|
||||||
|
const _address = lazy.value(() => {
|
||||||
const _address = lazy.value(function () {
|
const result = bech32.decode(a.address);
|
||||||
const result = bech32.decode(a.address)
|
const version = result.words.shift();
|
||||||
const version = result.words.shift()
|
const data = bech32.fromWords(result.words);
|
||||||
const data = bech32.fromWords(result.words)
|
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
prefix: result.prefix,
|
prefix: result.prefix,
|
||||||
data: Buffer.from(data)
|
data: Buffer.from(data),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input) })
|
const _rchunks = lazy.value(() => {
|
||||||
|
return bscript.decompile(a.redeem.input);
|
||||||
let network = a.network
|
});
|
||||||
|
let network = a.network;
|
||||||
if (!network) {
|
if (!network) {
|
||||||
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK
|
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
||||||
}
|
}
|
||||||
|
const o = { network };
|
||||||
const o = { network }
|
lazy.prop(o, 'address', () => {
|
||||||
|
if (!o.hash)
|
||||||
lazy.prop(o, 'address', function () {
|
return;
|
||||||
if (!o.hash) return
|
const words = bech32.toWords(o.hash);
|
||||||
const words = bech32.toWords(o.hash)
|
words.unshift(0x00);
|
||||||
words.unshift(0x00)
|
return bech32.encode(network.bech32, words);
|
||||||
return bech32.encode(network.bech32, words)
|
});
|
||||||
})
|
lazy.prop(o, 'hash', () => {
|
||||||
lazy.prop(o, 'hash', function () {
|
if (a.output)
|
||||||
if (a.output) return a.output.slice(2)
|
return a.output.slice(2);
|
||||||
if (a.address) return _address().data
|
if (a.address)
|
||||||
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output)
|
return _address().data;
|
||||||
})
|
if (o.redeem && o.redeem.output)
|
||||||
lazy.prop(o, 'output', function () {
|
return bcrypto.sha256(o.redeem.output);
|
||||||
if (!o.hash) return
|
});
|
||||||
return bscript.compile([
|
lazy.prop(o, 'output', () => {
|
||||||
OPS.OP_0,
|
if (!o.hash)
|
||||||
o.hash
|
return;
|
||||||
])
|
return bscript.compile([OPS.OP_0, o.hash]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'redeem', function () {
|
lazy.prop(o, 'redeem', () => {
|
||||||
if (!a.witness) return
|
if (!a.witness)
|
||||||
|
return;
|
||||||
return {
|
return {
|
||||||
output: a.witness[a.witness.length - 1],
|
output: a.witness[a.witness.length - 1],
|
||||||
input: EMPTY_BUFFER,
|
input: EMPTY_BUFFER,
|
||||||
witness: a.witness.slice(0, -1)
|
witness: a.witness.slice(0, -1),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'input', function () {
|
lazy.prop(o, 'input', () => {
|
||||||
if (!o.witness) return
|
if (!o.witness)
|
||||||
return EMPTY_BUFFER
|
return;
|
||||||
})
|
return EMPTY_BUFFER;
|
||||||
lazy.prop(o, 'witness', function () {
|
});
|
||||||
|
lazy.prop(o, 'witness', () => {
|
||||||
// transform redeem input to witness stack?
|
// transform redeem input to witness stack?
|
||||||
if (
|
if (a.redeem &&
|
||||||
a.redeem &&
|
|
||||||
a.redeem.input &&
|
a.redeem.input &&
|
||||||
a.redeem.input.length > 0 &&
|
a.redeem.input.length > 0 &&
|
||||||
a.redeem.output &&
|
a.redeem.output &&
|
||||||
a.redeem.output.length > 0
|
a.redeem.output.length > 0) {
|
||||||
) {
|
const stack = bscript.toStack(_rchunks());
|
||||||
const stack = bscript.toStack(_rchunks())
|
|
||||||
|
|
||||||
// assign, and blank the existing input
|
// assign, and blank the existing input
|
||||||
o.redeem = Object.assign({ witness: stack }, a.redeem)
|
o.redeem = Object.assign({ witness: stack }, a.redeem);
|
||||||
o.redeem.input = EMPTY_BUFFER
|
o.redeem.input = EMPTY_BUFFER;
|
||||||
return [].concat(stack, a.redeem.output)
|
return [].concat(stack, a.redeem.output);
|
||||||
}
|
}
|
||||||
|
if (!a.redeem)
|
||||||
if (!a.redeem) return
|
return;
|
||||||
if (!a.redeem.output) return
|
if (!a.redeem.output)
|
||||||
if (!a.redeem.witness) return
|
return;
|
||||||
return [].concat(a.redeem.witness, a.redeem.output)
|
if (!a.redeem.witness)
|
||||||
})
|
return;
|
||||||
|
return [].concat(a.redeem.witness, a.redeem.output);
|
||||||
|
});
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (_address().prefix !== network.bech32) throw new TypeError('Invalid prefix or Network mismatch')
|
if (_address().prefix !== network.bech32)
|
||||||
if (_address().version !== 0x00) throw new TypeError('Invalid address version')
|
throw new TypeError('Invalid prefix or Network mismatch');
|
||||||
if (_address().data.length !== 32) throw new TypeError('Invalid address data')
|
if (_address().version !== 0x00)
|
||||||
hash = _address().data
|
throw new TypeError('Invalid address version');
|
||||||
|
if (_address().data.length !== 32)
|
||||||
|
throw new TypeError('Invalid address data');
|
||||||
|
hash = _address().data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (a.output.length !== 34 ||
|
||||||
a.output.length !== 34 ||
|
|
||||||
a.output[0] !== OPS.OP_0 ||
|
a.output[0] !== OPS.OP_0 ||
|
||||||
a.output[1] !== 0x20) throw new TypeError('Output is invalid')
|
a.output[1] !== 0x20)
|
||||||
const hash2 = a.output.slice(2)
|
throw new TypeError('Output is invalid');
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
const hash2 = a.output.slice(2);
|
||||||
else hash = hash2
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.redeem) {
|
if (a.redeem) {
|
||||||
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch')
|
if (a.redeem.network && a.redeem.network !== network)
|
||||||
|
throw new TypeError('Network mismatch');
|
||||||
// is there two redeem sources?
|
// is there two redeem sources?
|
||||||
if (
|
if (a.redeem.input &&
|
||||||
a.redeem.input &&
|
|
||||||
a.redeem.input.length > 0 &&
|
a.redeem.input.length > 0 &&
|
||||||
a.redeem.witness &&
|
a.redeem.witness &&
|
||||||
a.redeem.witness.length > 0
|
a.redeem.witness.length > 0)
|
||||||
) throw new TypeError('Ambiguous witness source')
|
throw new TypeError('Ambiguous witness source');
|
||||||
|
|
||||||
// is the redeem output non-empty?
|
// is the redeem output non-empty?
|
||||||
if (a.redeem.output) {
|
if (a.redeem.output) {
|
||||||
if (bscript.decompile(a.redeem.output).length === 0) throw new TypeError('Redeem.output is invalid')
|
if (bscript.decompile(a.redeem.output).length === 0)
|
||||||
|
throw new TypeError('Redeem.output is invalid');
|
||||||
// match hash against other sources
|
// match hash against other sources
|
||||||
const hash2 = bcrypto.sha256(a.redeem.output)
|
const hash2 = bcrypto.sha256(a.redeem.output);
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
else hash = hash2
|
throw new TypeError('Hash mismatch');
|
||||||
|
else
|
||||||
|
hash = hash2;
|
||||||
}
|
}
|
||||||
|
if (a.redeem.input && !bscript.isPushOnly(_rchunks()))
|
||||||
if (a.redeem.input && !bscript.isPushOnly(_rchunks())) throw new TypeError('Non push-only scriptSig')
|
throw new TypeError('Non push-only scriptSig');
|
||||||
if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch')
|
if (a.witness &&
|
||||||
|
a.redeem.witness &&
|
||||||
|
!stacksEqual(a.witness, a.redeem.witness))
|
||||||
|
throw new TypeError('Witness and redeem.witness mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.witness) {
|
if (a.witness) {
|
||||||
if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch')
|
if (a.redeem &&
|
||||||
|
a.redeem.output &&
|
||||||
|
!a.redeem.output.equals(a.witness[a.witness.length - 1]))
|
||||||
|
throw new TypeError('Witness and redeem.output mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2wsh = p2wsh;
|
||||||
module.exports = p2wsh
|
|
||||||
|
|
318
src/script.js
318
src/script.js
|
@ -1,205 +1,191 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
"use strict";
|
||||||
const bip66 = require('bip66')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const ecc = require('tiny-secp256k1')
|
const scriptNumber = require("./script_number");
|
||||||
const pushdata = require('pushdata-bitcoin')
|
const scriptSignature = require("./script_signature");
|
||||||
const typeforce = require('typeforce')
|
const types = require("./types");
|
||||||
const types = require('./types')
|
const bip66 = require('bip66');
|
||||||
const scriptNumber = require('./script_number')
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const pushdata = require('pushdata-bitcoin');
|
||||||
const OPS = require('bitcoin-ops')
|
const typeforce = require('typeforce');
|
||||||
const REVERSE_OPS = require('bitcoin-ops/map')
|
exports.OPS = require('bitcoin-ops');
|
||||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const REVERSE_OPS = require('bitcoin-ops/map');
|
||||||
|
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1
|
||||||
function isOPInt (value) {
|
function isOPInt(value) {
|
||||||
return types.Number(value) &&
|
return (types.Number(value) &&
|
||||||
((value === OPS.OP_0) ||
|
(value === exports.OPS.OP_0 ||
|
||||||
(value >= OPS.OP_1 && value <= OPS.OP_16) ||
|
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) ||
|
||||||
(value === OPS.OP_1NEGATE))
|
value === exports.OPS.OP_1NEGATE));
|
||||||
}
|
}
|
||||||
|
function isPushOnlyChunk(value) {
|
||||||
function isPushOnlyChunk (value) {
|
return types.Buffer(value) || isOPInt(value);
|
||||||
return types.Buffer(value) || isOPInt(value)
|
|
||||||
}
|
}
|
||||||
|
function isPushOnly(value) {
|
||||||
function isPushOnly (value) {
|
return types.Array(value) && value.every(isPushOnlyChunk);
|
||||||
return types.Array(value) && value.every(isPushOnlyChunk)
|
|
||||||
}
|
}
|
||||||
|
exports.isPushOnly = isPushOnly;
|
||||||
function asMinimalOP (buffer) {
|
function asMinimalOP(buffer) {
|
||||||
if (buffer.length === 0) return OPS.OP_0
|
if (buffer.length === 0)
|
||||||
if (buffer.length !== 1) return
|
return exports.OPS.OP_0;
|
||||||
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]
|
if (buffer.length !== 1)
|
||||||
if (buffer[0] === 0x81) return OPS.OP_1NEGATE
|
return;
|
||||||
|
if (buffer[0] >= 1 && buffer[0] <= 16)
|
||||||
|
return OP_INT_BASE + buffer[0];
|
||||||
|
if (buffer[0] === 0x81)
|
||||||
|
return exports.OPS.OP_1NEGATE;
|
||||||
}
|
}
|
||||||
|
function chunksIsBuffer(buf) {
|
||||||
function compile (chunks) {
|
return Buffer.isBuffer(buf);
|
||||||
|
}
|
||||||
|
function chunksIsArray(buf) {
|
||||||
|
return types.Array(buf);
|
||||||
|
}
|
||||||
|
function singleChunkIsBuffer(buf) {
|
||||||
|
return Buffer.isBuffer(buf);
|
||||||
|
}
|
||||||
|
function compile(chunks) {
|
||||||
// TODO: remove me
|
// TODO: remove me
|
||||||
if (Buffer.isBuffer(chunks)) return chunks
|
if (chunksIsBuffer(chunks))
|
||||||
|
return chunks;
|
||||||
typeforce(types.Array, chunks)
|
typeforce(types.Array, chunks);
|
||||||
|
const bufferSize = chunks.reduce((accum, chunk) => {
|
||||||
const bufferSize = chunks.reduce(function (accum, chunk) {
|
|
||||||
// data chunk
|
// data chunk
|
||||||
if (Buffer.isBuffer(chunk)) {
|
if (singleChunkIsBuffer(chunk)) {
|
||||||
// adhere to BIP62.3, minimal push policy
|
// adhere to BIP62.3, minimal push policy
|
||||||
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
|
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
|
||||||
return accum + 1
|
return accum + 1;
|
||||||
}
|
}
|
||||||
|
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
|
||||||
return accum + pushdata.encodingLength(chunk.length) + chunk.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode
|
// opcode
|
||||||
return accum + 1
|
return accum + 1;
|
||||||
}, 0.0)
|
}, 0.0);
|
||||||
|
const buffer = Buffer.allocUnsafe(bufferSize);
|
||||||
const buffer = Buffer.allocUnsafe(bufferSize)
|
let offset = 0;
|
||||||
let offset = 0
|
chunks.forEach(chunk => {
|
||||||
|
|
||||||
chunks.forEach(function (chunk) {
|
|
||||||
// data chunk
|
// data chunk
|
||||||
if (Buffer.isBuffer(chunk)) {
|
if (singleChunkIsBuffer(chunk)) {
|
||||||
// adhere to BIP62.3, minimal push policy
|
// adhere to BIP62.3, minimal push policy
|
||||||
const opcode = asMinimalOP(chunk)
|
const opcode = asMinimalOP(chunk);
|
||||||
if (opcode !== undefined) {
|
if (opcode !== undefined) {
|
||||||
buffer.writeUInt8(opcode, offset)
|
buffer.writeUInt8(opcode, offset);
|
||||||
offset += 1
|
offset += 1;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
offset += pushdata.encode(buffer, chunk.length, offset);
|
||||||
offset += pushdata.encode(buffer, chunk.length, offset)
|
chunk.copy(buffer, offset);
|
||||||
chunk.copy(buffer, offset)
|
offset += chunk.length;
|
||||||
offset += chunk.length
|
|
||||||
|
|
||||||
// opcode
|
// opcode
|
||||||
} else {
|
|
||||||
buffer.writeUInt8(chunk, offset)
|
|
||||||
offset += 1
|
|
||||||
}
|
}
|
||||||
})
|
else {
|
||||||
|
buffer.writeUInt8(chunk, offset);
|
||||||
if (offset !== buffer.length) throw new Error('Could not decode chunks')
|
offset += 1;
|
||||||
return buffer
|
}
|
||||||
|
});
|
||||||
|
if (offset !== buffer.length)
|
||||||
|
throw new Error('Could not decode chunks');
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
exports.compile = compile;
|
||||||
function decompile (buffer) {
|
function decompile(buffer) {
|
||||||
// TODO: remove me
|
// TODO: remove me
|
||||||
if (types.Array(buffer)) return buffer
|
if (chunksIsArray(buffer))
|
||||||
|
return buffer;
|
||||||
typeforce(types.Buffer, buffer)
|
typeforce(types.Buffer, buffer);
|
||||||
|
const chunks = [];
|
||||||
const chunks = []
|
let i = 0;
|
||||||
let i = 0
|
|
||||||
|
|
||||||
while (i < buffer.length) {
|
while (i < buffer.length) {
|
||||||
const opcode = buffer[i]
|
const opcode = buffer[i];
|
||||||
|
|
||||||
// data chunk
|
// data chunk
|
||||||
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) {
|
if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) {
|
||||||
const d = pushdata.decode(buffer, i)
|
const d = pushdata.decode(buffer, i);
|
||||||
|
|
||||||
// did reading a pushDataInt fail?
|
// did reading a pushDataInt fail?
|
||||||
if (d === null) return null
|
if (d === null)
|
||||||
i += d.size
|
return null;
|
||||||
|
i += d.size;
|
||||||
// attempt to read too much data?
|
// attempt to read too much data?
|
||||||
if (i + d.number > buffer.length) return null
|
if (i + d.number > buffer.length)
|
||||||
|
return null;
|
||||||
const data = buffer.slice(i, i + d.number)
|
const data = buffer.slice(i, i + d.number);
|
||||||
i += d.number
|
i += d.number;
|
||||||
|
|
||||||
// decompile minimally
|
// decompile minimally
|
||||||
const op = asMinimalOP(data)
|
const op = asMinimalOP(data);
|
||||||
if (op !== undefined) {
|
if (op !== undefined) {
|
||||||
chunks.push(op)
|
chunks.push(op);
|
||||||
} else {
|
}
|
||||||
chunks.push(data)
|
else {
|
||||||
|
chunks.push(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode
|
// opcode
|
||||||
} else {
|
}
|
||||||
chunks.push(opcode)
|
else {
|
||||||
|
chunks.push(opcode);
|
||||||
i += 1
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
exports.decompile = decompile;
|
||||||
|
function toASM(chunks) {
|
||||||
|
if (chunksIsBuffer(chunks)) {
|
||||||
|
chunks = decompile(chunks);
|
||||||
|
}
|
||||||
return chunks
|
return chunks
|
||||||
}
|
.map(chunk => {
|
||||||
|
|
||||||
function toASM (chunks) {
|
|
||||||
if (Buffer.isBuffer(chunks)) {
|
|
||||||
chunks = decompile(chunks)
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks.map(function (chunk) {
|
|
||||||
// data?
|
// data?
|
||||||
if (Buffer.isBuffer(chunk)) {
|
if (singleChunkIsBuffer(chunk)) {
|
||||||
const op = asMinimalOP(chunk)
|
const op = asMinimalOP(chunk);
|
||||||
if (op === undefined) return chunk.toString('hex')
|
if (op === undefined)
|
||||||
chunk = op
|
return chunk.toString('hex');
|
||||||
|
chunk = op;
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode!
|
// opcode!
|
||||||
return REVERSE_OPS[chunk]
|
return REVERSE_OPS[chunk];
|
||||||
}).join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
exports.toASM = toASM;
|
||||||
function isCanonicalPubKey (buffer) {
|
function fromASM(asm) {
|
||||||
return ecc.isPoint(buffer)
|
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');
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
exports.fromASM = fromASM;
|
||||||
function isDefinedHashType (hashType) {
|
function toStack(chunks) {
|
||||||
const hashTypeMod = hashType & ~0x80
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.toStack = toStack;
|
||||||
|
function isCanonicalPubKey(buffer) {
|
||||||
|
return ecc.isPoint(buffer);
|
||||||
|
}
|
||||||
|
exports.isCanonicalPubKey = isCanonicalPubKey;
|
||||||
|
function isDefinedHashType(hashType) {
|
||||||
|
const hashTypeMod = hashType & ~0x80;
|
||||||
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
||||||
return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
|
||||||
}
|
}
|
||||||
|
exports.isDefinedHashType = isDefinedHashType;
|
||||||
function isCanonicalScriptSignature (buffer) {
|
function isCanonicalScriptSignature(buffer) {
|
||||||
if (!Buffer.isBuffer(buffer)) return false
|
if (!Buffer.isBuffer(buffer))
|
||||||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
return false;
|
||||||
|
if (!isDefinedHashType(buffer[buffer.length - 1]))
|
||||||
return bip66.check(buffer.slice(0, -1))
|
return false;
|
||||||
}
|
return bip66.check(buffer.slice(0, -1));
|
||||||
|
|
||||||
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.isCanonicalScriptSignature = isCanonicalScriptSignature;
|
||||||
|
// tslint:disable-next-line variable-name
|
||||||
|
exports.number = scriptNumber;
|
||||||
|
exports.signature = scriptSignature;
|
||||||
|
|
|
@ -1,67 +1,65 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
function decode (buffer, maxLength, minimal) {
|
function decode(buffer, maxLength, minimal) {
|
||||||
maxLength = maxLength || 4
|
maxLength = maxLength || 4;
|
||||||
minimal = minimal === undefined ? true : minimal
|
minimal = minimal === undefined ? true : minimal;
|
||||||
|
const length = buffer.length;
|
||||||
const length = buffer.length
|
if (length === 0)
|
||||||
if (length === 0) return 0
|
return 0;
|
||||||
if (length > maxLength) throw new TypeError('Script number overflow')
|
if (length > maxLength)
|
||||||
|
throw new TypeError('Script number overflow');
|
||||||
if (minimal) {
|
if (minimal) {
|
||||||
if ((buffer[length - 1] & 0x7f) === 0) {
|
if ((buffer[length - 1] & 0x7f) === 0) {
|
||||||
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) throw new Error('Non-minimally encoded script number')
|
if (length <= 1 || (buffer[length - 2] & 0x80) === 0)
|
||||||
|
throw new Error('Non-minimally encoded script number');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 40-bit
|
// 40-bit
|
||||||
if (length === 5) {
|
if (length === 5) {
|
||||||
const a = buffer.readUInt32LE(0)
|
const a = buffer.readUInt32LE(0);
|
||||||
const b = buffer.readUInt8(4)
|
const b = buffer.readUInt8(4);
|
||||||
|
if (b & 0x80)
|
||||||
if (b & 0x80) return -(((b & ~0x80) * 0x100000000) + a)
|
return -((b & ~0x80) * 0x100000000 + a);
|
||||||
return (b * 0x100000000) + a
|
return b * 0x100000000 + a;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 32-bit / 24-bit / 16-bit / 8-bit
|
// 32-bit / 24-bit / 16-bit / 8-bit
|
||||||
let result = 0
|
let result = 0;
|
||||||
for (var i = 0; i < length; ++i) {
|
for (let i = 0; i < length; ++i) {
|
||||||
result |= buffer[i] << (8 * i)
|
result |= buffer[i] << (8 * i);
|
||||||
}
|
}
|
||||||
|
if (buffer[length - 1] & 0x80)
|
||||||
if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1))))
|
return -(result & ~(0x80 << (8 * (length - 1))));
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
exports.decode = decode;
|
||||||
function scriptNumSize (i) {
|
function scriptNumSize(i) {
|
||||||
return i > 0x7fffffff ? 5
|
return i > 0x7fffffff
|
||||||
: i > 0x7fffff ? 4
|
? 5
|
||||||
: i > 0x7fff ? 3
|
: i > 0x7fffff
|
||||||
: i > 0x7f ? 2
|
? 4
|
||||||
: i > 0x00 ? 1
|
: i > 0x7fff
|
||||||
: 0
|
? 3
|
||||||
|
: i > 0x7f
|
||||||
|
? 2
|
||||||
|
: i > 0x00
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
|
function encode(_number) {
|
||||||
function encode (number) {
|
let value = Math.abs(_number);
|
||||||
let value = Math.abs(number)
|
const size = scriptNumSize(value);
|
||||||
const size = scriptNumSize(value)
|
const buffer = Buffer.allocUnsafe(size);
|
||||||
const buffer = Buffer.allocUnsafe(size)
|
const negative = _number < 0;
|
||||||
const negative = number < 0
|
for (let i = 0; i < size; ++i) {
|
||||||
|
buffer.writeUInt8(value & 0xff, i);
|
||||||
for (var i = 0; i < size; ++i) {
|
value >>= 8;
|
||||||
buffer.writeUInt8(value & 0xff, i)
|
|
||||||
value >>= 8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer[size - 1] & 0x80) {
|
if (buffer[size - 1] & 0x80) {
|
||||||
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1)
|
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1);
|
||||||
} else if (negative) {
|
|
||||||
buffer[size - 1] |= 0x80
|
|
||||||
}
|
}
|
||||||
|
else if (negative) {
|
||||||
return buffer
|
buffer[size - 1] |= 0x80;
|
||||||
}
|
}
|
||||||
|
return buffer;
|
||||||
module.exports = {
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
}
|
||||||
|
exports.encode = encode;
|
||||||
|
|
|
@ -1,64 +1,53 @@
|
||||||
const bip66 = require('bip66')
|
"use strict";
|
||||||
const Buffer = require('safe-buffer').Buffer
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const typeforce = require('typeforce')
|
const types = require("./types");
|
||||||
const types = require('./types')
|
const bip66 = require('bip66');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
const ZERO = Buffer.alloc(1, 0)
|
const ZERO = Buffer.alloc(1, 0);
|
||||||
function toDER (x) {
|
function toDER(x) {
|
||||||
let i = 0
|
let i = 0;
|
||||||
while (x[i] === 0) ++i
|
while (x[i] === 0)
|
||||||
if (i === x.length) return ZERO
|
++i;
|
||||||
x = x.slice(i)
|
if (i === x.length)
|
||||||
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length)
|
return ZERO;
|
||||||
return x
|
x = x.slice(i);
|
||||||
|
if (x[0] & 0x80)
|
||||||
|
return Buffer.concat([ZERO, x], 1 + x.length);
|
||||||
|
return x;
|
||||||
}
|
}
|
||||||
|
function fromDER(x) {
|
||||||
function fromDER (x) {
|
if (x[0] === 0x00)
|
||||||
if (x[0] === 0x00) x = x.slice(1)
|
x = x.slice(1);
|
||||||
const buffer = Buffer.alloc(32, 0)
|
const buffer = Buffer.alloc(32, 0);
|
||||||
const bstart = Math.max(0, 32 - x.length)
|
const bstart = Math.max(0, 32 - x.length);
|
||||||
x.copy(buffer, bstart)
|
x.copy(buffer, bstart);
|
||||||
return buffer
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
||||||
function decode (buffer) {
|
function decode(buffer) {
|
||||||
const hashType = buffer.readUInt8(buffer.length - 1)
|
const hashType = buffer.readUInt8(buffer.length - 1);
|
||||||
const hashTypeMod = hashType & ~0x80
|
const hashTypeMod = hashType & ~0x80;
|
||||||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
|
if (hashTypeMod <= 0 || hashTypeMod >= 4)
|
||||||
|
throw new Error('Invalid hashType ' + hashType);
|
||||||
const decode = bip66.decode(buffer.slice(0, -1))
|
const decoded = bip66.decode(buffer.slice(0, -1));
|
||||||
const r = fromDER(decode.r)
|
const r = fromDER(decoded.r);
|
||||||
const s = fromDER(decode.s)
|
const s = fromDER(decoded.s);
|
||||||
|
const signature = Buffer.concat([r, s], 64);
|
||||||
return {
|
return { signature, hashType };
|
||||||
signature: Buffer.concat([r, s], 64),
|
|
||||||
hashType: hashType
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
exports.decode = decode;
|
||||||
function encode (signature, hashType) {
|
function encode(signature, hashType) {
|
||||||
typeforce({
|
typeforce({
|
||||||
signature: types.BufferN(64),
|
signature: types.BufferN(64),
|
||||||
hashType: types.UInt8
|
hashType: types.UInt8,
|
||||||
}, { signature, hashType })
|
}, { signature, hashType });
|
||||||
|
const hashTypeMod = hashType & ~0x80;
|
||||||
const hashTypeMod = hashType & ~0x80
|
if (hashTypeMod <= 0 || hashTypeMod >= 4)
|
||||||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
|
throw new Error('Invalid hashType ' + hashType);
|
||||||
|
const hashTypeBuffer = Buffer.allocUnsafe(1);
|
||||||
const hashTypeBuffer = Buffer.allocUnsafe(1)
|
hashTypeBuffer.writeUInt8(hashType, 0);
|
||||||
hashTypeBuffer.writeUInt8(hashType, 0)
|
const r = toDER(signature.slice(0, 32));
|
||||||
|
const s = toDER(signature.slice(32, 64));
|
||||||
const r = toDER(signature.slice(0, 32))
|
return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]);
|
||||||
const s = toDER(signature.slice(32, 64))
|
|
||||||
|
|
||||||
return Buffer.concat([
|
|
||||||
bip66.encode(r, s),
|
|
||||||
hashTypeBuffer
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
}
|
||||||
|
exports.encode = encode;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
output: require('./output')
|
const input = require("./input");
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
|
"use strict";
|
||||||
// OP_0 [signatures ...]
|
// OP_0 [signatures ...]
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require("../../script");
|
||||||
|
function partialSignature(value) {
|
||||||
function partialSignature (value) {
|
return (value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value));
|
||||||
return value === OPS.OP_0 || bscript.isCanonicalScriptSignature(value)
|
|
||||||
}
|
}
|
||||||
|
function check(script, allowIncomplete) {
|
||||||
function check (script, allowIncomplete) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
if (chunks.length < 2)
|
||||||
if (chunks.length < 2) return false
|
return false;
|
||||||
if (chunks[0] !== OPS.OP_0) return false
|
if (chunks[0] !== script_1.OPS.OP_0)
|
||||||
|
return false;
|
||||||
if (allowIncomplete) {
|
if (allowIncomplete) {
|
||||||
return chunks.slice(1).every(partialSignature)
|
return chunks.slice(1).every(partialSignature);
|
||||||
}
|
}
|
||||||
|
return chunks.slice(1).every(bscript.isCanonicalScriptSignature);
|
||||||
return chunks.slice(1).every(bscript.isCanonicalScriptSignature)
|
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'multisig input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'multisig input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,29 +1,36 @@
|
||||||
|
"use strict";
|
||||||
// m [pubKeys ...] n OP_CHECKMULTISIG
|
// m [pubKeys ...] n OP_CHECKMULTISIG
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const types = require('../../types')
|
const script_1 = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const types = require("../../types");
|
||||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1
|
||||||
|
function check(script, allowIncomplete) {
|
||||||
function check (script, allowIncomplete) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
if (chunks.length < 4)
|
||||||
|
return false;
|
||||||
if (chunks.length < 4) return false
|
if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG)
|
||||||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false
|
return false;
|
||||||
if (!types.Number(chunks[0])) return false
|
if (!types.Number(chunks[0]))
|
||||||
if (!types.Number(chunks[chunks.length - 2])) return false
|
return false;
|
||||||
const m = chunks[0] - OP_INT_BASE
|
if (!types.Number(chunks[chunks.length - 2]))
|
||||||
const n = chunks[chunks.length - 2] - OP_INT_BASE
|
return false;
|
||||||
|
const m = chunks[0] - OP_INT_BASE;
|
||||||
if (m <= 0) return false
|
const n = chunks[chunks.length - 2] - OP_INT_BASE;
|
||||||
if (n > 16) return false
|
if (m <= 0)
|
||||||
if (m > n) return false
|
return false;
|
||||||
if (n !== chunks.length - 3) return false
|
if (n > 16)
|
||||||
if (allowIncomplete) return true
|
return false;
|
||||||
|
if (m > n)
|
||||||
const keys = chunks.slice(1, -2)
|
return false;
|
||||||
return keys.every(bscript.isCanonicalPubKey)
|
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' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'multi-sig output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
// OP_RETURN {data}
|
// OP_RETURN {data}
|
||||||
|
const bscript = require("../script");
|
||||||
const bscript = require('../script')
|
const OPS = bscript.OPS;
|
||||||
const OPS = require('bitcoin-ops')
|
function check(script) {
|
||||||
|
const buffer = bscript.compile(script);
|
||||||
function check (script) {
|
return buffer.length > 1 && buffer[0] === OPS.OP_RETURN;
|
||||||
const buffer = bscript.compile(script)
|
|
||||||
|
|
||||||
return buffer.length > 1 &&
|
|
||||||
buffer[0] === OPS.OP_RETURN
|
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'null data output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { output: { check: check } }
|
return 'null data output';
|
||||||
|
};
|
||||||
|
const output = { check };
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
output: require('./output')
|
const input = require("./input");
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
|
"use strict";
|
||||||
// {signature}
|
// {signature}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (chunks.length === 1 &&
|
||||||
|
bscript.isCanonicalScriptSignature(chunks[0]));
|
||||||
return chunks.length === 1 &&
|
|
||||||
bscript.isCanonicalScriptSignature(chunks[0])
|
|
||||||
}
|
|
||||||
check.toJSON = function () { return 'pubKey input' }
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
check: check
|
|
||||||
}
|
}
|
||||||
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
|
return 'pubKey input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
"use strict";
|
||||||
// {pubKey} OP_CHECKSIG
|
// {pubKey} OP_CHECKSIG
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (chunks.length === 2 &&
|
||||||
|
|
||||||
return chunks.length === 2 &&
|
|
||||||
bscript.isCanonicalPubKey(chunks[0]) &&
|
bscript.isCanonicalPubKey(chunks[0]) &&
|
||||||
chunks[1] === OPS.OP_CHECKSIG
|
chunks[1] === script_1.OPS.OP_CHECKSIG);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'pubKey output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'pubKey output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
output: require('./output')
|
const input = require("./input");
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
|
"use strict";
|
||||||
// {signature} {pubKey}
|
// {signature} {pubKey}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (chunks.length === 2 &&
|
||||||
|
|
||||||
return chunks.length === 2 &&
|
|
||||||
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
||||||
bscript.isCanonicalPubKey(chunks[1])
|
bscript.isCanonicalPubKey(chunks[1]));
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'pubKeyHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'pubKeyHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
|
"use strict";
|
||||||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return (buffer.length === 25 &&
|
||||||
|
buffer[0] === script_1.OPS.OP_DUP &&
|
||||||
return buffer.length === 25 &&
|
buffer[1] === script_1.OPS.OP_HASH160 &&
|
||||||
buffer[0] === OPS.OP_DUP &&
|
|
||||||
buffer[1] === OPS.OP_HASH160 &&
|
|
||||||
buffer[2] === 0x14 &&
|
buffer[2] === 0x14 &&
|
||||||
buffer[23] === OPS.OP_EQUALVERIFY &&
|
buffer[23] === script_1.OPS.OP_EQUALVERIFY &&
|
||||||
buffer[24] === OPS.OP_CHECKSIG
|
buffer[24] === script_1.OPS.OP_CHECKSIG);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'pubKeyHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'pubKeyHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
output: require('./output')
|
const input = require("./input");
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,48 +1,44 @@
|
||||||
|
"use strict";
|
||||||
// <scriptSig> {serialized scriptPubKey script}
|
// <scriptSig> {serialized scriptPubKey script}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const Buffer = require('safe-buffer').Buffer
|
const bscript = require("../../script");
|
||||||
const bscript = require('../../script')
|
const p2ms = require("../multisig");
|
||||||
|
const p2pk = require("../pubkey");
|
||||||
const p2ms = require('../multisig/')
|
const p2pkh = require("../pubkeyhash");
|
||||||
const p2pk = require('../pubkey/')
|
const p2wpkho = require("../witnesspubkeyhash/output");
|
||||||
const p2pkh = require('../pubkeyhash/')
|
const p2wsho = require("../witnessscripthash/output");
|
||||||
const p2wpkho = require('../witnesspubkeyhash/output')
|
function check(script, allowIncomplete) {
|
||||||
const p2wsho = require('../witnessscripthash/output')
|
const chunks = bscript.decompile(script);
|
||||||
|
if (chunks.length < 1)
|
||||||
function check (script, allowIncomplete) {
|
return false;
|
||||||
const chunks = bscript.decompile(script)
|
const lastChunk = chunks[chunks.length - 1];
|
||||||
if (chunks.length < 1) return false
|
if (!Buffer.isBuffer(lastChunk))
|
||||||
|
return false;
|
||||||
const lastChunk = chunks[chunks.length - 1]
|
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1)));
|
||||||
if (!Buffer.isBuffer(lastChunk)) return false
|
const redeemScriptChunks = bscript.decompile(lastChunk);
|
||||||
|
|
||||||
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1)))
|
|
||||||
const redeemScriptChunks = bscript.decompile(lastChunk)
|
|
||||||
|
|
||||||
// is redeemScript a valid script?
|
// is redeemScript a valid script?
|
||||||
if (!redeemScriptChunks) return false
|
if (!redeemScriptChunks)
|
||||||
|
return false;
|
||||||
// is redeemScriptSig push only?
|
// is redeemScriptSig push only?
|
||||||
if (!bscript.isPushOnly(scriptSigChunks)) return false
|
if (!bscript.isPushOnly(scriptSigChunks))
|
||||||
|
return false;
|
||||||
// is witness?
|
// is witness?
|
||||||
if (chunks.length === 1) {
|
if (chunks.length === 1) {
|
||||||
return p2wsho.check(redeemScriptChunks) ||
|
return (p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks));
|
||||||
p2wpkho.check(redeemScriptChunks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// match types
|
// match types
|
||||||
if (p2pkh.input.check(scriptSigChunks) &&
|
if (p2pkh.input.check(scriptSigChunks) &&
|
||||||
p2pkh.output.check(redeemScriptChunks)) return true
|
p2pkh.output.check(redeemScriptChunks))
|
||||||
|
return true;
|
||||||
if (p2ms.input.check(scriptSigChunks, allowIncomplete) &&
|
if (p2ms.input.check(scriptSigChunks, allowIncomplete) &&
|
||||||
p2ms.output.check(redeemScriptChunks)) return true
|
p2ms.output.check(redeemScriptChunks))
|
||||||
|
return true;
|
||||||
if (p2pk.input.check(scriptSigChunks) &&
|
if (p2pk.input.check(scriptSigChunks) &&
|
||||||
p2pk.output.check(redeemScriptChunks)) return true
|
p2pk.output.check(redeemScriptChunks))
|
||||||
|
return true;
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'scriptHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'scriptHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
|
"use strict";
|
||||||
// OP_HASH160 {scriptHash} OP_EQUAL
|
// OP_HASH160 {scriptHash} OP_EQUAL
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return (buffer.length === 23 &&
|
||||||
|
buffer[0] === script_1.OPS.OP_HASH160 &&
|
||||||
return buffer.length === 23 &&
|
|
||||||
buffer[0] === OPS.OP_HASH160 &&
|
|
||||||
buffer[1] === 0x14 &&
|
buffer[1] === 0x14 &&
|
||||||
buffer[22] === OPS.OP_EQUAL
|
buffer[22] === script_1.OPS.OP_EQUAL);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'scriptHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'scriptHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
output: require('./output')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
}
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,42 +1,32 @@
|
||||||
|
"use strict";
|
||||||
// OP_RETURN {aa21a9ed} {commitment}
|
// OP_RETURN {aa21a9ed} {commitment}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const Buffer = require('safe-buffer').Buffer
|
const bscript = require("../../script");
|
||||||
const bscript = require('../../script')
|
const script_1 = require("../../script");
|
||||||
const types = require('../../types')
|
const types = require("../../types");
|
||||||
const typeforce = require('typeforce')
|
const typeforce = require('typeforce');
|
||||||
const OPS = require('bitcoin-ops')
|
const HEADER = Buffer.from('aa21a9ed', 'hex');
|
||||||
|
function check(script) {
|
||||||
const HEADER = Buffer.from('aa21a9ed', 'hex')
|
const buffer = bscript.compile(script);
|
||||||
|
return (buffer.length > 37 &&
|
||||||
function check (script) {
|
buffer[0] === script_1.OPS.OP_RETURN &&
|
||||||
const buffer = bscript.compile(script)
|
|
||||||
|
|
||||||
return buffer.length > 37 &&
|
|
||||||
buffer[0] === OPS.OP_RETURN &&
|
|
||||||
buffer[1] === 0x24 &&
|
buffer[1] === 0x24 &&
|
||||||
buffer.slice(2, 6).equals(HEADER)
|
buffer.slice(2, 6).equals(HEADER));
|
||||||
}
|
}
|
||||||
|
exports.check = check;
|
||||||
check.toJSON = function () { return 'Witness commitment output' }
|
check.toJSON = () => {
|
||||||
|
return 'Witness commitment output';
|
||||||
function encode (commitment) {
|
};
|
||||||
typeforce(types.Hash256bit, commitment)
|
function encode(commitment) {
|
||||||
|
typeforce(types.Hash256bit, commitment);
|
||||||
const buffer = Buffer.allocUnsafe(36)
|
const buffer = Buffer.allocUnsafe(36);
|
||||||
HEADER.copy(buffer, 0)
|
HEADER.copy(buffer, 0);
|
||||||
commitment.copy(buffer, 4)
|
commitment.copy(buffer, 4);
|
||||||
|
return bscript.compile([script_1.OPS.OP_RETURN, buffer]);
|
||||||
return bscript.compile([OPS.OP_RETURN, buffer])
|
|
||||||
}
|
}
|
||||||
|
exports.encode = encode;
|
||||||
function decode (buffer) {
|
function decode(buffer) {
|
||||||
typeforce(check, buffer)
|
typeforce(check, buffer);
|
||||||
|
return bscript.decompile(buffer)[1].slice(4, 36);
|
||||||
return bscript.decompile(buffer)[1].slice(4, 36)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
check: check,
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
}
|
||||||
|
exports.decode = decode;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
output: require('./output')
|
const input = require("./input");
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
|
"use strict";
|
||||||
// {signature} {pubKey}
|
// {signature} {pubKey}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
|
function isCompressedCanonicalPubKey(pubKey) {
|
||||||
function isCompressedCanonicalPubKey (pubKey) {
|
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33;
|
||||||
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33
|
|
||||||
}
|
}
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (chunks.length === 2 &&
|
||||||
|
|
||||||
return chunks.length === 2 &&
|
|
||||||
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
||||||
isCompressedCanonicalPubKey(chunks[1])
|
isCompressedCanonicalPubKey(chunks[1]));
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'witnessPubKeyHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'witnessPubKeyHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
|
"use strict";
|
||||||
// OP_0 {pubKeyHash}
|
// OP_0 {pubKeyHash}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return buffer.length === 22 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x14;
|
||||||
|
|
||||||
return buffer.length === 22 &&
|
|
||||||
buffer[0] === OPS.OP_0 &&
|
|
||||||
buffer[1] === 0x14
|
|
||||||
}
|
|
||||||
check.toJSON = function () { return 'Witness pubKeyHash output' }
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
check
|
|
||||||
}
|
}
|
||||||
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
|
return 'Witness pubKeyHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
"use strict";
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
output: require('./output')
|
const input = require("./input");
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require("./output");
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,39 +1,36 @@
|
||||||
|
"use strict";
|
||||||
// <scriptSig> {serialized scriptPubKey script}
|
// <scriptSig> {serialized scriptPubKey script}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const types = require('../../types')
|
const typeforce = require('typeforce');
|
||||||
const typeforce = require('typeforce')
|
const p2ms = require("../multisig");
|
||||||
|
const p2pk = require("../pubkey");
|
||||||
const p2ms = require('../multisig/')
|
const p2pkh = require("../pubkeyhash");
|
||||||
const p2pk = require('../pubkey/')
|
function check(chunks, allowIncomplete) {
|
||||||
const p2pkh = require('../pubkeyhash/')
|
typeforce(typeforce.Array, chunks);
|
||||||
|
if (chunks.length < 1)
|
||||||
function check (chunks, allowIncomplete) {
|
return false;
|
||||||
typeforce(types.Array, chunks)
|
const witnessScript = chunks[chunks.length - 1];
|
||||||
if (chunks.length < 1) return false
|
if (!Buffer.isBuffer(witnessScript))
|
||||||
|
return false;
|
||||||
const witnessScript = chunks[chunks.length - 1]
|
const witnessScriptChunks = bscript.decompile(witnessScript);
|
||||||
if (!Buffer.isBuffer(witnessScript)) return false
|
|
||||||
|
|
||||||
const witnessScriptChunks = bscript.decompile(witnessScript)
|
|
||||||
|
|
||||||
// is witnessScript a valid script?
|
// is witnessScript a valid script?
|
||||||
if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false
|
if (!witnessScriptChunks || witnessScriptChunks.length === 0)
|
||||||
|
return false;
|
||||||
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1))
|
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1));
|
||||||
|
|
||||||
// match types
|
// match types
|
||||||
if (p2pkh.input.check(witnessRawScriptSig) &&
|
if (p2pkh.input.check(witnessRawScriptSig) &&
|
||||||
p2pkh.output.check(witnessScriptChunks)) return true
|
p2pkh.output.check(witnessScriptChunks))
|
||||||
|
return true;
|
||||||
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
|
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
|
||||||
p2ms.output.check(witnessScriptChunks)) return true
|
p2ms.output.check(witnessScriptChunks))
|
||||||
|
return true;
|
||||||
if (p2pk.input.check(witnessRawScriptSig) &&
|
if (p2pk.input.check(witnessRawScriptSig) &&
|
||||||
p2pk.output.check(witnessScriptChunks)) return true
|
p2pk.output.check(witnessScriptChunks))
|
||||||
|
return true;
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'witnessScriptHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'witnessScriptHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
|
"use strict";
|
||||||
// OP_0 {scriptHash}
|
// OP_0 {scriptHash}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require("../../script");
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require("../../script");
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return buffer.length === 34 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x20;
|
||||||
|
|
||||||
return buffer.length === 34 &&
|
|
||||||
buffer[0] === OPS.OP_0 &&
|
|
||||||
buffer[1] === 0x20
|
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'Witness scriptHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'Witness scriptHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,249 +1,195 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
"use strict";
|
||||||
const bcrypto = require('./crypto')
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bscript = require('./script')
|
const bufferutils = require("./bufferutils");
|
||||||
const bufferutils = require('./bufferutils')
|
const bufferutils_1 = require("./bufferutils");
|
||||||
const opcodes = require('bitcoin-ops')
|
const bcrypto = require("./crypto");
|
||||||
const typeforce = require('typeforce')
|
const bscript = require("./script");
|
||||||
const types = require('./types')
|
const script_1 = require("./script");
|
||||||
const varuint = require('varuint-bitcoin')
|
const types = require("./types");
|
||||||
|
const typeforce = require('typeforce');
|
||||||
function varSliceSize (someScript) {
|
const varuint = require('varuint-bitcoin');
|
||||||
const length = someScript.length
|
function varSliceSize(someScript) {
|
||||||
|
const length = someScript.length;
|
||||||
return varuint.encodingLength(length) + length
|
return varuint.encodingLength(length) + length;
|
||||||
}
|
}
|
||||||
|
function vectorSize(someVector) {
|
||||||
function vectorSize (someVector) {
|
const length = someVector.length;
|
||||||
const length = someVector.length
|
return (varuint.encodingLength(length) +
|
||||||
|
someVector.reduce((sum, witness) => {
|
||||||
return varuint.encodingLength(length) + someVector.reduce(function (sum, witness) {
|
return sum + varSliceSize(witness);
|
||||||
return sum + varSliceSize(witness)
|
}, 0));
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
|
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
|
||||||
function Transaction () {
|
const EMPTY_WITNESS = [];
|
||||||
this.version = 1
|
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');
|
||||||
this.locktime = 0
|
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex');
|
||||||
this.ins = []
|
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
|
||||||
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 BLANK_OUTPUT = {
|
const BLANK_OUTPUT = {
|
||||||
script: EMPTY_SCRIPT,
|
script: EMPTY_SCRIPT,
|
||||||
valueBuffer: VALUE_UINT64_MAX
|
valueBuffer: VALUE_UINT64_MAX,
|
||||||
|
};
|
||||||
|
function isOutput(out) {
|
||||||
|
return out.value !== undefined;
|
||||||
}
|
}
|
||||||
|
class Transaction {
|
||||||
Transaction.fromBuffer = function (buffer, __noStrict) {
|
constructor() {
|
||||||
let offset = 0
|
this.version = 1;
|
||||||
function readSlice (n) {
|
this.locktime = 0;
|
||||||
offset += n
|
this.ins = [];
|
||||||
return buffer.slice(offset - n, offset)
|
this.outs = [];
|
||||||
}
|
}
|
||||||
|
static fromBuffer(buffer, _NO_STRICT) {
|
||||||
function readUInt32 () {
|
let offset = 0;
|
||||||
const i = buffer.readUInt32LE(offset)
|
function readSlice(n) {
|
||||||
offset += 4
|
offset += n;
|
||||||
return i
|
return buffer.slice(offset - n, offset);
|
||||||
}
|
}
|
||||||
|
function readUInt32() {
|
||||||
function readInt32 () {
|
const i = buffer.readUInt32LE(offset);
|
||||||
const i = buffer.readInt32LE(offset)
|
offset += 4;
|
||||||
offset += 4
|
return i;
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
|
function readInt32() {
|
||||||
function readUInt64 () {
|
const i = buffer.readInt32LE(offset);
|
||||||
const i = bufferutils.readUInt64LE(buffer, offset)
|
offset += 4;
|
||||||
offset += 8
|
return i;
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
|
function readUInt64() {
|
||||||
function readVarInt () {
|
const i = bufferutils.readUInt64LE(buffer, offset);
|
||||||
const vi = varuint.decode(buffer, offset)
|
offset += 8;
|
||||||
offset += varuint.decode.bytes
|
return i;
|
||||||
return vi
|
|
||||||
}
|
}
|
||||||
|
function readVarInt() {
|
||||||
function readVarSlice () {
|
const vi = varuint.decode(buffer, offset);
|
||||||
return readSlice(readVarInt())
|
offset += varuint.decode.bytes;
|
||||||
|
return vi;
|
||||||
}
|
}
|
||||||
|
function readVarSlice() {
|
||||||
function readVector () {
|
return readSlice(readVarInt());
|
||||||
const count = readVarInt()
|
|
||||||
const vector = []
|
|
||||||
for (var i = 0; i < count; i++) vector.push(readVarSlice())
|
|
||||||
return vector
|
|
||||||
}
|
}
|
||||||
|
function readVector() {
|
||||||
const tx = new Transaction()
|
const count = readVarInt();
|
||||||
tx.version = readInt32()
|
const vector = [];
|
||||||
|
for (let i = 0; i < count; i++)
|
||||||
const marker = buffer.readUInt8(offset)
|
vector.push(readVarSlice());
|
||||||
const flag = buffer.readUInt8(offset + 1)
|
return vector;
|
||||||
|
}
|
||||||
let hasWitnesses = false
|
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 &&
|
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
|
||||||
flag === Transaction.ADVANCED_TRANSACTION_FLAG) {
|
flag === Transaction.ADVANCED_TRANSACTION_FLAG) {
|
||||||
offset += 2
|
offset += 2;
|
||||||
hasWitnesses = true
|
hasWitnesses = true;
|
||||||
}
|
}
|
||||||
|
const vinLen = readVarInt();
|
||||||
const vinLen = readVarInt()
|
for (let i = 0; i < vinLen; ++i) {
|
||||||
for (var i = 0; i < vinLen; ++i) {
|
|
||||||
tx.ins.push({
|
tx.ins.push({
|
||||||
hash: readSlice(32),
|
hash: readSlice(32),
|
||||||
index: readUInt32(),
|
index: readUInt32(),
|
||||||
script: readVarSlice(),
|
script: readVarSlice(),
|
||||||
sequence: readUInt32(),
|
sequence: readUInt32(),
|
||||||
witness: EMPTY_WITNESS
|
witness: EMPTY_WITNESS,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
const voutLen = readVarInt();
|
||||||
const voutLen = readVarInt()
|
for (let i = 0; i < voutLen; ++i) {
|
||||||
for (i = 0; i < voutLen; ++i) {
|
|
||||||
tx.outs.push({
|
tx.outs.push({
|
||||||
value: readUInt64(),
|
value: readUInt64(),
|
||||||
script: readVarSlice()
|
script: readVarSlice(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasWitnesses) {
|
if (hasWitnesses) {
|
||||||
for (i = 0; i < vinLen; ++i) {
|
for (let i = 0; i < vinLen; ++i) {
|
||||||
tx.ins[i].witness = readVector()
|
tx.ins[i].witness = readVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
// was this pointless?
|
// was this pointless?
|
||||||
if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data')
|
if (!tx.hasWitnesses())
|
||||||
|
throw new Error('Transaction has superfluous witness data');
|
||||||
}
|
}
|
||||||
|
tx.locktime = readUInt32();
|
||||||
tx.locktime = readUInt32()
|
if (_NO_STRICT)
|
||||||
|
return tx;
|
||||||
if (__noStrict) return tx
|
if (offset !== buffer.length)
|
||||||
if (offset !== buffer.length) throw new Error('Transaction has unexpected data')
|
throw new Error('Transaction has unexpected data');
|
||||||
|
return tx;
|
||||||
return tx
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
static fromHex(hex) {
|
||||||
}
|
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
|
||||||
|
}
|
||||||
Transaction.prototype.isCoinbase = function () {
|
static isCoinbaseHash(buffer) {
|
||||||
return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
|
typeforce(types.Hash256bit, buffer);
|
||||||
}
|
for (let i = 0; i < 32; ++i) {
|
||||||
|
if (buffer[i] !== 0)
|
||||||
Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) {
|
return false;
|
||||||
typeforce(types.tuple(
|
}
|
||||||
types.Hash256bit,
|
return true;
|
||||||
types.UInt32,
|
}
|
||||||
types.maybe(types.UInt32),
|
isCoinbase() {
|
||||||
types.maybe(types.Buffer)
|
return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash));
|
||||||
), arguments)
|
}
|
||||||
|
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)) {
|
if (types.Null(sequence)) {
|
||||||
sequence = Transaction.DEFAULT_SEQUENCE
|
sequence = Transaction.DEFAULT_SEQUENCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the input and return the input's index
|
// Add the input and return the input's index
|
||||||
return (this.ins.push({
|
return (this.ins.push({
|
||||||
hash: hash,
|
hash,
|
||||||
index: index,
|
index,
|
||||||
script: scriptSig || EMPTY_SCRIPT,
|
script: scriptSig || EMPTY_SCRIPT,
|
||||||
sequence: sequence,
|
sequence: sequence,
|
||||||
witness: EMPTY_WITNESS
|
witness: EMPTY_WITNESS,
|
||||||
}) - 1)
|
}) - 1);
|
||||||
}
|
}
|
||||||
|
addOutput(scriptPubKey, value) {
|
||||||
Transaction.prototype.addOutput = function (scriptPubKey, value) {
|
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments);
|
||||||
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments)
|
|
||||||
|
|
||||||
// Add the output and return the output's index
|
// Add the output and return the output's index
|
||||||
return (this.outs.push({
|
return (this.outs.push({
|
||||||
script: scriptPubKey,
|
script: scriptPubKey,
|
||||||
value: value
|
value,
|
||||||
}) - 1)
|
}) - 1);
|
||||||
}
|
}
|
||||||
|
hasWitnesses() {
|
||||||
Transaction.prototype.hasWitnesses = function () {
|
return this.ins.some(x => {
|
||||||
return this.ins.some(function (x) {
|
return x.witness.length !== 0;
|
||||||
return x.witness.length !== 0
|
});
|
||||||
})
|
}
|
||||||
}
|
weight() {
|
||||||
|
const base = this.__byteLength(false);
|
||||||
Transaction.prototype.weight = function () {
|
const total = this.__byteLength(true);
|
||||||
const base = this.__byteLength(false)
|
return base * 3 + total;
|
||||||
const total = this.__byteLength(true)
|
}
|
||||||
return base * 3 + total
|
virtualSize() {
|
||||||
}
|
return Math.ceil(this.weight() / 4);
|
||||||
|
}
|
||||||
Transaction.prototype.virtualSize = function () {
|
byteLength() {
|
||||||
return Math.ceil(this.weight() / 4)
|
return this.__byteLength(true);
|
||||||
}
|
}
|
||||||
|
clone() {
|
||||||
Transaction.prototype.byteLength = function () {
|
const newTx = new Transaction();
|
||||||
return this.__byteLength(true)
|
newTx.version = this.version;
|
||||||
}
|
newTx.locktime = this.locktime;
|
||||||
|
newTx.ins = this.ins.map(txIn => {
|
||||||
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 {
|
return {
|
||||||
hash: txIn.hash,
|
hash: txIn.hash,
|
||||||
index: txIn.index,
|
index: txIn.index,
|
||||||
script: txIn.script,
|
script: txIn.script,
|
||||||
sequence: txIn.sequence,
|
sequence: txIn.sequence,
|
||||||
witness: txIn.witness
|
witness: txIn.witness,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
newTx.outs = this.outs.map(txOut => {
|
||||||
newTx.outs = this.outs.map(function (txOut) {
|
|
||||||
return {
|
return {
|
||||||
script: txOut.script,
|
script: txOut.script,
|
||||||
value: txOut.value
|
value: txOut.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return newTx;
|
||||||
}
|
}
|
||||||
})
|
/**
|
||||||
|
|
||||||
return newTx
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash transaction for signing a specific input.
|
* Hash transaction for signing a specific input.
|
||||||
*
|
*
|
||||||
* Bitcoin uses a different hash for each signed transaction input.
|
* Bitcoin uses a different hash for each signed transaction input.
|
||||||
|
@ -251,242 +197,256 @@ Transaction.prototype.clone = function () {
|
||||||
* hashType, and then hashes the result.
|
* hashType, and then hashes the result.
|
||||||
* This hash can then be used to sign the provided transaction input.
|
* This hash can then be used to sign the provided transaction input.
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
|
hashForSignature(inIndex, prevOutScript, hashType) {
|
||||||
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments)
|
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments);
|
||||||
|
|
||||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
||||||
if (inIndex >= this.ins.length) return ONE
|
if (inIndex >= this.ins.length)
|
||||||
|
return ONE;
|
||||||
// ignore OP_CODESEPARATOR
|
// ignore OP_CODESEPARATOR
|
||||||
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) {
|
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => {
|
||||||
return x !== opcodes.OP_CODESEPARATOR
|
return x !== script_1.OPS.OP_CODESEPARATOR;
|
||||||
}))
|
}));
|
||||||
|
const txTmp = this.clone();
|
||||||
const txTmp = this.clone()
|
|
||||||
|
|
||||||
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
|
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
|
||||||
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
||||||
txTmp.outs = []
|
txTmp.outs = [];
|
||||||
|
|
||||||
// ignore sequence numbers (except at inIndex)
|
// ignore sequence numbers (except at inIndex)
|
||||||
txTmp.ins.forEach(function (input, i) {
|
txTmp.ins.forEach((input, i) => {
|
||||||
if (i === inIndex) return
|
if (i === inIndex)
|
||||||
|
return;
|
||||||
input.sequence = 0
|
input.sequence = 0;
|
||||||
})
|
});
|
||||||
|
|
||||||
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
|
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
|
||||||
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
}
|
||||||
|
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
||||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
|
||||||
if (inIndex >= this.outs.length) return ONE
|
if (inIndex >= this.outs.length)
|
||||||
|
return ONE;
|
||||||
// truncate outputs after
|
// truncate outputs after
|
||||||
txTmp.outs.length = inIndex + 1
|
txTmp.outs.length = inIndex + 1;
|
||||||
|
|
||||||
// "blank" outputs before
|
// "blank" outputs before
|
||||||
for (var i = 0; i < inIndex; i++) {
|
for (let i = 0; i < inIndex; i++) {
|
||||||
txTmp.outs[i] = BLANK_OUTPUT
|
txTmp.outs[i] = BLANK_OUTPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore sequence numbers (except at inIndex)
|
// ignore sequence numbers (except at inIndex)
|
||||||
txTmp.ins.forEach(function (input, y) {
|
txTmp.ins.forEach((input, y) => {
|
||||||
if (y === inIndex) return
|
if (y === inIndex)
|
||||||
|
return;
|
||||||
input.sequence = 0
|
input.sequence = 0;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
|
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
|
||||||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
||||||
txTmp.ins = [txTmp.ins[inIndex]]
|
txTmp.ins = [txTmp.ins[inIndex]];
|
||||||
txTmp.ins[0].script = ourScript
|
txTmp.ins[0].script = ourScript;
|
||||||
|
|
||||||
// SIGHASH_ALL: only ignore input scripts
|
// SIGHASH_ALL: only ignore input scripts
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// "blank" others input scripts
|
// "blank" others input scripts
|
||||||
txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
|
txTmp.ins.forEach(input => {
|
||||||
txTmp.ins[inIndex].script = ourScript
|
input.script = EMPTY_SCRIPT;
|
||||||
|
});
|
||||||
|
txTmp.ins[inIndex].script = ourScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize and hash
|
// serialize and hash
|
||||||
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4)
|
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4);
|
||||||
buffer.writeInt32LE(hashType, buffer.length - 4)
|
buffer.writeInt32LE(hashType, buffer.length - 4);
|
||||||
txTmp.__toBuffer(buffer, 0, false)
|
txTmp.__toBuffer(buffer, 0, false);
|
||||||
|
return bcrypto.hash256(buffer);
|
||||||
return bcrypto.hash256(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) }
|
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
|
||||||
|
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments);
|
||||||
let hashOutputs = ZERO
|
let tbuffer = Buffer.from([]);
|
||||||
let hashPrevouts = ZERO
|
let toffset = 0;
|
||||||
let hashSequence = ZERO
|
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)) {
|
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
|
||||||
tbuffer = Buffer.allocUnsafe(36 * this.ins.length)
|
tbuffer = Buffer.allocUnsafe(36 * this.ins.length);
|
||||||
toffset = 0
|
toffset = 0;
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
this.ins.forEach(function (txIn) {
|
writeSlice(txIn.hash);
|
||||||
writeSlice(txIn.hash)
|
writeUInt32(txIn.index);
|
||||||
writeUInt32(txIn.index)
|
});
|
||||||
})
|
hashPrevouts = bcrypto.hash256(tbuffer);
|
||||||
|
|
||||||
hashPrevouts = bcrypto.hash256(tbuffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
|
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
|
||||||
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
||||||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
||||||
tbuffer = Buffer.allocUnsafe(4 * this.ins.length)
|
tbuffer = Buffer.allocUnsafe(4 * this.ins.length);
|
||||||
toffset = 0
|
toffset = 0;
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
this.ins.forEach(function (txIn) {
|
writeUInt32(txIn.sequence);
|
||||||
writeUInt32(txIn.sequence)
|
});
|
||||||
})
|
hashSequence = bcrypto.hash256(tbuffer);
|
||||||
|
|
||||||
hashSequence = bcrypto.hash256(tbuffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
||||||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
||||||
const txOutsSize = this.outs.reduce(function (sum, output) {
|
const txOutsSize = this.outs.reduce((sum, output) => {
|
||||||
return sum + 8 + varSliceSize(output.script)
|
return sum + 8 + varSliceSize(output.script);
|
||||||
}, 0)
|
}, 0);
|
||||||
|
tbuffer = Buffer.allocUnsafe(txOutsSize);
|
||||||
tbuffer = Buffer.allocUnsafe(txOutsSize)
|
toffset = 0;
|
||||||
toffset = 0
|
this.outs.forEach(out => {
|
||||||
|
writeUInt64(out.value);
|
||||||
this.outs.forEach(function (out) {
|
writeVarSlice(out.script);
|
||||||
writeUInt64(out.value)
|
});
|
||||||
writeVarSlice(out.script)
|
hashOutputs = bcrypto.hash256(tbuffer);
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
|
||||||
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript))
|
inIndex < this.outs.length) {
|
||||||
toffset = 0
|
const output = this.outs[inIndex];
|
||||||
|
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script));
|
||||||
const input = this.ins[inIndex]
|
toffset = 0;
|
||||||
writeUInt32(this.version)
|
writeUInt64(output.value);
|
||||||
writeSlice(hashPrevouts)
|
writeVarSlice(output.script);
|
||||||
writeSlice(hashSequence)
|
hashOutputs = bcrypto.hash256(tbuffer);
|
||||||
writeSlice(input.hash)
|
}
|
||||||
writeUInt32(input.index)
|
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript));
|
||||||
writeVarSlice(prevOutScript)
|
toffset = 0;
|
||||||
writeUInt64(value)
|
const input = this.ins[inIndex];
|
||||||
writeUInt32(input.sequence)
|
writeUInt32(this.version);
|
||||||
writeSlice(hashOutputs)
|
writeSlice(hashPrevouts);
|
||||||
writeUInt32(this.locktime)
|
writeSlice(hashSequence);
|
||||||
writeUInt32(hashType)
|
writeSlice(input.hash);
|
||||||
return bcrypto.hash256(tbuffer)
|
writeUInt32(input.index);
|
||||||
}
|
writeVarSlice(prevOutScript);
|
||||||
|
writeUInt64(value);
|
||||||
Transaction.prototype.getHash = function () {
|
writeUInt32(input.sequence);
|
||||||
return bcrypto.hash256(this.__toBuffer(undefined, undefined, false))
|
writeSlice(hashOutputs);
|
||||||
}
|
writeUInt32(this.locktime);
|
||||||
|
writeUInt32(hashType);
|
||||||
Transaction.prototype.getId = function () {
|
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
|
// transaction hash's are displayed in reverse order
|
||||||
return this.getHash().reverse().toString('hex')
|
return bufferutils_1.reverseBuffer(this.getHash(false)).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) }
|
toBuffer(buffer, initialOffset) {
|
||||||
function writeVector (vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice) }
|
return this.__toBuffer(buffer, initialOffset, true);
|
||||||
|
}
|
||||||
writeInt32(this.version)
|
toHex() {
|
||||||
|
return this.toBuffer(undefined, undefined).toString('hex');
|
||||||
const hasWitnesses = __allowWitness && this.hasWitnesses()
|
}
|
||||||
|
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) {
|
if (hasWitnesses) {
|
||||||
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER)
|
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER);
|
||||||
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG)
|
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG);
|
||||||
}
|
}
|
||||||
|
writeVarInt(this.ins.length);
|
||||||
writeVarInt(this.ins.length)
|
this.ins.forEach(txIn => {
|
||||||
|
writeSlice(txIn.hash);
|
||||||
this.ins.forEach(function (txIn) {
|
writeUInt32(txIn.index);
|
||||||
writeSlice(txIn.hash)
|
writeVarSlice(txIn.script);
|
||||||
writeUInt32(txIn.index)
|
writeUInt32(txIn.sequence);
|
||||||
writeVarSlice(txIn.script)
|
});
|
||||||
writeUInt32(txIn.sequence)
|
writeVarInt(this.outs.length);
|
||||||
})
|
this.outs.forEach(txOut => {
|
||||||
|
if (isOutput(txOut)) {
|
||||||
writeVarInt(this.outs.length)
|
writeUInt64(txOut.value);
|
||||||
this.outs.forEach(function (txOut) {
|
|
||||||
if (!txOut.valueBuffer) {
|
|
||||||
writeUInt64(txOut.value)
|
|
||||||
} else {
|
|
||||||
writeSlice(txOut.valueBuffer)
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
writeVarSlice(txOut.script)
|
writeSlice(txOut.valueBuffer);
|
||||||
})
|
}
|
||||||
|
writeVarSlice(txOut.script);
|
||||||
|
});
|
||||||
if (hasWitnesses) {
|
if (hasWitnesses) {
|
||||||
this.ins.forEach(function (input) {
|
this.ins.forEach(input => {
|
||||||
writeVector(input.witness)
|
writeVector(input.witness);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
writeUInt32(this.locktime);
|
||||||
writeUInt32(this.locktime)
|
|
||||||
|
|
||||||
// avoid slicing unless necessary
|
// avoid slicing unless necessary
|
||||||
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset)
|
if (initialOffset !== undefined)
|
||||||
return buffer
|
return buffer.slice(initialOffset, offset);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
|
||||||
Transaction.prototype.toHex = function () {
|
Transaction.SIGHASH_ALL = 0x01;
|
||||||
return this.toBuffer().toString('hex')
|
Transaction.SIGHASH_NONE = 0x02;
|
||||||
}
|
Transaction.SIGHASH_SINGLE = 0x03;
|
||||||
|
Transaction.SIGHASH_ANYONECANPAY = 0x80;
|
||||||
Transaction.prototype.setInputScript = function (index, scriptSig) {
|
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
|
||||||
typeforce(types.tuple(types.Number, types.Buffer), arguments)
|
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
|
||||||
|
exports.Transaction = Transaction;
|
||||||
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
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
77
src/types.js
77
src/types.js
|
@ -1,49 +1,50 @@
|
||||||
const typeforce = require('typeforce')
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const UINT31_MAX = Math.pow(2, 31) - 1
|
const typeforce = require('typeforce');
|
||||||
function UInt31 (value) {
|
const UINT31_MAX = Math.pow(2, 31) - 1;
|
||||||
return typeforce.UInt32(value) && value <= UINT31_MAX
|
function UInt31(value) {
|
||||||
|
return typeforce.UInt32(value) && value <= UINT31_MAX;
|
||||||
}
|
}
|
||||||
|
exports.UInt31 = UInt31;
|
||||||
function BIP32Path (value) {
|
function BIP32Path(value) {
|
||||||
return typeforce.String(value) && value.match(/^(m\/)?(\d+'?\/)*\d+'?$/)
|
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/);
|
||||||
}
|
}
|
||||||
BIP32Path.toJSON = function () { return 'BIP32 derivation path' }
|
exports.BIP32Path = BIP32Path;
|
||||||
|
BIP32Path.toJSON = () => {
|
||||||
const SATOSHI_MAX = 21 * 1e14
|
return 'BIP32 derivation path';
|
||||||
function Satoshi (value) {
|
};
|
||||||
return typeforce.UInt53(value) && value <= SATOSHI_MAX
|
const SATOSHI_MAX = 21 * 1e14;
|
||||||
|
function Satoshi(value) {
|
||||||
|
return typeforce.UInt53(value) && value <= SATOSHI_MAX;
|
||||||
}
|
}
|
||||||
|
exports.Satoshi = Satoshi;
|
||||||
// external dependent types
|
// external dependent types
|
||||||
const ECPoint = typeforce.quacksLike('Point')
|
exports.ECPoint = typeforce.quacksLike('Point');
|
||||||
|
|
||||||
// exposed, external API
|
// exposed, external API
|
||||||
const Network = typeforce.compile({
|
exports.Network = typeforce.compile({
|
||||||
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
|
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
|
||||||
bip32: {
|
bip32: {
|
||||||
public: typeforce.UInt32,
|
public: typeforce.UInt32,
|
||||||
private: typeforce.UInt32
|
private: typeforce.UInt32,
|
||||||
},
|
},
|
||||||
pubKeyHash: typeforce.UInt8,
|
pubKeyHash: typeforce.UInt8,
|
||||||
scriptHash: typeforce.UInt8,
|
scriptHash: typeforce.UInt8,
|
||||||
wif: typeforce.UInt8
|
wif: typeforce.UInt8,
|
||||||
})
|
});
|
||||||
|
exports.Buffer256bit = typeforce.BufferN(32);
|
||||||
// extend typeforce types with ours
|
exports.Hash160bit = typeforce.BufferN(20);
|
||||||
const types = {
|
exports.Hash256bit = typeforce.BufferN(32);
|
||||||
BIP32Path: BIP32Path,
|
exports.Number = typeforce.Number; // tslint:disable-line variable-name
|
||||||
Buffer256bit: typeforce.BufferN(32),
|
exports.Array = typeforce.Array;
|
||||||
ECPoint: ECPoint,
|
exports.Boolean = typeforce.Boolean; // tslint:disable-line variable-name
|
||||||
Hash160bit: typeforce.BufferN(20),
|
exports.String = typeforce.String; // tslint:disable-line variable-name
|
||||||
Hash256bit: typeforce.BufferN(32),
|
exports.Buffer = typeforce.Buffer;
|
||||||
Network: Network,
|
exports.Hex = typeforce.Hex;
|
||||||
Satoshi: Satoshi,
|
exports.maybe = typeforce.maybe;
|
||||||
UInt31: UInt31
|
exports.tuple = typeforce.tuple;
|
||||||
}
|
exports.UInt8 = typeforce.UInt8;
|
||||||
|
exports.UInt32 = typeforce.UInt32;
|
||||||
for (var typeName in typeforce) {
|
exports.Function = typeforce.Function;
|
||||||
types[typeName] = typeforce[typeName]
|
exports.BufferN = typeforce.BufferN;
|
||||||
}
|
exports.Null = typeforce.Null;
|
||||||
|
exports.oneOf = typeforce.oneOf;
|
||||||
module.exports = types
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const { describe, it, beforeEach } = require('mocha')
|
const { describe, it, beforeEach } = require('mocha')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const Block = require('../src/block')
|
const Block = require('..').Block
|
||||||
|
|
||||||
const fixtures = require('./fixtures/block')
|
const fixtures = require('./fixtures/block')
|
||||||
|
|
||||||
|
@ -32,6 +32,9 @@ describe('Block', function () {
|
||||||
assert.strictEqual(block.version, f.version)
|
assert.strictEqual(block.version, f.version)
|
||||||
assert.strictEqual(block.prevHash.toString('hex'), f.prevHash)
|
assert.strictEqual(block.prevHash.toString('hex'), f.prevHash)
|
||||||
assert.strictEqual(block.merkleRoot.toString('hex'), f.merkleRoot)
|
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.timestamp, f.timestamp)
|
||||||
assert.strictEqual(block.bits, f.bits)
|
assert.strictEqual(block.bits, f.bits)
|
||||||
assert.strictEqual(block.nonce, f.nonce)
|
assert.strictEqual(block.nonce, f.nonce)
|
||||||
|
@ -113,10 +116,16 @@ describe('Block', function () {
|
||||||
it('returns ' + f.merkleRoot + ' for ' + f.id, function () {
|
it('returns ' + f.merkleRoot + ' for ' + f.id, function () {
|
||||||
assert.strictEqual(Block.calculateMerkleRoot(block.transactions).toString('hex'), f.merkleRoot)
|
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) {
|
fixtures.valid.forEach(function (f) {
|
||||||
if (f.hex.length === 160) return
|
if (f.hex.length === 160) return
|
||||||
|
|
||||||
|
@ -127,7 +136,7 @@ describe('Block', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns ' + f.valid + ' for ' + f.id, function () {
|
it('returns ' + f.valid + ' for ' + f.id, function () {
|
||||||
assert.strictEqual(block.checkMerkleRoot(), true)
|
assert.strictEqual(block.checkTxRoots(), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/* eslint-disable no-new */
|
|
||||||
|
|
||||||
const { describe, it, beforeEach } = require('mocha')
|
const { describe, it, beforeEach } = require('mocha')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const proxyquire = require('proxyquire')
|
const proxyquire = require('proxyquire')
|
||||||
|
@ -30,7 +28,7 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls pointFromScalar lazily', hoodwink(function () {
|
it('calls pointFromScalar lazily', hoodwink(function () {
|
||||||
assert.strictEqual(keyPair.__Q, null)
|
assert.strictEqual(keyPair.__Q, undefined)
|
||||||
|
|
||||||
// .publicKey forces the memoization
|
// .publicKey forces the memoization
|
||||||
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
||||||
|
@ -240,7 +238,7 @@ describe('ECPair', function () {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('throws if no private key is found', function () {
|
it('throws if no private key is found', function () {
|
||||||
delete keyPair.__d
|
delete keyPair.__D
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
keyPair.sign(hash)
|
keyPair.sign(hash)
|
||||||
|
|
15
test/fixtures/block.json
vendored
15
test/fixtures/block.json
vendored
|
@ -119,6 +119,21 @@
|
||||||
"timestamp": 1231006505,
|
"timestamp": 1231006505,
|
||||||
"valid": true,
|
"valid": true,
|
||||||
"version": 1
|
"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": [
|
"invalid": [
|
||||||
|
|
2
test/fixtures/transaction_builder.json
vendored
2
test/fixtures/transaction_builder.json
vendored
|
@ -1976,7 +1976,7 @@
|
||||||
"sign": [
|
"sign": [
|
||||||
{
|
{
|
||||||
"description": "Transaction w/ witness value mismatch",
|
"description": "Transaction w/ witness value mismatch",
|
||||||
"exception": "Input didn\\'t match witnessValue",
|
"exception": "Input did not match witnessValue",
|
||||||
"network": "testnet",
|
"network": "testnet",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,13 @@ const u = require('./payments.utils')
|
||||||
|
|
||||||
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) {
|
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) {
|
||||||
describe(p, function () {
|
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)
|
const fixtures = require('./fixtures/' + p)
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f, i) {
|
fixtures.valid.forEach(function (f, i) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ const { describe, it, beforeEach } = require('mocha')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
const fixtures = require('./fixtures/transaction')
|
const fixtures = require('./fixtures/transaction')
|
||||||
const Transaction = require('../src/transaction')
|
const Transaction = require('..').Transaction
|
||||||
|
|
||||||
describe('Transaction', function () {
|
describe('Transaction', function () {
|
||||||
function fromRaw (raw, noWitness) {
|
function fromRaw (raw, noWitness) {
|
||||||
|
|
|
@ -5,8 +5,8 @@ const bscript = require('../src/script')
|
||||||
const payments = require('../src/payments')
|
const payments = require('../src/payments')
|
||||||
|
|
||||||
const ECPair = require('../src/ecpair')
|
const ECPair = require('../src/ecpair')
|
||||||
const Transaction = require('../src/transaction')
|
const Transaction = require('..').Transaction
|
||||||
const TransactionBuilder = require('../src/transaction_builder')
|
const TransactionBuilder = require('..').TransactionBuilder
|
||||||
const NETWORKS = require('../src/networks')
|
const NETWORKS = require('../src/networks')
|
||||||
|
|
||||||
const fixtures = require('./fixtures/transaction_builder')
|
const fixtures = require('./fixtures/transaction_builder')
|
||||||
|
@ -164,7 +164,7 @@ describe('TransactionBuilder', function () {
|
||||||
const tx = Transaction.fromHex(fixtures.valid.classification.hex)
|
const tx = Transaction.fromHex(fixtures.valid.classification.hex)
|
||||||
const txb = TransactionBuilder.fromTransaction(tx)
|
const txb = TransactionBuilder.fromTransaction(tx)
|
||||||
|
|
||||||
txb.__inputs.forEach(function (i) {
|
txb.__INPUTS.forEach(function (i) {
|
||||||
assert.strictEqual(i.prevOutType, 'scripthash')
|
assert.strictEqual(i.prevOutType, 'scripthash')
|
||||||
assert.strictEqual(i.redeemScriptType, 'multisig')
|
assert.strictEqual(i.redeemScriptType, 'multisig')
|
||||||
})
|
})
|
||||||
|
@ -191,22 +191,22 @@ describe('TransactionBuilder', function () {
|
||||||
const vin = txb.addInput(txHash, 1, 54)
|
const vin = txb.addInput(txHash, 1, 54)
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
|
||||||
const txIn = txb.__tx.ins[0]
|
const txIn = txb.__TX.ins[0]
|
||||||
assert.strictEqual(txIn.hash, txHash)
|
assert.strictEqual(txIn.hash, txHash)
|
||||||
assert.strictEqual(txIn.index, 1)
|
assert.strictEqual(txIn.index, 1)
|
||||||
assert.strictEqual(txIn.sequence, 54)
|
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 () {
|
it('accepts a txHash, index [, sequence number and scriptPubKey]', function () {
|
||||||
const vin = txb.addInput(txHash, 1, 54, scripts[1])
|
const vin = txb.addInput(txHash, 1, 54, scripts[1])
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
|
||||||
const txIn = txb.__tx.ins[0]
|
const txIn = txb.__TX.ins[0]
|
||||||
assert.strictEqual(txIn.hash, txHash)
|
assert.strictEqual(txIn.hash, txHash)
|
||||||
assert.strictEqual(txIn.index, 1)
|
assert.strictEqual(txIn.index, 1)
|
||||||
assert.strictEqual(txIn.sequence, 54)
|
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 () {
|
it('accepts a prevTx, index [and sequence number]', function () {
|
||||||
|
@ -217,11 +217,11 @@ describe('TransactionBuilder', function () {
|
||||||
const vin = txb.addInput(prevTx, 1, 54)
|
const vin = txb.addInput(prevTx, 1, 54)
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
|
||||||
const txIn = txb.__tx.ins[0]
|
const txIn = txb.__TX.ins[0]
|
||||||
assert.deepEqual(txIn.hash, prevTx.getHash())
|
assert.deepEqual(txIn.hash, prevTx.getHash())
|
||||||
assert.strictEqual(txIn.index, 1)
|
assert.strictEqual(txIn.index, 1)
|
||||||
assert.strictEqual(txIn.sequence, 54)
|
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 () {
|
it('returns the input index', function () {
|
||||||
|
@ -251,7 +251,7 @@ describe('TransactionBuilder', function () {
|
||||||
const vout = txb.addOutput(address, 1000)
|
const vout = txb.addOutput(address, 1000)
|
||||||
assert.strictEqual(vout, 0)
|
assert.strictEqual(vout, 0)
|
||||||
|
|
||||||
const txout = txb.__tx.outs[0]
|
const txout = txb.__TX.outs[0]
|
||||||
assert.deepEqual(txout.script, scripts[0])
|
assert.deepEqual(txout.script, scripts[0])
|
||||||
assert.strictEqual(txout.value, 1000)
|
assert.strictEqual(txout.value, 1000)
|
||||||
})
|
})
|
||||||
|
@ -260,7 +260,7 @@ describe('TransactionBuilder', function () {
|
||||||
const vout = txb.addOutput(scripts[0], 1000)
|
const vout = txb.addOutput(scripts[0], 1000)
|
||||||
assert.strictEqual(vout, 0)
|
assert.strictEqual(vout, 0)
|
||||||
|
|
||||||
const txout = txb.__tx.outs[0]
|
const txout = txb.__TX.outs[0]
|
||||||
assert.deepEqual(txout.script, scripts[0])
|
assert.deepEqual(txout.script, scripts[0])
|
||||||
assert.strictEqual(txout.value, 1000)
|
assert.strictEqual(txout.value, 1000)
|
||||||
})
|
})
|
||||||
|
@ -533,10 +533,10 @@ describe('TransactionBuilder', function () {
|
||||||
'194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' +
|
'194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' +
|
||||||
'0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000'
|
'0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000'
|
||||||
const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx))
|
const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx))
|
||||||
txb.__inputs[0].value = 241530
|
txb.__INPUTS[0].value = 241530
|
||||||
txb.__inputs[1].value = 241530
|
txb.__INPUTS[1].value = 241530
|
||||||
txb.__inputs[2].value = 248920
|
txb.__INPUTS[2].value = 248920
|
||||||
txb.__inputs[3].value = 248920
|
txb.__INPUTS[3].value = 248920
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
txb.build()
|
txb.build()
|
||||||
|
|
119
ts_src/address.ts
Normal file
119
ts_src/address.ts
Normal file
|
@ -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
Normal file
285
ts_src/block.ts
Normal file
|
@ -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
Normal file
44
ts_src/bufferutils.ts
Normal file
|
@ -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
Normal file
71
ts_src/classify.ts
Normal file
|
@ -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
Normal file
33
ts_src/crypto.ts
Normal file
|
@ -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
Normal file
131
ts_src/ecpair.ts
Normal file
|
@ -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
Normal file
20
ts_src/index.ts
Normal file
|
@ -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
Normal file
49
ts_src/networks.ts
Normal file
|
@ -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
Normal file
58
ts_src/payments/embed.ts
Normal file
|
@ -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
Normal file
41
ts_src/payments/index.ts
Normal file
|
@ -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
Normal file
28
ts_src/payments/lazy.ts
Normal file
|
@ -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
Normal file
158
ts_src/payments/p2ms.ts
Normal file
|
@ -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
Normal file
80
ts_src/payments/p2pk.ts
Normal file
|
@ -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
Normal file
147
ts_src/payments/p2pkh.ts
Normal file
|
@ -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
Normal file
213
ts_src/payments/p2sh.ts
Normal file
|
@ -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
Normal file
142
ts_src/payments/p2wpkh.ts
Normal file
|
@ -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
Normal file
198
ts_src/payments/p2wsh.ts
Normal file
|
@ -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
Normal file
216
ts_src/script.ts
Normal file
|
@ -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
Normal file
71
ts_src/script_number.ts
Normal file
|
@ -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
Normal file
64
ts_src/script_signature.ts
Normal file
|
@ -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
Normal file
4
ts_src/templates/multisig/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as input from './input';
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { input, output };
|
31
ts_src/templates/multisig/input.ts
Normal file
31
ts_src/templates/multisig/input.ts
Normal file
|
@ -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
Normal file
33
ts_src/templates/multisig/output.ts
Normal file
|
@ -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
Normal file
16
ts_src/templates/nulldata.ts
Normal file
|
@ -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
Normal file
4
ts_src/templates/pubkey/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as input from './input';
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { input, output };
|
16
ts_src/templates/pubkey/input.ts
Normal file
16
ts_src/templates/pubkey/input.ts
Normal file
|
@ -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
Normal file
18
ts_src/templates/pubkey/output.ts
Normal file
|
@ -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
Normal file
4
ts_src/templates/pubkeyhash/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as input from './input';
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { input, output };
|
17
ts_src/templates/pubkeyhash/input.ts
Normal file
17
ts_src/templates/pubkeyhash/input.ts
Normal file
|
@ -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
Normal file
20
ts_src/templates/pubkeyhash/output.ts
Normal file
|
@ -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
Normal file
4
ts_src/templates/scripthash/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as input from './input';
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { input, output };
|
61
ts_src/templates/scripthash/input.ts
Normal file
61
ts_src/templates/scripthash/input.ts
Normal file
|
@ -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
Normal file
18
ts_src/templates/scripthash/output.ts
Normal file
|
@ -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
Normal file
3
ts_src/templates/witnesscommitment/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { output };
|
40
ts_src/templates/witnesscommitment/output.ts
Normal file
40
ts_src/templates/witnesscommitment/output.ts
Normal file
|
@ -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
Normal file
4
ts_src/templates/witnesspubkeyhash/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as input from './input';
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { input, output };
|
21
ts_src/templates/witnesspubkeyhash/input.ts
Normal file
21
ts_src/templates/witnesspubkeyhash/input.ts
Normal file
|
@ -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
Normal file
13
ts_src/templates/witnesspubkeyhash/output.ts
Normal file
|
@ -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
Normal file
4
ts_src/templates/witnessscripthash/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as input from './input';
|
||||||
|
import * as output from './output';
|
||||||
|
|
||||||
|
export { input, output };
|
47
ts_src/templates/witnessscripthash/input.ts
Normal file
47
ts_src/templates/witnessscripthash/input.ts
Normal file
|
@ -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
Normal file
13
ts_src/templates/witnessscripthash/output.ts
Normal file
|
@ -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 have changed in this diff Show more
Loading…
Reference in a new issue