Remove TransactionBuilder from tests (besides transaction_builder.spec.ts)
This commit is contained in:
parent
603b0eb544
commit
f376913a4c
6 changed files with 623 additions and 1046 deletions
11
README.md
11
README.md
|
@ -85,14 +85,6 @@ The below examples are implemented as integration tests, they should be very eas
|
||||||
Otherwise, pull requests are appreciated.
|
Otherwise, pull requests are appreciated.
|
||||||
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
|
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)
|
- [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)
|
- [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)
|
- [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 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)
|
- [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 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 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 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)
|
- [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 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 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)
|
- [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)
|
- [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 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)
|
- [Export a BIP32 xpub](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts)
|
||||||
|
|
|
@ -5,6 +5,14 @@ import { regtestUtils } from './_regtest';
|
||||||
const regtest = regtestUtils.network;
|
const regtest = regtestUtils.network;
|
||||||
const bip65 = require('bip65');
|
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() as Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
const alice = bitcoin.ECPair.fromWIF(
|
const alice = bitcoin.ECPair.fromWIF(
|
||||||
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
|
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
|
||||||
regtest,
|
regtest,
|
||||||
|
@ -13,7 +21,6 @@ const bob = bitcoin.ECPair.fromWIF(
|
||||||
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
|
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
|
||||||
regtest,
|
regtest,
|
||||||
);
|
);
|
||||||
console.warn = () => {}; // Silence the Deprecation Warning
|
|
||||||
|
|
||||||
describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||||
// force update MTP
|
// force update MTP
|
||||||
|
@ -59,14 +66,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
const unspent = await regtestUtils.faucet(address!, 1e5);
|
const unspent = await regtestUtils.faucet(address!, 1e5);
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.setLockTime(lockTime);
|
tx.locktime = lockTime;
|
||||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// {Alice's signature} OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
|
@ -102,14 +108,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
const unspent = await regtestUtils.faucet(address!, 1e5);
|
const unspent = await regtestUtils.faucet(address!, 1e5);
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.setLockTime(lockTime);
|
tx.locktime = lockTime;
|
||||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// {Alice's signature} OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
|
@ -147,14 +152,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
const unspent = await regtestUtils.faucet(address!, 2e5);
|
const unspent = await regtestUtils.faucet(address!, 2e5);
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.setLockTime(lockTime);
|
tx.locktime = lockTime;
|
||||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4);
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 8e4);
|
||||||
|
|
||||||
// {Alice's signature} {Bob's signature} OP_FALSE
|
// {Alice's signature} {Bob's signature} OP_FALSE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
|
@ -189,14 +193,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
const unspent = await regtestUtils.faucet(address!, 2e4);
|
const unspent = await regtestUtils.faucet(address!, 2e4);
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.setLockTime(lockTime);
|
tx.locktime = lockTime;
|
||||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4);
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// {Alice's signature} OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
|
|
|
@ -5,6 +5,14 @@ import { regtestUtils } from './_regtest';
|
||||||
const regtest = regtestUtils.network;
|
const regtest = regtestUtils.network;
|
||||||
const bip68 = require('bip68');
|
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() as Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
const alice = bitcoin.ECPair.fromWIF(
|
const alice = bitcoin.ECPair.fromWIF(
|
||||||
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
|
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
|
||||||
regtest,
|
regtest,
|
||||||
|
@ -21,7 +29,6 @@ const dave = bitcoin.ECPair.fromWIF(
|
||||||
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx',
|
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx',
|
||||||
regtest,
|
regtest,
|
||||||
);
|
);
|
||||||
console.warn = () => {}; // Silence the Deprecation Warning
|
|
||||||
|
|
||||||
describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// force update MTP
|
// force update MTP
|
||||||
|
@ -111,12 +118,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// fund the P2SH(CSV) address
|
// fund the P2SH(CSV) address
|
||||||
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence);
|
tx.version = 2;
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, sequence);
|
||||||
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// {Alice's signature} OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(
|
const signatureHash = tx.hashForSignature(
|
||||||
0,
|
0,
|
||||||
p2sh.redeem!.output!,
|
p2sh.redeem!.output!,
|
||||||
|
@ -164,12 +171,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// fund the P2SH(CSV) address
|
// fund the P2SH(CSV) address
|
||||||
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4);
|
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4);
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence);
|
tx.version = 2;
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, sequence);
|
||||||
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4);
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// {Alice's signature} OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(
|
const signatureHash = tx.hashForSignature(
|
||||||
0,
|
0,
|
||||||
p2sh.redeem!.output!,
|
p2sh.redeem!.output!,
|
||||||
|
@ -219,12 +226,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// fund the P2SH(CCSV) address
|
// fund the P2SH(CCSV) address
|
||||||
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.addInput(unspent.txId, unspent.vout);
|
tx.version = 2;
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
tx.addInput(idToHash(unspent.txId), unspent.vout);
|
||||||
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
|
||||||
|
|
||||||
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
|
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(
|
const signatureHash = tx.hashForSignature(
|
||||||
0,
|
0,
|
||||||
p2sh.redeem!.output!,
|
p2sh.redeem!.output!,
|
||||||
|
@ -282,12 +289,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// fund the P2SH(CCSV) address
|
// fund the P2SH(CCSV) address
|
||||||
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence1); // Set sequence1 for input
|
tx.version = 2;
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, sequence1); // Set sequence1 for input
|
||||||
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
|
||||||
|
|
||||||
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
|
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(
|
const signatureHash = tx.hashForSignature(
|
||||||
0,
|
0,
|
||||||
p2sh.redeem!.output!,
|
p2sh.redeem!.output!,
|
||||||
|
@ -345,12 +352,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// fund the P2SH(CCSV) address
|
// fund the P2SH(CCSV) address
|
||||||
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = new bitcoin.Transaction();
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence2); // Set sequence2 for input
|
tx.version = 2;
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
tx.addInput(idToHash(unspent.txId), unspent.vout, sequence2); // Set sequence2 for input
|
||||||
|
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
|
||||||
|
|
||||||
// {Alice mediator sig} OP_FALSE
|
// {Alice mediator sig} OP_FALSE
|
||||||
const tx = txb.buildIncomplete();
|
|
||||||
const signatureHash = tx.hashForSignature(
|
const signatureHash = tx.hashForSignature(
|
||||||
0,
|
0,
|
||||||
p2sh.redeem!.output!,
|
p2sh.redeem!.output!,
|
||||||
|
|
|
@ -6,7 +6,6 @@ const keyPairs = [
|
||||||
bitcoin.ECPair.makeRandom({ network: NETWORK }),
|
bitcoin.ECPair.makeRandom({ network: NETWORK }),
|
||||||
bitcoin.ECPair.makeRandom({ network: NETWORK }),
|
bitcoin.ECPair.makeRandom({ network: NETWORK }),
|
||||||
];
|
];
|
||||||
console.warn = () => {}; // Silence the Deprecation Warning
|
|
||||||
|
|
||||||
async function buildAndSign(
|
async function buildAndSign(
|
||||||
depends: any,
|
depends: any,
|
||||||
|
@ -15,37 +14,35 @@ async function buildAndSign(
|
||||||
witnessScript: any,
|
witnessScript: any,
|
||||||
) {
|
) {
|
||||||
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4);
|
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4);
|
||||||
|
const utx = await regtestUtils.fetch(unspent.txId);
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(NETWORK);
|
const psbt = new bitcoin.Psbt({ network: NETWORK })
|
||||||
txb.addInput(unspent.txId, unspent.vout, undefined, prevOutput);
|
.addInput({
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
|
hash: unspent.txId,
|
||||||
|
index: unspent.vout,
|
||||||
const posType = depends.prevOutScriptType;
|
nonWitnessUtxo: Buffer.from(utx.txHex, 'hex'),
|
||||||
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh';
|
...(redeemScript ? { redeemScript } : {}),
|
||||||
|
...(witnessScript ? { witnessScript } : {}),
|
||||||
|
})
|
||||||
|
.addOutput({
|
||||||
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
|
value: 2e4,
|
||||||
|
});
|
||||||
|
|
||||||
if (depends.signatures) {
|
if (depends.signatures) {
|
||||||
keyPairs.forEach(keyPair => {
|
keyPairs.forEach(keyPair => {
|
||||||
txb.sign({
|
psbt.signInput(0, keyPair);
|
||||||
prevOutScriptType: posType,
|
|
||||||
vin: 0,
|
|
||||||
keyPair,
|
|
||||||
redeemScript,
|
|
||||||
witnessValue: needsValue ? unspent.value : undefined,
|
|
||||||
witnessScript,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else if (depends.signature) {
|
} else if (depends.signature) {
|
||||||
txb.sign({
|
psbt.signInput(0, keyPairs[0]);
|
||||||
prevOutScriptType: posType,
|
|
||||||
vin: 0,
|
|
||||||
keyPair: keyPairs[0],
|
|
||||||
redeemScript,
|
|
||||||
witnessValue: needsValue ? unspent.value : undefined,
|
|
||||||
witnessScript,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return regtestUtils.broadcast(txb.build().toHex());
|
return regtestUtils.broadcast(
|
||||||
|
psbt
|
||||||
|
.finalizeAllInputs()
|
||||||
|
.extractTransaction()
|
||||||
|
.toHex(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
|
['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -2,190 +2,230 @@ import * as assert from 'assert';
|
||||||
import { describe, it } from 'mocha';
|
import { describe, it } from 'mocha';
|
||||||
import * as bitcoin from '../..';
|
import * as bitcoin from '../..';
|
||||||
import { regtestUtils } from './_regtest';
|
import { regtestUtils } from './_regtest';
|
||||||
|
import * as bip32 from 'bip32';
|
||||||
|
const rng = require('randombytes');
|
||||||
const regtest = regtestUtils.network;
|
const regtest = regtestUtils.network;
|
||||||
console.warn = () => {}; // Silence the Deprecation Warning
|
|
||||||
|
|
||||||
function rng() {
|
// See bottom of file for some helper functions used to make the payment objects needed.
|
||||||
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('bitcoinjs-lib (transactions)', () => {
|
describe('bitcoinjs-lib (transactions with psbt)', () => {
|
||||||
it('can create a 1-to-1 Transaction', () => {
|
it('can create a 1-to-1 Transaction', () => {
|
||||||
const alice = bitcoin.ECPair.fromWIF(
|
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);
|
// non-segwit inputs now require passing the whole previous tx as Buffer
|
||||||
txb.addInput(
|
nonWitnessUtxo: Buffer.from(
|
||||||
'61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d',
|
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
|
||||||
0,
|
'452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
|
||||||
); // Alice's previous transaction output, has 15000 satoshis
|
'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
|
||||||
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000);
|
'9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
|
||||||
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
|
'631e5e1e66009ce3710ceea5b1ad13ffffffff01' +
|
||||||
|
// value in satoshis (Int64LE) = 0x015f90 = 90000
|
||||||
|
'905f010000000000' +
|
||||||
|
// scriptPubkey length
|
||||||
|
'19' +
|
||||||
|
// scriptPubkey
|
||||||
|
'76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' +
|
||||||
|
// locktime
|
||||||
|
'00000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
|
||||||
txb.sign({
|
// // If this input was segwit, instead of nonWitnessUtxo, you would add
|
||||||
prevOutScriptType: 'p2pkh',
|
// // a witnessUtxo as follows. The scriptPubkey and the value only are needed.
|
||||||
vin: 0,
|
// witnessUtxo: {
|
||||||
keyPair: alice,
|
// 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({
|
||||||
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
|
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
||||||
assert.strictEqual(
|
value: 80000,
|
||||||
txb.build().toHex(),
|
|
||||||
'01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
psbt.signInput(0, alice);
|
||||||
it('can create a 2-to-2 Transaction', () => {
|
psbt.validateSignaturesOfInput(0);
|
||||||
const alice = bitcoin.ECPair.fromWIF(
|
psbt.finalizeAllInputs();
|
||||||
'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(
|
assert.strictEqual(
|
||||||
txb.build().toHex(),
|
psbt.extractTransaction().toHex(),
|
||||||
'01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000',
|
'02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
|
||||||
|
'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' +
|
||||||
|
'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' +
|
||||||
|
'9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' +
|
||||||
|
'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
|
||||||
|
'08a22724efa6f6a07b0ec4c79aa88ac00000000',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
|
it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
|
||||||
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest });
|
// these are { payment: Payment; keys: ECPair[] }
|
||||||
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest });
|
const alice1 = createPayment('p2pkh');
|
||||||
const aliceChange = bitcoin.ECPair.makeRandom({
|
const alice2 = createPayment('p2pkh');
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
// give Alice 2 unspent outputs
|
// 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);
|
// network is only needed if you pass an address to addOutput
|
||||||
|
// using script (Buffer of scriptPubkey) instead will avoid needed network.
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||||
txb.addInput(unspent0.txId, unspent0.vout); // alice1 unspent
|
.addInput(inputData1) // alice1 unspent
|
||||||
txb.addInput(unspent1.txId, unspent1.vout); // alice2 unspent
|
.addInput(inputData2) // alice2 unspent
|
||||||
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4); // the actual "spend"
|
.addOutput({
|
||||||
txb.addOutput(aliceCpkh.address!, 1e4); // Alice's change
|
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
|
// (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
|
// Alice signs each input with the respective private keys
|
||||||
txb.sign({
|
// signInput and signInputAsync are better
|
||||||
prevOutScriptType: 'p2pkh',
|
// (They take the input index explicitly as the first arg)
|
||||||
vin: 0,
|
signer1.signAllInputs(alice1.keys[0]);
|
||||||
keyPair: alice1,
|
signer2.signAllInputs(alice2.keys[0]);
|
||||||
});
|
|
||||||
txb.sign({
|
// If your signer object's sign method returns a promise, use the following
|
||||||
prevOutScriptType: 'p2pkh',
|
// await signer2.signAllInputsAsync(alice2.keys[0])
|
||||||
vin: 1,
|
|
||||||
keyPair: alice2,
|
// 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
|
// 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
|
// 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 () => {
|
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
const alice1 = createPayment('p2pkh');
|
||||||
const p2pkh = bitcoin.payments.p2pkh({
|
const inputData1 = await getInputData(
|
||||||
pubkey: keyPair.publicKey,
|
2e5,
|
||||||
network: regtest,
|
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 data = Buffer.from('bitcoinjs-lib', 'utf8');
|
||||||
const embed = bitcoin.payments.embed({ data: [data] });
|
const embed = bitcoin.payments.embed({ data: [data] });
|
||||||
txb.addInput(unspent.txId, unspent.vout);
|
|
||||||
txb.addOutput(embed.output!, 1000);
|
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5);
|
.addInput(inputData1)
|
||||||
txb.sign({
|
.addOutput({
|
||||||
prevOutScriptType: 'p2pkh',
|
script: embed.output!,
|
||||||
vin: 0,
|
value: 1000,
|
||||||
keyPair,
|
})
|
||||||
});
|
.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
|
// 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 () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => {
|
||||||
const keyPairs = [
|
const multisig = createPayment('p2sh-p2ms(2 of 4)');
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
const inputData1 = await getInputData(2e4, multisig.payment, false, 'p2sh');
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
{
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
const {
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
hash,
|
||||||
];
|
index,
|
||||||
const pubkeys = keyPairs.map(x => x.publicKey);
|
nonWitnessUtxo,
|
||||||
const p2ms = bitcoin.payments.p2ms({
|
redeemScript, // NEW: P2SH needs to give redeemScript when adding an input.
|
||||||
m: 2,
|
} = inputData1;
|
||||||
pubkeys: pubkeys,
|
assert.deepStrictEqual(
|
||||||
network: regtest,
|
{ hash, index, nonWitnessUtxo, redeemScript },
|
||||||
});
|
inputData1,
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest });
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
|
||||||
txb.addInput(unspent.txId, unspent.vout);
|
assert.strictEqual(
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
|
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({
|
const tx = psbt.extractTransaction();
|
||||||
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();
|
|
||||||
|
|
||||||
// build and broadcast to the Bitcoin RegTest network
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
await regtestUtils.broadcast(tx.toHex());
|
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 () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
const p2sh = createPayment('p2sh-p2wpkh');
|
||||||
const p2wpkh = bitcoin.payments.p2wpkh({
|
const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh');
|
||||||
pubkey: keyPair.publicKey,
|
const inputData2 = await getInputData(5e4, p2sh.payment, true, 'p2sh');
|
||||||
network: regtest,
|
{
|
||||||
|
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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tx = txb.build();
|
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
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
await regtestUtils.broadcast(tx.toHex());
|
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 () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
// For learning purposes, ignore this test.
|
||||||
const p2wpkh = bitcoin.payments.p2wpkh({
|
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
|
||||||
pubkey: keyPair.publicKey,
|
const p2wpkh = createPayment('p2wpkh');
|
||||||
network: regtest,
|
const inputData = await getInputData(
|
||||||
});
|
5e4,
|
||||||
|
p2wpkh.payment,
|
||||||
const unspent = await regtestUtils.faucetComplex(p2wpkh.output!, 5e4);
|
false,
|
||||||
|
'noredeem',
|
||||||
// XXX: build the Transaction w/ a P2WPKH input
|
);
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||||
txb.addInput(unspent.txId, unspent.vout, undefined, p2wpkh.output); // NOTE: provide the prevOutScript!
|
.addInput(inputData)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
|
.addOutput({
|
||||||
txb.sign({
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
prevOutScriptType: 'p2wpkh',
|
value: 2e4,
|
||||||
vin: 0,
|
})
|
||||||
keyPair: keyPair,
|
.signInput(0, p2wpkh.keys[0]);
|
||||||
witnessValue: unspent.value,
|
psbt.finalizeAllInputs();
|
||||||
}); // NOTE: no redeem script
|
const tx = psbt.extractTransaction();
|
||||||
const tx = txb.build();
|
|
||||||
|
|
||||||
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
|
|
||||||
await regtestUtils.broadcast(tx.toHex());
|
await regtestUtils.broadcast(tx.toHex());
|
||||||
|
|
||||||
await regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
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 () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
const p2wsh = createPayment('p2wsh-p2pk');
|
||||||
const p2pk = bitcoin.payments.p2pk({
|
const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh');
|
||||||
pubkey: keyPair.publicKey,
|
{
|
||||||
network: regtest,
|
const {
|
||||||
});
|
hash,
|
||||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest });
|
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
|
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
psbt.finalizeAllInputs();
|
||||||
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();
|
|
||||||
|
|
||||||
// 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.broadcast(tx.toHex());
|
||||||
|
|
||||||
await regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
|
@ -298,50 +414,67 @@ describe('bitcoinjs-lib (transactions)', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
|
||||||
const keyPairs = [
|
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh');
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
{
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
const {
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
hash,
|
||||||
];
|
index,
|
||||||
const pubkeys = keyPairs.map(x => x.publicKey);
|
witnessUtxo,
|
||||||
|
redeemScript,
|
||||||
|
witnessScript,
|
||||||
|
} = inputData;
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
{ hash, index, witnessUtxo, redeemScript, witnessScript },
|
||||||
|
inputData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest });
|
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest });
|
.addInput(inputData)
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest });
|
.addOutput({
|
||||||
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
|
value: 2e4,
|
||||||
|
})
|
||||||
|
.signInput(0, p2sh.keys[0])
|
||||||
|
.signInput(0, p2sh.keys[2])
|
||||||
|
.signInput(0, p2sh.keys[3]);
|
||||||
|
|
||||||
const unspent = await regtestUtils.faucet(p2sh.address!, 6e4);
|
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 txb = new bitcoin.TransactionBuilder(regtest);
|
const tx = psbt.extractTransaction();
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tx = txb.build();
|
|
||||||
|
|
||||||
// build and broadcast to the Bitcoin RegTest network
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
await regtestUtils.broadcast(tx.toHex());
|
await regtestUtils.broadcast(tx.toHex());
|
||||||
|
@ -350,72 +483,187 @@ describe('bitcoinjs-lib (transactions)', () => {
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 3e4,
|
value: 2e4,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can verify Transaction (P2PKH) signatures', () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => {
|
||||||
const txHex =
|
// For learning purposes, ignore this test.
|
||||||
'010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700';
|
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
|
||||||
const keyPairs = [
|
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
|
||||||
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d',
|
const inputData = await getInputData(
|
||||||
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a',
|
5e4,
|
||||||
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f',
|
p2sh.payment,
|
||||||
].map(q => {
|
false,
|
||||||
return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex'));
|
'p2sh-p2wsh',
|
||||||
});
|
);
|
||||||
|
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||||
const tx = bitcoin.Transaction.fromHex(txHex);
|
.addInput(inputData)
|
||||||
|
.addOutput({
|
||||||
tx.ins.forEach((input, i) => {
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
const keyPair = keyPairs[i];
|
value: 2e4,
|
||||||
const p2pkh = bitcoin.payments.p2pkh({
|
})
|
||||||
pubkey: keyPair.publicKey,
|
.signInput(0, p2sh.keys[0])
|
||||||
input: input.script,
|
.signInput(0, p2sh.keys[2])
|
||||||
});
|
.signInput(0, p2sh.keys[3]);
|
||||||
|
psbt.finalizeAllInputs();
|
||||||
const ss = bitcoin.script.signature.decode(p2pkh.signature!);
|
const tx = psbt.extractTransaction();
|
||||||
const hash = tx.hashForSignature(i, p2pkh.output!, ss.hashType);
|
await regtestUtils.broadcast(tx.toHex());
|
||||||
|
await regtestUtils.verify({
|
||||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true);
|
txId: tx.getId(),
|
||||||
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
|
vout: 0,
|
||||||
|
value: 2e4,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can verify Transaction (P2SH(P2WPKH)) signatures', () => {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => {
|
||||||
const utxos = {
|
const hdRoot = bip32.fromSeed(rng(64));
|
||||||
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': {
|
const masterFingerprint = hdRoot.fingerprint;
|
||||||
value: 50000,
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
const txHex =
|
// You can add extra attributes for updateData into the addInput(s) object(s)
|
||||||
'02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000';
|
Object.assign(inputData, updateData);
|
||||||
const tx = bitcoin.Transaction.fromHex(txHex);
|
|
||||||
|
|
||||||
tx.ins.forEach((input, i) => {
|
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||||
const txId = (Buffer.from(input.hash).reverse() as Buffer).toString(
|
.addInput(inputData)
|
||||||
'hex',
|
// .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,
|
||||||
);
|
);
|
||||||
const utxo = (utxos as any)[`${txId}:${i}`];
|
psbt.finalizeAllInputs();
|
||||||
if (!utxo) throw new Error('Missing utxo');
|
|
||||||
|
|
||||||
const p2sh = bitcoin.payments.p2sh({
|
const tx = psbt.extractTransaction();
|
||||||
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!);
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
const hash = tx.hashForWitnessV0(
|
await regtestUtils.broadcast(tx.toHex());
|
||||||
i,
|
|
||||||
p2pkh.output!,
|
|
||||||
utxo.value,
|
|
||||||
ss.hashType,
|
|
||||||
);
|
|
||||||
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey!); // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
|
|
||||||
|
|
||||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue