Merge pull request #1476 from bitcoinjs/removeTxb

Tests cleanup
This commit is contained in:
d-yokoi 2019-09-12 17:53:39 +09:00 committed by GitHub
commit 41bf2cd03d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 1284 additions and 1556 deletions

View file

@ -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:

View file

@ -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)

13
package-lock.json generated
View file

@ -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": {

View file

@ -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",

View file

@ -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);
});
});
});

View file

@ -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);

View file

@ -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);
});
});

View file

@ -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,

View file

@ -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(

View file

@ -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',
);
});

View file

@ -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);

View file

@ -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\)/);
});
},
);
});

View file

@ -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,
});
},
);
});

View file

@ -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<null> {
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 => {

View file

@ -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,
};
}

View file

@ -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<any> {
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,
};
}

View file

@ -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];

View file

@ -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 || {};

View file

@ -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);

View file

@ -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);
}
});

View file

@ -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'),

View file

@ -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 +

View file

@ -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);

View file

@ -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",

1
types/address.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Network } from './networks';
export interface Base58CheckResult {
hash: Buffer;

1
types/block.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Transaction } from './transaction';
export declare class Block {
static fromBuffer(buffer: Buffer): Block;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
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;

1
types/classify.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
declare const types: {
P2MS: string;
NONSTANDARD: string;

1
types/crypto.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function ripemd160(buffer: Buffer): Buffer;
export declare function sha1(buffer: Buffer): Buffer;
export declare function sha256(buffer: Buffer): Buffer;

1
types/ecpair.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Network } from './networks';
interface ECPairOptions {
compressed?: boolean;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Network } from '../networks';
import { p2data as embed } from './embed';
import { p2ms } from './p2ms';

1
types/psbt.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Psbt as PsbtBase } from 'bip174';
import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TransactionInput } from 'bip174/src/lib/interfaces';
import { Signer, SignerAsync } from './ecpair';

1
types/script.d.ts vendored
View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from './payments';
import * as scriptNumber from './script_number';
import * as scriptSignature from './script_signature';

View file

@ -1,3 +1,2 @@
/// <reference types="node" />
export declare function decode(buffer: Buffer, maxLength?: number, minimal?: boolean): number;
export declare function encode(_number: number): Buffer;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
interface ScriptSignature {
signature: Buffer;
hashType: number;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from '../../payments';
export declare function check(script: Buffer | Stack, allowIncomplete?: boolean): boolean;
export declare namespace check {

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from '../../payments';
export declare function check(script: Buffer | Stack, allowIncomplete?: boolean): boolean;
export declare namespace check {

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from '../../payments';
export declare function check(script: Buffer | Stack): boolean;
export declare namespace check {

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from '../../payments';
export declare function check(script: Buffer | Stack): boolean;
export declare namespace check {

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from '../../payments';
export declare function check(script: Buffer | Stack): boolean;
export declare namespace check {

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>, allowIncomplete?: boolean): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Stack } from '../../payments';
export declare function check(script: Buffer | Stack): boolean;
export declare namespace check {

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(chunks: Buffer[], allowIncomplete?: boolean): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export declare function check(script: Buffer | Array<number | Buffer>): boolean;
export declare namespace check {
var toJSON: () => string;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
export interface BlankOutput {
script: Buffer;
valueBuffer: Buffer;

View file

@ -1,4 +1,3 @@
/// <reference types="node" />
import { Signer } from './ecpair';
import { Network } from './networks';
import { Transaction } from './transaction';