From bc28949056f4cd4841c3391c06085695d2a12c8a Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 4 Jan 2019 18:33:02 +0900 Subject: [PATCH] Commit js, ts, and definitions in separate folders --- .gitignore | 1 - package.json | 8 +- src/address.js | 99 +++ src/block.js | 190 +++++ src/bufferutils.js | 42 ++ src/classify.js | 75 ++ src/crypto.js | 23 + src/ecpair.js | 97 +++ src/index.js | 24 + src/networks.js | 35 + src/payments/embed.js | 52 ++ src/payments/index.js | 18 + src/payments/lazy.js | 32 + src/payments/p2ms.js | 144 ++++ src/payments/p2pk.js | 80 ++ src/payments/p2pkh.js | 144 ++++ src/payments/p2sh.js | 191 +++++ src/payments/p2wpkh.js | 145 ++++ src/payments/p2wsh.js | 178 +++++ src/script.js | 188 +++++ src/script_number.js | 60 ++ src/script_signature.js | 58 ++ src/templates/multisig/index.js | 6 + src/templates/multisig/input.js | 21 + src/templates/multisig/output.js | 34 + src/templates/nulldata.js | 14 + src/templates/pubkey/index.js | 6 + src/templates/pubkey/input.js | 11 + src/templates/pubkey/output.js | 13 + src/templates/pubkeyhash/index.js | 6 + src/templates/pubkeyhash/input.js | 12 + src/templates/pubkeyhash/output.js | 16 + src/templates/scripthash/index.js | 6 + src/templates/scripthash/input.js | 43 ++ src/templates/scripthash/output.js | 14 + src/templates/witnesscommitment/index.js | 4 + src/templates/witnesscommitment/output.js | 30 + src/templates/witnesspubkeyhash/index.js | 6 + src/templates/witnesspubkeyhash/input.js | 15 + src/templates/witnesspubkeyhash/output.js | 13 + src/templates/witnessscripthash/index.js | 6 + src/templates/witnessscripthash/input.js | 34 + src/templates/witnessscripthash/output.js | 13 + src/transaction.js | 448 +++++++++++ src/transaction_builder.js | 699 ++++++++++++++++++ src/types.js | 48 ++ test/address.js | 6 +- test/bufferutils.js | 2 +- test/classify.js | 20 +- test/crypto.js | 2 +- test/ecpair.js | 6 +- test/payments.js | 2 +- test/payments.utils.js | 4 +- test/script.js | 2 +- test/script_number.js | 2 +- test/script_signature.js | 2 +- test/transaction.js | 2 +- test/transaction_builder.js | 10 +- test/types.js | 2 +- {src => ts_src}/address.ts | 0 {src => ts_src}/block.ts | 0 {src => ts_src}/bufferutils.ts | 0 {src => ts_src}/classify.ts | 0 {src => ts_src}/crypto.ts | 0 {src => ts_src}/ecpair.ts | 0 {src => ts_src}/index.ts | 0 {src => ts_src}/networks.ts | 0 {src => ts_src}/payments/embed.ts | 0 {src => ts_src}/payments/index.ts | 0 {src => ts_src}/payments/lazy.ts | 0 {src => ts_src}/payments/p2ms.ts | 0 {src => ts_src}/payments/p2pk.ts | 0 {src => ts_src}/payments/p2pkh.ts | 0 {src => ts_src}/payments/p2sh.ts | 0 {src => ts_src}/payments/p2wpkh.ts | 0 {src => ts_src}/payments/p2wsh.ts | 0 {src => ts_src}/script.ts | 0 {src => ts_src}/script_number.ts | 0 {src => ts_src}/script_signature.ts | 0 {src => ts_src}/templates/multisig/index.ts | 0 {src => ts_src}/templates/multisig/input.ts | 0 {src => ts_src}/templates/multisig/output.ts | 0 {src => ts_src}/templates/nulldata.ts | 0 {src => ts_src}/templates/pubkey/index.ts | 0 {src => ts_src}/templates/pubkey/input.ts | 0 {src => ts_src}/templates/pubkey/output.ts | 0 {src => ts_src}/templates/pubkeyhash/index.ts | 0 {src => ts_src}/templates/pubkeyhash/input.ts | 0 .../templates/pubkeyhash/output.ts | 0 {src => ts_src}/templates/scripthash/index.ts | 0 {src => ts_src}/templates/scripthash/input.ts | 0 .../templates/scripthash/output.ts | 0 .../templates/witnesscommitment/index.ts | 0 .../templates/witnesscommitment/output.ts | 0 .../templates/witnesspubkeyhash/index.ts | 0 .../templates/witnesspubkeyhash/input.ts | 0 .../templates/witnesspubkeyhash/output.ts | 0 .../templates/witnessscripthash/index.ts | 0 .../templates/witnessscripthash/input.ts | 0 .../templates/witnessscripthash/output.ts | 0 {src => ts_src}/transaction.ts | 0 {src => ts_src}/transaction_builder.ts | 0 {src => ts_src}/types.ts | 0 tsconfig.json | 7 +- types/address.d.ts | 17 + types/block.d.ts | 27 + types/bufferutils.d.ts | 4 + types/classify.d.ts | 16 + types/crypto.d.ts | 6 + types/ecpair.d.ts | 34 + types/index.d.ts | 17 + types/networks.d.ts | 16 + types/payments/embed.d.ts | 2 + types/payments/index.d.ts | 30 + types/payments/lazy.d.ts | 2 + types/payments/p2ms.d.ts | 2 + types/payments/p2pk.d.ts | 2 + types/payments/p2pkh.d.ts | 2 + types/payments/p2sh.d.ts | 2 + types/payments/p2wpkh.d.ts | 2 + types/payments/p2wsh.d.ts | 2 + types/script.d.ts | 18 + types/script_number.d.ts | 3 + types/script_signature.d.ts | 8 + types/templates/multisig/index.d.ts | 3 + types/templates/multisig/input.d.ts | 5 + types/templates/multisig/output.d.ts | 5 + types/templates/nulldata.d.ts | 9 + types/templates/pubkey/index.d.ts | 3 + types/templates/pubkey/input.d.ts | 5 + types/templates/pubkey/output.d.ts | 5 + types/templates/pubkeyhash/index.d.ts | 3 + types/templates/pubkeyhash/input.d.ts | 5 + types/templates/pubkeyhash/output.d.ts | 5 + types/templates/scripthash/index.d.ts | 3 + types/templates/scripthash/input.d.ts | 5 + types/templates/scripthash/output.d.ts | 5 + types/templates/witnesscommitment/index.d.ts | 2 + types/templates/witnesscommitment/output.d.ts | 7 + types/templates/witnesspubkeyhash/index.d.ts | 3 + types/templates/witnesspubkeyhash/input.d.ts | 5 + types/templates/witnesspubkeyhash/output.d.ts | 5 + types/templates/witnessscripthash/index.d.ts | 3 + types/templates/witnessscripthash/input.d.ts | 5 + types/templates/witnessscripthash/output.d.ts | 5 + types/transaction.d.ts | 59 ++ types/transaction_builder.d.ts | 26 + types/types.d.ts | 25 + 148 files changed, 3850 insertions(+), 39 deletions(-) create mode 100644 src/address.js create mode 100644 src/block.js create mode 100644 src/bufferutils.js create mode 100644 src/classify.js create mode 100644 src/crypto.js create mode 100644 src/ecpair.js create mode 100644 src/index.js create mode 100644 src/networks.js create mode 100644 src/payments/embed.js create mode 100644 src/payments/index.js create mode 100644 src/payments/lazy.js create mode 100644 src/payments/p2ms.js create mode 100644 src/payments/p2pk.js create mode 100644 src/payments/p2pkh.js create mode 100644 src/payments/p2sh.js create mode 100644 src/payments/p2wpkh.js create mode 100644 src/payments/p2wsh.js create mode 100644 src/script.js create mode 100644 src/script_number.js create mode 100644 src/script_signature.js create mode 100644 src/templates/multisig/index.js create mode 100644 src/templates/multisig/input.js create mode 100644 src/templates/multisig/output.js create mode 100644 src/templates/nulldata.js create mode 100644 src/templates/pubkey/index.js create mode 100644 src/templates/pubkey/input.js create mode 100644 src/templates/pubkey/output.js create mode 100644 src/templates/pubkeyhash/index.js create mode 100644 src/templates/pubkeyhash/input.js create mode 100644 src/templates/pubkeyhash/output.js create mode 100644 src/templates/scripthash/index.js create mode 100644 src/templates/scripthash/input.js create mode 100644 src/templates/scripthash/output.js create mode 100644 src/templates/witnesscommitment/index.js create mode 100644 src/templates/witnesscommitment/output.js create mode 100644 src/templates/witnesspubkeyhash/index.js create mode 100644 src/templates/witnesspubkeyhash/input.js create mode 100644 src/templates/witnesspubkeyhash/output.js create mode 100644 src/templates/witnessscripthash/index.js create mode 100644 src/templates/witnessscripthash/input.js create mode 100644 src/templates/witnessscripthash/output.js create mode 100644 src/transaction.js create mode 100644 src/transaction_builder.js create mode 100644 src/types.js rename {src => ts_src}/address.ts (100%) rename {src => ts_src}/block.ts (100%) rename {src => ts_src}/bufferutils.ts (100%) rename {src => ts_src}/classify.ts (100%) rename {src => ts_src}/crypto.ts (100%) rename {src => ts_src}/ecpair.ts (100%) rename {src => ts_src}/index.ts (100%) rename {src => ts_src}/networks.ts (100%) rename {src => ts_src}/payments/embed.ts (100%) rename {src => ts_src}/payments/index.ts (100%) rename {src => ts_src}/payments/lazy.ts (100%) rename {src => ts_src}/payments/p2ms.ts (100%) rename {src => ts_src}/payments/p2pk.ts (100%) rename {src => ts_src}/payments/p2pkh.ts (100%) rename {src => ts_src}/payments/p2sh.ts (100%) rename {src => ts_src}/payments/p2wpkh.ts (100%) rename {src => ts_src}/payments/p2wsh.ts (100%) rename {src => ts_src}/script.ts (100%) rename {src => ts_src}/script_number.ts (100%) rename {src => ts_src}/script_signature.ts (100%) rename {src => ts_src}/templates/multisig/index.ts (100%) rename {src => ts_src}/templates/multisig/input.ts (100%) rename {src => ts_src}/templates/multisig/output.ts (100%) rename {src => ts_src}/templates/nulldata.ts (100%) rename {src => ts_src}/templates/pubkey/index.ts (100%) rename {src => ts_src}/templates/pubkey/input.ts (100%) rename {src => ts_src}/templates/pubkey/output.ts (100%) rename {src => ts_src}/templates/pubkeyhash/index.ts (100%) rename {src => ts_src}/templates/pubkeyhash/input.ts (100%) rename {src => ts_src}/templates/pubkeyhash/output.ts (100%) rename {src => ts_src}/templates/scripthash/index.ts (100%) rename {src => ts_src}/templates/scripthash/input.ts (100%) rename {src => ts_src}/templates/scripthash/output.ts (100%) rename {src => ts_src}/templates/witnesscommitment/index.ts (100%) rename {src => ts_src}/templates/witnesscommitment/output.ts (100%) rename {src => ts_src}/templates/witnesspubkeyhash/index.ts (100%) rename {src => ts_src}/templates/witnesspubkeyhash/input.ts (100%) rename {src => ts_src}/templates/witnesspubkeyhash/output.ts (100%) rename {src => ts_src}/templates/witnessscripthash/index.ts (100%) rename {src => ts_src}/templates/witnessscripthash/input.ts (100%) rename {src => ts_src}/templates/witnessscripthash/output.ts (100%) rename {src => ts_src}/transaction.ts (100%) rename {src => ts_src}/transaction_builder.ts (100%) rename {src => ts_src}/types.ts (100%) create mode 100644 types/address.d.ts create mode 100644 types/block.d.ts create mode 100644 types/bufferutils.d.ts create mode 100644 types/classify.d.ts create mode 100644 types/crypto.d.ts create mode 100644 types/ecpair.d.ts create mode 100644 types/index.d.ts create mode 100644 types/networks.d.ts create mode 100644 types/payments/embed.d.ts create mode 100644 types/payments/index.d.ts create mode 100644 types/payments/lazy.d.ts create mode 100644 types/payments/p2ms.d.ts create mode 100644 types/payments/p2pk.d.ts create mode 100644 types/payments/p2pkh.d.ts create mode 100644 types/payments/p2sh.d.ts create mode 100644 types/payments/p2wpkh.d.ts create mode 100644 types/payments/p2wsh.d.ts create mode 100644 types/script.d.ts create mode 100644 types/script_number.d.ts create mode 100644 types/script_signature.d.ts create mode 100644 types/templates/multisig/index.d.ts create mode 100644 types/templates/multisig/input.d.ts create mode 100644 types/templates/multisig/output.d.ts create mode 100644 types/templates/nulldata.d.ts create mode 100644 types/templates/pubkey/index.d.ts create mode 100644 types/templates/pubkey/input.d.ts create mode 100644 types/templates/pubkey/output.d.ts create mode 100644 types/templates/pubkeyhash/index.d.ts create mode 100644 types/templates/pubkeyhash/input.d.ts create mode 100644 types/templates/pubkeyhash/output.d.ts create mode 100644 types/templates/scripthash/index.d.ts create mode 100644 types/templates/scripthash/input.d.ts create mode 100644 types/templates/scripthash/output.d.ts create mode 100644 types/templates/witnesscommitment/index.d.ts create mode 100644 types/templates/witnesscommitment/output.d.ts create mode 100644 types/templates/witnesspubkeyhash/index.d.ts create mode 100644 types/templates/witnesspubkeyhash/input.d.ts create mode 100644 types/templates/witnesspubkeyhash/output.d.ts create mode 100644 types/templates/witnessscripthash/index.d.ts create mode 100644 types/templates/witnessscripthash/input.d.ts create mode 100644 types/templates/witnessscripthash/output.d.ts create mode 100644 types/transaction.d.ts create mode 100644 types/transaction_builder.d.ts create mode 100644 types/types.d.ts diff --git a/.gitignore b/.gitignore index 3d940fb..a6c0ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ coverage -dist node_modules .nyc_output npm-debug.log diff --git a/package.json b/package.json index 81761a9..c00414a 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "bitcoinjs-lib", "version": "4.0.2", "description": "Client-side Bitcoin JavaScript library", - "main": "./dist/src/index.js", - "types": "./dist/src/index.d.ts", + "main": "./src/index.js", + "types": "./types/index.d.ts", "engines": { "node": ">=8.0.0" }, @@ -24,7 +24,7 @@ "nobuild:coverage-html": "nyc report --reporter=html", "nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha", "nobuild:integration": "mocha --timeout 50000 test/integration/", - "nobuild:standard": "standard src/**/*.ts", + "nobuild:standard": "standard ts_src/**/*.ts", "nobuild:unit": "mocha", "prepare": "npm run build", "standard": "npm run build && npm run nobuild:standard", @@ -36,7 +36,7 @@ "url": "https://github.com/bitcoinjs/bitcoinjs-lib.git" }, "files": [ - "dist/src" + "src" ], "dependencies": { "@types/node": "^10.12.18", diff --git a/src/address.js b/src/address.js new file mode 100644 index 0000000..ed11e5c --- /dev/null +++ b/src/address.js @@ -0,0 +1,99 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const types = require("./types"); +const bscript = require("./script"); +const networks = require("./networks"); +const payments = require("./payments"); +const bech32 = require('bech32'); +const bs58check = require('bs58check'); +const typeforce = require('typeforce'); +function fromBase58Check(address) { + const payload = bs58check.decode(address); + // TODO: 4.0.0, move to "toOutputScript" + if (payload.length < 21) + throw new TypeError(address + ' is too short'); + if (payload.length > 21) + throw new TypeError(address + ' is too long'); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version: version, hash: hash }; +} +exports.fromBase58Check = fromBase58Check; +function fromBech32(address) { + const result = bech32.decode(address); + const data = bech32.fromWords(result.words.slice(1)); + return { + version: result.words[0], + prefix: result.prefix, + data: Buffer.from(data) + }; +} +exports.fromBech32 = fromBech32; +function toBase58Check(hash, version) { + typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(version, 0); + hash.copy(payload, 1); + return bs58check.encode(payload); +} +exports.toBase58Check = toBase58Check; +function toBech32(data, version, prefix) { + const words = bech32.toWords(data); + words.unshift(version); + return bech32.encode(prefix, words); +} +exports.toBech32 = toBech32; +function fromOutputScript(output, network) { + network = network || networks.bitcoin; + try { + return payments.p2pkh({ output, network }).address; + } + catch (e) { } + try { + return payments.p2sh({ output, network }).address; + } + catch (e) { } + try { + return payments.p2wpkh({ output, network }).address; + } + catch (e) { } + try { + return payments.p2wsh({ output, network }).address; + } + catch (e) { } + throw new Error(bscript.toASM(output) + ' has no matching Address'); +} +exports.fromOutputScript = fromOutputScript; +function toOutputScript(address, network) { + network = network || networks.bitcoin; + let decodeBase58 = undefined; + let decodeBech32 = undefined; + try { + decodeBase58 = fromBase58Check(address); + } + catch (e) { } + if (decodeBase58) { + if (decodeBase58.version === network.pubKeyHash) + return payments.p2pkh({ hash: decodeBase58.hash }).output; + if (decodeBase58.version === network.scriptHash) + return payments.p2sh({ hash: decodeBase58.hash }).output; + } + else { + try { + decodeBech32 = fromBech32(address); + } + catch (e) { } + if (decodeBech32) { + if (decodeBech32.prefix !== network.bech32) + throw new Error(address + ' has an invalid prefix'); + if (decodeBech32.version === 0) { + if (decodeBech32.data.length === 20) + return payments.p2wpkh({ hash: decodeBech32.data }).output; + if (decodeBech32.data.length === 32) + return payments.p2wsh({ hash: decodeBech32.data }).output; + } + } + } + throw new Error(address + ' has no matching Script'); +} +exports.toOutputScript = toOutputScript; diff --git a/src/block.js b/src/block.js new file mode 100644 index 0000000..5b4776f --- /dev/null +++ b/src/block.js @@ -0,0 +1,190 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const transaction_1 = require("./transaction"); +const types = require("./types"); +const bcrypto = require("./crypto"); +const bufferutils_1 = require("./bufferutils"); +const fastMerkleRoot = require('merkle-lib/fastRoot'); +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); +const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); +const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); +const errorBufferTooSmall = new Error('Buffer too small (< 80 bytes)'); +function txesHaveWitness(transactions) { + return transactions !== undefined && + transactions instanceof Array && + transactions[0] && + transactions[0].ins && + transactions[0].ins instanceof Array && + transactions[0].ins[0] && + transactions[0].ins[0].witness && + transactions[0].ins[0].witness instanceof Array && + transactions[0].ins[0].witness.length > 0; +} +class Block { + constructor() { + this.version = 1; + this.timestamp = 0; + this.bits = 0; + this.nonce = 0; + this.prevHash = undefined; + this.merkleRoot = undefined; + this.witnessCommit = undefined; + this.transactions = undefined; + } + static fromBuffer(buffer) { + if (buffer.length < 80) + throw errorBufferTooSmall; + let offset = 0; + const readSlice = (n) => { + offset += n; + return buffer.slice(offset - n, offset); + }; + const readUInt32 = () => { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + }; + const readInt32 = () => { + const i = buffer.readInt32LE(offset); + offset += 4; + return i; + }; + const block = new Block(); + block.version = readInt32(); + block.prevHash = readSlice(32); + block.merkleRoot = readSlice(32); + block.timestamp = readUInt32(); + block.bits = readUInt32(); + block.nonce = readUInt32(); + if (buffer.length === 80) + return block; + const readVarInt = () => { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + }; + const readTransaction = () => { + const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true); + offset += tx.byteLength(); + return tx; + }; + const nTransactions = readVarInt(); + block.transactions = []; + for (var i = 0; i < nTransactions; ++i) { + const tx = readTransaction(); + block.transactions.push(tx); + } + // This Block contains a witness commit + if (block.hasWitnessCommit()) { + // The merkle root for the witness data is in an OP_RETURN output. + // There is no rule for the index of the output, so use filter to find it. + // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed + // If multiple commits are found, the output with highest index is assumed. + let witnessCommits = block.transactions[0].outs + .filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))) + .map(out => out.script.slice(6, 38)); + // Use the commit with the highest output (should only be one though) + block.witnessCommit = witnessCommits[witnessCommits.length - 1]; + } + return block; + } + static fromHex(hex) { + return Block.fromBuffer(Buffer.from(hex, 'hex')); + } + static calculateTarget(bits) { + const exponent = ((bits & 0xff000000) >> 24) - 3; + const mantissa = bits & 0x007fffff; + const target = Buffer.alloc(32, 0); + target.writeUIntBE(mantissa, 29 - exponent, 3); + return target; + } + static calculateMerkleRoot(transactions, forWitness) { + typeforce([{ getHash: types.Function }], transactions); + if (transactions.length === 0) + throw errorMerkleNoTxes; + if (forWitness && !txesHaveWitness(transactions)) + throw errorWitnessNotSegwit; + const hashes = transactions.map(transaction => transaction.getHash(forWitness)); + const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); + return forWitness + ? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) + : rootHash; + } + hasWitnessCommit() { + return txesHaveWitness(this.transactions); + } + byteLength(headersOnly) { + if (headersOnly || !this.transactions) + return 80; + return 80 + varuint.encodingLength(this.transactions.length) + + this.transactions.reduce((a, x) => a + x.byteLength(), 0); + } + getHash() { + return bcrypto.hash256(this.toBuffer(true)); + } + getId() { + return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); + } + getUTCDate() { + const date = new Date(0); // epoch + date.setUTCSeconds(this.timestamp); + return date; + } + // TODO: buffer, offset compatibility + toBuffer(headersOnly) { + const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); + let offset = 0; + const writeSlice = (slice) => { + slice.copy(buffer, offset); + offset += slice.length; + }; + const writeInt32 = (i) => { + buffer.writeInt32LE(i, offset); + offset += 4; + }; + const writeUInt32 = (i) => { + buffer.writeUInt32LE(i, offset); + offset += 4; + }; + writeInt32(this.version); + writeSlice(this.prevHash); + writeSlice(this.merkleRoot); + writeUInt32(this.timestamp); + writeUInt32(this.bits); + writeUInt32(this.nonce); + if (headersOnly || !this.transactions) + return buffer; + varuint.encode(this.transactions.length, buffer, offset); + offset += varuint.encode.bytes; + this.transactions.forEach(tx => { + const txSize = tx.byteLength(); // TODO: extract from toBuffer? + tx.toBuffer(buffer, offset); + offset += txSize; + }); + return buffer; + } + toHex(headersOnly) { + return this.toBuffer(headersOnly).toString('hex'); + } + checkMerkleRoot() { + if (!this.transactions) + throw errorMerkleNoTxes; + const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); + return this.merkleRoot.compare(actualMerkleRoot) === 0; + } + checkWitnessCommit() { + if (!this.transactions) + throw errorMerkleNoTxes; + if (!this.hasWitnessCommit()) + throw errorWitnessNotSegwit; + const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true); + return this.witnessCommit.compare(actualWitnessCommit) === 0; + } + checkProofOfWork() { + const hash = bufferutils_1.reverseBuffer(this.getHash()); + const target = Block.calculateTarget(this.bits); + return hash.compare(target) <= 0; + } +} +exports.Block = Block; diff --git a/src/bufferutils.js b/src/bufferutils.js new file mode 100644 index 0000000..f768250 --- /dev/null +++ b/src/bufferutils.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +// https://github.com/feross/buffer/blob/master/index.js#L1127 +function verifuint(value, max) { + if (typeof value !== 'number') + throw new Error('cannot write a non-number as a number'); + if (value < 0) + throw new Error('specified a negative value for writing an unsigned value'); + if (value > max) + throw new Error('RangeError: value out of range'); + if (Math.floor(value) !== value) + throw new Error('value has a fractional component'); +} +function readUInt64LE(buffer, offset) { + const a = buffer.readUInt32LE(offset); + let b = buffer.readUInt32LE(offset + 4); + b *= 0x100000000; + verifuint(b + a, 0x001fffffffffffff); + return b + a; +} +exports.readUInt64LE = readUInt64LE; +function writeUInt64LE(buffer, value, offset) { + verifuint(value, 0x001fffffffffffff); + buffer.writeInt32LE(value & -1, offset); + buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); + return offset + 8; +} +exports.writeUInt64LE = writeUInt64LE; +function reverseBuffer(buffer) { + if (buffer.length < 1) + return buffer; + let j = buffer.length - 1; + let tmp = 0; + for (let i = 0; i < buffer.length / 2; i++) { + tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + j--; + } + return buffer; +} +exports.reverseBuffer = reverseBuffer; diff --git a/src/classify.js b/src/classify.js new file mode 100644 index 0000000..a2109c2 --- /dev/null +++ b/src/classify.js @@ -0,0 +1,75 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const script_1 = require("./script"); +const multisig = require("./templates/multisig"); +const nullData = require("./templates/nulldata"); +const pubKey = require("./templates/pubkey"); +const pubKeyHash = require("./templates/pubkeyhash"); +const scriptHash = require("./templates/scripthash"); +const witnessPubKeyHash = require("./templates/witnesspubkeyhash"); +const witnessScriptHash = require("./templates/witnessscripthash"); +const witnessCommitment = require("./templates/witnesscommitment"); +const types = { + P2MS: 'multisig', + NONSTANDARD: 'nonstandard', + NULLDATA: 'nulldata', + P2PK: 'pubkey', + P2PKH: 'pubkeyhash', + P2SH: 'scripthash', + P2WPKH: 'witnesspubkeyhash', + P2WSH: 'witnessscripthash', + WITNESS_COMMITMENT: 'witnesscommitment' +}; +exports.types = types; +function classifyOutput(script) { + if (witnessPubKeyHash.output.check(script)) + return types.P2WPKH; + if (witnessScriptHash.output.check(script)) + return types.P2WSH; + if (pubKeyHash.output.check(script)) + return types.P2PKH; + if (scriptHash.output.check(script)) + return types.P2SH; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) + throw new TypeError('Invalid script'); + if (multisig.output.check(chunks)) + return types.P2MS; + if (pubKey.output.check(chunks)) + return types.P2PK; + if (witnessCommitment.output.check(chunks)) + return types.WITNESS_COMMITMENT; + if (nullData.output.check(chunks)) + return types.NULLDATA; + return types.NONSTANDARD; +} +exports.output = classifyOutput; +function classifyInput(script, allowIncomplete) { + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) + throw new TypeError('Invalid script'); + if (pubKeyHash.input.check(chunks)) + return types.P2PKH; + if (scriptHash.input.check(chunks, allowIncomplete)) + return types.P2SH; + if (multisig.input.check(chunks, allowIncomplete)) + return types.P2MS; + if (pubKey.input.check(chunks)) + return types.P2PK; + return types.NONSTANDARD; +} +exports.input = classifyInput; +function classifyWitness(script, allowIncomplete) { + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) + throw new TypeError('Invalid script'); + if (witnessPubKeyHash.input.check(chunks)) + return types.P2WPKH; + if (witnessScriptHash.input.check(chunks, allowIncomplete)) + return types.P2WSH; + return types.NONSTANDARD; +} +exports.witness = classifyWitness; diff --git a/src/crypto.js b/src/crypto.js new file mode 100644 index 0000000..4165d30 --- /dev/null +++ b/src/crypto.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const createHash = require('create-hash'); +function ripemd160(buffer) { + return createHash('rmd160').update(buffer).digest(); +} +exports.ripemd160 = ripemd160; +function sha1(buffer) { + return createHash('sha1').update(buffer).digest(); +} +exports.sha1 = sha1; +function sha256(buffer) { + return createHash('sha256').update(buffer).digest(); +} +exports.sha256 = sha256; +function hash160(buffer) { + return ripemd160(sha256(buffer)); +} +exports.hash160 = hash160; +function hash256(buffer) { + return sha256(sha256(buffer)); +} +exports.hash256 = hash256; diff --git a/src/ecpair.js b/src/ecpair.js new file mode 100644 index 0000000..d4dc94f --- /dev/null +++ b/src/ecpair.js @@ -0,0 +1,97 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const NETWORKS = require("./networks"); +const types = require("./types"); +const ecc = require('tiny-secp256k1'); +const randomBytes = require('randombytes'); +const typeforce = require('typeforce'); +const wif = require('wif'); +const isOptions = typeforce.maybe(typeforce.compile({ + compressed: types.maybe(types.Boolean), + network: types.maybe(types.Network) +})); +class ECPair { + constructor(d, Q, options) { + if (options === undefined) + options = {}; + this.compressed = options.compressed === undefined ? true : options.compressed; + this.network = options.network || NETWORKS.bitcoin; + this.__d = undefined; + this.__Q = undefined; + if (d !== undefined) + this.__d = d; + if (Q !== undefined) + this.__Q = ecc.pointCompress(Q, this.compressed); + } + get privateKey() { + return this.__d; + } + get publicKey() { + if (!this.__Q) + this.__Q = ecc.pointFromScalar(this.__d, this.compressed); + return this.__Q; + } + toWIF() { + if (!this.__d) + throw new Error('Missing private key'); + return wif.encode(this.network.wif, this.__d, this.compressed); + } + sign(hash) { + if (!this.__d) + throw new Error('Missing private key'); + return ecc.sign(hash, this.__d); + } + verify(hash, signature) { + return ecc.verify(hash, this.publicKey, signature); + } +} +function fromPrivateKey(buffer, options) { + typeforce(types.Buffer256bit, buffer); + if (!ecc.isPrivate(buffer)) + throw new TypeError('Private key not in range [1, n)'); + typeforce(isOptions, options); + return new ECPair(buffer, undefined, options); +} +exports.fromPrivateKey = fromPrivateKey; +function fromPublicKey(buffer, options) { + typeforce(ecc.isPoint, buffer); + typeforce(isOptions, options); + return new ECPair(undefined, buffer, options); +} +exports.fromPublicKey = fromPublicKey; +function fromWIF(string, network) { + const decoded = wif.decode(string); + const version = decoded.version; + // list of networks? + if (types.Array(network)) { + network = network.filter(function (x) { + return version === x.wif; + }).pop(); + if (!network) + throw new Error('Unknown network version'); + // otherwise, assume a network object (or default to bitcoin) + } + else { + network = network || NETWORKS.bitcoin; + if (version !== network.wif) + throw new Error('Invalid network version'); + } + return fromPrivateKey(decoded.privateKey, { + compressed: decoded.compressed, + network: network + }); +} +exports.fromWIF = fromWIF; +function makeRandom(options) { + typeforce(isOptions, options); + if (options === undefined) + options = {}; + const rng = options.rng || randomBytes; + let d; + do { + d = rng(32); + typeforce(types.Buffer256bit, d); + } while (!ecc.isPrivate(d)); + return fromPrivateKey(d, options); +} +exports.makeRandom = makeRandom; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7455d4a --- /dev/null +++ b/src/index.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bip32 = require("bip32"); +exports.bip32 = bip32; +const ECPair = require("./ecpair"); +exports.ECPair = ECPair; +const address = require("./address"); +exports.address = address; +const crypto = require("./crypto"); +exports.crypto = crypto; +const networks = require("./networks"); +exports.networks = networks; +const payments = require("./payments"); +exports.payments = payments; +const script = require("./script"); +exports.script = script; +var block_1 = require("./block"); +exports.Block = block_1.Block; +var transaction_1 = require("./transaction"); +exports.Transaction = transaction_1.Transaction; +var transaction_builder_1 = require("./transaction_builder"); +exports.TransactionBuilder = transaction_builder_1.TransactionBuilder; +var script_1 = require("./script"); +exports.opcodes = script_1.OPS; diff --git a/src/networks.js b/src/networks.js new file mode 100644 index 0000000..821cd96 --- /dev/null +++ b/src/networks.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.bitcoin = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80 +}; +exports.regtest = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bcrt', + bip32: { + public: 0x043587cf, + private: 0x04358394 + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef +}; +exports.testnet = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'tb', + bip32: { + public: 0x043587cf, + private: 0x04358394 + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef +}; diff --git a/src/payments/embed.js b/src/payments/embed.js new file mode 100644 index 0000000..7d341ab --- /dev/null +++ b/src/payments/embed.js @@ -0,0 +1,52 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../script"); +const lazy = require("./lazy"); +const networks_1 = require("../networks"); +const typef = require('typeforce'); +const OPS = bscript.OPS; +function stacksEqual(a, b) { + if (a.length !== b.length) + return false; + return a.every(function (x, i) { + return x.equals(b[i]); + }); +} +// output: OP_RETURN ... +function p2data(a, opts) { + if (!a.data && + !a.output) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + data: typef.maybe(typef.arrayOf(typef.Buffer)) + }, a); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'output', function () { + if (!a.data) + return; + return bscript.compile([OPS.OP_RETURN].concat(a.data)); + }); + lazy.prop(o, 'data', function () { + if (!a.output) + return; + return bscript.decompile(a.output).slice(1); + }); + // extended validation + if (opts.validate) { + if (a.output) { + const chunks = bscript.decompile(a.output); + if (chunks[0] !== OPS.OP_RETURN) + throw new TypeError('Output is invalid'); + if (!chunks.slice(1).every(typef.Buffer)) + throw new TypeError('Output is invalid'); + if (a.data && !stacksEqual(a.data, o.data)) + throw new TypeError('Data mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2data = p2data; diff --git a/src/payments/index.js b/src/payments/index.js new file mode 100644 index 0000000..f21762d --- /dev/null +++ b/src/payments/index.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const embed_1 = require("./embed"); +exports.embed = embed_1.p2data; +const p2ms_1 = require("./p2ms"); +exports.p2ms = p2ms_1.p2ms; +const p2pk_1 = require("./p2pk"); +exports.p2pk = p2pk_1.p2pk; +const p2pkh_1 = require("./p2pkh"); +exports.p2pkh = p2pkh_1.p2pkh; +const p2sh_1 = require("./p2sh"); +exports.p2sh = p2sh_1.p2sh; +const p2wpkh_1 = require("./p2wpkh"); +exports.p2wpkh = p2wpkh_1.p2wpkh; +const p2wsh_1 = require("./p2wsh"); +exports.p2wsh = p2wsh_1.p2wsh; +// TODO +// witness commitment diff --git a/src/payments/lazy.js b/src/payments/lazy.js new file mode 100644 index 0000000..2c848e1 --- /dev/null +++ b/src/payments/lazy.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function prop(object, name, f) { + Object.defineProperty(object, name, { + configurable: true, + enumerable: true, + get: function () { + let value = f.call(this); + this[name] = value; + return value; + }, + set: function (value) { + Object.defineProperty(this, name, { + configurable: true, + enumerable: true, + value: value, + writable: true + }); + } + }); +} +exports.prop = prop; +function value(f) { + let value; + return function () { + if (value !== undefined) + return value; + value = f(); + return value; + }; +} +exports.value = value; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js new file mode 100644 index 0000000..a45bd48 --- /dev/null +++ b/src/payments/p2ms.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../script"); +const lazy = require("./lazy"); +const networks_1 = require("../networks"); +const OPS = bscript.OPS; +const typef = require('typeforce'); +const ecc = require('tiny-secp256k1'); +const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 +function stacksEqual(a, b) { + if (a.length !== b.length) + return false; + return a.every(function (x, i) { + return x.equals(b[i]); + }); +} +// input: OP_0 [signatures ...] +// output: m [pubKeys ...] n OP_CHECKMULTISIG +function p2ms(a, opts) { + if (!a.input && + !a.output && + !(a.pubkeys && a.m !== undefined) && + !a.signatures) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + function isAcceptableSignature(x) { + return bscript.isCanonicalScriptSignature(x) || + (opts.allowIncomplete && + (x === OPS.OP_0)) !== undefined; // eslint-disable-line + } + typef({ + network: typef.maybe(typef.Object), + m: typef.maybe(typef.Number), + n: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), + signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), + input: typef.maybe(typef.Buffer) + }, a); + const network = a.network || networks_1.bitcoin; + const o = { network }; + let chunks = []; + let decoded = false; + function decode(output) { + if (decoded) + return; + decoded = true; + chunks = bscript.decompile(output); + o.m = chunks[0] - OP_INT_BASE; // eslint-disable-line + o.n = chunks[chunks.length - 2] - OP_INT_BASE; // eslint-disable-line + o.pubkeys = chunks.slice(1, -2); + } + lazy.prop(o, 'output', function () { + if (!a.m) + return; + if (!o.n) + return; + if (!a.pubkeys) + return; + return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG)); + }); + lazy.prop(o, 'm', function () { + if (!o.output) + return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', function () { + if (!o.pubkeys) + return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', function () { + if (!a.output) + return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', function () { + if (!a.input) + return; + return bscript.decompile(a.input).slice(1); + }); + lazy.prop(o, 'input', function () { + if (!a.signatures) + return; + return bscript.compile([OPS.OP_0].concat(a.signatures)); + }); + lazy.prop(o, 'witness', function () { + if (!o.input) + return; + return []; + }); + // extended validation + if (opts.validate) { + if (a.output) { + decode(a.output); + if (!typef.Number(chunks[0])) + throw new TypeError('Output is invalid'); + if (!typef.Number(chunks[chunks.length - 2])) + throw new TypeError('Output is invalid'); + if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) + throw new TypeError('Output is invalid'); + if (o.m <= 0 || // eslint-disable-line + o.n > 16 || // eslint-disable-line + o.m > o.n || // eslint-disable-line + o.n !== chunks.length - 3) + throw new TypeError('Output is invalid'); + if (!o.pubkeys.every(x => ecc.isPoint(x))) + throw new TypeError('Output is invalid'); + if (a.m !== undefined && a.m !== o.m) + throw new TypeError('m mismatch'); + if (a.n !== undefined && a.n !== o.n) + throw new TypeError('n mismatch'); + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + throw new TypeError('Pubkeys mismatch'); + } + if (a.pubkeys) { + if (a.n !== undefined && a.n !== a.pubkeys.length) + throw new TypeError('Pubkey count mismatch'); + o.n = a.pubkeys.length; + if (o.n < o.m) + throw new TypeError('Pubkey count cannot be less than m'); + } + if (a.signatures) { + if (a.signatures.length < o.m) + throw new TypeError('Not enough signatures provided'); + if (a.signatures.length > o.m) + throw new TypeError('Too many signatures provided'); + } + if (a.input) { + if (a.input[0] !== OPS.OP_0) + throw new TypeError('Input is invalid'); + if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) + throw new TypeError('Input has invalid signature(s)'); + if (a.signatures && !stacksEqual(a.signatures, o.signatures)) + throw new TypeError('Signature mismatch'); + if (a.m !== undefined && a.m !== a.signatures.length) + throw new TypeError('Signature count mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2ms = p2ms; diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js new file mode 100644 index 0000000..ab8654d --- /dev/null +++ b/src/payments/p2pk.js @@ -0,0 +1,80 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../script"); +const lazy = require("./lazy"); +const networks_1 = require("../networks"); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); +// input: {signature} +// output: {pubKey} OP_CHECKSIG +function p2pk(a, opts) { + if (!a.input && + !a.output && + !a.pubkey && + !a.input && + !a.signature) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer) + }, a); + const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'output', function () { + if (!a.pubkey) + return; + return bscript.compile([ + a.pubkey, + OPS.OP_CHECKSIG + ]); + }); + lazy.prop(o, 'pubkey', function () { + if (!a.output) + return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', function () { + if (!a.input) + return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', function () { + if (!a.signature) + return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', function () { + if (!o.input) + return; + return []; + }); + // extended validation + if (opts.validate) { + if (a.output) { + if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) + throw new TypeError('Output is invalid'); + if (!ecc.isPoint(o.pubkey)) + throw new TypeError('Output pubkey is invalid'); + if (a.pubkey && !a.pubkey.equals(o.pubkey)) + throw new TypeError('Pubkey mismatch'); + } + if (a.signature) { + if (a.input && !a.input.equals(o.input)) + throw new TypeError('Signature mismatch'); + } + if (a.input) { + if (_chunks().length !== 1) + throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(o.signature)) + throw new TypeError('Input has invalid signature'); + } + } + return Object.assign(o, a); +} +exports.p2pk = p2pk; diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js new file mode 100644 index 0000000..443464a --- /dev/null +++ b/src/payments/p2pkh.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../script"); +const bcrypto = require("../crypto"); +const lazy = require("./lazy"); +const networks_1 = require("../networks"); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); +const bs58check = require('bs58check'); +// input: {signature} {pubkey} +// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG +function p2pkh(a, opts) { + if (!a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(25)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer) + }, a); + const _address = lazy.value(function () { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'address', function () { + if (!o.hash) + return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(network.pubKeyHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', function () { + if (a.output) + return a.output.slice(3, 23); + if (a.address) + return _address().hash; + if (a.pubkey || o.pubkey) + return bcrypto.hash160(a.pubkey || o.pubkey); // eslint-disable-line + }); + lazy.prop(o, 'output', function () { + if (!o.hash) + return; + return bscript.compile([ + OPS.OP_DUP, + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUALVERIFY, + OPS.OP_CHECKSIG + ]); + }); + lazy.prop(o, 'pubkey', function () { + if (!a.input) + return; + return _chunks()[1]; + }); + lazy.prop(o, 'signature', function () { + if (!a.input) + return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', function () { + if (!a.pubkey) + return; + if (!a.signature) + return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', function () { + if (!o.input) + return; + return []; + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().version !== network.pubKeyHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) + throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(3, 23); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else + hash = pkh; + } + if (a.input) { + const chunks = _chunks(); + if (chunks.length !== 2) + throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(chunks[0])) + throw new TypeError('Input has invalid signature'); + if (!ecc.isPoint(chunks[1])) + throw new TypeError('Input has invalid pubkey'); + if (a.signature && !a.signature.equals(chunks[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(chunks[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(chunks[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2pkh = p2pkh; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js new file mode 100644 index 0000000..8ec9910 --- /dev/null +++ b/src/payments/p2sh.js @@ -0,0 +1,191 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks_1 = require("../networks"); // eslint-disable-line +const bscript = require("../script"); +const bcrypto = require("../crypto"); +const lazy = require("./lazy"); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const bs58check = require('bs58check'); +function stacksEqual(a, b) { + if (a.length !== b.length) + return false; + return a.every(function (x, i) { + return x.equals(b[i]); + }); +} +// input: [redeemScriptSig ...] {redeemScript} +// witness: +// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL +function p2sh(a, opts) { + if (!a.address && + !a.hash && + !a.output && + !a.redeem && + !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(23)), + redeem: typef.maybe({ + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }, a); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + } + const o = { network }; + const _address = lazy.value(function () { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); + const _redeem = lazy.value(function () { + const chunks = _chunks(); + return { + network, + output: chunks[chunks.length - 1], + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [] + }; + }); + // output dependents + lazy.prop(o, 'address', function () { + if (!o.hash) + return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(o.network.scriptHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', function () { + // in order of least effort + if (a.output) + return a.output.slice(2, 22); + if (a.address) + return _address().hash; + if (o.redeem && o.redeem.output) + return bcrypto.hash160(o.redeem.output); + }); + lazy.prop(o, 'output', function () { + if (!o.hash) + return; + return bscript.compile([ + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUAL + ]); + }); + // input dependents + lazy.prop(o, 'redeem', function () { + if (!a.input) + return; + return _redeem(); + }); + lazy.prop(o, 'input', function () { + if (!a.redeem || !a.redeem.input || !a.redeem.output) + return; + return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output)); + }); + lazy.prop(o, 'witness', function () { + if (o.redeem && o.redeem.witness) + return o.redeem.witness; + if (o.input) + return []; + }); + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().version !== network.scriptHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) + throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2, 22); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + // inlined to prevent 'no-inner-declarations' failing + const checkRedeem = function (redeem) { + // is the redeem output empty/invalid? + if (redeem.output) { + const decompile = bscript.decompile(redeem.output); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output too short'); + // match hash against other sources + const hash2 = bcrypto.hash160(redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (redeem.input) { + const hasInput = redeem.input.length > 0; + const hasWitness = redeem.witness && redeem.witness.length > 0; + if (!hasInput && !hasWitness) + throw new TypeError('Empty input'); + if (hasInput && hasWitness) + throw new TypeError('Input and witness provided'); + if (hasInput) { + const richunks = bscript.decompile(redeem.input); + if (!bscript.isPushOnly(richunks)) + throw new TypeError('Non push-only scriptSig'); + } + } + }; + if (a.input) { + const chunks = _chunks(); + if (!chunks || chunks.length < 1) + throw new TypeError('Input too short'); + if (!Buffer.isBuffer(_redeem().output)) + throw new TypeError('Input is invalid'); + checkRedeem(_redeem()); + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + if (a.input) { + const redeem = _redeem(); + if (a.redeem.output && !a.redeem.output.equals(redeem.output)) + throw new TypeError('Redeem.output mismatch'); + if (a.redeem.input && !a.redeem.input.equals(redeem.input)) + throw new TypeError('Redeem.input mismatch'); + } + checkRedeem(a.redeem); + } + if (a.witness) { + if (a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness)) + throw new TypeError('Witness and redeem.witness mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2sh = p2sh; diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js new file mode 100644 index 0000000..98951c6 --- /dev/null +++ b/src/payments/p2wpkh.js @@ -0,0 +1,145 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../script"); +const bcrypto = require("../crypto"); +const lazy = require("./lazy"); +const networks_1 = require("../networks"); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); +const bech32 = require('bech32'); +const EMPTY_BUFFER = Buffer.alloc(0); +// witness: {signature} {pubKey} +// input: <> +// output: OP_0 {pubKeyHash} +function p2wpkh(a, opts) { + if (!a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(22)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }, a); + const _address = lazy.value(function () { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data) + }; + }); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'address', function () { + if (!o.hash) + return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', function () { + if (a.output) + return a.output.slice(2, 22); + if (a.address) + return _address().data; + if (a.pubkey || o.pubkey) + return bcrypto.hash160(a.pubkey || o.pubkey); // eslint-disable-line + }); + lazy.prop(o, 'output', function () { + if (!o.hash) + return; + return bscript.compile([ + OPS.OP_0, + o.hash + ]); + }); + lazy.prop(o, 'pubkey', function () { + if (a.pubkey) + return a.pubkey; + if (!a.witness) + return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', function () { + if (!a.witness) + return; + return a.witness[0]; + }); + lazy.prop(o, 'input', function () { + if (!o.witness) + return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', function () { + if (!a.pubkey) + return; + if (!a.signature) + return; + return [a.signature, a.pubkey]; + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 20) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 22 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x14) + throw new TypeError('Output is invalid'); + if (hash.length > 0 && !hash.equals(a.output.slice(2))) + throw new TypeError('Hash mismatch'); + else + hash = a.output.slice(2); + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else + hash = pkh; + } + if (a.witness) { + if (a.witness.length !== 2) + throw new TypeError('Witness is invalid'); + if (!bscript.isCanonicalScriptSignature(a.witness[0])) + throw new TypeError('Witness has invalid signature'); + if (!ecc.isPoint(a.witness[1])) + throw new TypeError('Witness has invalid pubkey'); + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(a.witness[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(a.witness[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2wpkh = p2wpkh; diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js new file mode 100644 index 0000000..a7e9f8b --- /dev/null +++ b/src/payments/p2wsh.js @@ -0,0 +1,178 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks_1 = require("../networks"); // eslint-disable-line +const bscript = require("../script"); +const bcrypto = require("../crypto"); +const lazy = require("./lazy"); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const bech32 = require('bech32'); +const EMPTY_BUFFER = Buffer.alloc(0); +function stacksEqual(a, b) { + if (a.length !== b.length) + return false; + return a.every(function (x, i) { + return x.equals(b[i]); + }); +} +// input: <> +// witness: [redeemScriptSig ...] {redeemScript} +// output: OP_0 {sha256(redeemScript)} +function p2wsh(a, opts) { + if (!a.address && + !a.hash && + !a.output && + !a.redeem && + !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(32)), + output: typef.maybe(typef.BufferN(34)), + redeem: typef.maybe({ + input: typef.maybe(typef.Buffer), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }), + input: typef.maybe(typef.BufferN(0)), + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }, a); + const _address = lazy.value(function () { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data) + }; + }); + const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input); }); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + } + const o = { network }; + lazy.prop(o, 'address', function () { + if (!o.hash) + return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', function () { + if (a.output) + return a.output.slice(2); + if (a.address) + return _address().data; + if (o.redeem && o.redeem.output) + return bcrypto.sha256(o.redeem.output); + }); + lazy.prop(o, 'output', function () { + if (!o.hash) + return; + return bscript.compile([ + OPS.OP_0, + o.hash + ]); + }); + lazy.prop(o, 'redeem', function () { + if (!a.witness) + return; + return { + output: a.witness[a.witness.length - 1], + input: EMPTY_BUFFER, + witness: a.witness.slice(0, -1) + }; + }); + lazy.prop(o, 'input', function () { + if (!o.witness) + return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', function () { + // transform redeem input to witness stack? + if (a.redeem && + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.output && + a.redeem.output.length > 0) { + const stack = bscript.toStack(_rchunks()); + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem); + o.redeem.input = EMPTY_BUFFER; + return [].concat(stack, a.redeem.output); + } + if (!a.redeem) + return; + if (!a.redeem.output) + return; + if (!a.redeem.witness) + return; + return [].concat(a.redeem.witness, a.redeem.output); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().prefix !== network.bech32) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 34 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x20) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + // is there two redeem sources? + if (a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness && + a.redeem.witness.length > 0) + throw new TypeError('Ambiguous witness source'); + // is the redeem output non-empty? + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output).length === 0) + throw new TypeError('Redeem.output is invalid'); + // match hash against other sources + const hash2 = bcrypto.sha256(a.redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) + throw new TypeError('Non push-only scriptSig'); + if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) + throw new TypeError('Witness and redeem.witness mismatch'); + } + if (a.witness) { + if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) + throw new TypeError('Witness and redeem.output mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2wsh = p2wsh; diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..3d115cb --- /dev/null +++ b/src/script.js @@ -0,0 +1,188 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const types = require("./types"); +const scriptNumber = require("./script_number"); +const scriptSignature = require("./script_signature"); +const bip66 = require('bip66'); +const ecc = require('tiny-secp256k1'); +const pushdata = require('pushdata-bitcoin'); +const typeforce = require('typeforce'); +exports.OPS = require('bitcoin-ops'); +const REVERSE_OPS = require('bitcoin-ops/map'); +const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1 +function isOPInt(value) { + return types.Number(value) && + ((value === exports.OPS.OP_0) || + (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || + (value === exports.OPS.OP_1NEGATE)); +} +function isPushOnlyChunk(value) { + return types.Buffer(value) || isOPInt(value); +} +function isPushOnly(value) { + return types.Array(value) && value.every(isPushOnlyChunk); +} +exports.isPushOnly = isPushOnly; +function asMinimalOP(buffer) { + if (buffer.length === 0) + return exports.OPS.OP_0; + if (buffer.length !== 1) + return; + if (buffer[0] >= 1 && buffer[0] <= 16) + return OP_INT_BASE + buffer[0]; + if (buffer[0] === 0x81) + return exports.OPS.OP_1NEGATE; +} +function chunksIsBuffer(buf) { + return Buffer.isBuffer(buf); +} +function chunksIsArray(buf) { + return types.Array(buf); +} +function singleChunkIsBuffer(buf) { + return Buffer.isBuffer(buf); +} +function compile(chunks) { + // TODO: remove me + if (chunksIsBuffer(chunks)) + return chunks; + typeforce(types.Array, chunks); + const bufferSize = chunks.reduce(function (accum, chunk) { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { + return accum + 1; + } + return accum + pushdata.encodingLength(chunk.length) + chunk.length; + } + // opcode + return accum + 1; + }, 0.0); + const buffer = Buffer.allocUnsafe(bufferSize); + let offset = 0; + chunks.forEach(function (chunk) { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + const opcode = asMinimalOP(chunk); + if (opcode !== undefined) { + buffer.writeUInt8(opcode, offset); + offset += 1; + return; + } + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; + // opcode + } + else { + buffer.writeUInt8(chunk, offset); + offset += 1; + } + }); + if (offset !== buffer.length) + throw new Error('Could not decode chunks'); + return buffer; +} +exports.compile = compile; +function decompile(buffer) { + // TODO: remove me + if (chunksIsArray(buffer)) + return buffer; + typeforce(types.Buffer, buffer); + const chunks = []; + let i = 0; + while (i < buffer.length) { + const opcode = buffer[i]; + // data chunk + if ((opcode > exports.OPS.OP_0) && (opcode <= exports.OPS.OP_PUSHDATA4)) { + const d = pushdata.decode(buffer, i); + // did reading a pushDataInt fail? + if (d === null) + return null; + i += d.size; + // attempt to read too much data? + if (i + d.number > buffer.length) + return null; + const data = buffer.slice(i, i + d.number); + i += d.number; + // decompile minimally + const op = asMinimalOP(data); + if (op !== undefined) { + chunks.push(op); + } + else { + chunks.push(data); + } + // opcode + } + else { + chunks.push(opcode); + i += 1; + } + } + return chunks; +} +exports.decompile = decompile; +function toASM(chunks) { + if (chunksIsBuffer(chunks)) { + chunks = decompile(chunks); + } + return chunks.map(function (chunk) { + // data? + if (singleChunkIsBuffer(chunk)) { + const op = asMinimalOP(chunk); + if (op === undefined) + return chunk.toString('hex'); + chunk = op; + } + // opcode! + return REVERSE_OPS[chunk]; + }).join(' '); +} +exports.toASM = toASM; +function fromASM(asm) { + typeforce(types.String, asm); + return compile(asm.split(' ').map(function (chunkStr) { + // opcode? + if (exports.OPS[chunkStr] !== undefined) + return exports.OPS[chunkStr]; + typeforce(types.Hex, chunkStr); + // data! + return Buffer.from(chunkStr, 'hex'); + })); +} +exports.fromASM = fromASM; +function toStack(chunks) { + chunks = decompile(chunks); + typeforce(isPushOnly, chunks); + return chunks.map(function (op) { + if (singleChunkIsBuffer(op)) + return op; + if (op === exports.OPS.OP_0) + return Buffer.allocUnsafe(0); + return scriptNumber.encode(op - OP_INT_BASE); + }); +} +exports.toStack = toStack; +function isCanonicalPubKey(buffer) { + return ecc.isPoint(buffer); +} +exports.isCanonicalPubKey = isCanonicalPubKey; +function isDefinedHashType(hashType) { + const hashTypeMod = hashType & ~0x80; + // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE + return hashTypeMod > 0x00 && hashTypeMod < 0x04; +} +exports.isDefinedHashType = isDefinedHashType; +function isCanonicalScriptSignature(buffer) { + if (!Buffer.isBuffer(buffer)) + return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) + return false; + return bip66.check(buffer.slice(0, -1)); +} +exports.isCanonicalScriptSignature = isCanonicalScriptSignature; +exports.number = scriptNumber; +exports.signature = scriptSignature; diff --git a/src/script_number.js b/src/script_number.js new file mode 100644 index 0000000..da12de6 --- /dev/null +++ b/src/script_number.js @@ -0,0 +1,60 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function decode(buffer, maxLength, minimal) { + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; + const length = buffer.length; + if (length === 0) + return 0; + if (length > maxLength) + throw new TypeError('Script number overflow'); + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) + throw new Error('Non-minimally encoded script number'); + } + } + // 40-bit + if (length === 5) { + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); + if (b & 0x80) + return -(((b & ~0x80) * 0x100000000) + a); + return (b * 0x100000000) + a; + } + // 32-bit / 24-bit / 16-bit / 8-bit + let result = 0; + for (var i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i); + } + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; +} +exports.decode = decode; +function scriptNumSize(i) { + return i > 0x7fffffff ? 5 + : i > 0x7fffff ? 4 + : i > 0x7fff ? 3 + : i > 0x7f ? 2 + : i > 0x00 ? 1 + : 0; +} +function encode(number) { + let value = Math.abs(number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = number < 0; + for (var i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i); + value >>= 8; + } + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); + } + else if (negative) { + buffer[size - 1] |= 0x80; + } + return buffer; +} +exports.encode = encode; diff --git a/src/script_signature.js b/src/script_signature.js new file mode 100644 index 0000000..c3372cd --- /dev/null +++ b/src/script_signature.js @@ -0,0 +1,58 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const types = require("./types"); +const bip66 = require('bip66'); +const typeforce = require('typeforce'); +const ZERO = Buffer.alloc(1, 0); +function toDER(x) { + let i = 0; + while (x[i] === 0) + ++i; + if (i === x.length) + return ZERO; + x = x.slice(i); + if (x[0] & 0x80) + return Buffer.concat([ZERO, x], 1 + x.length); + return x; +} +function fromDER(x) { + if (x[0] === 0x00) + x = x.slice(1); + const buffer = Buffer.alloc(32, 0); + const bstart = Math.max(0, 32 - x.length); + x.copy(buffer, bstart); + return buffer; +} +// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) +function decode(buffer) { + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const decode = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decode.r); + const s = fromDER(decode.s); + return { + signature: Buffer.concat([r, s], 64), + hashType: hashType + }; +} +exports.decode = decode; +function encode(signature, hashType) { + typeforce({ + signature: types.BufferN(64), + hashType: types.UInt8 + }, { signature, hashType }); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); + return Buffer.concat([ + bip66.encode(r, s), + hashTypeBuffer + ]); +} +exports.encode = encode; diff --git a/src/templates/multisig/index.js b/src/templates/multisig/index.js new file mode 100644 index 0000000..85a15b9 --- /dev/null +++ b/src/templates/multisig/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = require("./input"); +exports.input = input; +const output = require("./output"); +exports.output = output; diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js new file mode 100644 index 0000000..f8dd3d3 --- /dev/null +++ b/src/templates/multisig/input.js @@ -0,0 +1,21 @@ +"use strict"; +// OP_0 [signatures ...] +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const script_1 = require("../../script"); +function partialSignature(value) { + return value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value); +} +function check(script, allowIncomplete) { + const chunks = bscript.decompile(script); + if (chunks.length < 2) + return false; + if (chunks[0] !== script_1.OPS.OP_0) + return false; + if (allowIncomplete) { + return chunks.slice(1).every(partialSignature); + } + return chunks.slice(1).every(bscript.isCanonicalScriptSignature); +} +exports.check = check; +check.toJSON = function () { return 'multisig input'; }; diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js new file mode 100644 index 0000000..811ed12 --- /dev/null +++ b/src/templates/multisig/output.js @@ -0,0 +1,34 @@ +"use strict"; +// m [pubKeys ...] n OP_CHECKMULTISIG +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const types = require("../../types"); +const script_1 = require("../../script"); +const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 +function check(script, allowIncomplete) { + const chunks = bscript.decompile(script); + if (chunks.length < 4) + return false; + if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) + return false; + if (!types.Number(chunks[0])) + return false; + if (!types.Number(chunks[chunks.length - 2])) + return false; + const m = chunks[0] - OP_INT_BASE; + const n = chunks[chunks.length - 2] - OP_INT_BASE; + if (m <= 0) + return false; + if (n > 16) + return false; + if (m > n) + return false; + if (n !== chunks.length - 3) + return false; + if (allowIncomplete) + return true; + const keys = chunks.slice(1, -2); + return keys.every(bscript.isCanonicalPubKey); +} +exports.check = check; +check.toJSON = function () { return 'multi-sig output'; }; diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js new file mode 100644 index 0000000..fd5320d --- /dev/null +++ b/src/templates/nulldata.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +// OP_RETURN {data} +const bscript = require("../script"); +const OPS = bscript.OPS; +function check(script) { + const buffer = bscript.compile(script); + return buffer.length > 1 && + buffer[0] === OPS.OP_RETURN; +} +exports.check = check; +check.toJSON = function () { return 'null data output'; }; +const output = { check }; +exports.output = output; diff --git a/src/templates/pubkey/index.js b/src/templates/pubkey/index.js new file mode 100644 index 0000000..85a15b9 --- /dev/null +++ b/src/templates/pubkey/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = require("./input"); +exports.input = input; +const output = require("./output"); +exports.output = output; diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js new file mode 100644 index 0000000..bc5e566 --- /dev/null +++ b/src/templates/pubkey/input.js @@ -0,0 +1,11 @@ +"use strict"; +// {signature} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +function check(script) { + const chunks = bscript.decompile(script); + return chunks.length === 1 && + bscript.isCanonicalScriptSignature(chunks[0]); +} +exports.check = check; +check.toJSON = function () { return 'pubKey input'; }; diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js new file mode 100644 index 0000000..043150e --- /dev/null +++ b/src/templates/pubkey/output.js @@ -0,0 +1,13 @@ +"use strict"; +// {pubKey} OP_CHECKSIG +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const script_1 = require("../../script"); +function check(script) { + const chunks = bscript.decompile(script); + return chunks.length === 2 && + bscript.isCanonicalPubKey(chunks[0]) && + chunks[1] === script_1.OPS.OP_CHECKSIG; +} +exports.check = check; +check.toJSON = function () { return 'pubKey output'; }; diff --git a/src/templates/pubkeyhash/index.js b/src/templates/pubkeyhash/index.js new file mode 100644 index 0000000..85a15b9 --- /dev/null +++ b/src/templates/pubkeyhash/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = require("./input"); +exports.input = input; +const output = require("./output"); +exports.output = output; diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js new file mode 100644 index 0000000..29a684e --- /dev/null +++ b/src/templates/pubkeyhash/input.js @@ -0,0 +1,12 @@ +"use strict"; +// {signature} {pubKey} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +function check(script) { + const chunks = bscript.decompile(script); + return chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + bscript.isCanonicalPubKey(chunks[1]); +} +exports.check = check; +check.toJSON = function () { return 'pubKeyHash input'; }; diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js new file mode 100644 index 0000000..9016c6a --- /dev/null +++ b/src/templates/pubkeyhash/output.js @@ -0,0 +1,16 @@ +"use strict"; +// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const script_1 = require("../../script"); +function check(script) { + const buffer = bscript.compile(script); + return buffer.length === 25 && + buffer[0] === script_1.OPS.OP_DUP && + buffer[1] === script_1.OPS.OP_HASH160 && + buffer[2] === 0x14 && + buffer[23] === script_1.OPS.OP_EQUALVERIFY && + buffer[24] === script_1.OPS.OP_CHECKSIG; +} +exports.check = check; +check.toJSON = function () { return 'pubKeyHash output'; }; diff --git a/src/templates/scripthash/index.js b/src/templates/scripthash/index.js new file mode 100644 index 0000000..85a15b9 --- /dev/null +++ b/src/templates/scripthash/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = require("./input"); +exports.input = input; +const output = require("./output"); +exports.output = output; diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js new file mode 100644 index 0000000..c40bc26 --- /dev/null +++ b/src/templates/scripthash/input.js @@ -0,0 +1,43 @@ +"use strict"; +// {serialized scriptPubKey script} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const p2ms = require("../multisig"); +const p2pk = require("../pubkey"); +const p2pkh = require("../pubkeyhash"); +const p2wpkho = require("../witnesspubkeyhash/output"); +const p2wsho = require("../witnessscripthash/output"); +function check(script, allowIncomplete) { + const chunks = bscript.decompile(script); + if (chunks.length < 1) + return false; + const lastChunk = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(lastChunk)) + return false; + const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))); + const redeemScriptChunks = bscript.decompile(lastChunk); + // is redeemScript a valid script? + if (!redeemScriptChunks) + return false; + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) + return false; + // is witness? + if (chunks.length === 1) { + return p2wsho.check(redeemScriptChunks) || + p2wpkho.check(redeemScriptChunks); + } + // match types + if (p2pkh.input.check(scriptSigChunks) && + p2pkh.output.check(redeemScriptChunks)) + return true; + if (p2ms.input.check(scriptSigChunks, allowIncomplete) && + p2ms.output.check(redeemScriptChunks)) + return true; + if (p2pk.input.check(scriptSigChunks) && + p2pk.output.check(redeemScriptChunks)) + return true; + return false; +} +exports.check = check; +check.toJSON = function () { return 'scriptHash input'; }; diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js new file mode 100644 index 0000000..bd38d5a --- /dev/null +++ b/src/templates/scripthash/output.js @@ -0,0 +1,14 @@ +"use strict"; +// OP_HASH160 {scriptHash} OP_EQUAL +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const script_1 = require("../../script"); +function check(script) { + const buffer = bscript.compile(script); + return buffer.length === 23 && + buffer[0] === script_1.OPS.OP_HASH160 && + buffer[1] === 0x14 && + buffer[22] === script_1.OPS.OP_EQUAL; +} +exports.check = check; +check.toJSON = function () { return 'scriptHash output'; }; diff --git a/src/templates/witnesscommitment/index.js b/src/templates/witnesscommitment/index.js new file mode 100644 index 0000000..aff0618 --- /dev/null +++ b/src/templates/witnesscommitment/index.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const output = require("./output"); +exports.output = output; diff --git a/src/templates/witnesscommitment/output.js b/src/templates/witnesscommitment/output.js new file mode 100644 index 0000000..95a8bc1 --- /dev/null +++ b/src/templates/witnesscommitment/output.js @@ -0,0 +1,30 @@ +"use strict"; +// OP_RETURN {aa21a9ed} {commitment} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const types = require("../../types"); +const typeforce = require('typeforce'); +const script_1 = require("../../script"); +const HEADER = Buffer.from('aa21a9ed', 'hex'); +function check(script) { + const buffer = bscript.compile(script); + return buffer.length > 37 && + buffer[0] === script_1.OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).equals(HEADER); +} +exports.check = check; +check.toJSON = function () { return 'Witness commitment output'; }; +function encode(commitment) { + typeforce(types.Hash256bit, commitment); + const buffer = Buffer.allocUnsafe(36); + HEADER.copy(buffer, 0); + commitment.copy(buffer, 4); + return bscript.compile([script_1.OPS.OP_RETURN, buffer]); +} +exports.encode = encode; +function decode(buffer) { + typeforce(check, buffer); + return bscript.decompile(buffer)[1].slice(4, 36); +} +exports.decode = decode; diff --git a/src/templates/witnesspubkeyhash/index.js b/src/templates/witnesspubkeyhash/index.js new file mode 100644 index 0000000..85a15b9 --- /dev/null +++ b/src/templates/witnesspubkeyhash/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = require("./input"); +exports.input = input; +const output = require("./output"); +exports.output = output; diff --git a/src/templates/witnesspubkeyhash/input.js b/src/templates/witnesspubkeyhash/input.js new file mode 100644 index 0000000..131a49e --- /dev/null +++ b/src/templates/witnesspubkeyhash/input.js @@ -0,0 +1,15 @@ +"use strict"; +// {signature} {pubKey} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +function isCompressedCanonicalPubKey(pubKey) { + return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; +} +function check(script) { + const chunks = bscript.decompile(script); + return chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + isCompressedCanonicalPubKey(chunks[1]); +} +exports.check = check; +check.toJSON = function () { return 'witnessPubKeyHash input'; }; diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js new file mode 100644 index 0000000..2fb6e15 --- /dev/null +++ b/src/templates/witnesspubkeyhash/output.js @@ -0,0 +1,13 @@ +"use strict"; +// OP_0 {pubKeyHash} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const script_1 = require("../../script"); +function check(script) { + const buffer = bscript.compile(script); + return buffer.length === 22 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x14; +} +exports.check = check; +check.toJSON = function () { return 'Witness pubKeyHash output'; }; diff --git a/src/templates/witnessscripthash/index.js b/src/templates/witnessscripthash/index.js new file mode 100644 index 0000000..85a15b9 --- /dev/null +++ b/src/templates/witnessscripthash/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = require("./input"); +exports.input = input; +const output = require("./output"); +exports.output = output; diff --git a/src/templates/witnessscripthash/input.js b/src/templates/witnessscripthash/input.js new file mode 100644 index 0000000..255ea86 --- /dev/null +++ b/src/templates/witnessscripthash/input.js @@ -0,0 +1,34 @@ +"use strict"; +// {serialized scriptPubKey script} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const typeforce = require('typeforce'); +const p2ms = require("../multisig"); +const p2pk = require("../pubkey"); +const p2pkh = require("../pubkeyhash"); +function check(chunks, allowIncomplete) { + typeforce(typeforce.Array, chunks); + if (chunks.length < 1) + return false; + const witnessScript = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(witnessScript)) + return false; + const witnessScriptChunks = bscript.decompile(witnessScript); + // is witnessScript a valid script? + if (!witnessScriptChunks || witnessScriptChunks.length === 0) + return false; + const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); + // match types + if (p2pkh.input.check(witnessRawScriptSig) && + p2pkh.output.check(witnessScriptChunks)) + return true; + if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && + p2ms.output.check(witnessScriptChunks)) + return true; + if (p2pk.input.check(witnessRawScriptSig) && + p2pk.output.check(witnessScriptChunks)) + return true; + return false; +} +exports.check = check; +check.toJSON = function () { return 'witnessScriptHash input'; }; diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js new file mode 100644 index 0000000..994a75a --- /dev/null +++ b/src/templates/witnessscripthash/output.js @@ -0,0 +1,13 @@ +"use strict"; +// OP_0 {scriptHash} +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = require("../../script"); +const script_1 = require("../../script"); +function check(script) { + const buffer = bscript.compile(script); + return buffer.length === 34 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x20; +} +exports.check = check; +check.toJSON = function () { return 'Witness scriptHash output'; }; diff --git a/src/transaction.js b/src/transaction.js new file mode 100644 index 0000000..6619b79 --- /dev/null +++ b/src/transaction.js @@ -0,0 +1,448 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bcrypto = require("./crypto"); +const bscript = require("./script"); +const types = require("./types"); +const bufferutils = require("./bufferutils"); +const bufferutils_1 = require("./bufferutils"); +const script_1 = require("./script"); +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); +function varSliceSize(someScript) { + const length = someScript.length; + return varuint.encodingLength(length) + length; +} +function vectorSize(someVector) { + const length = someVector.length; + return varuint.encodingLength(length) + someVector.reduce((sum, witness) => { + return sum + varSliceSize(witness); + }, 0); +} +const EMPTY_SCRIPT = Buffer.allocUnsafe(0); +const EMPTY_WITNESS = []; +const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); +const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); +const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); +const BLANK_OUTPUT = { + script: EMPTY_SCRIPT, + valueBuffer: VALUE_UINT64_MAX +}; +function isOutput(out) { + return out.value !== undefined; +} +class Transaction { + constructor() { + this.version = 1; + this.locktime = 0; + this.ins = []; + this.outs = []; + } + static fromBuffer(buffer, __noStrict) { + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + function readUInt32() { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + } + function readInt32() { + const i = buffer.readInt32LE(offset); + offset += 4; + return i; + } + function readUInt64() { + const i = bufferutils.readUInt64LE(buffer, offset); + offset += 8; + return i; + } + function readVarInt() { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + } + function readVarSlice() { + return readSlice(readVarInt()); + } + function readVector() { + const count = readVarInt(); + const vector = []; + for (var i = 0; i < count; i++) + vector.push(readVarSlice()); + return vector; + } + const tx = new Transaction(); + tx.version = readInt32(); + const marker = buffer.readUInt8(offset); + const flag = buffer.readUInt8(offset + 1); + let hasWitnesses = false; + if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && + flag === Transaction.ADVANCED_TRANSACTION_FLAG) { + offset += 2; + hasWitnesses = true; + } + const vinLen = readVarInt(); + for (var i = 0; i < vinLen; ++i) { + tx.ins.push({ + hash: readSlice(32), + index: readUInt32(), + script: readVarSlice(), + sequence: readUInt32(), + witness: EMPTY_WITNESS + }); + } + const voutLen = readVarInt(); + for (i = 0; i < voutLen; ++i) { + tx.outs.push({ + value: readUInt64(), + script: readVarSlice() + }); + } + if (hasWitnesses) { + for (i = 0; i < vinLen; ++i) { + tx.ins[i].witness = readVector(); + } + // was this pointless? + if (!tx.hasWitnesses()) + throw new Error('Transaction has superfluous witness data'); + } + tx.locktime = readUInt32(); + if (__noStrict) + return tx; + if (offset !== buffer.length) + throw new Error('Transaction has unexpected data'); + return tx; + } + static fromHex(hex) { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); + } + static isCoinbaseHash(buffer) { + typeforce(types.Hash256bit, buffer); + for (var i = 0; i < 32; ++i) { + if (buffer[i] !== 0) + return false; + } + return true; + } + isCoinbase() { + return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash); + } + addInput(hash, index, sequence, scriptSig) { + typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer)), arguments); + if (types.Null(sequence)) { + sequence = Transaction.DEFAULT_SEQUENCE; + } + // Add the input and return the input's index + return (this.ins.push({ + hash: hash, + index: index, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence, + witness: EMPTY_WITNESS + }) - 1); + } + addOutput(scriptPubKey, value) { + typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); + // Add the output and return the output's index + return (this.outs.push({ + script: scriptPubKey, + value: value + }) - 1); + } + hasWitnesses() { + return this.ins.some((x) => { + return x.witness.length !== 0; + }); + } + weight() { + const base = this.__byteLength(false); + const total = this.__byteLength(true); + return base * 3 + total; + } + virtualSize() { + return Math.ceil(this.weight() / 4); + } + byteLength() { + return this.__byteLength(true); + } + __byteLength(__allowWitness) { + const hasWitnesses = __allowWitness && this.hasWitnesses(); + return ((hasWitnesses ? 10 : 8) + + varuint.encodingLength(this.ins.length) + + varuint.encodingLength(this.outs.length) + + this.ins.reduce((sum, input) => { + return sum + 40 + varSliceSize(input.script); + }, 0) + + this.outs.reduce((sum, output) => { + return sum + 8 + varSliceSize(output.script); + }, 0) + + (hasWitnesses ? this.ins.reduce((sum, input) => { + return sum + vectorSize(input.witness); + }, 0) : 0)); + } + clone() { + const newTx = new Transaction(); + newTx.version = this.version; + newTx.locktime = this.locktime; + newTx.ins = this.ins.map((txIn) => { + return { + hash: txIn.hash, + index: txIn.index, + script: txIn.script, + sequence: txIn.sequence, + witness: txIn.witness + }; + }); + newTx.outs = this.outs.map((txOut) => { + return { + script: txOut.script, + value: txOut.value + }; + }); + return newTx; + } + /** + * Hash transaction for signing a specific input. + * + * Bitcoin uses a different hash for each signed transaction input. + * This method copies the transaction, makes the necessary changes based on the + * hashType, and then hashes the result. + * This hash can then be used to sign the provided transaction input. + */ + hashForSignature(inIndex, prevOutScript, hashType) { + typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments); + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 + if (inIndex >= this.ins.length) + return ONE; + // ignore OP_CODESEPARATOR + const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter((x) => { + return x !== script_1.OPS.OP_CODESEPARATOR; + })); + const txTmp = this.clone(); + // SIGHASH_NONE: ignore all outputs? (wildcard payee) + if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { + txTmp.outs = []; + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, i) => { + if (i === inIndex) + return; + input.sequence = 0; + }); + // SIGHASH_SINGLE: ignore all outputs, except at the same index? + } + else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 + if (inIndex >= this.outs.length) + return ONE; + // truncate outputs after + txTmp.outs.length = inIndex + 1; + // "blank" outputs before + for (var i = 0; i < inIndex; i++) { + txTmp.outs[i] = BLANK_OUTPUT; + } + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, y) => { + if (y === inIndex) + return; + input.sequence = 0; + }); + } + // SIGHASH_ANYONECANPAY: ignore inputs entirely? + if (hashType & Transaction.SIGHASH_ANYONECANPAY) { + txTmp.ins = [txTmp.ins[inIndex]]; + txTmp.ins[0].script = ourScript; + // SIGHASH_ALL: only ignore input scripts + } + else { + // "blank" others input scripts + txTmp.ins.forEach((input) => { + input.script = EMPTY_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; + } + // serialize and hash + const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false); + return bcrypto.hash256(buffer); + } + hashForWitnessV0(inIndex, prevOutScript, value, hashType) { + typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments); + let tbuffer = Buffer.from([]); + let toffset = 0; + function writeSlice(slice) { + toffset += slice.copy(tbuffer, toffset); + } + function writeUInt32(i) { + toffset = tbuffer.writeUInt32LE(i, toffset); + } + function writeUInt64(i) { + toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); + } + function writeVarInt(i) { + varuint.encode(i, tbuffer, toffset); + toffset += varuint.encode.bytes; + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + let hashOutputs = ZERO; + let hashPrevouts = ZERO; + let hashSequence = ZERO; + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + tbuffer = Buffer.allocUnsafe(36 * this.ins.length); + toffset = 0; + this.ins.forEach((txIn) => { + writeSlice(txIn.hash); + writeUInt32(txIn.index); + }); + hashPrevouts = bcrypto.hash256(tbuffer); + } + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && + (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + tbuffer = Buffer.allocUnsafe(4 * this.ins.length); + toffset = 0; + this.ins.forEach((txIn) => { + writeUInt32(txIn.sequence); + }); + hashSequence = bcrypto.hash256(tbuffer); + } + if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + const txOutsSize = this.outs.reduce((sum, output) => { + return sum + 8 + varSliceSize(output.script); + }, 0); + tbuffer = Buffer.allocUnsafe(txOutsSize); + toffset = 0; + this.outs.forEach((out) => { + writeUInt64(out.value); + writeVarSlice(out.script); + }); + hashOutputs = bcrypto.hash256(tbuffer); + } + else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { + const output = this.outs[inIndex]; + tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); + toffset = 0; + writeUInt64(output.value); + writeVarSlice(output.script); + hashOutputs = bcrypto.hash256(tbuffer); + } + tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)); + toffset = 0; + const input = this.ins[inIndex]; + writeUInt32(this.version); + writeSlice(hashPrevouts); + writeSlice(hashSequence); + writeSlice(input.hash); + writeUInt32(input.index); + writeVarSlice(prevOutScript); + writeUInt64(value); + writeUInt32(input.sequence); + writeSlice(hashOutputs); + writeUInt32(this.locktime); + writeUInt32(hashType); + return bcrypto.hash256(tbuffer); + } + getHash(forWitness) { + // wtxid for coinbase is always 32 bytes of 0x00 + if (forWitness && this.isCoinbase()) + return Buffer.alloc(32, 0); + return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); + } + getId() { + // transaction hash's are displayed in reverse order + return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); + } + toBuffer(buffer, initialOffset) { + return this.__toBuffer(buffer, initialOffset, true); + } + __toBuffer(buffer, initialOffset, __allowWitness) { + if (!buffer) + buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness)); + let offset = initialOffset || 0; + function writeSlice(slice) { + offset += slice.copy(buffer, offset); + } + function writeUInt8(i) { + offset = buffer.writeUInt8(i, offset); + } + function writeUInt32(i) { + offset = buffer.writeUInt32LE(i, offset); + } + function writeInt32(i) { + offset = buffer.writeInt32LE(i, offset); + } + function writeUInt64(i) { + offset = bufferutils.writeUInt64LE(buffer, i, offset); + } + function writeVarInt(i) { + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + function writeVector(vector) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + writeInt32(this.version); + const hasWitnesses = __allowWitness && this.hasWitnesses(); + if (hasWitnesses) { + writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); + writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); + } + writeVarInt(this.ins.length); + this.ins.forEach((txIn) => { + writeSlice(txIn.hash); + writeUInt32(txIn.index); + writeVarSlice(txIn.script); + writeUInt32(txIn.sequence); + }); + writeVarInt(this.outs.length); + this.outs.forEach((txOut) => { + if (isOutput(txOut)) { + writeUInt64(txOut.value); + } + else { + writeSlice(txOut.valueBuffer); + } + writeVarSlice(txOut.script); + }); + if (hasWitnesses) { + this.ins.forEach((input) => { + writeVector(input.witness); + }); + } + writeUInt32(this.locktime); + // avoid slicing unless necessary + if (initialOffset !== undefined) + return buffer.slice(initialOffset, offset); + return buffer; + } + toHex() { + return this.toBuffer(undefined, undefined).toString('hex'); + } + setInputScript(index, scriptSig) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.ins[index].script = scriptSig; + } + setWitness(index, witness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].witness = witness; + } +} +Transaction.DEFAULT_SEQUENCE = 0xffffffff; +Transaction.SIGHASH_ALL = 0x01; +Transaction.SIGHASH_NONE = 0x02; +Transaction.SIGHASH_SINGLE = 0x03; +Transaction.SIGHASH_ANYONECANPAY = 0x80; +Transaction.ADVANCED_TRANSACTION_MARKER = 0x00; +Transaction.ADVANCED_TRANSACTION_FLAG = 0x01; +exports.Transaction = Transaction; diff --git a/src/transaction_builder.js b/src/transaction_builder.js new file mode 100644 index 0000000..2121156 --- /dev/null +++ b/src/transaction_builder.js @@ -0,0 +1,699 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks = require("./networks"); +const bufferutils_1 = require("./bufferutils"); +const transaction_1 = require("./transaction"); +const ECPair = require("./ecpair"); +const types = require("./types"); +const baddress = require("./address"); +const bcrypto = require("./crypto"); +const bscript = require("./script"); +const payments = require("./payments"); +const classify = require("./classify"); +const script_1 = require("./script"); +const typeforce = require('typeforce'); +const SCRIPT_TYPES = classify.types; +function txIsString(tx) { + return typeof tx === 'string' || tx instanceof String; +} +function txIsTransaction(tx) { + return tx instanceof transaction_1.Transaction; +} +class TransactionBuilder { + constructor(network, maximumFeeRate) { + this.__prevTxSet = {}; + this.network = network || networks.bitcoin; + // WARNING: This is __NOT__ to be relied on, its just another potential safety mechanism (safety in-depth) + this.maximumFeeRate = maximumFeeRate || 2500; + this.__inputs = []; + this.__tx = new transaction_1.Transaction(); + this.__tx.version = 2; + } + static fromTransaction(transaction, network) { + const txb = new TransactionBuilder(network); + // Copy transaction fields + txb.setVersion(transaction.version); + txb.setLockTime(transaction.locktime); + // Copy outputs (done first to avoid signature invalidation) + transaction.outs.forEach(txOut => { + txb.addOutput(txOut.script, txOut.value); + }); + // Copy inputs + transaction.ins.forEach(txIn => { + txb.__addInputUnsafe(txIn.hash, txIn.index, { + sequence: txIn.sequence, + script: txIn.script, + witness: txIn.witness + }); + }); + // fix some things not possible through the public API + txb.__inputs.forEach((input, i) => { + fixMultisigOrder(input, transaction, i); + }); + return txb; + } + setLockTime(locktime) { + typeforce(types.UInt32, locktime); + // if any signatures exist, throw + if (this.__inputs.some(input => { + if (!input.signatures) + return false; + return input.signatures.some(s => s !== undefined); + })) { + throw new Error('No, this would invalidate signatures'); + } + this.__tx.locktime = locktime; + } + setVersion(version) { + typeforce(types.UInt32, version); + // XXX: this might eventually become more complex depending on what the versions represent + this.__tx.version = version; + } + addInput(txHash, vout, sequence, prevOutScript) { + if (!this.__canModifyInputs()) { + throw new Error('No, this would invalidate signatures'); + } + let value = undefined; + // is it a hex string? + if (txIsString(txHash)) { + // transaction hashs's are displayed in reverse order, un-reverse it + txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); + // is it a Transaction object? + } + else if (txIsTransaction(txHash)) { + const txOut = txHash.outs[vout]; + prevOutScript = txOut.script; + value = txOut.value; + txHash = txHash.getHash(false); + } + return this.__addInputUnsafe(txHash, vout, { + sequence: sequence, + prevOutScript: prevOutScript, + value: value + }); + } + __addInputUnsafe(txHash, vout, options) { + if (transaction_1.Transaction.isCoinbaseHash(txHash)) { + throw new Error('coinbase inputs not supported'); + } + const prevTxOut = txHash.toString('hex') + ':' + vout; + if (this.__prevTxSet[prevTxOut] !== undefined) + throw new Error('Duplicate TxOut: ' + prevTxOut); + let input = {}; + // derive what we can from the scriptSig + if (options.script !== undefined) { + input = expandInput(options.script, options.witness || []); + } + // if an input value was given, retain it + if (options.value !== undefined) { + input.value = options.value; + } + // derive what we can from the previous transactions output script + if (!input.prevOutScript && options.prevOutScript) { + let prevOutType; + if (!input.pubkeys && !input.signatures) { + const expanded = expandOutput(options.prevOutScript); + if (expanded.pubkeys) { + input.pubkeys = expanded.pubkeys; + input.signatures = expanded.signatures; + } + prevOutType = expanded.type; + } + input.prevOutScript = options.prevOutScript; + input.prevOutType = prevOutType || classify.output(options.prevOutScript); + } + const vin = this.__tx.addInput(txHash, vout, options.sequence, options.scriptSig); + this.__inputs[vin] = input; + this.__prevTxSet[prevTxOut] = true; + return vin; + } + addOutput(scriptPubKey, value) { + if (!this.__canModifyOutputs()) { + throw new Error('No, this would invalidate signatures'); + } + // Attempt to get a script if it's a base58 or bech32 address string + if (typeof scriptPubKey === 'string') { + scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); + } + return this.__tx.addOutput(scriptPubKey, value); + } + build() { + return this.__build(false); + } + buildIncomplete() { + return this.__build(true); + } + __build(allowIncomplete) { + if (!allowIncomplete) { + if (!this.__tx.ins.length) + throw new Error('Transaction has no inputs'); + if (!this.__tx.outs.length) + throw new Error('Transaction has no outputs'); + } + const tx = this.__tx.clone(); + // create script signatures from inputs + this.__inputs.forEach((input, i) => { + if (!input.prevOutType && !allowIncomplete) + throw new Error('Transaction is not complete'); + const result = build(input.prevOutType, input, allowIncomplete); + if (!result) { + if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) + throw new Error('Unknown input type'); + if (!allowIncomplete) + throw new Error('Not enough information'); + return; + } + tx.setInputScript(i, result.input); + tx.setWitness(i, result.witness); + }); + if (!allowIncomplete) { + // do not rely on this, its merely a last resort + if (this.__overMaximumFees(tx.virtualSize())) { + throw new Error('Transaction has absurd fees'); + } + } + return tx; + } + sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { + // TODO: remove keyPair.network matching in 4.0.0 + if (keyPair.network && keyPair.network !== this.network) + throw new TypeError('Inconsistent network'); + if (!this.__inputs[vin]) + throw new Error('No input at index: ' + vin); + hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; + if (this.__needsOutputs(hashType)) + throw new Error('Transaction needs outputs'); + const input = this.__inputs[vin]; + // if redeemScript was previously provided, enforce consistency + if (input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript)) { + throw new Error('Inconsistent redeemScript'); + } + const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); + if (!canSign(input)) { + if (witnessValue !== undefined) { + if (input.value !== undefined && input.value !== witnessValue) + throw new Error('Input didn\'t match witnessValue'); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; + } + if (!canSign(input)) { + const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript); + // updates inline + Object.assign(input, prepared); + } + if (!canSign(input)) + throw Error(input.prevOutType + ' not supported'); + } + // ready to sign + let signatureHash; + if (input.hasWitness) { + signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType); + } + else { + signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType); + } + // enforce in order signing of public keys + const signed = input.pubkeys.some((pubKey, i) => { + if (!ourPubKey.equals(pubKey)) + return false; + if (input.signatures[i]) + throw new Error('Signature already exists'); + // TODO: add tests + if (ourPubKey.length !== 33 && input.hasWitness) { + throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); + } + const signature = keyPair.sign(signatureHash); + input.signatures[i] = bscript.signature.encode(signature, hashType); + return true; + }); + if (!signed) + throw new Error('Key pair cannot sign for this input'); + } + __canModifyInputs() { + return this.__inputs.every(input => { + if (!input.signatures) + return true; + return input.signatures.every(signature => { + if (!signature) + return true; + const hashType = signatureHashType(signature); + // if SIGHASH_ANYONECANPAY is set, signatures would not + // be invalidated by more inputs + return (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0; + }); + }); + } + __needsOutputs(signingHashType) { + if (signingHashType === transaction_1.Transaction.SIGHASH_ALL) { + return this.__tx.outs.length === 0; + } + // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs + // .build() will fail, but .buildIncomplete() is OK + return (this.__tx.outs.length === 0) && this.__inputs.some((input) => { + if (!input.signatures) + return false; + return input.signatures.some((signature) => { + if (!signature) + return false; // no signature, no issue + const hashType = signatureHashType(signature); + if (hashType & transaction_1.Transaction.SIGHASH_NONE) + return false; // SIGHASH_NONE doesn't care about outputs + return true; // SIGHASH_* does care + }); + }); + } + __canModifyOutputs() { + const nInputs = this.__tx.ins.length; + const nOutputs = this.__tx.outs.length; + return this.__inputs.every(input => { + if (input.signatures === undefined) + return true; + return input.signatures.every((signature => { + if (!signature) + return true; + const hashType = signatureHashType(signature); + const hashTypeMod = hashType & 0x1f; + if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) + return true; + if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { + // if SIGHASH_SINGLE is set, and nInputs > nOutputs + // some signatures would be invalidated by the addition + // of more outputs + return nInputs <= nOutputs; + } + })); + }); + } + __overMaximumFees(bytes) { + // not all inputs will have .value defined + const incoming = this.__inputs.reduce((a, x) => a + (x.value >>> 0), 0); + // but all outputs do, and if we have any input value + // we can immediately determine if the outputs are too small + const outgoing = this.__tx.outs.reduce((a, x) => a + x.value, 0); + const fee = incoming - outgoing; + const feeRate = fee / bytes; + return feeRate > this.maximumFeeRate; + } +} +exports.TransactionBuilder = TransactionBuilder; +function expandInput(scriptSig, witnessStack, type, scriptPubKey) { + if (scriptSig.length === 0 && witnessStack.length === 0) + return {}; + if (!type) { + let ssType = classify.input(scriptSig, true); + let wsType = classify.witness(witnessStack, true); + if (ssType === SCRIPT_TYPES.NONSTANDARD) + ssType = undefined; + if (wsType === SCRIPT_TYPES.NONSTANDARD) + wsType = undefined; + type = ssType || wsType; + } + switch (type) { + case SCRIPT_TYPES.P2WPKH: { + const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack }); + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WPKH, + pubkeys: [pubkey], + signatures: [signature] + }; + } + case SCRIPT_TYPES.P2PKH: { + const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig }); + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2PKH, + pubkeys: [pubkey], + signatures: [signature] + }; + } + case SCRIPT_TYPES.P2PK: { + const { signature } = payments.p2pk({ input: scriptSig }); + return { + prevOutType: SCRIPT_TYPES.P2PK, + pubkeys: [undefined], + signatures: [signature] + }; + } + case SCRIPT_TYPES.P2MS: { + const { m, pubkeys, signatures } = payments.p2ms({ + input: scriptSig, + output: scriptPubKey + }, { allowIncomplete: true }); + return { + prevOutType: SCRIPT_TYPES.P2MS, + pubkeys: pubkeys, + signatures: signatures, + maxSignatures: m + }; + } + } + if (type === SCRIPT_TYPES.P2SH) { + const { output, redeem } = payments.p2sh({ + input: scriptSig, + witness: witnessStack + }); + const outputType = classify.output(redeem.output); + const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output); + if (!expanded.prevOutType) + return {}; + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2SH, + redeemScript: redeem.output, + redeemScriptType: expanded.prevOutType, + witnessScript: expanded.witnessScript, + witnessScriptType: expanded.witnessScriptType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures + }; + } + if (type === SCRIPT_TYPES.P2WSH) { + const { output, redeem } = payments.p2wsh({ + input: scriptSig, + witness: witnessStack + }); + const outputType = classify.output(redeem.output); + let expanded; + if (outputType === SCRIPT_TYPES.P2WPKH) { + expanded = expandInput(redeem.input, redeem.witness, outputType); + } + else { + expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output); + } + if (!expanded.prevOutType) + return {}; + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WSH, + witnessScript: redeem.output, + witnessScriptType: expanded.prevOutType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures + }; + } + return { + prevOutType: SCRIPT_TYPES.NONSTANDARD, + prevOutScript: scriptSig + }; +} +// could be done in expandInput, but requires the original Transaction for hashForSignature +function fixMultisigOrder(input, transaction, vin) { + if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) + return; + if (input.pubkeys.length === input.signatures.length) + return; + const unmatched = input.signatures.concat(); + input.signatures = input.pubkeys.map(pubKey => { + const keyPair = ECPair.fromPublicKey(pubKey); + let match; + // check for a signature + unmatched.some((signature, i) => { + // skip if undefined || OP_0 + if (!signature) + return false; + // TODO: avoid O(n) hashForSignature + const parsed = bscript.signature.decode(signature); + const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType); + // skip if signature does not match pubKey + if (!keyPair.verify(hash, parsed.signature)) + return false; + // remove matched signature from unmatched + unmatched[i] = undefined; + match = signature; + return true; + }); + return match; + }); +} +function expandOutput(script, ourPubKey) { + typeforce(types.Buffer, script); + const type = classify.output(script); + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (!ourPubKey) + return { type }; + // does our hash160(pubKey) match the output scripts? + const pkh1 = payments.p2pkh({ output: script }).hash; + const pkh2 = bcrypto.hash160(ourPubKey); + if (!pkh1.equals(pkh2)) + return { type }; + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined] + }; + } + case SCRIPT_TYPES.P2WPKH: { + if (!ourPubKey) + return { type }; + // does our hash160(pubKey) match the output scripts? + const wpkh1 = payments.p2wpkh({ output: script }).hash; + const wpkh2 = bcrypto.hash160(ourPubKey); + if (!wpkh1.equals(wpkh2)) + return { type }; + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined] + }; + } + case SCRIPT_TYPES.P2PK: { + const p2pk = payments.p2pk({ output: script }); + return { + type, + pubkeys: [p2pk.pubkey], + signatures: [undefined] + }; + } + case SCRIPT_TYPES.P2MS: { + const p2ms = payments.p2ms({ output: script }); + return { + type, + pubkeys: p2ms.pubkeys, + signatures: p2ms.pubkeys.map(() => undefined), + maxSignatures: p2ms.m + }; + } + } + return { type }; +} +function prepareInput(input, ourPubKey, redeemScript, witnessScript) { + if (redeemScript && witnessScript) { + const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); + const p2wshAlt = payments.p2wsh({ output: redeemScript }); + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); + const p2shAlt = payments.p2sh({ redeem: p2wsh }); + // enforces P2SH(P2WSH(...)) + if (!p2wsh.hash.equals(p2wshAlt.hash)) + throw new Error('Witness script inconsistent with prevOutScript'); + if (!p2sh.hash.equals(p2shAlt.hash)) + throw new Error('Redeem script inconsistent with prevOutScript'); + const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')'); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); + return { + redeemScript, + redeemScriptType: SCRIPT_TYPES.P2WSH, + witnessScript, + witnessScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + hasWitness: true, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures + }; + } + if (redeemScript) { + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); + if (input.prevOutScript) { + let p2shAlt; + try { + p2shAlt = payments.p2sh({ output: input.prevOutScript }); + } + catch (e) { + throw new Error('PrevOutScript must be P2SH'); + } + if (!p2sh.hash.equals(p2shAlt.hash)) + throw new Error('Redeem script inconsistent with prevOutScript'); + } + const expanded = expandOutput(p2sh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error(expanded.type + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')'); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = redeemScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; + } + return { + redeemScript, + redeemScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures + }; + } + if (witnessScript) { + const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); + if (input.prevOutScript) { + const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }); + if (!p2wsh.hash.equals(p2wshAlt.hash)) + throw new Error('Witness script inconsistent with prevOutScript'); + } + const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')'); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2WSH(P2WPKH) is a consensus failure'); + return { + witnessScript, + witnessScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2WSH, + prevOutScript: p2wsh.output, + hasWitness: true, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures + }; + } + if (input.prevOutType && input.prevOutScript) { + // embedded scripts are not possible without extra information + if (input.prevOutType === SCRIPT_TYPES.P2SH) + throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript'); + if (input.prevOutType === SCRIPT_TYPES.P2WSH) + throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript'); + if (!input.prevOutScript) + throw new Error('PrevOutScript is missing'); + const expanded = expandOutput(input.prevOutScript, ourPubKey); + if (!expanded.pubkeys) + throw new Error(expanded.type + ' not supported (' + bscript.toASM(input.prevOutScript) + ')'); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = input.prevOutScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; + } + return { + prevOutType: expanded.type, + prevOutScript: input.prevOutScript, + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures + }; + } + const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; + return { + prevOutType: SCRIPT_TYPES.P2PKH, + prevOutScript: prevOutScript, + hasWitness: false, + signScript: prevOutScript, + signType: SCRIPT_TYPES.P2PKH, + pubkeys: [ourPubKey], + signatures: [undefined] + }; +} +function build(type, input, allowIncomplete) { + const pubkeys = (input.pubkeys || []); + let signatures = (input.signatures || []); + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (pubkeys.length === 0) + break; + if (signatures.length === 0) + break; + return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2WPKH: { + if (pubkeys.length === 0) + break; + if (signatures.length === 0) + break; + return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2PK: { + if (pubkeys.length === 0) + break; + if (signatures.length === 0) + break; + return payments.p2pk({ signature: signatures[0] }); + } + case SCRIPT_TYPES.P2MS: { + const m = input.maxSignatures; + if (allowIncomplete) { + signatures = signatures.map(x => x || script_1.OPS.OP_0); + } + else { + signatures = signatures.filter(x => x); + } + // if the transaction is not not complete (complete), or if signatures.length === m, validate + // otherwise, the number of OP_0's may be >= m, so don't validate (boo) + const validate = !allowIncomplete || (m === signatures.length); + return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }); + } + case SCRIPT_TYPES.P2SH: { + const redeem = build(input.redeemScriptType, input, allowIncomplete); + if (!redeem) + return; + return payments.p2sh({ + redeem: { + output: redeem.output || input.redeemScript, + input: redeem.input, + witness: redeem.witness + } + }); + } + case SCRIPT_TYPES.P2WSH: { + const redeem = build(input.witnessScriptType, input, allowIncomplete); + if (!redeem) + return; + return payments.p2wsh({ + redeem: { + output: input.witnessScript, + input: redeem.input, + witness: redeem.witness + } + }); + } + } +} +function canSign(input) { + return input.signScript !== undefined && + input.signType !== undefined && + input.pubkeys !== undefined && + input.signatures !== undefined && + input.signatures.length === input.pubkeys.length && + input.pubkeys.length > 0 && + (input.hasWitness === false || + input.value !== undefined); +} +function signatureHashType(buffer) { + return buffer.readUInt8(buffer.length - 1); +} diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..13d1bc8 --- /dev/null +++ b/src/types.js @@ -0,0 +1,48 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const typeforce = require('typeforce'); +const UINT31_MAX = Math.pow(2, 31) - 1; +function UInt31(value) { + return typeforce.UInt32(value) && value <= UINT31_MAX; +} +exports.UInt31 = UInt31; +function BIP32Path(value) { + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); +} +exports.BIP32Path = BIP32Path; +BIP32Path.toJSON = function () { return 'BIP32 derivation path'; }; +const SATOSHI_MAX = 21 * 1e14; +function Satoshi(value) { + return typeforce.UInt53(value) && value <= SATOSHI_MAX; +} +exports.Satoshi = Satoshi; +// external dependent types +exports.ECPoint = typeforce.quacksLike('Point'); +// exposed, external API +exports.Network = typeforce.compile({ + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: typeforce.UInt32, + private: typeforce.UInt32 + }, + pubKeyHash: typeforce.UInt8, + scriptHash: typeforce.UInt8, + wif: typeforce.UInt8 +}); +exports.Buffer256bit = typeforce.BufferN(32); +exports.Hash160bit = typeforce.BufferN(20); +exports.Hash256bit = typeforce.BufferN(32); +exports.Number = typeforce.Number; +exports.Array = typeforce.Array; +exports.Boolean = typeforce.Boolean; +exports.String = typeforce.String; +exports.Buffer = typeforce.Buffer; +exports.Hex = typeforce.Hex; +exports.maybe = typeforce.maybe; +exports.tuple = typeforce.tuple; +exports.UInt8 = typeforce.UInt8; +exports.UInt32 = typeforce.UInt32; +exports.Function = typeforce.Function; +exports.BufferN = typeforce.BufferN; +exports.Null = typeforce.Null; +exports.oneOf = typeforce.oneOf; diff --git a/test/address.js b/test/address.js index 19093b6..a0f4df0 100644 --- a/test/address.js +++ b/test/address.js @@ -1,7 +1,7 @@ const { describe, it } = require('mocha') const assert = require('assert') -const baddress = require('../dist/src/address') -const bscript = require('../dist/src/script') +const baddress = require('../src/address') +const bscript = require('../src/script') const fixtures = require('./fixtures/address.json') const NETWORKS = Object.assign({ litecoin: { @@ -14,7 +14,7 @@ const NETWORKS = Object.assign({ scriptHash: 0x32, wif: 0xb0 } -}, require('../dist/src/networks')) +}, require('../src/networks')) describe('address', function () { describe('fromBase58Check', function () { diff --git a/test/bufferutils.js b/test/bufferutils.js index ceb649a..5f2c39e 100644 --- a/test/bufferutils.js +++ b/test/bufferutils.js @@ -1,6 +1,6 @@ const { describe, it } = require('mocha') const assert = require('assert') -const bufferutils = require('../dist/src/bufferutils') +const bufferutils = require('../src/bufferutils') const fixtures = require('./fixtures/bufferutils.json') diff --git a/test/classify.js b/test/classify.js index 33225b5..3efcc74 100644 --- a/test/classify.js +++ b/test/classify.js @@ -1,18 +1,18 @@ const { describe, it } = require('mocha') const assert = require('assert') -const bscript = require('../dist/src/script') -const classify = require('../dist/src/classify') +const bscript = require('../src/script') +const classify = require('../src/classify') const fixtures = require('./fixtures/templates.json') -const multisig = require('../dist/src/templates/multisig') -const nullData = require('../dist/src/templates/nulldata') -const pubKey = require('../dist/src/templates/pubkey') -const pubKeyHash = require('../dist/src/templates/pubkeyhash') -const scriptHash = require('../dist/src/templates/scripthash') -const witnessPubKeyHash = require('../dist/src/templates/witnesspubkeyhash') -const witnessScriptHash = require('../dist/src/templates/witnessscripthash') -const witnessCommitment = require('../dist/src/templates/witnesscommitment') +const multisig = require('../src/templates/multisig') +const nullData = require('../src/templates/nulldata') +const pubKey = require('../src/templates/pubkey') +const pubKeyHash = require('../src/templates/pubkeyhash') +const scriptHash = require('../src/templates/scripthash') +const witnessPubKeyHash = require('../src/templates/witnesspubkeyhash') +const witnessScriptHash = require('../src/templates/witnessscripthash') +const witnessCommitment = require('../src/templates/witnesscommitment') const tmap = { pubKey, diff --git a/test/crypto.js b/test/crypto.js index 0a01c98..3f7802a 100644 --- a/test/crypto.js +++ b/test/crypto.js @@ -1,6 +1,6 @@ const { describe, it } = require('mocha') const assert = require('assert') -const bcrypto = require('../dist/src/crypto') +const bcrypto = require('../src/crypto') const fixtures = require('./fixtures/crypto') diff --git a/test/ecpair.js b/test/ecpair.js index f590e18..4299470 100644 --- a/test/ecpair.js +++ b/test/ecpair.js @@ -5,12 +5,12 @@ const assert = require('assert') const proxyquire = require('proxyquire') const hoodwink = require('hoodwink') -const ECPair = require('../dist/src/ecpair') +const ECPair = require('../src/ecpair') const tinysecp = require('tiny-secp256k1') const fixtures = require('./fixtures/ecpair.json') -const NETWORKS = require('../dist/src/networks') +const NETWORKS = require('../src/networks') const NETWORKS_LIST = [] // Object.values(NETWORKS) for (let networkName in NETWORKS) { NETWORKS_LIST.push(NETWORKS[networkName]) @@ -144,7 +144,7 @@ describe('ECPair', function () { describe('uses randombytes RNG', function () { it('generates a ECPair', function () { const stub = { randombytes: function () { return d } } - const ProxiedECPair = proxyquire('../dist/src/ecpair', stub) + const ProxiedECPair = proxyquire('../src/ecpair', stub) const keyPair = ProxiedECPair.makeRandom() assert.strictEqual(keyPair.toWIF(), exWIF) diff --git a/test/payments.js b/test/payments.js index 6617047..a4f07bb 100644 --- a/test/payments.js +++ b/test/payments.js @@ -5,7 +5,7 @@ const u = require('./payments.utils') ;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) { describe(p, function () { let fn - let payment = require('../dist/src/payments/' + p) + let payment = require('../src/payments/' + p) if (p === 'embed') { fn = payment.p2data } else { diff --git a/test/payments.utils.js b/test/payments.utils.js index 086561d..485bf03 100644 --- a/test/payments.utils.js +++ b/test/payments.utils.js @@ -1,6 +1,6 @@ const t = require('assert') -const bscript = require('../dist/src/script') -const BNETWORKS = require('../dist/src/networks') +const bscript = require('../src/script') +const BNETWORKS = require('../src/networks') function tryHex (x) { if (Buffer.isBuffer(x)) return x.toString('hex') diff --git a/test/script.js b/test/script.js index 013d50a..c2a60ad 100644 --- a/test/script.js +++ b/test/script.js @@ -1,6 +1,6 @@ const { describe, it } = require('mocha') const assert = require('assert') -const bscript = require('../dist/src/script') +const bscript = require('../src/script') const minimalData = require('minimaldata') const fixtures = require('./fixtures/script.json') diff --git a/test/script_number.js b/test/script_number.js index 1d38e48..bc8f395 100644 --- a/test/script_number.js +++ b/test/script_number.js @@ -1,6 +1,6 @@ const { describe, it } = require('mocha') const assert = require('assert') -const scriptNumber = require('../dist/src/script_number') +const scriptNumber = require('../src/script_number') const fixtures = require('./fixtures/script_number.json') describe('script-number', function () { diff --git a/test/script_signature.js b/test/script_signature.js index e56ff68..cee69bd 100644 --- a/test/script_signature.js +++ b/test/script_signature.js @@ -1,6 +1,6 @@ const { describe, it } = require('mocha') const assert = require('assert') -const bscriptSig = require('../dist/src/script').signature +const bscriptSig = require('../src/script').signature const Buffer = require('safe-buffer').Buffer const fixtures = require('./fixtures/signature.json') diff --git a/test/transaction.js b/test/transaction.js index 74afc8f..d923ce2 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -1,6 +1,6 @@ const { describe, it, beforeEach } = require('mocha') const assert = require('assert') -const bscript = require('../dist/src/script') +const bscript = require('../src/script') const fixtures = require('./fixtures/transaction') const Transaction = require('..').Transaction diff --git a/test/transaction_builder.js b/test/transaction_builder.js index 34d9270..574b669 100644 --- a/test/transaction_builder.js +++ b/test/transaction_builder.js @@ -1,13 +1,13 @@ const { describe, it, beforeEach } = require('mocha') const assert = require('assert') -const baddress = require('../dist/src/address') -const bscript = require('../dist/src/script') -const payments = require('../dist/src/payments') +const baddress = require('../src/address') +const bscript = require('../src/script') +const payments = require('../src/payments') -const ECPair = require('../dist/src/ecpair') +const ECPair = require('../src/ecpair') const Transaction = require('..').Transaction const TransactionBuilder = require('..').TransactionBuilder -const NETWORKS = require('../dist/src/networks') +const NETWORKS = require('../src/networks') const fixtures = require('./fixtures/transaction_builder') diff --git a/test/types.js b/test/types.js index 831d415..d245d53 100644 --- a/test/types.js +++ b/test/types.js @@ -1,6 +1,6 @@ const { describe, it } = require('mocha') const assert = require('assert') -const types = require('../dist/src/types') +const types = require('../src/types') const typeforce = require('typeforce') describe('types', function () { diff --git a/src/address.ts b/ts_src/address.ts similarity index 100% rename from src/address.ts rename to ts_src/address.ts diff --git a/src/block.ts b/ts_src/block.ts similarity index 100% rename from src/block.ts rename to ts_src/block.ts diff --git a/src/bufferutils.ts b/ts_src/bufferutils.ts similarity index 100% rename from src/bufferutils.ts rename to ts_src/bufferutils.ts diff --git a/src/classify.ts b/ts_src/classify.ts similarity index 100% rename from src/classify.ts rename to ts_src/classify.ts diff --git a/src/crypto.ts b/ts_src/crypto.ts similarity index 100% rename from src/crypto.ts rename to ts_src/crypto.ts diff --git a/src/ecpair.ts b/ts_src/ecpair.ts similarity index 100% rename from src/ecpair.ts rename to ts_src/ecpair.ts diff --git a/src/index.ts b/ts_src/index.ts similarity index 100% rename from src/index.ts rename to ts_src/index.ts diff --git a/src/networks.ts b/ts_src/networks.ts similarity index 100% rename from src/networks.ts rename to ts_src/networks.ts diff --git a/src/payments/embed.ts b/ts_src/payments/embed.ts similarity index 100% rename from src/payments/embed.ts rename to ts_src/payments/embed.ts diff --git a/src/payments/index.ts b/ts_src/payments/index.ts similarity index 100% rename from src/payments/index.ts rename to ts_src/payments/index.ts diff --git a/src/payments/lazy.ts b/ts_src/payments/lazy.ts similarity index 100% rename from src/payments/lazy.ts rename to ts_src/payments/lazy.ts diff --git a/src/payments/p2ms.ts b/ts_src/payments/p2ms.ts similarity index 100% rename from src/payments/p2ms.ts rename to ts_src/payments/p2ms.ts diff --git a/src/payments/p2pk.ts b/ts_src/payments/p2pk.ts similarity index 100% rename from src/payments/p2pk.ts rename to ts_src/payments/p2pk.ts diff --git a/src/payments/p2pkh.ts b/ts_src/payments/p2pkh.ts similarity index 100% rename from src/payments/p2pkh.ts rename to ts_src/payments/p2pkh.ts diff --git a/src/payments/p2sh.ts b/ts_src/payments/p2sh.ts similarity index 100% rename from src/payments/p2sh.ts rename to ts_src/payments/p2sh.ts diff --git a/src/payments/p2wpkh.ts b/ts_src/payments/p2wpkh.ts similarity index 100% rename from src/payments/p2wpkh.ts rename to ts_src/payments/p2wpkh.ts diff --git a/src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts similarity index 100% rename from src/payments/p2wsh.ts rename to ts_src/payments/p2wsh.ts diff --git a/src/script.ts b/ts_src/script.ts similarity index 100% rename from src/script.ts rename to ts_src/script.ts diff --git a/src/script_number.ts b/ts_src/script_number.ts similarity index 100% rename from src/script_number.ts rename to ts_src/script_number.ts diff --git a/src/script_signature.ts b/ts_src/script_signature.ts similarity index 100% rename from src/script_signature.ts rename to ts_src/script_signature.ts diff --git a/src/templates/multisig/index.ts b/ts_src/templates/multisig/index.ts similarity index 100% rename from src/templates/multisig/index.ts rename to ts_src/templates/multisig/index.ts diff --git a/src/templates/multisig/input.ts b/ts_src/templates/multisig/input.ts similarity index 100% rename from src/templates/multisig/input.ts rename to ts_src/templates/multisig/input.ts diff --git a/src/templates/multisig/output.ts b/ts_src/templates/multisig/output.ts similarity index 100% rename from src/templates/multisig/output.ts rename to ts_src/templates/multisig/output.ts diff --git a/src/templates/nulldata.ts b/ts_src/templates/nulldata.ts similarity index 100% rename from src/templates/nulldata.ts rename to ts_src/templates/nulldata.ts diff --git a/src/templates/pubkey/index.ts b/ts_src/templates/pubkey/index.ts similarity index 100% rename from src/templates/pubkey/index.ts rename to ts_src/templates/pubkey/index.ts diff --git a/src/templates/pubkey/input.ts b/ts_src/templates/pubkey/input.ts similarity index 100% rename from src/templates/pubkey/input.ts rename to ts_src/templates/pubkey/input.ts diff --git a/src/templates/pubkey/output.ts b/ts_src/templates/pubkey/output.ts similarity index 100% rename from src/templates/pubkey/output.ts rename to ts_src/templates/pubkey/output.ts diff --git a/src/templates/pubkeyhash/index.ts b/ts_src/templates/pubkeyhash/index.ts similarity index 100% rename from src/templates/pubkeyhash/index.ts rename to ts_src/templates/pubkeyhash/index.ts diff --git a/src/templates/pubkeyhash/input.ts b/ts_src/templates/pubkeyhash/input.ts similarity index 100% rename from src/templates/pubkeyhash/input.ts rename to ts_src/templates/pubkeyhash/input.ts diff --git a/src/templates/pubkeyhash/output.ts b/ts_src/templates/pubkeyhash/output.ts similarity index 100% rename from src/templates/pubkeyhash/output.ts rename to ts_src/templates/pubkeyhash/output.ts diff --git a/src/templates/scripthash/index.ts b/ts_src/templates/scripthash/index.ts similarity index 100% rename from src/templates/scripthash/index.ts rename to ts_src/templates/scripthash/index.ts diff --git a/src/templates/scripthash/input.ts b/ts_src/templates/scripthash/input.ts similarity index 100% rename from src/templates/scripthash/input.ts rename to ts_src/templates/scripthash/input.ts diff --git a/src/templates/scripthash/output.ts b/ts_src/templates/scripthash/output.ts similarity index 100% rename from src/templates/scripthash/output.ts rename to ts_src/templates/scripthash/output.ts diff --git a/src/templates/witnesscommitment/index.ts b/ts_src/templates/witnesscommitment/index.ts similarity index 100% rename from src/templates/witnesscommitment/index.ts rename to ts_src/templates/witnesscommitment/index.ts diff --git a/src/templates/witnesscommitment/output.ts b/ts_src/templates/witnesscommitment/output.ts similarity index 100% rename from src/templates/witnesscommitment/output.ts rename to ts_src/templates/witnesscommitment/output.ts diff --git a/src/templates/witnesspubkeyhash/index.ts b/ts_src/templates/witnesspubkeyhash/index.ts similarity index 100% rename from src/templates/witnesspubkeyhash/index.ts rename to ts_src/templates/witnesspubkeyhash/index.ts diff --git a/src/templates/witnesspubkeyhash/input.ts b/ts_src/templates/witnesspubkeyhash/input.ts similarity index 100% rename from src/templates/witnesspubkeyhash/input.ts rename to ts_src/templates/witnesspubkeyhash/input.ts diff --git a/src/templates/witnesspubkeyhash/output.ts b/ts_src/templates/witnesspubkeyhash/output.ts similarity index 100% rename from src/templates/witnesspubkeyhash/output.ts rename to ts_src/templates/witnesspubkeyhash/output.ts diff --git a/src/templates/witnessscripthash/index.ts b/ts_src/templates/witnessscripthash/index.ts similarity index 100% rename from src/templates/witnessscripthash/index.ts rename to ts_src/templates/witnessscripthash/index.ts diff --git a/src/templates/witnessscripthash/input.ts b/ts_src/templates/witnessscripthash/input.ts similarity index 100% rename from src/templates/witnessscripthash/input.ts rename to ts_src/templates/witnessscripthash/input.ts diff --git a/src/templates/witnessscripthash/output.ts b/ts_src/templates/witnessscripthash/output.ts similarity index 100% rename from src/templates/witnessscripthash/output.ts rename to ts_src/templates/witnessscripthash/output.ts diff --git a/src/transaction.ts b/ts_src/transaction.ts similarity index 100% rename from src/transaction.ts rename to ts_src/transaction.ts diff --git a/src/transaction_builder.ts b/ts_src/transaction_builder.ts similarity index 100% rename from src/transaction_builder.ts rename to ts_src/transaction_builder.ts diff --git a/src/types.ts b/ts_src/types.ts similarity index 100% rename from src/types.ts rename to ts_src/types.ts diff --git a/tsconfig.json b/tsconfig.json index 1d68be9..787769d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,9 @@ "compilerOptions": { "target": "ES2015", "module": "commonjs", - "outDir": "./dist", - "rootDir": "./", + "outDir": "./src", + "declarationDir": "./types", + "rootDir": "./ts_src", "types": [ "node" ], @@ -19,7 +20,7 @@ "esModuleInterop": false }, "include": [ - "src/**/*" + "ts_src/**/*.ts" ], "exclude": [ "**/*.spec.ts", diff --git a/types/address.d.ts b/types/address.d.ts new file mode 100644 index 0000000..68fbedd --- /dev/null +++ b/types/address.d.ts @@ -0,0 +1,17 @@ +/// +import { Network } from './networks'; +export declare type Base58CheckResult = { + hash: Buffer; + version: number; +}; +export declare type Bech32Result = { + version: number; + prefix: string; + data: Buffer; +}; +export declare function fromBase58Check(address: string): Base58CheckResult; +export declare function fromBech32(address: string): Bech32Result; +export declare function toBase58Check(hash: Buffer, version: number): string; +export declare function toBech32(data: Buffer, version: number, prefix: string): string; +export declare function fromOutputScript(output: Buffer, network: Network): string; +export declare function toOutputScript(address: string, network: Network): Buffer; diff --git a/types/block.d.ts b/types/block.d.ts new file mode 100644 index 0000000..ff98de2 --- /dev/null +++ b/types/block.d.ts @@ -0,0 +1,27 @@ +/// +import { Transaction } from './transaction'; +export declare class Block { + version: number; + prevHash?: Buffer; + merkleRoot?: Buffer; + timestamp: number; + witnessCommit?: Buffer; + bits: number; + nonce: number; + transactions?: Array; + constructor(); + static fromBuffer(buffer: Buffer): Block; + static fromHex(hex: string): Block; + static calculateTarget(bits: number): Buffer; + static calculateMerkleRoot(transactions: Array, forWitness?: boolean): Buffer; + hasWitnessCommit(): boolean; + byteLength(headersOnly: boolean): number; + getHash(): Buffer; + getId(): string; + getUTCDate(): Date; + toBuffer(headersOnly: boolean): Buffer; + toHex(headersOnly: boolean): string; + checkMerkleRoot(): boolean; + checkWitnessCommit(): boolean; + checkProofOfWork(): boolean; +} diff --git a/types/bufferutils.d.ts b/types/bufferutils.d.ts new file mode 100644 index 0000000..2686e4e --- /dev/null +++ b/types/bufferutils.d.ts @@ -0,0 +1,4 @@ +/// +export declare function readUInt64LE(buffer: Buffer, offset: number): number; +export declare function writeUInt64LE(buffer: Buffer, value: number, offset: number): number; +export declare function reverseBuffer(buffer: Buffer): Buffer; diff --git a/types/classify.d.ts b/types/classify.d.ts new file mode 100644 index 0000000..b394c71 --- /dev/null +++ b/types/classify.d.ts @@ -0,0 +1,16 @@ +/// +declare const types: { + P2MS: string; + NONSTANDARD: string; + NULLDATA: string; + P2PK: string; + P2PKH: string; + P2SH: string; + P2WPKH: string; + P2WSH: string; + WITNESS_COMMITMENT: string; +}; +declare function classifyOutput(script: Buffer): string; +declare function classifyInput(script: Buffer, allowIncomplete: boolean): string; +declare function classifyWitness(script: Array, allowIncomplete: boolean): string; +export { classifyInput as input, classifyOutput as output, classifyWitness as witness, types, }; diff --git a/types/crypto.d.ts b/types/crypto.d.ts new file mode 100644 index 0000000..1743681 --- /dev/null +++ b/types/crypto.d.ts @@ -0,0 +1,6 @@ +/// +export declare function ripemd160(buffer: Buffer): Buffer; +export declare function sha1(buffer: Buffer): Buffer; +export declare function sha256(buffer: Buffer): Buffer; +export declare function hash160(buffer: Buffer): Buffer; +export declare function hash256(buffer: Buffer): Buffer; diff --git a/types/ecpair.d.ts b/types/ecpair.d.ts new file mode 100644 index 0000000..58ea4be --- /dev/null +++ b/types/ecpair.d.ts @@ -0,0 +1,34 @@ +/// +import { Network } from './networks'; +interface ECPairOptions { + compressed?: boolean; + network?: Network; + rng?(arg0: Buffer): Buffer; +} +export interface ECPairInterface { + compressed: boolean; + network: Network; + privateKey?: Buffer; + publicKey?: Buffer; + toWIF(): string; + sign(hash: Buffer): Buffer; + verify(hash: Buffer, signature: Buffer): Buffer; + getPublicKey?(): Buffer; +} +declare class ECPair implements ECPairInterface { + compressed: boolean; + network: Network; + private __d?; + private __Q?; + constructor(d?: Buffer, Q?: Buffer, options?: ECPairOptions); + readonly privateKey: Buffer | undefined; + readonly publicKey: Buffer | undefined; + toWIF(): string; + sign(hash: Buffer): Buffer; + verify(hash: Buffer, signature: Buffer): Buffer; +} +declare function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair; +declare function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair; +declare function fromWIF(string: string, network?: Network | Array): ECPair; +declare function makeRandom(options?: ECPairOptions): ECPair; +export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF }; diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..9682271 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,17 @@ +import * as bip32 from 'bip32'; +import * as ECPair from './ecpair'; +import * as address from './address'; +import * as crypto from './crypto'; +import * as networks from './networks'; +import * as payments from './payments'; +import * as script from './script'; +export { ECPair, address, bip32, crypto, networks, payments, script, }; +export { Block } from './block'; +export { Transaction } from './transaction'; +export { TransactionBuilder } from './transaction_builder'; +export { OPS as opcodes } from './script'; +export { Payment, PaymentOpts } from './payments'; +export { Input as TxInput, Output as TxOutput } from './transaction'; +export { Network } from './networks'; +export { OpCode } from './script'; +export { BIP32Interface } from 'bip32'; diff --git a/types/networks.d.ts b/types/networks.d.ts new file mode 100644 index 0000000..e402873 --- /dev/null +++ b/types/networks.d.ts @@ -0,0 +1,16 @@ +export declare type Network = { + messagePrefix: string; + bech32: string; + bip32: bip32; + pubKeyHash: number; + scriptHash: number; + wif: number; +}; +declare type bip32 = { + public: number; + private: number; +}; +export declare const bitcoin: Network; +export declare const regtest: Network; +export declare const testnet: Network; +export {}; diff --git a/types/payments/embed.d.ts b/types/payments/embed.d.ts new file mode 100644 index 0000000..76a9ed2 --- /dev/null +++ b/types/payments/embed.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2data(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/index.d.ts b/types/payments/index.d.ts new file mode 100644 index 0000000..5afc847 --- /dev/null +++ b/types/payments/index.d.ts @@ -0,0 +1,30 @@ +/// +import { Network } from '../networks'; +import { p2data as embed } from './embed'; +import { p2ms } from './p2ms'; +import { p2pk } from './p2pk'; +import { p2pkh } from './p2pkh'; +import { p2sh } from './p2sh'; +import { p2wpkh } from './p2wpkh'; +import { p2wsh } from './p2wsh'; +export interface Payment { + network?: Network; + output?: Buffer; + data?: Array; + m?: number; + n?: number; + pubkeys?: Array; + input?: Buffer; + signatures?: Array; + pubkey?: Buffer; + signature?: Buffer; + address?: string; + hash?: Buffer; + redeem?: Payment; + witness?: Array; +} +export interface PaymentOpts { + validate?: boolean; + allowIncomplete?: boolean; +} +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }; diff --git a/types/payments/lazy.d.ts b/types/payments/lazy.d.ts new file mode 100644 index 0000000..705f530 --- /dev/null +++ b/types/payments/lazy.d.ts @@ -0,0 +1,2 @@ +export declare function prop(object: Object, name: string, f: () => any): void; +export declare function value(f: () => T): () => T; diff --git a/types/payments/p2ms.d.ts b/types/payments/p2ms.d.ts new file mode 100644 index 0000000..199e029 --- /dev/null +++ b/types/payments/p2ms.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2ms(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2pk.d.ts b/types/payments/p2pk.d.ts new file mode 100644 index 0000000..d7e824d --- /dev/null +++ b/types/payments/p2pk.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2pk(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2pkh.d.ts b/types/payments/p2pkh.d.ts new file mode 100644 index 0000000..a33eeb0 --- /dev/null +++ b/types/payments/p2pkh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2pkh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2sh.d.ts b/types/payments/p2sh.d.ts new file mode 100644 index 0000000..bb76772 --- /dev/null +++ b/types/payments/p2sh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2sh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2wpkh.d.ts b/types/payments/p2wpkh.d.ts new file mode 100644 index 0000000..3609391 --- /dev/null +++ b/types/payments/p2wpkh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2wpkh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2wsh.d.ts b/types/payments/p2wsh.d.ts new file mode 100644 index 0000000..d9ae925 --- /dev/null +++ b/types/payments/p2wsh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2wsh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/script.d.ts b/types/script.d.ts new file mode 100644 index 0000000..702e76b --- /dev/null +++ b/types/script.d.ts @@ -0,0 +1,18 @@ +/// +import * as scriptNumber from './script_number'; +import * as scriptSignature from './script_signature'; +export declare type OpCode = number; +export declare const OPS: { + [index: string]: number; +}; +export declare function isPushOnly(value: Array): boolean; +export declare function compile(chunks: Buffer | Array): Buffer; +export declare function decompile(buffer: Buffer | Array): Array | null; +export declare function toASM(chunks: Buffer | Array): string; +export declare function fromASM(asm: string): Buffer; +export declare function toStack(chunks: Buffer | Array): Array; +export declare function isCanonicalPubKey(buffer: Buffer): boolean; +export declare function isDefinedHashType(hashType: number): boolean; +export declare function isCanonicalScriptSignature(buffer: Buffer): boolean; +export declare const number: typeof scriptNumber; +export declare const signature: typeof scriptSignature; diff --git a/types/script_number.d.ts b/types/script_number.d.ts new file mode 100644 index 0000000..d0b87b1 --- /dev/null +++ b/types/script_number.d.ts @@ -0,0 +1,3 @@ +/// +export declare function decode(buffer: Buffer, maxLength?: number, minimal?: boolean): number; +export declare function encode(number: number): Buffer; diff --git a/types/script_signature.d.ts b/types/script_signature.d.ts new file mode 100644 index 0000000..2057dd9 --- /dev/null +++ b/types/script_signature.d.ts @@ -0,0 +1,8 @@ +/// +interface ScriptSignature { + signature: Buffer; + hashType: number; +} +export declare function decode(buffer: Buffer): ScriptSignature; +export declare function encode(signature: Buffer, hashType: number): Buffer; +export {}; diff --git a/types/templates/multisig/index.d.ts b/types/templates/multisig/index.d.ts new file mode 100644 index 0000000..cff90ee --- /dev/null +++ b/types/templates/multisig/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output, }; diff --git a/types/templates/multisig/input.d.ts b/types/templates/multisig/input.d.ts new file mode 100644 index 0000000..a04d03c --- /dev/null +++ b/types/templates/multisig/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/multisig/output.d.ts b/types/templates/multisig/output.d.ts new file mode 100644 index 0000000..a04d03c --- /dev/null +++ b/types/templates/multisig/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/nulldata.d.ts b/types/templates/nulldata.d.ts new file mode 100644 index 0000000..aff3cd0 --- /dev/null +++ b/types/templates/nulldata.d.ts @@ -0,0 +1,9 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} +declare const output: { + check: typeof check; +}; +export { output }; diff --git a/types/templates/pubkey/index.d.ts b/types/templates/pubkey/index.d.ts new file mode 100644 index 0000000..cff90ee --- /dev/null +++ b/types/templates/pubkey/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output, }; diff --git a/types/templates/pubkey/input.d.ts b/types/templates/pubkey/input.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/pubkey/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/pubkey/output.d.ts b/types/templates/pubkey/output.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/pubkey/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/pubkeyhash/index.d.ts b/types/templates/pubkeyhash/index.d.ts new file mode 100644 index 0000000..cff90ee --- /dev/null +++ b/types/templates/pubkeyhash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output, }; diff --git a/types/templates/pubkeyhash/input.d.ts b/types/templates/pubkeyhash/input.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/pubkeyhash/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/pubkeyhash/output.d.ts b/types/templates/pubkeyhash/output.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/pubkeyhash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/scripthash/index.d.ts b/types/templates/scripthash/index.d.ts new file mode 100644 index 0000000..cff90ee --- /dev/null +++ b/types/templates/scripthash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output, }; diff --git a/types/templates/scripthash/input.d.ts b/types/templates/scripthash/input.d.ts new file mode 100644 index 0000000..a04d03c --- /dev/null +++ b/types/templates/scripthash/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/scripthash/output.d.ts b/types/templates/scripthash/output.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/scripthash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnesscommitment/index.d.ts b/types/templates/witnesscommitment/index.d.ts new file mode 100644 index 0000000..a072ea9 --- /dev/null +++ b/types/templates/witnesscommitment/index.d.ts @@ -0,0 +1,2 @@ +import * as output from './output'; +export { output, }; diff --git a/types/templates/witnesscommitment/output.d.ts b/types/templates/witnesscommitment/output.d.ts new file mode 100644 index 0000000..778c9a1 --- /dev/null +++ b/types/templates/witnesscommitment/output.d.ts @@ -0,0 +1,7 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} +export declare function encode(commitment: Buffer): Buffer; +export declare function decode(buffer: Buffer): Buffer; diff --git a/types/templates/witnesspubkeyhash/index.d.ts b/types/templates/witnesspubkeyhash/index.d.ts new file mode 100644 index 0000000..cff90ee --- /dev/null +++ b/types/templates/witnesspubkeyhash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output, }; diff --git a/types/templates/witnesspubkeyhash/input.d.ts b/types/templates/witnesspubkeyhash/input.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/witnesspubkeyhash/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnesspubkeyhash/output.d.ts b/types/templates/witnesspubkeyhash/output.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/witnesspubkeyhash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnessscripthash/index.d.ts b/types/templates/witnessscripthash/index.d.ts new file mode 100644 index 0000000..cff90ee --- /dev/null +++ b/types/templates/witnessscripthash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output, }; diff --git a/types/templates/witnessscripthash/input.d.ts b/types/templates/witnessscripthash/input.d.ts new file mode 100644 index 0000000..7786731 --- /dev/null +++ b/types/templates/witnessscripthash/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(chunks: Array, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnessscripthash/output.d.ts b/types/templates/witnessscripthash/output.d.ts new file mode 100644 index 0000000..091758f --- /dev/null +++ b/types/templates/witnessscripthash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/transaction.d.ts b/types/transaction.d.ts new file mode 100644 index 0000000..60bbf45 --- /dev/null +++ b/types/transaction.d.ts @@ -0,0 +1,59 @@ +/// +export declare type BlankOutput = { + script: Buffer; + valueBuffer: Buffer; +}; +export declare type Output = { + script: Buffer; + value: number; +}; +export declare type Input = { + hash: Buffer; + index: number; + script: Buffer; + sequence: number; + witness: Array; +}; +export declare class Transaction { + version: number; + locktime: number; + ins: Array; + outs: Array; + static readonly DEFAULT_SEQUENCE = 4294967295; + static readonly SIGHASH_ALL = 1; + static readonly SIGHASH_NONE = 2; + static readonly SIGHASH_SINGLE = 3; + static readonly SIGHASH_ANYONECANPAY = 128; + static readonly ADVANCED_TRANSACTION_MARKER = 0; + static readonly ADVANCED_TRANSACTION_FLAG = 1; + constructor(); + static fromBuffer(buffer: Buffer, __noStrict?: boolean): Transaction; + static fromHex(hex: string): Transaction; + static isCoinbaseHash(buffer: Buffer): boolean; + isCoinbase(): boolean; + addInput(hash: Buffer, index: number, sequence?: number, scriptSig?: Buffer): number; + addOutput(scriptPubKey: Buffer, value: number): number; + hasWitnesses(): boolean; + weight(): number; + virtualSize(): number; + byteLength(): number; + private __byteLength; + clone(): Transaction; + /** + * Hash transaction for signing a specific input. + * + * Bitcoin uses a different hash for each signed transaction input. + * This method copies the transaction, makes the necessary changes based on the + * hashType, and then hashes the result. + * This hash can then be used to sign the provided transaction input. + */ + hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer; + hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer; + getHash(forWitness?: boolean): Buffer; + getId(): string; + toBuffer(buffer?: Buffer, initialOffset?: number): Buffer; + private __toBuffer; + toHex(): string; + setInputScript(index: number, scriptSig: Buffer): void; + setWitness(index: number, witness: Array): void; +} diff --git a/types/transaction_builder.d.ts b/types/transaction_builder.d.ts new file mode 100644 index 0000000..4be5968 --- /dev/null +++ b/types/transaction_builder.d.ts @@ -0,0 +1,26 @@ +/// +import { Network } from './networks'; +import { Transaction } from './transaction'; +import { ECPairInterface } from './ecpair'; +export declare class TransactionBuilder { + network: Network; + maximumFeeRate: number; + private __prevTxSet; + private __inputs; + private __tx; + constructor(network?: Network, maximumFeeRate?: number); + static fromTransaction(transaction: Transaction, network?: Network): TransactionBuilder; + setLockTime(locktime: number): void; + setVersion(version: number): void; + addInput(txHash: Buffer | string | Transaction, vout: number, sequence: number, prevOutScript: Buffer): number; + private __addInputUnsafe; + addOutput(scriptPubKey: string | Buffer, value: number): number; + build(): Transaction; + buildIncomplete(): Transaction; + private __build; + sign(vin: number, keyPair: ECPairInterface, redeemScript: Buffer, hashType: number, witnessValue: number, witnessScript: Buffer): void; + private __canModifyInputs; + private __needsOutputs; + private __canModifyOutputs; + private __overMaximumFees; +} diff --git a/types/types.d.ts b/types/types.d.ts new file mode 100644 index 0000000..242bab8 --- /dev/null +++ b/types/types.d.ts @@ -0,0 +1,25 @@ +export declare function UInt31(value: number): boolean; +export declare function BIP32Path(value: string): boolean; +export declare namespace BIP32Path { + var toJSON: () => string; +} +export declare function Satoshi(value: number): boolean; +export declare const ECPoint: any; +export declare const Network: any; +export declare const Buffer256bit: any; +export declare const Hash160bit: any; +export declare const Hash256bit: any; +export declare const Number: any; +export declare const Array: any; +export declare const Boolean: any; +export declare const String: any; +export declare const Buffer: any; +export declare const Hex: any; +export declare const maybe: any; +export declare const tuple: any; +export declare const UInt8: any; +export declare const UInt32: any; +export declare const Function: any; +export declare const BufferN: any; +export declare const Null: any; +export declare const oneOf: any;