bitcoinjs-lib
Advanced tools
Comparing version 4.0.4 to 5.0.0
@@ -0,1 +1,13 @@ | ||
# 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 | ||
@@ -2,0 +14,0 @@ __fixed__ |
{ | ||
"name": "bitcoinjs-lib", | ||
"version": "4.0.4", | ||
"version": "5.0.0", | ||
"description": "Client-side Bitcoin JavaScript library", | ||
"main": "./src/index.js", | ||
"types": "./types/index.d.ts", | ||
"engines": { | ||
@@ -17,9 +18,19 @@ "node": ">=8.0.0" | ||
"scripts": { | ||
"coverage-report": "nyc report --reporter=lcov", | ||
"coverage-html": "nyc report --reporter=html", | ||
"coverage": "nyc --check-coverage --branches 90 --functions 90 mocha", | ||
"integration": "mocha --timeout 50000 test/integration/", | ||
"standard": "standard", | ||
"test": "npm run standard && npm run coverage", | ||
"unit": "mocha" | ||
"build": "tsc -p ./tsconfig.json", | ||
"coverage-report": "npm run build && npm run nobuild:coverage-report", | ||
"coverage-html": "npm run build && npm run nobuild:coverage-html", | ||
"coverage": "npm run build && npm run nobuild:coverage", | ||
"format": "npm run prettier -- --write", | ||
"format:ci": "npm run prettier -- --check", | ||
"gitdiff:ci": "npm run build && git diff --exit-code", | ||
"integration": "npm run build && npm run nobuild:integration", | ||
"lint": "tslint -p tsconfig.json -c tslint.json", | ||
"nobuild:coverage-report": "nyc report --reporter=lcov", | ||
"nobuild:coverage-html": "nyc report --reporter=html", | ||
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha", | ||
"nobuild:integration": "mocha --timeout 50000 test/integration/", | ||
"nobuild:unit": "mocha", | ||
"prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore", | ||
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", | ||
"unit": "npm run build && npm run nobuild:unit" | ||
}, | ||
@@ -31,7 +42,9 @@ "repository": { | ||
"files": [ | ||
"src" | ||
"src", | ||
"types" | ||
], | ||
"dependencies": { | ||
"@types/node": "10.12.18", | ||
"bech32": "^1.1.2", | ||
"bip32": "^1.0.3", | ||
"bip32": "^2.0.0", | ||
"bip66": "^1.1.0", | ||
@@ -45,3 +58,2 @@ "bitcoin-ops": "^1.4.0", | ||
"randombytes": "^2.0.1", | ||
"safe-buffer": "^5.1.1", | ||
"tiny-secp256k1": "^1.0.0", | ||
@@ -62,7 +74,9 @@ "typeforce": "^1.11.3", | ||
"mocha": "^5.2.0", | ||
"nyc": "^11.8.0", | ||
"nyc": "^13.3.0", | ||
"prettier": "1.16.4", | ||
"proxyquire": "^2.0.1", | ||
"standard": "^11.0.1" | ||
"tslint": "5.13.1", | ||
"typescript": "3.2.2" | ||
}, | ||
"license": "MIT" | ||
} |
@@ -5,5 +5,5 @@ # BitcoinJS (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. | ||
@@ -27,3 +27,3 @@ Released under the terms of the [MIT LICENSE](LICENSE). | ||
- 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. | ||
@@ -83,27 +83,4 @@ | ||
### 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 | ||
@@ -110,0 +87,0 @@ The below examples are implemented as integration tests, they should be very easy to understand. |
@@ -1,97 +0,100 @@ | ||
const Buffer = require('safe-buffer').Buffer | ||
const bech32 = require('bech32') | ||
const bs58check = require('bs58check') | ||
const bscript = require('./script') | ||
const networks = require('./networks') | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const payments = require('./payments') | ||
function fromBase58Check (address) { | ||
const payload = bs58check.decode(address) | ||
// TODO: 4.0.0, move to "toOutputScript" | ||
if (payload.length < 21) throw new TypeError(address + ' is too short') | ||
if (payload.length > 21) throw new TypeError(address + ' is too long') | ||
const version = payload.readUInt8(0) | ||
const hash = payload.slice(1) | ||
return { version: version, hash: hash } | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const networks = require("./networks"); | ||
const payments = require("./payments"); | ||
const bscript = require("./script"); | ||
const types = require("./types"); | ||
const bech32 = require('bech32'); | ||
const bs58check = require('bs58check'); | ||
const typeforce = require('typeforce'); | ||
function fromBase58Check(address) { | ||
const payload = bs58check.decode(address); | ||
// TODO: 4.0.0, move to "toOutputScript" | ||
if (payload.length < 21) | ||
throw new TypeError(address + ' is too short'); | ||
if (payload.length > 21) | ||
throw new TypeError(address + ' is too long'); | ||
const version = payload.readUInt8(0); | ||
const hash = payload.slice(1); | ||
return { version, hash }; | ||
} | ||
function fromBech32 (address) { | ||
const result = bech32.decode(address) | ||
const data = bech32.fromWords(result.words.slice(1)) | ||
return { | ||
version: result.words[0], | ||
prefix: result.prefix, | ||
data: Buffer.from(data) | ||
} | ||
exports.fromBase58Check = fromBase58Check; | ||
function fromBech32(address) { | ||
const result = bech32.decode(address); | ||
const data = bech32.fromWords(result.words.slice(1)); | ||
return { | ||
version: result.words[0], | ||
prefix: result.prefix, | ||
data: Buffer.from(data), | ||
}; | ||
} | ||
function toBase58Check (hash, version) { | ||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments) | ||
const payload = Buffer.allocUnsafe(21) | ||
payload.writeUInt8(version, 0) | ||
hash.copy(payload, 1) | ||
return bs58check.encode(payload) | ||
exports.fromBech32 = fromBech32; | ||
function toBase58Check(hash, version) { | ||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); | ||
const payload = Buffer.allocUnsafe(21); | ||
payload.writeUInt8(version, 0); | ||
hash.copy(payload, 1); | ||
return bs58check.encode(payload); | ||
} | ||
function toBech32 (data, version, prefix) { | ||
const words = bech32.toWords(data) | ||
words.unshift(version) | ||
return bech32.encode(prefix, words) | ||
exports.toBase58Check = toBase58Check; | ||
function toBech32(data, version, prefix) { | ||
const words = bech32.toWords(data); | ||
words.unshift(version); | ||
return bech32.encode(prefix, words); | ||
} | ||
function fromOutputScript (output, network) { | ||
network = network || networks.bitcoin | ||
try { return payments.p2pkh({ output, network }).address } catch (e) {} | ||
try { return payments.p2sh({ output, network }).address } catch (e) {} | ||
try { return payments.p2wpkh({ output, network }).address } catch (e) {} | ||
try { return payments.p2wsh({ output, network }).address } catch (e) {} | ||
throw new Error(bscript.toASM(output) + ' has no matching Address') | ||
exports.toBech32 = toBech32; | ||
function fromOutputScript(output, network) { | ||
// 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 { | ||
decode = fromBase58Check(address) | ||
} catch (e) {} | ||
if (decode) { | ||
if (decode.version === network.pubKeyHash) return payments.p2pkh({ hash: decode.hash }).output | ||
if (decode.version === network.scriptHash) return payments.p2sh({ hash: decode.hash }).output | ||
} else { | ||
exports.fromOutputScript = fromOutputScript; | ||
function toOutputScript(address, network) { | ||
network = network || networks.bitcoin; | ||
let decodeBase58; | ||
let decodeBech32; | ||
try { | ||
decode = fromBech32(address) | ||
} catch (e) {} | ||
if (decode) { | ||
if (decode.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix') | ||
if (decode.version === 0) { | ||
if (decode.data.length === 20) return payments.p2wpkh({ hash: decode.data }).output | ||
if (decode.data.length === 32) return payments.p2wsh({ hash: decode.data }).output | ||
} | ||
decodeBase58 = fromBase58Check(address); | ||
} | ||
} | ||
throw new Error(address + ' has no matching Script') | ||
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'); | ||
} | ||
module.exports = { | ||
fromBase58Check: fromBase58Check, | ||
fromBech32: fromBech32, | ||
fromOutputScript: fromOutputScript, | ||
toBase58Check: toBase58Check, | ||
toBech32: toBech32, | ||
toOutputScript: toOutputScript | ||
} | ||
exports.toOutputScript = toOutputScript; |
393
src/block.js
@@ -1,177 +0,222 @@ | ||
const Buffer = require('safe-buffer').Buffer | ||
const bcrypto = require('./crypto') | ||
const fastMerkleRoot = require('merkle-lib/fastRoot') | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const varuint = require('varuint-bitcoin') | ||
const Transaction = require('./transaction') | ||
function Block () { | ||
this.version = 1 | ||
this.prevHash = null | ||
this.merkleRoot = null | ||
this.timestamp = 0 | ||
this.bits = 0 | ||
this.nonce = 0 | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bufferutils_1 = require("./bufferutils"); | ||
const bcrypto = require("./crypto"); | ||
const transaction_1 = require("./transaction"); | ||
const types = require("./types"); | ||
const fastMerkleRoot = require('merkle-lib/fastRoot'); | ||
const typeforce = require('typeforce'); | ||
const varuint = require('varuint-bitcoin'); | ||
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); | ||
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); | ||
class Block { | ||
constructor() { | ||
this.version = 1; | ||
this.prevHash = undefined; | ||
this.merkleRoot = undefined; | ||
this.timestamp = 0; | ||
this.witnessCommit = undefined; | ||
this.bits = 0; | ||
this.nonce = 0; | ||
this.transactions = undefined; | ||
} | ||
static fromBuffer(buffer) { | ||
if (buffer.length < 80) | ||
throw new Error('Buffer too small (< 80 bytes)'); | ||
let offset = 0; | ||
const readSlice = (n) => { | ||
offset += n; | ||
return buffer.slice(offset - n, offset); | ||
}; | ||
const readUInt32 = () => { | ||
const i = buffer.readUInt32LE(offset); | ||
offset += 4; | ||
return i; | ||
}; | ||
const readInt32 = () => { | ||
const i = buffer.readInt32LE(offset); | ||
offset += 4; | ||
return i; | ||
}; | ||
const block = new Block(); | ||
block.version = readInt32(); | ||
block.prevHash = readSlice(32); | ||
block.merkleRoot = readSlice(32); | ||
block.timestamp = readUInt32(); | ||
block.bits = readUInt32(); | ||
block.nonce = readUInt32(); | ||
if (buffer.length === 80) | ||
return block; | ||
const readVarInt = () => { | ||
const vi = varuint.decode(buffer, offset); | ||
offset += varuint.decode.bytes; | ||
return vi; | ||
}; | ||
const readTransaction = () => { | ||
const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true); | ||
offset += tx.byteLength(); | ||
return tx; | ||
}; | ||
const nTransactions = readVarInt(); | ||
block.transactions = []; | ||
for (let i = 0; i < nTransactions; ++i) { | ||
const tx = readTransaction(); | ||
block.transactions.push(tx); | ||
} | ||
const witnessCommit = block.getWitnessCommit(); | ||
// This Block contains a witness commit | ||
if (witnessCommit) | ||
block.witnessCommit = witnessCommit; | ||
return block; | ||
} | ||
static fromHex(hex) { | ||
return Block.fromBuffer(Buffer.from(hex, 'hex')); | ||
} | ||
static calculateTarget(bits) { | ||
const exponent = ((bits & 0xff000000) >> 24) - 3; | ||
const mantissa = bits & 0x007fffff; | ||
const target = Buffer.alloc(32, 0); | ||
target.writeUIntBE(mantissa, 29 - exponent, 3); | ||
return target; | ||
} | ||
static calculateMerkleRoot(transactions, forWitness) { | ||
typeforce([{ getHash: types.Function }], transactions); | ||
if (transactions.length === 0) | ||
throw errorMerkleNoTxes; | ||
if (forWitness && !txesHaveWitnessCommit(transactions)) | ||
throw errorWitnessNotSegwit; | ||
const hashes = transactions.map(transaction => transaction.getHash(forWitness)); | ||
const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); | ||
return forWitness | ||
? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) | ||
: rootHash; | ||
} | ||
getWitnessCommit() { | ||
if (!txesHaveWitnessCommit(this.transactions)) | ||
return null; | ||
// The merkle root for the witness data is in an OP_RETURN output. | ||
// There is no rule for the index of the output, so use filter to find it. | ||
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed | ||
// If multiple commits are found, the output with highest index is assumed. | ||
const witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38)); | ||
if (witnessCommits.length === 0) | ||
return null; | ||
// Use the commit with the highest output (should only be one though) | ||
const result = witnessCommits[witnessCommits.length - 1]; | ||
if (!(result instanceof Buffer && result.length === 32)) | ||
return null; | ||
return result; | ||
} | ||
hasWitnessCommit() { | ||
if (this.witnessCommit instanceof Buffer && | ||
this.witnessCommit.length === 32) | ||
return true; | ||
if (this.getWitnessCommit() !== null) | ||
return true; | ||
return false; | ||
} | ||
hasWitness() { | ||
return anyTxHasWitness(this.transactions); | ||
} | ||
byteLength(headersOnly) { | ||
if (headersOnly || !this.transactions) | ||
return 80; | ||
return (80 + | ||
varuint.encodingLength(this.transactions.length) + | ||
this.transactions.reduce((a, x) => a + x.byteLength(), 0)); | ||
} | ||
getHash() { | ||
return bcrypto.hash256(this.toBuffer(true)); | ||
} | ||
getId() { | ||
return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); | ||
} | ||
getUTCDate() { | ||
const date = new Date(0); // epoch | ||
date.setUTCSeconds(this.timestamp); | ||
return date; | ||
} | ||
// TODO: buffer, offset compatibility | ||
toBuffer(headersOnly) { | ||
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); | ||
let offset = 0; | ||
const writeSlice = (slice) => { | ||
slice.copy(buffer, offset); | ||
offset += slice.length; | ||
}; | ||
const writeInt32 = (i) => { | ||
buffer.writeInt32LE(i, offset); | ||
offset += 4; | ||
}; | ||
const writeUInt32 = (i) => { | ||
buffer.writeUInt32LE(i, offset); | ||
offset += 4; | ||
}; | ||
writeInt32(this.version); | ||
writeSlice(this.prevHash); | ||
writeSlice(this.merkleRoot); | ||
writeUInt32(this.timestamp); | ||
writeUInt32(this.bits); | ||
writeUInt32(this.nonce); | ||
if (headersOnly || !this.transactions) | ||
return buffer; | ||
varuint.encode(this.transactions.length, buffer, offset); | ||
offset += varuint.encode.bytes; | ||
this.transactions.forEach(tx => { | ||
const txSize = tx.byteLength(); // TODO: extract from toBuffer? | ||
tx.toBuffer(buffer, offset); | ||
offset += txSize; | ||
}); | ||
return buffer; | ||
} | ||
toHex(headersOnly) { | ||
return this.toBuffer(headersOnly).toString('hex'); | ||
} | ||
checkTxRoots() { | ||
// If the Block has segwit transactions but no witness commit, | ||
// there's no way it can be valid, so fail the check. | ||
const hasWitnessCommit = this.hasWitnessCommit(); | ||
if (!hasWitnessCommit && this.hasWitness()) | ||
return false; | ||
return (this.__checkMerkleRoot() && | ||
(hasWitnessCommit ? this.__checkWitnessCommit() : true)); | ||
} | ||
checkProofOfWork() { | ||
const hash = bufferutils_1.reverseBuffer(this.getHash()); | ||
const target = Block.calculateTarget(this.bits); | ||
return hash.compare(target) <= 0; | ||
} | ||
__checkMerkleRoot() { | ||
if (!this.transactions) | ||
throw errorMerkleNoTxes; | ||
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); | ||
return this.merkleRoot.compare(actualMerkleRoot) === 0; | ||
} | ||
__checkWitnessCommit() { | ||
if (!this.transactions) | ||
throw errorMerkleNoTxes; | ||
if (!this.hasWitnessCommit()) | ||
throw errorWitnessNotSegwit; | ||
const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true); | ||
return this.witnessCommit.compare(actualWitnessCommit) === 0; | ||
} | ||
} | ||
Block.fromBuffer = function (buffer) { | ||
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)') | ||
let offset = 0 | ||
function readSlice (n) { | ||
offset += n | ||
return buffer.slice(offset - n, offset) | ||
} | ||
function readUInt32 () { | ||
const i = buffer.readUInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
function readInt32 () { | ||
const i = buffer.readInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
const block = new Block() | ||
block.version = readInt32() | ||
block.prevHash = readSlice(32) | ||
block.merkleRoot = readSlice(32) | ||
block.timestamp = readUInt32() | ||
block.bits = readUInt32() | ||
block.nonce = readUInt32() | ||
if (buffer.length === 80) return block | ||
function readVarInt () { | ||
const vi = varuint.decode(buffer, offset) | ||
offset += varuint.decode.bytes | ||
return vi | ||
} | ||
function readTransaction () { | ||
const tx = Transaction.fromBuffer(buffer.slice(offset), true) | ||
offset += tx.byteLength() | ||
return tx | ||
} | ||
const nTransactions = readVarInt() | ||
block.transactions = [] | ||
for (var i = 0; i < nTransactions; ++i) { | ||
const tx = readTransaction() | ||
block.transactions.push(tx) | ||
} | ||
return block | ||
exports.Block = Block; | ||
function txesHaveWitnessCommit(transactions) { | ||
return (transactions instanceof Array && | ||
transactions[0] && | ||
transactions[0].ins && | ||
transactions[0].ins instanceof Array && | ||
transactions[0].ins[0] && | ||
transactions[0].ins[0].witness && | ||
transactions[0].ins[0].witness instanceof Array && | ||
transactions[0].ins[0].witness.length > 0); | ||
} | ||
Block.prototype.byteLength = function (headersOnly) { | ||
if (headersOnly || !this.transactions) return 80 | ||
return 80 + varuint.encodingLength(this.transactions.length) + this.transactions.reduce(function (a, x) { | ||
return a + x.byteLength() | ||
}, 0) | ||
function anyTxHasWitness(transactions) { | ||
return (transactions instanceof Array && | ||
transactions.some(tx => typeof tx === 'object' && | ||
tx.ins instanceof Array && | ||
tx.ins.some(input => typeof input === 'object' && | ||
input.witness instanceof Array && | ||
input.witness.length > 0))); | ||
} | ||
Block.fromHex = function (hex) { | ||
return Block.fromBuffer(Buffer.from(hex, 'hex')) | ||
} | ||
Block.prototype.getHash = function () { | ||
return bcrypto.hash256(this.toBuffer(true)) | ||
} | ||
Block.prototype.getId = function () { | ||
return this.getHash().reverse().toString('hex') | ||
} | ||
Block.prototype.getUTCDate = function () { | ||
const date = new Date(0) // epoch | ||
date.setUTCSeconds(this.timestamp) | ||
return date | ||
} | ||
// TODO: buffer, offset compatibility | ||
Block.prototype.toBuffer = function (headersOnly) { | ||
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)) | ||
let offset = 0 | ||
function writeSlice (slice) { | ||
slice.copy(buffer, offset) | ||
offset += slice.length | ||
} | ||
function writeInt32 (i) { | ||
buffer.writeInt32LE(i, offset) | ||
offset += 4 | ||
} | ||
function writeUInt32 (i) { | ||
buffer.writeUInt32LE(i, offset) | ||
offset += 4 | ||
} | ||
writeInt32(this.version) | ||
writeSlice(this.prevHash) | ||
writeSlice(this.merkleRoot) | ||
writeUInt32(this.timestamp) | ||
writeUInt32(this.bits) | ||
writeUInt32(this.nonce) | ||
if (headersOnly || !this.transactions) return buffer | ||
varuint.encode(this.transactions.length, buffer, offset) | ||
offset += varuint.encode.bytes | ||
this.transactions.forEach(function (tx) { | ||
const txSize = tx.byteLength() // TODO: extract from toBuffer? | ||
tx.toBuffer(buffer, offset) | ||
offset += txSize | ||
}) | ||
return buffer | ||
} | ||
Block.prototype.toHex = function (headersOnly) { | ||
return this.toBuffer(headersOnly).toString('hex') | ||
} | ||
Block.calculateTarget = function (bits) { | ||
const exponent = ((bits & 0xff000000) >> 24) - 3 | ||
const mantissa = bits & 0x007fffff | ||
const target = Buffer.alloc(32, 0) | ||
target.writeUIntBE(mantissa, 29 - exponent, 3) | ||
return target | ||
} | ||
Block.calculateMerkleRoot = function (transactions) { | ||
typeforce([{ getHash: types.Function }], transactions) | ||
if (transactions.length === 0) throw TypeError('Cannot compute merkle root for zero transactions') | ||
const hashes = transactions.map(function (transaction) { | ||
return transaction.getHash() | ||
}) | ||
return fastMerkleRoot(hashes, bcrypto.hash256) | ||
} | ||
Block.prototype.checkMerkleRoot = function () { | ||
if (!this.transactions) return false | ||
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions) | ||
return this.merkleRoot.compare(actualMerkleRoot) === 0 | ||
} | ||
Block.prototype.checkProofOfWork = function () { | ||
const hash = this.getHash().reverse() | ||
const target = Block.calculateTarget(this.bits) | ||
return hash.compare(target) <= 0 | ||
} | ||
module.exports = Block |
@@ -0,29 +1,42 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// https://github.com/feross/buffer/blob/master/index.js#L1127 | ||
function verifuint (value, max) { | ||
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number') | ||
if (value < 0) throw new Error('specified a negative value for writing an unsigned value') | ||
if (value > max) throw new Error('RangeError: value out of range') | ||
if (Math.floor(value) !== value) throw new Error('value has a fractional component') | ||
function verifuint(value, max) { | ||
if (typeof value !== 'number') | ||
throw new Error('cannot write a non-number as a number'); | ||
if (value < 0) | ||
throw new Error('specified a negative value for writing an unsigned value'); | ||
if (value > max) | ||
throw new Error('RangeError: value out of range'); | ||
if (Math.floor(value) !== value) | ||
throw new Error('value has a fractional component'); | ||
} | ||
function readUInt64LE (buffer, offset) { | ||
const a = buffer.readUInt32LE(offset) | ||
let b = buffer.readUInt32LE(offset + 4) | ||
b *= 0x100000000 | ||
verifuint(b + a, 0x001fffffffffffff) | ||
return b + a | ||
function readUInt64LE(buffer, offset) { | ||
const a = buffer.readUInt32LE(offset); | ||
let b = buffer.readUInt32LE(offset + 4); | ||
b *= 0x100000000; | ||
verifuint(b + a, 0x001fffffffffffff); | ||
return b + a; | ||
} | ||
function writeUInt64LE (buffer, value, offset) { | ||
verifuint(value, 0x001fffffffffffff) | ||
buffer.writeInt32LE(value & -1, offset) | ||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4) | ||
return offset + 8 | ||
exports.readUInt64LE = readUInt64LE; | ||
function writeUInt64LE(buffer, value, offset) { | ||
verifuint(value, 0x001fffffffffffff); | ||
buffer.writeInt32LE(value & -1, offset); | ||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); | ||
return offset + 8; | ||
} | ||
module.exports = { | ||
readUInt64LE: readUInt64LE, | ||
writeUInt64LE: writeUInt64LE | ||
exports.writeUInt64LE = writeUInt64LE; | ||
function reverseBuffer(buffer) { | ||
if (buffer.length < 1) | ||
return buffer; | ||
let j = buffer.length - 1; | ||
let tmp = 0; | ||
for (let i = 0; i < buffer.length / 2; i++) { | ||
tmp = buffer[i]; | ||
buffer[i] = buffer[j]; | ||
buffer[j] = tmp; | ||
j--; | ||
} | ||
return buffer; | ||
} | ||
exports.reverseBuffer = reverseBuffer; |
@@ -1,70 +0,75 @@ | ||
const decompile = require('./script').decompile | ||
const multisig = require('./templates/multisig') | ||
const nullData = require('./templates/nulldata') | ||
const pubKey = require('./templates/pubkey') | ||
const pubKeyHash = require('./templates/pubkeyhash') | ||
const scriptHash = require('./templates/scripthash') | ||
const witnessPubKeyHash = require('./templates/witnesspubkeyhash') | ||
const witnessScriptHash = require('./templates/witnessscripthash') | ||
const witnessCommitment = require('./templates/witnesscommitment') | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const script_1 = require("./script"); | ||
const multisig = require("./templates/multisig"); | ||
const nullData = require("./templates/nulldata"); | ||
const pubKey = require("./templates/pubkey"); | ||
const pubKeyHash = require("./templates/pubkeyhash"); | ||
const scriptHash = require("./templates/scripthash"); | ||
const witnessCommitment = require("./templates/witnesscommitment"); | ||
const witnessPubKeyHash = require("./templates/witnesspubkeyhash"); | ||
const witnessScriptHash = require("./templates/witnessscripthash"); | ||
const types = { | ||
P2MS: 'multisig', | ||
NONSTANDARD: 'nonstandard', | ||
NULLDATA: 'nulldata', | ||
P2PK: 'pubkey', | ||
P2PKH: 'pubkeyhash', | ||
P2SH: 'scripthash', | ||
P2WPKH: 'witnesspubkeyhash', | ||
P2WSH: 'witnessscripthash', | ||
WITNESS_COMMITMENT: 'witnesscommitment' | ||
P2MS: 'multisig', | ||
NONSTANDARD: 'nonstandard', | ||
NULLDATA: 'nulldata', | ||
P2PK: 'pubkey', | ||
P2PKH: 'pubkeyhash', | ||
P2SH: 'scripthash', | ||
P2WPKH: 'witnesspubkeyhash', | ||
P2WSH: 'witnessscripthash', | ||
WITNESS_COMMITMENT: 'witnesscommitment', | ||
}; | ||
exports.types = types; | ||
function classifyOutput(script) { | ||
if (witnessPubKeyHash.output.check(script)) | ||
return types.P2WPKH; | ||
if (witnessScriptHash.output.check(script)) | ||
return types.P2WSH; | ||
if (pubKeyHash.output.check(script)) | ||
return types.P2PKH; | ||
if (scriptHash.output.check(script)) | ||
return types.P2SH; | ||
// XXX: optimization, below functions .decompile before use | ||
const chunks = script_1.decompile(script); | ||
if (!chunks) | ||
throw new TypeError('Invalid script'); | ||
if (multisig.output.check(chunks)) | ||
return types.P2MS; | ||
if (pubKey.output.check(chunks)) | ||
return types.P2PK; | ||
if (witnessCommitment.output.check(chunks)) | ||
return types.WITNESS_COMMITMENT; | ||
if (nullData.output.check(chunks)) | ||
return types.NULLDATA; | ||
return types.NONSTANDARD; | ||
} | ||
function classifyOutput (script) { | ||
if (witnessPubKeyHash.output.check(script)) return types.P2WPKH | ||
if (witnessScriptHash.output.check(script)) return types.P2WSH | ||
if (pubKeyHash.output.check(script)) return types.P2PKH | ||
if (scriptHash.output.check(script)) return types.P2SH | ||
// XXX: optimization, below functions .decompile before use | ||
const chunks = decompile(script) | ||
if (!chunks) throw new TypeError('Invalid script') | ||
if (multisig.output.check(chunks)) return types.P2MS | ||
if (pubKey.output.check(chunks)) return types.P2PK | ||
if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT | ||
if (nullData.output.check(chunks)) return types.NULLDATA | ||
return types.NONSTANDARD | ||
exports.output = classifyOutput; | ||
function classifyInput(script, allowIncomplete) { | ||
// XXX: optimization, below functions .decompile before use | ||
const chunks = script_1.decompile(script); | ||
if (!chunks) | ||
throw new TypeError('Invalid script'); | ||
if (pubKeyHash.input.check(chunks)) | ||
return types.P2PKH; | ||
if (scriptHash.input.check(chunks, allowIncomplete)) | ||
return types.P2SH; | ||
if (multisig.input.check(chunks, allowIncomplete)) | ||
return types.P2MS; | ||
if (pubKey.input.check(chunks)) | ||
return types.P2PK; | ||
return types.NONSTANDARD; | ||
} | ||
function classifyInput (script, allowIncomplete) { | ||
// XXX: optimization, below functions .decompile before use | ||
const chunks = decompile(script) | ||
if (!chunks) throw new TypeError('Invalid script') | ||
if (pubKeyHash.input.check(chunks)) return types.P2PKH | ||
if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH | ||
if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS | ||
if (pubKey.input.check(chunks)) return types.P2PK | ||
return types.NONSTANDARD | ||
exports.input = classifyInput; | ||
function classifyWitness(script, allowIncomplete) { | ||
// XXX: optimization, below functions .decompile before use | ||
const chunks = script_1.decompile(script); | ||
if (!chunks) | ||
throw new TypeError('Invalid script'); | ||
if (witnessPubKeyHash.input.check(chunks)) | ||
return types.P2WPKH; | ||
if (witnessScriptHash.input.check(chunks, allowIncomplete)) | ||
return types.P2WSH; | ||
return types.NONSTANDARD; | ||
} | ||
function classifyWitness (script, allowIncomplete) { | ||
// XXX: optimization, below functions .decompile before use | ||
const chunks = decompile(script) | ||
if (!chunks) throw new TypeError('Invalid script') | ||
if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH | ||
if (witnessScriptHash.input.check(chunks, allowIncomplete)) return types.P2WSH | ||
return types.NONSTANDARD | ||
} | ||
module.exports = { | ||
input: classifyInput, | ||
output: classifyOutput, | ||
witness: classifyWitness, | ||
types: types | ||
} | ||
exports.witness = classifyWitness; |
@@ -1,40 +0,36 @@ | ||
const createHash = require('create-hash') | ||
let rmd160 | ||
try { | ||
rmd160 = require('crypto') | ||
.getHashes() | ||
.includes('rmd160') | ||
? 'rmd160' | ||
: 'ripemd160' | ||
} catch (err) { | ||
rmd160 = 'rmd160' | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const createHash = require('create-hash'); | ||
function ripemd160(buffer) { | ||
try { | ||
return createHash('rmd160') | ||
.update(buffer) | ||
.digest(); | ||
} | ||
catch (err) { | ||
return createHash('ripemd160') | ||
.update(buffer) | ||
.digest(); | ||
} | ||
} | ||
function ripemd160 (buffer) { | ||
return createHash(rmd160).update(buffer).digest() | ||
exports.ripemd160 = ripemd160; | ||
function sha1(buffer) { | ||
return createHash('sha1') | ||
.update(buffer) | ||
.digest(); | ||
} | ||
function sha1 (buffer) { | ||
return createHash('sha1').update(buffer).digest() | ||
exports.sha1 = sha1; | ||
function sha256(buffer) { | ||
return createHash('sha256') | ||
.update(buffer) | ||
.digest(); | ||
} | ||
function sha256 (buffer) { | ||
return createHash('sha256').update(buffer).digest() | ||
exports.sha256 = sha256; | ||
function hash160(buffer) { | ||
return ripemd160(sha256(buffer)); | ||
} | ||
function hash160 (buffer) { | ||
return ripemd160(sha256(buffer)) | ||
exports.hash160 = hash160; | ||
function hash256(buffer) { | ||
return sha256(sha256(buffer)); | ||
} | ||
function hash256 (buffer) { | ||
return sha256(sha256(buffer)) | ||
} | ||
module.exports = { | ||
hash160: hash160, | ||
hash256: hash256, | ||
ripemd160: ripemd160, | ||
sha1: sha1, | ||
sha256: sha256 | ||
} | ||
exports.hash256 = hash256; |
@@ -1,106 +0,98 @@ | ||
const ecc = require('tiny-secp256k1') | ||
const randomBytes = require('randombytes') | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const wif = require('wif') | ||
const NETWORKS = require('./networks') | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const NETWORKS = require("./networks"); | ||
const types = require("./types"); | ||
const ecc = require('tiny-secp256k1'); | ||
const randomBytes = require('randombytes'); | ||
const typeforce = require('typeforce'); | ||
const wif = require('wif'); | ||
const isOptions = typeforce.maybe(typeforce.compile({ | ||
compressed: types.maybe(types.Boolean), | ||
network: types.maybe(types.Network) | ||
})) | ||
function ECPair (d, Q, options) { | ||
options = options || {} | ||
this.compressed = options.compressed === undefined ? true : options.compressed | ||
this.network = options.network || NETWORKS.bitcoin | ||
this.__d = d || null | ||
this.__Q = null | ||
if (Q) this.__Q = ecc.pointCompress(Q, this.compressed) | ||
compressed: types.maybe(types.Boolean), | ||
network: types.maybe(types.Network), | ||
})); | ||
class ECPair { | ||
constructor(__D, __Q, options) { | ||
this.__D = __D; | ||
this.__Q = __Q; | ||
if (options === undefined) | ||
options = {}; | ||
this.compressed = | ||
options.compressed === undefined ? true : options.compressed; | ||
this.network = options.network || NETWORKS.bitcoin; | ||
if (__Q !== undefined) | ||
this.__Q = ecc.pointCompress(__Q, this.compressed); | ||
} | ||
get privateKey() { | ||
return this.__D; | ||
} | ||
get publicKey() { | ||
if (!this.__Q) | ||
this.__Q = ecc.pointFromScalar(this.__D, this.compressed); | ||
return this.__Q; | ||
} | ||
toWIF() { | ||
if (!this.__D) | ||
throw new Error('Missing private key'); | ||
return wif.encode(this.network.wif, this.__D, this.compressed); | ||
} | ||
sign(hash) { | ||
if (!this.__D) | ||
throw new Error('Missing private key'); | ||
return ecc.sign(hash, this.__D); | ||
} | ||
verify(hash, signature) { | ||
return ecc.verify(hash, this.publicKey, signature); | ||
} | ||
} | ||
Object.defineProperty(ECPair.prototype, 'privateKey', { | ||
enumerable: false, | ||
get: function () { return this.__d } | ||
}) | ||
Object.defineProperty(ECPair.prototype, 'publicKey', { get: function () { | ||
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed) | ||
return this.__Q | ||
}}) | ||
ECPair.prototype.toWIF = function () { | ||
if (!this.__d) throw new Error('Missing private key') | ||
return wif.encode(this.network.wif, this.__d, this.compressed) | ||
function fromPrivateKey(buffer, options) { | ||
typeforce(types.Buffer256bit, buffer); | ||
if (!ecc.isPrivate(buffer)) | ||
throw new TypeError('Private key not in range [1, n)'); | ||
typeforce(isOptions, options); | ||
return new ECPair(buffer, undefined, options); | ||
} | ||
ECPair.prototype.sign = function (hash) { | ||
if (!this.__d) throw new Error('Missing private key') | ||
return ecc.sign(hash, this.__d) | ||
exports.fromPrivateKey = fromPrivateKey; | ||
function fromPublicKey(buffer, options) { | ||
typeforce(ecc.isPoint, buffer); | ||
typeforce(isOptions, options); | ||
return new ECPair(undefined, buffer, options); | ||
} | ||
ECPair.prototype.verify = function (hash, signature) { | ||
return ecc.verify(hash, this.publicKey, signature) | ||
exports.fromPublicKey = fromPublicKey; | ||
function fromWIF(wifString, network) { | ||
const decoded = wif.decode(wifString); | ||
const version = decoded.version; | ||
// list of networks? | ||
if (types.Array(network)) { | ||
network = network | ||
.filter((x) => { | ||
return version === x.wif; | ||
}) | ||
.pop(); | ||
if (!network) | ||
throw new Error('Unknown network version'); | ||
// otherwise, assume a network object (or default to bitcoin) | ||
} | ||
else { | ||
network = network || NETWORKS.bitcoin; | ||
if (version !== network.wif) | ||
throw new Error('Invalid network version'); | ||
} | ||
return fromPrivateKey(decoded.privateKey, { | ||
compressed: decoded.compressed, | ||
network: network, | ||
}); | ||
} | ||
function fromPrivateKey (buffer, options) { | ||
typeforce(types.Buffer256bit, buffer) | ||
if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)') | ||
typeforce(isOptions, options) | ||
return new ECPair(buffer, null, options) | ||
exports.fromWIF = fromWIF; | ||
function makeRandom(options) { | ||
typeforce(isOptions, options); | ||
if (options === undefined) | ||
options = {}; | ||
const rng = options.rng || randomBytes; | ||
let d; | ||
do { | ||
d = rng(32); | ||
typeforce(types.Buffer256bit, d); | ||
} while (!ecc.isPrivate(d)); | ||
return fromPrivateKey(d, options); | ||
} | ||
function fromPublicKey (buffer, options) { | ||
typeforce(ecc.isPoint, buffer) | ||
typeforce(isOptions, options) | ||
return new ECPair(null, buffer, options) | ||
} | ||
function fromWIF (string, network) { | ||
const decoded = wif.decode(string) | ||
const version = decoded.version | ||
// list of networks? | ||
if (types.Array(network)) { | ||
network = network.filter(function (x) { | ||
return version === x.wif | ||
}).pop() | ||
if (!network) throw new Error('Unknown network version') | ||
// otherwise, assume a network object (or default to bitcoin) | ||
} else { | ||
network = network || NETWORKS.bitcoin | ||
if (version !== network.wif) throw new Error('Invalid network version') | ||
} | ||
return fromPrivateKey(decoded.privateKey, { | ||
compressed: decoded.compressed, | ||
network: network | ||
}) | ||
} | ||
function makeRandom (options) { | ||
typeforce(isOptions, options) | ||
options = options || {} | ||
const rng = options.rng || randomBytes | ||
let d | ||
do { | ||
d = rng(32) | ||
typeforce(types.Buffer256bit, d) | ||
} while (!ecc.isPrivate(d)) | ||
return fromPrivateKey(d, options) | ||
} | ||
module.exports = { | ||
makeRandom, | ||
fromPrivateKey, | ||
fromPublicKey, | ||
fromWIF | ||
} | ||
exports.makeRandom = makeRandom; |
@@ -1,16 +0,24 @@ | ||
const script = require('./script') | ||
module.exports = { | ||
Block: require('./block'), | ||
ECPair: require('./ecpair'), | ||
Transaction: require('./transaction'), | ||
TransactionBuilder: require('./transaction_builder'), | ||
address: require('./address'), | ||
bip32: require('bip32'), | ||
crypto: require('./crypto'), | ||
networks: require('./networks'), | ||
opcodes: require('bitcoin-ops'), | ||
payments: require('./payments'), | ||
script: script | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bip32 = require("bip32"); | ||
exports.bip32 = bip32; | ||
const address = require("./address"); | ||
exports.address = address; | ||
const crypto = require("./crypto"); | ||
exports.crypto = crypto; | ||
const ECPair = require("./ecpair"); | ||
exports.ECPair = ECPair; | ||
const networks = require("./networks"); | ||
exports.networks = networks; | ||
const payments = require("./payments"); | ||
exports.payments = payments; | ||
const script = require("./script"); | ||
exports.script = script; | ||
var block_1 = require("./block"); | ||
exports.Block = block_1.Block; | ||
var script_1 = require("./script"); | ||
exports.opcodes = script_1.OPS; | ||
var transaction_1 = require("./transaction"); | ||
exports.Transaction = transaction_1.Transaction; | ||
var transaction_builder_1 = require("./transaction_builder"); | ||
exports.TransactionBuilder = transaction_builder_1.TransactionBuilder; |
@@ -1,38 +0,35 @@ | ||
// https://en.bitcoin.it/wiki/List_of_address_prefixes | ||
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 | ||
module.exports = { | ||
bitcoin: { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.bitcoin = { | ||
messagePrefix: '\x18Bitcoin Signed Message:\n', | ||
bech32: 'bc', | ||
bip32: { | ||
public: 0x0488b21e, | ||
private: 0x0488ade4 | ||
public: 0x0488b21e, | ||
private: 0x0488ade4, | ||
}, | ||
pubKeyHash: 0x00, | ||
scriptHash: 0x05, | ||
wif: 0x80 | ||
}, | ||
regtest: { | ||
wif: 0x80, | ||
}; | ||
exports.regtest = { | ||
messagePrefix: '\x18Bitcoin Signed Message:\n', | ||
bech32: 'bcrt', | ||
bip32: { | ||
public: 0x043587cf, | ||
private: 0x04358394 | ||
public: 0x043587cf, | ||
private: 0x04358394, | ||
}, | ||
pubKeyHash: 0x6f, | ||
scriptHash: 0xc4, | ||
wif: 0xef | ||
}, | ||
testnet: { | ||
wif: 0xef, | ||
}; | ||
exports.testnet = { | ||
messagePrefix: '\x18Bitcoin Signed Message:\n', | ||
bech32: 'tb', | ||
bip32: { | ||
public: 0x043587cf, | ||
private: 0x04358394 | ||
public: 0x043587cf, | ||
private: 0x04358394, | ||
}, | ||
pubKeyHash: 0x6f, | ||
scriptHash: 0xc4, | ||
wif: 0xef | ||
} | ||
} | ||
wif: 0xef, | ||
}; |
@@ -1,56 +0,51 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
function stacksEqual (a, b) { | ||
if (a.length !== b.length) return false | ||
return a.every(function (x, i) { | ||
return x.equals(b[i]) | ||
}) | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const typef = require('typeforce'); | ||
const OPS = bscript.OPS; | ||
function stacksEqual(a, b) { | ||
if (a.length !== b.length) | ||
return false; | ||
return a.every((x, i) => { | ||
return x.equals(b[i]); | ||
}); | ||
} | ||
// output: OP_RETURN ... | ||
function p2data (a, opts) { | ||
if ( | ||
!a.data && | ||
!a.output | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
data: typef.maybe(typef.arrayOf(typef.Buffer)) | ||
}, a) | ||
const network = a.network || BITCOIN_NETWORK | ||
const o = { network } | ||
lazy.prop(o, 'output', function () { | ||
if (!a.data) return | ||
return bscript.compile([OPS.OP_RETURN].concat(a.data)) | ||
}) | ||
lazy.prop(o, 'data', function () { | ||
if (!a.output) return | ||
return bscript.decompile(a.output).slice(1) | ||
}) | ||
// extended validation | ||
if (opts.validate) { | ||
if (a.output) { | ||
const chunks = bscript.decompile(a.output) | ||
if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid') | ||
if (!chunks.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid') | ||
if (a.data && !stacksEqual(a.data, o.data)) throw new TypeError('Data mismatch') | ||
function p2data(a, opts) { | ||
if (!a.data && !a.output) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
data: typef.maybe(typef.arrayOf(typef.Buffer)), | ||
}, a); | ||
const network = a.network || networks_1.bitcoin; | ||
const o = { network }; | ||
lazy.prop(o, 'output', () => { | ||
if (!a.data) | ||
return; | ||
return bscript.compile([OPS.OP_RETURN].concat(a.data)); | ||
}); | ||
lazy.prop(o, 'data', () => { | ||
if (!a.output) | ||
return; | ||
return bscript.decompile(a.output).slice(1); | ||
}); | ||
// extended validation | ||
if (opts.validate) { | ||
if (a.output) { | ||
const chunks = bscript.decompile(a.output); | ||
if (chunks[0] !== OPS.OP_RETURN) | ||
throw new TypeError('Output is invalid'); | ||
if (!chunks.slice(1).every(typef.Buffer)) | ||
throw new TypeError('Output is invalid'); | ||
if (a.data && !stacksEqual(a.data, o.data)) | ||
throw new TypeError('Data mismatch'); | ||
} | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2data | ||
exports.p2data = p2data; |
@@ -1,12 +0,18 @@ | ||
const embed = require('./embed') | ||
const p2ms = require('./p2ms') | ||
const p2pk = require('./p2pk') | ||
const p2pkh = require('./p2pkh') | ||
const p2sh = require('./p2sh') | ||
const p2wpkh = require('./p2wpkh') | ||
const p2wsh = require('./p2wsh') | ||
module.exports = { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh } | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const embed_1 = require("./embed"); | ||
exports.embed = embed_1.p2data; | ||
const p2ms_1 = require("./p2ms"); | ||
exports.p2ms = p2ms_1.p2ms; | ||
const p2pk_1 = require("./p2pk"); | ||
exports.p2pk = p2pk_1.p2pk; | ||
const p2pkh_1 = require("./p2pkh"); | ||
exports.p2pkh = p2pkh_1.p2pkh; | ||
const p2sh_1 = require("./p2sh"); | ||
exports.p2sh = p2sh_1.p2sh; | ||
const p2wpkh_1 = require("./p2wpkh"); | ||
exports.p2wpkh = p2wpkh_1.p2wpkh; | ||
const p2wsh_1 = require("./p2wsh"); | ||
exports.p2wsh = p2wsh_1.p2wsh; | ||
// TODO | ||
// witness commitment |
@@ -1,30 +0,32 @@ | ||
function prop (object, name, f) { | ||
Object.defineProperty(object, name, { | ||
configurable: true, | ||
enumerable: true, | ||
get: function () { | ||
let value = f.call(this) | ||
this[name] = value | ||
return value | ||
}, | ||
set: function (value) { | ||
Object.defineProperty(this, name, { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function prop(object, name, f) { | ||
Object.defineProperty(object, name, { | ||
configurable: true, | ||
enumerable: true, | ||
value: value, | ||
writable: true | ||
}) | ||
} | ||
}) | ||
get() { | ||
const _value = f.call(this); | ||
this[name] = _value; | ||
return _value; | ||
}, | ||
set(_value) { | ||
Object.defineProperty(this, name, { | ||
configurable: true, | ||
enumerable: true, | ||
value: _value, | ||
writable: true, | ||
}); | ||
}, | ||
}); | ||
} | ||
function value (f) { | ||
let value | ||
return function () { | ||
if (value !== undefined) return value | ||
value = f() | ||
return value | ||
} | ||
exports.prop = prop; | ||
function value(f) { | ||
let _value; | ||
return () => { | ||
if (_value !== undefined) | ||
return _value; | ||
_value = f(); | ||
return _value; | ||
}; | ||
} | ||
module.exports = { prop, value } | ||
exports.value = value; |
@@ -1,140 +0,141 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const ecc = require('tiny-secp256k1') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 | ||
function stacksEqual (a, b) { | ||
if (a.length !== b.length) return false | ||
return a.every(function (x, i) { | ||
return x.equals(b[i]) | ||
}) | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const OPS = bscript.OPS; | ||
const typef = require('typeforce'); | ||
const ecc = require('tiny-secp256k1'); | ||
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 | ||
function stacksEqual(a, b) { | ||
if (a.length !== b.length) | ||
return false; | ||
return a.every((x, i) => { | ||
return x.equals(b[i]); | ||
}); | ||
} | ||
// input: OP_0 [signatures ...] | ||
// output: m [pubKeys ...] n OP_CHECKMULTISIG | ||
function p2ms (a, opts) { | ||
if ( | ||
!a.input && | ||
!a.output && | ||
!(a.pubkeys && a.m !== undefined) && | ||
!a.signatures | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
function isAcceptableSignature (x) { | ||
return bscript.isCanonicalScriptSignature(x) || (opts.allowIncomplete && (x === OPS.OP_0)) | ||
} | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
m: typef.maybe(typef.Number), | ||
n: typef.maybe(typef.Number), | ||
output: typef.maybe(typef.Buffer), | ||
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), | ||
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), | ||
input: typef.maybe(typef.Buffer) | ||
}, a) | ||
const network = a.network || BITCOIN_NETWORK | ||
const o = { network } | ||
let chunks | ||
let decoded = false | ||
function decode (output) { | ||
if (decoded) return | ||
decoded = true | ||
chunks = bscript.decompile(output) | ||
o.m = chunks[0] - OP_INT_BASE | ||
o.n = chunks[chunks.length - 2] - OP_INT_BASE | ||
o.pubkeys = chunks.slice(1, -2) | ||
} | ||
lazy.prop(o, 'output', function () { | ||
if (!a.m) return | ||
if (!o.n) return | ||
if (!a.pubkeys) return | ||
return bscript.compile([].concat( | ||
OP_INT_BASE + a.m, | ||
a.pubkeys, | ||
OP_INT_BASE + o.n, | ||
OPS.OP_CHECKMULTISIG | ||
)) | ||
}) | ||
lazy.prop(o, 'm', function () { | ||
if (!o.output) return | ||
decode(o.output) | ||
return o.m | ||
}) | ||
lazy.prop(o, 'n', function () { | ||
if (!o.pubkeys) return | ||
return o.pubkeys.length | ||
}) | ||
lazy.prop(o, 'pubkeys', function () { | ||
if (!a.output) return | ||
decode(a.output) | ||
return o.pubkeys | ||
}) | ||
lazy.prop(o, 'signatures', function () { | ||
if (!a.input) return | ||
return bscript.decompile(a.input).slice(1) | ||
}) | ||
lazy.prop(o, 'input', function () { | ||
if (!a.signatures) return | ||
return bscript.compile([OPS.OP_0].concat(a.signatures)) | ||
}) | ||
lazy.prop(o, 'witness', function () { | ||
if (!o.input) return | ||
return [] | ||
}) | ||
// extended validation | ||
if (opts.validate) { | ||
if (a.output) { | ||
decode(a.output) | ||
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid') | ||
if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid') | ||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid') | ||
if ( | ||
o.m <= 0 || | ||
o.n > 16 || | ||
o.m > o.n || | ||
o.n !== chunks.length - 3) throw new TypeError('Output is invalid') | ||
if (!o.pubkeys.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid') | ||
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch') | ||
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch') | ||
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch') | ||
function p2ms(a, opts) { | ||
if (!a.input && | ||
!a.output && | ||
!(a.pubkeys && a.m !== undefined) && | ||
!a.signatures) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
function isAcceptableSignature(x) { | ||
return (bscript.isCanonicalScriptSignature(x) || | ||
(opts.allowIncomplete && x === OPS.OP_0) !== undefined); | ||
} | ||
if (a.pubkeys) { | ||
if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch') | ||
o.n = a.pubkeys.length | ||
if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m') | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
m: typef.maybe(typef.Number), | ||
n: typef.maybe(typef.Number), | ||
output: typef.maybe(typef.Buffer), | ||
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), | ||
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), | ||
input: typef.maybe(typef.Buffer), | ||
}, a); | ||
const network = a.network || networks_1.bitcoin; | ||
const o = { network }; | ||
let chunks = []; | ||
let decoded = false; | ||
function decode(output) { | ||
if (decoded) | ||
return; | ||
decoded = true; | ||
chunks = bscript.decompile(output); | ||
o.m = chunks[0] - OP_INT_BASE; | ||
o.n = chunks[chunks.length - 2] - OP_INT_BASE; | ||
o.pubkeys = chunks.slice(1, -2); | ||
} | ||
if (a.signatures) { | ||
if (a.signatures.length < o.m) throw new TypeError('Not enough signatures provided') | ||
if (a.signatures.length > o.m) throw new TypeError('Too many signatures provided') | ||
lazy.prop(o, 'output', () => { | ||
if (!a.m) | ||
return; | ||
if (!o.n) | ||
return; | ||
if (!a.pubkeys) | ||
return; | ||
return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG)); | ||
}); | ||
lazy.prop(o, 'm', () => { | ||
if (!o.output) | ||
return; | ||
decode(o.output); | ||
return o.m; | ||
}); | ||
lazy.prop(o, 'n', () => { | ||
if (!o.pubkeys) | ||
return; | ||
return o.pubkeys.length; | ||
}); | ||
lazy.prop(o, 'pubkeys', () => { | ||
if (!a.output) | ||
return; | ||
decode(a.output); | ||
return o.pubkeys; | ||
}); | ||
lazy.prop(o, 'signatures', () => { | ||
if (!a.input) | ||
return; | ||
return bscript.decompile(a.input).slice(1); | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
if (!a.signatures) | ||
return; | ||
return bscript.compile([OPS.OP_0].concat(a.signatures)); | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
if (!o.input) | ||
return; | ||
return []; | ||
}); | ||
// extended validation | ||
if (opts.validate) { | ||
if (a.output) { | ||
decode(a.output); | ||
if (!typef.Number(chunks[0])) | ||
throw new TypeError('Output is invalid'); | ||
if (!typef.Number(chunks[chunks.length - 2])) | ||
throw new TypeError('Output is invalid'); | ||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) | ||
throw new TypeError('Output is invalid'); | ||
if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3) | ||
throw new TypeError('Output is invalid'); | ||
if (!o.pubkeys.every(x => ecc.isPoint(x))) | ||
throw new TypeError('Output is invalid'); | ||
if (a.m !== undefined && a.m !== o.m) | ||
throw new TypeError('m mismatch'); | ||
if (a.n !== undefined && a.n !== o.n) | ||
throw new TypeError('n mismatch'); | ||
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) | ||
throw new TypeError('Pubkeys mismatch'); | ||
} | ||
if (a.pubkeys) { | ||
if (a.n !== undefined && a.n !== a.pubkeys.length) | ||
throw new TypeError('Pubkey count mismatch'); | ||
o.n = a.pubkeys.length; | ||
if (o.n < o.m) | ||
throw new TypeError('Pubkey count cannot be less than m'); | ||
} | ||
if (a.signatures) { | ||
if (a.signatures.length < o.m) | ||
throw new TypeError('Not enough signatures provided'); | ||
if (a.signatures.length > o.m) | ||
throw new TypeError('Too many signatures provided'); | ||
} | ||
if (a.input) { | ||
if (a.input[0] !== OPS.OP_0) | ||
throw new TypeError('Input is invalid'); | ||
if (o.signatures.length === 0 || | ||
!o.signatures.every(isAcceptableSignature)) | ||
throw new TypeError('Input has invalid signature(s)'); | ||
if (a.signatures && !stacksEqual(a.signatures, o.signatures)) | ||
throw new TypeError('Signature mismatch'); | ||
if (a.m !== undefined && a.m !== a.signatures.length) | ||
throw new TypeError('Signature count mismatch'); | ||
} | ||
} | ||
if (a.input) { | ||
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid') | ||
if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)') | ||
if (a.signatures && !stacksEqual(a.signatures, o.signatures)) throw new TypeError('Signature mismatch') | ||
if (a.m !== undefined && a.m !== a.signatures.length) throw new TypeError('Signature count mismatch') | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2ms | ||
exports.p2ms = p2ms; |
@@ -1,80 +0,75 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const ecc = require('tiny-secp256k1') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const typef = require('typeforce'); | ||
const OPS = bscript.OPS; | ||
const ecc = require('tiny-secp256k1'); | ||
// input: {signature} | ||
// output: {pubKey} OP_CHECKSIG | ||
function p2pk (a, opts) { | ||
if ( | ||
!a.input && | ||
!a.output && | ||
!a.pubkey && | ||
!a.input && | ||
!a.signature | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
pubkey: typef.maybe(ecc.isPoint), | ||
signature: typef.maybe(bscript.isCanonicalScriptSignature), | ||
input: typef.maybe(typef.Buffer) | ||
}, a) | ||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) }) | ||
const network = a.network || BITCOIN_NETWORK | ||
const o = { network } | ||
lazy.prop(o, 'output', function () { | ||
if (!a.pubkey) return | ||
return bscript.compile([ | ||
a.pubkey, | ||
OPS.OP_CHECKSIG | ||
]) | ||
}) | ||
lazy.prop(o, 'pubkey', function () { | ||
if (!a.output) return | ||
return a.output.slice(1, -1) | ||
}) | ||
lazy.prop(o, 'signature', function () { | ||
if (!a.input) return | ||
return _chunks()[0] | ||
}) | ||
lazy.prop(o, 'input', function () { | ||
if (!a.signature) return | ||
return bscript.compile([a.signature]) | ||
}) | ||
lazy.prop(o, 'witness', function () { | ||
if (!o.input) return | ||
return [] | ||
}) | ||
// extended validation | ||
if (opts.validate) { | ||
if (a.output) { | ||
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid') | ||
if (!ecc.isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid') | ||
if (a.pubkey && !a.pubkey.equals(o.pubkey)) throw new TypeError('Pubkey mismatch') | ||
function p2pk(a, opts) { | ||
if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
pubkey: typef.maybe(ecc.isPoint), | ||
signature: typef.maybe(bscript.isCanonicalScriptSignature), | ||
input: typef.maybe(typef.Buffer), | ||
}, a); | ||
const _chunks = lazy.value(() => { | ||
return bscript.decompile(a.input); | ||
}); | ||
const network = a.network || networks_1.bitcoin; | ||
const o = { network }; | ||
lazy.prop(o, 'output', () => { | ||
if (!a.pubkey) | ||
return; | ||
return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); | ||
}); | ||
lazy.prop(o, 'pubkey', () => { | ||
if (!a.output) | ||
return; | ||
return a.output.slice(1, -1); | ||
}); | ||
lazy.prop(o, 'signature', () => { | ||
if (!a.input) | ||
return; | ||
return _chunks()[0]; | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
if (!a.signature) | ||
return; | ||
return bscript.compile([a.signature]); | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
if (!o.input) | ||
return; | ||
return []; | ||
}); | ||
// extended validation | ||
if (opts.validate) { | ||
if (a.output) { | ||
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) | ||
throw new TypeError('Output is invalid'); | ||
if (!ecc.isPoint(o.pubkey)) | ||
throw new TypeError('Output pubkey is invalid'); | ||
if (a.pubkey && !a.pubkey.equals(o.pubkey)) | ||
throw new TypeError('Pubkey mismatch'); | ||
} | ||
if (a.signature) { | ||
if (a.input && !a.input.equals(o.input)) | ||
throw new TypeError('Signature mismatch'); | ||
} | ||
if (a.input) { | ||
if (_chunks().length !== 1) | ||
throw new TypeError('Input is invalid'); | ||
if (!bscript.isCanonicalScriptSignature(o.signature)) | ||
throw new TypeError('Input has invalid signature'); | ||
} | ||
} | ||
if (a.signature) { | ||
if (a.input && !a.input.equals(o.input)) throw new TypeError('Signature mismatch') | ||
} | ||
if (a.input) { | ||
if (_chunks().length !== 1) throw new TypeError('Input is invalid') | ||
if (!bscript.isCanonicalScriptSignature(o.signature)) throw new TypeError('Input has invalid signature') | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2pk | ||
exports.p2pk = p2pk; |
@@ -1,137 +0,142 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const ecc = require('tiny-secp256k1') | ||
const bcrypto = require('../crypto') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
const bs58check = require('bs58check') | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bcrypto = require("../crypto"); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const typef = require('typeforce'); | ||
const OPS = bscript.OPS; | ||
const ecc = require('tiny-secp256k1'); | ||
const bs58check = require('bs58check'); | ||
// input: {signature} {pubkey} | ||
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG | ||
function p2pkh (a, opts) { | ||
if ( | ||
!a.address && | ||
!a.hash && | ||
!a.output && | ||
!a.pubkey && | ||
!a.input | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(20)), | ||
output: typef.maybe(typef.BufferN(25)), | ||
pubkey: typef.maybe(ecc.isPoint), | ||
signature: typef.maybe(bscript.isCanonicalScriptSignature), | ||
input: typef.maybe(typef.Buffer) | ||
}, a) | ||
const _address = lazy.value(function () { | ||
const payload = bs58check.decode(a.address) | ||
const version = payload.readUInt8(0) | ||
const hash = payload.slice(1) | ||
return { version, hash } | ||
}) | ||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) }) | ||
const network = a.network || BITCOIN_NETWORK | ||
const o = { network } | ||
lazy.prop(o, 'address', function () { | ||
if (!o.hash) return | ||
const payload = Buffer.allocUnsafe(21) | ||
payload.writeUInt8(network.pubKeyHash, 0) | ||
o.hash.copy(payload, 1) | ||
return bs58check.encode(payload) | ||
}) | ||
lazy.prop(o, 'hash', function () { | ||
if (a.output) return a.output.slice(3, 23) | ||
if (a.address) return _address().hash | ||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey) | ||
}) | ||
lazy.prop(o, 'output', function () { | ||
if (!o.hash) return | ||
return bscript.compile([ | ||
OPS.OP_DUP, | ||
OPS.OP_HASH160, | ||
o.hash, | ||
OPS.OP_EQUALVERIFY, | ||
OPS.OP_CHECKSIG | ||
]) | ||
}) | ||
lazy.prop(o, 'pubkey', function () { | ||
if (!a.input) return | ||
return _chunks()[1] | ||
}) | ||
lazy.prop(o, 'signature', function () { | ||
if (!a.input) return | ||
return _chunks()[0] | ||
}) | ||
lazy.prop(o, 'input', function () { | ||
if (!a.pubkey) return | ||
if (!a.signature) return | ||
return bscript.compile([a.signature, a.pubkey]) | ||
}) | ||
lazy.prop(o, 'witness', function () { | ||
if (!o.input) return | ||
return [] | ||
}) | ||
// extended validation | ||
if (opts.validate) { | ||
let hash | ||
if (a.address) { | ||
if (_address().version !== network.pubKeyHash) throw new TypeError('Invalid version or Network mismatch') | ||
if (_address().hash.length !== 20) throw new TypeError('Invalid address') | ||
hash = _address().hash | ||
function p2pkh(a, opts) { | ||
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(20)), | ||
output: typef.maybe(typef.BufferN(25)), | ||
pubkey: typef.maybe(ecc.isPoint), | ||
signature: typef.maybe(bscript.isCanonicalScriptSignature), | ||
input: typef.maybe(typef.Buffer), | ||
}, a); | ||
const _address = lazy.value(() => { | ||
const payload = bs58check.decode(a.address); | ||
const version = payload.readUInt8(0); | ||
const hash = payload.slice(1); | ||
return { version, hash }; | ||
}); | ||
const _chunks = lazy.value(() => { | ||
return bscript.decompile(a.input); | ||
}); | ||
const network = a.network || networks_1.bitcoin; | ||
const o = { network }; | ||
lazy.prop(o, 'address', () => { | ||
if (!o.hash) | ||
return; | ||
const payload = Buffer.allocUnsafe(21); | ||
payload.writeUInt8(network.pubKeyHash, 0); | ||
o.hash.copy(payload, 1); | ||
return bs58check.encode(payload); | ||
}); | ||
lazy.prop(o, 'hash', () => { | ||
if (a.output) | ||
return a.output.slice(3, 23); | ||
if (a.address) | ||
return _address().hash; | ||
if (a.pubkey || o.pubkey) | ||
return bcrypto.hash160(a.pubkey || o.pubkey); | ||
}); | ||
lazy.prop(o, 'output', () => { | ||
if (!o.hash) | ||
return; | ||
return bscript.compile([ | ||
OPS.OP_DUP, | ||
OPS.OP_HASH160, | ||
o.hash, | ||
OPS.OP_EQUALVERIFY, | ||
OPS.OP_CHECKSIG, | ||
]); | ||
}); | ||
lazy.prop(o, 'pubkey', () => { | ||
if (!a.input) | ||
return; | ||
return _chunks()[1]; | ||
}); | ||
lazy.prop(o, 'signature', () => { | ||
if (!a.input) | ||
return; | ||
return _chunks()[0]; | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
if (!a.pubkey) | ||
return; | ||
if (!a.signature) | ||
return; | ||
return bscript.compile([a.signature, a.pubkey]); | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
if (!o.input) | ||
return; | ||
return []; | ||
}); | ||
// extended validation | ||
if (opts.validate) { | ||
let hash = Buffer.from([]); | ||
if (a.address) { | ||
if (_address().version !== network.pubKeyHash) | ||
throw new TypeError('Invalid version or Network mismatch'); | ||
if (_address().hash.length !== 20) | ||
throw new TypeError('Invalid address'); | ||
hash = _address().hash; | ||
} | ||
if (a.hash) { | ||
if (hash.length > 0 && !hash.equals(a.hash)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = a.hash; | ||
} | ||
if (a.output) { | ||
if (a.output.length !== 25 || | ||
a.output[0] !== OPS.OP_DUP || | ||
a.output[1] !== OPS.OP_HASH160 || | ||
a.output[2] !== 0x14 || | ||
a.output[23] !== OPS.OP_EQUALVERIFY || | ||
a.output[24] !== OPS.OP_CHECKSIG) | ||
throw new TypeError('Output is invalid'); | ||
const hash2 = a.output.slice(3, 23); | ||
if (hash.length > 0 && !hash.equals(hash2)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = hash2; | ||
} | ||
if (a.pubkey) { | ||
const pkh = bcrypto.hash160(a.pubkey); | ||
if (hash.length > 0 && !hash.equals(pkh)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = pkh; | ||
} | ||
if (a.input) { | ||
const chunks = _chunks(); | ||
if (chunks.length !== 2) | ||
throw new TypeError('Input is invalid'); | ||
if (!bscript.isCanonicalScriptSignature(chunks[0])) | ||
throw new TypeError('Input has invalid signature'); | ||
if (!ecc.isPoint(chunks[1])) | ||
throw new TypeError('Input has invalid pubkey'); | ||
if (a.signature && !a.signature.equals(chunks[0])) | ||
throw new TypeError('Signature mismatch'); | ||
if (a.pubkey && !a.pubkey.equals(chunks[1])) | ||
throw new TypeError('Pubkey mismatch'); | ||
const pkh = bcrypto.hash160(chunks[1]); | ||
if (hash.length > 0 && !hash.equals(pkh)) | ||
throw new TypeError('Hash mismatch'); | ||
} | ||
} | ||
if (a.hash) { | ||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') | ||
else hash = a.hash | ||
} | ||
if (a.output) { | ||
if ( | ||
a.output.length !== 25 || | ||
a.output[0] !== OPS.OP_DUP || | ||
a.output[1] !== OPS.OP_HASH160 || | ||
a.output[2] !== 0x14 || | ||
a.output[23] !== OPS.OP_EQUALVERIFY || | ||
a.output[24] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid') | ||
const hash2 = a.output.slice(3, 23) | ||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') | ||
else hash = hash2 | ||
} | ||
if (a.pubkey) { | ||
const pkh = bcrypto.hash160(a.pubkey) | ||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') | ||
else hash = pkh | ||
} | ||
if (a.input) { | ||
const chunks = _chunks() | ||
if (chunks.length !== 2) throw new TypeError('Input is invalid') | ||
if (!bscript.isCanonicalScriptSignature(chunks[0])) throw new TypeError('Input has invalid signature') | ||
if (!ecc.isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey') | ||
if (a.signature && !a.signature.equals(chunks[0])) throw new TypeError('Signature mismatch') | ||
if (a.pubkey && !a.pubkey.equals(chunks[1])) throw new TypeError('Pubkey mismatch') | ||
const pkh = bcrypto.hash160(chunks[1]) | ||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2pkh | ||
exports.p2pkh = p2pkh; |
@@ -1,193 +0,185 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const bcrypto = require('../crypto') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
const bs58check = require('bs58check') | ||
function stacksEqual (a, b) { | ||
if (a.length !== b.length) return false | ||
return a.every(function (x, i) { | ||
return x.equals(b[i]) | ||
}) | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bcrypto = require("../crypto"); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const typef = require('typeforce'); | ||
const OPS = bscript.OPS; | ||
const bs58check = require('bs58check'); | ||
function stacksEqual(a, b) { | ||
if (a.length !== b.length) | ||
return false; | ||
return a.every((x, i) => { | ||
return x.equals(b[i]); | ||
}); | ||
} | ||
// input: [redeemScriptSig ...] {redeemScript} | ||
// witness: <?> | ||
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL | ||
function p2sh (a, opts) { | ||
if ( | ||
!a.address && | ||
!a.hash && | ||
!a.output && | ||
!a.redeem && | ||
!a.input | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(20)), | ||
output: typef.maybe(typef.BufferN(23)), | ||
redeem: typef.maybe({ | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
input: typef.maybe(typef.Buffer), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)) | ||
}), | ||
input: typef.maybe(typef.Buffer), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)) | ||
}, a) | ||
let network = a.network | ||
if (!network) { | ||
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK | ||
} | ||
const o = { network } | ||
const _address = lazy.value(function () { | ||
const payload = bs58check.decode(a.address) | ||
const version = payload.readUInt8(0) | ||
const hash = payload.slice(1) | ||
return { version, hash } | ||
}) | ||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) }) | ||
const _redeem = lazy.value(function () { | ||
const chunks = _chunks() | ||
return { | ||
network, | ||
output: chunks[chunks.length - 1], | ||
input: bscript.compile(chunks.slice(0, -1)), | ||
witness: a.witness || [] | ||
function p2sh(a, opts) { | ||
if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(20)), | ||
output: typef.maybe(typef.BufferN(23)), | ||
redeem: typef.maybe({ | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
input: typef.maybe(typef.Buffer), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)), | ||
}), | ||
input: typef.maybe(typef.Buffer), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)), | ||
}, a); | ||
let network = a.network; | ||
if (!network) { | ||
network = (a.redeem && a.redeem.network) || networks_1.bitcoin; | ||
} | ||
}) | ||
// output dependents | ||
lazy.prop(o, 'address', function () { | ||
if (!o.hash) return | ||
const payload = Buffer.allocUnsafe(21) | ||
payload.writeUInt8(network.scriptHash, 0) | ||
o.hash.copy(payload, 1) | ||
return bs58check.encode(payload) | ||
}) | ||
lazy.prop(o, 'hash', function () { | ||
// in order of least effort | ||
if (a.output) return a.output.slice(2, 22) | ||
if (a.address) return _address().hash | ||
if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output) | ||
}) | ||
lazy.prop(o, 'output', function () { | ||
if (!o.hash) return | ||
return bscript.compile([ | ||
OPS.OP_HASH160, | ||
o.hash, | ||
OPS.OP_EQUAL | ||
]) | ||
}) | ||
// input dependents | ||
lazy.prop(o, 'redeem', function () { | ||
if (!a.input) return | ||
return _redeem() | ||
}) | ||
lazy.prop(o, 'input', function () { | ||
if (!a.redeem || !a.redeem.input || !a.redeem.output) return | ||
return bscript.compile([].concat( | ||
bscript.decompile(a.redeem.input), | ||
a.redeem.output | ||
)) | ||
}) | ||
lazy.prop(o, 'witness', function () { | ||
if (o.redeem && o.redeem.witness) return o.redeem.witness | ||
if (o.input) return [] | ||
}) | ||
if (opts.validate) { | ||
let hash | ||
if (a.address) { | ||
if (_address().version !== network.scriptHash) throw new TypeError('Invalid version or Network mismatch') | ||
if (_address().hash.length !== 20) throw new TypeError('Invalid address') | ||
hash = _address().hash | ||
} | ||
if (a.hash) { | ||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') | ||
else hash = a.hash | ||
} | ||
if (a.output) { | ||
if ( | ||
a.output.length !== 23 || | ||
a.output[0] !== OPS.OP_HASH160 || | ||
a.output[1] !== 0x14 || | ||
a.output[22] !== OPS.OP_EQUAL) throw new TypeError('Output is invalid') | ||
const hash2 = a.output.slice(2, 22) | ||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') | ||
else hash = hash2 | ||
} | ||
// inlined to prevent 'no-inner-declarations' failing | ||
const checkRedeem = function (redeem) { | ||
// is the redeem output empty/invalid? | ||
if (redeem.output) { | ||
const decompile = bscript.decompile(redeem.output) | ||
if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short') | ||
// match hash against other sources | ||
const hash2 = bcrypto.hash160(redeem.output) | ||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') | ||
else hash = hash2 | ||
} | ||
if (redeem.input) { | ||
const hasInput = redeem.input.length > 0 | ||
const hasWitness = redeem.witness && redeem.witness.length > 0 | ||
if (!hasInput && !hasWitness) throw new TypeError('Empty input') | ||
if (hasInput && hasWitness) throw new TypeError('Input and witness provided') | ||
if (hasInput) { | ||
const richunks = bscript.decompile(redeem.input) | ||
if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig') | ||
const o = { network }; | ||
const _address = lazy.value(() => { | ||
const payload = bs58check.decode(a.address); | ||
const version = payload.readUInt8(0); | ||
const hash = payload.slice(1); | ||
return { version, hash }; | ||
}); | ||
const _chunks = lazy.value(() => { | ||
return bscript.decompile(a.input); | ||
}); | ||
const _redeem = lazy.value(() => { | ||
const chunks = _chunks(); | ||
return { | ||
network, | ||
output: chunks[chunks.length - 1], | ||
input: bscript.compile(chunks.slice(0, -1)), | ||
witness: a.witness || [], | ||
}; | ||
}); | ||
// output dependents | ||
lazy.prop(o, 'address', () => { | ||
if (!o.hash) | ||
return; | ||
const payload = Buffer.allocUnsafe(21); | ||
payload.writeUInt8(o.network.scriptHash, 0); | ||
o.hash.copy(payload, 1); | ||
return bs58check.encode(payload); | ||
}); | ||
lazy.prop(o, 'hash', () => { | ||
// in order of least effort | ||
if (a.output) | ||
return a.output.slice(2, 22); | ||
if (a.address) | ||
return _address().hash; | ||
if (o.redeem && o.redeem.output) | ||
return bcrypto.hash160(o.redeem.output); | ||
}); | ||
lazy.prop(o, 'output', () => { | ||
if (!o.hash) | ||
return; | ||
return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); | ||
}); | ||
// input dependents | ||
lazy.prop(o, 'redeem', () => { | ||
if (!a.input) | ||
return; | ||
return _redeem(); | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
if (!a.redeem || !a.redeem.input || !a.redeem.output) | ||
return; | ||
return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output)); | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
if (o.redeem && o.redeem.witness) | ||
return o.redeem.witness; | ||
if (o.input) | ||
return []; | ||
}); | ||
if (opts.validate) { | ||
let hash = Buffer.from([]); | ||
if (a.address) { | ||
if (_address().version !== network.scriptHash) | ||
throw new TypeError('Invalid version or Network mismatch'); | ||
if (_address().hash.length !== 20) | ||
throw new TypeError('Invalid address'); | ||
hash = _address().hash; | ||
} | ||
} | ||
if (a.hash) { | ||
if (hash.length > 0 && !hash.equals(a.hash)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = a.hash; | ||
} | ||
if (a.output) { | ||
if (a.output.length !== 23 || | ||
a.output[0] !== OPS.OP_HASH160 || | ||
a.output[1] !== 0x14 || | ||
a.output[22] !== OPS.OP_EQUAL) | ||
throw new TypeError('Output is invalid'); | ||
const hash2 = a.output.slice(2, 22); | ||
if (hash.length > 0 && !hash.equals(hash2)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = hash2; | ||
} | ||
// inlined to prevent 'no-inner-declarations' failing | ||
const checkRedeem = (redeem) => { | ||
// is the redeem output empty/invalid? | ||
if (redeem.output) { | ||
const decompile = bscript.decompile(redeem.output); | ||
if (!decompile || decompile.length < 1) | ||
throw new TypeError('Redeem.output too short'); | ||
// match hash against other sources | ||
const hash2 = bcrypto.hash160(redeem.output); | ||
if (hash.length > 0 && !hash.equals(hash2)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = hash2; | ||
} | ||
if (redeem.input) { | ||
const hasInput = redeem.input.length > 0; | ||
const hasWitness = redeem.witness && redeem.witness.length > 0; | ||
if (!hasInput && !hasWitness) | ||
throw new TypeError('Empty input'); | ||
if (hasInput && hasWitness) | ||
throw new TypeError('Input and witness provided'); | ||
if (hasInput) { | ||
const richunks = bscript.decompile(redeem.input); | ||
if (!bscript.isPushOnly(richunks)) | ||
throw new TypeError('Non push-only scriptSig'); | ||
} | ||
} | ||
}; | ||
if (a.input) { | ||
const chunks = _chunks(); | ||
if (!chunks || chunks.length < 1) | ||
throw new TypeError('Input too short'); | ||
if (!Buffer.isBuffer(_redeem().output)) | ||
throw new TypeError('Input is invalid'); | ||
checkRedeem(_redeem()); | ||
} | ||
if (a.redeem) { | ||
if (a.redeem.network && a.redeem.network !== network) | ||
throw new TypeError('Network mismatch'); | ||
if (a.input) { | ||
const redeem = _redeem(); | ||
if (a.redeem.output && !a.redeem.output.equals(redeem.output)) | ||
throw new TypeError('Redeem.output mismatch'); | ||
if (a.redeem.input && !a.redeem.input.equals(redeem.input)) | ||
throw new TypeError('Redeem.input mismatch'); | ||
} | ||
checkRedeem(a.redeem); | ||
} | ||
if (a.witness) { | ||
if (a.redeem && | ||
a.redeem.witness && | ||
!stacksEqual(a.redeem.witness, a.witness)) | ||
throw new TypeError('Witness and redeem.witness mismatch'); | ||
} | ||
} | ||
if (a.input) { | ||
const chunks = _chunks() | ||
if (!chunks || chunks.length < 1) throw new TypeError('Input too short') | ||
if (!Buffer.isBuffer(_redeem().output)) throw new TypeError('Input is invalid') | ||
checkRedeem(_redeem()) | ||
} | ||
if (a.redeem) { | ||
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') | ||
if (a.input) { | ||
const redeem = _redeem() | ||
if (a.redeem.output && !a.redeem.output.equals(redeem.output)) throw new TypeError('Redeem.output mismatch') | ||
if (a.redeem.input && !a.redeem.input.equals(redeem.input)) throw new TypeError('Redeem.input mismatch') | ||
} | ||
checkRedeem(a.redeem) | ||
} | ||
if (a.witness) { | ||
if ( | ||
a.redeem && | ||
a.redeem.witness && | ||
!stacksEqual(a.redeem.witness, a.witness)) throw new TypeError('Witness and redeem.witness mismatch') | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2sh | ||
exports.p2sh = p2sh; |
@@ -1,135 +0,138 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const ecc = require('tiny-secp256k1') | ||
const bcrypto = require('../crypto') | ||
const bech32 = require('bech32') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
const EMPTY_BUFFER = Buffer.alloc(0) | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bcrypto = require("../crypto"); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const typef = require('typeforce'); | ||
const OPS = bscript.OPS; | ||
const ecc = require('tiny-secp256k1'); | ||
const bech32 = require('bech32'); | ||
const EMPTY_BUFFER = Buffer.alloc(0); | ||
// witness: {signature} {pubKey} | ||
// input: <> | ||
// output: OP_0 {pubKeyHash} | ||
function p2wpkh (a, opts) { | ||
if ( | ||
!a.address && | ||
!a.hash && | ||
!a.output && | ||
!a.pubkey && | ||
!a.witness | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
typef({ | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(20)), | ||
input: typef.maybe(typef.BufferN(0)), | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.BufferN(22)), | ||
pubkey: typef.maybe(ecc.isPoint), | ||
signature: typef.maybe(bscript.isCanonicalScriptSignature), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)) | ||
}, a) | ||
const _address = lazy.value(function () { | ||
const result = bech32.decode(a.address) | ||
const version = result.words.shift() | ||
const data = bech32.fromWords(result.words) | ||
return { | ||
version, | ||
prefix: result.prefix, | ||
data: Buffer.from(data) | ||
function p2wpkh(a, opts) { | ||
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
typef({ | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(20)), | ||
input: typef.maybe(typef.BufferN(0)), | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.BufferN(22)), | ||
pubkey: typef.maybe(ecc.isPoint), | ||
signature: typef.maybe(bscript.isCanonicalScriptSignature), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)), | ||
}, a); | ||
const _address = lazy.value(() => { | ||
const result = bech32.decode(a.address); | ||
const version = result.words.shift(); | ||
const data = bech32.fromWords(result.words); | ||
return { | ||
version, | ||
prefix: result.prefix, | ||
data: Buffer.from(data), | ||
}; | ||
}); | ||
const network = a.network || networks_1.bitcoin; | ||
const o = { network }; | ||
lazy.prop(o, 'address', () => { | ||
if (!o.hash) | ||
return; | ||
const words = bech32.toWords(o.hash); | ||
words.unshift(0x00); | ||
return bech32.encode(network.bech32, words); | ||
}); | ||
lazy.prop(o, 'hash', () => { | ||
if (a.output) | ||
return a.output.slice(2, 22); | ||
if (a.address) | ||
return _address().data; | ||
if (a.pubkey || o.pubkey) | ||
return bcrypto.hash160(a.pubkey || o.pubkey); | ||
}); | ||
lazy.prop(o, 'output', () => { | ||
if (!o.hash) | ||
return; | ||
return bscript.compile([OPS.OP_0, o.hash]); | ||
}); | ||
lazy.prop(o, 'pubkey', () => { | ||
if (a.pubkey) | ||
return a.pubkey; | ||
if (!a.witness) | ||
return; | ||
return a.witness[1]; | ||
}); | ||
lazy.prop(o, 'signature', () => { | ||
if (!a.witness) | ||
return; | ||
return a.witness[0]; | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
if (!o.witness) | ||
return; | ||
return EMPTY_BUFFER; | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
if (!a.pubkey) | ||
return; | ||
if (!a.signature) | ||
return; | ||
return [a.signature, a.pubkey]; | ||
}); | ||
// extended validation | ||
if (opts.validate) { | ||
let hash = Buffer.from([]); | ||
if (a.address) { | ||
if (network && network.bech32 !== _address().prefix) | ||
throw new TypeError('Invalid prefix or Network mismatch'); | ||
if (_address().version !== 0x00) | ||
throw new TypeError('Invalid address version'); | ||
if (_address().data.length !== 20) | ||
throw new TypeError('Invalid address data'); | ||
hash = _address().data; | ||
} | ||
if (a.hash) { | ||
if (hash.length > 0 && !hash.equals(a.hash)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = a.hash; | ||
} | ||
if (a.output) { | ||
if (a.output.length !== 22 || | ||
a.output[0] !== OPS.OP_0 || | ||
a.output[1] !== 0x14) | ||
throw new TypeError('Output is invalid'); | ||
if (hash.length > 0 && !hash.equals(a.output.slice(2))) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = a.output.slice(2); | ||
} | ||
if (a.pubkey) { | ||
const pkh = bcrypto.hash160(a.pubkey); | ||
if (hash.length > 0 && !hash.equals(pkh)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = pkh; | ||
} | ||
if (a.witness) { | ||
if (a.witness.length !== 2) | ||
throw new TypeError('Witness is invalid'); | ||
if (!bscript.isCanonicalScriptSignature(a.witness[0])) | ||
throw new TypeError('Witness has invalid signature'); | ||
if (!ecc.isPoint(a.witness[1])) | ||
throw new TypeError('Witness has invalid pubkey'); | ||
if (a.signature && !a.signature.equals(a.witness[0])) | ||
throw new TypeError('Signature mismatch'); | ||
if (a.pubkey && !a.pubkey.equals(a.witness[1])) | ||
throw new TypeError('Pubkey mismatch'); | ||
const pkh = bcrypto.hash160(a.witness[1]); | ||
if (hash.length > 0 && !hash.equals(pkh)) | ||
throw new TypeError('Hash mismatch'); | ||
} | ||
} | ||
}) | ||
const network = a.network || BITCOIN_NETWORK | ||
const o = { network } | ||
lazy.prop(o, 'address', function () { | ||
if (!o.hash) return | ||
const words = bech32.toWords(o.hash) | ||
words.unshift(0x00) | ||
return bech32.encode(network.bech32, words) | ||
}) | ||
lazy.prop(o, 'hash', function () { | ||
if (a.output) return a.output.slice(2, 22) | ||
if (a.address) return _address().data | ||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey) | ||
}) | ||
lazy.prop(o, 'output', function () { | ||
if (!o.hash) return | ||
return bscript.compile([ | ||
OPS.OP_0, | ||
o.hash | ||
]) | ||
}) | ||
lazy.prop(o, 'pubkey', function () { | ||
if (a.pubkey) return a.pubkey | ||
if (!a.witness) return | ||
return a.witness[1] | ||
}) | ||
lazy.prop(o, 'signature', function () { | ||
if (!a.witness) return | ||
return a.witness[0] | ||
}) | ||
lazy.prop(o, 'input', function () { | ||
if (!o.witness) return | ||
return EMPTY_BUFFER | ||
}) | ||
lazy.prop(o, 'witness', function () { | ||
if (!a.pubkey) return | ||
if (!a.signature) return | ||
return [a.signature, a.pubkey] | ||
}) | ||
// extended validation | ||
if (opts.validate) { | ||
let hash | ||
if (a.address) { | ||
if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch') | ||
if (_address().version !== 0x00) throw new TypeError('Invalid address version') | ||
if (_address().data.length !== 20) throw new TypeError('Invalid address data') | ||
hash = _address().data | ||
} | ||
if (a.hash) { | ||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') | ||
else hash = a.hash | ||
} | ||
if (a.output) { | ||
if ( | ||
a.output.length !== 22 || | ||
a.output[0] !== OPS.OP_0 || | ||
a.output[1] !== 0x14) throw new TypeError('Output is invalid') | ||
if (hash && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch') | ||
else hash = a.output.slice(2) | ||
} | ||
if (a.pubkey) { | ||
const pkh = bcrypto.hash160(a.pubkey) | ||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') | ||
else hash = pkh | ||
} | ||
if (a.witness) { | ||
if (a.witness.length !== 2) throw new TypeError('Witness is invalid') | ||
if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature') | ||
if (!ecc.isPoint(a.witness[1])) throw new TypeError('Witness has invalid pubkey') | ||
if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch') | ||
if (a.pubkey && !a.pubkey.equals(a.witness[1])) throw new TypeError('Pubkey mismatch') | ||
const pkh = bcrypto.hash160(a.witness[1]) | ||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2wpkh | ||
exports.p2wpkh = p2wpkh; |
@@ -1,180 +0,177 @@ | ||
const lazy = require('./lazy') | ||
const typef = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const bech32 = require('bech32') | ||
const bcrypto = require('../crypto') | ||
const bscript = require('../script') | ||
const BITCOIN_NETWORK = require('../networks').bitcoin | ||
const EMPTY_BUFFER = Buffer.alloc(0) | ||
function stacksEqual (a, b) { | ||
if (a.length !== b.length) return false | ||
return a.every(function (x, i) { | ||
return x.equals(b[i]) | ||
}) | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bcrypto = require("../crypto"); | ||
const networks_1 = require("../networks"); | ||
const bscript = require("../script"); | ||
const lazy = require("./lazy"); | ||
const typef = require('typeforce'); | ||
const OPS = bscript.OPS; | ||
const bech32 = require('bech32'); | ||
const EMPTY_BUFFER = Buffer.alloc(0); | ||
function stacksEqual(a, b) { | ||
if (a.length !== b.length) | ||
return false; | ||
return a.every((x, i) => { | ||
return x.equals(b[i]); | ||
}); | ||
} | ||
// input: <> | ||
// witness: [redeemScriptSig ...] {redeemScript} | ||
// output: OP_0 {sha256(redeemScript)} | ||
function p2wsh (a, opts) { | ||
if ( | ||
!a.address && | ||
!a.hash && | ||
!a.output && | ||
!a.redeem && | ||
!a.witness | ||
) throw new TypeError('Not enough data') | ||
opts = Object.assign({ validate: true }, opts || {}) | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(32)), | ||
output: typef.maybe(typef.BufferN(34)), | ||
redeem: typef.maybe({ | ||
input: typef.maybe(typef.Buffer), | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)) | ||
}), | ||
input: typef.maybe(typef.BufferN(0)), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)) | ||
}, a) | ||
const _address = lazy.value(function () { | ||
const result = bech32.decode(a.address) | ||
const version = result.words.shift() | ||
const data = bech32.fromWords(result.words) | ||
return { | ||
version, | ||
prefix: result.prefix, | ||
data: Buffer.from(data) | ||
function p2wsh(a, opts) { | ||
if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) | ||
throw new TypeError('Not enough data'); | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
typef({ | ||
network: typef.maybe(typef.Object), | ||
address: typef.maybe(typef.String), | ||
hash: typef.maybe(typef.BufferN(32)), | ||
output: typef.maybe(typef.BufferN(34)), | ||
redeem: typef.maybe({ | ||
input: typef.maybe(typef.Buffer), | ||
network: typef.maybe(typef.Object), | ||
output: typef.maybe(typef.Buffer), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)), | ||
}), | ||
input: typef.maybe(typef.BufferN(0)), | ||
witness: typef.maybe(typef.arrayOf(typef.Buffer)), | ||
}, a); | ||
const _address = lazy.value(() => { | ||
const result = bech32.decode(a.address); | ||
const version = result.words.shift(); | ||
const data = bech32.fromWords(result.words); | ||
return { | ||
version, | ||
prefix: result.prefix, | ||
data: Buffer.from(data), | ||
}; | ||
}); | ||
const _rchunks = lazy.value(() => { | ||
return bscript.decompile(a.redeem.input); | ||
}); | ||
let network = a.network; | ||
if (!network) { | ||
network = (a.redeem && a.redeem.network) || networks_1.bitcoin; | ||
} | ||
}) | ||
const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input) }) | ||
let network = a.network | ||
if (!network) { | ||
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK | ||
} | ||
const o = { network } | ||
lazy.prop(o, 'address', function () { | ||
if (!o.hash) return | ||
const words = bech32.toWords(o.hash) | ||
words.unshift(0x00) | ||
return bech32.encode(network.bech32, words) | ||
}) | ||
lazy.prop(o, 'hash', function () { | ||
if (a.output) return a.output.slice(2) | ||
if (a.address) return _address().data | ||
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output) | ||
}) | ||
lazy.prop(o, 'output', function () { | ||
if (!o.hash) return | ||
return bscript.compile([ | ||
OPS.OP_0, | ||
o.hash | ||
]) | ||
}) | ||
lazy.prop(o, 'redeem', function () { | ||
if (!a.witness) return | ||
return { | ||
output: a.witness[a.witness.length - 1], | ||
input: EMPTY_BUFFER, | ||
witness: a.witness.slice(0, -1) | ||
const o = { network }; | ||
lazy.prop(o, 'address', () => { | ||
if (!o.hash) | ||
return; | ||
const words = bech32.toWords(o.hash); | ||
words.unshift(0x00); | ||
return bech32.encode(network.bech32, words); | ||
}); | ||
lazy.prop(o, 'hash', () => { | ||
if (a.output) | ||
return a.output.slice(2); | ||
if (a.address) | ||
return _address().data; | ||
if (o.redeem && o.redeem.output) | ||
return bcrypto.sha256(o.redeem.output); | ||
}); | ||
lazy.prop(o, 'output', () => { | ||
if (!o.hash) | ||
return; | ||
return bscript.compile([OPS.OP_0, o.hash]); | ||
}); | ||
lazy.prop(o, 'redeem', () => { | ||
if (!a.witness) | ||
return; | ||
return { | ||
output: a.witness[a.witness.length - 1], | ||
input: EMPTY_BUFFER, | ||
witness: a.witness.slice(0, -1), | ||
}; | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
if (!o.witness) | ||
return; | ||
return EMPTY_BUFFER; | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
// transform redeem input to witness stack? | ||
if (a.redeem && | ||
a.redeem.input && | ||
a.redeem.input.length > 0 && | ||
a.redeem.output && | ||
a.redeem.output.length > 0) { | ||
const stack = bscript.toStack(_rchunks()); | ||
// assign, and blank the existing input | ||
o.redeem = Object.assign({ witness: stack }, a.redeem); | ||
o.redeem.input = EMPTY_BUFFER; | ||
return [].concat(stack, a.redeem.output); | ||
} | ||
if (!a.redeem) | ||
return; | ||
if (!a.redeem.output) | ||
return; | ||
if (!a.redeem.witness) | ||
return; | ||
return [].concat(a.redeem.witness, a.redeem.output); | ||
}); | ||
// extended validation | ||
if (opts.validate) { | ||
let hash = Buffer.from([]); | ||
if (a.address) { | ||
if (_address().prefix !== network.bech32) | ||
throw new TypeError('Invalid prefix or Network mismatch'); | ||
if (_address().version !== 0x00) | ||
throw new TypeError('Invalid address version'); | ||
if (_address().data.length !== 32) | ||
throw new TypeError('Invalid address data'); | ||
hash = _address().data; | ||
} | ||
if (a.hash) { | ||
if (hash.length > 0 && !hash.equals(a.hash)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = a.hash; | ||
} | ||
if (a.output) { | ||
if (a.output.length !== 34 || | ||
a.output[0] !== OPS.OP_0 || | ||
a.output[1] !== 0x20) | ||
throw new TypeError('Output is invalid'); | ||
const hash2 = a.output.slice(2); | ||
if (hash.length > 0 && !hash.equals(hash2)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = hash2; | ||
} | ||
if (a.redeem) { | ||
if (a.redeem.network && a.redeem.network !== network) | ||
throw new TypeError('Network mismatch'); | ||
// is there two redeem sources? | ||
if (a.redeem.input && | ||
a.redeem.input.length > 0 && | ||
a.redeem.witness && | ||
a.redeem.witness.length > 0) | ||
throw new TypeError('Ambiguous witness source'); | ||
// is the redeem output non-empty? | ||
if (a.redeem.output) { | ||
if (bscript.decompile(a.redeem.output).length === 0) | ||
throw new TypeError('Redeem.output is invalid'); | ||
// match hash against other sources | ||
const hash2 = bcrypto.sha256(a.redeem.output); | ||
if (hash.length > 0 && !hash.equals(hash2)) | ||
throw new TypeError('Hash mismatch'); | ||
else | ||
hash = hash2; | ||
} | ||
if (a.redeem.input && !bscript.isPushOnly(_rchunks())) | ||
throw new TypeError('Non push-only scriptSig'); | ||
if (a.witness && | ||
a.redeem.witness && | ||
!stacksEqual(a.witness, a.redeem.witness)) | ||
throw new TypeError('Witness and redeem.witness mismatch'); | ||
} | ||
if (a.witness) { | ||
if (a.redeem && | ||
a.redeem.output && | ||
!a.redeem.output.equals(a.witness[a.witness.length - 1])) | ||
throw new TypeError('Witness and redeem.output mismatch'); | ||
} | ||
} | ||
}) | ||
lazy.prop(o, 'input', function () { | ||
if (!o.witness) return | ||
return EMPTY_BUFFER | ||
}) | ||
lazy.prop(o, 'witness', function () { | ||
// transform redeem input to witness stack? | ||
if ( | ||
a.redeem && | ||
a.redeem.input && | ||
a.redeem.input.length > 0 && | ||
a.redeem.output && | ||
a.redeem.output.length > 0 | ||
) { | ||
const stack = bscript.toStack(_rchunks()) | ||
// assign, and blank the existing input | ||
o.redeem = Object.assign({ witness: stack }, a.redeem) | ||
o.redeem.input = EMPTY_BUFFER | ||
return [].concat(stack, a.redeem.output) | ||
} | ||
if (!a.redeem) return | ||
if (!a.redeem.output) return | ||
if (!a.redeem.witness) return | ||
return [].concat(a.redeem.witness, a.redeem.output) | ||
}) | ||
// extended validation | ||
if (opts.validate) { | ||
let hash | ||
if (a.address) { | ||
if (_address().prefix !== network.bech32) throw new TypeError('Invalid prefix or Network mismatch') | ||
if (_address().version !== 0x00) throw new TypeError('Invalid address version') | ||
if (_address().data.length !== 32) throw new TypeError('Invalid address data') | ||
hash = _address().data | ||
} | ||
if (a.hash) { | ||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') | ||
else hash = a.hash | ||
} | ||
if (a.output) { | ||
if ( | ||
a.output.length !== 34 || | ||
a.output[0] !== OPS.OP_0 || | ||
a.output[1] !== 0x20) throw new TypeError('Output is invalid') | ||
const hash2 = a.output.slice(2) | ||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') | ||
else hash = hash2 | ||
} | ||
if (a.redeem) { | ||
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') | ||
// is there two redeem sources? | ||
if ( | ||
a.redeem.input && | ||
a.redeem.input.length > 0 && | ||
a.redeem.witness && | ||
a.redeem.witness.length > 0 | ||
) throw new TypeError('Ambiguous witness source') | ||
// is the redeem output non-empty? | ||
if (a.redeem.output) { | ||
if (bscript.decompile(a.redeem.output).length === 0) throw new TypeError('Redeem.output is invalid') | ||
// match hash against other sources | ||
const hash2 = bcrypto.sha256(a.redeem.output) | ||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') | ||
else hash = hash2 | ||
} | ||
if (a.redeem.input && !bscript.isPushOnly(_rchunks())) throw new TypeError('Non push-only scriptSig') | ||
if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch') | ||
} | ||
if (a.witness) { | ||
if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch') | ||
} | ||
} | ||
return Object.assign(o, a) | ||
return Object.assign(o, a); | ||
} | ||
module.exports = p2wsh | ||
exports.p2wsh = p2wsh; |
@@ -1,67 +0,65 @@ | ||
const Buffer = require('safe-buffer').Buffer | ||
function decode (buffer, maxLength, minimal) { | ||
maxLength = maxLength || 4 | ||
minimal = minimal === undefined ? true : minimal | ||
const length = buffer.length | ||
if (length === 0) return 0 | ||
if (length > maxLength) throw new TypeError('Script number overflow') | ||
if (minimal) { | ||
if ((buffer[length - 1] & 0x7f) === 0) { | ||
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) throw new Error('Non-minimally encoded script number') | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function decode(buffer, maxLength, minimal) { | ||
maxLength = maxLength || 4; | ||
minimal = minimal === undefined ? true : minimal; | ||
const length = buffer.length; | ||
if (length === 0) | ||
return 0; | ||
if (length > maxLength) | ||
throw new TypeError('Script number overflow'); | ||
if (minimal) { | ||
if ((buffer[length - 1] & 0x7f) === 0) { | ||
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) | ||
throw new Error('Non-minimally encoded script number'); | ||
} | ||
} | ||
} | ||
// 40-bit | ||
if (length === 5) { | ||
const a = buffer.readUInt32LE(0) | ||
const b = buffer.readUInt8(4) | ||
if (b & 0x80) return -(((b & ~0x80) * 0x100000000) + a) | ||
return (b * 0x100000000) + a | ||
} | ||
// 32-bit / 24-bit / 16-bit / 8-bit | ||
let result = 0 | ||
for (var i = 0; i < length; ++i) { | ||
result |= buffer[i] << (8 * i) | ||
} | ||
if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) | ||
return result | ||
// 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) { | ||
return i > 0x7fffffff ? 5 | ||
: i > 0x7fffff ? 4 | ||
: i > 0x7fff ? 3 | ||
: i > 0x7f ? 2 | ||
: i > 0x00 ? 1 | ||
: 0 | ||
exports.decode = decode; | ||
function scriptNumSize(i) { | ||
return i > 0x7fffffff | ||
? 5 | ||
: i > 0x7fffff | ||
? 4 | ||
: i > 0x7fff | ||
? 3 | ||
: i > 0x7f | ||
? 2 | ||
: i > 0x00 | ||
? 1 | ||
: 0; | ||
} | ||
function encode (number) { | ||
let value = Math.abs(number) | ||
const size = scriptNumSize(value) | ||
const buffer = Buffer.allocUnsafe(size) | ||
const negative = number < 0 | ||
for (var i = 0; i < size; ++i) { | ||
buffer.writeUInt8(value & 0xff, i) | ||
value >>= 8 | ||
} | ||
if (buffer[size - 1] & 0x80) { | ||
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1) | ||
} else if (negative) { | ||
buffer[size - 1] |= 0x80 | ||
} | ||
return buffer | ||
function encode(_number) { | ||
let value = Math.abs(_number); | ||
const size = scriptNumSize(value); | ||
const buffer = Buffer.allocUnsafe(size); | ||
const negative = _number < 0; | ||
for (let i = 0; i < size; ++i) { | ||
buffer.writeUInt8(value & 0xff, i); | ||
value >>= 8; | ||
} | ||
if (buffer[size - 1] & 0x80) { | ||
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); | ||
} | ||
else if (negative) { | ||
buffer[size - 1] |= 0x80; | ||
} | ||
return buffer; | ||
} | ||
module.exports = { | ||
decode: decode, | ||
encode: encode | ||
} | ||
exports.encode = encode; |
@@ -1,64 +0,53 @@ | ||
const bip66 = require('bip66') | ||
const Buffer = require('safe-buffer').Buffer | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const ZERO = Buffer.alloc(1, 0) | ||
function toDER (x) { | ||
let i = 0 | ||
while (x[i] === 0) ++i | ||
if (i === x.length) return ZERO | ||
x = x.slice(i) | ||
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length) | ||
return x | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const types = require("./types"); | ||
const bip66 = require('bip66'); | ||
const typeforce = require('typeforce'); | ||
const ZERO = Buffer.alloc(1, 0); | ||
function toDER(x) { | ||
let i = 0; | ||
while (x[i] === 0) | ||
++i; | ||
if (i === x.length) | ||
return ZERO; | ||
x = x.slice(i); | ||
if (x[0] & 0x80) | ||
return Buffer.concat([ZERO, x], 1 + x.length); | ||
return x; | ||
} | ||
function fromDER (x) { | ||
if (x[0] === 0x00) x = x.slice(1) | ||
const buffer = Buffer.alloc(32, 0) | ||
const bstart = Math.max(0, 32 - x.length) | ||
x.copy(buffer, bstart) | ||
return buffer | ||
function fromDER(x) { | ||
if (x[0] === 0x00) | ||
x = x.slice(1); | ||
const buffer = Buffer.alloc(32, 0); | ||
const bstart = Math.max(0, 32 - x.length); | ||
x.copy(buffer, bstart); | ||
return buffer; | ||
} | ||
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) | ||
function decode (buffer) { | ||
const hashType = buffer.readUInt8(buffer.length - 1) | ||
const hashTypeMod = hashType & ~0x80 | ||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) | ||
const decode = bip66.decode(buffer.slice(0, -1)) | ||
const r = fromDER(decode.r) | ||
const s = fromDER(decode.s) | ||
return { | ||
signature: Buffer.concat([r, s], 64), | ||
hashType: hashType | ||
} | ||
function decode(buffer) { | ||
const hashType = buffer.readUInt8(buffer.length - 1); | ||
const hashTypeMod = hashType & ~0x80; | ||
if (hashTypeMod <= 0 || hashTypeMod >= 4) | ||
throw new Error('Invalid hashType ' + hashType); | ||
const decoded = bip66.decode(buffer.slice(0, -1)); | ||
const r = fromDER(decoded.r); | ||
const s = fromDER(decoded.s); | ||
const signature = Buffer.concat([r, s], 64); | ||
return { signature, hashType }; | ||
} | ||
function encode (signature, hashType) { | ||
typeforce({ | ||
signature: types.BufferN(64), | ||
hashType: types.UInt8 | ||
}, { signature, hashType }) | ||
const hashTypeMod = hashType & ~0x80 | ||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) | ||
const hashTypeBuffer = Buffer.allocUnsafe(1) | ||
hashTypeBuffer.writeUInt8(hashType, 0) | ||
const r = toDER(signature.slice(0, 32)) | ||
const s = toDER(signature.slice(32, 64)) | ||
return Buffer.concat([ | ||
bip66.encode(r, s), | ||
hashTypeBuffer | ||
]) | ||
exports.decode = decode; | ||
function encode(signature, hashType) { | ||
typeforce({ | ||
signature: types.BufferN(64), | ||
hashType: types.UInt8, | ||
}, { signature, hashType }); | ||
const hashTypeMod = hashType & ~0x80; | ||
if (hashTypeMod <= 0 || hashTypeMod >= 4) | ||
throw new Error('Invalid hashType ' + hashType); | ||
const hashTypeBuffer = Buffer.allocUnsafe(1); | ||
hashTypeBuffer.writeUInt8(hashType, 0); | ||
const r = toDER(signature.slice(0, 32)); | ||
const s = toDER(signature.slice(32, 64)); | ||
return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); | ||
} | ||
module.exports = { | ||
decode: decode, | ||
encode: encode | ||
} | ||
exports.encode = encode; |
@@ -1,205 +0,191 @@ | ||
const Buffer = require('safe-buffer').Buffer | ||
const bip66 = require('bip66') | ||
const ecc = require('tiny-secp256k1') | ||
const pushdata = require('pushdata-bitcoin') | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const scriptNumber = require('./script_number') | ||
const OPS = require('bitcoin-ops') | ||
const REVERSE_OPS = require('bitcoin-ops/map') | ||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 | ||
function isOPInt (value) { | ||
return types.Number(value) && | ||
((value === OPS.OP_0) || | ||
(value >= OPS.OP_1 && value <= OPS.OP_16) || | ||
(value === OPS.OP_1NEGATE)) | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const scriptNumber = require("./script_number"); | ||
const scriptSignature = require("./script_signature"); | ||
const types = require("./types"); | ||
const bip66 = require('bip66'); | ||
const ecc = require('tiny-secp256k1'); | ||
const pushdata = require('pushdata-bitcoin'); | ||
const typeforce = require('typeforce'); | ||
exports.OPS = require('bitcoin-ops'); | ||
const REVERSE_OPS = require('bitcoin-ops/map'); | ||
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1 | ||
function isOPInt(value) { | ||
return (types.Number(value) && | ||
(value === exports.OPS.OP_0 || | ||
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || | ||
value === exports.OPS.OP_1NEGATE)); | ||
} | ||
function isPushOnlyChunk (value) { | ||
return types.Buffer(value) || isOPInt(value) | ||
function isPushOnlyChunk(value) { | ||
return types.Buffer(value) || isOPInt(value); | ||
} | ||
function isPushOnly (value) { | ||
return types.Array(value) && value.every(isPushOnlyChunk) | ||
function isPushOnly(value) { | ||
return types.Array(value) && value.every(isPushOnlyChunk); | ||
} | ||
function asMinimalOP (buffer) { | ||
if (buffer.length === 0) return OPS.OP_0 | ||
if (buffer.length !== 1) return | ||
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0] | ||
if (buffer[0] === 0x81) return OPS.OP_1NEGATE | ||
exports.isPushOnly = isPushOnly; | ||
function asMinimalOP(buffer) { | ||
if (buffer.length === 0) | ||
return exports.OPS.OP_0; | ||
if (buffer.length !== 1) | ||
return; | ||
if (buffer[0] >= 1 && buffer[0] <= 16) | ||
return OP_INT_BASE + buffer[0]; | ||
if (buffer[0] === 0x81) | ||
return exports.OPS.OP_1NEGATE; | ||
} | ||
function compile (chunks) { | ||
// TODO: remove me | ||
if (Buffer.isBuffer(chunks)) return chunks | ||
typeforce(types.Array, chunks) | ||
const bufferSize = chunks.reduce(function (accum, chunk) { | ||
// data chunk | ||
if (Buffer.isBuffer(chunk)) { | ||
// adhere to BIP62.3, minimal push policy | ||
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { | ||
return accum + 1 | ||
} | ||
return accum + pushdata.encodingLength(chunk.length) + chunk.length | ||
} | ||
// opcode | ||
return accum + 1 | ||
}, 0.0) | ||
const buffer = Buffer.allocUnsafe(bufferSize) | ||
let offset = 0 | ||
chunks.forEach(function (chunk) { | ||
// data chunk | ||
if (Buffer.isBuffer(chunk)) { | ||
// adhere to BIP62.3, minimal push policy | ||
const opcode = asMinimalOP(chunk) | ||
if (opcode !== undefined) { | ||
buffer.writeUInt8(opcode, offset) | ||
offset += 1 | ||
return | ||
} | ||
offset += pushdata.encode(buffer, chunk.length, offset) | ||
chunk.copy(buffer, offset) | ||
offset += chunk.length | ||
// opcode | ||
} else { | ||
buffer.writeUInt8(chunk, offset) | ||
offset += 1 | ||
} | ||
}) | ||
if (offset !== buffer.length) throw new Error('Could not decode chunks') | ||
return buffer | ||
function chunksIsBuffer(buf) { | ||
return Buffer.isBuffer(buf); | ||
} | ||
function decompile (buffer) { | ||
// TODO: remove me | ||
if (types.Array(buffer)) return buffer | ||
typeforce(types.Buffer, buffer) | ||
const chunks = [] | ||
let i = 0 | ||
while (i < buffer.length) { | ||
const opcode = buffer[i] | ||
// data chunk | ||
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { | ||
const d = pushdata.decode(buffer, i) | ||
// did reading a pushDataInt fail? | ||
if (d === null) return null | ||
i += d.size | ||
// attempt to read too much data? | ||
if (i + d.number > buffer.length) return null | ||
const data = buffer.slice(i, i + d.number) | ||
i += d.number | ||
// decompile minimally | ||
const op = asMinimalOP(data) | ||
if (op !== undefined) { | ||
chunks.push(op) | ||
} else { | ||
chunks.push(data) | ||
} | ||
// opcode | ||
} else { | ||
chunks.push(opcode) | ||
i += 1 | ||
function chunksIsArray(buf) { | ||
return types.Array(buf); | ||
} | ||
function singleChunkIsBuffer(buf) { | ||
return Buffer.isBuffer(buf); | ||
} | ||
function compile(chunks) { | ||
// TODO: remove me | ||
if (chunksIsBuffer(chunks)) | ||
return chunks; | ||
typeforce(types.Array, chunks); | ||
const bufferSize = chunks.reduce((accum, chunk) => { | ||
// data chunk | ||
if (singleChunkIsBuffer(chunk)) { | ||
// adhere to BIP62.3, minimal push policy | ||
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { | ||
return accum + 1; | ||
} | ||
return accum + pushdata.encodingLength(chunk.length) + chunk.length; | ||
} | ||
// opcode | ||
return accum + 1; | ||
}, 0.0); | ||
const buffer = Buffer.allocUnsafe(bufferSize); | ||
let offset = 0; | ||
chunks.forEach(chunk => { | ||
// data chunk | ||
if (singleChunkIsBuffer(chunk)) { | ||
// adhere to BIP62.3, minimal push policy | ||
const opcode = asMinimalOP(chunk); | ||
if (opcode !== undefined) { | ||
buffer.writeUInt8(opcode, offset); | ||
offset += 1; | ||
return; | ||
} | ||
offset += pushdata.encode(buffer, chunk.length, offset); | ||
chunk.copy(buffer, offset); | ||
offset += chunk.length; | ||
// opcode | ||
} | ||
else { | ||
buffer.writeUInt8(chunk, offset); | ||
offset += 1; | ||
} | ||
}); | ||
if (offset !== buffer.length) | ||
throw new Error('Could not decode chunks'); | ||
return buffer; | ||
} | ||
exports.compile = compile; | ||
function decompile(buffer) { | ||
// TODO: remove me | ||
if (chunksIsArray(buffer)) | ||
return buffer; | ||
typeforce(types.Buffer, buffer); | ||
const chunks = []; | ||
let i = 0; | ||
while (i < buffer.length) { | ||
const opcode = buffer[i]; | ||
// data chunk | ||
if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { | ||
const d = pushdata.decode(buffer, i); | ||
// did reading a pushDataInt fail? | ||
if (d === null) | ||
return null; | ||
i += d.size; | ||
// attempt to read too much data? | ||
if (i + d.number > buffer.length) | ||
return null; | ||
const data = buffer.slice(i, i + d.number); | ||
i += d.number; | ||
// decompile minimally | ||
const op = asMinimalOP(data); | ||
if (op !== undefined) { | ||
chunks.push(op); | ||
} | ||
else { | ||
chunks.push(data); | ||
} | ||
// opcode | ||
} | ||
else { | ||
chunks.push(opcode); | ||
i += 1; | ||
} | ||
} | ||
} | ||
return chunks | ||
return chunks; | ||
} | ||
function toASM (chunks) { | ||
if (Buffer.isBuffer(chunks)) { | ||
chunks = decompile(chunks) | ||
} | ||
return chunks.map(function (chunk) { | ||
// data? | ||
if (Buffer.isBuffer(chunk)) { | ||
const op = asMinimalOP(chunk) | ||
if (op === undefined) return chunk.toString('hex') | ||
chunk = op | ||
exports.decompile = decompile; | ||
function toASM(chunks) { | ||
if (chunksIsBuffer(chunks)) { | ||
chunks = decompile(chunks); | ||
} | ||
// opcode! | ||
return REVERSE_OPS[chunk] | ||
}).join(' ') | ||
return chunks | ||
.map(chunk => { | ||
// data? | ||
if (singleChunkIsBuffer(chunk)) { | ||
const op = asMinimalOP(chunk); | ||
if (op === undefined) | ||
return chunk.toString('hex'); | ||
chunk = op; | ||
} | ||
// opcode! | ||
return REVERSE_OPS[chunk]; | ||
}) | ||
.join(' '); | ||
} | ||
function fromASM (asm) { | ||
typeforce(types.String, asm) | ||
return compile(asm.split(' ').map(function (chunkStr) { | ||
// opcode? | ||
if (OPS[chunkStr] !== undefined) return OPS[chunkStr] | ||
typeforce(types.Hex, chunkStr) | ||
// data! | ||
return Buffer.from(chunkStr, 'hex') | ||
})) | ||
exports.toASM = toASM; | ||
function fromASM(asm) { | ||
typeforce(types.String, asm); | ||
return compile(asm.split(' ').map(chunkStr => { | ||
// opcode? | ||
if (exports.OPS[chunkStr] !== undefined) | ||
return exports.OPS[chunkStr]; | ||
typeforce(types.Hex, chunkStr); | ||
// data! | ||
return Buffer.from(chunkStr, 'hex'); | ||
})); | ||
} | ||
function toStack (chunks) { | ||
chunks = decompile(chunks) | ||
typeforce(isPushOnly, chunks) | ||
return chunks.map(function (op) { | ||
if (Buffer.isBuffer(op)) return op | ||
if (op === OPS.OP_0) return Buffer.allocUnsafe(0) | ||
return scriptNumber.encode(op - OP_INT_BASE) | ||
}) | ||
exports.fromASM = fromASM; | ||
function toStack(chunks) { | ||
chunks = decompile(chunks); | ||
typeforce(isPushOnly, chunks); | ||
return chunks.map(op => { | ||
if (singleChunkIsBuffer(op)) | ||
return op; | ||
if (op === exports.OPS.OP_0) | ||
return Buffer.allocUnsafe(0); | ||
return scriptNumber.encode(op - OP_INT_BASE); | ||
}); | ||
} | ||
function isCanonicalPubKey (buffer) { | ||
return ecc.isPoint(buffer) | ||
exports.toStack = toStack; | ||
function isCanonicalPubKey(buffer) { | ||
return ecc.isPoint(buffer); | ||
} | ||
function isDefinedHashType (hashType) { | ||
const hashTypeMod = hashType & ~0x80 | ||
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE | ||
return hashTypeMod > 0x00 && hashTypeMod < 0x04 | ||
exports.isCanonicalPubKey = isCanonicalPubKey; | ||
function isDefinedHashType(hashType) { | ||
const hashTypeMod = hashType & ~0x80; | ||
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE | ||
return hashTypeMod > 0x00 && hashTypeMod < 0x04; | ||
} | ||
function isCanonicalScriptSignature (buffer) { | ||
if (!Buffer.isBuffer(buffer)) return false | ||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false | ||
return bip66.check(buffer.slice(0, -1)) | ||
exports.isDefinedHashType = isDefinedHashType; | ||
function isCanonicalScriptSignature(buffer) { | ||
if (!Buffer.isBuffer(buffer)) | ||
return false; | ||
if (!isDefinedHashType(buffer[buffer.length - 1])) | ||
return false; | ||
return bip66.check(buffer.slice(0, -1)); | ||
} | ||
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,4 +0,6 @@ | ||
module.exports = { | ||
input: require('./input'), | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const input = require("./input"); | ||
exports.input = input; | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,23 +1,23 @@ | ||
"use strict"; | ||
// OP_0 [signatures ...] | ||
const bscript = require('../../script') | ||
const OPS = require('bitcoin-ops') | ||
function partialSignature (value) { | ||
return value === OPS.OP_0 || bscript.isCanonicalScriptSignature(value) | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
function partialSignature(value) { | ||
return (value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)); | ||
} | ||
function check (script, allowIncomplete) { | ||
const chunks = bscript.decompile(script) | ||
if (chunks.length < 2) return false | ||
if (chunks[0] !== OPS.OP_0) return false | ||
if (allowIncomplete) { | ||
return chunks.slice(1).every(partialSignature) | ||
} | ||
return chunks.slice(1).every(bscript.isCanonicalScriptSignature) | ||
function check(script, allowIncomplete) { | ||
const chunks = bscript.decompile(script); | ||
if (chunks.length < 2) | ||
return false; | ||
if (chunks[0] !== script_1.OPS.OP_0) | ||
return false; | ||
if (allowIncomplete) { | ||
return chunks.slice(1).every(partialSignature); | ||
} | ||
return chunks.slice(1).every(bscript.isCanonicalScriptSignature); | ||
} | ||
check.toJSON = function () { return 'multisig input' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'multisig input'; | ||
}; |
@@ -0,29 +1,36 @@ | ||
"use strict"; | ||
// m [pubKeys ...] n OP_CHECKMULTISIG | ||
const bscript = require('../../script') | ||
const types = require('../../types') | ||
const OPS = require('bitcoin-ops') | ||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 | ||
function check (script, allowIncomplete) { | ||
const chunks = bscript.decompile(script) | ||
if (chunks.length < 4) return false | ||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false | ||
if (!types.Number(chunks[0])) return false | ||
if (!types.Number(chunks[chunks.length - 2])) return false | ||
const m = chunks[0] - OP_INT_BASE | ||
const n = chunks[chunks.length - 2] - OP_INT_BASE | ||
if (m <= 0) return false | ||
if (n > 16) return false | ||
if (m > n) return false | ||
if (n !== chunks.length - 3) return false | ||
if (allowIncomplete) return true | ||
const keys = chunks.slice(1, -2) | ||
return keys.every(bscript.isCanonicalPubKey) | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
const types = require("../../types"); | ||
const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 | ||
function check(script, allowIncomplete) { | ||
const chunks = bscript.decompile(script); | ||
if (chunks.length < 4) | ||
return false; | ||
if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) | ||
return false; | ||
if (!types.Number(chunks[0])) | ||
return false; | ||
if (!types.Number(chunks[chunks.length - 2])) | ||
return false; | ||
const m = chunks[0] - OP_INT_BASE; | ||
const n = chunks[chunks.length - 2] - OP_INT_BASE; | ||
if (m <= 0) | ||
return false; | ||
if (n > 16) | ||
return false; | ||
if (m > n) | ||
return false; | ||
if (n !== chunks.length - 3) | ||
return false; | ||
if (allowIncomplete) | ||
return true; | ||
const keys = chunks.slice(1, -2); | ||
return keys.every(bscript.isCanonicalPubKey); | ||
} | ||
check.toJSON = function () { return 'multi-sig output' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'multi-sig output'; | ||
}; |
@@ -0,14 +1,15 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// OP_RETURN {data} | ||
const bscript = require('../script') | ||
const OPS = require('bitcoin-ops') | ||
function check (script) { | ||
const buffer = bscript.compile(script) | ||
return buffer.length > 1 && | ||
buffer[0] === OPS.OP_RETURN | ||
const bscript = require("../script"); | ||
const OPS = bscript.OPS; | ||
function check(script) { | ||
const buffer = bscript.compile(script); | ||
return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; | ||
} | ||
check.toJSON = function () { return 'null data output' } | ||
module.exports = { output: { check: check } } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'null data output'; | ||
}; | ||
const output = { check }; | ||
exports.output = output; |
@@ -1,4 +0,6 @@ | ||
module.exports = { | ||
input: require('./input'), | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const input = require("./input"); | ||
exports.input = input; | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,15 +1,13 @@ | ||
"use strict"; | ||
// {signature} | ||
const bscript = require('../../script') | ||
function check (script) { | ||
const chunks = bscript.decompile(script) | ||
return chunks.length === 1 && | ||
bscript.isCanonicalScriptSignature(chunks[0]) | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
function check(script) { | ||
const chunks = bscript.decompile(script); | ||
return (chunks.length === 1 && | ||
bscript.isCanonicalScriptSignature(chunks[0])); | ||
} | ||
check.toJSON = function () { return 'pubKey input' } | ||
module.exports = { | ||
check: check | ||
} | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'pubKey input'; | ||
}; |
@@ -0,15 +1,15 @@ | ||
"use strict"; | ||
// {pubKey} OP_CHECKSIG | ||
const bscript = require('../../script') | ||
const OPS = require('bitcoin-ops') | ||
function check (script) { | ||
const chunks = bscript.decompile(script) | ||
return chunks.length === 2 && | ||
bscript.isCanonicalPubKey(chunks[0]) && | ||
chunks[1] === OPS.OP_CHECKSIG | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
function check(script) { | ||
const chunks = bscript.decompile(script); | ||
return (chunks.length === 2 && | ||
bscript.isCanonicalPubKey(chunks[0]) && | ||
chunks[1] === script_1.OPS.OP_CHECKSIG); | ||
} | ||
check.toJSON = function () { return 'pubKey output' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'pubKey output'; | ||
}; |
@@ -1,4 +0,6 @@ | ||
module.exports = { | ||
input: require('./input'), | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const input = require("./input"); | ||
exports.input = input; | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,14 +1,14 @@ | ||
"use strict"; | ||
// {signature} {pubKey} | ||
const bscript = require('../../script') | ||
function check (script) { | ||
const chunks = bscript.decompile(script) | ||
return chunks.length === 2 && | ||
bscript.isCanonicalScriptSignature(chunks[0]) && | ||
bscript.isCanonicalPubKey(chunks[1]) | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
function check(script) { | ||
const chunks = bscript.decompile(script); | ||
return (chunks.length === 2 && | ||
bscript.isCanonicalScriptSignature(chunks[0]) && | ||
bscript.isCanonicalPubKey(chunks[1])); | ||
} | ||
check.toJSON = function () { return 'pubKeyHash input' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'pubKeyHash input'; | ||
}; |
@@ -0,18 +1,18 @@ | ||
"use strict"; | ||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG | ||
const bscript = require('../../script') | ||
const OPS = require('bitcoin-ops') | ||
function check (script) { | ||
const buffer = bscript.compile(script) | ||
return buffer.length === 25 && | ||
buffer[0] === OPS.OP_DUP && | ||
buffer[1] === OPS.OP_HASH160 && | ||
buffer[2] === 0x14 && | ||
buffer[23] === OPS.OP_EQUALVERIFY && | ||
buffer[24] === OPS.OP_CHECKSIG | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
function check(script) { | ||
const buffer = bscript.compile(script); | ||
return (buffer.length === 25 && | ||
buffer[0] === script_1.OPS.OP_DUP && | ||
buffer[1] === script_1.OPS.OP_HASH160 && | ||
buffer[2] === 0x14 && | ||
buffer[23] === script_1.OPS.OP_EQUALVERIFY && | ||
buffer[24] === script_1.OPS.OP_CHECKSIG); | ||
} | ||
check.toJSON = function () { return 'pubKeyHash output' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'pubKeyHash output'; | ||
}; |
@@ -1,4 +0,6 @@ | ||
module.exports = { | ||
input: require('./input'), | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const input = require("./input"); | ||
exports.input = input; | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,48 +1,44 @@ | ||
"use strict"; | ||
// <scriptSig> {serialized scriptPubKey script} | ||
const Buffer = require('safe-buffer').Buffer | ||
const bscript = require('../../script') | ||
const p2ms = require('../multisig/') | ||
const p2pk = require('../pubkey/') | ||
const p2pkh = require('../pubkeyhash/') | ||
const p2wpkho = require('../witnesspubkeyhash/output') | ||
const p2wsho = require('../witnessscripthash/output') | ||
function check (script, allowIncomplete) { | ||
const chunks = bscript.decompile(script) | ||
if (chunks.length < 1) return false | ||
const lastChunk = chunks[chunks.length - 1] | ||
if (!Buffer.isBuffer(lastChunk)) return false | ||
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))) | ||
const redeemScriptChunks = bscript.decompile(lastChunk) | ||
// is redeemScript a valid script? | ||
if (!redeemScriptChunks) return false | ||
// is redeemScriptSig push only? | ||
if (!bscript.isPushOnly(scriptSigChunks)) return false | ||
// is witness? | ||
if (chunks.length === 1) { | ||
return p2wsho.check(redeemScriptChunks) || | ||
p2wpkho.check(redeemScriptChunks) | ||
} | ||
// match types | ||
if (p2pkh.input.check(scriptSigChunks) && | ||
p2pkh.output.check(redeemScriptChunks)) return true | ||
if (p2ms.input.check(scriptSigChunks, allowIncomplete) && | ||
p2ms.output.check(redeemScriptChunks)) return true | ||
if (p2pk.input.check(scriptSigChunks) && | ||
p2pk.output.check(redeemScriptChunks)) return true | ||
return false | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const p2ms = require("../multisig"); | ||
const p2pk = require("../pubkey"); | ||
const p2pkh = require("../pubkeyhash"); | ||
const p2wpkho = require("../witnesspubkeyhash/output"); | ||
const p2wsho = require("../witnessscripthash/output"); | ||
function check(script, allowIncomplete) { | ||
const chunks = bscript.decompile(script); | ||
if (chunks.length < 1) | ||
return false; | ||
const lastChunk = chunks[chunks.length - 1]; | ||
if (!Buffer.isBuffer(lastChunk)) | ||
return false; | ||
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))); | ||
const redeemScriptChunks = bscript.decompile(lastChunk); | ||
// is redeemScript a valid script? | ||
if (!redeemScriptChunks) | ||
return false; | ||
// is redeemScriptSig push only? | ||
if (!bscript.isPushOnly(scriptSigChunks)) | ||
return false; | ||
// is witness? | ||
if (chunks.length === 1) { | ||
return (p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)); | ||
} | ||
// match types | ||
if (p2pkh.input.check(scriptSigChunks) && | ||
p2pkh.output.check(redeemScriptChunks)) | ||
return true; | ||
if (p2ms.input.check(scriptSigChunks, allowIncomplete) && | ||
p2ms.output.check(redeemScriptChunks)) | ||
return true; | ||
if (p2pk.input.check(scriptSigChunks) && | ||
p2pk.output.check(redeemScriptChunks)) | ||
return true; | ||
return false; | ||
} | ||
check.toJSON = function () { return 'scriptHash input' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'scriptHash input'; | ||
}; |
@@ -0,16 +1,16 @@ | ||
"use strict"; | ||
// OP_HASH160 {scriptHash} OP_EQUAL | ||
const bscript = require('../../script') | ||
const OPS = require('bitcoin-ops') | ||
function check (script) { | ||
const buffer = bscript.compile(script) | ||
return buffer.length === 23 && | ||
buffer[0] === OPS.OP_HASH160 && | ||
buffer[1] === 0x14 && | ||
buffer[22] === OPS.OP_EQUAL | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
function check(script) { | ||
const buffer = bscript.compile(script); | ||
return (buffer.length === 23 && | ||
buffer[0] === script_1.OPS.OP_HASH160 && | ||
buffer[1] === 0x14 && | ||
buffer[22] === script_1.OPS.OP_EQUAL); | ||
} | ||
check.toJSON = function () { return 'scriptHash output' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'scriptHash output'; | ||
}; |
@@ -1,3 +0,4 @@ | ||
module.exports = { | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,42 +1,32 @@ | ||
"use strict"; | ||
// OP_RETURN {aa21a9ed} {commitment} | ||
const Buffer = require('safe-buffer').Buffer | ||
const bscript = require('../../script') | ||
const types = require('../../types') | ||
const typeforce = require('typeforce') | ||
const OPS = require('bitcoin-ops') | ||
const HEADER = Buffer.from('aa21a9ed', 'hex') | ||
function check (script) { | ||
const buffer = bscript.compile(script) | ||
return buffer.length > 37 && | ||
buffer[0] === OPS.OP_RETURN && | ||
buffer[1] === 0x24 && | ||
buffer.slice(2, 6).equals(HEADER) | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
const types = require("../../types"); | ||
const typeforce = require('typeforce'); | ||
const HEADER = Buffer.from('aa21a9ed', 'hex'); | ||
function check(script) { | ||
const buffer = bscript.compile(script); | ||
return (buffer.length > 37 && | ||
buffer[0] === script_1.OPS.OP_RETURN && | ||
buffer[1] === 0x24 && | ||
buffer.slice(2, 6).equals(HEADER)); | ||
} | ||
check.toJSON = function () { return 'Witness commitment output' } | ||
function encode (commitment) { | ||
typeforce(types.Hash256bit, commitment) | ||
const buffer = Buffer.allocUnsafe(36) | ||
HEADER.copy(buffer, 0) | ||
commitment.copy(buffer, 4) | ||
return bscript.compile([OPS.OP_RETURN, buffer]) | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'Witness commitment output'; | ||
}; | ||
function encode(commitment) { | ||
typeforce(types.Hash256bit, commitment); | ||
const buffer = Buffer.allocUnsafe(36); | ||
HEADER.copy(buffer, 0); | ||
commitment.copy(buffer, 4); | ||
return bscript.compile([script_1.OPS.OP_RETURN, buffer]); | ||
} | ||
function decode (buffer) { | ||
typeforce(check, buffer) | ||
return bscript.decompile(buffer)[1].slice(4, 36) | ||
exports.encode = encode; | ||
function decode(buffer) { | ||
typeforce(check, buffer); | ||
return bscript.decompile(buffer)[1].slice(4, 36); | ||
} | ||
module.exports = { | ||
check: check, | ||
decode: decode, | ||
encode: encode | ||
} | ||
exports.decode = decode; |
@@ -1,4 +0,6 @@ | ||
module.exports = { | ||
input: require('./input'), | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const input = require("./input"); | ||
exports.input = input; | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,18 +1,17 @@ | ||
"use strict"; | ||
// {signature} {pubKey} | ||
const bscript = require('../../script') | ||
function isCompressedCanonicalPubKey (pubKey) { | ||
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33 | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
function isCompressedCanonicalPubKey(pubKey) { | ||
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; | ||
} | ||
function check (script) { | ||
const chunks = bscript.decompile(script) | ||
return chunks.length === 2 && | ||
bscript.isCanonicalScriptSignature(chunks[0]) && | ||
isCompressedCanonicalPubKey(chunks[1]) | ||
function check(script) { | ||
const chunks = bscript.decompile(script); | ||
return (chunks.length === 2 && | ||
bscript.isCanonicalScriptSignature(chunks[0]) && | ||
isCompressedCanonicalPubKey(chunks[1])); | ||
} | ||
check.toJSON = function () { return 'witnessPubKeyHash input' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'witnessPubKeyHash input'; | ||
}; |
@@ -0,17 +1,13 @@ | ||
"use strict"; | ||
// OP_0 {pubKeyHash} | ||
const bscript = require('../../script') | ||
const OPS = require('bitcoin-ops') | ||
function check (script) { | ||
const buffer = bscript.compile(script) | ||
return buffer.length === 22 && | ||
buffer[0] === OPS.OP_0 && | ||
buffer[1] === 0x14 | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
function check(script) { | ||
const buffer = bscript.compile(script); | ||
return buffer.length === 22 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x14; | ||
} | ||
check.toJSON = function () { return 'Witness pubKeyHash output' } | ||
module.exports = { | ||
check | ||
} | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'Witness pubKeyHash output'; | ||
}; |
@@ -1,4 +0,6 @@ | ||
module.exports = { | ||
input: require('./input'), | ||
output: require('./output') | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const input = require("./input"); | ||
exports.input = input; | ||
const output = require("./output"); | ||
exports.output = output; |
@@ -0,39 +1,36 @@ | ||
"use strict"; | ||
// <scriptSig> {serialized scriptPubKey script} | ||
const bscript = require('../../script') | ||
const types = require('../../types') | ||
const typeforce = require('typeforce') | ||
const p2ms = require('../multisig/') | ||
const p2pk = require('../pubkey/') | ||
const p2pkh = require('../pubkeyhash/') | ||
function check (chunks, allowIncomplete) { | ||
typeforce(types.Array, chunks) | ||
if (chunks.length < 1) return false | ||
const witnessScript = chunks[chunks.length - 1] | ||
if (!Buffer.isBuffer(witnessScript)) return false | ||
const witnessScriptChunks = bscript.decompile(witnessScript) | ||
// is witnessScript a valid script? | ||
if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false | ||
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)) | ||
// match types | ||
if (p2pkh.input.check(witnessRawScriptSig) && | ||
p2pkh.output.check(witnessScriptChunks)) return true | ||
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && | ||
p2ms.output.check(witnessScriptChunks)) return true | ||
if (p2pk.input.check(witnessRawScriptSig) && | ||
p2pk.output.check(witnessScriptChunks)) return true | ||
return false | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const typeforce = require('typeforce'); | ||
const p2ms = require("../multisig"); | ||
const p2pk = require("../pubkey"); | ||
const p2pkh = require("../pubkeyhash"); | ||
function check(chunks, allowIncomplete) { | ||
typeforce(typeforce.Array, chunks); | ||
if (chunks.length < 1) | ||
return false; | ||
const witnessScript = chunks[chunks.length - 1]; | ||
if (!Buffer.isBuffer(witnessScript)) | ||
return false; | ||
const witnessScriptChunks = bscript.decompile(witnessScript); | ||
// is witnessScript a valid script? | ||
if (!witnessScriptChunks || witnessScriptChunks.length === 0) | ||
return false; | ||
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); | ||
// match types | ||
if (p2pkh.input.check(witnessRawScriptSig) && | ||
p2pkh.output.check(witnessScriptChunks)) | ||
return true; | ||
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && | ||
p2ms.output.check(witnessScriptChunks)) | ||
return true; | ||
if (p2pk.input.check(witnessRawScriptSig) && | ||
p2pk.output.check(witnessScriptChunks)) | ||
return true; | ||
return false; | ||
} | ||
check.toJSON = function () { return 'witnessScriptHash input' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'witnessScriptHash input'; | ||
}; |
@@ -0,15 +1,13 @@ | ||
"use strict"; | ||
// OP_0 {scriptHash} | ||
const bscript = require('../../script') | ||
const OPS = require('bitcoin-ops') | ||
function check (script) { | ||
const buffer = bscript.compile(script) | ||
return buffer.length === 34 && | ||
buffer[0] === OPS.OP_0 && | ||
buffer[1] === 0x20 | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bscript = require("../../script"); | ||
const script_1 = require("../../script"); | ||
function check(script) { | ||
const buffer = bscript.compile(script); | ||
return buffer.length === 34 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x20; | ||
} | ||
check.toJSON = function () { return 'Witness scriptHash output' } | ||
module.exports = { check } | ||
exports.check = check; | ||
check.toJSON = () => { | ||
return 'Witness scriptHash output'; | ||
}; |
@@ -1,777 +0,720 @@ | ||
const Buffer = require('safe-buffer').Buffer | ||
const baddress = require('./address') | ||
const bcrypto = require('./crypto') | ||
const bscript = require('./script') | ||
const networks = require('./networks') | ||
const ops = require('bitcoin-ops') | ||
const payments = require('./payments') | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const classify = require('./classify') | ||
const SCRIPT_TYPES = classify.types | ||
const ECPair = require('./ecpair') | ||
const Transaction = require('./transaction') | ||
function expandInput (scriptSig, witnessStack, type, scriptPubKey) { | ||
if (scriptSig.length === 0 && witnessStack.length === 0) return {} | ||
if (!type) { | ||
let ssType = classify.input(scriptSig, true) | ||
let wsType = classify.witness(witnessStack, true) | ||
if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined | ||
if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined | ||
type = ssType || wsType | ||
} | ||
switch (type) { | ||
case SCRIPT_TYPES.P2WPKH: { | ||
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack }) | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2WPKH, | ||
pubkeys: [pubkey], | ||
signatures: [signature] | ||
} | ||
} | ||
case SCRIPT_TYPES.P2PKH: { | ||
const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig }) | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2PKH, | ||
pubkeys: [pubkey], | ||
signatures: [signature] | ||
} | ||
} | ||
case SCRIPT_TYPES.P2PK: { | ||
const { signature } = payments.p2pk({ input: scriptSig }) | ||
return { | ||
prevOutType: SCRIPT_TYPES.P2PK, | ||
pubkeys: [undefined], | ||
signatures: [signature] | ||
} | ||
} | ||
case SCRIPT_TYPES.P2MS: { | ||
const { m, pubkeys, signatures } = payments.p2ms({ | ||
input: scriptSig, | ||
output: scriptPubKey | ||
}, { allowIncomplete: true }) | ||
return { | ||
prevOutType: SCRIPT_TYPES.P2MS, | ||
pubkeys: pubkeys, | ||
signatures: signatures, | ||
maxSignatures: m | ||
} | ||
} | ||
} | ||
if (type === SCRIPT_TYPES.P2SH) { | ||
const { output, redeem } = payments.p2sh({ | ||
input: scriptSig, | ||
witness: witnessStack | ||
}) | ||
const outputType = classify.output(redeem.output) | ||
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output) | ||
if (!expanded.prevOutType) return {} | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2SH, | ||
redeemScript: redeem.output, | ||
redeemScriptType: expanded.prevOutType, | ||
witnessScript: expanded.witnessScript, | ||
witnessScriptType: expanded.witnessScriptType, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures | ||
} | ||
} | ||
if (type === SCRIPT_TYPES.P2WSH) { | ||
const { output, redeem } = payments.p2wsh({ | ||
input: scriptSig, | ||
witness: witnessStack | ||
}) | ||
const outputType = classify.output(redeem.output) | ||
let expanded | ||
if (outputType === SCRIPT_TYPES.P2WPKH) { | ||
expanded = expandInput(redeem.input, redeem.witness, outputType) | ||
} else { | ||
expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output) | ||
} | ||
if (!expanded.prevOutType) return {} | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2WSH, | ||
witnessScript: redeem.output, | ||
witnessScriptType: expanded.prevOutType, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures | ||
} | ||
} | ||
return { | ||
prevOutType: SCRIPT_TYPES.NONSTANDARD, | ||
prevOutScript: scriptSig | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const baddress = require("./address"); | ||
const bufferutils_1 = require("./bufferutils"); | ||
const classify = require("./classify"); | ||
const bcrypto = require("./crypto"); | ||
const ECPair = require("./ecpair"); | ||
const networks = require("./networks"); | ||
const payments = require("./payments"); | ||
const bscript = require("./script"); | ||
const script_1 = require("./script"); | ||
const transaction_1 = require("./transaction"); | ||
const types = require("./types"); | ||
const typeforce = require('typeforce'); | ||
const SCRIPT_TYPES = classify.types; | ||
function txIsString(tx) { | ||
return typeof tx === 'string' || tx instanceof String; | ||
} | ||
// could be done in expandInput, but requires the original Transaction for hashForSignature | ||
function fixMultisigOrder (input, transaction, vin) { | ||
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) return | ||
if (input.pubkeys.length === input.signatures.length) return | ||
const unmatched = input.signatures.concat() | ||
input.signatures = input.pubkeys.map(function (pubKey) { | ||
const keyPair = ECPair.fromPublicKey(pubKey) | ||
let match | ||
// check for a signature | ||
unmatched.some(function (signature, i) { | ||
// skip if undefined || OP_0 | ||
if (!signature) return false | ||
// TODO: avoid O(n) hashForSignature | ||
const parsed = bscript.signature.decode(signature) | ||
const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType) | ||
// skip if signature does not match pubKey | ||
if (!keyPair.verify(hash, parsed.signature)) return false | ||
// remove matched signature from unmatched | ||
unmatched[i] = undefined | ||
match = signature | ||
return true | ||
}) | ||
return match | ||
}) | ||
function txIsTransaction(tx) { | ||
return tx instanceof transaction_1.Transaction; | ||
} | ||
function expandOutput (script, ourPubKey) { | ||
typeforce(types.Buffer, script) | ||
const type = classify.output(script) | ||
switch (type) { | ||
case SCRIPT_TYPES.P2PKH: { | ||
if (!ourPubKey) return { type } | ||
// does our hash160(pubKey) match the output scripts? | ||
const pkh1 = payments.p2pkh({ output: script }).hash | ||
const pkh2 = bcrypto.hash160(ourPubKey) | ||
if (!pkh1.equals(pkh2)) return { type } | ||
return { | ||
type, | ||
pubkeys: [ourPubKey], | ||
signatures: [undefined] | ||
} | ||
class TransactionBuilder { | ||
// WARNING: maximumFeeRate is __NOT__ to be relied on, | ||
// it's just another potential safety mechanism (safety in-depth) | ||
constructor(network = networks.bitcoin, maximumFeeRate = 2500) { | ||
this.network = network; | ||
this.maximumFeeRate = maximumFeeRate; | ||
this.__PREV_TX_SET = {}; | ||
this.__INPUTS = []; | ||
this.__TX = new transaction_1.Transaction(); | ||
this.__TX.version = 2; | ||
} | ||
case SCRIPT_TYPES.P2WPKH: { | ||
if (!ourPubKey) return { type } | ||
// does our hash160(pubKey) match the output scripts? | ||
const wpkh1 = payments.p2wpkh({ output: script }).hash | ||
const wpkh2 = bcrypto.hash160(ourPubKey) | ||
if (!wpkh1.equals(wpkh2)) return { type } | ||
return { | ||
type, | ||
pubkeys: [ourPubKey], | ||
signatures: [undefined] | ||
} | ||
static fromTransaction(transaction, network) { | ||
const txb = new TransactionBuilder(network); | ||
// Copy transaction fields | ||
txb.setVersion(transaction.version); | ||
txb.setLockTime(transaction.locktime); | ||
// Copy outputs (done first to avoid signature invalidation) | ||
transaction.outs.forEach(txOut => { | ||
txb.addOutput(txOut.script, txOut.value); | ||
}); | ||
// Copy inputs | ||
transaction.ins.forEach(txIn => { | ||
txb.__addInputUnsafe(txIn.hash, txIn.index, { | ||
sequence: txIn.sequence, | ||
script: txIn.script, | ||
witness: txIn.witness, | ||
}); | ||
}); | ||
// fix some things not possible through the public API | ||
txb.__INPUTS.forEach((input, i) => { | ||
fixMultisigOrder(input, transaction, i); | ||
}); | ||
return txb; | ||
} | ||
case SCRIPT_TYPES.P2PK: { | ||
const p2pk = payments.p2pk({ output: script }) | ||
return { | ||
type, | ||
pubkeys: [p2pk.pubkey], | ||
signatures: [undefined] | ||
} | ||
setLockTime(locktime) { | ||
typeforce(types.UInt32, locktime); | ||
// if any signatures exist, throw | ||
if (this.__INPUTS.some(input => { | ||
if (!input.signatures) | ||
return false; | ||
return input.signatures.some(s => s !== undefined); | ||
})) { | ||
throw new Error('No, this would invalidate signatures'); | ||
} | ||
this.__TX.locktime = locktime; | ||
} | ||
case SCRIPT_TYPES.P2MS: { | ||
const p2ms = payments.p2ms({ output: script }) | ||
return { | ||
type, | ||
pubkeys: p2ms.pubkeys, | ||
signatures: p2ms.pubkeys.map(() => undefined), | ||
maxSignatures: p2ms.m | ||
} | ||
setVersion(version) { | ||
typeforce(types.UInt32, version); | ||
// XXX: this might eventually become more complex depending on what the versions represent | ||
this.__TX.version = version; | ||
} | ||
} | ||
return { type } | ||
} | ||
function prepareInput (input, ourPubKey, redeemScript, witnessScript) { | ||
if (redeemScript && witnessScript) { | ||
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }) | ||
const p2wshAlt = payments.p2wsh({ output: redeemScript }) | ||
const p2sh = payments.p2sh({ redeem: { output: redeemScript } }) | ||
const p2shAlt = payments.p2sh({ redeem: p2wsh }) | ||
// enforces P2SH(P2WSH(...)) | ||
if (!p2wsh.hash.equals(p2wshAlt.hash)) throw new Error('Witness script inconsistent with prevOutScript') | ||
if (!p2sh.hash.equals(p2shAlt.hash)) throw new Error('Redeem script inconsistent with prevOutScript') | ||
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey) | ||
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')') | ||
if (input.signatures && input.signatures.some(x => x)) { | ||
expanded.signatures = input.signatures | ||
addInput(txHash, vout, sequence, prevOutScript) { | ||
if (!this.__canModifyInputs()) { | ||
throw new Error('No, this would invalidate signatures'); | ||
} | ||
let value; | ||
// is it a hex string? | ||
if (txIsString(txHash)) { | ||
// transaction hashs's are displayed in reverse order, un-reverse it | ||
txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); | ||
// is it a Transaction object? | ||
} | ||
else if (txIsTransaction(txHash)) { | ||
const txOut = txHash.outs[vout]; | ||
prevOutScript = txOut.script; | ||
value = txOut.value; | ||
txHash = txHash.getHash(false); | ||
} | ||
return this.__addInputUnsafe(txHash, vout, { | ||
sequence, | ||
prevOutScript, | ||
value, | ||
}); | ||
} | ||
let signScript = witnessScript | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure') | ||
return { | ||
redeemScript, | ||
redeemScriptType: SCRIPT_TYPES.P2WSH, | ||
witnessScript, | ||
witnessScriptType: expanded.type, | ||
prevOutType: SCRIPT_TYPES.P2SH, | ||
prevOutScript: p2sh.output, | ||
hasWitness: true, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures | ||
addOutput(scriptPubKey, value) { | ||
if (!this.__canModifyOutputs()) { | ||
throw new Error('No, this would invalidate signatures'); | ||
} | ||
// Attempt to get a script if it's a base58 or bech32 address string | ||
if (typeof scriptPubKey === 'string') { | ||
scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); | ||
} | ||
return this.__TX.addOutput(scriptPubKey, value); | ||
} | ||
} | ||
if (redeemScript) { | ||
const p2sh = payments.p2sh({ redeem: { output: redeemScript } }) | ||
if (input.prevOutScript) { | ||
let p2shAlt | ||
try { | ||
p2shAlt = payments.p2sh({ output: input.prevOutScript }) | ||
} catch (e) { throw new Error('PrevOutScript must be P2SH') } | ||
if (!p2sh.hash.equals(p2shAlt.hash)) throw new Error('Redeem script inconsistent with prevOutScript') | ||
build() { | ||
return this.__build(false); | ||
} | ||
const expanded = expandOutput(p2sh.redeem.output, ourPubKey) | ||
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')') | ||
if (input.signatures && input.signatures.some(x => x)) { | ||
expanded.signatures = input.signatures | ||
buildIncomplete() { | ||
return this.__build(true); | ||
} | ||
let signScript = redeemScript | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) { | ||
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output | ||
sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { | ||
// TODO: remove keyPair.network matching in 4.0.0 | ||
if (keyPair.network && keyPair.network !== this.network) | ||
throw new TypeError('Inconsistent network'); | ||
if (!this.__INPUTS[vin]) | ||
throw new Error('No input at index: ' + vin); | ||
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; | ||
if (this.__needsOutputs(hashType)) | ||
throw new Error('Transaction needs outputs'); | ||
const input = this.__INPUTS[vin]; | ||
// if redeemScript was previously provided, enforce consistency | ||
if (input.redeemScript !== undefined && | ||
redeemScript && | ||
!input.redeemScript.equals(redeemScript)) { | ||
throw new Error('Inconsistent redeemScript'); | ||
} | ||
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); | ||
if (!canSign(input)) { | ||
if (witnessValue !== undefined) { | ||
if (input.value !== undefined && input.value !== witnessValue) | ||
throw new Error('Input did not match witnessValue'); | ||
typeforce(types.Satoshi, witnessValue); | ||
input.value = witnessValue; | ||
} | ||
if (!canSign(input)) { | ||
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript); | ||
// updates inline | ||
Object.assign(input, prepared); | ||
} | ||
if (!canSign(input)) | ||
throw Error(input.prevOutType + ' not supported'); | ||
} | ||
// ready to sign | ||
let signatureHash; | ||
if (input.hasWitness) { | ||
signatureHash = this.__TX.hashForWitnessV0(vin, input.signScript, input.value, hashType); | ||
} | ||
else { | ||
signatureHash = this.__TX.hashForSignature(vin, input.signScript, hashType); | ||
} | ||
// enforce in order signing of public keys | ||
const signed = input.pubkeys.some((pubKey, i) => { | ||
if (!ourPubKey.equals(pubKey)) | ||
return false; | ||
if (input.signatures[i]) | ||
throw new Error('Signature already exists'); | ||
// TODO: add tests | ||
if (ourPubKey.length !== 33 && input.hasWitness) { | ||
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); | ||
} | ||
const signature = keyPair.sign(signatureHash); | ||
input.signatures[i] = bscript.signature.encode(signature, hashType); | ||
return true; | ||
}); | ||
if (!signed) | ||
throw new Error('Key pair cannot sign for this input'); | ||
} | ||
return { | ||
redeemScript, | ||
redeemScriptType: expanded.type, | ||
prevOutType: SCRIPT_TYPES.P2SH, | ||
prevOutScript: p2sh.output, | ||
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures | ||
__addInputUnsafe(txHash, vout, options) { | ||
if (transaction_1.Transaction.isCoinbaseHash(txHash)) { | ||
throw new Error('coinbase inputs not supported'); | ||
} | ||
const prevTxOut = txHash.toString('hex') + ':' + vout; | ||
if (this.__PREV_TX_SET[prevTxOut] !== undefined) | ||
throw new Error('Duplicate TxOut: ' + prevTxOut); | ||
let input = {}; | ||
// derive what we can from the scriptSig | ||
if (options.script !== undefined) { | ||
input = expandInput(options.script, options.witness || []); | ||
} | ||
// if an input value was given, retain it | ||
if (options.value !== undefined) { | ||
input.value = options.value; | ||
} | ||
// derive what we can from the previous transactions output script | ||
if (!input.prevOutScript && options.prevOutScript) { | ||
let prevOutType; | ||
if (!input.pubkeys && !input.signatures) { | ||
const expanded = expandOutput(options.prevOutScript); | ||
if (expanded.pubkeys) { | ||
input.pubkeys = expanded.pubkeys; | ||
input.signatures = expanded.signatures; | ||
} | ||
prevOutType = expanded.type; | ||
} | ||
input.prevOutScript = options.prevOutScript; | ||
input.prevOutType = prevOutType || classify.output(options.prevOutScript); | ||
} | ||
const vin = this.__TX.addInput(txHash, vout, options.sequence, options.scriptSig); | ||
this.__INPUTS[vin] = input; | ||
this.__PREV_TX_SET[prevTxOut] = true; | ||
return vin; | ||
} | ||
} | ||
if (witnessScript) { | ||
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }) | ||
if (input.prevOutScript) { | ||
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }) | ||
if (!p2wsh.hash.equals(p2wshAlt.hash)) throw new Error('Witness script inconsistent with prevOutScript') | ||
__build(allowIncomplete) { | ||
if (!allowIncomplete) { | ||
if (!this.__TX.ins.length) | ||
throw new Error('Transaction has no inputs'); | ||
if (!this.__TX.outs.length) | ||
throw new Error('Transaction has no outputs'); | ||
} | ||
const tx = this.__TX.clone(); | ||
// create script signatures from inputs | ||
this.__INPUTS.forEach((input, i) => { | ||
if (!input.prevOutType && !allowIncomplete) | ||
throw new Error('Transaction is not complete'); | ||
const result = build(input.prevOutType, input, allowIncomplete); | ||
if (!result) { | ||
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) | ||
throw new Error('Unknown input type'); | ||
if (!allowIncomplete) | ||
throw new Error('Not enough information'); | ||
return; | ||
} | ||
tx.setInputScript(i, result.input); | ||
tx.setWitness(i, result.witness); | ||
}); | ||
if (!allowIncomplete) { | ||
// do not rely on this, its merely a last resort | ||
if (this.__overMaximumFees(tx.virtualSize())) { | ||
throw new Error('Transaction has absurd fees'); | ||
} | ||
} | ||
return tx; | ||
} | ||
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey) | ||
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')') | ||
if (input.signatures && input.signatures.some(x => x)) { | ||
expanded.signatures = input.signatures | ||
__canModifyInputs() { | ||
return this.__INPUTS.every(input => { | ||
if (!input.signatures) | ||
return true; | ||
return input.signatures.every(signature => { | ||
if (!signature) | ||
return true; | ||
const hashType = signatureHashType(signature); | ||
// if SIGHASH_ANYONECANPAY is set, signatures would not | ||
// be invalidated by more inputs | ||
return (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0; | ||
}); | ||
}); | ||
} | ||
let signScript = witnessScript | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2WSH(P2WPKH) is a consensus failure') | ||
return { | ||
witnessScript, | ||
witnessScriptType: expanded.type, | ||
prevOutType: SCRIPT_TYPES.P2WSH, | ||
prevOutScript: p2wsh.output, | ||
hasWitness: true, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures | ||
__needsOutputs(signingHashType) { | ||
if (signingHashType === transaction_1.Transaction.SIGHASH_ALL) { | ||
return this.__TX.outs.length === 0; | ||
} | ||
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs | ||
// .build() will fail, but .buildIncomplete() is OK | ||
return (this.__TX.outs.length === 0 && | ||
this.__INPUTS.some(input => { | ||
if (!input.signatures) | ||
return false; | ||
return input.signatures.some(signature => { | ||
if (!signature) | ||
return false; // no signature, no issue | ||
const hashType = signatureHashType(signature); | ||
if (hashType & transaction_1.Transaction.SIGHASH_NONE) | ||
return false; // SIGHASH_NONE doesn't care about outputs | ||
return true; // SIGHASH_* does care | ||
}); | ||
})); | ||
} | ||
} | ||
if (input.prevOutType && input.prevOutScript) { | ||
// embedded scripts are not possible without extra information | ||
if (input.prevOutType === SCRIPT_TYPES.P2SH) throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript') | ||
if (input.prevOutType === SCRIPT_TYPES.P2WSH) throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript') | ||
if (!input.prevOutScript) throw new Error('PrevOutScript is missing') | ||
const expanded = expandOutput(input.prevOutScript, ourPubKey) | ||
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported (' + bscript.toASM(input.prevOutScript) + ')') | ||
if (input.signatures && input.signatures.some(x => x)) { | ||
expanded.signatures = input.signatures | ||
__canModifyOutputs() { | ||
const nInputs = this.__TX.ins.length; | ||
const nOutputs = this.__TX.outs.length; | ||
return this.__INPUTS.every(input => { | ||
if (input.signatures === undefined) | ||
return true; | ||
return input.signatures.every(signature => { | ||
if (!signature) | ||
return true; | ||
const hashType = signatureHashType(signature); | ||
const hashTypeMod = hashType & 0x1f; | ||
if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) | ||
return true; | ||
if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { | ||
// if SIGHASH_SINGLE is set, and nInputs > nOutputs | ||
// some signatures would be invalidated by the addition | ||
// of more outputs | ||
return nInputs <= nOutputs; | ||
} | ||
return false; | ||
}); | ||
}); | ||
} | ||
let signScript = input.prevOutScript | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) { | ||
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output | ||
__overMaximumFees(bytes) { | ||
// not all inputs will have .value defined | ||
const incoming = this.__INPUTS.reduce((a, x) => a + (x.value >>> 0), 0); | ||
// but all outputs do, and if we have any input value | ||
// we can immediately determine if the outputs are too small | ||
const outgoing = this.__TX.outs.reduce((a, x) => a + x.value, 0); | ||
const fee = incoming - outgoing; | ||
const feeRate = fee / bytes; | ||
return feeRate > this.maximumFeeRate; | ||
} | ||
return { | ||
prevOutType: expanded.type, | ||
prevOutScript: input.prevOutScript, | ||
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures | ||
} | ||
} | ||
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output | ||
return { | ||
prevOutType: SCRIPT_TYPES.P2PKH, | ||
prevOutScript: prevOutScript, | ||
hasWitness: false, | ||
signScript: prevOutScript, | ||
signType: SCRIPT_TYPES.P2PKH, | ||
pubkeys: [ourPubKey], | ||
signatures: [undefined] | ||
} | ||
} | ||
function build (type, input, allowIncomplete) { | ||
const pubkeys = input.pubkeys || [] | ||
let signatures = input.signatures || [] | ||
switch (type) { | ||
case SCRIPT_TYPES.P2PKH: { | ||
if (pubkeys.length === 0) break | ||
if (signatures.length === 0) break | ||
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }) | ||
exports.TransactionBuilder = TransactionBuilder; | ||
function expandInput(scriptSig, witnessStack, type, scriptPubKey) { | ||
if (scriptSig.length === 0 && witnessStack.length === 0) | ||
return {}; | ||
if (!type) { | ||
let ssType = classify.input(scriptSig, true); | ||
let wsType = classify.witness(witnessStack, true); | ||
if (ssType === SCRIPT_TYPES.NONSTANDARD) | ||
ssType = undefined; | ||
if (wsType === SCRIPT_TYPES.NONSTANDARD) | ||
wsType = undefined; | ||
type = ssType || wsType; | ||
} | ||
case SCRIPT_TYPES.P2WPKH: { | ||
if (pubkeys.length === 0) break | ||
if (signatures.length === 0) break | ||
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }) | ||
switch (type) { | ||
case SCRIPT_TYPES.P2WPKH: { | ||
const { output, pubkey, signature } = payments.p2wpkh({ | ||
witness: witnessStack, | ||
}); | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2WPKH, | ||
pubkeys: [pubkey], | ||
signatures: [signature], | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2PKH: { | ||
const { output, pubkey, signature } = payments.p2pkh({ | ||
input: scriptSig, | ||
}); | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2PKH, | ||
pubkeys: [pubkey], | ||
signatures: [signature], | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2PK: { | ||
const { signature } = payments.p2pk({ input: scriptSig }); | ||
return { | ||
prevOutType: SCRIPT_TYPES.P2PK, | ||
pubkeys: [undefined], | ||
signatures: [signature], | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2MS: { | ||
const { m, pubkeys, signatures } = payments.p2ms({ | ||
input: scriptSig, | ||
output: scriptPubKey, | ||
}, { allowIncomplete: true }); | ||
return { | ||
prevOutType: SCRIPT_TYPES.P2MS, | ||
pubkeys, | ||
signatures, | ||
maxSignatures: m, | ||
}; | ||
} | ||
} | ||
case SCRIPT_TYPES.P2PK: { | ||
if (pubkeys.length === 0) break | ||
if (signatures.length === 0) break | ||
return payments.p2pk({ signature: signatures[0] }) | ||
if (type === SCRIPT_TYPES.P2SH) { | ||
const { output, redeem } = payments.p2sh({ | ||
input: scriptSig, | ||
witness: witnessStack, | ||
}); | ||
const outputType = classify.output(redeem.output); | ||
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output); | ||
if (!expanded.prevOutType) | ||
return {}; | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2SH, | ||
redeemScript: redeem.output, | ||
redeemScriptType: expanded.prevOutType, | ||
witnessScript: expanded.witnessScript, | ||
witnessScriptType: expanded.witnessScriptType, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2MS: { | ||
const m = input.maxSignatures | ||
if (allowIncomplete) { | ||
signatures = signatures.map(x => x || ops.OP_0) | ||
} else { | ||
signatures = signatures.filter(x => x) | ||
} | ||
// if the transaction is not not complete (complete), or if signatures.length === m, validate | ||
// otherwise, the number of OP_0's may be >= m, so don't validate (boo) | ||
const validate = !allowIncomplete || (m === signatures.length) | ||
return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }) | ||
} | ||
case SCRIPT_TYPES.P2SH: { | ||
const redeem = build(input.redeemScriptType, input, allowIncomplete) | ||
if (!redeem) return | ||
return payments.p2sh({ | ||
redeem: { | ||
output: redeem.output || input.redeemScript, | ||
input: redeem.input, | ||
witness: redeem.witness | ||
if (type === SCRIPT_TYPES.P2WSH) { | ||
const { output, redeem } = payments.p2wsh({ | ||
input: scriptSig, | ||
witness: witnessStack, | ||
}); | ||
const outputType = classify.output(redeem.output); | ||
let expanded; | ||
if (outputType === SCRIPT_TYPES.P2WPKH) { | ||
expanded = expandInput(redeem.input, redeem.witness, outputType); | ||
} | ||
}) | ||
} | ||
case SCRIPT_TYPES.P2WSH: { | ||
const redeem = build(input.witnessScriptType, input, allowIncomplete) | ||
if (!redeem) return | ||
return payments.p2wsh({ | ||
redeem: { | ||
output: input.witnessScript, | ||
input: redeem.input, | ||
witness: redeem.witness | ||
else { | ||
expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output); | ||
} | ||
}) | ||
if (!expanded.prevOutType) | ||
return {}; | ||
return { | ||
prevOutScript: output, | ||
prevOutType: SCRIPT_TYPES.P2WSH, | ||
witnessScript: redeem.output, | ||
witnessScriptType: expanded.prevOutType, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
}; | ||
} | ||
} | ||
return { | ||
prevOutType: SCRIPT_TYPES.NONSTANDARD, | ||
prevOutScript: scriptSig, | ||
}; | ||
} | ||
function TransactionBuilder (network, maximumFeeRate) { | ||
this.__prevTxSet = {} | ||
this.network = network || networks.bitcoin | ||
// WARNING: This is __NOT__ to be relied on, its just another potential safety mechanism (safety in-depth) | ||
this.maximumFeeRate = maximumFeeRate || 2500 | ||
this.__inputs = [] | ||
this.__tx = new Transaction() | ||
this.__tx.version = 2 | ||
// could be done in expandInput, but requires the original Transaction for hashForSignature | ||
function fixMultisigOrder(input, transaction, vin) { | ||
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) | ||
return; | ||
if (input.pubkeys.length === input.signatures.length) | ||
return; | ||
const unmatched = input.signatures.concat(); | ||
input.signatures = input.pubkeys.map(pubKey => { | ||
const keyPair = ECPair.fromPublicKey(pubKey); | ||
let match; | ||
// check for a signature | ||
unmatched.some((signature, i) => { | ||
// skip if undefined || OP_0 | ||
if (!signature) | ||
return false; | ||
// TODO: avoid O(n) hashForSignature | ||
const parsed = bscript.signature.decode(signature); | ||
const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType); | ||
// skip if signature does not match pubKey | ||
if (!keyPair.verify(hash, parsed.signature)) | ||
return false; | ||
// remove matched signature from unmatched | ||
unmatched[i] = undefined; | ||
match = signature; | ||
return true; | ||
}); | ||
return match; | ||
}); | ||
} | ||
TransactionBuilder.prototype.setLockTime = function (locktime) { | ||
typeforce(types.UInt32, locktime) | ||
// if any signatures exist, throw | ||
if (this.__inputs.some(function (input) { | ||
if (!input.signatures) return false | ||
return input.signatures.some(function (s) { return s }) | ||
})) { | ||
throw new Error('No, this would invalidate signatures') | ||
} | ||
this.__tx.locktime = locktime | ||
} | ||
TransactionBuilder.prototype.setVersion = function (version) { | ||
typeforce(types.UInt32, version) | ||
// XXX: this might eventually become more complex depending on what the versions represent | ||
this.__tx.version = version | ||
} | ||
TransactionBuilder.fromTransaction = function (transaction, network) { | ||
const txb = new TransactionBuilder(network) | ||
// Copy transaction fields | ||
txb.setVersion(transaction.version) | ||
txb.setLockTime(transaction.locktime) | ||
// Copy outputs (done first to avoid signature invalidation) | ||
transaction.outs.forEach(function (txOut) { | ||
txb.addOutput(txOut.script, txOut.value) | ||
}) | ||
// Copy inputs | ||
transaction.ins.forEach(function (txIn) { | ||
txb.__addInputUnsafe(txIn.hash, txIn.index, { | ||
sequence: txIn.sequence, | ||
script: txIn.script, | ||
witness: txIn.witness | ||
}) | ||
}) | ||
// fix some things not possible through the public API | ||
txb.__inputs.forEach(function (input, i) { | ||
fixMultisigOrder(input, transaction, i) | ||
}) | ||
return txb | ||
} | ||
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) { | ||
if (!this.__canModifyInputs()) { | ||
throw new Error('No, this would invalidate signatures') | ||
} | ||
let value | ||
// is it a hex string? | ||
if (typeof txHash === 'string') { | ||
// transaction hashs's are displayed in reverse order, un-reverse it | ||
txHash = Buffer.from(txHash, 'hex').reverse() | ||
// is it a Transaction object? | ||
} else if (txHash instanceof Transaction) { | ||
const txOut = txHash.outs[vout] | ||
prevOutScript = txOut.script | ||
value = txOut.value | ||
txHash = txHash.getHash() | ||
} | ||
return this.__addInputUnsafe(txHash, vout, { | ||
sequence: sequence, | ||
prevOutScript: prevOutScript, | ||
value: value | ||
}) | ||
} | ||
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options) { | ||
if (Transaction.isCoinbaseHash(txHash)) { | ||
throw new Error('coinbase inputs not supported') | ||
} | ||
const prevTxOut = txHash.toString('hex') + ':' + vout | ||
if (this.__prevTxSet[prevTxOut] !== undefined) throw new Error('Duplicate TxOut: ' + prevTxOut) | ||
let input = {} | ||
// derive what we can from the scriptSig | ||
if (options.script !== undefined) { | ||
input = expandInput(options.script, options.witness || []) | ||
} | ||
// if an input value was given, retain it | ||
if (options.value !== undefined) { | ||
input.value = options.value | ||
} | ||
// derive what we can from the previous transactions output script | ||
if (!input.prevOutScript && options.prevOutScript) { | ||
let prevOutType | ||
if (!input.pubkeys && !input.signatures) { | ||
const expanded = expandOutput(options.prevOutScript) | ||
if (expanded.pubkeys) { | ||
input.pubkeys = expanded.pubkeys | ||
input.signatures = expanded.signatures | ||
} | ||
prevOutType = expanded.type | ||
function expandOutput(script, ourPubKey) { | ||
typeforce(types.Buffer, script); | ||
const type = classify.output(script); | ||
switch (type) { | ||
case SCRIPT_TYPES.P2PKH: { | ||
if (!ourPubKey) | ||
return { type }; | ||
// does our hash160(pubKey) match the output scripts? | ||
const pkh1 = payments.p2pkh({ output: script }).hash; | ||
const pkh2 = bcrypto.hash160(ourPubKey); | ||
if (!pkh1.equals(pkh2)) | ||
return { type }; | ||
return { | ||
type, | ||
pubkeys: [ourPubKey], | ||
signatures: [undefined], | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2WPKH: { | ||
if (!ourPubKey) | ||
return { type }; | ||
// does our hash160(pubKey) match the output scripts? | ||
const wpkh1 = payments.p2wpkh({ output: script }).hash; | ||
const wpkh2 = bcrypto.hash160(ourPubKey); | ||
if (!wpkh1.equals(wpkh2)) | ||
return { type }; | ||
return { | ||
type, | ||
pubkeys: [ourPubKey], | ||
signatures: [undefined], | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2PK: { | ||
const p2pk = payments.p2pk({ output: script }); | ||
return { | ||
type, | ||
pubkeys: [p2pk.pubkey], | ||
signatures: [undefined], | ||
}; | ||
} | ||
case SCRIPT_TYPES.P2MS: { | ||
const p2ms = payments.p2ms({ output: script }); | ||
return { | ||
type, | ||
pubkeys: p2ms.pubkeys, | ||
signatures: p2ms.pubkeys.map(() => undefined), | ||
maxSignatures: p2ms.m, | ||
}; | ||
} | ||
} | ||
input.prevOutScript = options.prevOutScript | ||
input.prevOutType = prevOutType || classify.output(options.prevOutScript) | ||
} | ||
const vin = this.__tx.addInput(txHash, vout, options.sequence, options.scriptSig) | ||
this.__inputs[vin] = input | ||
this.__prevTxSet[prevTxOut] = true | ||
return vin | ||
return { type }; | ||
} | ||
TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) { | ||
if (!this.__canModifyOutputs()) { | ||
throw new Error('No, this would invalidate signatures') | ||
} | ||
// Attempt to get a script if it's a base58 or bech32 address string | ||
if (typeof scriptPubKey === 'string') { | ||
scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network) | ||
} | ||
return this.__tx.addOutput(scriptPubKey, value) | ||
} | ||
TransactionBuilder.prototype.build = function () { | ||
return this.__build(false) | ||
} | ||
TransactionBuilder.prototype.buildIncomplete = function () { | ||
return this.__build(true) | ||
} | ||
TransactionBuilder.prototype.__build = function (allowIncomplete) { | ||
if (!allowIncomplete) { | ||
if (!this.__tx.ins.length) throw new Error('Transaction has no inputs') | ||
if (!this.__tx.outs.length) throw new Error('Transaction has no outputs') | ||
} | ||
const tx = this.__tx.clone() | ||
// create script signatures from inputs | ||
this.__inputs.forEach(function (input, i) { | ||
if (!input.prevOutType && !allowIncomplete) throw new Error('Transaction is not complete') | ||
const result = build(input.prevOutType, input, allowIncomplete) | ||
if (!result) { | ||
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) throw new Error('Unknown input type') | ||
if (!allowIncomplete) throw new Error('Not enough information') | ||
return | ||
function prepareInput(input, ourPubKey, redeemScript, witnessScript) { | ||
if (redeemScript && witnessScript) { | ||
const p2wsh = payments.p2wsh({ | ||
redeem: { output: witnessScript }, | ||
}); | ||
const p2wshAlt = payments.p2wsh({ output: redeemScript }); | ||
const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); | ||
const p2shAlt = payments.p2sh({ redeem: p2wsh }); | ||
// enforces P2SH(P2WSH(...)) | ||
if (!p2wsh.hash.equals(p2wshAlt.hash)) | ||
throw new Error('Witness script inconsistent with prevOutScript'); | ||
if (!p2sh.hash.equals(p2shAlt.hash)) | ||
throw new Error('Redeem script inconsistent with prevOutScript'); | ||
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); | ||
if (!expanded.pubkeys) | ||
throw new Error(expanded.type + | ||
' not supported as witnessScript (' + | ||
bscript.toASM(witnessScript) + | ||
')'); | ||
if (input.signatures && input.signatures.some(x => x !== undefined)) { | ||
expanded.signatures = input.signatures; | ||
} | ||
const signScript = witnessScript; | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) | ||
throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); | ||
return { | ||
redeemScript, | ||
redeemScriptType: SCRIPT_TYPES.P2WSH, | ||
witnessScript, | ||
witnessScriptType: expanded.type, | ||
prevOutType: SCRIPT_TYPES.P2SH, | ||
prevOutScript: p2sh.output, | ||
hasWitness: true, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures, | ||
}; | ||
} | ||
tx.setInputScript(i, result.input) | ||
tx.setWitness(i, result.witness) | ||
}) | ||
if (!allowIncomplete) { | ||
// do not rely on this, its merely a last resort | ||
if (this.__overMaximumFees(tx.virtualSize())) { | ||
throw new Error('Transaction has absurd fees') | ||
if (redeemScript) { | ||
const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); | ||
if (input.prevOutScript) { | ||
let p2shAlt; | ||
try { | ||
p2shAlt = payments.p2sh({ output: input.prevOutScript }); | ||
} | ||
catch (e) { | ||
throw new Error('PrevOutScript must be P2SH'); | ||
} | ||
if (!p2sh.hash.equals(p2shAlt.hash)) | ||
throw new Error('Redeem script inconsistent with prevOutScript'); | ||
} | ||
const expanded = expandOutput(p2sh.redeem.output, ourPubKey); | ||
if (!expanded.pubkeys) | ||
throw new Error(expanded.type + | ||
' not supported as redeemScript (' + | ||
bscript.toASM(redeemScript) + | ||
')'); | ||
if (input.signatures && input.signatures.some(x => x !== undefined)) { | ||
expanded.signatures = input.signatures; | ||
} | ||
let signScript = redeemScript; | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) { | ||
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; | ||
} | ||
return { | ||
redeemScript, | ||
redeemScriptType: expanded.type, | ||
prevOutType: SCRIPT_TYPES.P2SH, | ||
prevOutScript: p2sh.output, | ||
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures, | ||
}; | ||
} | ||
} | ||
return tx | ||
} | ||
function canSign (input) { | ||
return input.signScript !== undefined && | ||
input.signType !== undefined && | ||
input.pubkeys !== undefined && | ||
input.signatures !== undefined && | ||
input.signatures.length === input.pubkeys.length && | ||
input.pubkeys.length > 0 && | ||
( | ||
input.hasWitness === false || | ||
input.value !== undefined | ||
) | ||
} | ||
TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { | ||
// TODO: remove keyPair.network matching in 4.0.0 | ||
if (keyPair.network && keyPair.network !== this.network) throw new TypeError('Inconsistent network') | ||
if (!this.__inputs[vin]) throw new Error('No input at index: ' + vin) | ||
hashType = hashType || Transaction.SIGHASH_ALL | ||
if (this.__needsOutputs(hashType)) throw new Error('Transaction needs outputs') | ||
const input = this.__inputs[vin] | ||
// if redeemScript was previously provided, enforce consistency | ||
if (input.redeemScript !== undefined && | ||
redeemScript && | ||
!input.redeemScript.equals(redeemScript)) { | ||
throw new Error('Inconsistent redeemScript') | ||
} | ||
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey() | ||
if (!canSign(input)) { | ||
if (witnessValue !== undefined) { | ||
if (input.value !== undefined && input.value !== witnessValue) throw new Error('Input didn\'t match witnessValue') | ||
typeforce(types.Satoshi, witnessValue) | ||
input.value = witnessValue | ||
if (witnessScript) { | ||
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); | ||
if (input.prevOutScript) { | ||
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }); | ||
if (!p2wsh.hash.equals(p2wshAlt.hash)) | ||
throw new Error('Witness script inconsistent with prevOutScript'); | ||
} | ||
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); | ||
if (!expanded.pubkeys) | ||
throw new Error(expanded.type + | ||
' not supported as witnessScript (' + | ||
bscript.toASM(witnessScript) + | ||
')'); | ||
if (input.signatures && input.signatures.some(x => x !== undefined)) { | ||
expanded.signatures = input.signatures; | ||
} | ||
const signScript = witnessScript; | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) | ||
throw new Error('P2WSH(P2WPKH) is a consensus failure'); | ||
return { | ||
witnessScript, | ||
witnessScriptType: expanded.type, | ||
prevOutType: SCRIPT_TYPES.P2WSH, | ||
prevOutScript: p2wsh.output, | ||
hasWitness: true, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures, | ||
}; | ||
} | ||
if (!canSign(input)) { | ||
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript) | ||
// updates inline | ||
Object.assign(input, prepared) | ||
if (input.prevOutType && input.prevOutScript) { | ||
// embedded scripts are not possible without extra information | ||
if (input.prevOutType === SCRIPT_TYPES.P2SH) | ||
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript'); | ||
if (input.prevOutType === SCRIPT_TYPES.P2WSH) | ||
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript'); | ||
if (!input.prevOutScript) | ||
throw new Error('PrevOutScript is missing'); | ||
const expanded = expandOutput(input.prevOutScript, ourPubKey); | ||
if (!expanded.pubkeys) | ||
throw new Error(expanded.type + | ||
' not supported (' + | ||
bscript.toASM(input.prevOutScript) + | ||
')'); | ||
if (input.signatures && input.signatures.some(x => x !== undefined)) { | ||
expanded.signatures = input.signatures; | ||
} | ||
let signScript = input.prevOutScript; | ||
if (expanded.type === SCRIPT_TYPES.P2WPKH) { | ||
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }) | ||
.output; | ||
} | ||
return { | ||
prevOutType: expanded.type, | ||
prevOutScript: input.prevOutScript, | ||
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, | ||
signScript, | ||
signType: expanded.type, | ||
pubkeys: expanded.pubkeys, | ||
signatures: expanded.signatures, | ||
maxSignatures: expanded.maxSignatures, | ||
}; | ||
} | ||
if (!canSign(input)) throw Error(input.prevOutType + ' not supported') | ||
} | ||
// ready to sign | ||
let signatureHash | ||
if (input.hasWitness) { | ||
signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType) | ||
} else { | ||
signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType) | ||
} | ||
// enforce in order signing of public keys | ||
const signed = input.pubkeys.some(function (pubKey, i) { | ||
if (!ourPubKey.equals(pubKey)) return false | ||
if (input.signatures[i]) throw new Error('Signature already exists') | ||
// TODO: add tests | ||
if (ourPubKey.length !== 33 && input.hasWitness) { | ||
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH') | ||
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; | ||
return { | ||
prevOutType: SCRIPT_TYPES.P2PKH, | ||
prevOutScript, | ||
hasWitness: false, | ||
signScript: prevOutScript, | ||
signType: SCRIPT_TYPES.P2PKH, | ||
pubkeys: [ourPubKey], | ||
signatures: [undefined], | ||
}; | ||
} | ||
function build(type, input, allowIncomplete) { | ||
const pubkeys = (input.pubkeys || []); | ||
let signatures = (input.signatures || []); | ||
switch (type) { | ||
case SCRIPT_TYPES.P2PKH: { | ||
if (pubkeys.length === 0) | ||
break; | ||
if (signatures.length === 0) | ||
break; | ||
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); | ||
} | ||
case SCRIPT_TYPES.P2WPKH: { | ||
if (pubkeys.length === 0) | ||
break; | ||
if (signatures.length === 0) | ||
break; | ||
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); | ||
} | ||
case SCRIPT_TYPES.P2PK: { | ||
if (pubkeys.length === 0) | ||
break; | ||
if (signatures.length === 0) | ||
break; | ||
return payments.p2pk({ signature: signatures[0] }); | ||
} | ||
case SCRIPT_TYPES.P2MS: { | ||
const m = input.maxSignatures; | ||
if (allowIncomplete) { | ||
signatures = signatures.map(x => x || script_1.OPS.OP_0); | ||
} | ||
else { | ||
signatures = signatures.filter(x => x); | ||
} | ||
// if the transaction is not not complete (complete), or if signatures.length === m, validate | ||
// otherwise, the number of OP_0's may be >= m, so don't validate (boo) | ||
const validate = !allowIncomplete || m === signatures.length; | ||
return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }); | ||
} | ||
case SCRIPT_TYPES.P2SH: { | ||
const redeem = build(input.redeemScriptType, input, allowIncomplete); | ||
if (!redeem) | ||
return; | ||
return payments.p2sh({ | ||
redeem: { | ||
output: redeem.output || input.redeemScript, | ||
input: redeem.input, | ||
witness: redeem.witness, | ||
}, | ||
}); | ||
} | ||
case SCRIPT_TYPES.P2WSH: { | ||
const redeem = build(input.witnessScriptType, input, allowIncomplete); | ||
if (!redeem) | ||
return; | ||
return payments.p2wsh({ | ||
redeem: { | ||
output: input.witnessScript, | ||
input: redeem.input, | ||
witness: redeem.witness, | ||
}, | ||
}); | ||
} | ||
} | ||
const signature = keyPair.sign(signatureHash) | ||
input.signatures[i] = bscript.signature.encode(signature, hashType) | ||
return true | ||
}) | ||
if (!signed) throw new Error('Key pair cannot sign for this input') | ||
} | ||
function signatureHashType (buffer) { | ||
return buffer.readUInt8(buffer.length - 1) | ||
function canSign(input) { | ||
return (input.signScript !== undefined && | ||
input.signType !== undefined && | ||
input.pubkeys !== undefined && | ||
input.signatures !== undefined && | ||
input.signatures.length === input.pubkeys.length && | ||
input.pubkeys.length > 0 && | ||
(input.hasWitness === false || input.value !== undefined)); | ||
} | ||
TransactionBuilder.prototype.__canModifyInputs = function () { | ||
return this.__inputs.every(function (input) { | ||
if (!input.signatures) return true | ||
return input.signatures.every(function (signature) { | ||
if (!signature) return true | ||
const hashType = signatureHashType(signature) | ||
// if SIGHASH_ANYONECANPAY is set, signatures would not | ||
// be invalidated by more inputs | ||
return hashType & Transaction.SIGHASH_ANYONECANPAY | ||
}) | ||
}) | ||
function signatureHashType(buffer) { | ||
return buffer.readUInt8(buffer.length - 1); | ||
} | ||
TransactionBuilder.prototype.__needsOutputs = function (signingHashType) { | ||
if (signingHashType === Transaction.SIGHASH_ALL) { | ||
return this.__tx.outs.length === 0 | ||
} | ||
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs | ||
// .build() will fail, but .buildIncomplete() is OK | ||
return (this.__tx.outs.length === 0) && this.__inputs.some((input) => { | ||
if (!input.signatures) return false | ||
return input.signatures.some((signature) => { | ||
if (!signature) return false // no signature, no issue | ||
const hashType = signatureHashType(signature) | ||
if (hashType & Transaction.SIGHASH_NONE) return false // SIGHASH_NONE doesn't care about outputs | ||
return true // SIGHASH_* does care | ||
}) | ||
}) | ||
} | ||
TransactionBuilder.prototype.__canModifyOutputs = function () { | ||
const nInputs = this.__tx.ins.length | ||
const nOutputs = this.__tx.outs.length | ||
return this.__inputs.every(function (input) { | ||
if (input.signatures === undefined) return true | ||
return input.signatures.every(function (signature) { | ||
if (!signature) return true | ||
const hashType = signatureHashType(signature) | ||
const hashTypeMod = hashType & 0x1f | ||
if (hashTypeMod === Transaction.SIGHASH_NONE) return true | ||
if (hashTypeMod === Transaction.SIGHASH_SINGLE) { | ||
// if SIGHASH_SINGLE is set, and nInputs > nOutputs | ||
// some signatures would be invalidated by the addition | ||
// of more outputs | ||
return nInputs <= nOutputs | ||
} | ||
}) | ||
}) | ||
} | ||
TransactionBuilder.prototype.__overMaximumFees = function (bytes) { | ||
// not all inputs will have .value defined | ||
const incoming = this.__inputs.reduce(function (a, x) { return a + (x.value >>> 0) }, 0) | ||
// but all outputs do, and if we have any input value | ||
// we can immediately determine if the outputs are too small | ||
const outgoing = this.__tx.outs.reduce(function (a, x) { return a + x.value }, 0) | ||
const fee = incoming - outgoing | ||
const feeRate = fee / bytes | ||
return feeRate > this.maximumFeeRate | ||
} | ||
module.exports = TransactionBuilder |
@@ -1,492 +0,452 @@ | ||
const Buffer = require('safe-buffer').Buffer | ||
const bcrypto = require('./crypto') | ||
const bscript = require('./script') | ||
const bufferutils = require('./bufferutils') | ||
const opcodes = require('bitcoin-ops') | ||
const typeforce = require('typeforce') | ||
const types = require('./types') | ||
const varuint = require('varuint-bitcoin') | ||
function varSliceSize (someScript) { | ||
const length = someScript.length | ||
return varuint.encodingLength(length) + length | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bufferutils = require("./bufferutils"); | ||
const bufferutils_1 = require("./bufferutils"); | ||
const bcrypto = require("./crypto"); | ||
const bscript = require("./script"); | ||
const script_1 = require("./script"); | ||
const types = require("./types"); | ||
const typeforce = require('typeforce'); | ||
const varuint = require('varuint-bitcoin'); | ||
function varSliceSize(someScript) { | ||
const length = someScript.length; | ||
return varuint.encodingLength(length) + length; | ||
} | ||
function vectorSize (someVector) { | ||
const length = someVector.length | ||
return varuint.encodingLength(length) + someVector.reduce(function (sum, witness) { | ||
return sum + varSliceSize(witness) | ||
}, 0) | ||
function vectorSize(someVector) { | ||
const length = someVector.length; | ||
return (varuint.encodingLength(length) + | ||
someVector.reduce((sum, witness) => { | ||
return sum + varSliceSize(witness); | ||
}, 0)); | ||
} | ||
function Transaction () { | ||
this.version = 1 | ||
this.locktime = 0 | ||
this.ins = [] | ||
this.outs = [] | ||
} | ||
Transaction.DEFAULT_SEQUENCE = 0xffffffff | ||
Transaction.SIGHASH_ALL = 0x01 | ||
Transaction.SIGHASH_NONE = 0x02 | ||
Transaction.SIGHASH_SINGLE = 0x03 | ||
Transaction.SIGHASH_ANYONECANPAY = 0x80 | ||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00 | ||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01 | ||
const EMPTY_SCRIPT = Buffer.allocUnsafe(0) | ||
const EMPTY_WITNESS = [] | ||
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') | ||
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex') | ||
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex') | ||
const EMPTY_SCRIPT = Buffer.allocUnsafe(0); | ||
const EMPTY_WITNESS = []; | ||
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); | ||
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); | ||
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); | ||
const BLANK_OUTPUT = { | ||
script: EMPTY_SCRIPT, | ||
valueBuffer: VALUE_UINT64_MAX | ||
script: EMPTY_SCRIPT, | ||
valueBuffer: VALUE_UINT64_MAX, | ||
}; | ||
function isOutput(out) { | ||
return out.value !== undefined; | ||
} | ||
Transaction.fromBuffer = function (buffer, __noStrict) { | ||
let offset = 0 | ||
function readSlice (n) { | ||
offset += n | ||
return buffer.slice(offset - n, offset) | ||
} | ||
function readUInt32 () { | ||
const i = buffer.readUInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
function readInt32 () { | ||
const i = buffer.readInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
function readUInt64 () { | ||
const i = bufferutils.readUInt64LE(buffer, offset) | ||
offset += 8 | ||
return i | ||
} | ||
function readVarInt () { | ||
const vi = varuint.decode(buffer, offset) | ||
offset += varuint.decode.bytes | ||
return vi | ||
} | ||
function readVarSlice () { | ||
return readSlice(readVarInt()) | ||
} | ||
function readVector () { | ||
const count = readVarInt() | ||
const vector = [] | ||
for (var i = 0; i < count; i++) vector.push(readVarSlice()) | ||
return vector | ||
} | ||
const tx = new Transaction() | ||
tx.version = readInt32() | ||
const marker = buffer.readUInt8(offset) | ||
const flag = buffer.readUInt8(offset + 1) | ||
let hasWitnesses = false | ||
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && | ||
flag === Transaction.ADVANCED_TRANSACTION_FLAG) { | ||
offset += 2 | ||
hasWitnesses = true | ||
} | ||
const vinLen = readVarInt() | ||
for (var i = 0; i < vinLen; ++i) { | ||
tx.ins.push({ | ||
hash: readSlice(32), | ||
index: readUInt32(), | ||
script: readVarSlice(), | ||
sequence: readUInt32(), | ||
witness: EMPTY_WITNESS | ||
}) | ||
} | ||
const voutLen = readVarInt() | ||
for (i = 0; i < voutLen; ++i) { | ||
tx.outs.push({ | ||
value: readUInt64(), | ||
script: readVarSlice() | ||
}) | ||
} | ||
if (hasWitnesses) { | ||
for (i = 0; i < vinLen; ++i) { | ||
tx.ins[i].witness = readVector() | ||
class Transaction { | ||
constructor() { | ||
this.version = 1; | ||
this.locktime = 0; | ||
this.ins = []; | ||
this.outs = []; | ||
} | ||
// was this pointless? | ||
if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data') | ||
} | ||
tx.locktime = readUInt32() | ||
if (__noStrict) return tx | ||
if (offset !== buffer.length) throw new Error('Transaction has unexpected data') | ||
return tx | ||
} | ||
Transaction.fromHex = function (hex) { | ||
return Transaction.fromBuffer(Buffer.from(hex, 'hex')) | ||
} | ||
Transaction.isCoinbaseHash = function (buffer) { | ||
typeforce(types.Hash256bit, buffer) | ||
for (var i = 0; i < 32; ++i) { | ||
if (buffer[i] !== 0) return false | ||
} | ||
return true | ||
} | ||
Transaction.prototype.isCoinbase = function () { | ||
return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) | ||
} | ||
Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) { | ||
typeforce(types.tuple( | ||
types.Hash256bit, | ||
types.UInt32, | ||
types.maybe(types.UInt32), | ||
types.maybe(types.Buffer) | ||
), arguments) | ||
if (types.Null(sequence)) { | ||
sequence = Transaction.DEFAULT_SEQUENCE | ||
} | ||
// Add the input and return the input's index | ||
return (this.ins.push({ | ||
hash: hash, | ||
index: index, | ||
script: scriptSig || EMPTY_SCRIPT, | ||
sequence: sequence, | ||
witness: EMPTY_WITNESS | ||
}) - 1) | ||
} | ||
Transaction.prototype.addOutput = function (scriptPubKey, value) { | ||
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments) | ||
// Add the output and return the output's index | ||
return (this.outs.push({ | ||
script: scriptPubKey, | ||
value: value | ||
}) - 1) | ||
} | ||
Transaction.prototype.hasWitnesses = function () { | ||
return this.ins.some(function (x) { | ||
return x.witness.length !== 0 | ||
}) | ||
} | ||
Transaction.prototype.weight = function () { | ||
const base = this.__byteLength(false) | ||
const total = this.__byteLength(true) | ||
return base * 3 + total | ||
} | ||
Transaction.prototype.virtualSize = function () { | ||
return Math.ceil(this.weight() / 4) | ||
} | ||
Transaction.prototype.byteLength = function () { | ||
return this.__byteLength(true) | ||
} | ||
Transaction.prototype.__byteLength = function (__allowWitness) { | ||
const hasWitnesses = __allowWitness && this.hasWitnesses() | ||
return ( | ||
(hasWitnesses ? 10 : 8) + | ||
varuint.encodingLength(this.ins.length) + | ||
varuint.encodingLength(this.outs.length) + | ||
this.ins.reduce(function (sum, input) { return sum + 40 + varSliceSize(input.script) }, 0) + | ||
this.outs.reduce(function (sum, output) { return sum + 8 + varSliceSize(output.script) }, 0) + | ||
(hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0) | ||
) | ||
} | ||
Transaction.prototype.clone = function () { | ||
const newTx = new Transaction() | ||
newTx.version = this.version | ||
newTx.locktime = this.locktime | ||
newTx.ins = this.ins.map(function (txIn) { | ||
return { | ||
hash: txIn.hash, | ||
index: txIn.index, | ||
script: txIn.script, | ||
sequence: txIn.sequence, | ||
witness: txIn.witness | ||
static fromBuffer(buffer, _NO_STRICT) { | ||
let offset = 0; | ||
function readSlice(n) { | ||
offset += n; | ||
return buffer.slice(offset - n, offset); | ||
} | ||
function readUInt32() { | ||
const i = buffer.readUInt32LE(offset); | ||
offset += 4; | ||
return i; | ||
} | ||
function readInt32() { | ||
const i = buffer.readInt32LE(offset); | ||
offset += 4; | ||
return i; | ||
} | ||
function readUInt64() { | ||
const i = bufferutils.readUInt64LE(buffer, offset); | ||
offset += 8; | ||
return i; | ||
} | ||
function readVarInt() { | ||
const vi = varuint.decode(buffer, offset); | ||
offset += varuint.decode.bytes; | ||
return vi; | ||
} | ||
function readVarSlice() { | ||
return readSlice(readVarInt()); | ||
} | ||
function readVector() { | ||
const count = readVarInt(); | ||
const vector = []; | ||
for (let i = 0; i < count; i++) | ||
vector.push(readVarSlice()); | ||
return vector; | ||
} | ||
const tx = new Transaction(); | ||
tx.version = readInt32(); | ||
const marker = buffer.readUInt8(offset); | ||
const flag = buffer.readUInt8(offset + 1); | ||
let hasWitnesses = false; | ||
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && | ||
flag === Transaction.ADVANCED_TRANSACTION_FLAG) { | ||
offset += 2; | ||
hasWitnesses = true; | ||
} | ||
const vinLen = readVarInt(); | ||
for (let i = 0; i < vinLen; ++i) { | ||
tx.ins.push({ | ||
hash: readSlice(32), | ||
index: readUInt32(), | ||
script: readVarSlice(), | ||
sequence: readUInt32(), | ||
witness: EMPTY_WITNESS, | ||
}); | ||
} | ||
const voutLen = readVarInt(); | ||
for (let i = 0; i < voutLen; ++i) { | ||
tx.outs.push({ | ||
value: readUInt64(), | ||
script: readVarSlice(), | ||
}); | ||
} | ||
if (hasWitnesses) { | ||
for (let i = 0; i < vinLen; ++i) { | ||
tx.ins[i].witness = readVector(); | ||
} | ||
// was this pointless? | ||
if (!tx.hasWitnesses()) | ||
throw new Error('Transaction has superfluous witness data'); | ||
} | ||
tx.locktime = readUInt32(); | ||
if (_NO_STRICT) | ||
return tx; | ||
if (offset !== buffer.length) | ||
throw new Error('Transaction has unexpected data'); | ||
return tx; | ||
} | ||
}) | ||
newTx.outs = this.outs.map(function (txOut) { | ||
return { | ||
script: txOut.script, | ||
value: txOut.value | ||
static fromHex(hex) { | ||
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); | ||
} | ||
}) | ||
return newTx | ||
} | ||
/** | ||
* Hash transaction for signing a specific input. | ||
* | ||
* Bitcoin uses a different hash for each signed transaction input. | ||
* This method copies the transaction, makes the necessary changes based on the | ||
* hashType, and then hashes the result. | ||
* This hash can then be used to sign the provided transaction input. | ||
*/ | ||
Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) { | ||
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments) | ||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 | ||
if (inIndex >= this.ins.length) return ONE | ||
// ignore OP_CODESEPARATOR | ||
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) { | ||
return x !== opcodes.OP_CODESEPARATOR | ||
})) | ||
const txTmp = this.clone() | ||
// SIGHASH_NONE: ignore all outputs? (wildcard payee) | ||
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { | ||
txTmp.outs = [] | ||
// ignore sequence numbers (except at inIndex) | ||
txTmp.ins.forEach(function (input, i) { | ||
if (i === inIndex) return | ||
input.sequence = 0 | ||
}) | ||
// SIGHASH_SINGLE: ignore all outputs, except at the same index? | ||
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { | ||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 | ||
if (inIndex >= this.outs.length) return ONE | ||
// truncate outputs after | ||
txTmp.outs.length = inIndex + 1 | ||
// "blank" outputs before | ||
for (var i = 0; i < inIndex; i++) { | ||
txTmp.outs[i] = BLANK_OUTPUT | ||
static isCoinbaseHash(buffer) { | ||
typeforce(types.Hash256bit, buffer); | ||
for (let i = 0; i < 32; ++i) { | ||
if (buffer[i] !== 0) | ||
return false; | ||
} | ||
return true; | ||
} | ||
// ignore sequence numbers (except at inIndex) | ||
txTmp.ins.forEach(function (input, y) { | ||
if (y === inIndex) return | ||
input.sequence = 0 | ||
}) | ||
} | ||
// SIGHASH_ANYONECANPAY: ignore inputs entirely? | ||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) { | ||
txTmp.ins = [txTmp.ins[inIndex]] | ||
txTmp.ins[0].script = ourScript | ||
// SIGHASH_ALL: only ignore input scripts | ||
} else { | ||
// "blank" others input scripts | ||
txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT }) | ||
txTmp.ins[inIndex].script = ourScript | ||
} | ||
// serialize and hash | ||
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4) | ||
buffer.writeInt32LE(hashType, buffer.length - 4) | ||
txTmp.__toBuffer(buffer, 0, false) | ||
return bcrypto.hash256(buffer) | ||
} | ||
Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) { | ||
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments) | ||
let tbuffer, toffset | ||
function writeSlice (slice) { toffset += slice.copy(tbuffer, toffset) } | ||
function writeUInt32 (i) { toffset = tbuffer.writeUInt32LE(i, toffset) } | ||
function writeUInt64 (i) { toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) } | ||
function writeVarInt (i) { | ||
varuint.encode(i, tbuffer, toffset) | ||
toffset += varuint.encode.bytes | ||
} | ||
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) } | ||
let hashOutputs = ZERO | ||
let hashPrevouts = ZERO | ||
let hashSequence = ZERO | ||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { | ||
tbuffer = Buffer.allocUnsafe(36 * this.ins.length) | ||
toffset = 0 | ||
this.ins.forEach(function (txIn) { | ||
writeSlice(txIn.hash) | ||
writeUInt32(txIn.index) | ||
}) | ||
hashPrevouts = bcrypto.hash256(tbuffer) | ||
} | ||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && | ||
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && | ||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) { | ||
tbuffer = Buffer.allocUnsafe(4 * this.ins.length) | ||
toffset = 0 | ||
this.ins.forEach(function (txIn) { | ||
writeUInt32(txIn.sequence) | ||
}) | ||
hashSequence = bcrypto.hash256(tbuffer) | ||
} | ||
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && | ||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) { | ||
const txOutsSize = this.outs.reduce(function (sum, output) { | ||
return sum + 8 + varSliceSize(output.script) | ||
}, 0) | ||
tbuffer = Buffer.allocUnsafe(txOutsSize) | ||
toffset = 0 | ||
this.outs.forEach(function (out) { | ||
writeUInt64(out.value) | ||
writeVarSlice(out.script) | ||
}) | ||
hashOutputs = bcrypto.hash256(tbuffer) | ||
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { | ||
const output = this.outs[inIndex] | ||
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)) | ||
toffset = 0 | ||
writeUInt64(output.value) | ||
writeVarSlice(output.script) | ||
hashOutputs = bcrypto.hash256(tbuffer) | ||
} | ||
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)) | ||
toffset = 0 | ||
const input = this.ins[inIndex] | ||
writeUInt32(this.version) | ||
writeSlice(hashPrevouts) | ||
writeSlice(hashSequence) | ||
writeSlice(input.hash) | ||
writeUInt32(input.index) | ||
writeVarSlice(prevOutScript) | ||
writeUInt64(value) | ||
writeUInt32(input.sequence) | ||
writeSlice(hashOutputs) | ||
writeUInt32(this.locktime) | ||
writeUInt32(hashType) | ||
return bcrypto.hash256(tbuffer) | ||
} | ||
Transaction.prototype.getHash = function () { | ||
return bcrypto.hash256(this.__toBuffer(undefined, undefined, false)) | ||
} | ||
Transaction.prototype.getId = function () { | ||
// transaction hash's are displayed in reverse order | ||
return this.getHash().reverse().toString('hex') | ||
} | ||
Transaction.prototype.toBuffer = function (buffer, initialOffset) { | ||
return this.__toBuffer(buffer, initialOffset, true) | ||
} | ||
Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitness) { | ||
if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness)) | ||
let offset = initialOffset || 0 | ||
function writeSlice (slice) { offset += slice.copy(buffer, offset) } | ||
function writeUInt8 (i) { offset = buffer.writeUInt8(i, offset) } | ||
function writeUInt32 (i) { offset = buffer.writeUInt32LE(i, offset) } | ||
function writeInt32 (i) { offset = buffer.writeInt32LE(i, offset) } | ||
function writeUInt64 (i) { offset = bufferutils.writeUInt64LE(buffer, i, offset) } | ||
function writeVarInt (i) { | ||
varuint.encode(i, buffer, offset) | ||
offset += varuint.encode.bytes | ||
} | ||
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) } | ||
function writeVector (vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice) } | ||
writeInt32(this.version) | ||
const hasWitnesses = __allowWitness && this.hasWitnesses() | ||
if (hasWitnesses) { | ||
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER) | ||
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) | ||
} | ||
writeVarInt(this.ins.length) | ||
this.ins.forEach(function (txIn) { | ||
writeSlice(txIn.hash) | ||
writeUInt32(txIn.index) | ||
writeVarSlice(txIn.script) | ||
writeUInt32(txIn.sequence) | ||
}) | ||
writeVarInt(this.outs.length) | ||
this.outs.forEach(function (txOut) { | ||
if (!txOut.valueBuffer) { | ||
writeUInt64(txOut.value) | ||
} else { | ||
writeSlice(txOut.valueBuffer) | ||
isCoinbase() { | ||
return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)); | ||
} | ||
writeVarSlice(txOut.script) | ||
}) | ||
if (hasWitnesses) { | ||
this.ins.forEach(function (input) { | ||
writeVector(input.witness) | ||
}) | ||
} | ||
writeUInt32(this.locktime) | ||
// avoid slicing unless necessary | ||
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) | ||
return buffer | ||
addInput(hash, index, sequence, scriptSig) { | ||
typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer)), arguments); | ||
if (types.Null(sequence)) { | ||
sequence = Transaction.DEFAULT_SEQUENCE; | ||
} | ||
// Add the input and return the input's index | ||
return (this.ins.push({ | ||
hash, | ||
index, | ||
script: scriptSig || EMPTY_SCRIPT, | ||
sequence: sequence, | ||
witness: EMPTY_WITNESS, | ||
}) - 1); | ||
} | ||
addOutput(scriptPubKey, value) { | ||
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); | ||
// Add the output and return the output's index | ||
return (this.outs.push({ | ||
script: scriptPubKey, | ||
value, | ||
}) - 1); | ||
} | ||
hasWitnesses() { | ||
return this.ins.some(x => { | ||
return x.witness.length !== 0; | ||
}); | ||
} | ||
weight() { | ||
const base = this.__byteLength(false); | ||
const total = this.__byteLength(true); | ||
return base * 3 + total; | ||
} | ||
virtualSize() { | ||
return Math.ceil(this.weight() / 4); | ||
} | ||
byteLength() { | ||
return this.__byteLength(true); | ||
} | ||
clone() { | ||
const newTx = new Transaction(); | ||
newTx.version = this.version; | ||
newTx.locktime = this.locktime; | ||
newTx.ins = this.ins.map(txIn => { | ||
return { | ||
hash: txIn.hash, | ||
index: txIn.index, | ||
script: txIn.script, | ||
sequence: txIn.sequence, | ||
witness: txIn.witness, | ||
}; | ||
}); | ||
newTx.outs = this.outs.map(txOut => { | ||
return { | ||
script: txOut.script, | ||
value: txOut.value, | ||
}; | ||
}); | ||
return newTx; | ||
} | ||
/** | ||
* Hash transaction for signing a specific input. | ||
* | ||
* Bitcoin uses a different hash for each signed transaction input. | ||
* This method copies the transaction, makes the necessary changes based on the | ||
* hashType, and then hashes the result. | ||
* This hash can then be used to sign the provided transaction input. | ||
*/ | ||
hashForSignature(inIndex, prevOutScript, hashType) { | ||
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments); | ||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 | ||
if (inIndex >= this.ins.length) | ||
return ONE; | ||
// ignore OP_CODESEPARATOR | ||
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => { | ||
return x !== script_1.OPS.OP_CODESEPARATOR; | ||
})); | ||
const txTmp = this.clone(); | ||
// SIGHASH_NONE: ignore all outputs? (wildcard payee) | ||
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { | ||
txTmp.outs = []; | ||
// ignore sequence numbers (except at inIndex) | ||
txTmp.ins.forEach((input, i) => { | ||
if (i === inIndex) | ||
return; | ||
input.sequence = 0; | ||
}); | ||
// SIGHASH_SINGLE: ignore all outputs, except at the same index? | ||
} | ||
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { | ||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 | ||
if (inIndex >= this.outs.length) | ||
return ONE; | ||
// truncate outputs after | ||
txTmp.outs.length = inIndex + 1; | ||
// "blank" outputs before | ||
for (let i = 0; i < inIndex; i++) { | ||
txTmp.outs[i] = BLANK_OUTPUT; | ||
} | ||
// ignore sequence numbers (except at inIndex) | ||
txTmp.ins.forEach((input, y) => { | ||
if (y === inIndex) | ||
return; | ||
input.sequence = 0; | ||
}); | ||
} | ||
// SIGHASH_ANYONECANPAY: ignore inputs entirely? | ||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) { | ||
txTmp.ins = [txTmp.ins[inIndex]]; | ||
txTmp.ins[0].script = ourScript; | ||
// SIGHASH_ALL: only ignore input scripts | ||
} | ||
else { | ||
// "blank" others input scripts | ||
txTmp.ins.forEach(input => { | ||
input.script = EMPTY_SCRIPT; | ||
}); | ||
txTmp.ins[inIndex].script = ourScript; | ||
} | ||
// serialize and hash | ||
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); | ||
buffer.writeInt32LE(hashType, buffer.length - 4); | ||
txTmp.__toBuffer(buffer, 0, false); | ||
return bcrypto.hash256(buffer); | ||
} | ||
hashForWitnessV0(inIndex, prevOutScript, value, hashType) { | ||
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments); | ||
let tbuffer = Buffer.from([]); | ||
let toffset = 0; | ||
function writeSlice(slice) { | ||
toffset += slice.copy(tbuffer, toffset); | ||
} | ||
function writeUInt32(i) { | ||
toffset = tbuffer.writeUInt32LE(i, toffset); | ||
} | ||
function writeUInt64(i) { | ||
toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); | ||
} | ||
function writeVarInt(i) { | ||
varuint.encode(i, tbuffer, toffset); | ||
toffset += varuint.encode.bytes; | ||
} | ||
function writeVarSlice(slice) { | ||
writeVarInt(slice.length); | ||
writeSlice(slice); | ||
} | ||
let hashOutputs = ZERO; | ||
let hashPrevouts = ZERO; | ||
let hashSequence = ZERO; | ||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { | ||
tbuffer = Buffer.allocUnsafe(36 * this.ins.length); | ||
toffset = 0; | ||
this.ins.forEach(txIn => { | ||
writeSlice(txIn.hash); | ||
writeUInt32(txIn.index); | ||
}); | ||
hashPrevouts = bcrypto.hash256(tbuffer); | ||
} | ||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && | ||
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && | ||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) { | ||
tbuffer = Buffer.allocUnsafe(4 * this.ins.length); | ||
toffset = 0; | ||
this.ins.forEach(txIn => { | ||
writeUInt32(txIn.sequence); | ||
}); | ||
hashSequence = bcrypto.hash256(tbuffer); | ||
} | ||
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && | ||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) { | ||
const txOutsSize = this.outs.reduce((sum, output) => { | ||
return sum + 8 + varSliceSize(output.script); | ||
}, 0); | ||
tbuffer = Buffer.allocUnsafe(txOutsSize); | ||
toffset = 0; | ||
this.outs.forEach(out => { | ||
writeUInt64(out.value); | ||
writeVarSlice(out.script); | ||
}); | ||
hashOutputs = bcrypto.hash256(tbuffer); | ||
} | ||
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && | ||
inIndex < this.outs.length) { | ||
const output = this.outs[inIndex]; | ||
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); | ||
toffset = 0; | ||
writeUInt64(output.value); | ||
writeVarSlice(output.script); | ||
hashOutputs = bcrypto.hash256(tbuffer); | ||
} | ||
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)); | ||
toffset = 0; | ||
const input = this.ins[inIndex]; | ||
writeUInt32(this.version); | ||
writeSlice(hashPrevouts); | ||
writeSlice(hashSequence); | ||
writeSlice(input.hash); | ||
writeUInt32(input.index); | ||
writeVarSlice(prevOutScript); | ||
writeUInt64(value); | ||
writeUInt32(input.sequence); | ||
writeSlice(hashOutputs); | ||
writeUInt32(this.locktime); | ||
writeUInt32(hashType); | ||
return bcrypto.hash256(tbuffer); | ||
} | ||
getHash(forWitness) { | ||
// wtxid for coinbase is always 32 bytes of 0x00 | ||
if (forWitness && this.isCoinbase()) | ||
return Buffer.alloc(32, 0); | ||
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); | ||
} | ||
getId() { | ||
// transaction hash's are displayed in reverse order | ||
return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); | ||
} | ||
toBuffer(buffer, initialOffset) { | ||
return this.__toBuffer(buffer, initialOffset, true); | ||
} | ||
toHex() { | ||
return this.toBuffer(undefined, undefined).toString('hex'); | ||
} | ||
setInputScript(index, scriptSig) { | ||
typeforce(types.tuple(types.Number, types.Buffer), arguments); | ||
this.ins[index].script = scriptSig; | ||
} | ||
setWitness(index, witness) { | ||
typeforce(types.tuple(types.Number, [types.Buffer]), arguments); | ||
this.ins[index].witness = witness; | ||
} | ||
__byteLength(_ALLOW_WITNESS) { | ||
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); | ||
return ((hasWitnesses ? 10 : 8) + | ||
varuint.encodingLength(this.ins.length) + | ||
varuint.encodingLength(this.outs.length) + | ||
this.ins.reduce((sum, input) => { | ||
return sum + 40 + varSliceSize(input.script); | ||
}, 0) + | ||
this.outs.reduce((sum, output) => { | ||
return sum + 8 + varSliceSize(output.script); | ||
}, 0) + | ||
(hasWitnesses | ||
? this.ins.reduce((sum, input) => { | ||
return sum + vectorSize(input.witness); | ||
}, 0) | ||
: 0)); | ||
} | ||
__toBuffer(buffer, initialOffset, _ALLOW_WITNESS) { | ||
if (!buffer) | ||
buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS)); | ||
let offset = initialOffset || 0; | ||
function writeSlice(slice) { | ||
offset += slice.copy(buffer, offset); | ||
} | ||
function writeUInt8(i) { | ||
offset = buffer.writeUInt8(i, offset); | ||
} | ||
function writeUInt32(i) { | ||
offset = buffer.writeUInt32LE(i, offset); | ||
} | ||
function writeInt32(i) { | ||
offset = buffer.writeInt32LE(i, offset); | ||
} | ||
function writeUInt64(i) { | ||
offset = bufferutils.writeUInt64LE(buffer, i, offset); | ||
} | ||
function writeVarInt(i) { | ||
varuint.encode(i, buffer, offset); | ||
offset += varuint.encode.bytes; | ||
} | ||
function writeVarSlice(slice) { | ||
writeVarInt(slice.length); | ||
writeSlice(slice); | ||
} | ||
function writeVector(vector) { | ||
writeVarInt(vector.length); | ||
vector.forEach(writeVarSlice); | ||
} | ||
writeInt32(this.version); | ||
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); | ||
if (hasWitnesses) { | ||
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); | ||
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); | ||
} | ||
writeVarInt(this.ins.length); | ||
this.ins.forEach(txIn => { | ||
writeSlice(txIn.hash); | ||
writeUInt32(txIn.index); | ||
writeVarSlice(txIn.script); | ||
writeUInt32(txIn.sequence); | ||
}); | ||
writeVarInt(this.outs.length); | ||
this.outs.forEach(txOut => { | ||
if (isOutput(txOut)) { | ||
writeUInt64(txOut.value); | ||
} | ||
else { | ||
writeSlice(txOut.valueBuffer); | ||
} | ||
writeVarSlice(txOut.script); | ||
}); | ||
if (hasWitnesses) { | ||
this.ins.forEach(input => { | ||
writeVector(input.witness); | ||
}); | ||
} | ||
writeUInt32(this.locktime); | ||
// avoid slicing unless necessary | ||
if (initialOffset !== undefined) | ||
return buffer.slice(initialOffset, offset); | ||
return buffer; | ||
} | ||
} | ||
Transaction.prototype.toHex = function () { | ||
return this.toBuffer().toString('hex') | ||
} | ||
Transaction.prototype.setInputScript = function (index, scriptSig) { | ||
typeforce(types.tuple(types.Number, types.Buffer), arguments) | ||
this.ins[index].script = scriptSig | ||
} | ||
Transaction.prototype.setWitness = function (index, witness) { | ||
typeforce(types.tuple(types.Number, [types.Buffer]), arguments) | ||
this.ins[index].witness = witness | ||
} | ||
module.exports = Transaction | ||
Transaction.DEFAULT_SEQUENCE = 0xffffffff; | ||
Transaction.SIGHASH_ALL = 0x01; | ||
Transaction.SIGHASH_NONE = 0x02; | ||
Transaction.SIGHASH_SINGLE = 0x03; | ||
Transaction.SIGHASH_ANYONECANPAY = 0x80; | ||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00; | ||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01; | ||
exports.Transaction = Transaction; |
@@ -1,49 +0,50 @@ | ||
const typeforce = require('typeforce') | ||
const UINT31_MAX = Math.pow(2, 31) - 1 | ||
function UInt31 (value) { | ||
return typeforce.UInt32(value) && value <= UINT31_MAX | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const typeforce = require('typeforce'); | ||
const UINT31_MAX = Math.pow(2, 31) - 1; | ||
function UInt31(value) { | ||
return typeforce.UInt32(value) && value <= UINT31_MAX; | ||
} | ||
function BIP32Path (value) { | ||
return typeforce.String(value) && value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) | ||
exports.UInt31 = UInt31; | ||
function BIP32Path(value) { | ||
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); | ||
} | ||
BIP32Path.toJSON = function () { return 'BIP32 derivation path' } | ||
const SATOSHI_MAX = 21 * 1e14 | ||
function Satoshi (value) { | ||
return typeforce.UInt53(value) && value <= SATOSHI_MAX | ||
exports.BIP32Path = BIP32Path; | ||
BIP32Path.toJSON = () => { | ||
return 'BIP32 derivation path'; | ||
}; | ||
const SATOSHI_MAX = 21 * 1e14; | ||
function Satoshi(value) { | ||
return typeforce.UInt53(value) && value <= SATOSHI_MAX; | ||
} | ||
exports.Satoshi = Satoshi; | ||
// external dependent types | ||
const ECPoint = typeforce.quacksLike('Point') | ||
exports.ECPoint = typeforce.quacksLike('Point'); | ||
// exposed, external API | ||
const Network = typeforce.compile({ | ||
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), | ||
bip32: { | ||
public: typeforce.UInt32, | ||
private: typeforce.UInt32 | ||
}, | ||
pubKeyHash: typeforce.UInt8, | ||
scriptHash: typeforce.UInt8, | ||
wif: typeforce.UInt8 | ||
}) | ||
// extend typeforce types with ours | ||
const types = { | ||
BIP32Path: BIP32Path, | ||
Buffer256bit: typeforce.BufferN(32), | ||
ECPoint: ECPoint, | ||
Hash160bit: typeforce.BufferN(20), | ||
Hash256bit: typeforce.BufferN(32), | ||
Network: Network, | ||
Satoshi: Satoshi, | ||
UInt31: UInt31 | ||
} | ||
for (var typeName in typeforce) { | ||
types[typeName] = typeforce[typeName] | ||
} | ||
module.exports = types | ||
exports.Network = typeforce.compile({ | ||
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), | ||
bip32: { | ||
public: typeforce.UInt32, | ||
private: typeforce.UInt32, | ||
}, | ||
pubKeyHash: typeforce.UInt8, | ||
scriptHash: typeforce.UInt8, | ||
wif: typeforce.UInt8, | ||
}); | ||
exports.Buffer256bit = typeforce.BufferN(32); | ||
exports.Hash160bit = typeforce.BufferN(20); | ||
exports.Hash256bit = typeforce.BufferN(32); | ||
exports.Number = typeforce.Number; // tslint:disable-line variable-name | ||
exports.Array = typeforce.Array; | ||
exports.Boolean = typeforce.Boolean; // tslint:disable-line variable-name | ||
exports.String = typeforce.String; // tslint:disable-line variable-name | ||
exports.Buffer = typeforce.Buffer; | ||
exports.Hex = typeforce.Hex; | ||
exports.maybe = typeforce.maybe; | ||
exports.tuple = typeforce.tuple; | ||
exports.UInt8 = typeforce.UInt8; | ||
exports.UInt32 = typeforce.UInt32; | ||
exports.Function = typeforce.Function; | ||
exports.BufferN = typeforce.BufferN; | ||
exports.Null = typeforce.Null; | ||
exports.oneOf = typeforce.oneOf; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
167918
92
3899
14
157
1
+ Added@types/node@10.12.18
+ Added@types/node@10.12.18(transitive)
+ Addedbip32@2.0.6(transitive)
- Removedsafe-buffer@^5.1.1
- Removedbip32@1.0.4(transitive)
Updatedbip32@^2.0.0