diff --git a/.travis.yml b/.travis.yml index bb87738..f7aad0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ matrix: env: TEST_SUITE=gitdiff:ci - node_js: "lts/*" env: TEST_SUITE=lint + - node_js: "lts/*" + env: TEST_SUITE=lint:tests - node_js: "lts/*" env: TEST_SUITE=coverage env: diff --git a/README.md b/README.md index a94d0ae..b034f20 100644 --- a/README.md +++ b/README.md @@ -85,14 +85,6 @@ The below examples are implemented as integration tests, they should be very eas Otherwise, pull requests are appreciated. Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). -### Warning: Currently the tests use TransactionBuilder, which will be removed in the future (v6.x.x or higher) -We will move towards replacing all instances of TransactionBuilder in the tests with the new Psbt. - -Currently we have a few examples on how to use the newer Psbt class at the following link: -- [Psbt examples](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions-psbt.spec.ts) - -The rest of the examples are below (using TransactionBuilder for Transaction creation) - - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) @@ -104,7 +96,6 @@ The rest of the examples are below (using TransactionBuilder for Transaction cre - [Generate a Testnet address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a Litecoin address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Create a 1-to-1 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) -- [Create a 2-to-2 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a typical Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with an OP\_RETURN output](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a 2-of-4 P2SH(multisig) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) @@ -112,7 +103,7 @@ The rest of the examples are below (using TransactionBuilder for Transaction cre - [Create (and broadcast via 3PBP) a Transaction with a SegWit P2WPKH input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a SegWit P2PK input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a SegWit 3-of-4 P2SH(P2WSH(multisig)) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) -- [Verify a Transaction signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) +- [Create (and broadcast via 3PBP) a Transaction and sign with an HDSigner interface (bip32)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Import a BIP32 testnet xpriv and export to WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts) - [Export a BIP32 xpriv, then import it](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts) - [Export a BIP32 xpub](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts) diff --git a/package-lock.json b/package-lock.json index 5812365..4c3dd29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,9 +156,9 @@ "dev": true }, "@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + "version": "12.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz", + "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==" }, "@types/proxyquire": { "version": "1.3.28", @@ -261,6 +261,13 @@ "tiny-secp256k1": "^1.1.0", "typeforce": "^1.11.5", "wif": "^2.0.6" + }, + "dependencies": { + "@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + } } }, "bip39": { diff --git a/package.json b/package.json index 700b7fd..093a097 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "gitdiff:ci": "npm run build && git diff --exit-code", "integration": "npm run build && npm run nobuild:integration", "lint": "tslint -p tsconfig.json -c tslint.json", + "lint:tests": "tslint -p test/tsconfig.json -c tslint.json", "mocha:ts": "mocha --recursive --require test/ts-node-register", "nobuild:coverage-report": "nyc report --reporter=lcov", "nobuild:coverage-html": "nyc report --reporter=html", @@ -48,7 +49,7 @@ "types" ], "dependencies": { - "@types/node": "10.12.18", + "@types/node": "12.7.5", "bech32": "^1.1.2", "bip174": "^1.0.1", "bip32": "^2.0.4", diff --git a/test/bitcoin.core.spec.ts b/test/bitcoin.core.spec.ts index 55b16ac..94b74e3 100644 --- a/test/bitcoin.core.spec.ts +++ b/test/bitcoin.core.spec.ts @@ -7,8 +7,8 @@ import * as base58KeysInvalid from './fixtures/core/base58_keys_invalid.json'; import * as base58KeysValid from './fixtures/core/base58_keys_valid.json'; import * as blocksValid from './fixtures/core/blocks.json'; import * as sigCanonical from './fixtures/core/sig_canonical.json'; -import * as sigHash from './fixtures/core/sighash.json'; import * as sigNoncanonical from './fixtures/core/sig_noncanonical.json'; +import * as sigHash from './fixtures/core/sighash.json'; import * as txValid from './fixtures/core/tx_valid.json'; describe('Bitcoin-core', () => { @@ -72,11 +72,11 @@ describe('Bitcoin-core', () => { ]; base58KeysInvalid.forEach(f => { - const string = f[0]; + const strng = f[0]; - it('throws on ' + string, () => { + it('throws on ' + strng, () => { assert.throws(() => { - const address = bitcoin.address.fromBase58Check(string); + const address = bitcoin.address.fromBase58Check(strng); assert.notStrictEqual( allowedNetworks.indexOf(address.version), @@ -121,11 +121,11 @@ describe('Bitcoin-core', () => { ]; base58KeysInvalid.forEach(f => { - const string = f[0]; + const strng = f[0]; - it('throws on ' + string, () => { + it('throws on ' + strng, () => { assert.throws(() => { - bitcoin.ECPair.fromWIF(string, allowedNetworks); + bitcoin.ECPair.fromWIF(strng, allowedNetworks); }, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/); }); }); @@ -242,9 +242,14 @@ describe('Bitcoin-core', () => { const buffer = Buffer.from(hex, 'hex'); it('throws on ' + description, () => { + const reg = new RegExp( + 'Expected DER (integer|sequence)|(R|S) value (excessively ' + + 'padded|is negative)|(R|S|DER sequence) length is (zero|too ' + + 'short|too long|invalid)|Invalid hashType', + ); assert.throws(() => { bitcoin.script.signature.decode(buffer); - }, /Expected DER (integer|sequence)|(R|S) value (excessively padded|is negative)|(R|S|DER sequence) length is (zero|too short|too long|invalid)|Invalid hashType/); + }, reg); }); }); }); diff --git a/test/block.spec.ts b/test/block.spec.ts index 6f8ed03..0f74392 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -8,7 +8,9 @@ describe('Block', () => { describe('version', () => { it('should be interpreted as an int32le', () => { const blockHex = - 'ffffffff0000000000000000000000000000000000000000000000000000000000000000414141414141414141414141414141414141414141414141414141414141414101000000020000000300000000'; + 'ffffffff000000000000000000000000000000000000000000000000000000000000' + + '00004141414141414141414141414141414141414141414141414141414141414141' + + '01000000020000000300000000'; const block = Block.fromHex(blockHex); assert.strictEqual(-1, block.version); assert.strictEqual(1, block.timestamp); diff --git a/test/bufferutils.spec.ts b/test/bufferutils.spec.ts index 4308af9..33ad8f5 100644 --- a/test/bufferutils.spec.ts +++ b/test/bufferutils.spec.ts @@ -9,9 +9,9 @@ describe('bufferutils', () => { fixtures.valid.forEach(f => { it('decodes ' + f.hex, () => { const buffer = Buffer.from(f.hex, 'hex'); - const number = bufferutils.readUInt64LE(buffer, 0); + const num = bufferutils.readUInt64LE(buffer, 0); - assert.strictEqual(number, f.dec); + assert.strictEqual(num, f.dec); }); }); diff --git a/test/classify.spec.ts b/test/classify.spec.ts index b2464c0..931250e 100644 --- a/test/classify.spec.ts +++ b/test/classify.spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; -import * as bscript from '../src/script'; import * as classify from '../src/classify'; +import * as bscript from '../src/script'; import * as fixtures from './fixtures/templates.json'; @@ -10,9 +10,9 @@ import * as nullData from '../src/templates/nulldata'; import * as pubKey from '../src/templates/pubkey'; import * as pubKeyHash from '../src/templates/pubkeyhash'; import * as scriptHash from '../src/templates/scripthash'; +import * as witnessCommitment from '../src/templates/witnesscommitment'; import * as witnessPubKeyHash from '../src/templates/witnesspubkeyhash'; import * as witnessScriptHash from '../src/templates/witnessscripthash'; -import * as witnessCommitment from '../src/templates/witnesscommitment'; const tmap = { pubKey, diff --git a/test/integration/addresses.spec.ts b/test/integration/addresses.spec.ts index 49dc578..d8feb7c 100644 --- a/test/integration/addresses.spec.ts +++ b/test/integration/addresses.spec.ts @@ -6,23 +6,27 @@ const dhttp = regtestUtils.dhttp; const TESTNET = bitcoin.networks.testnet; describe('bitcoinjs-lib (addresses)', () => { - it('can generate a random address [and support the retrieval of transactions for that address (via 3PBP)', async () => { - const keyPair = bitcoin.ECPair.makeRandom(); - const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }); + it( + 'can generate a random address [and support the retrieval of ' + + 'transactions for that address (via 3PBP)]', + async () => { + const keyPair = bitcoin.ECPair.makeRandom(); + const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }); - // bitcoin P2PKH addresses start with a '1' - assert.strictEqual(address!.startsWith('1'), true); + // bitcoin P2PKH addresses start with a '1' + assert.strictEqual(address!.startsWith('1'), true); - const result = await dhttp({ - method: 'GET', - url: 'https://blockchain.info/rawaddr/' + address, - }); + const result = await dhttp({ + method: 'GET', + url: 'https://blockchain.info/rawaddr/' + address, + }); - // random private keys [probably!] have no transactions - assert.strictEqual((result as any).n_tx, 0); - assert.strictEqual((result as any).total_received, 0); - assert.strictEqual((result as any).total_sent, 0); - }); + // random private keys [probably!] have no transactions + assert.strictEqual((result as any).n_tx, 0); + assert.strictEqual((result as any).total_received, 0); + assert.strictEqual((result as any).total_sent, 0); + }, + ); it('can import an address via WIF', () => { const keyPair = bitcoin.ECPair.fromWIF( diff --git a/test/integration/bip32.spec.ts b/test/integration/bip32.spec.ts index 1279d78..7cd9e2f 100644 --- a/test/integration/bip32.spec.ts +++ b/test/integration/bip32.spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; -import { describe, it } from 'mocha'; import * as bip32 from 'bip32'; import * as bip39 from 'bip39'; +import { describe, it } from 'mocha'; import * as bitcoin from '../..'; function getAddress(node: any, network?: any): string { @@ -25,8 +25,8 @@ describe('bitcoinjs-lib (BIP32)', () => { 'praise you muffin lion enable neck grocery crumble super myself license ghost'; const seed = bip39.mnemonicToSeedSync(mnemonic); const node = bip32.fromSeed(seed); - const string = node.toBase58(); - const restored = bip32.fromBase58(string); + const strng = node.toBase58(); + const restored = bip32.fromBase58(strng); assert.strictEqual(getAddress(node), getAddress(restored)); // same public key assert.strictEqual(node.toWIF(), restored.toWIF()); // same private key @@ -37,10 +37,10 @@ describe('bitcoinjs-lib (BIP32)', () => { 'praise you muffin lion enable neck grocery crumble super myself license ghost'; const seed = bip39.mnemonicToSeedSync(mnemonic); const node = bip32.fromSeed(seed); - const string = node.neutered().toBase58(); + const strng = node.neutered().toBase58(); assert.strictEqual( - string, + strng, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n', ); }); diff --git a/test/integration/blocks.spec.ts b/test/integration/blocks.spec.ts index 5eed0fc..a98c5eb 100644 --- a/test/integration/blocks.spec.ts +++ b/test/integration/blocks.spec.ts @@ -6,7 +6,14 @@ describe('bitcoinjs-lib (blocks)', () => { it('can extract a height from a CoinBase transaction', () => { // from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6 const txHex = - '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000'; + '010000000001010000000000000000000000000000000000000000000000000000000' + + '000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a' + + '2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98f' + + 'd16761d220400000000000000aa340000d49f0000ffffffff02b07fc3660000000019' + + '76a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266' + + 'a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12' + + 'bf1e40012000000000000000000000000000000000000000000000000000000000000' + + '0000000000000'; const tx = bitcoin.Transaction.fromHex(txHex); assert.strictEqual(tx.ins.length, 1); diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index afdcaa5..d5141c7 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -5,6 +5,14 @@ import { regtestUtils } from './_regtest'; const regtest = regtestUtils.network; const bip65 = require('bip65'); +function toOutputScript(address: string): Buffer { + return bitcoin.address.toOutputScript(address, regtest); +} + +function idToHash(txid: string): Buffer { + return Buffer.from(txid, 'hex').reverse(); +} + const alice = bitcoin.ECPair.fromWIF( 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest, @@ -13,7 +21,6 @@ const bob = bitcoin.ECPair.fromWIF( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest, ); -console.warn = () => {}; // Silence the Deprecation Warning describe('bitcoinjs-lib (transactions w/ CLTV)', () => { // force update MTP @@ -23,8 +30,14 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => { const hashType = bitcoin.Transaction.SIGHASH_ALL; - type keyPair = { publicKey: Buffer }; - function cltvCheckSigOutput(aQ: keyPair, bQ: keyPair, lockTime: number) { + interface KeyPair { + publicKey: Buffer; + } + function cltvCheckSigOutput( + aQ: KeyPair, + bQ: KeyPair, + lockTime: number, + ): Buffer { return bitcoin.script.fromASM( ` OP_IF @@ -43,177 +56,201 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => { ); } - function utcNow() { + function utcNow(): number { return Math.floor(Date.now() / 1000); } // expiry past, {Alice's signature} OP_TRUE - it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', async () => { - // 3 hours ago - const lockTime = bip65.encode({ utc: utcNow() - 3600 * 3 }); - const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); - const { address } = bitcoin.payments.p2sh({ - redeem: { output: redeemScript, network: regtest }, - network: regtest, - }); + it( + 'can create (and broadcast via 3PBP) a Transaction where Alice can redeem ' + + 'the output after the expiry (in the past)', + async () => { + // 3 hours ago + const lockTime = bip65.encode({ utc: utcNow() - 3600 * 3 }); + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); + const { address } = bitcoin.payments.p2sh({ + redeem: { output: redeemScript, network: regtest }, + network: regtest, + }); - // fund the P2SH(CLTV) address - const unspent = await regtestUtils.faucet(address!, 1e5); - const txb = new bitcoin.TransactionBuilder(regtest); - txb.setLockTime(lockTime); - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. - txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address!, 1e5); + const tx = new bitcoin.Transaction(); + tx.locktime = lockTime; + // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); - // {Alice's signature} OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature(0, redeemScript, hashType); - const redeemScriptSig = bitcoin.payments.p2sh({ - redeem: { - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE, - ]), - output: redeemScript, - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + // {Alice's signature} OP_TRUE + const signatureHash = tx.hashForSignature(0, redeemScript, hashType); + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.opcodes.OP_TRUE, + ]), + output: redeemScript, + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); - await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4, - }); - }); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4, + }); + }, + ); // expiry will pass, {Alice's signature} OP_TRUE - it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', async () => { - const height = await regtestUtils.height(); - // 5 blocks from now - const lockTime = bip65.encode({ blocks: height + 5 }); - const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); - const { address } = bitcoin.payments.p2sh({ - redeem: { output: redeemScript, network: regtest }, - network: regtest, - }); + it( + 'can create (and broadcast via 3PBP) a Transaction where Alice can redeem ' + + 'the output after the expiry (in the future)', + async () => { + const height = await regtestUtils.height(); + // 5 blocks from now + const lockTime = bip65.encode({ blocks: height + 5 }); + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); + const { address } = bitcoin.payments.p2sh({ + redeem: { output: redeemScript, network: regtest }, + network: regtest, + }); - // fund the P2SH(CLTV) address - const unspent = await regtestUtils.faucet(address!, 1e5); - const txb = new bitcoin.TransactionBuilder(regtest); - txb.setLockTime(lockTime); - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. - txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address!, 1e5); + const tx = new bitcoin.Transaction(); + tx.locktime = lockTime; + // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); - // {Alice's signature} OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature(0, redeemScript, hashType); - const redeemScriptSig = bitcoin.payments.p2sh({ - redeem: { - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE, - ]), - output: redeemScript, - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + // {Alice's signature} OP_TRUE + const signatureHash = tx.hashForSignature(0, redeemScript, hashType); + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.opcodes.OP_TRUE, + ]), + output: redeemScript, + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); - // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently - // ... - // into the future! - await regtestUtils.mine(5); - await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4, - }); - }); + // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently + // ... + // into the future! + await regtestUtils.mine(5); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4, + }); + }, + ); // expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE - it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', async () => { - // two hours ago - const lockTime = bip65.encode({ utc: utcNow() - 3600 * 2 }); - const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); - const { address } = bitcoin.payments.p2sh({ - redeem: { output: redeemScript, network: regtest }, - network: regtest, - }); + it( + 'can create (and broadcast via 3PBP) a Transaction where Alice and Bob can ' + + 'redeem the output at any time', + async () => { + // two hours ago + const lockTime = bip65.encode({ utc: utcNow() - 3600 * 2 }); + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); + const { address } = bitcoin.payments.p2sh({ + redeem: { output: redeemScript, network: regtest }, + network: regtest, + }); - // fund the P2SH(CLTV) address - const unspent = await regtestUtils.faucet(address!, 2e5); - const txb = new bitcoin.TransactionBuilder(regtest); - txb.setLockTime(lockTime); - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. - txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4); + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address!, 2e5); + const tx = new bitcoin.Transaction(); + tx.locktime = lockTime; + // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 8e4); - // {Alice's signature} {Bob's signature} OP_FALSE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature(0, redeemScript, hashType); - const redeemScriptSig = bitcoin.payments.p2sh({ - redeem: { - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.opcodes.OP_FALSE, - ]), - output: redeemScript, - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + // {Alice's signature} {Bob's signature} OP_FALSE + const signatureHash = tx.hashForSignature(0, redeemScript, hashType); + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_FALSE, + ]), + output: redeemScript, + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); - await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 8e4, - }); - }); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 8e4, + }); + }, + ); // expiry in the future, {Alice's signature} OP_TRUE - it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', async () => { - // two hours from now - const lockTime = bip65.encode({ utc: utcNow() + 3600 * 2 }); - const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); - const { address } = bitcoin.payments.p2sh({ - redeem: { output: redeemScript, network: regtest }, - network: regtest, - }); + it( + 'can create (but fail to broadcast via 3PBP) a Transaction where Alice ' + + 'attempts to redeem before the expiry', + async () => { + // two hours from now + const lockTime = bip65.encode({ utc: utcNow() + 3600 * 2 }); + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime); + const { address } = bitcoin.payments.p2sh({ + redeem: { output: redeemScript, network: regtest }, + network: regtest, + }); - // fund the P2SH(CLTV) address - const unspent = await regtestUtils.faucet(address!, 2e4); - const txb = new bitcoin.TransactionBuilder(regtest); - txb.setLockTime(lockTime); - // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. - txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address!, 2e4); + const tx = new bitcoin.Transaction(); + tx.locktime = lockTime; + // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. + tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4); - // {Alice's signature} OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature(0, redeemScript, hashType); - const redeemScriptSig = bitcoin.payments.p2sh({ - redeem: { - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE, - ]), - output: redeemScript, - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + // {Alice's signature} OP_TRUE + const signatureHash = tx.hashForSignature(0, redeemScript, hashType); + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE, + ]), + output: redeemScript, + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); - await regtestUtils.broadcast(tx.toHex()).catch(err => { - assert.throws(() => { - if (err) throw err; - }, /Error: non-final \(code 64\)/); - }); - }); + await regtestUtils.broadcast(tx.toHex()).catch(err => { + assert.throws(() => { + if (err) throw err; + }, /Error: non-final \(code 64\)/); + }); + }, + ); }); diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 0bfb970..d6f99c7 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -5,6 +5,14 @@ import { regtestUtils } from './_regtest'; const regtest = regtestUtils.network; const bip68 = require('bip68'); +function toOutputScript(address: string): Buffer { + return bitcoin.address.toOutputScript(address, regtest); +} + +function idToHash(txid: string): Buffer { + return Buffer.from(txid, 'hex').reverse(); +} + const alice = bitcoin.ECPair.fromWIF( 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest, @@ -21,7 +29,6 @@ const dave = bitcoin.ECPair.fromWIF( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest, ); -console.warn = () => {}; // Silence the Deprecation Warning describe('bitcoinjs-lib (transactions w/ CSV)', () => { // force update MTP @@ -31,9 +38,15 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => { const hashType = bitcoin.Transaction.SIGHASH_ALL; - type keyPair = { publicKey: Buffer }; + interface KeyPair { + publicKey: Buffer; + } // IF MTP (from when confirmed) > seconds, _alice can redeem - function csvCheckSigOutput(_alice: keyPair, _bob: keyPair, sequence: number) { + function csvCheckSigOutput( + _alice: KeyPair, + _bob: KeyPair, + sequence: number, + ): Buffer { return bitcoin.script.fromASM( ` OP_IF @@ -55,17 +68,20 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => { // 2 of 3 multisig of _bob, _charles, _dave, // but after sequence1 time, _alice can allow the multisig to become 1 of 3. // but after sequence2 time, _alice can sign for the output all by themself. + + /* tslint:disable-next-line */ // Ref: https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch07.asciidoc#complex-script-example + // Note: bitcoinjs-lib will not offer specific support for problems with // advanced script usages such as below. Use at your own risk. function complexCsvOutput( - _alice: keyPair, - _bob: keyPair, - _charles: keyPair, - _dave: keyPair, + _alice: KeyPair, + _bob: KeyPair, + _charles: KeyPair, + _dave: KeyPair, sequence1: number, sequence2: number, - ) { + ): Buffer { return bitcoin.script.fromASM( ` OP_IF @@ -98,287 +114,319 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => { } // expiry will pass, {Alice's signature} OP_TRUE - it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', async () => { - // 5 blocks from now - const sequence = bip68.encode({ blocks: 5 }); - const p2sh = bitcoin.payments.p2sh({ - redeem: { - output: csvCheckSigOutput(alice, bob, sequence), - }, - network: regtest, - }); - - // fund the P2SH(CSV) address - const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, sequence); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); - - // {Alice's signature} OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature( - 0, - p2sh.redeem!.output!, - hashType, - ); - const redeemScriptSig = bitcoin.payments.p2sh({ - network: regtest, - redeem: { + it( + 'can create (and broadcast via 3PBP) a Transaction where Alice can redeem ' + + 'the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', + async () => { + // 5 blocks from now + const sequence = bip68.encode({ blocks: 5 }); + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: csvCheckSigOutput(alice, bob, sequence), + }, network: regtest, - output: p2sh.redeem!.output, - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE, - ]), - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + }); - // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently - // ... - // into the future! - await regtestUtils.mine(10); + // fund the P2SH(CSV) address + const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - await regtestUtils.broadcast(tx.toHex()); + const tx = new bitcoin.Transaction(); + tx.version = 2; + tx.addInput(idToHash(unspent.txId), unspent.vout, sequence); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4, - }); - }); + // {Alice's signature} OP_TRUE + const signatureHash = tx.hashForSignature( + 0, + p2sh.redeem!.output!, + hashType, + ); + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem!.output, + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.opcodes.OP_TRUE, + ]), + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); + + // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently + // ... + // into the future! + await regtestUtils.mine(10); + + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4, + }); + }, + ); // expiry in the future, {Alice's signature} OP_TRUE - it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', async () => { - // two hours after confirmation - const sequence = bip68.encode({ seconds: 7168 }); - const p2sh = bitcoin.payments.p2sh({ - network: regtest, - redeem: { - output: csvCheckSigOutput(alice, bob, sequence), - }, - }); - - // fund the P2SH(CSV) address - const unspent = await regtestUtils.faucet(p2sh.address!, 2e4); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, sequence); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); - - // {Alice's signature} OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature( - 0, - p2sh.redeem!.output!, - hashType, - ); - const redeemScriptSig = bitcoin.payments.p2sh({ - network: regtest, - redeem: { + it( + 'can create (but fail to broadcast via 3PBP) a Transaction where Alice ' + + 'attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', + async () => { + // two hours after confirmation + const sequence = bip68.encode({ seconds: 7168 }); + const p2sh = bitcoin.payments.p2sh({ network: regtest, - output: p2sh.redeem!.output, - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE, - ]), - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + redeem: { + output: csvCheckSigOutput(alice, bob, sequence), + }, + }); - await regtestUtils.broadcast(tx.toHex()).catch(err => { - assert.throws(() => { - if (err) throw err; - }, /Error: non-BIP68-final \(code 64\)/); - }); - }); + // fund the P2SH(CSV) address + const unspent = await regtestUtils.faucet(p2sh.address!, 2e4); + + const tx = new bitcoin.Transaction(); + tx.version = 2; + tx.addInput(idToHash(unspent.txId), unspent.vout, sequence); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4); + + // {Alice's signature} OP_TRUE + const signatureHash = tx.hashForSignature( + 0, + p2sh.redeem!.output!, + hashType, + ); + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem!.output, + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE, + ]), + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); + + await regtestUtils.broadcast(tx.toHex()).catch(err => { + assert.throws(() => { + if (err) throw err; + }, /Error: non-BIP68-final \(code 64\)/); + }); + }, + ); // Check first combination of complex CSV, 2 of 3 - it('can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)', async () => { - // 2 blocks from now - const sequence1 = bip68.encode({ blocks: 2 }); - // 5 blocks from now - const sequence2 = bip68.encode({ blocks: 5 }); - const p2sh = bitcoin.payments.p2sh({ - redeem: { - output: complexCsvOutput( - alice, - bob, - charles, - dave, - sequence1, - sequence2, - ), - }, - network: regtest, - }); - - // fund the P2SH(CCSV) address - const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); - - // OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature( - 0, - p2sh.redeem!.output!, - hashType, - ); - const redeemScriptSig = bitcoin.payments.p2sh({ - network: regtest, - redeem: { - network: regtest, - output: p2sh.redeem!.output, - input: bitcoin.script.compile([ - bitcoin.opcodes.OP_0, - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.script.signature.encode( - charles.sign(signatureHash), - hashType, + it( + 'can create (and broadcast via 3PBP) a Transaction where Bob and Charles ' + + 'can send (complex CHECKSEQUENCEVERIFY)', + async () => { + // 2 blocks from now + const sequence1 = bip68.encode({ blocks: 2 }); + // 5 blocks from now + const sequence2 = bip68.encode({ blocks: 5 }); + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: complexCsvOutput( + alice, + bob, + charles, + dave, + sequence1, + sequence2, ), - bitcoin.opcodes.OP_TRUE, - bitcoin.opcodes.OP_TRUE, - ]), - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + }, + network: regtest, + }); - await regtestUtils.broadcast(tx.toHex()); + // fund the P2SH(CCSV) address + const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4, - }); - }); + const tx = new bitcoin.Transaction(); + tx.version = 2; + tx.addInput(idToHash(unspent.txId), unspent.vout); + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); + + // OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE + const signatureHash = tx.hashForSignature( + 0, + p2sh.redeem!.output!, + hashType, + ); + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem!.output, + input: bitcoin.script.compile([ + bitcoin.opcodes.OP_0, + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.script.signature.encode( + charles.sign(signatureHash), + hashType, + ), + bitcoin.opcodes.OP_TRUE, + bitcoin.opcodes.OP_TRUE, + ]), + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); + + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4, + }); + }, + ); // Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks - it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', async () => { - // 2 blocks from now - const sequence1 = bip68.encode({ blocks: 2 }); - // 5 blocks from now - const sequence2 = bip68.encode({ blocks: 5 }); - const p2sh = bitcoin.payments.p2sh({ - redeem: { - output: complexCsvOutput( - alice, - bob, - charles, - dave, - sequence1, - sequence2, - ), - }, - network: regtest, - }); - - // fund the P2SH(CCSV) address - const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, sequence1); // Set sequence1 for input - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); - - // OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature( - 0, - p2sh.redeem!.output!, - hashType, - ); - const redeemScriptSig = bitcoin.payments.p2sh({ - network: regtest, - redeem: { + it( + 'can create (and broadcast via 3PBP) a Transaction where Alice (mediator) ' + + 'and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', + async () => { + // 2 blocks from now + const sequence1 = bip68.encode({ blocks: 2 }); + // 5 blocks from now + const sequence2 = bip68.encode({ blocks: 5 }); + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: complexCsvOutput( + alice, + bob, + charles, + dave, + sequence1, + sequence2, + ), + }, network: regtest, - output: p2sh.redeem!.output, - input: bitcoin.script.compile([ - bitcoin.opcodes.OP_0, - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_0, - bitcoin.opcodes.OP_TRUE, - ]), - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + }); - // Wait 2 blocks - await regtestUtils.mine(2); + // fund the P2SH(CCSV) address + const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - await regtestUtils.broadcast(tx.toHex()); + const tx = new bitcoin.Transaction(); + tx.version = 2; + tx.addInput(idToHash(unspent.txId), unspent.vout, sequence1); // Set sequence1 for input + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4, - }); - }); + // OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE + const signatureHash = tx.hashForSignature( + 0, + p2sh.redeem!.output!, + hashType, + ); + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem!.output, + input: bitcoin.script.compile([ + bitcoin.opcodes.OP_0, + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.opcodes.OP_0, + bitcoin.opcodes.OP_TRUE, + ]), + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); + + // Wait 2 blocks + await regtestUtils.mine(2); + + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4, + }); + }, + ); // Check first combination of complex CSV, mediator after 5 blocks - it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', async () => { - // 2 blocks from now - const sequence1 = bip68.encode({ blocks: 2 }); - // 5 blocks from now - const sequence2 = bip68.encode({ blocks: 5 }); - const p2sh = bitcoin.payments.p2sh({ - redeem: { - output: complexCsvOutput( - alice, - bob, - charles, - dave, - sequence1, - sequence2, - ), - }, - network: regtest, - }); - - // fund the P2SH(CCSV) address - const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, sequence2); // Set sequence2 for input - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); - - // {Alice mediator sig} OP_FALSE - const tx = txb.buildIncomplete(); - const signatureHash = tx.hashForSignature( - 0, - p2sh.redeem!.output!, - hashType, - ); - const redeemScriptSig = bitcoin.payments.p2sh({ - network: regtest, - redeem: { + it( + 'can create (and broadcast via 3PBP) a Transaction where Alice (mediator) ' + + 'can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', + async () => { + // 2 blocks from now + const sequence1 = bip68.encode({ blocks: 2 }); + // 5 blocks from now + const sequence2 = bip68.encode({ blocks: 5 }); + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: complexCsvOutput( + alice, + bob, + charles, + dave, + sequence1, + sequence2, + ), + }, network: regtest, - output: p2sh.redeem!.output, - input: bitcoin.script.compile([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_0, - ]), - }, - }).input; - tx.setInputScript(0, redeemScriptSig!); + }); - // Wait 5 blocks - await regtestUtils.mine(5); + // fund the P2SH(CCSV) address + const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); - await regtestUtils.broadcast(tx.toHex()); + const tx = new bitcoin.Transaction(); + tx.version = 2; + tx.addInput(idToHash(unspent.txId), unspent.vout, sequence2); // Set sequence2 for input + tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4, - }); - }); + // {Alice mediator sig} OP_FALSE + const signatureHash = tx.hashForSignature( + 0, + p2sh.redeem!.output!, + hashType, + ); + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem!.output, + input: bitcoin.script.compile([ + bitcoin.script.signature.encode( + alice.sign(signatureHash), + hashType, + ), + bitcoin.opcodes.OP_0, + ]), + }, + }).input; + tx.setInputScript(0, redeemScriptSig!); + + // Wait 5 blocks + await regtestUtils.mine(5); + + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4, + }); + }, + ); }); diff --git a/test/integration/payments.spec.ts b/test/integration/payments.spec.ts index 6592c2c..58f48f5 100644 --- a/test/integration/payments.spec.ts +++ b/test/integration/payments.spec.ts @@ -6,46 +6,43 @@ const keyPairs = [ bitcoin.ECPair.makeRandom({ network: NETWORK }), bitcoin.ECPair.makeRandom({ network: NETWORK }), ]; -console.warn = () => {}; // Silence the Deprecation Warning async function buildAndSign( depends: any, prevOutput: any, redeemScript: any, witnessScript: any, -) { +): Promise { const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4); + const utx = await regtestUtils.fetch(unspent.txId); - const txb = new bitcoin.TransactionBuilder(NETWORK); - txb.addInput(unspent.txId, unspent.vout, undefined, prevOutput); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); - - const posType = depends.prevOutScriptType; - const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh'; + const psbt = new bitcoin.Psbt({ network: NETWORK }) + .addInput({ + hash: unspent.txId, + index: unspent.vout, + nonWitnessUtxo: Buffer.from(utx.txHex, 'hex'), + ...(redeemScript ? { redeemScript } : {}), + ...(witnessScript ? { witnessScript } : {}), + }) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }); if (depends.signatures) { keyPairs.forEach(keyPair => { - txb.sign({ - prevOutScriptType: posType, - vin: 0, - keyPair, - redeemScript, - witnessValue: needsValue ? unspent.value : undefined, - witnessScript, - }); + psbt.signInput(0, keyPair); }); } else if (depends.signature) { - txb.sign({ - prevOutScriptType: posType, - vin: 0, - keyPair: keyPairs[0], - redeemScript, - witnessValue: needsValue ? unspent.value : undefined, - witnessScript, - }); + psbt.signInput(0, keyPairs[0]); } - return regtestUtils.broadcast(txb.build().toHex()); + return regtestUtils.broadcast( + psbt + .finalizeAllInputs() + .extractTransaction() + .toHex(), + ); } ['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => { diff --git a/test/integration/transactions-psbt.spec.ts b/test/integration/transactions-psbt.spec.ts deleted file mode 100644 index a98407d..0000000 --- a/test/integration/transactions-psbt.spec.ts +++ /dev/null @@ -1,669 +0,0 @@ -import * as assert from 'assert'; -import { describe, it } from 'mocha'; -import * as bitcoin from '../..'; -import { regtestUtils } from './_regtest'; -import * as bip32 from 'bip32'; -const rng = require('randombytes'); -const regtest = regtestUtils.network; - -// See bottom of file for some helper functions used to make the payment objects needed. - -describe('bitcoinjs-lib (transactions with psbt)', () => { - it('can create a 1-to-1 Transaction', () => { - const alice = bitcoin.ECPair.fromWIF( - 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr', - ); - const psbt = new bitcoin.Psbt(); - psbt.setVersion(2); // These are defaults. This line is not needed. - psbt.setLocktime(0); // These are defaults. This line is not needed. - psbt.addInput({ - // if hash is string, txid, if hash is Buffer, is reversed compared to txid - hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e', - index: 0, - sequence: 0xffffffff, // These are defaults. This line is not needed. - - // non-segwit inputs now require passing the whole previous tx as Buffer - nonWitnessUtxo: Buffer.from( - '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' + - '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' + - 'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' + - '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' + - '631e5e1e66009ce3710ceea5b1ad13ffffffff01' + - // value in satoshis (Int64LE) = 0x015f90 = 90000 - '905f010000000000' + - // scriptPubkey length - '19' + - // scriptPubkey - '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' + - // locktime - '00000000', - 'hex', - ), - - // // If this input was segwit, instead of nonWitnessUtxo, you would add - // // a witnessUtxo as follows. The scriptPubkey and the value only are needed. - // witnessUtxo: { - // script: Buffer.from( - // '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac', - // 'hex', - // ), - // value: 90000, - // }, - - // Not featured here: - // redeemScript. A Buffer of the redeemScript for P2SH - // witnessScript. A Buffer of the witnessScript for P2WSH - }); - psbt.addOutput({ - address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp', - value: 80000, - }); - psbt.signInput(0, alice); - psbt.validateSignaturesOfInput(0); - psbt.finalizeAllInputs(); - assert.strictEqual( - psbt.extractTransaction().toHex(), - '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' + - 'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' + - 'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' + - '9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' + - 'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' + - '08a22724efa6f6a07b0ec4c79aa88ac00000000', - ); - }); - - it('can create (and broadcast via 3PBP) a typical Transaction', async () => { - // these are { payment: Payment; keys: ECPair[] } - const alice1 = createPayment('p2pkh'); - const alice2 = createPayment('p2pkh'); - - // give Alice 2 unspent outputs - const inputData1 = await getInputData( - 5e4, - alice1.payment, - false, - 'noredeem', - ); - const inputData2 = await getInputData( - 7e4, - alice2.payment, - false, - 'noredeem', - ); - { - const { - hash, // string of txid or Buffer of tx hash. (txid and hash are reverse order) - index, // the output index of the txo you are spending - nonWitnessUtxo, // the full previous transaction as a Buffer - } = inputData1; - assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData1); - } - - // network is only needed if you pass an address to addOutput - // using script (Buffer of scriptPubkey) instead will avoid needed network. - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData1) // alice1 unspent - .addInput(inputData2) // alice2 unspent - .addOutput({ - address: 'mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', - value: 8e4, - }) // the actual "spend" - .addOutput({ - address: alice2.payment.address, // OR script, which is a Buffer. - value: 1e4, - }); // Alice's change - // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee - - // Let's show a new feature with PSBT. - // We can have multiple signers sign in parrallel and combine them. - // (this is not necessary, but a nice feature) - - // encode to send out to the signers - const psbtBaseText = psbt.toBase64(); - - // each signer imports - const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText); - const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText); - - // Alice signs each input with the respective private keys - // signInput and signInputAsync are better - // (They take the input index explicitly as the first arg) - signer1.signAllInputs(alice1.keys[0]); - signer2.signAllInputs(alice2.keys[0]); - - // If your signer object's sign method returns a promise, use the following - // await signer2.signAllInputsAsync(alice2.keys[0]) - - // encode to send back to combiner (signer 1 and 2 are not near each other) - const s1text = signer1.toBase64(); - const s2text = signer2.toBase64(); - - const final1 = bitcoin.Psbt.fromBase64(s1text); - const final2 = bitcoin.Psbt.fromBase64(s2text); - - // final1.combine(final2) would give the exact same result - psbt.combine(final1, final2); - - // Finalizer wants to check all signatures are valid before finalizing. - // If the finalizer wants to check for specific pubkeys, the second arg - // can be passed. See the first multisig example below. - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - assert.strictEqual(psbt.validateSignaturesOfInput(1), true); - - // This step it new. Since we separate the signing operation and - // the creation of the scriptSig and witness stack, we are able to - psbt.finalizeAllInputs(); - - // build and broadcast our RegTest network - await regtestUtils.broadcast(psbt.extractTransaction().toHex()); - // to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839 - }); - - it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => { - const alice1 = createPayment('p2pkh'); - const inputData1 = await getInputData( - 2e5, - alice1.payment, - false, - 'noredeem', - ); - - const data = Buffer.from('bitcoinjs-lib', 'utf8'); - const embed = bitcoin.payments.embed({ data: [data] }); - - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData1) - .addOutput({ - script: embed.output!, - value: 1000, - }) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 1e5, - }) - .signInput(0, alice1.keys[0]); - - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - psbt.finalizeAllInputs(); - - // build and broadcast to the RegTest network - await regtestUtils.broadcast(psbt.extractTransaction().toHex()); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => { - const multisig = createPayment('p2sh-p2ms(2 of 4)'); - const inputData1 = await getInputData(2e4, multisig.payment, false, 'p2sh'); - { - const { - hash, - index, - nonWitnessUtxo, - redeemScript, // NEW: P2SH needs to give redeemScript when adding an input. - } = inputData1; - assert.deepStrictEqual( - { hash, index, nonWitnessUtxo, redeemScript }, - inputData1, - ); - } - - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData1) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 1e4, - }) - .signInput(0, multisig.keys[0]) - .signInput(0, multisig.keys[2]); - - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - assert.strictEqual( - psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey), - true, - ); - assert.throws(() => { - psbt.validateSignaturesOfInput(0, multisig.keys[3].publicKey); - }, new RegExp('No signatures for this pubkey')); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(); - - // build and broadcast to the Bitcoin RegTest network - await regtestUtils.broadcast(tx.toHex()); - - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 1e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => { - const p2sh = createPayment('p2sh-p2wpkh'); - const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh'); - const inputData2 = await getInputData(5e4, p2sh.payment, true, 'p2sh'); - { - const { - hash, - index, - witnessUtxo, // NEW: this is an object of the output being spent { script: Buffer; value: Satoshis; } - redeemScript, - } = inputData; - assert.deepStrictEqual( - { hash, index, witnessUtxo, redeemScript }, - inputData, - ); - } - const keyPair = p2sh.keys[0]; - const outputData = { - script: p2sh.payment.output, // sending to myself for fun - value: 2e4, - }; - const outputData2 = { - script: p2sh.payment.output, // sending to myself for fun - value: 7e4, - }; - - const tx = new bitcoin.Psbt() - .addInputs([inputData, inputData2]) - .addOutputs([outputData, outputData2]) - .signAllInputs(keyPair) - .finalizeAllInputs() - .extractTransaction(); - - // build and broadcast to the Bitcoin RegTest network - await regtestUtils.broadcast(tx.toHex()); - - await regtestUtils.verify({ - txId: tx.getId(), - address: p2sh.payment.address, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input with nonWitnessUtxo', async () => { - // For learning purposes, ignore this test. - // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData - const p2sh = createPayment('p2sh-p2wpkh'); - const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh'); - const inputData2 = await getInputData(5e4, p2sh.payment, false, 'p2sh'); - const keyPair = p2sh.keys[0]; - const outputData = { - script: p2sh.payment.output, - value: 2e4, - }; - const outputData2 = { - script: p2sh.payment.output, - value: 7e4, - }; - const tx = new bitcoin.Psbt() - .addInputs([inputData, inputData2]) - .addOutputs([outputData, outputData2]) - .signAllInputs(keyPair) - .finalizeAllInputs() - .extractTransaction(); - await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: p2sh.payment.address, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { - // the only thing that changes is you don't give a redeemscript for input data - - const p2wpkh = createPayment('p2wpkh'); - const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem'); - { - const { hash, index, witnessUtxo } = inputData; - assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData); - } - - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInput(0, p2wpkh.keys[0]); - - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(); - - // build and broadcast to the Bitcoin RegTest network - await regtestUtils.broadcast(tx.toHex()); - - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => { - // For learning purposes, ignore this test. - // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData - const p2wpkh = createPayment('p2wpkh'); - const inputData = await getInputData( - 5e4, - p2wpkh.payment, - false, - 'noredeem', - ); - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInput(0, p2wpkh.keys[0]); - psbt.finalizeAllInputs(); - const tx = psbt.extractTransaction(); - await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => { - const p2wsh = createPayment('p2wsh-p2pk'); - const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh'); - { - const { - hash, - index, - witnessUtxo, - witnessScript, // NEW: A Buffer of the witnessScript - } = inputData; - assert.deepStrictEqual( - { hash, index, witnessUtxo, witnessScript }, - inputData, - ); - } - - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInput(0, p2wsh.keys[0]); - - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(); - - // build and broadcast to the Bitcoin RegTest network - await regtestUtils.broadcast(tx.toHex()); - - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input with nonWitnessUtxo', async () => { - // For learning purposes, ignore this test. - // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData - const p2wsh = createPayment('p2wsh-p2pk'); - const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh'); - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInput(0, p2wsh.keys[0]); - psbt.finalizeAllInputs(); - const tx = psbt.extractTransaction(); - await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => { - const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)'); - const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh'); - { - const { - hash, - index, - witnessUtxo, - redeemScript, - witnessScript, - } = inputData; - assert.deepStrictEqual( - { hash, index, witnessUtxo, redeemScript, witnessScript }, - inputData, - ); - } - - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInput(0, p2sh.keys[0]) - .signInput(0, p2sh.keys[2]) - .signInput(0, p2sh.keys[3]); - - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - assert.strictEqual( - psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey), - true, - ); - assert.throws(() => { - psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey); - }, new RegExp('No signatures for this pubkey')); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(); - - // build and broadcast to the Bitcoin RegTest network - await regtestUtils.broadcast(tx.toHex()); - - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => { - // For learning purposes, ignore this test. - // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData - const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)'); - const inputData = await getInputData( - 5e4, - p2sh.payment, - false, - 'p2sh-p2wsh', - ); - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInput(0, p2sh.keys[0]) - .signInput(0, p2sh.keys[2]) - .signInput(0, p2sh.keys[3]); - psbt.finalizeAllInputs(); - const tx = psbt.extractTransaction(); - await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); - - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => { - const hdRoot = bip32.fromSeed(rng(64)); - const masterFingerprint = hdRoot.fingerprint; - const path = "m/84'/0'/0'/0/0"; - const childNode = hdRoot.derivePath(path); - const pubkey = childNode.publicKey; - - // This information should be added to your input via updateInput - // You can add multiple bip32Derivation objects for multisig, but - // each must have a unique pubkey. - // - // This is useful because as long as you store the masterFingerprint on - // the PSBT Creator's server, you can have the PSBT Creator do the heavy - // lifting with derivation from your m/84'/0'/0' xpub, (deriving only 0/0 ) - // and your signer just needs to pass in an HDSigner interface (ie. bip32 library) - const updateData = { - bip32Derivation: [ - { - masterFingerprint, - path, - pubkey, - }, - ], - }; - const p2wpkh = createPayment('p2wpkh', [childNode]); - const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem'); - { - const { hash, index, witnessUtxo } = inputData; - assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData); - } - - // You can add extra attributes for updateData into the addInput(s) object(s) - Object.assign(inputData, updateData); - - const psbt = new bitcoin.Psbt({ network: regtest }) - .addInput(inputData) - // .updateInput(0, updateData) // if you didn't merge the bip32Derivation with inputData - .addOutput({ - address: regtestUtils.RANDOM_ADDRESS, - value: 2e4, - }) - .signInputHD(0, hdRoot); // must sign with root!!! - - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - assert.strictEqual( - psbt.validateSignaturesOfInput(0, childNode.publicKey), - true, - ); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(); - - // build and broadcast to the Bitcoin RegTest network - await regtestUtils.broadcast(tx.toHex()); - - await regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4, - }); - }); -}); - -function createPayment(_type: string, myKeys?: any[], network?: any) { - network = network || regtest; - const splitType = _type.split('-').reverse(); - const isMultisig = splitType[0].slice(0, 4) === 'p2ms'; - const keys = myKeys || []; - let m: number | undefined; - if (isMultisig) { - const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/); - m = parseInt(match![1]); - let n = parseInt(match![2]); - if (keys.length > 0 && keys.length !== n) { - throw new Error('Need n keys for multisig'); - } - while (!myKeys && n > 1) { - keys.push(bitcoin.ECPair.makeRandom({ network })); - n--; - } - } - if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network })); - - let payment: any; - splitType.forEach(type => { - if (type.slice(0, 4) === 'p2ms') { - payment = bitcoin.payments.p2ms({ - m, - pubkeys: keys.map(key => key.publicKey).sort(), - network, - }); - } else if (['p2sh', 'p2wsh'].indexOf(type) > -1) { - payment = (bitcoin.payments as any)[type]({ - redeem: payment, - network, - }); - } else { - payment = (bitcoin.payments as any)[type]({ - pubkey: keys[0].publicKey, - network, - }); - } - }); - - return { - payment, - keys, - }; -} - -function getWitnessUtxo(out: any) { - delete out.address; - out.script = Buffer.from(out.script, 'hex'); - return out; -} - -async function getInputData( - amount: number, - payment: any, - isSegwit: boolean, - redeemType: string, -) { - const unspent = await regtestUtils.faucetComplex(payment.output, amount); - const utx = await regtestUtils.fetch(unspent.txId); - // for non segwit inputs, you must pass the full transaction buffer - const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex'); - // for segwit inputs, you only need the output script and value as an object. - const witnessUtxo = getWitnessUtxo(utx.outs[unspent.vout]); - const mixin = isSegwit ? { witnessUtxo } : { nonWitnessUtxo }; - const mixin2: any = {}; - switch (redeemType) { - case 'p2sh': - mixin2.redeemScript = payment.redeem.output; - break; - case 'p2wsh': - mixin2.witnessScript = payment.redeem.output; - break; - case 'p2sh-p2wsh': - mixin2.witnessScript = payment.redeem.redeem.output; - mixin2.redeemScript = payment.redeem.output; - break; - } - return { - hash: unspent.txId, - index: unspent.vout, - ...mixin, - ...mixin2, - }; -} diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index 8a313f2..2bcee3b 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,191 +1,231 @@ import * as assert from 'assert'; +import * as bip32 from 'bip32'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; +const rng = require('randombytes'); const regtest = regtestUtils.network; -console.warn = () => {}; // Silence the Deprecation Warning -function rng() { - return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64'); -} +// See bottom of file for some helper functions used to make the payment objects needed. -describe('bitcoinjs-lib (transactions)', () => { +describe('bitcoinjs-lib (transactions with psbt)', () => { it('can create a 1-to-1 Transaction', () => { const alice = bitcoin.ECPair.fromWIF( - 'L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy', + 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr', ); - const txb = new bitcoin.TransactionBuilder(); + const psbt = new bitcoin.Psbt(); + psbt.setVersion(2); // These are defaults. This line is not needed. + psbt.setLocktime(0); // These are defaults. This line is not needed. + psbt.addInput({ + // if hash is string, txid, if hash is Buffer, is reversed compared to txid + hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e', + index: 0, + sequence: 0xffffffff, // These are defaults. This line is not needed. - txb.setVersion(1); - txb.addInput( - '61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', - 0, - ); // Alice's previous transaction output, has 15000 satoshis - txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000); - // (in)15000 - (out)12000 = (fee)3000, this is the miner fee + // non-segwit inputs now require passing the whole previous tx as Buffer + nonWitnessUtxo: Buffer.from( + '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' + + '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' + + 'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' + + '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' + + '631e5e1e66009ce3710ceea5b1ad13ffffffff01' + + // value in satoshis (Int64LE) = 0x015f90 = 90000 + '905f010000000000' + + // scriptPubkey length + '19' + + // scriptPubkey + '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' + + // locktime + '00000000', + 'hex', + ), - txb.sign({ - prevOutScriptType: 'p2pkh', - vin: 0, - keyPair: alice, + // // If this input was segwit, instead of nonWitnessUtxo, you would add + // // a witnessUtxo as follows. The scriptPubkey and the value only are needed. + // witnessUtxo: { + // script: Buffer.from( + // '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac', + // 'hex', + // ), + // value: 90000, + // }, + + // Not featured here: + // redeemScript. A Buffer of the redeemScript for P2SH + // witnessScript. A Buffer of the witnessScript for P2WSH }); - - // prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below + psbt.addOutput({ + address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp', + value: 80000, + }); + psbt.signInput(0, alice); + psbt.validateSignaturesOfInput(0); + psbt.finalizeAllInputs(); assert.strictEqual( - txb.build().toHex(), - '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000', - ); - }); - - it('can create a 2-to-2 Transaction', () => { - const alice = bitcoin.ECPair.fromWIF( - 'L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1', - ); - const bob = bitcoin.ECPair.fromWIF( - 'KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z', - ); - - const txb = new bitcoin.TransactionBuilder(); - txb.setVersion(1); - txb.addInput( - 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', - 6, - ); // Alice's previous transaction output, has 200000 satoshis - txb.addInput( - '7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', - 0, - ); // Bob's previous transaction output, has 300000 satoshis - txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000); - txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000); - // (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee - - txb.sign({ - prevOutScriptType: 'p2pkh', - vin: 1, - keyPair: bob, - }); // Bob signs his input, which was the second input (1th) - txb.sign({ - prevOutScriptType: 'p2pkh', - vin: 0, - keyPair: alice, - }); // Alice signs her input, which was the first input (0th) - - // prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below - assert.strictEqual( - txb.build().toHex(), - '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000', + psbt.extractTransaction().toHex(), + '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' + + 'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' + + 'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' + + '9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' + + 'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' + + '08a22724efa6f6a07b0ec4c79aa88ac00000000', ); }); it('can create (and broadcast via 3PBP) a typical Transaction', async () => { - const alice1 = bitcoin.ECPair.makeRandom({ network: regtest }); - const alice2 = bitcoin.ECPair.makeRandom({ network: regtest }); - const aliceChange = bitcoin.ECPair.makeRandom({ - network: regtest, - rng: rng, - }); - - const alice1pkh = bitcoin.payments.p2pkh({ - pubkey: alice1.publicKey, - network: regtest, - }); - const alice2pkh = bitcoin.payments.p2pkh({ - pubkey: alice2.publicKey, - network: regtest, - }); - const aliceCpkh = bitcoin.payments.p2pkh({ - pubkey: aliceChange.publicKey, - network: regtest, - }); + // these are { payment: Payment; keys: ECPair[] } + const alice1 = createPayment('p2pkh'); + const alice2 = createPayment('p2pkh'); // give Alice 2 unspent outputs - const unspent0 = await regtestUtils.faucet(alice1pkh.address!, 5e4); + const inputData1 = await getInputData( + 5e4, + alice1.payment, + false, + 'noredeem', + ); + const inputData2 = await getInputData( + 7e4, + alice2.payment, + false, + 'noredeem', + ); + { + const { + hash, // string of txid or Buffer of tx hash. (txid and hash are reverse order) + index, // the output index of the txo you are spending + nonWitnessUtxo, // the full previous transaction as a Buffer + } = inputData1; + assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData1); + } - const unspent1 = await regtestUtils.faucet(alice2pkh.address!, 7e4); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent0.txId, unspent0.vout); // alice1 unspent - txb.addInput(unspent1.txId, unspent1.vout); // alice2 unspent - txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4); // the actual "spend" - txb.addOutput(aliceCpkh.address!, 1e4); // Alice's change + // network is only needed if you pass an address to addOutput + // using script (Buffer of scriptPubkey) instead will avoid needed network. + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData1) // alice1 unspent + .addInput(inputData2) // alice2 unspent + .addOutput({ + address: 'mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', + value: 8e4, + }) // the actual "spend" + .addOutput({ + address: alice2.payment.address, // OR script, which is a Buffer. + value: 1e4, + }); // Alice's change // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee + // Let's show a new feature with PSBT. + // We can have multiple signers sign in parrallel and combine them. + // (this is not necessary, but a nice feature) + + // encode to send out to the signers + const psbtBaseText = psbt.toBase64(); + + // each signer imports + const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText); + const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText); + // Alice signs each input with the respective private keys - txb.sign({ - prevOutScriptType: 'p2pkh', - vin: 0, - keyPair: alice1, - }); - txb.sign({ - prevOutScriptType: 'p2pkh', - vin: 1, - keyPair: alice2, - }); + // signInput and signInputAsync are better + // (They take the input index explicitly as the first arg) + signer1.signAllInputs(alice1.keys[0]); + signer2.signAllInputs(alice2.keys[0]); + + // If your signer object's sign method returns a promise, use the following + // await signer2.signAllInputsAsync(alice2.keys[0]) + + // encode to send back to combiner (signer 1 and 2 are not near each other) + const s1text = signer1.toBase64(); + const s2text = signer2.toBase64(); + + const final1 = bitcoin.Psbt.fromBase64(s1text); + const final2 = bitcoin.Psbt.fromBase64(s2text); + + // final1.combine(final2) would give the exact same result + psbt.combine(final1, final2); + + // Finalizer wants to check all signatures are valid before finalizing. + // If the finalizer wants to check for specific pubkeys, the second arg + // can be passed. See the first multisig example below. + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(1), true); + + // This step it new. Since we separate the signing operation and + // the creation of the scriptSig and witness stack, we are able to + psbt.finalizeAllInputs(); // build and broadcast our RegTest network - await regtestUtils.broadcast(txb.build().toHex()); + await regtestUtils.broadcast(psbt.extractTransaction().toHex()); // to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839 }); it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => { - const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); - const p2pkh = bitcoin.payments.p2pkh({ - pubkey: keyPair.publicKey, - network: regtest, - }); + const alice1 = createPayment('p2pkh'); + const inputData1 = await getInputData( + 2e5, + alice1.payment, + false, + 'noredeem', + ); - const unspent = await regtestUtils.faucet(p2pkh.address!, 2e5); - - const txb = new bitcoin.TransactionBuilder(regtest); const data = Buffer.from('bitcoinjs-lib', 'utf8'); const embed = bitcoin.payments.embed({ data: [data] }); - txb.addInput(unspent.txId, unspent.vout); - txb.addOutput(embed.output!, 1000); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5); - txb.sign({ - prevOutScriptType: 'p2pkh', - vin: 0, - keyPair, - }); + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData1) + .addOutput({ + script: embed.output!, + value: 1000, + }) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 1e5, + }) + .signInput(0, alice1.keys[0]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + psbt.finalizeAllInputs(); // build and broadcast to the RegTest network - await regtestUtils.broadcast(txb.build().toHex()); + await regtestUtils.broadcast(psbt.extractTransaction().toHex()); }); it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => { - const keyPairs = [ - bitcoin.ECPair.makeRandom({ network: regtest }), - bitcoin.ECPair.makeRandom({ network: regtest }), - bitcoin.ECPair.makeRandom({ network: regtest }), - bitcoin.ECPair.makeRandom({ network: regtest }), - ]; - const pubkeys = keyPairs.map(x => x.publicKey); - const p2ms = bitcoin.payments.p2ms({ - m: 2, - pubkeys: pubkeys, - network: regtest, - }); - const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest }); + const multisig = createPayment('p2sh-p2ms(2 of 4)'); + const inputData1 = await getInputData(2e4, multisig.payment, false, 'p2sh'); + { + const { + hash, + index, + nonWitnessUtxo, + redeemScript, // NEW: P2SH needs to give redeemScript when adding an input. + } = inputData1; + assert.deepStrictEqual( + { hash, index, nonWitnessUtxo, redeemScript }, + inputData1, + ); + } - const unspent = await regtestUtils.faucet(p2sh.address!, 2e4); + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData1) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 1e4, + }) + .signInput(0, multisig.keys[0]) + .signInput(0, multisig.keys[2]); - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual( + psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey), + true, + ); + assert.throws(() => { + psbt.validateSignaturesOfInput(0, multisig.keys[3].publicKey); + }, new RegExp('No signatures for this pubkey')); + psbt.finalizeAllInputs(); - txb.sign({ - prevOutScriptType: 'p2sh-p2ms', - vin: 0, - keyPair: keyPairs[0], - redeemScript: p2sh.redeem!.output, - }); - txb.sign({ - prevOutScriptType: 'p2sh-p2ms', - vin: 0, - keyPair: keyPairs[2], - redeemScript: p2sh.redeem!.output, - }); - const tx = txb.build(); + const tx = psbt.extractTransaction(); // build and broadcast to the Bitcoin RegTest network await regtestUtils.broadcast(tx.toHex()); @@ -199,27 +239,101 @@ describe('bitcoinjs-lib (transactions)', () => { }); it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => { - const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); - const p2wpkh = bitcoin.payments.p2wpkh({ - pubkey: keyPair.publicKey, - network: regtest, + const p2sh = createPayment('p2sh-p2wpkh'); + const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh'); + const inputData2 = await getInputData(5e4, p2sh.payment, true, 'p2sh'); + { + const { + hash, + index, + witnessUtxo, // NEW: this is an object of the output being spent { script: Buffer; value: Satoshis; } + redeemScript, + } = inputData; + assert.deepStrictEqual( + { hash, index, witnessUtxo, redeemScript }, + inputData, + ); + } + const keyPair = p2sh.keys[0]; + const outputData = { + script: p2sh.payment.output, // sending to myself for fun + value: 2e4, + }; + const outputData2 = { + script: p2sh.payment.output, // sending to myself for fun + value: 7e4, + }; + + const tx = new bitcoin.Psbt() + .addInputs([inputData, inputData2]) + .addOutputs([outputData, outputData2]) + .signAllInputs(keyPair) + .finalizeAllInputs() + .extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: p2sh.payment.address, + vout: 0, + value: 2e4, }); - const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest }); + }); - const unspent = await regtestUtils.faucet(p2sh.address!, 5e4); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); - txb.sign({ - prevOutScriptType: 'p2sh-p2wpkh', - vin: 0, - keyPair: keyPair, - redeemScript: p2sh.redeem!.output, - witnessValue: unspent.value, + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input with nonWitnessUtxo', async () => { + // For learning purposes, ignore this test. + // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData + const p2sh = createPayment('p2sh-p2wpkh'); + const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh'); + const inputData2 = await getInputData(5e4, p2sh.payment, false, 'p2sh'); + const keyPair = p2sh.keys[0]; + const outputData = { + script: p2sh.payment.output, + value: 2e4, + }; + const outputData2 = { + script: p2sh.payment.output, + value: 7e4, + }; + const tx = new bitcoin.Psbt() + .addInputs([inputData, inputData2]) + .addOutputs([outputData, outputData2]) + .signAllInputs(keyPair) + .finalizeAllInputs() + .extractTransaction(); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: p2sh.payment.address, + vout: 0, + value: 2e4, }); + }); - const tx = txb.build(); + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { + // the only thing that changes is you don't give a redeemscript for input data + + const p2wpkh = createPayment('p2wpkh'); + const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem'); + { + const { hash, index, witnessUtxo } = inputData; + assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData); + } + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2wpkh.keys[0]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); // build and broadcast to the Bitcoin RegTest network await regtestUtils.broadcast(tx.toHex()); @@ -232,30 +346,26 @@ describe('bitcoinjs-lib (transactions)', () => { }); }); - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { - const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); - const p2wpkh = bitcoin.payments.p2wpkh({ - pubkey: keyPair.publicKey, - network: regtest, - }); - - const unspent = await regtestUtils.faucetComplex(p2wpkh.output!, 5e4); - - // XXX: build the Transaction w/ a P2WPKH input - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, undefined, p2wpkh.output); // NOTE: provide the prevOutScript! - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); - txb.sign({ - prevOutScriptType: 'p2wpkh', - vin: 0, - keyPair: keyPair, - witnessValue: unspent.value, - }); // NOTE: no redeem script - const tx = txb.build(); - - // build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => { + // For learning purposes, ignore this test. + // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData + const p2wpkh = createPayment('p2wpkh'); + const inputData = await getInputData( + 5e4, + p2wpkh.payment, + false, + 'noredeem', + ); + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2wpkh.keys[0]); + psbt.finalizeAllInputs(); + const tx = psbt.extractTransaction(); await regtestUtils.broadcast(tx.toHex()); - await regtestUtils.verify({ txId: tx.getId(), address: regtestUtils.RANDOM_ADDRESS, @@ -265,29 +375,35 @@ describe('bitcoinjs-lib (transactions)', () => { }); it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => { - const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); - const p2pk = bitcoin.payments.p2pk({ - pubkey: keyPair.publicKey, - network: regtest, - }); - const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest }); + const p2wsh = createPayment('p2wsh-p2pk'); + const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh'); + { + const { + hash, + index, + witnessUtxo, + witnessScript, // NEW: A Buffer of the witnessScript + } = inputData; + assert.deepStrictEqual( + { hash, index, witnessUtxo, witnessScript }, + inputData, + ); + } - const unspent = await regtestUtils.faucetComplex(p2wsh.output!, 5e4); + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2wsh.keys[0]); - // XXX: build the Transaction w/ a P2WSH input - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, undefined, p2wsh.output); // NOTE: provide the prevOutScript! - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); - txb.sign({ - prevOutScriptType: 'p2wsh-p2pk', - vin: 0, - keyPair: keyPair, - witnessValue: 5e4, - witnessScript: p2wsh.redeem!.output, - }); // NOTE: provide a witnessScript! - const tx = txb.build(); + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + psbt.finalizeAllInputs(); - // build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network await regtestUtils.broadcast(tx.toHex()); await regtestUtils.verify({ @@ -298,50 +414,173 @@ describe('bitcoinjs-lib (transactions)', () => { }); }); - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => { - const keyPairs = [ - bitcoin.ECPair.makeRandom({ network: regtest }), - bitcoin.ECPair.makeRandom({ network: regtest }), - bitcoin.ECPair.makeRandom({ network: regtest }), - bitcoin.ECPair.makeRandom({ network: regtest }), - ]; - const pubkeys = keyPairs.map(x => x.publicKey); - - const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest }); - const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest }); - const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest }); - - const unspent = await regtestUtils.faucet(p2sh.address!, 6e4); - - const txb = new bitcoin.TransactionBuilder(regtest); - txb.addInput(unspent.txId, unspent.vout, undefined, p2sh.output); - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4); - txb.sign({ - prevOutScriptType: 'p2sh-p2wsh-p2ms', - vin: 0, - keyPair: keyPairs[0], - redeemScript: p2sh.redeem!.output, - witnessValue: unspent.value, - witnessScript: p2wsh.redeem!.output, - }); - txb.sign({ - prevOutScriptType: 'p2sh-p2wsh-p2ms', - vin: 0, - keyPair: keyPairs[2], - redeemScript: p2sh.redeem!.output, - witnessValue: unspent.value, - witnessScript: p2wsh.redeem!.output, - }); - txb.sign({ - prevOutScriptType: 'p2sh-p2wsh-p2ms', - vin: 0, - keyPair: keyPairs[3], - redeemScript: p2sh.redeem!.output, - witnessValue: unspent.value, - witnessScript: p2wsh.redeem!.output, + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input with nonWitnessUtxo', async () => { + // For learning purposes, ignore this test. + // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData + const p2wsh = createPayment('p2wsh-p2pk'); + const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh'); + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2wsh.keys[0]); + psbt.finalizeAllInputs(); + const tx = psbt.extractTransaction(); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, }); + }); - const tx = txb.build(); + it( + 'can create (and broadcast via 3PBP) a Transaction, w/ a ' + + 'P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', + async () => { + const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)'); + const inputData = await getInputData( + 5e4, + p2sh.payment, + true, + 'p2sh-p2wsh', + ); + { + const { + hash, + index, + witnessUtxo, + redeemScript, + witnessScript, + } = inputData; + assert.deepStrictEqual( + { hash, index, witnessUtxo, redeemScript, witnessScript }, + inputData, + ); + } + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2sh.keys[0]) + .signInput(0, p2sh.keys[2]) + .signInput(0, p2sh.keys[3]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual( + psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey), + true, + ); + assert.throws(() => { + psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey); + }, new RegExp('No signatures for this pubkey')); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }, + ); + + it( + 'can create (and broadcast via 3PBP) a Transaction, w/ a ' + + 'P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', + async () => { + // For learning purposes, ignore this test. + // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData + const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)'); + const inputData = await getInputData( + 5e4, + p2sh.payment, + false, + 'p2sh-p2wsh', + ); + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2sh.keys[0]) + .signInput(0, p2sh.keys[2]) + .signInput(0, p2sh.keys[3]); + psbt.finalizeAllInputs(); + const tx = psbt.extractTransaction(); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }, + ); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => { + const hdRoot = bip32.fromSeed(rng(64)); + const masterFingerprint = hdRoot.fingerprint; + const path = "m/84'/0'/0'/0/0"; + const childNode = hdRoot.derivePath(path); + const pubkey = childNode.publicKey; + + // This information should be added to your input via updateInput + // You can add multiple bip32Derivation objects for multisig, but + // each must have a unique pubkey. + // + // This is useful because as long as you store the masterFingerprint on + // the PSBT Creator's server, you can have the PSBT Creator do the heavy + // lifting with derivation from your m/84'/0'/0' xpub, (deriving only 0/0 ) + // and your signer just needs to pass in an HDSigner interface (ie. bip32 library) + const updateData = { + bip32Derivation: [ + { + masterFingerprint, + path, + pubkey, + }, + ], + }; + const p2wpkh = createPayment('p2wpkh', [childNode]); + const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem'); + { + const { hash, index, witnessUtxo } = inputData; + assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData); + } + + // You can add extra attributes for updateData into the addInput(s) object(s) + Object.assign(inputData, updateData); + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + // .updateInput(0, updateData) // if you didn't merge the bip32Derivation with inputData + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInputHD(0, hdRoot); // must sign with root!!! + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual( + psbt.validateSignaturesOfInput(0, childNode.publicKey), + true, + ); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); // build and broadcast to the Bitcoin RegTest network await regtestUtils.broadcast(tx.toHex()); @@ -350,72 +589,94 @@ describe('bitcoinjs-lib (transactions)', () => { txId: tx.getId(), address: regtestUtils.RANDOM_ADDRESS, vout: 0, - value: 3e4, - }); - }); - - it('can verify Transaction (P2PKH) signatures', () => { - const txHex = - '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'; - const keyPairs = [ - '032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d', - '0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a', - '039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f', - ].map(q => { - return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')); - }); - - const tx = bitcoin.Transaction.fromHex(txHex); - - tx.ins.forEach((input, i) => { - const keyPair = keyPairs[i]; - const p2pkh = bitcoin.payments.p2pkh({ - pubkey: keyPair.publicKey, - input: input.script, - }); - - const ss = bitcoin.script.signature.decode(p2pkh.signature!); - const hash = tx.hashForSignature(i, p2pkh.output!, ss.hashType); - - assert.strictEqual(keyPair.verify(hash, ss.signature), true); - }); - }); - - it('can verify Transaction (P2SH(P2WPKH)) signatures', () => { - const utxos = { - 'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': { - value: 50000, - }, - }; - - const txHex = - '02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'; - const tx = bitcoin.Transaction.fromHex(txHex); - - tx.ins.forEach((input, i) => { - const txId = (Buffer.from(input.hash).reverse() as Buffer).toString( - 'hex', - ); - const utxo = (utxos as any)[`${txId}:${i}`]; - if (!utxo) throw new Error('Missing utxo'); - - const p2sh = bitcoin.payments.p2sh({ - input: input.script, - witness: input.witness, - }); - const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem!); - const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }); // because P2WPKH is annoying - - const ss = bitcoin.script.signature.decode(p2wpkh.signature!); - const hash = tx.hashForWitnessV0( - i, - p2pkh.output!, - utxo.value, - ss.hashType, - ); - const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey!); // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk - - assert.strictEqual(keyPair.verify(hash, ss.signature), true); + value: 2e4, }); }); }); + +function createPayment(_type: string, myKeys?: any[], network?: any): any { + network = network || regtest; + const splitType = _type.split('-').reverse(); + const isMultisig = splitType[0].slice(0, 4) === 'p2ms'; + const keys = myKeys || []; + let m: number | undefined; + if (isMultisig) { + const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/); + m = parseInt(match![1], 10); + let n = parseInt(match![2], 10); + if (keys.length > 0 && keys.length !== n) { + throw new Error('Need n keys for multisig'); + } + while (!myKeys && n > 1) { + keys.push(bitcoin.ECPair.makeRandom({ network })); + n--; + } + } + if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network })); + + let payment: any; + splitType.forEach(type => { + if (type.slice(0, 4) === 'p2ms') { + payment = bitcoin.payments.p2ms({ + m, + pubkeys: keys.map(key => key.publicKey).sort(), + network, + }); + } else if (['p2sh', 'p2wsh'].indexOf(type) > -1) { + payment = (bitcoin.payments as any)[type]({ + redeem: payment, + network, + }); + } else { + payment = (bitcoin.payments as any)[type]({ + pubkey: keys[0].publicKey, + network, + }); + } + }); + + return { + payment, + keys, + }; +} + +function getWitnessUtxo(out: any): any { + delete out.address; + out.script = Buffer.from(out.script, 'hex'); + return out; +} + +async function getInputData( + amount: number, + payment: any, + isSegwit: boolean, + redeemType: string, +): Promise { + const unspent = await regtestUtils.faucetComplex(payment.output, amount); + const utx = await regtestUtils.fetch(unspent.txId); + // for non segwit inputs, you must pass the full transaction buffer + const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex'); + // for segwit inputs, you only need the output script and value as an object. + const witnessUtxo = getWitnessUtxo(utx.outs[unspent.vout]); + const mixin = isSegwit ? { witnessUtxo } : { nonWitnessUtxo }; + const mixin2: any = {}; + switch (redeemType) { + case 'p2sh': + mixin2.redeemScript = payment.redeem.output; + break; + case 'p2wsh': + mixin2.witnessScript = payment.redeem.output; + break; + case 'p2sh-p2wsh': + mixin2.witnessScript = payment.redeem.redeem.output; + mixin2.redeemScript = payment.redeem.output; + break; + } + return { + hash: unspent.txId, + index: unspent.vout, + ...mixin, + ...mixin2, + }; +} diff --git a/test/payments.spec.ts b/test/payments.spec.ts index 051cc04..bc123cb 100644 --- a/test/payments.spec.ts +++ b/test/payments.spec.ts @@ -1,11 +1,11 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; -import * as u from './payments.utils'; import { PaymentCreator } from '../src/payments'; +import * as u from './payments.utils'; ['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => { describe(p, () => { let fn: PaymentCreator; - let payment = require('../src/payments/' + p); + const payment = require('../src/payments/' + p); if (p === 'embed') { fn = payment.p2data; } else { @@ -83,7 +83,7 @@ import { PaymentCreator } from '../src/payments'; disabled[k] = true; }); - for (let key in depends) { + for (const key in depends) { if (key in disabled) continue; const dependencies = depends[key]; diff --git a/test/payments.utils.ts b/test/payments.utils.ts index bcf79c7..3f0ce23 100644 --- a/test/payments.utils.ts +++ b/test/payments.utils.ts @@ -1,6 +1,6 @@ import * as t from 'assert'; -import * as bscript from '../src/script'; import * as BNETWORKS from '../src/networks'; +import * as bscript from '../src/script'; function tryHex(x: Buffer | Buffer[]): string | string[] { if (Buffer.isBuffer(x)) return x.toString('hex'); @@ -17,12 +17,13 @@ function tryASM(x: Buffer): string { if (Buffer.isBuffer(x)) return bscript.toASM(x); return x; } -function asmToBuffer(x: string) { +function asmToBuffer(x: string): Buffer { if (x === '') return Buffer.alloc(0); return bscript.fromASM(x); } -function carryOver(a: any, b: any) { - for (let k in b) { +function carryOver(a: any, b: any): void { + for (const k in b) { + if (!k) continue; if (k in a && k === 'redeem') { carryOver(a[k], b[k]); continue; @@ -36,7 +37,7 @@ function carryOver(a: any, b: any) { } } -function equateBase(a: any, b: any, context: string) { +function equateBase(a: any, b: any, context: string): void { if ('output' in b) t.strictEqual( tryASM(a.output), @@ -53,7 +54,7 @@ function equateBase(a: any, b: any, context: string) { ); } -export function equate(a: any, b: any, args?: any) { +export function equate(a: any, b: any, args?: any): void { b = Object.assign({}, b); carryOver(b, args); @@ -108,7 +109,7 @@ export function equate(a: any, b: any, args?: any) { t.deepStrictEqual(tryHex(a.data), tryHex(b.data), 'Inequal *.data'); } -export function preform(x: any) { +export function preform(x: any): any { x = Object.assign({}, x); if (x.network) x.network = (BNETWORKS as any)[x.network]; @@ -148,7 +149,7 @@ export function preform(x: any) { return x; } -export function from(path: string, object: any, result?: any) { +export function from(path: string, object: any, result?: any): any { const paths = path.split('.'); result = result || {}; diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 02a420d..4eb32dc 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -14,12 +14,12 @@ const initBuffers = (object: any): typeof preFixtures => const data = result[1]; const encoding = result[2]; - return Buffer.from(data, encoding); + return Buffer.from(data, encoding as BufferEncoding); }); const fixtures = initBuffers(preFixtures); -const upperCaseFirstLetter = (str: string) => +const upperCaseFirstLetter = (str: string): string => str.replace(/^./, s => s.toUpperCase()); // const b = (hex: string) => Buffer.from(hex, 'hex'); @@ -662,7 +662,7 @@ describe(`Psbt`, () => { const psbt = Psbt.fromBuffer( Buffer.from( '70736274ff01000a01000000000000000000000000', - 'hex', //cHNidP8BAAoBAAAAAAAAAAAAAAAA + 'hex', // cHNidP8BAAoBAAAAAAAAAAAAAAAA ), ); assert.strictEqual(psbt instanceof Psbt, true); diff --git a/test/script.spec.ts b/test/script.spec.ts index 23da986..95a339d 100644 --- a/test/script.spec.ts +++ b/test/script.spec.ts @@ -12,7 +12,7 @@ describe('script', () => { assert.strictEqual(false, bscript.isCanonicalPubKey(0 as any)); }); it('rejects smaller than 33', () => { - for (var i = 0; i < 33; i++) { + for (let i = 0; i < 33; i++) { assert.strictEqual( false, bscript.isCanonicalPubKey(Buffer.allocUnsafe(i)), @@ -20,7 +20,9 @@ describe('script', () => { } }); }); - describe.skip('isCanonicalScriptSignature', () => {}); + describe.skip('isCanonicalScriptSignature', () => { + assert.ok(true); + }); describe('fromASM/toASM', () => { fixtures.valid.forEach(f => { @@ -157,19 +159,19 @@ describe('script', () => { }); }); - function testEncodingForSize(i: number) { - it('compliant for data PUSH of length ' + i, () => { - const buffer = Buffer.alloc(i); + function testEncodingForSize(num: number): void { + it('compliant for data PUSH of length ' + num, () => { + const buffer = Buffer.alloc(num); const script = bscript.compile([buffer]); assert( minimalData(script), - 'Failed for ' + i + ' length script: ' + script.toString('hex'), + 'Failed for ' + num + ' length script: ' + script.toString('hex'), ); }); } - for (var i = 0; i < 520; ++i) { + for (let i = 0; i < 520; ++i) { testEncodingForSize(i); } }); diff --git a/test/script_signature.spec.ts b/test/script_signature.spec.ts index 3be52ad..54c416e 100644 --- a/test/script_signature.spec.ts +++ b/test/script_signature.spec.ts @@ -4,14 +4,19 @@ import { signature as bscriptSig } from '../src/script'; import * as fixtures from './fixtures/signature.json'; describe('Script Signatures', () => { - function fromRaw(signature: { r: string; s: string }) { + function fromRaw(signature: { r: string; s: string }): Buffer { return Buffer.concat( [Buffer.from(signature.r, 'hex'), Buffer.from(signature.s, 'hex')], 64, ); } - function toRaw(signature: Buffer) { + function toRaw( + signature: Buffer, + ): { + r: string; + s: string; + } { return { r: signature.slice(0, 32).toString('hex'), s: signature.slice(32, 64).toString('hex'), diff --git a/test/transaction.spec.ts b/test/transaction.spec.ts index c87d9e6..6744545 100644 --- a/test/transaction.spec.ts +++ b/test/transaction.spec.ts @@ -5,7 +5,7 @@ import * as bscript from '../src/script'; import * as fixtures from './fixtures/transaction.json'; describe('Transaction', () => { - function fromRaw(raw: any, noWitness?: boolean) { + function fromRaw(raw: any, noWitness?: boolean): Transaction { const tx = new Transaction(); tx.version = raw.version; tx.locktime = raw.locktime; @@ -47,7 +47,7 @@ describe('Transaction', () => { } describe('fromBuffer/fromHex', () => { - function importExport(f: any) { + function importExport(f: any): void { const id = f.id || f.hash; const txHex = f.hex || f.txHex; @@ -218,7 +218,7 @@ describe('Transaction', () => { }); describe('getHash/getId', () => { - function verify(f: any) { + function verify(f: any): void { it('should return the id for ' + f.id + '(' + f.description + ')', () => { const tx = Transaction.fromHex(f.whex || f.hex); @@ -231,7 +231,7 @@ describe('Transaction', () => { }); describe('isCoinbase', () => { - function verify(f: any) { + function verify(f: any): void { it( 'should return ' + f.coinbase + diff --git a/test/transaction_builder.spec.ts b/test/transaction_builder.spec.ts index 0daa103..b07462e 100644 --- a/test/transaction_builder.spec.ts +++ b/test/transaction_builder.spec.ts @@ -1,16 +1,18 @@ import * as assert from 'assert'; import { beforeEach, describe, it } from 'mocha'; -import * as baddress from '../src/address'; -import * as bscript from '../src/script'; -import * as payments from '../src/payments'; import { ECPair, networks as NETWORKS, Transaction, TransactionBuilder, } from '..'; +import * as baddress from '../src/address'; +import * as payments from '../src/payments'; +import * as bscript from '../src/script'; -console.warn = () => {}; // Silence the Deprecation Warning +console.warn = (): void => { + return; +}; // Silence the Deprecation Warning import * as fixtures from './fixtures/transaction_builder.json'; @@ -128,7 +130,7 @@ for (const useOldSignArgs of [false, true]) { if (useOldSignArgs) { consoleWarn = console.warn; // Silence console.warn during these tests - console.warn = () => undefined; + console.warn = (): undefined => undefined; } describe(`TransactionBuilder: useOldSignArgs === ${useOldSignArgs}`, () => { // constants @@ -425,13 +427,13 @@ for (const useOldSignArgs of [false, true]) { describe('sign', () => { it('supports the alternative abstract interface { publicKey, sign }', () => { - const keyPair = { + const innerKeyPair = { publicKey: ECPair.makeRandom({ - rng: () => { + rng: (): Buffer => { return Buffer.alloc(32, 1); }, }).publicKey, - sign: () => { + sign: (): Buffer => { return Buffer.alloc(64, 0x5f); }, }; @@ -446,11 +448,16 @@ for (const useOldSignArgs of [false, true]) { txb.sign({ prevOutScriptType: 'p2pkh', vin: 0, - keyPair, + keyPair: innerKeyPair, }); assert.strictEqual( txb.build().toHex(), - '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000', + '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f' + + '5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f' + + '5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565' + + 'd71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914' + + '000000000000000000000000000000000000000088ac00000000', ); }); @@ -470,7 +477,12 @@ for (const useOldSignArgs of [false, true]) { // high R assert.strictEqual( txb.build().toHex(), - '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000', + '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f' + + '32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb49' + + '41f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b' + + '07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a9' + + '14000000000000000000000000000000000000000088ac00000000', ); txb = new TransactionBuilder(); @@ -489,12 +501,17 @@ for (const useOldSignArgs of [false, true]) { // low R assert.strictEqual( txb.build().toHex(), - '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000', + '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b' + + '49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee' + + '48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07' + + '029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914' + + '000000000000000000000000000000000000000088ac00000000', ); }); it('fails when missing required arguments', () => { - let txb = new TransactionBuilder(); + const txb = new TransactionBuilder(); txb.setVersion(1); txb.addInput( 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', @@ -663,7 +680,12 @@ for (const useOldSignArgs of [false, true]) { it('for incomplete with 0 signatures', () => { const randomTxData = - '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'; + '010000000001010001000000000000000000000000000000000000000000000000' + + '0000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4' + + '207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2' + + 'c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f7' + + '4d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d345102' + + '5c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'; const randomAddress = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'; const randomTx = Transaction.fromHex(randomTxData); @@ -676,7 +698,9 @@ for (const useOldSignArgs of [false, true]) { it('for incomplete P2SH with 0 signatures', () => { const inp = Buffer.from( - '010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4fee489184c462a9b1b9237488700000000', + '010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be9' + + '59391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4' + + 'fee489184c462a9b1b9237488700000000', 'hex', ); // arbitrary P2SH input const inpTx = Transaction.fromBuffer(inp); @@ -690,7 +714,9 @@ for (const useOldSignArgs of [false, true]) { it('for incomplete P2WPKH with 0 signatures', () => { const inp = Buffer.from( - '010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9f68ccc887fca2e63547d794b00000000', + '010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be9' + + '59391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9' + + 'f68ccc887fca2e63547d794b00000000', 'hex', ); const inpTx = Transaction.fromBuffer(inp); @@ -705,7 +731,9 @@ for (const useOldSignArgs of [false, true]) { it('for incomplete P2WSH with 0 signatures', () => { const inpTx = Transaction.fromBuffer( Buffer.from( - '010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b231b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', + '010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80b' + + 'e959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b2' + + '31b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', 'hex', ), ); @@ -800,12 +828,14 @@ for (const useOldSignArgs of [false, true]) { }); it('should classify witness inputs with witness = true during multisigning', () => { - const keyPair = ECPair.fromWIF( + const innerKeyPair = ECPair.fromWIF( 'cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS', network, ); const witnessScript = Buffer.from( - '522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae', + '522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e3' + + '52e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532' + + 'b9ea1952ae', 'hex', ); const redeemScript = Buffer.from( @@ -828,7 +858,7 @@ for (const useOldSignArgs of [false, true]) { txb.sign({ prevOutScriptType: 'p2sh-p2wsh-p2ms', vin: 0, - keyPair, + keyPair: innerKeyPair, redeemScript, witnessValue: 100000, witnessScript, @@ -850,10 +880,24 @@ for (const useOldSignArgs of [false, true]) { it('should handle badly pre-filled OP_0s', () => { // OP_0 is used where a signature is missing const redeemScripSig = bscript.fromASM( - 'OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae', + 'OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17' + + 'be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621eb' + + 'd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bf' + + 'cdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b44' + + '8a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778' + + 'e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f6326' + + '53266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c' + + '845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99' + + '934c2231b6cb9fd7584b8e67253ae', ); const redeemScript = bscript.fromASM( - 'OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG', + 'OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f' + + '81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d' + + '4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c70' + + '9ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe5' + + '2a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce03' + + '6f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67' + + '2 OP_3 OP_CHECKMULTISIG', ); const tx = new Transaction(); @@ -895,7 +939,17 @@ for (const useOldSignArgs of [false, true]) { ); assert.strictEqual( bscript.toASM(tx2.ins[0].script), - 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae', + 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b' + + '63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691' + + 'd6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4' + + '466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881' + + 'd7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870' + + 'b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a' + + '8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07' + + 'cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceae' + + 'ef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5' + + '229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f35' + + '66500a99934c2231b6cb9fd7584b8e67253ae', ); }); @@ -908,7 +962,7 @@ for (const useOldSignArgs of [false, true]) { ); const incomplete = txb.buildIncomplete().toHex(); - const keyPair = ECPair.fromWIF( + const innerKeyPair = ECPair.fromWIF( 'L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy', ); @@ -917,7 +971,7 @@ for (const useOldSignArgs of [false, true]) { txb.sign({ prevOutScriptType: 'p2pkh', vin: 0, - keyPair, + keyPair: innerKeyPair, }); const txId = txb.build().getId(); assert.strictEqual( @@ -933,7 +987,7 @@ for (const useOldSignArgs of [false, true]) { txb.sign({ prevOutScriptType: 'p2pkh', vin: 0, - keyPair, + keyPair: innerKeyPair, }); const txId2 = txb.build().getId(); assert.strictEqual(txId, txId2); diff --git a/tslint.json b/tslint.json index 8a9cde6..d42da60 100644 --- a/tslint.json +++ b/tslint.json @@ -21,7 +21,7 @@ "no-var-requires": false, "no-unused-expression": false, "object-literal-sort-keys": false, - "quotemark": [true, "single"], + "quotemark": [true, "single", "avoid-escape"], "typedef": [ true, "call-signature", diff --git a/types/address.d.ts b/types/address.d.ts index be0e00a..5c7ed5a 100644 --- a/types/address.d.ts +++ b/types/address.d.ts @@ -1,4 +1,3 @@ -/// import { Network } from './networks'; export interface Base58CheckResult { hash: Buffer; diff --git a/types/block.d.ts b/types/block.d.ts index 0cda4c8..d77bb1b 100644 --- a/types/block.d.ts +++ b/types/block.d.ts @@ -1,4 +1,3 @@ -/// import { Transaction } from './transaction'; export declare class Block { static fromBuffer(buffer: Buffer): Block; diff --git a/types/bufferutils.d.ts b/types/bufferutils.d.ts index 2686e4e..1eff78d 100644 --- a/types/bufferutils.d.ts +++ b/types/bufferutils.d.ts @@ -1,4 +1,3 @@ -/// 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 index d0f07b4..6ea4754 100644 --- a/types/classify.d.ts +++ b/types/classify.d.ts @@ -1,4 +1,3 @@ -/// declare const types: { P2MS: string; NONSTANDARD: string; diff --git a/types/crypto.d.ts b/types/crypto.d.ts index 1743681..5d93acd 100644 --- a/types/crypto.d.ts +++ b/types/crypto.d.ts @@ -1,4 +1,3 @@ -/// export declare function ripemd160(buffer: Buffer): Buffer; export declare function sha1(buffer: Buffer): Buffer; export declare function sha256(buffer: Buffer): Buffer; diff --git a/types/ecpair.d.ts b/types/ecpair.d.ts index 0b69dfe..447c608 100644 --- a/types/ecpair.d.ts +++ b/types/ecpair.d.ts @@ -1,4 +1,3 @@ -/// import { Network } from './networks'; interface ECPairOptions { compressed?: boolean; diff --git a/types/payments/index.d.ts b/types/payments/index.d.ts index 1edf071..922e0bf 100644 --- a/types/payments/index.d.ts +++ b/types/payments/index.d.ts @@ -1,4 +1,3 @@ -/// import { Network } from '../networks'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; diff --git a/types/psbt.d.ts b/types/psbt.d.ts index 4db075c..b1bacea 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -1,4 +1,3 @@ -/// import { Psbt as PsbtBase } from 'bip174'; import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TransactionInput } from 'bip174/src/lib/interfaces'; import { Signer, SignerAsync } from './ecpair'; diff --git a/types/script.d.ts b/types/script.d.ts index 52ad4dd..4b04615 100644 --- a/types/script.d.ts +++ b/types/script.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from './payments'; import * as scriptNumber from './script_number'; import * as scriptSignature from './script_signature'; diff --git a/types/script_number.d.ts b/types/script_number.d.ts index 015bb89..cf535fc 100644 --- a/types/script_number.d.ts +++ b/types/script_number.d.ts @@ -1,3 +1,2 @@ -/// 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 index 2057dd9..fbf18d5 100644 --- a/types/script_signature.d.ts +++ b/types/script_signature.d.ts @@ -1,4 +1,3 @@ -/// interface ScriptSignature { signature: Buffer; hashType: number; diff --git a/types/templates/multisig/input.d.ts b/types/templates/multisig/input.d.ts index a207dd6..6d46515 100644 --- a/types/templates/multisig/input.d.ts +++ b/types/templates/multisig/input.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from '../../payments'; export declare function check(script: Buffer | Stack, allowIncomplete?: boolean): boolean; export declare namespace check { diff --git a/types/templates/multisig/output.d.ts b/types/templates/multisig/output.d.ts index a207dd6..6d46515 100644 --- a/types/templates/multisig/output.d.ts +++ b/types/templates/multisig/output.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from '../../payments'; export declare function check(script: Buffer | Stack, allowIncomplete?: boolean): boolean; export declare namespace check { diff --git a/types/templates/nulldata.d.ts b/types/templates/nulldata.d.ts index aff3cd0..bf6497c 100644 --- a/types/templates/nulldata.d.ts +++ b/types/templates/nulldata.d.ts @@ -1,4 +1,3 @@ -/// export declare function check(script: Buffer | Array): boolean; export declare namespace check { var toJSON: () => string; diff --git a/types/templates/pubkey/input.d.ts b/types/templates/pubkey/input.d.ts index c4ffeab..4b70d2d 100644 --- a/types/templates/pubkey/input.d.ts +++ b/types/templates/pubkey/input.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from '../../payments'; export declare function check(script: Buffer | Stack): boolean; export declare namespace check { diff --git a/types/templates/pubkey/output.d.ts b/types/templates/pubkey/output.d.ts index c4ffeab..4b70d2d 100644 --- a/types/templates/pubkey/output.d.ts +++ b/types/templates/pubkey/output.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from '../../payments'; export declare function check(script: Buffer | Stack): boolean; export declare namespace check { diff --git a/types/templates/pubkeyhash/input.d.ts b/types/templates/pubkeyhash/input.d.ts index c4ffeab..4b70d2d 100644 --- a/types/templates/pubkeyhash/input.d.ts +++ b/types/templates/pubkeyhash/input.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from '../../payments'; export declare function check(script: Buffer | Stack): boolean; export declare namespace check { diff --git a/types/templates/pubkeyhash/output.d.ts b/types/templates/pubkeyhash/output.d.ts index 091758f..d66d8ac 100644 --- a/types/templates/pubkeyhash/output.d.ts +++ b/types/templates/pubkeyhash/output.d.ts @@ -1,4 +1,3 @@ -/// export declare function check(script: Buffer | Array): boolean; export declare namespace check { var toJSON: () => string; diff --git a/types/templates/scripthash/input.d.ts b/types/templates/scripthash/input.d.ts index a04d03c..14bbd5b 100644 --- a/types/templates/scripthash/input.d.ts +++ b/types/templates/scripthash/input.d.ts @@ -1,4 +1,3 @@ -/// 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 index 091758f..d66d8ac 100644 --- a/types/templates/scripthash/output.d.ts +++ b/types/templates/scripthash/output.d.ts @@ -1,4 +1,3 @@ -/// export declare function check(script: Buffer | Array): boolean; export declare namespace check { var toJSON: () => string; diff --git a/types/templates/witnesscommitment/output.d.ts b/types/templates/witnesscommitment/output.d.ts index 778c9a1..ec155a2 100644 --- a/types/templates/witnesscommitment/output.d.ts +++ b/types/templates/witnesscommitment/output.d.ts @@ -1,4 +1,3 @@ -/// export declare function check(script: Buffer | Array): boolean; export declare namespace check { var toJSON: () => string; diff --git a/types/templates/witnesspubkeyhash/input.d.ts b/types/templates/witnesspubkeyhash/input.d.ts index c4ffeab..4b70d2d 100644 --- a/types/templates/witnesspubkeyhash/input.d.ts +++ b/types/templates/witnesspubkeyhash/input.d.ts @@ -1,4 +1,3 @@ -/// import { Stack } from '../../payments'; export declare function check(script: Buffer | Stack): boolean; export declare namespace check { diff --git a/types/templates/witnesspubkeyhash/output.d.ts b/types/templates/witnesspubkeyhash/output.d.ts index 091758f..d66d8ac 100644 --- a/types/templates/witnesspubkeyhash/output.d.ts +++ b/types/templates/witnesspubkeyhash/output.d.ts @@ -1,4 +1,3 @@ -/// export declare function check(script: Buffer | Array): boolean; export declare namespace check { var toJSON: () => string; diff --git a/types/templates/witnessscripthash/input.d.ts b/types/templates/witnessscripthash/input.d.ts index b2a6e8a..767df3a 100644 --- a/types/templates/witnessscripthash/input.d.ts +++ b/types/templates/witnessscripthash/input.d.ts @@ -1,4 +1,3 @@ -/// export declare function check(chunks: Buffer[], 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 index 091758f..d66d8ac 100644 --- a/types/templates/witnessscripthash/output.d.ts +++ b/types/templates/witnessscripthash/output.d.ts @@ -1,4 +1,3 @@ -/// 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 index 9bdba19..d7462b4 100644 --- a/types/transaction.d.ts +++ b/types/transaction.d.ts @@ -1,4 +1,3 @@ -/// export interface BlankOutput { script: Buffer; valueBuffer: Buffer; diff --git a/types/transaction_builder.d.ts b/types/transaction_builder.d.ts index 2799464..a80fc0f 100644 --- a/types/transaction_builder.d.ts +++ b/types/transaction_builder.d.ts @@ -1,4 +1,3 @@ -/// import { Signer } from './ecpair'; import { Network } from './networks'; import { Transaction } from './transaction';