bitcoinjs-lib/test/integration/transactions-psbt.js

451 lines
15 KiB
JavaScript
Raw Normal View History

2019-07-05 12:40:31 +02:00
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
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)', () => {
2019-07-08 08:46:06 +02:00
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
})
psbt.addOutput({
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
value: 80000
})
psbt.signInput(0, alice)
psbt.validateSignatures(0)
psbt.finalizeAllInputs()
assert.strictEqual(
psbt.extractTransaction().toHex(),
'02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' +
'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' +
'9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' +
'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
'08a22724efa6f6a07b0ec4c79aa88ac00000000',
)
})
2019-07-05 12:40:31 +02:00
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
2019-07-08 09:30:59 +02:00
// signInput and signInputAsync are better
// (They take the input index explicitly as the first arg)
signer1.sign(alice1.keys[0])
signer2.sign(alice2.keys[0])
// If your signer object's sign method returns a promise, use the following
// await signer2.signAsync(alice2.keys[0])
2019-07-05 12:40:31 +02:00
// 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)
2019-07-08 08:46:06 +02:00
// 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.validateSignatures(0), true)
assert.strictEqual(psbt.validateSignatures(1), true)
2019-07-05 12:40:31 +02:00
// 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()
// it returns an array of the success of each input, also a result attribute
// which is true if all array items are true.
// 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])
2019-07-08 08:46:06 +02:00
assert.strictEqual(psbt.validateSignatures(0), true)
2019-07-05 12:40:31 +02:00
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])
2019-07-08 08:46:06 +02:00
assert.strictEqual(psbt.validateSignatures(0), true)
assert.strictEqual(psbt.validateSignatures(0, multisig.keys[0].publicKey), true)
assert.throws(() => {
psbt.validateSignatures(0, multisig.keys[3].publicKey)
}, new RegExp('No signatures for this pubkey'))
2019-07-05 12:40:31 +02:00
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 {
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)
}
2019-07-09 08:45:56 +02:00
const keyPair = p2sh.keys[0]
const outputData = {
script: p2sh.payment.output, // sending to myself for fun
value: 2e4
}
2019-07-05 12:40:31 +02:00
2019-07-09 08:45:56 +02:00
const tx = new bitcoin.Psbt()
2019-07-05 12:40:31 +02:00
.addInput(inputData)
2019-07-09 08:45:56 +02:00
.addOutput(outputData)
.sign(keyPair)
.finalizeAllInputs()
.extractTransaction()
2019-07-05 12:40:31 +02:00
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.verify({
txId: tx.getId(),
2019-07-09 08:45:56 +02:00
address: p2sh.payment.address,
2019-07-05 12:40:31 +02:00
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])
2019-07-08 08:46:06 +02:00
assert.strictEqual(psbt.validateSignatures(0), true)
2019-07-05 12:40:31 +02:00
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', 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])
2019-07-08 08:46:06 +02:00
assert.strictEqual(psbt.validateSignatures(0), true)
2019-07-05 12:40:31 +02:00
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', 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])
2019-07-08 08:46:06 +02:00
assert.strictEqual(psbt.validateSignatures(0), true)
assert.strictEqual(psbt.validateSignatures(0, p2sh.keys[3].publicKey), true)
assert.throws(() => {
psbt.validateSignatures(0, p2sh.keys[1].publicKey)
}, new RegExp('No signatures for this pubkey'))
2019-07-05 12:40:31 +02:00
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
})
})
})
2019-07-08 08:46:06 +02:00
function createPayment(_type, network) {
network = network || regtest
2019-07-05 12:40:31 +02:00
const splitType = _type.split('-').reverse();
const isMultisig = splitType[0].slice(0, 4) === 'p2ms';
const keys = [];
let m;
if (isMultisig) {
const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/)
m = parseInt(match[1])
let n = parseInt(match[2])
while (n > 1) {
2019-07-08 08:46:06 +02:00
keys.push(bitcoin.ECPair.makeRandom({ network }));
2019-07-05 12:40:31 +02:00
n--
}
}
2019-07-08 08:46:06 +02:00
keys.push(bitcoin.ECPair.makeRandom({ network }));
2019-07-05 12:40:31 +02:00
let payment;
splitType.forEach(type => {
if (type.slice(0, 4) === 'p2ms') {
payment = bitcoin.payments.p2ms({
m,
pubkeys: keys.map(key => key.publicKey).sort(),
2019-07-08 08:46:06 +02:00
network,
2019-07-05 12:40:31 +02:00
});
} else if (['p2sh', 'p2wsh'].indexOf(type) > -1) {
payment = bitcoin.payments[type]({
redeem: payment,
2019-07-08 08:46:06 +02:00
network,
2019-07-05 12:40:31 +02:00
});
} else {
payment = bitcoin.payments[type]({
pubkey: keys[0].publicKey,
2019-07-08 08:46:06 +02:00
network,
2019-07-05 12:40:31 +02:00
});
}
});
return {
payment,
keys,
};
}
function getWitnessUtxo(out) {
delete out.address;
out.script = Buffer.from(out.script, 'hex');
return out;
}
async function getInputData(amount, payment, isSegwit, redeemType) {
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 = {};
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,
};
}