bitcoinjs-lib
Advanced tools
Comparing version 2.3.0 to 3.0.0
@@ -0,1 +1,27 @@ | ||
# 3.0.0 | ||
From this release users can expect out-of-the-box Segregated Witness support. | ||
The majority of breaking changes have been in how `script` encoding/decoding occurs, with the introduction of witness stacks. | ||
__added__ | ||
- Added `script.types` enums (#679) | ||
- Added `script.*.*.{check,encode,decode[,encodeStack,decodeStack]}` functions (#681, #682) | ||
- Added minimal `TransactionBuilder.prototype.build` absurd fee-safety (#696) | ||
- Added `script.(decompile/compile)PushOnly` and `script.toStack` functions (#700) | ||
- Added `Transaction.prototype.toBuffer` Segregated Witness serialization support (#684, #701) | ||
- Added `Transaction.prototype.hasWitnesses` (#718) | ||
- Added `script.witnessCommitment.*` template | ||
__fixed__ | ||
- Fixed `script` must compile minimally (#638) | ||
- Fixed `Transaction` and `Block` versions should be Int32, signed integers (#662) | ||
__removed__ | ||
- Removed `ecdsa.calcPubKeyRecoveryParam`, `ecdsa.recoverPubKey` (#456) | ||
- Removed `buffer-equals`/`buffer-compare` dependencies (#650) | ||
- Removed `HDNode.prototype.toString` (#665) | ||
- Removed `dogecoin` network (#675) | ||
__renamed__ | ||
- Removed `script.*` functions in favour of `bitcoin.script.*.(input/output).(encode/decode/check)` style (#682) | ||
# 2.3.0 | ||
@@ -2,0 +28,0 @@ __added__ |
{ | ||
"name": "bitcoinjs-lib", | ||
"version": "2.3.0", | ||
"version": "3.0.0", | ||
"description": "Client-side Bitcoin JavaScript library", | ||
"main": "./src/index.js", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
}, | ||
"keywords": [ | ||
@@ -35,8 +38,8 @@ "bitcoin", | ||
"scripts": { | ||
"coverage": "mocha --require blanket -R travis-cov", | ||
"coverage-local": "mocha --require blanket -R html-cov", | ||
"coverage-report": "nyc report --reporter=lcov", | ||
"coverage-html": "nyc report --reporter=html", | ||
"coverage": "nyc --check-coverage --branches 90 --functions 90 mocha", | ||
"integration": "mocha test/integration/", | ||
"prepublish": "npm run test", | ||
"standard": "standard", | ||
"test": "npm run standard && npm run unit", | ||
"test": "npm run standard && npm run coverage", | ||
"unit": "mocha" | ||
@@ -51,45 +54,30 @@ }, | ||
], | ||
"config": { | ||
"blanket": { | ||
"pattern": [ | ||
"" | ||
], | ||
"data-cover-never": [ | ||
"node_modules", | ||
"test" | ||
] | ||
}, | ||
"travis-cov": { | ||
"threshold": 99 | ||
} | ||
}, | ||
"dependencies": { | ||
"bigi": "^1.4.0", | ||
"bip66": "^1.1.0", | ||
"bs58check": "^1.0.5", | ||
"buffer-compare": "^1.1.0", | ||
"buffer-equals": "^1.0.3", | ||
"buffer-reverse": "^1.0.0", | ||
"bitcoin-ops": "^1.3.0", | ||
"bs58check": "^2.0.0", | ||
"create-hash": "^1.1.0", | ||
"create-hmac": "^1.1.3", | ||
"ecurve": "^1.0.0", | ||
"merkle-lib": "^2.0.10", | ||
"pushdata-bitcoin": "^1.0.1", | ||
"randombytes": "^2.0.1", | ||
"typeforce": "^1.6.2", | ||
"typeforce": "^1.8.7", | ||
"varuint-bitcoin": "^1.0.4", | ||
"wif": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"async": "^1.5.0", | ||
"blanket": "^1.1.0", | ||
"browserify": "^10.0.0", | ||
"bs58": "^2.0.1", | ||
"async": "^2.0.1", | ||
"bs58": "^4.0.0", | ||
"cb-http-client": "^0.2.0", | ||
"httpify": "^1.0.0", | ||
"minimaldata": "^1.0.0", | ||
"mocha": "^2.2.0", | ||
"coinselect": "^3.1.1", | ||
"minimaldata": "^1.0.2", | ||
"mocha": "^3.1.0", | ||
"nyc": "^10.2.0", | ||
"proxyquire": "^1.4.0", | ||
"sinon": "^1.12.2", | ||
"standard": "^5.0.0", | ||
"travis-cov": "^0.2.0" | ||
"standard": "^9.0.2" | ||
}, | ||
"license": "MIT" | ||
} |
@@ -60,4 +60,3 @@ # BitcoinJS (bitcoinjs-lib) | ||
ecurve: require('ecurve'), | ||
BigInteger: require('bigi'), | ||
Buffer: require('buffer') | ||
BigInteger: require('bigi') | ||
} | ||
@@ -68,3 +67,3 @@ ``` | ||
``` bash | ||
npm install bs58 bitcoinjs-lib ecurve bigi buffer | ||
npm install bs58 bitcoinjs-lib ecurve bigi | ||
``` | ||
@@ -81,9 +80,11 @@ | ||
**NOTE**: When uglifying the javascript, you must exclude the following variable names from being mangled: `Array`, `BigInteger`, `Boolean`, `Buffer`, `ECPair`, `Function`, `Number`, `Point` and `Script`. | ||
**NOTE**: When uglifying the javascript, you must exclude the following variable names from being mangled: `Array`, `BigInteger`, `Boolean`, `ECPair`, `Function`, `Number`, `Point` and `Script`. | ||
This is because of the function-name-duck-typing used in [typeforce](https://github.com/dcousens/typeforce). | ||
Example: | ||
``` bash | ||
uglifyjs ... --mangle --reserved 'Array,BigInteger,Boolean,Buffer,ECPair,Function,Number,Point' | ||
uglifyjs ... --mangle --reserved 'Array,BigInteger,Boolean,ECPair,Function,Number,Point' | ||
``` | ||
**NOTE**: If you expect this library to run on an iOS 10 device, ensure that you are using [buffer@5.0.5](https://www.npmjs.com/package/buffer) or greater. | ||
### Flow | ||
@@ -104,19 +105,19 @@ | ||
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L9) | ||
- [Generate a address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L20) | ||
- [Generate a address and WIF for Litecoin](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L30) | ||
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L44) | ||
- [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L51) | ||
- [Sign a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L8) | ||
- [Verify a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L16) | ||
- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L24) | ||
- [Create a 2-of-3 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L9) | ||
- [Spend from a 2-of-4 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L25) | ||
- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L11) | ||
- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L48) | ||
- [Recover a BIP32 parent private key from the parent public key and a derived non-hardened child private key](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L14) | ||
- [Recover a Private key from duplicate R values in a signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L60) | ||
- [Create a CLTV locked transaction where the expiry is past](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L36) | ||
- [Create a CLTV locked transaction where the parties bypass the expiry](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L70) | ||
- [Create a CLTV locked transaction which fails due to expiry in the future](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L102) | ||
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L9) | ||
- [Generate a address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L20) | ||
- [Generate a address and WIF for Litecoin](https://github.com/bitcoin/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L29) | ||
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L43) | ||
- [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L50) | ||
- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/advanced.js#L24) | ||
- [Create a 2-of-3 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/multisig.js#L9) | ||
- [Spend from a 2-of-4 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/multisig.js#L25) | ||
- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/stealth.js) | ||
- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/stealth.js) | ||
- [Create a BIP32 wallet external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/bip32.js) | ||
- [Create a BIP44, bitcoin, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/bip32.js) | ||
- [Recover a BIP32 parent private key from the parent public key and a derived non-hardened child private key](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/bip32.js) | ||
- [Recover a Private key from duplicate R values in a signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/crypto.js) | ||
- [Create a CLTV locked transaction where the expiry is past](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/cltv.js#L36) | ||
- [Create a CLTV locked transaction where the parties bypass the expiry](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/cltv.js#L70) | ||
- [Create a CLTV locked transaction which fails due to expiry in the future](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/cltv.js#L102) | ||
@@ -167,3 +168,2 @@ If you have a use case that you feel could be listed here, please [ask for it](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new)! | ||
- [BIP32-Utils](https://github.com/bitcoinjs/bip32-utils) - A set of utilities for working with BIP32 | ||
- [BIP32-Wallet](https://github.com/bitcoinjs/bip32-wallet) - A BIP32 Wallet backed by bitcoinjs-lib, lite on features but heavily tested | ||
- [BIP66](https://github.com/bitcoinjs/bip66) - Strict DER signature decoding | ||
@@ -174,3 +174,6 @@ - [BIP69](https://github.com/bitcoinjs/bip69) - Lexicographical Indexing of Transaction Inputs and Outputs | ||
- [BCoin](https://github.com/indutny/bcoin) - BIP37 / Bloom Filters / SPV client | ||
- [coinselect](https://github.com/bitcoinjs/coinselect) - A fee-optimizing, transaction input selection module for bitcoinjs-lib. | ||
- [insight](https://github.com/bitpay/insight) - A bitcoin blockchain API for web wallets. | ||
- [merkle-lib](https://github.com/bitcoinjs/merkle-lib) - A performance conscious library for merkle root and tree calculations. | ||
- [minimaldata](https://github.com/bitcoinjs/minimaldata) - A module to check bitcoin policy: SCRIPT_VERIFY_MINIMALDATA | ||
@@ -177,0 +180,0 @@ |
@@ -18,11 +18,2 @@ var bs58check = require('bs58check') | ||
function fromOutputScript (scriptPubKey, network) { | ||
network = network || networks.bitcoin | ||
if (bscript.isPubKeyHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash) | ||
if (bscript.isScriptHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash) | ||
throw new Error(bscript.toASM(scriptPubKey) + ' has no matching Address') | ||
} | ||
function toBase58Check (hash, version) { | ||
@@ -38,2 +29,11 @@ typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments) | ||
function fromOutputScript (outputScript, network) { | ||
network = network || networks.bitcoin | ||
if (bscript.pubKeyHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(3, 23), network.pubKeyHash) | ||
if (bscript.scriptHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(2, 22), network.scriptHash) | ||
throw new Error(bscript.toASM(outputScript) + ' has no matching Address') | ||
} | ||
function toOutputScript (address, network) { | ||
@@ -43,4 +43,4 @@ network = network || networks.bitcoin | ||
var decode = fromBase58Check(address) | ||
if (decode.version === network.pubKeyHash) return bscript.pubKeyHashOutput(decode.hash) | ||
if (decode.version === network.scriptHash) return bscript.scriptHashOutput(decode.hash) | ||
if (decode.version === network.pubKeyHash) return bscript.pubKeyHash.output.encode(decode.hash) | ||
if (decode.version === network.scriptHash) return bscript.scriptHash.output.encode(decode.hash) | ||
@@ -47,0 +47,0 @@ throw new Error(address + ' has no matching Script') |
@@ -1,6 +0,7 @@ | ||
var createHash = require('create-hash') | ||
var bcrypto = require('./crypto') | ||
var bufferutils = require('./bufferutils') | ||
var bcrypto = require('./crypto') | ||
var bufferCompare = require('buffer-compare') | ||
var bufferReverse = require('buffer-reverse') | ||
var fastMerkleRoot = require('merkle-lib/fastRoot') | ||
var typeforce = require('typeforce') | ||
var types = require('./types') | ||
var varuint = require('varuint-bitcoin') | ||
@@ -33,4 +34,10 @@ var Transaction = require('./transaction') | ||
function readInt32 () { | ||
var i = buffer.readInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
var block = new Block() | ||
block.version = readUInt32() | ||
block.version = readInt32() | ||
block.prevHash = readSlice(32) | ||
@@ -52,3 +59,2 @@ block.merkleRoot = readSlice(32) | ||
var tx = Transaction.fromBuffer(buffer.slice(offset), true) | ||
offset += tx.byteLength() | ||
@@ -69,2 +75,10 @@ return tx | ||
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) | ||
} | ||
Block.fromHex = function (hex) { | ||
@@ -79,3 +93,3 @@ return Block.fromBuffer(new Buffer(hex, 'hex')) | ||
Block.prototype.getId = function () { | ||
return bufferReverse(this.getHash()).toString('hex') | ||
return this.getHash().reverse().toString('hex') | ||
} | ||
@@ -90,4 +104,5 @@ | ||
// TODO: buffer, offset compatibility | ||
Block.prototype.toBuffer = function (headersOnly) { | ||
var buffer = new Buffer(80) | ||
var buffer = new Buffer(this.byteLength(headersOnly)) | ||
@@ -100,2 +115,6 @@ var offset = 0 | ||
function writeInt32 (i) { | ||
buffer.writeInt32LE(i, offset) | ||
offset += 4 | ||
} | ||
function writeUInt32 (i) { | ||
@@ -106,3 +125,3 @@ buffer.writeUInt32LE(i, offset) | ||
writeUInt32(this.version) | ||
writeInt32(this.version) | ||
writeSlice(this.prevHash) | ||
@@ -116,8 +135,12 @@ writeSlice(this.merkleRoot) | ||
var txLenBuffer = bufferutils.varIntBuffer(this.transactions.length) | ||
var txBuffers = this.transactions.map(function (tx) { | ||
return tx.toBuffer() | ||
varuint.encode(this.transactions.length, buffer, offset) | ||
offset += varuint.encode.bytes | ||
this.transactions.forEach(function (tx) { | ||
var txSize = tx.byteLength() // TODO: extract from toBuffer? | ||
tx.toBuffer(buffer, offset) | ||
offset += txSize | ||
}) | ||
return Buffer.concat([buffer, txLenBuffer].concat(txBuffers)) | ||
return buffer | ||
} | ||
@@ -132,12 +155,5 @@ | ||
var mantissa = bits & 0x007fffff | ||
var i = 31 - exponent | ||
var target = new Buffer(32) | ||
target.fill(0) | ||
target[i] = mantissa & 0xff | ||
target[i - 1] = mantissa >> 8 | ||
target[i - 2] = mantissa >> 16 | ||
target[i - 3] = mantissa >> 24 | ||
target.writeUInt32BE(mantissa, 28 - exponent) | ||
return target | ||
@@ -147,21 +163,10 @@ } | ||
Block.calculateMerkleRoot = function (transactions) { | ||
var length = transactions.length | ||
if (length === 0) throw TypeError('Cannot compute merkle root for zero transactions') | ||
typeforce([{ getHash: types.Function }], transactions) | ||
if (transactions.length === 0) throw TypeError('Cannot compute merkle root for zero transactions') | ||
var hashes = transactions.map(function (transaction) { return transaction.getHash() }) | ||
var hashes = transactions.map(function (transaction) { | ||
return transaction.getHash() | ||
}) | ||
while (length > 1) { | ||
var j = 0 | ||
for (var i = 0; i < length; i += 2, ++j) { | ||
var hasher = createHash('sha256') | ||
hasher.update(hashes[i]) | ||
hasher.update(i + 1 !== length ? hashes[i + 1] : hashes[i]) | ||
hashes[j] = bcrypto.sha256(hasher.digest()) | ||
} | ||
length = j | ||
} | ||
return hashes[0] | ||
return fastMerkleRoot(hashes, bcrypto.hash256) | ||
} | ||
@@ -173,12 +178,12 @@ | ||
var actualMerkleRoot = Block.calculateMerkleRoot(this.transactions) | ||
return bufferCompare(this.merkleRoot, actualMerkleRoot) === 0 | ||
return this.merkleRoot.compare(actualMerkleRoot) === 0 | ||
} | ||
Block.prototype.checkProofOfWork = function () { | ||
var hash = bufferReverse(this.getHash()) | ||
var hash = this.getHash().reverse() | ||
var target = Block.calculateTarget(this.bits) | ||
return bufferCompare(hash, target) <= 0 | ||
return hash.compare(target) <= 0 | ||
} | ||
module.exports = Block |
@@ -1,2 +0,3 @@ | ||
var opcodes = require('./opcodes.json') | ||
var pushdata = require('pushdata-bitcoin') | ||
var varuint = require('varuint-bitcoin') | ||
@@ -7,50 +8,6 @@ // https://github.com/feross/buffer/blob/master/index.js#L1127 | ||
if (value < 0) throw new Error('specified a negative value for writing an unsigned value') | ||
if (value > max) throw new Error('value is larger than maximum value for type') | ||
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 pushDataSize (i) { | ||
return i < opcodes.OP_PUSHDATA1 ? 1 | ||
: i <= 0xff ? 2 | ||
: i <= 0xffff ? 3 | ||
: 5 | ||
} | ||
function readPushDataInt (buffer, offset) { | ||
var opcode = buffer.readUInt8(offset) | ||
var number, size | ||
// ~6 bit | ||
if (opcode < opcodes.OP_PUSHDATA1) { | ||
number = opcode | ||
size = 1 | ||
// 8 bit | ||
} else if (opcode === opcodes.OP_PUSHDATA1) { | ||
if (offset + 2 > buffer.length) return null | ||
number = buffer.readUInt8(offset + 1) | ||
size = 2 | ||
// 16 bit | ||
} else if (opcode === opcodes.OP_PUSHDATA2) { | ||
if (offset + 3 > buffer.length) return null | ||
number = buffer.readUInt16LE(offset + 1) | ||
size = 3 | ||
// 32 bit | ||
} else { | ||
if (offset + 5 > buffer.length) return null | ||
if (opcode !== opcodes.OP_PUSHDATA4) throw new Error('Unexpected opcode') | ||
number = buffer.readUInt32LE(offset + 1) | ||
size = 5 | ||
} | ||
return { | ||
opcode: opcode, | ||
number: number, | ||
size: size | ||
} | ||
} | ||
function readUInt64LE (buffer, offset) { | ||
@@ -66,59 +23,2 @@ var a = buffer.readUInt32LE(offset) | ||
function readVarInt (buffer, offset) { | ||
var t = buffer.readUInt8(offset) | ||
var number, size | ||
// 8 bit | ||
if (t < 253) { | ||
number = t | ||
size = 1 | ||
// 16 bit | ||
} else if (t < 254) { | ||
number = buffer.readUInt16LE(offset + 1) | ||
size = 3 | ||
// 32 bit | ||
} else if (t < 255) { | ||
number = buffer.readUInt32LE(offset + 1) | ||
size = 5 | ||
// 64 bit | ||
} else { | ||
number = readUInt64LE(buffer, offset + 1) | ||
size = 9 | ||
} | ||
return { | ||
number: number, | ||
size: size | ||
} | ||
} | ||
function writePushDataInt (buffer, number, offset) { | ||
var size = pushDataSize(number) | ||
// ~6 bit | ||
if (size === 1) { | ||
buffer.writeUInt8(number, offset) | ||
// 8 bit | ||
} else if (size === 2) { | ||
buffer.writeUInt8(opcodes.OP_PUSHDATA1, offset) | ||
buffer.writeUInt8(number, offset + 1) | ||
// 16 bit | ||
} else if (size === 3) { | ||
buffer.writeUInt8(opcodes.OP_PUSHDATA2, offset) | ||
buffer.writeUInt16LE(number, offset + 1) | ||
// 32 bit | ||
} else { | ||
buffer.writeUInt8(opcodes.OP_PUSHDATA4, offset) | ||
buffer.writeUInt32LE(number, offset + 1) | ||
} | ||
return size | ||
} | ||
function writeUInt64LE (buffer, value, offset) { | ||
@@ -129,57 +29,31 @@ verifuint(value, 0x001fffffffffffff) | ||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4) | ||
return offset + 8 | ||
} | ||
function varIntSize (i) { | ||
return i < 253 ? 1 | ||
: i < 0x10000 ? 3 | ||
: i < 0x100000000 ? 5 | ||
: 9 | ||
} | ||
// TODO: remove in 4.0.0? | ||
function readVarInt (buffer, offset) { | ||
var result = varuint.decode(buffer, offset) | ||
function writeVarInt (buffer, number, offset) { | ||
var size = varIntSize(number) | ||
// 8 bit | ||
if (size === 1) { | ||
buffer.writeUInt8(number, offset) | ||
// 16 bit | ||
} else if (size === 3) { | ||
buffer.writeUInt8(253, offset) | ||
buffer.writeUInt16LE(number, offset + 1) | ||
// 32 bit | ||
} else if (size === 5) { | ||
buffer.writeUInt8(254, offset) | ||
buffer.writeUInt32LE(number, offset + 1) | ||
// 64 bit | ||
} else { | ||
buffer.writeUInt8(255, offset) | ||
writeUInt64LE(buffer, number, offset + 1) | ||
return { | ||
number: result, | ||
size: varuint.decode.bytes | ||
} | ||
return size | ||
} | ||
function varIntBuffer (i) { | ||
var size = varIntSize(i) | ||
var buffer = new Buffer(size) | ||
writeVarInt(buffer, i, 0) | ||
return buffer | ||
// TODO: remove in 4.0.0? | ||
function writeVarInt (buffer, number, offset) { | ||
varuint.encode(number, buffer, offset) | ||
return varuint.encode.bytes | ||
} | ||
module.exports = { | ||
equal: require('buffer-equals'), | ||
pushDataSize: pushDataSize, | ||
readPushDataInt: readPushDataInt, | ||
pushDataSize: pushdata.encodingLength, | ||
readPushDataInt: pushdata.decode, | ||
readUInt64LE: readUInt64LE, | ||
readVarInt: readVarInt, | ||
reverse: require('buffer-reverse'), | ||
varIntBuffer: varIntBuffer, | ||
varIntSize: varIntSize, | ||
writePushDataInt: writePushDataInt, | ||
varIntBuffer: varuint.encode, | ||
varIntSize: varuint.encodingLength, | ||
writePushDataInt: pushdata.encode, | ||
writeUInt64LE: writeUInt64LE, | ||
writeVarInt: writeVarInt | ||
} |
var createHash = require('create-hash') | ||
function hash160 (buffer) { | ||
return ripemd160(sha256(buffer)) | ||
} | ||
function hash256 (buffer) { | ||
return sha256(sha256(buffer)) | ||
} | ||
function ripemd160 (buffer) { | ||
@@ -23,2 +15,10 @@ return createHash('rmd160').update(buffer).digest() | ||
function hash160 (buffer) { | ||
return ripemd160(sha256(buffer)) | ||
} | ||
function hash256 (buffer) { | ||
return sha256(sha256(buffer)) | ||
} | ||
module.exports = { | ||
@@ -25,0 +25,0 @@ hash160: hash160, |
@@ -157,89 +157,4 @@ var createHmac = require('create-hmac') | ||
/** | ||
* Recover a public key from a signature. | ||
* | ||
* See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public | ||
* Key Recovery Operation". | ||
* | ||
* http://www.secg.org/download/aid-780/sec1-v2.pdf | ||
*/ | ||
function recoverPubKey (e, signature, i) { | ||
typeforce(types.tuple( | ||
types.BigInt, | ||
types.ECSignature, | ||
types.UInt2 | ||
), arguments) | ||
var n = secp256k1.n | ||
var G = secp256k1.G | ||
var r = signature.r | ||
var s = signature.s | ||
if (r.signum() <= 0 || r.compareTo(n) >= 0) throw new Error('Invalid r value') | ||
if (s.signum() <= 0 || s.compareTo(n) >= 0) throw new Error('Invalid s value') | ||
// A set LSB signifies that the y-coordinate is odd | ||
var isYOdd = i & 1 | ||
// The more significant bit specifies whether we should use the | ||
// first or second candidate key. | ||
var isSecondKey = i >> 1 | ||
// 1.1 Let x = r + jn | ||
var x = isSecondKey ? r.add(n) : r | ||
var R = secp256k1.pointFromX(isYOdd, x) | ||
// 1.4 Check that nR is at infinity | ||
var nR = R.multiply(n) | ||
if (!secp256k1.isInfinity(nR)) throw new Error('nR is not a valid curve point') | ||
// Compute r^-1 | ||
var rInv = r.modInverse(n) | ||
// Compute -e from e | ||
var eNeg = e.negate().mod(n) | ||
// 1.6.1 Compute Q = r^-1 (sR - eG) | ||
// Q = r^-1 (sR + -eG) | ||
var Q = R.multiplyTwo(s, G, eNeg).multiply(rInv) | ||
secp256k1.validate(Q) | ||
return Q | ||
} | ||
/** | ||
* Calculate pubkey extraction parameter. | ||
* | ||
* When extracting a pubkey from a signature, we have to | ||
* distinguish four different cases. Rather than putting this | ||
* burden on the verifier, Bitcoin includes a 2-bit value with the | ||
* signature. | ||
* | ||
* This function simply tries all four cases and returns the value | ||
* that resulted in a successful pubkey recovery. | ||
*/ | ||
function calcPubKeyRecoveryParam (e, signature, Q) { | ||
typeforce(types.tuple( | ||
types.BigInt, | ||
types.ECSignature, | ||
types.ECPoint | ||
), arguments) | ||
for (var i = 0; i < 4; i++) { | ||
var Qprime = recoverPubKey(e, signature, i) | ||
// 1.6.2 Verify Q | ||
if (Qprime.equals(Q)) { | ||
return i | ||
} | ||
} | ||
throw new Error('Unable to find valid recovery factor') | ||
} | ||
module.exports = { | ||
calcPubKeyRecoveryParam: calcPubKeyRecoveryParam, | ||
deterministicGenerateK: deterministicGenerateK, | ||
recoverPubKey: recoverPubKey, | ||
sign: sign, | ||
@@ -246,0 +161,0 @@ verify: verify, |
@@ -64,6 +64,6 @@ var baddress = require('./address') | ||
// [network, ...] | ||
// list of networks? | ||
if (types.Array(network)) { | ||
network = network.filter(function (network) { | ||
return version === network.wif | ||
network = network.filter(function (x) { | ||
return version === x.wif | ||
}).pop() | ||
@@ -73,3 +73,3 @@ | ||
// network | ||
// otherwise, assume a network object (or default to bitcoin) | ||
} else { | ||
@@ -76,0 +76,0 @@ network = network || NETWORKS.bitcoin |
@@ -64,5 +64,5 @@ var base58check = require('bs58check') | ||
if (Array.isArray(networks)) { | ||
network = networks.filter(function (network) { | ||
return version === network.bip32.private || | ||
version === network.bip32.public | ||
network = networks.filter(function (x) { | ||
return version === x.bip32.private || | ||
version === x.bip32.public | ||
}).pop() | ||
@@ -103,11 +103,8 @@ | ||
var d = BigInteger.fromBuffer(buffer.slice(46, 78)) | ||
keyPair = new ECPair(d, null, { network: network }) | ||
keyPair = new ECPair(d, null, { | ||
network: network | ||
}) | ||
// 33 bytes: public key data (0x02 + X or 0x03 + X) | ||
} else { | ||
var Q = ecurve.Point.decodeFrom(curve, buffer.slice(45, 78)) | ||
if (!Q.compressed) throw new Error('Invalid public key') | ||
// Q.compressed is assumed, if somehow this assumption is broken, `new HDNode` will throw | ||
@@ -118,5 +115,3 @@ // Verify that the X coordinate in the public point corresponds to a point on the curve. | ||
keyPair = new ECPair(null, Q, { | ||
network: network | ||
}) | ||
keyPair = new ECPair(null, Q, { network: network }) | ||
} | ||
@@ -300,3 +295,3 @@ | ||
HDNode.prototype.derivePath = function (path) { | ||
typeforce(types.Bip32Path, path) | ||
typeforce(types.BIP32Path, path) | ||
@@ -324,4 +319,2 @@ var splitPath = path.split('/') | ||
HDNode.prototype.toString = HDNode.prototype.toBase58 | ||
module.exports = HDNode |
@@ -12,6 +12,5 @@ module.exports = { | ||
crypto: require('./crypto'), | ||
message: require('./message'), | ||
networks: require('./networks'), | ||
opcodes: require('./opcodes.json'), | ||
opcodes: require('bitcoin-ops'), | ||
script: require('./script') | ||
} |
@@ -13,4 +13,3 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes | ||
scriptHash: 0x05, | ||
wif: 0x80, | ||
dustThreshold: 546 // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162 | ||
wif: 0x80 | ||
}, | ||
@@ -25,4 +24,3 @@ testnet: { | ||
scriptHash: 0xc4, | ||
wif: 0xef, | ||
dustThreshold: 546 | ||
wif: 0xef | ||
}, | ||
@@ -37,16 +35,4 @@ litecoin: { | ||
scriptHash: 0x05, | ||
wif: 0xb0, | ||
dustThreshold: 0 // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365 | ||
}, | ||
dogecoin: { | ||
messagePrefix: '\x19Dogecoin Signed Message:\n', | ||
bip32: { | ||
public: 0x02facafd, | ||
private: 0x02fac398 | ||
}, | ||
pubKeyHash: 0x1e, | ||
scriptHash: 0x16, | ||
wif: 0x9e, | ||
dustThreshold: 0 // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 | ||
wif: 0xb0 | ||
} | ||
} |
var bip66 = require('bip66') | ||
var bufferutils = require('./bufferutils') | ||
var pushdata = require('pushdata-bitcoin') | ||
var typeforce = require('typeforce') | ||
var types = require('./types') | ||
var scriptNumber = require('./script_number') | ||
var OPS = require('./opcodes.json') | ||
var REVERSE_OPS = (function () { | ||
var result = {} | ||
for (var op in OPS) { | ||
var code = OPS[op] | ||
result[code] = op | ||
} | ||
return result | ||
})() | ||
var OPS = require('bitcoin-ops') | ||
var REVERSE_OPS = require('bitcoin-ops/map') | ||
var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 | ||
function toASM (chunks) { | ||
if (Buffer.isBuffer(chunks)) { | ||
chunks = decompile(chunks) | ||
} | ||
function isOPInt (value) { | ||
return types.Number(value) && | ||
((value === OPS.OP_0) || | ||
(value >= OPS.OP_1 && value <= OPS.OP_16) || | ||
(value === OPS.OP_1NEGATE)) | ||
} | ||
return chunks.map(function (chunk) { | ||
// data? | ||
if (Buffer.isBuffer(chunk)) return chunk.toString('hex') | ||
// opcode! | ||
return REVERSE_OPS[chunk] | ||
}).join(' ') | ||
function isPushOnlyChunk (value) { | ||
return types.Buffer(value) || isOPInt(value) | ||
} | ||
function fromASM (asm) { | ||
typeforce(types.String, asm) | ||
return compile(asm.split(' ').map(function (chunkStr) { | ||
// opcode? | ||
if (OPS[chunkStr] !== undefined) return OPS[chunkStr] | ||
// data! | ||
return new Buffer(chunkStr, 'hex') | ||
})) | ||
function isPushOnly (value) { | ||
return types.Array(value) && value.every(isPushOnlyChunk) | ||
} | ||
@@ -53,3 +35,8 @@ | ||
if (Buffer.isBuffer(chunk)) { | ||
return accum + bufferutils.pushDataSize(chunk.length) + chunk.length | ||
// adhere to BIP62.3, minimal push policy | ||
if (chunk.length === 1 && (chunk[0] === 0x81 || (chunk[0] >= 1 && chunk[0] <= 16))) { | ||
return accum + 1 | ||
} | ||
return accum + pushdata.encodingLength(chunk.length) + chunk.length | ||
} | ||
@@ -67,4 +54,18 @@ | ||
if (Buffer.isBuffer(chunk)) { | ||
offset += bufferutils.writePushDataInt(buffer, chunk.length, offset) | ||
// adhere to BIP62.3, minimal push policy | ||
if (chunk.length === 1 && chunk[0] >= 1 && chunk[0] <= 16) { | ||
var opcode = OP_INT_BASE + chunk[0] | ||
buffer.writeUInt8(opcode, offset) | ||
offset += 1 | ||
return | ||
} | ||
if (chunk.length === 1 && chunk[0] === 0x81) { | ||
buffer.writeUInt8(OPS.OP_1NEGATE, offset) | ||
offset += 1 | ||
return | ||
} | ||
offset += pushdata.encode(buffer, chunk.length, offset) | ||
chunk.copy(buffer, offset) | ||
@@ -98,3 +99,3 @@ offset += chunk.length | ||
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { | ||
var d = bufferutils.readPushDataInt(buffer, i) | ||
var d = pushdata.decode(buffer, i) | ||
@@ -124,2 +125,41 @@ // did reading a pushDataInt fail? empty script | ||
function toASM (chunks) { | ||
if (Buffer.isBuffer(chunks)) { | ||
chunks = decompile(chunks) | ||
} | ||
return chunks.map(function (chunk) { | ||
// data? | ||
if (Buffer.isBuffer(chunk)) return chunk.toString('hex') | ||
// 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 new Buffer(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 new Buffer(0) | ||
return scriptNumber.encode(op - OP_INT_BASE) | ||
}) | ||
} | ||
function isCanonicalPubKey (buffer) { | ||
@@ -140,9 +180,2 @@ if (!Buffer.isBuffer(buffer)) return false | ||
function isCanonicalSignature (buffer) { | ||
if (!Buffer.isBuffer(buffer)) return false | ||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false | ||
return bip66.check(buffer.slice(0, -1)) | ||
} | ||
function isDefinedHashType (hashType) { | ||
@@ -155,260 +188,9 @@ var hashTypeMod = hashType & ~0x80 | ||
function isPubKeyHashInput (script) { | ||
var chunks = decompile(script) | ||
function isCanonicalSignature (buffer) { | ||
if (!Buffer.isBuffer(buffer)) return false | ||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false | ||
return chunks.length === 2 && | ||
isCanonicalSignature(chunks[0]) && | ||
isCanonicalPubKey(chunks[1]) | ||
return bip66.check(buffer.slice(0, -1)) | ||
} | ||
function isPubKeyHashOutput (script) { | ||
var buffer = 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 | ||
} | ||
function isPubKeyInput (script) { | ||
var chunks = decompile(script) | ||
return chunks.length === 1 && | ||
isCanonicalSignature(chunks[0]) | ||
} | ||
function isPubKeyOutput (script) { | ||
var chunks = decompile(script) | ||
return chunks.length === 2 && | ||
isCanonicalPubKey(chunks[0]) && | ||
chunks[1] === OPS.OP_CHECKSIG | ||
} | ||
function isScriptHashInput (script, allowIncomplete) { | ||
var chunks = decompile(script) | ||
if (chunks.length < 2) return false | ||
var lastChunk = chunks[chunks.length - 1] | ||
if (!Buffer.isBuffer(lastChunk)) return false | ||
var scriptSigChunks = chunks.slice(0, -1) | ||
var redeemScriptChunks = decompile(lastChunk) | ||
// is redeemScript a valid script? | ||
if (redeemScriptChunks.length === 0) return false | ||
return classifyInput(scriptSigChunks, allowIncomplete) === classifyOutput(redeemScriptChunks) | ||
} | ||
function isScriptHashOutput (script) { | ||
var buffer = compile(script) | ||
return buffer.length === 23 && | ||
buffer[0] === OPS.OP_HASH160 && | ||
buffer[1] === 0x14 && | ||
buffer[22] === OPS.OP_EQUAL | ||
} | ||
function isWitnessPubKeyHashOutput (script) { | ||
var buffer = compile(script) | ||
return buffer.length === 22 && | ||
buffer[0] === OPS.OP_0 && | ||
buffer[1] === 0x14 | ||
} | ||
function isWitnessScriptHashOutput (script) { | ||
var buffer = compile(script) | ||
return buffer.length === 34 && | ||
buffer[0] === OPS.OP_0 && | ||
buffer[1] === 0x20 | ||
} | ||
// allowIncomplete is to account for combining signatures | ||
// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197 | ||
function isMultisigInput (script, allowIncomplete) { | ||
var chunks = decompile(script) | ||
if (chunks.length < 2) return false | ||
if (chunks[0] !== OPS.OP_0) return false | ||
if (allowIncomplete) { | ||
return chunks.slice(1).every(function (chunk) { | ||
return chunk === OPS.OP_0 || isCanonicalSignature(chunk) | ||
}) | ||
} | ||
return chunks.slice(1).every(isCanonicalSignature) | ||
} | ||
function isMultisigOutput (script) { | ||
var chunks = decompile(script) | ||
if (chunks.length < 4) return false | ||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false | ||
var mOp = chunks[0] | ||
var nOp = chunks[chunks.length - 2] | ||
if (!types.Number(mOp)) return false | ||
if (!types.Number(nOp)) return false | ||
var m = mOp - OP_INT_BASE | ||
var n = nOp - OP_INT_BASE | ||
// 0 < m <= n <= 16 | ||
if (m <= 0) return false | ||
if (m > n) return false | ||
if (n > 16) return false | ||
if (n !== chunks.length - 3) return false | ||
return chunks.slice(1, -2).every(isCanonicalPubKey) | ||
} | ||
function isNullDataOutput (script) { | ||
var chunks = decompile(script) | ||
return chunks[0] === OPS.OP_RETURN | ||
} | ||
function classifyOutput (script) { | ||
var chunks = decompile(script) | ||
if (isWitnessPubKeyHashOutput(chunks)) { | ||
return 'witnesspubkeyhash' | ||
} else if (isWitnessScriptHashOutput(chunks)) { | ||
return 'witnessscripthash' | ||
} else if (isPubKeyHashOutput(chunks)) { | ||
return 'pubkeyhash' | ||
} else if (isScriptHashOutput(chunks)) { | ||
return 'scripthash' | ||
} else if (isMultisigOutput(chunks)) { | ||
return 'multisig' | ||
} else if (isPubKeyOutput(chunks)) { | ||
return 'pubkey' | ||
} else if (isNullDataOutput(chunks)) { | ||
return 'nulldata' | ||
} | ||
return 'nonstandard' | ||
} | ||
function classifyInput (script, allowIncomplete) { | ||
var chunks = decompile(script) | ||
if (isPubKeyHashInput(chunks)) { | ||
return 'pubkeyhash' | ||
} else if (isMultisigInput(chunks, allowIncomplete)) { | ||
return 'multisig' | ||
} else if (isScriptHashInput(chunks, allowIncomplete)) { | ||
return 'scripthash' | ||
} else if (isPubKeyInput(chunks)) { | ||
return 'pubkey' | ||
} | ||
return 'nonstandard' | ||
} | ||
// Standard Script Templates | ||
// {pubKey} OP_CHECKSIG | ||
function pubKeyOutput (pubKey) { | ||
return compile([pubKey, OPS.OP_CHECKSIG]) | ||
} | ||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG | ||
function pubKeyHashOutput (pubKeyHash) { | ||
typeforce(types.Hash160bit, pubKeyHash) | ||
return compile([OPS.OP_DUP, OPS.OP_HASH160, pubKeyHash, OPS.OP_EQUALVERIFY, OPS.OP_CHECKSIG]) | ||
} | ||
// OP_HASH160 {scriptHash} OP_EQUAL | ||
function scriptHashOutput (scriptHash) { | ||
typeforce(types.Hash160bit, scriptHash) | ||
return compile([OPS.OP_HASH160, scriptHash, OPS.OP_EQUAL]) | ||
} | ||
// m [pubKeys ...] n OP_CHECKMULTISIG | ||
function multisigOutput (m, pubKeys) { | ||
typeforce(types.tuple(types.Number, [types.Buffer]), arguments) | ||
var n = pubKeys.length | ||
if (n < m) throw new Error('Not enough pubKeys provided') | ||
return compile([].concat( | ||
OP_INT_BASE + m, | ||
pubKeys, | ||
OP_INT_BASE + n, | ||
OPS.OP_CHECKMULTISIG | ||
)) | ||
} | ||
// OP_0 {pubKeyHash} | ||
function witnessPubKeyHashOutput (pubKeyHash) { | ||
typeforce(types.Hash160bit, pubKeyHash) | ||
return compile([OPS.OP_0, pubKeyHash]) | ||
} | ||
// OP_0 {scriptHash} | ||
function witnessScriptHashOutput (scriptHash) { | ||
typeforce(types.Hash256bit, scriptHash) | ||
return compile([OPS.OP_0, scriptHash]) | ||
} | ||
// {signature} | ||
function pubKeyInput (signature) { | ||
typeforce(types.Buffer, signature) | ||
return compile([signature]) | ||
} | ||
// {signature} {pubKey} | ||
function pubKeyHashInput (signature, pubKey) { | ||
typeforce(types.tuple(types.Buffer, types.Buffer), arguments) | ||
return compile([signature, pubKey]) | ||
} | ||
// <scriptSig> {serialized scriptPubKey script} | ||
function scriptHashInput (scriptSig, scriptPubKey) { | ||
var scriptSigChunks = decompile(scriptSig) | ||
var serializedScriptPubKey = compile(scriptPubKey) | ||
return compile([].concat( | ||
scriptSigChunks, | ||
serializedScriptPubKey | ||
)) | ||
} | ||
// <scriptSig> {serialized scriptPubKey script} | ||
function witnessScriptHashInput (scriptSig, scriptPubKey) { | ||
return scriptHashInput(scriptSig, scriptPubKey) | ||
} | ||
// OP_0 [signatures ...] | ||
function multisigInput (signatures, scriptPubKey) { | ||
if (scriptPubKey) { | ||
var chunks = decompile(scriptPubKey) | ||
if (!isMultisigOutput(chunks)) throw new Error('Expected multisig scriptPubKey') | ||
var mOp = chunks[0] | ||
var nOp = chunks[chunks.length - 2] | ||
var m = mOp - OP_INT_BASE | ||
var n = nOp - OP_INT_BASE | ||
if (signatures.length < m) throw new Error('Not enough signatures provided') | ||
if (signatures.length > n) throw new Error('Too many signatures provided') | ||
} | ||
return compile([].concat(OPS.OP_0, signatures)) | ||
} | ||
function nullDataOutput (data) { | ||
return compile([OPS.OP_RETURN, data]) | ||
} | ||
module.exports = { | ||
@@ -419,2 +201,3 @@ compile: compile, | ||
toASM: toASM, | ||
toStack: toStack, | ||
@@ -425,30 +208,9 @@ number: require('./script_number'), | ||
isCanonicalSignature: isCanonicalSignature, | ||
isDefinedHashType: isDefinedHashType, | ||
isPubKeyHashInput: isPubKeyHashInput, | ||
isPubKeyHashOutput: isPubKeyHashOutput, | ||
isPubKeyInput: isPubKeyInput, | ||
isPubKeyOutput: isPubKeyOutput, | ||
isScriptHashInput: isScriptHashInput, | ||
isScriptHashOutput: isScriptHashOutput, | ||
isWitnessPubKeyHashOutput: isWitnessPubKeyHashOutput, | ||
isWitnessScriptHashOutput: isWitnessScriptHashOutput, | ||
isMultisigInput: isMultisigInput, | ||
isMultisigOutput: isMultisigOutput, | ||
isNullDataOutput: isNullDataOutput, | ||
isPushOnly: isPushOnly, | ||
isDefinedHashType: isDefinedHashType | ||
} | ||
classifyOutput: classifyOutput, | ||
classifyInput: classifyInput, | ||
pubKeyOutput: pubKeyOutput, | ||
pubKeyHashOutput: pubKeyHashOutput, | ||
scriptHashOutput: scriptHashOutput, | ||
witnessPubKeyHashOutput: witnessPubKeyHashOutput, | ||
witnessScriptHashInput: witnessScriptHashInput, | ||
witnessScriptHashOutput: witnessScriptHashOutput, | ||
multisigOutput: multisigOutput, | ||
pubKeyInput: pubKeyInput, | ||
pubKeyHashInput: pubKeyHashInput, | ||
scriptHashInput: scriptHashInput, | ||
multisigInput: multisigInput, | ||
nullDataOutput: nullDataOutput | ||
var templates = require('./templates') | ||
for (var key in templates) { | ||
module.exports[key] = templates[key] | ||
} |
var baddress = require('./address') | ||
var bcrypto = require('./crypto') | ||
var bscript = require('./script') | ||
var bufferEquals = require('buffer-equals') | ||
var bufferReverse = require('buffer-reverse') | ||
var networks = require('./networks') | ||
var ops = require('./opcodes.json') | ||
var ops = require('bitcoin-ops') | ||
var typeforce = require('typeforce') | ||
var types = require('./types') | ||
var scriptTypes = bscript.types | ||
var SIGNABLE = [bscript.types.P2PKH, bscript.types.P2PK, bscript.types.MULTISIG] | ||
var P2SH = SIGNABLE.concat([bscript.types.P2WPKH, bscript.types.P2WSH]) | ||
@@ -15,16 +16,159 @@ var ECPair = require('./ecpair') | ||
// re-orders signatures to match pubKeys, fills undefined otherwise | ||
function fixMSSignatures (transaction, vin, pubKeys, signatures, prevOutScript, hashType, skipPubKey) { | ||
// maintain a local copy of unmatched signatures | ||
var unmatched = signatures.slice() | ||
var cache = {} | ||
function extractChunks (type, chunks, script) { | ||
var pubKeys = [] | ||
var signatures = [] | ||
switch (type) { | ||
case scriptTypes.P2PKH: | ||
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)') | ||
pubKeys = chunks.slice(1) | ||
signatures = chunks.slice(0, 1) | ||
break | ||
return pubKeys.map(function (pubKey) { | ||
// skip optionally provided pubKey | ||
if (skipPubKey && bufferEquals(skipPubKey, pubKey)) return undefined | ||
case scriptTypes.P2PK: | ||
pubKeys[0] = script ? bscript.pubKey.output.decode(script) : undefined | ||
signatures = chunks.slice(0, 1) | ||
break | ||
var matched | ||
var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey) | ||
case scriptTypes.MULTISIG: | ||
if (script) { | ||
var multisig = bscript.multisig.output.decode(script) | ||
pubKeys = multisig.pubKeys | ||
} | ||
// check for a matching signature | ||
signatures = chunks.slice(1).map(function (chunk) { | ||
return chunk.length === 0 ? undefined : chunk | ||
}) | ||
break | ||
} | ||
return { | ||
pubKeys: pubKeys, | ||
signatures: signatures | ||
} | ||
} | ||
function expandInput (scriptSig, witnessStack) { | ||
var prevOutScript | ||
var prevOutType | ||
var scriptType | ||
var script | ||
var redeemScript | ||
var witnessScript | ||
var witnessScriptType | ||
var redeemScriptType | ||
var witness = false | ||
var p2wsh = false | ||
var p2sh = false | ||
var witnessProgram | ||
var chunks | ||
var scriptSigChunks = bscript.decompile(scriptSig) | ||
var sigType = bscript.classifyInput(scriptSigChunks, true) | ||
if (sigType === scriptTypes.P2SH) { | ||
p2sh = true | ||
redeemScript = scriptSigChunks[scriptSigChunks.length - 1] | ||
redeemScriptType = bscript.classifyOutput(redeemScript) | ||
prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(redeemScript)) | ||
prevOutType = scriptTypes.P2SH | ||
script = redeemScript | ||
} | ||
var classifyWitness = bscript.classifyWitness(witnessStack) | ||
if (classifyWitness === scriptTypes.P2WSH) { | ||
witnessScript = witnessStack[witnessStack.length - 1] | ||
witnessScriptType = bscript.classifyOutput(witnessScript) | ||
p2wsh = true | ||
if (scriptSig.length === 0) { | ||
prevOutScript = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript)) | ||
prevOutType = scriptTypes.P2WSH | ||
if (typeof redeemScript !== 'undefined') { | ||
throw new Error('Redeem script given when unnecessary') | ||
} | ||
// bare witness | ||
} else { | ||
if (!redeemScript) { | ||
throw new Error('No redeemScript provided for P2WSH, but scriptSig non-empty') | ||
} | ||
witnessProgram = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript)) | ||
if (!redeemScript.equals(witnessProgram)) { | ||
throw new Error('Redeem script didn\'t match witnessScript') | ||
} | ||
} | ||
if (SIGNABLE.indexOf(bscript.classifyOutput(witnessScript)) === -1) { | ||
throw new Error('unsupported witness script') | ||
} | ||
script = witnessScript | ||
scriptType = witnessScriptType | ||
chunks = witnessStack.slice(0, -1) | ||
} else if (classifyWitness === scriptTypes.P2WPKH) { | ||
var key = witnessStack[witnessStack.length - 1] | ||
var keyHash = bcrypto.hash160(key) | ||
if (scriptSig.length === 0) { | ||
prevOutScript = bscript.witnessPubKeyHash.output.encode(keyHash) | ||
prevOutType = scriptTypes.P2WPKH | ||
if (typeof redeemScript !== 'undefined') { | ||
throw new Error('Redeem script given when unnecessary') | ||
} | ||
} else { | ||
if (!redeemScript) { | ||
throw new Error('No redeemScript provided for P2WPKH, but scriptSig wasn\'t empty') | ||
} | ||
witnessProgram = bscript.witnessPubKeyHash.output.encode(keyHash) | ||
if (!redeemScript.equals(witnessProgram)) { | ||
throw new Error('Redeem script did not have the right witness program') | ||
} | ||
} | ||
scriptType = scriptTypes.P2PKH | ||
chunks = witnessStack | ||
} else if (redeemScript) { | ||
if (P2SH.indexOf(redeemScriptType) === -1) { | ||
throw new Error('Bad redeemscript!') | ||
} | ||
script = redeemScript | ||
scriptType = redeemScriptType | ||
chunks = scriptSigChunks.slice(0, -1) | ||
} else { | ||
prevOutType = scriptType = bscript.classifyInput(scriptSig) | ||
chunks = scriptSigChunks | ||
} | ||
var expanded = extractChunks(scriptType, chunks, script) | ||
var result = { | ||
pubKeys: expanded.pubKeys, | ||
signatures: expanded.signatures, | ||
prevOutScript: prevOutScript, | ||
prevOutType: prevOutType, | ||
signType: scriptType, | ||
signScript: script, | ||
witness: Boolean(witness) | ||
} | ||
if (p2sh) { | ||
result.redeemScript = redeemScript | ||
result.redeemScriptType = redeemScriptType | ||
} | ||
if (p2wsh) { | ||
result.witnessScript = witnessScript | ||
result.witnessScriptType = witnessScriptType | ||
} | ||
return result | ||
} | ||
// could be done in expandInput, but requires the original Transaction for hashForSignature | ||
function fixMultisigOrder (input, transaction, vin) { | ||
if (input.redeemScriptType !== scriptTypes.MULTISIG || !input.redeemScript) return | ||
if (input.pubKeys.length === input.signatures.length) return | ||
var unmatched = input.signatures.concat() | ||
input.signatures = input.pubKeys.map(function (pubKey) { | ||
var keyPair = ECPair.fromPublicKeyBuffer(pubKey) | ||
var match | ||
// check for a signature | ||
unmatched.some(function (signature, i) { | ||
@@ -34,8 +178,12 @@ // skip if undefined || OP_0 | ||
var signatureHash = cache[hashType] = cache[hashType] || transaction.hashForSignature(vin, prevOutScript, hashType) | ||
if (!keyPair2.verify(signatureHash, signature)) return false | ||
// TODO: avoid O(n) hashForSignature | ||
var parsed = ECSignature.parseScriptSignature(signature) | ||
var 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 | ||
matched = signature | ||
match = signature | ||
@@ -45,106 +193,264 @@ return true | ||
return matched || undefined | ||
return match | ||
}) | ||
} | ||
function extractInput (transaction, txIn, vin) { | ||
if (txIn.script.length === 0) return {} | ||
function expandOutput (script, scriptType, ourPubKey) { | ||
typeforce(types.Buffer, script) | ||
var scriptSigChunks = bscript.decompile(txIn.script) | ||
var prevOutType = bscript.classifyInput(scriptSigChunks, true) | ||
var scriptChunks = bscript.decompile(script) | ||
if (!scriptType) { | ||
scriptType = bscript.classifyOutput(script) | ||
} | ||
function processScript (scriptType, scriptSigChunks, redeemScriptChunks) { | ||
// ensure chunks are decompiled | ||
scriptSigChunks = bscript.decompile(scriptSigChunks) | ||
redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined | ||
var pubKeys = [] | ||
var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed | ||
switch (scriptType) { | ||
// does our hash160(pubKey) match the output scripts? | ||
case scriptTypes.P2PKH: | ||
if (!ourPubKey) break | ||
switch (scriptType) { | ||
case 'scripthash': | ||
redeemScript = scriptSigChunks.slice(-1)[0] | ||
scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1)) | ||
var pkh1 = scriptChunks[2] | ||
var pkh2 = bcrypto.hash160(ourPubKey) | ||
if (pkh1.equals(pkh2)) pubKeys = [ourPubKey] | ||
break | ||
redeemScriptType = bscript.classifyInput(scriptSigChunks, true) | ||
prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) | ||
// does our hash160(pubKey) match the output scripts? | ||
case scriptTypes.P2WPKH: | ||
if (!ourPubKey) break | ||
result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript)) | ||
var wpkh1 = scriptChunks[1] | ||
var wpkh2 = bcrypto.hash160(ourPubKey) | ||
if (wpkh1.equals(wpkh2)) pubKeys = [ourPubKey] | ||
break | ||
result.prevOutScript = prevOutScript | ||
result.redeemScript = redeemScript | ||
result.redeemScriptType = redeemScriptType | ||
case scriptTypes.P2PK: | ||
pubKeys = scriptChunks.slice(0, 1) | ||
break | ||
return result | ||
case scriptTypes.MULTISIG: | ||
pubKeys = scriptChunks.slice(1, -2) | ||
break | ||
case 'pubkeyhash': | ||
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) | ||
hashType = parsed.hashType | ||
pubKeys = scriptSigChunks.slice(1) | ||
signatures = [parsed.signature] | ||
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0])) | ||
default: return { scriptType: scriptType } | ||
} | ||
break | ||
return { | ||
pubKeys: pubKeys, | ||
scriptType: scriptType, | ||
signatures: pubKeys.map(function () { return undefined }) | ||
} | ||
} | ||
case 'pubkey': | ||
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) | ||
hashType = parsed.hashType | ||
signatures = [parsed.signature] | ||
function checkP2shInput (input, redeemScriptHash) { | ||
if (input.prevOutType) { | ||
if (input.prevOutType !== scriptTypes.P2SH) throw new Error('PrevOutScript must be P2SH') | ||
if (redeemScriptChunks) { | ||
pubKeys = redeemScriptChunks.slice(0, 1) | ||
} | ||
var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1] | ||
if (!prevOutScriptScriptHash.equals(redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)') | ||
} | ||
} | ||
break | ||
function checkP2WSHInput (input, witnessScriptHash) { | ||
if (input.prevOutType) { | ||
if (input.prevOutType !== scriptTypes.P2WSH) throw new Error('PrevOutScript must be P2WSH') | ||
case 'multisig': | ||
signatures = scriptSigChunks.slice(1).map(function (chunk) { | ||
if (chunk === ops.OP_0) return undefined | ||
var scriptHash = bscript.decompile(input.prevOutScript)[1] | ||
if (!scriptHash.equals(witnessScriptHash)) throw new Error('Inconsistent sha25(WitnessScript)') | ||
} | ||
} | ||
parsed = ECSignature.parseScriptSignature(chunk) | ||
hashType = parsed.hashType | ||
function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScript) { | ||
var expanded | ||
var prevOutType | ||
var prevOutScript | ||
return parsed.signature | ||
}) | ||
var p2sh = false | ||
var p2shType | ||
var redeemScriptHash | ||
if (redeemScriptChunks) { | ||
pubKeys = redeemScriptChunks.slice(1, -2) | ||
var witness = false | ||
var p2wsh = false | ||
var witnessType | ||
var witnessScriptHash | ||
if (pubKeys.length !== signatures.length) { | ||
signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, bscript.compile(redeemScriptChunks), hashType, redeemScript) | ||
} | ||
} | ||
var signType | ||
var signScript | ||
break | ||
if (redeemScript && witnessScript) { | ||
redeemScriptHash = bcrypto.hash160(redeemScript) | ||
witnessScriptHash = bcrypto.sha256(witnessScript) | ||
checkP2shInput(input, redeemScriptHash) | ||
if (!redeemScript.equals(bscript.witnessScriptHash.output.encode(witnessScriptHash))) throw new Error('Witness script inconsistent with redeem script') | ||
expanded = expandOutput(witnessScript, undefined, kpPubKey) | ||
if (!expanded.pubKeys) throw new Error('WitnessScript not supported "' + bscript.toASM(redeemScript) + '"') | ||
prevOutType = bscript.types.P2SH | ||
prevOutScript = bscript.scriptHash.output.encode(redeemScriptHash) | ||
p2sh = witness = p2wsh = true | ||
p2shType = bscript.types.P2WSH | ||
signType = witnessType = expanded.scriptType | ||
signScript = witnessScript | ||
} else if (redeemScript) { | ||
redeemScriptHash = bcrypto.hash160(redeemScript) | ||
checkP2shInput(input, redeemScriptHash) | ||
expanded = expandOutput(redeemScript, undefined, kpPubKey) | ||
if (!expanded.pubKeys) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"') | ||
prevOutType = bscript.types.P2SH | ||
prevOutScript = bscript.scriptHash.output.encode(redeemScriptHash) | ||
p2sh = true | ||
signType = p2shType = expanded.scriptType | ||
signScript = redeemScript | ||
witness = signType === bscript.types.P2WPKH | ||
} else if (witnessScript) { | ||
witnessScriptHash = bcrypto.sha256(witnessScript) | ||
checkP2WSHInput(input, witnessScriptHash) | ||
expanded = expandOutput(witnessScript, undefined, kpPubKey) | ||
if (!expanded.pubKeys) throw new Error('WitnessScript not supported "' + bscript.toASM(redeemScript) + '"') | ||
prevOutType = bscript.types.P2WSH | ||
prevOutScript = bscript.witnessScriptHash.output.encode(witnessScriptHash) | ||
witness = p2wsh = true | ||
signType = witnessType = expanded.scriptType | ||
signScript = witnessScript | ||
} else if (input.prevOutType) { | ||
// embedded scripts are not possible without a redeemScript | ||
if (input.prevOutType === scriptTypes.P2SH || | ||
input.prevOutType === scriptTypes.P2WSH) { | ||
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript') | ||
} | ||
return { | ||
hashType: hashType, | ||
pubKeys: pubKeys, | ||
signatures: signatures, | ||
prevOutScript: prevOutScript, | ||
redeemScript: redeemScript, | ||
redeemScriptType: redeemScriptType | ||
prevOutType = input.prevOutType | ||
prevOutScript = input.prevOutScript | ||
expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey) | ||
if (!expanded.pubKeys) return | ||
witness = (input.prevOutType === scriptTypes.P2WPKH) | ||
signType = prevOutType | ||
signScript = prevOutScript | ||
} else { | ||
prevOutScript = bscript.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey)) | ||
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey) | ||
prevOutType = scriptTypes.P2PKH | ||
witness = false | ||
signType = prevOutType | ||
signScript = prevOutScript | ||
} | ||
if (witness && !types.Satoshi(witnessValue)) { | ||
throw new Error('Input was witness but not given witness value') | ||
} | ||
if (signType === scriptTypes.P2WPKH) { | ||
signScript = bscript.pubKeyHash.output.encode(bscript.witnessPubKeyHash.output.decode(signScript)) | ||
} | ||
if (p2sh) { | ||
input.redeemScript = redeemScript | ||
input.redeemScriptType = p2shType | ||
} | ||
if (p2wsh) { | ||
input.witnessScript = witnessScript | ||
input.witnessScriptType = witnessType | ||
} | ||
input.pubKeys = expanded.pubKeys | ||
input.signatures = expanded.signatures | ||
input.signScript = signScript | ||
input.signType = signType | ||
input.prevOutScript = prevOutScript | ||
input.prevOutType = prevOutType | ||
input.witness = witness | ||
} | ||
function buildStack (type, signatures, pubKeys, allowIncomplete) { | ||
if (type === scriptTypes.P2PKH) { | ||
if (signatures.length === 1 && signatures[0] instanceof Buffer && pubKeys.length === 1) return bscript.pubKeyHash.input.encodeStack(signatures[0], pubKeys[0]) | ||
} else if (type === scriptTypes.P2PK) { | ||
if (signatures.length === 1 && signatures[0] instanceof Buffer) return bscript.pubKey.input.encodeStack(signatures[0]) | ||
} else if (type === scriptTypes.MULTISIG) { | ||
if (signatures.length > 0) { | ||
signatures = signatures.map(function (signature) { | ||
return signature || ops.OP_0 | ||
}) | ||
if (!allowIncomplete) { | ||
// remove blank signatures | ||
signatures = signatures.filter(function (x) { return x !== ops.OP_0 }) | ||
} | ||
return bscript.multisig.input.encodeStack(signatures /* see if it's necessary first */) | ||
} | ||
} else { | ||
throw new Error('Not yet supported') | ||
} | ||
// Extract hashType, pubKeys, signatures and prevOutScript | ||
var result = processScript(prevOutType, scriptSigChunks) | ||
if (!allowIncomplete) throw new Error('Not enough signatures provided') | ||
return [] | ||
} | ||
function buildInput (input, allowIncomplete) { | ||
var scriptType = input.prevOutType | ||
var sig = [] | ||
var witness = [] | ||
if (SIGNABLE.indexOf(scriptType) !== -1) { | ||
sig = buildStack(scriptType, input.signatures, input.pubKeys, input.script, allowIncomplete) | ||
} | ||
var p2sh = false | ||
if (scriptType === bscript.types.P2SH) { | ||
// We can remove this error later when we have a guarantee prepareInput | ||
// rejects unsignable scripts - it MUST be signable at this point. | ||
if (P2SH.indexOf(input.redeemScriptType) === -1) { | ||
throw new Error('Impossible to sign this type') | ||
} | ||
p2sh = true | ||
if (SIGNABLE.indexOf(input.redeemScriptType) !== -1) { | ||
sig = buildStack(input.redeemScriptType, input.signatures, input.pubKeys, allowIncomplete) | ||
} | ||
// If it wasn't SIGNABLE, it's witness, defer to that | ||
scriptType = input.redeemScriptType | ||
} | ||
if (scriptType === bscript.types.P2WPKH) { | ||
// P2WPKH is a special case of P2PKH | ||
witness = buildStack(bscript.types.P2PKH, input.signatures, input.pubKeys, allowIncomplete) | ||
} else if (scriptType === bscript.types.P2WSH) { | ||
// We can remove this check later | ||
if (SIGNABLE.indexOf(input.witnessScriptType) !== -1) { | ||
witness = buildStack(input.witnessScriptType, input.signatures, input.pubKeys, allowIncomplete) | ||
witness.push(input.witnessScript) | ||
} else { | ||
// We can remove this error later when we have a guarantee prepareInput | ||
// rejects unsignble scripts - it MUST be signable at this point. | ||
throw new Error() | ||
} | ||
scriptType = input.witnessScriptType | ||
} | ||
// append redeemScript if necessary | ||
if (p2sh) { | ||
sig.push(input.redeemScript) | ||
} | ||
return { | ||
hashType: result.hashType, | ||
prevOutScript: result.prevOutScript, | ||
prevOutType: prevOutType, | ||
pubKeys: result.pubKeys, | ||
redeemScript: result.redeemScript, | ||
redeemScriptType: result.redeemScriptType, | ||
signatures: result.signatures | ||
type: scriptType, | ||
script: bscript.compile(sig), | ||
witness: witness | ||
} | ||
} | ||
function TransactionBuilder (network) { | ||
function TransactionBuilder (network, maximumFeeRate) { | ||
this.prevTxMap = {} | ||
this.prevOutScripts = {} | ||
this.prevOutTypes = {} | ||
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 || 1000 | ||
this.inputs = [] | ||
@@ -179,12 +485,7 @@ this.tx = new Transaction() | ||
// Copy other transaction fields | ||
txb.tx.version = transaction.version | ||
txb.tx.locktime = transaction.locktime | ||
// Copy transaction fields | ||
txb.setVersion(transaction.version) | ||
txb.setLockTime(transaction.locktime) | ||
// Extract/add inputs | ||
transaction.ins.forEach(function (txIn) { | ||
txb.addInput(txIn.hash, txIn.index, txIn.sequence) | ||
}) | ||
// Extract/add outputs | ||
// Copy outputs (done first to avoid signature invalidation) | ||
transaction.outs.forEach(function (txOut) { | ||
@@ -194,10 +495,14 @@ txb.addOutput(txOut.script, txOut.value) | ||
// Extract/add signatures | ||
txb.inputs = transaction.ins.map(function (txIn, vin) { | ||
// TODO: verify whether extractInput is sane with coinbase scripts | ||
if (Transaction.isCoinbaseHash(txIn.hash)) { | ||
throw new Error('coinbase inputs not supported') | ||
} | ||
// Copy inputs | ||
transaction.ins.forEach(function (txIn) { | ||
txb.__addInputUnsafe(txIn.hash, txIn.index, { | ||
sequence: txIn.sequence, | ||
script: txIn.script, | ||
witness: txIn.witness | ||
}) | ||
}) | ||
return extractInput(transaction, txIn, vin) | ||
// fix some things not possible through the public API | ||
txb.inputs.forEach(function (input, i) { | ||
fixMultisigOrder(input, transaction, i) | ||
}) | ||
@@ -209,58 +514,71 @@ | ||
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) { | ||
if (!this.__canModifyInputs()) { | ||
throw new Error('No, this would invalidate signatures') | ||
} | ||
var value | ||
// is it a hex string? | ||
if (typeof txHash === 'string') { | ||
// transaction hashs's are displayed in reverse order, un-reverse it | ||
txHash = bufferReverse(new Buffer(txHash, 'hex')) | ||
txHash = new Buffer(txHash, 'hex').reverse() | ||
// is it a Transaction object? | ||
} else if (txHash instanceof Transaction) { | ||
prevOutScript = txHash.outs[vout].script | ||
var 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') | ||
} | ||
var prevTxOut = txHash.toString('hex') + ':' + vout | ||
if (this.prevTxMap[prevTxOut] !== undefined) throw new Error('Duplicate TxOut: ' + prevTxOut) | ||
var input = {} | ||
if (prevOutScript) { | ||
var prevOutScriptChunks = bscript.decompile(prevOutScript) | ||
var prevOutType = bscript.classifyOutput(prevOutScriptChunks) | ||
// if we can, extract pubKey information | ||
switch (prevOutType) { | ||
case 'multisig': | ||
input.pubKeys = prevOutScriptChunks.slice(1, -2) | ||
input.signatures = input.pubKeys.map(function () { return undefined }) | ||
// derive what we can from the scriptSig | ||
if (options.script !== undefined) { | ||
input = expandInput(options.script, options.witness) | ||
} | ||
break | ||
// if an input value was given, retain it | ||
if (options.value !== undefined) { | ||
input.value = options.value | ||
} | ||
case 'pubkey': | ||
input.pubKeys = prevOutScriptChunks.slice(0, 1) | ||
input.signatures = [undefined] | ||
// derive what we can from the previous transactions output script | ||
if (!input.prevOutScript && options.prevOutScript) { | ||
var prevOutType | ||
break | ||
} | ||
if (!input.pubKeys && !input.signatures) { | ||
var expanded = expandOutput(options.prevOutScript) | ||
if (prevOutType !== 'scripthash') { | ||
input.scriptType = prevOutType | ||
if (expanded.pubKeys) { | ||
input.pubKeys = expanded.pubKeys | ||
input.signatures = expanded.signatures | ||
} | ||
prevOutType = expanded.scriptType | ||
} | ||
input.prevOutScript = prevOutScript | ||
input.prevOutType = prevOutType | ||
input.prevOutScript = options.prevOutScript | ||
input.prevOutType = prevOutType || bscript.classifyOutput(options.prevOutScript) | ||
} | ||
// if signatures exist, adding inputs is only acceptable if SIGHASH_ANYONECANPAY is used | ||
// throw if any signatures *didn't* use SIGHASH_ANYONECANPAY | ||
if (!this.inputs.every(function (otherInput) { | ||
// no signature | ||
if (otherInput.hashType === undefined) return true | ||
return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY | ||
})) { | ||
throw new Error('No, this would invalidate signatures') | ||
} | ||
var prevOut = txHash.toString('hex') + ':' + vout | ||
if (this.prevTxMap[prevOut]) throw new Error('Transaction is already an input') | ||
var vin = this.tx.addInput(txHash, vout, sequence) | ||
var vin = this.tx.addInput(txHash, vout, options.sequence, options.scriptSig) | ||
this.inputs[vin] = input | ||
this.prevTxMap[prevOut] = vin | ||
this.prevTxMap[prevTxOut] = vin | ||
@@ -271,19 +589,3 @@ return vin | ||
TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) { | ||
var nOutputs = this.tx.outs.length | ||
// if signatures exist, adding outputs is only acceptable if SIGHASH_NONE or SIGHASH_SINGLE is used | ||
// throws if any signatures didn't use SIGHASH_NONE|SIGHASH_SINGLE | ||
if (!this.inputs.every(function (input, index) { | ||
// no signature | ||
if (input.hashType === undefined) return true | ||
var hashTypeMod = input.hashType & 0x1f | ||
if (hashTypeMod === Transaction.SIGHASH_NONE) return true | ||
if (hashTypeMod === Transaction.SIGHASH_SINGLE) { | ||
// account for SIGHASH_SINGLE signing of a non-existing output, aka the "SIGHASH_SINGLE" bug | ||
return index < nOutputs | ||
} | ||
return false | ||
})) { | ||
if (!this.__canModifyOutputs()) { | ||
throw new Error('No, this would invalidate signatures') | ||
@@ -307,50 +609,2 @@ } | ||
var canBuildTypes = { | ||
'multisig': true, | ||
'pubkey': true, | ||
'pubkeyhash': true | ||
} | ||
function buildFromInputData (input, scriptType, parentType, redeemScript, allowIncomplete) { | ||
var scriptSig | ||
switch (scriptType) { | ||
case 'pubkeyhash': | ||
var pkhSignature = input.signatures[0].toScriptSignature(input.hashType) | ||
scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0]) | ||
break | ||
case 'pubkey': | ||
var pkSignature = input.signatures[0].toScriptSignature(input.hashType) | ||
scriptSig = bscript.pubKeyInput(pkSignature) | ||
break | ||
case 'multisig': | ||
var msSignatures = input.signatures.map(function (signature) { | ||
return signature && signature.toScriptSignature(input.hashType) | ||
}) | ||
// fill in blanks with OP_0 | ||
if (allowIncomplete) { | ||
for (var i = 0; i < msSignatures.length; ++i) { | ||
msSignatures[i] = msSignatures[i] || ops.OP_0 | ||
} | ||
// remove blank signatures | ||
} else { | ||
msSignatures = msSignatures.filter(function (x) { return x }) | ||
} | ||
scriptSig = bscript.multisigInput(msSignatures, allowIncomplete ? undefined : redeemScript) | ||
break | ||
} | ||
// wrap as scriptHash if necessary | ||
if (parentType === 'scripthash') { | ||
scriptSig = bscript.scriptHashInput(scriptSig, redeemScript) | ||
} | ||
return scriptSig | ||
} | ||
TransactionBuilder.prototype.__build = function (allowIncomplete) { | ||
@@ -363,25 +617,25 @@ if (!allowIncomplete) { | ||
var tx = this.tx.clone() | ||
// Create script signatures from inputs | ||
this.inputs.forEach(function (input, index) { | ||
var scriptType = input.redeemScriptType || input.prevOutType | ||
var scriptSig | ||
this.inputs.forEach(function (input, i) { | ||
var scriptType = input.witnessScriptType || input.redeemScriptType || input.prevOutType | ||
if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete') | ||
var result = buildInput(input, allowIncomplete) | ||
// skip if no result | ||
if (!allowIncomplete) { | ||
if (!scriptType) throw new Error('Transaction is not complete') | ||
if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported') | ||
// XXX: only relevant to types that need signatures | ||
if (!input.signatures) throw new Error('Transaction is missing signatures') | ||
if (SIGNABLE.indexOf(result.type) === -1 && result.type !== bscript.types.P2WPKH) { | ||
throw new Error(result.type + ' not supported') | ||
} | ||
} | ||
if (input.signatures) { | ||
scriptSig = buildFromInputData(input, scriptType, input.prevOutType, input.redeemScript, allowIncomplete) | ||
} | ||
tx.setInputScript(i, result.script) | ||
tx.setWitness(i, result.witness) | ||
}) | ||
// did we build a scriptSig? Buffer('') is allowed | ||
if (scriptSig) { | ||
tx.setInputScript(index, scriptSig) | ||
if (!allowIncomplete) { | ||
// do not rely on this, its merely a last resort | ||
if (this.__overMaximumFees(tx.byteLength())) { | ||
throw new Error('Transaction has absurd fees') | ||
} | ||
}) | ||
} | ||
@@ -391,121 +645,107 @@ return tx | ||
function extractFromOutputScript (outputScript, keyPair, kpPubKey) { | ||
var scriptType = bscript.classifyOutput(outputScript) | ||
var outputScriptChunks = bscript.decompile(outputScript) | ||
switch (scriptType) { | ||
case 'pubkeyhash': | ||
var pkh1 = outputScriptChunks[2] | ||
var pkh2 = bcrypto.hash160(keyPair.getPublicKeyBuffer()) | ||
if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') | ||
return { | ||
pubKeys: [kpPubKey], | ||
scriptType: scriptType | ||
} | ||
case 'pubkey': | ||
return { | ||
pubKeys: outputScriptChunks.slice(0, 1), | ||
scriptType: scriptType | ||
} | ||
case 'multisig': | ||
return { | ||
pubKeys: outputScriptChunks.slice(1, -2), | ||
scriptType: scriptType | ||
} | ||
} | ||
function canSign (input) { | ||
return input.prevOutScript !== undefined && | ||
input.signScript !== undefined && | ||
input.pubKeys !== undefined && | ||
input.signatures !== undefined && | ||
input.signatures.length === input.pubKeys.length && | ||
input.pubKeys.length > 0 && | ||
input.witness !== undefined | ||
} | ||
TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) { | ||
TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { | ||
if (keyPair.network !== this.network) throw new Error('Inconsistent network') | ||
if (!this.inputs[index]) throw new Error('No input at index: ' + index) | ||
if (!this.inputs[vin]) throw new Error('No input at index: ' + vin) | ||
hashType = hashType || Transaction.SIGHASH_ALL | ||
var input = this.inputs[index] | ||
var canSign = input.hashType && | ||
input.prevOutScript && | ||
input.prevOutType && | ||
input.pubKeys && | ||
input.redeemScriptType && | ||
input.signatures && | ||
input.signatures.length === input.pubKeys.length | ||
var 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') | ||
} | ||
var kpPubKey = keyPair.getPublicKeyBuffer() | ||
var signatureScript | ||
if (!canSign(input)) { | ||
prepareInput(input, kpPubKey, redeemScript, witnessValue, witnessScript) | ||
if (!canSign(input)) throw Error(input.prevOutType + ' not supported') | ||
} | ||
// are we ready to sign? | ||
if (canSign) { | ||
// if redeemScript was provided, enforce consistency | ||
if (redeemScript) { | ||
if (!bufferEquals(input.redeemScript, redeemScript)) throw new Error('Inconsistent redeemScript') | ||
} | ||
// ready to sign | ||
var signatureHash | ||
if (input.witness) { | ||
signatureHash = this.tx.hashForWitnessV0(vin, input.signScript, witnessValue, hashType) | ||
} else { | ||
signatureHash = this.tx.hashForSignature(vin, input.signScript, hashType) | ||
} | ||
// enforce in order signing of public keys | ||
var signed = input.pubKeys.some(function (pubKey, i) { | ||
if (!kpPubKey.equals(pubKey)) return false | ||
if (input.signatures[i]) throw new Error('Signature already exists') | ||
if (input.hashType !== hashType) throw new Error('Inconsistent hashType') | ||
input.signatures[i] = keyPair.sign(signatureHash).toScriptSignature(hashType) | ||
return true | ||
}) | ||
// no? prepare | ||
} else { | ||
// must be pay-to-scriptHash? | ||
if (redeemScript) { | ||
// if we have a prevOutScript, enforce scriptHash equality to the redeemScript | ||
if (input.prevOutScript) { | ||
if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH') | ||
if (!signed) throw new Error('Key pair cannot sign for this input') | ||
} | ||
var scriptHash = bscript.decompile(input.prevOutScript)[1] | ||
if (!bufferEquals(scriptHash, bcrypto.hash160(redeemScript))) throw new Error('RedeemScript does not match ' + scriptHash.toString('hex')) | ||
} | ||
function signatureHashType (buffer) { | ||
return buffer.readUInt8(buffer.length - 1) | ||
} | ||
var extracted = extractFromOutputScript(redeemScript, keyPair, kpPubKey) | ||
if (!extracted) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"') | ||
TransactionBuilder.prototype.__canModifyInputs = function () { | ||
return this.inputs.every(function (input) { | ||
// any signatures? | ||
if (input.signatures === undefined) return true | ||
// if we don't have a prevOutScript, generate a P2SH script | ||
if (!input.prevOutScript) { | ||
input.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) | ||
input.prevOutType = 'scripthash' | ||
} | ||
return input.signatures.every(function (signature) { | ||
if (!signature) return true | ||
var hashType = signatureHashType(signature) | ||
input.pubKeys = extracted.pubKeys | ||
input.redeemScript = redeemScript | ||
input.redeemScriptType = extracted.scriptType | ||
input.signatures = extracted.pubKeys.map(function () { return undefined }) | ||
} else { | ||
// pay-to-scriptHash is not possible without a redeemScript | ||
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') | ||
// if SIGHASH_ANYONECANPAY is set, signatures would not | ||
// be invalidated by more inputs | ||
return hashType & Transaction.SIGHASH_ANYONECANPAY | ||
}) | ||
}) | ||
} | ||
// if we don't have a scriptType, assume pubKeyHash otherwise | ||
if (!input.scriptType) { | ||
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(keyPair.getPublicKeyBuffer())) | ||
input.prevOutType = 'pubkeyhash' | ||
TransactionBuilder.prototype.__canModifyOutputs = function () { | ||
var nInputs = this.tx.ins.length | ||
var nOutputs = this.tx.outs.length | ||
input.pubKeys = [kpPubKey] | ||
input.scriptType = input.prevOutType | ||
input.signatures = [undefined] | ||
} else { | ||
// throw if we can't sign with it | ||
if (!input.pubKeys || !input.signatures) throw new Error(input.scriptType + ' not supported') | ||
} | ||
} | ||
return this.inputs.every(function (input) { | ||
if (input.signatures === undefined) return true | ||
input.hashType = hashType | ||
} | ||
return input.signatures.every(function (signature) { | ||
if (!signature) return true | ||
var hashType = signatureHashType(signature) | ||
// ready to sign? | ||
signatureScript = signatureScript || input.redeemScript || input.prevOutScript | ||
var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) | ||
var 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 | ||
} | ||
}) | ||
}) | ||
} | ||
// enforce in order signing of public keys | ||
var valid = input.pubKeys.some(function (pubKey, i) { | ||
if (!bufferEquals(kpPubKey, pubKey)) return false | ||
if (input.signatures[i]) throw new Error('Signature already exists') | ||
TransactionBuilder.prototype.__overMaximumFees = function (bytes) { | ||
// not all inputs will have .value defined | ||
var incoming = this.inputs.reduce(function (a, x) { return a + (x.value >>> 0) }, 0) | ||
input.signatures[i] = keyPair.sign(signatureHash) | ||
// but all outputs do, and if we have any input value | ||
// we can immediately determine if the outputs are too small | ||
var outgoing = this.tx.outs.reduce(function (a, x) { return a + x.value }, 0) | ||
var fee = incoming - outgoing | ||
var feeRate = fee / bytes | ||
return true | ||
}) | ||
if (!valid) throw new Error('Key pair cannot sign for this input') | ||
return feeRate > this.maximumFeeRate | ||
} | ||
module.exports = TransactionBuilder |
var bcrypto = require('./crypto') | ||
var bscript = require('./script') | ||
var bufferutils = require('./bufferutils') | ||
var bufferReverse = require('buffer-reverse') | ||
var opcodes = require('./opcodes.json') | ||
var opcodes = require('bitcoin-ops') | ||
var typeforce = require('typeforce') | ||
var types = require('./types') | ||
function varSliceSize (someScript) { | ||
var length = someScript.length | ||
return bufferutils.varIntSize(length) + length | ||
} | ||
function vectorSize (someVector) { | ||
var length = someVector.length | ||
return bufferutils.varIntSize(length) + someVector.reduce(function (sum, witness) { | ||
return sum + varSliceSize(witness) | ||
}, 0) | ||
} | ||
function Transaction () { | ||
@@ -21,3 +34,15 @@ this.version = 1 | ||
Transaction.SIGHASH_ANYONECANPAY = 0x80 | ||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00 | ||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01 | ||
var EMPTY_SCRIPT = new Buffer(0) | ||
var EMPTY_WITNESS = [] | ||
var ZERO = new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') | ||
var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex') | ||
var VALUE_UINT64_MAX = new Buffer('ffffffffffffffff', 'hex') | ||
var BLANK_OUTPUT = { | ||
script: EMPTY_SCRIPT, | ||
valueBuffer: VALUE_UINT64_MAX | ||
} | ||
Transaction.fromBuffer = function (buffer, __noStrict) { | ||
@@ -36,2 +61,8 @@ var offset = 0 | ||
function readInt32 () { | ||
var i = buffer.readInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
function readUInt64 () { | ||
@@ -49,9 +80,26 @@ var i = bufferutils.readUInt64LE(buffer, offset) | ||
function readScript () { | ||
function readVarSlice () { | ||
return readSlice(readVarInt()) | ||
} | ||
function readVector () { | ||
var count = readVarInt() | ||
var vector = [] | ||
for (var i = 0; i < count; i++) vector.push(readVarSlice()) | ||
return vector | ||
} | ||
var tx = new Transaction() | ||
tx.version = readUInt32() | ||
tx.version = readInt32() | ||
var marker = buffer.readUInt8(offset) | ||
var flag = buffer.readUInt8(offset + 1) | ||
var hasWitnesses = false | ||
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && | ||
flag === Transaction.ADVANCED_TRANSACTION_FLAG) { | ||
offset += 2 | ||
hasWitnesses = true | ||
} | ||
var vinLen = readVarInt() | ||
@@ -62,4 +110,5 @@ for (var i = 0; i < vinLen; ++i) { | ||
index: readUInt32(), | ||
script: readScript(), | ||
sequence: readUInt32() | ||
script: readVarSlice(), | ||
sequence: readUInt32(), | ||
witness: EMPTY_WITNESS | ||
}) | ||
@@ -72,6 +121,15 @@ } | ||
value: readUInt64(), | ||
script: readScript() | ||
script: readVarSlice() | ||
}) | ||
} | ||
if (hasWitnesses) { | ||
for (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() | ||
@@ -86,3 +144,3 @@ | ||
Transaction.fromHex = function (hex) { | ||
return Transaction.fromBuffer(new Buffer(hex, 'hex')) | ||
return Transaction.fromBuffer(Buffer.from(hex, 'hex')) | ||
} | ||
@@ -102,4 +160,2 @@ | ||
var EMPTY_SCRIPT = new Buffer(0) | ||
Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) { | ||
@@ -122,3 +178,4 @@ typeforce(types.tuple( | ||
script: scriptSig || EMPTY_SCRIPT, | ||
sequence: sequence | ||
sequence: sequence, | ||
witness: EMPTY_WITNESS | ||
}) - 1) | ||
@@ -128,3 +185,3 @@ } | ||
Transaction.prototype.addOutput = function (scriptPubKey, value) { | ||
typeforce(types.tuple(types.Buffer, types.UInt53), arguments) | ||
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments) | ||
@@ -138,15 +195,22 @@ // Add the output and return the output's index | ||
Transaction.prototype.hasWitnesses = function () { | ||
return this.ins.some(function (x) { | ||
return x.witness.length !== 0 | ||
}) | ||
} | ||
Transaction.prototype.byteLength = function () { | ||
function scriptSize (someScript) { | ||
var length = someScript.length | ||
return this.__byteLength(true) | ||
} | ||
return bufferutils.varIntSize(length) + length | ||
} | ||
Transaction.prototype.__byteLength = function (__allowWitness) { | ||
var hasWitnesses = __allowWitness && this.hasWitnesses() | ||
return ( | ||
8 + | ||
(hasWitnesses ? 10 : 8) + | ||
bufferutils.varIntSize(this.ins.length) + | ||
bufferutils.varIntSize(this.outs.length) + | ||
this.ins.reduce(function (sum, input) { return sum + 40 + scriptSize(input.script) }, 0) + | ||
this.outs.reduce(function (sum, output) { return sum + 8 + scriptSize(output.script) }, 0) | ||
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) | ||
) | ||
@@ -165,3 +229,4 @@ } | ||
script: txIn.script, | ||
sequence: txIn.sequence | ||
sequence: txIn.sequence, | ||
witness: txIn.witness | ||
} | ||
@@ -180,9 +245,2 @@ }) | ||
var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex') | ||
var VALUE_UINT64_MAX = new Buffer('ffffffffffffffff', 'hex') | ||
var BLANK_OUTPUT = { | ||
script: EMPTY_SCRIPT, | ||
valueBuffer: VALUE_UINT64_MAX | ||
} | ||
/** | ||
@@ -234,4 +292,4 @@ * Hash transaction for signing a specific input. | ||
// ignore sequence numbers (except at inIndex) | ||
txTmp.ins.forEach(function (input, i) { | ||
if (i === inIndex) return | ||
txTmp.ins.forEach(function (input, y) { | ||
if (y === inIndex) return | ||
@@ -255,5 +313,5 @@ input.sequence = 0 | ||
// serialize and hash | ||
var buffer = new Buffer(txTmp.byteLength() + 4) | ||
var buffer = new Buffer(txTmp.__byteLength(false) + 4) | ||
buffer.writeInt32LE(hashType, buffer.length - 4) | ||
txTmp.toBuffer(buffer, 0) | ||
txTmp.__toBuffer(buffer, 0, false) | ||
@@ -263,4 +321,87 @@ return bcrypto.hash256(buffer) | ||
Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) { | ||
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments) | ||
var 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) { toffset += bufferutils.writeVarInt(tbuffer, i, toffset) } | ||
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) } | ||
var hashOutputs = ZERO | ||
var hashPrevouts = ZERO | ||
var hashSequence = ZERO | ||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { | ||
tbuffer = new Buffer(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 = new Buffer(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) { | ||
var txOutsSize = this.outs.reduce(function (sum, output) { | ||
return sum + 8 + varSliceSize(output.script) | ||
}, 0) | ||
tbuffer = new Buffer(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) { | ||
var output = this.outs[inIndex] | ||
tbuffer = new Buffer(8 + varSliceSize(output.script)) | ||
toffset = 0 | ||
writeUInt64(output.value) | ||
writeVarSlice(output.script) | ||
hashOutputs = bcrypto.hash256(tbuffer) | ||
} | ||
tbuffer = new Buffer(156 + varSliceSize(prevOutScript)) | ||
toffset = 0 | ||
var 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()) | ||
return bcrypto.hash256(this.__toBuffer(undefined, undefined, false)) | ||
} | ||
@@ -270,30 +411,31 @@ | ||
// transaction hash's are displayed in reverse order | ||
return bufferReverse(this.getHash()).toString('hex') | ||
return this.getHash().reverse().toString('hex') | ||
} | ||
Transaction.prototype.toBuffer = function (buffer, initialOffset) { | ||
if (!buffer) buffer = new Buffer(this.byteLength()) | ||
return this.__toBuffer(buffer, initialOffset, true) | ||
} | ||
Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitness) { | ||
if (!buffer) buffer = new Buffer(this.__byteLength(__allowWitness)) | ||
var offset = initialOffset || 0 | ||
function writeSlice (slice) { | ||
slice.copy(buffer, offset) | ||
offset += slice.length | ||
} | ||
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) { offset += bufferutils.writeVarInt(buffer, i, offset) } | ||
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) } | ||
function writeVector (vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice) } | ||
function writeUInt32 (i) { | ||
buffer.writeUInt32LE(i, offset) | ||
offset += 4 | ||
} | ||
writeInt32(this.version) | ||
function writeUInt64 (i) { | ||
bufferutils.writeUInt64LE(buffer, i, offset) | ||
offset += 8 | ||
} | ||
var hasWitnesses = __allowWitness && this.hasWitnesses() | ||
function writeVarInt (i) { | ||
var n = bufferutils.writeVarInt(buffer, i, offset) | ||
offset += n | ||
if (hasWitnesses) { | ||
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER) | ||
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) | ||
} | ||
writeUInt32(this.version) | ||
writeVarInt(this.ins.length) | ||
@@ -304,4 +446,3 @@ | ||
writeUInt32(txIn.index) | ||
writeVarInt(txIn.script.length) | ||
writeSlice(txIn.script) | ||
writeVarSlice(txIn.script) | ||
writeUInt32(txIn.sequence) | ||
@@ -318,6 +459,11 @@ }) | ||
writeVarInt(txOut.script.length) | ||
writeSlice(txOut.script) | ||
writeVarSlice(txOut.script) | ||
}) | ||
if (hasWitnesses) { | ||
this.ins.forEach(function (input) { | ||
writeVector(input.witness) | ||
}) | ||
} | ||
writeUInt32(this.locktime) | ||
@@ -327,3 +473,2 @@ | ||
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) | ||
return buffer | ||
@@ -342,2 +487,8 @@ } | ||
Transaction.prototype.setWitness = function (index, witness) { | ||
typeforce(types.tuple(types.Number, [types.Buffer]), arguments) | ||
this.ins[index].witness = witness | ||
} | ||
module.exports = Transaction |
var typeforce = require('typeforce') | ||
function nBuffer (value, n) { | ||
typeforce(types.Buffer, value) | ||
if (value.length !== n) throw new typeforce.TfTypeError('Expected ' + (n * 8) + '-bit Buffer, got ' + (value.length * 8) + '-bit Buffer') | ||
return true | ||
} | ||
function Hash160bit (value) { return nBuffer(value, 20) } | ||
function Hash256bit (value) { return nBuffer(value, 32) } | ||
function Buffer256bit (value) { return nBuffer(value, 32) } | ||
var UINT53_MAX = Math.pow(2, 53) - 1 | ||
var UINT31_MAX = Math.pow(2, 31) - 1 | ||
function UInt2 (value) { return (value & 3) === value } | ||
function UInt8 (value) { return (value & 0xff) === value } | ||
function UInt32 (value) { return (value >>> 0) === value } | ||
function UInt31 (value) { | ||
return UInt32(value) && value <= UINT31_MAX | ||
return typeforce.UInt32(value) && value <= UINT31_MAX | ||
} | ||
function UInt53 (value) { | ||
return typeforce.Number(value) && | ||
value >= 0 && | ||
value <= UINT53_MAX && | ||
Math.floor(value) === value | ||
function BIP32Path (value) { | ||
return typeforce.String(value) && value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) | ||
} | ||
BIP32Path.toJSON = function () { return 'BIP32 derivation path' } | ||
function Bip32Path (value) { | ||
return typeforce.String(value) && | ||
value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) | ||
var SATOSHI_MAX = 21 * 1e14 | ||
function Satoshi (value) { | ||
return typeforce.UInt53(value) && value <= SATOSHI_MAX | ||
} | ||
@@ -43,9 +27,8 @@ | ||
bip32: { | ||
public: UInt32, | ||
private: UInt32 | ||
public: typeforce.UInt32, | ||
private: typeforce.UInt32 | ||
}, | ||
pubKeyHash: UInt8, | ||
scriptHash: UInt8, | ||
wif: UInt8, | ||
dustThreshold: UInt53 | ||
pubKeyHash: typeforce.UInt8, | ||
scriptHash: typeforce.UInt8, | ||
wif: typeforce.UInt8 | ||
}) | ||
@@ -56,14 +39,11 @@ | ||
BigInt: BigInt, | ||
Buffer256bit: Buffer256bit, | ||
BIP32Path: BIP32Path, | ||
Buffer256bit: typeforce.BufferN(32), | ||
ECPoint: ECPoint, | ||
ECSignature: ECSignature, | ||
Hash160bit: Hash160bit, | ||
Hash256bit: Hash256bit, | ||
Hash160bit: typeforce.BufferN(20), | ||
Hash256bit: typeforce.BufferN(32), | ||
Network: Network, | ||
UInt2: UInt2, | ||
UInt8: UInt8, | ||
UInt31: UInt31, | ||
UInt32: UInt32, | ||
UInt53: UInt53, | ||
Bip32Path: Bip32Path | ||
Satoshi: Satoshi, | ||
UInt31: UInt31 | ||
} | ||
@@ -70,0 +50,0 @@ |
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
107349
10
41
2587
189
13
1
+ Addedbitcoin-ops@^1.3.0
+ Addedmerkle-lib@^2.0.10
+ Addedpushdata-bitcoin@^1.0.1
+ Addedvaruint-bitcoin@^1.0.4
+ Addedbase-x@3.0.10(transitive)
+ Addedbitcoin-ops@1.4.1(transitive)
+ Addedbs58@4.0.1(transitive)
+ Addedbs58check@2.1.2(transitive)
+ Addedmerkle-lib@2.0.10(transitive)
+ Addedpushdata-bitcoin@1.0.1(transitive)
+ Addedvaruint-bitcoin@1.1.2(transitive)
- Removedbuffer-compare@^1.1.0
- Removedbuffer-equals@^1.0.3
- Removedbuffer-reverse@^1.0.0
- Removedbase-x@1.1.0(transitive)
- Removedbs58@3.1.0(transitive)
- Removedbs58check@1.3.4(transitive)
- Removedbuffer-compare@1.1.1(transitive)
- Removedbuffer-equals@1.0.4(transitive)
- Removedbuffer-reverse@1.0.1(transitive)
Updatedbs58check@^2.0.0
Updatedtypeforce@^1.8.7