Move tests to TypeScript (coverage is still JS based)
This commit is contained in:
parent
11e4a12caf
commit
6c08a0be40
41 changed files with 3920 additions and 2712 deletions
|
@ -1,8 +0,0 @@
|
|||
const { RegtestUtils } = require('regtest-client')
|
||||
|
||||
const APIPASS = process.env.APIPASS || 'satoshi'
|
||||
const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1'
|
||||
|
||||
const regtestUtils = new RegtestUtils({ APIPASS, APIURL })
|
||||
|
||||
module.exports = regtestUtils;
|
6
test/integration/_regtest.ts
Normal file
6
test/integration/_regtest.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { RegtestUtils } from 'regtest-client';
|
||||
|
||||
const APIPASS = process.env.APIPASS || 'satoshi';
|
||||
const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1';
|
||||
|
||||
export const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
|
|
@ -1,117 +1,137 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bitcoin = require('../../')
|
||||
const dhttp = require('./_regtest').dhttp
|
||||
const TESTNET = bitcoin.networks.testnet
|
||||
import * as assert from 'assert';
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
const dhttp = regtestUtils.dhttp;
|
||||
const TESTNET = bitcoin.networks.testnet;
|
||||
|
||||
describe('bitcoinjs-lib (addresses)', () => {
|
||||
it('can generate a random address [and support the retrieval of transactions for that address (via 3PBP)', async () => {
|
||||
const keyPair = bitcoin.ECPair.makeRandom()
|
||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
||||
const keyPair = bitcoin.ECPair.makeRandom();
|
||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey });
|
||||
|
||||
// bitcoin P2PKH addresses start with a '1'
|
||||
assert.strictEqual(address.startsWith('1'), true)
|
||||
assert.strictEqual(address!.startsWith('1'), true);
|
||||
|
||||
const result = await dhttp({
|
||||
method: 'GET',
|
||||
url: 'https://blockchain.info/rawaddr/' + address
|
||||
})
|
||||
url: 'https://blockchain.info/rawaddr/' + address,
|
||||
});
|
||||
|
||||
// random private keys [probably!] have no transactions
|
||||
assert.strictEqual(result.n_tx, 0)
|
||||
assert.strictEqual(result.total_received, 0)
|
||||
assert.strictEqual(result.total_sent, 0)
|
||||
})
|
||||
assert.strictEqual((result as any).n_tx, 0);
|
||||
assert.strictEqual((result as any).total_received, 0);
|
||||
assert.strictEqual((result as any).total_sent, 0);
|
||||
});
|
||||
|
||||
it('can import an address via WIF', () => {
|
||||
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
|
||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
||||
const keyPair = bitcoin.ECPair.fromWIF(
|
||||
'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn',
|
||||
);
|
||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey });
|
||||
|
||||
assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH')
|
||||
})
|
||||
assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH');
|
||||
});
|
||||
|
||||
it('can generate a P2SH, pay-to-multisig (2-of-3) address', () => {
|
||||
const pubkeys = [
|
||||
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
||||
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9'
|
||||
].map((hex) => Buffer.from(hex, 'hex'))
|
||||
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9',
|
||||
].map(hex => Buffer.from(hex, 'hex'));
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys })
|
||||
})
|
||||
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }),
|
||||
});
|
||||
|
||||
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
|
||||
})
|
||||
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7');
|
||||
});
|
||||
|
||||
it('can generate a SegWit address', () => {
|
||||
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
|
||||
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
|
||||
const keyPair = bitcoin.ECPair.fromWIF(
|
||||
'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn',
|
||||
);
|
||||
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey });
|
||||
|
||||
assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4')
|
||||
})
|
||||
assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4');
|
||||
});
|
||||
|
||||
it('can generate a SegWit address (via P2SH)', () => {
|
||||
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
|
||||
const keyPair = bitcoin.ECPair.fromWIF(
|
||||
'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn',
|
||||
);
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
|
||||
})
|
||||
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }),
|
||||
});
|
||||
|
||||
assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN')
|
||||
})
|
||||
assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN');
|
||||
});
|
||||
|
||||
it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', () => {
|
||||
const pubkeys = [
|
||||
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
||||
'023e4740d0ba639e28963f3476157b7cf2fb7c6fdf4254f97099cf8670b505ea59',
|
||||
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9'
|
||||
].map((hex) => Buffer.from(hex, 'hex'))
|
||||
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9',
|
||||
].map(hex => Buffer.from(hex, 'hex'));
|
||||
const { address } = bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: 3, pubkeys })
|
||||
})
|
||||
redeem: bitcoin.payments.p2ms({ m: 3, pubkeys }),
|
||||
});
|
||||
|
||||
assert.strictEqual(address, 'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul')
|
||||
})
|
||||
assert.strictEqual(
|
||||
address,
|
||||
'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul',
|
||||
);
|
||||
});
|
||||
|
||||
it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', () => {
|
||||
const pubkeys = [
|
||||
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9'
|
||||
].map((hex) => Buffer.from(hex, 'hex'))
|
||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
||||
].map(hex => Buffer.from(hex, 'hex'));
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys })
|
||||
})
|
||||
})
|
||||
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }),
|
||||
}),
|
||||
});
|
||||
|
||||
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN')
|
||||
})
|
||||
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN');
|
||||
});
|
||||
|
||||
// examples using other network information
|
||||
it('can generate a Testnet address', () => {
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET })
|
||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET })
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET });
|
||||
const { address } = bitcoin.payments.p2pkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
network: TESTNET,
|
||||
});
|
||||
|
||||
// bitcoin testnet P2PKH addresses start with a 'm' or 'n'
|
||||
assert.strictEqual(address.startsWith('m') || address.startsWith('n'), true)
|
||||
})
|
||||
assert.strictEqual(
|
||||
address!.startsWith('m') || address!.startsWith('n'),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('can generate a Litecoin address', () => {
|
||||
// WARNING: although possible, bitcoinjs is NOT necessarily compatible with Litecoin
|
||||
const LITECOIN = {
|
||||
messagePrefix: '\x19Litecoin Signed Message:\n',
|
||||
bech32: 'ltc',
|
||||
bip32: {
|
||||
public: 0x019da462,
|
||||
private: 0x019d9cfe
|
||||
private: 0x019d9cfe,
|
||||
},
|
||||
pubKeyHash: 0x30,
|
||||
scriptHash: 0x32,
|
||||
wif: 0xb0
|
||||
}
|
||||
wif: 0xb0,
|
||||
};
|
||||
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN })
|
||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN })
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN });
|
||||
const { address } = bitcoin.payments.p2pkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
network: LITECOIN,
|
||||
});
|
||||
|
||||
assert.strictEqual(address.startsWith('L'), true)
|
||||
})
|
||||
})
|
||||
assert.strictEqual(address!.startsWith('L'), true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,101 +1,151 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bip32 = require('bip32')
|
||||
const bip39 = require('bip39')
|
||||
const bitcoin = require('../../')
|
||||
import * as assert from 'assert';
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bip32 from 'bip32';
|
||||
import * as bip39 from 'bip39';
|
||||
import * as bitcoin from '../..';
|
||||
|
||||
function getAddress (node, network) {
|
||||
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
|
||||
function getAddress(node: any, network?: any): string {
|
||||
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
|
||||
}
|
||||
|
||||
describe('bitcoinjs-lib (BIP32)', () => {
|
||||
it('can import a BIP32 testnet xpriv and export to WIF', () => {
|
||||
const xpriv = 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK'
|
||||
const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet)
|
||||
const xpriv =
|
||||
'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK';
|
||||
const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet);
|
||||
|
||||
assert.strictEqual(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7')
|
||||
})
|
||||
assert.strictEqual(
|
||||
node.toWIF(),
|
||||
'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7',
|
||||
);
|
||||
});
|
||||
|
||||
it('can export a BIP32 xpriv, then import it', () => {
|
||||
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||
const node = bip32.fromSeed(seed)
|
||||
const string = node.toBase58()
|
||||
const restored = bip32.fromBase58(string)
|
||||
const mnemonic =
|
||||
'praise you muffin lion enable neck grocery crumble super myself license ghost';
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const node = bip32.fromSeed(seed);
|
||||
const string = node.toBase58();
|
||||
const restored = bip32.fromBase58(string);
|
||||
|
||||
assert.strictEqual(getAddress(node), getAddress(restored)) // same public key
|
||||
assert.strictEqual(node.toWIF(), restored.toWIF()) // same private key
|
||||
})
|
||||
assert.strictEqual(getAddress(node), getAddress(restored)); // same public key
|
||||
assert.strictEqual(node.toWIF(), restored.toWIF()); // same private key
|
||||
});
|
||||
|
||||
it('can export a BIP32 xpub', () => {
|
||||
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||
const node = bip32.fromSeed(seed)
|
||||
const string = node.neutered().toBase58()
|
||||
const mnemonic =
|
||||
'praise you muffin lion enable neck grocery crumble super myself license ghost';
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const node = bip32.fromSeed(seed);
|
||||
const string = node.neutered().toBase58();
|
||||
|
||||
assert.strictEqual(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n')
|
||||
})
|
||||
assert.strictEqual(
|
||||
string,
|
||||
'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create a BIP32, bitcoin, account 0, external address', () => {
|
||||
const path = "m/0'/0/0"
|
||||
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
|
||||
const path = "m/0'/0/0";
|
||||
const root = bip32.fromSeed(
|
||||
Buffer.from(
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'hex',
|
||||
),
|
||||
);
|
||||
|
||||
const child1 = root.derivePath(path)
|
||||
const child1 = root.derivePath(path);
|
||||
|
||||
// option 2, manually
|
||||
const child1b = root.deriveHardened(0)
|
||||
.derive(0)
|
||||
const child1b = root
|
||||
.deriveHardened(0)
|
||||
.derive(0)
|
||||
.derive(0);
|
||||
|
||||
assert.strictEqual(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
|
||||
assert.strictEqual(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
|
||||
})
|
||||
assert.strictEqual(
|
||||
getAddress(child1),
|
||||
'1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7',
|
||||
);
|
||||
assert.strictEqual(
|
||||
getAddress(child1b),
|
||||
'1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create a BIP44, bitcoin, account 0, external address', () => {
|
||||
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
|
||||
const root = bip32.fromSeed(
|
||||
Buffer.from(
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'hex',
|
||||
),
|
||||
);
|
||||
|
||||
const child1 = root.derivePath("m/44'/0'/0'/0/0")
|
||||
const child1 = root.derivePath("m/44'/0'/0'/0/0");
|
||||
|
||||
// option 2, manually
|
||||
const child1b = root.deriveHardened(44)
|
||||
const child1b = root
|
||||
.deriveHardened(44)
|
||||
.deriveHardened(0)
|
||||
.deriveHardened(0)
|
||||
.derive(0)
|
||||
.derive(0)
|
||||
.derive(0);
|
||||
|
||||
assert.strictEqual(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
|
||||
assert.strictEqual(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
|
||||
})
|
||||
assert.strictEqual(
|
||||
getAddress(child1),
|
||||
'12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au',
|
||||
);
|
||||
assert.strictEqual(
|
||||
getAddress(child1b),
|
||||
'12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create a BIP49, bitcoin testnet, account 0, external address', () => {
|
||||
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
|
||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||
const root = bip32.fromSeed(seed)
|
||||
const mnemonic =
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = bip32.fromSeed(seed);
|
||||
|
||||
const path = "m/49'/1'/0'/0/0"
|
||||
const child = root.derivePath(path)
|
||||
const path = "m/49'/1'/0'/0/0";
|
||||
const child = root.derivePath(path);
|
||||
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }),
|
||||
network: bitcoin.networks.testnet
|
||||
})
|
||||
assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
|
||||
})
|
||||
redeem: bitcoin.payments.p2wpkh({
|
||||
pubkey: child.publicKey,
|
||||
network: bitcoin.networks.testnet,
|
||||
}),
|
||||
network: bitcoin.networks.testnet,
|
||||
});
|
||||
assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2');
|
||||
});
|
||||
|
||||
it('can use BIP39 to generate BIP32 addresses', () => {
|
||||
// var mnemonic = bip39.generateMnemonic()
|
||||
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
||||
assert(bip39.validateMnemonic(mnemonic))
|
||||
const mnemonic =
|
||||
'praise you muffin lion enable neck grocery crumble super myself license ghost';
|
||||
assert(bip39.validateMnemonic(mnemonic));
|
||||
|
||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||
const root = bip32.fromSeed(seed)
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = bip32.fromSeed(seed);
|
||||
|
||||
// receive addresses
|
||||
assert.strictEqual(getAddress(root.derivePath("m/0'/0/0")), '1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC')
|
||||
assert.strictEqual(getAddress(root.derivePath("m/0'/0/1")), '1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL')
|
||||
assert.strictEqual(
|
||||
getAddress(root.derivePath("m/0'/0/0")),
|
||||
'1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC',
|
||||
);
|
||||
assert.strictEqual(
|
||||
getAddress(root.derivePath("m/0'/0/1")),
|
||||
'1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL',
|
||||
);
|
||||
|
||||
// change addresses
|
||||
assert.strictEqual(getAddress(root.derivePath("m/0'/1/0")), '1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn')
|
||||
assert.strictEqual(getAddress(root.derivePath("m/0'/1/1")), '1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6')
|
||||
})
|
||||
})
|
||||
assert.strictEqual(
|
||||
getAddress(root.derivePath("m/0'/1/0")),
|
||||
'1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn',
|
||||
);
|
||||
assert.strictEqual(
|
||||
getAddress(root.derivePath("m/0'/1/1")),
|
||||
'1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
'use strict'
|
||||
|
||||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bitcoin = require('../../')
|
||||
import * as assert from 'assert';
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
|
||||
describe('bitcoinjs-lib (blocks)', () => {
|
||||
it('can extract a height from a CoinBase transaction', () => {
|
||||
// from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6
|
||||
const txHex = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000'
|
||||
const tx = bitcoin.Transaction.fromHex(txHex)
|
||||
const txHex =
|
||||
'010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000';
|
||||
const tx = bitcoin.Transaction.fromHex(txHex);
|
||||
|
||||
assert.strictEqual(tx.ins.length, 1)
|
||||
const script = tx.ins[0].script
|
||||
assert.strictEqual(tx.ins.length, 1);
|
||||
const script = tx.ins[0].script;
|
||||
// bitcoin.script.decompile(script) // returns [] :(
|
||||
|
||||
assert.strictEqual(script[0], 0x03)
|
||||
const heightBuffer = script.slice(1, 4)
|
||||
const height = bitcoin.script.number.decode(heightBuffer)
|
||||
assert.strictEqual(height, 498303)
|
||||
})
|
||||
})
|
||||
assert.strictEqual(script[0], 0x03);
|
||||
const heightBuffer = script.slice(1, 4);
|
||||
const height = bitcoin.script.number.decode(heightBuffer);
|
||||
assert.strictEqual(height, 498303);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,198 +1,219 @@
|
|||
const { describe, it, before } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bitcoin = require('../../')
|
||||
const regtestUtils = require('./_regtest')
|
||||
const regtest = regtestUtils.network
|
||||
const bip65 = require('bip65')
|
||||
import * as assert from 'assert';
|
||||
import { before, describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
const regtest = regtestUtils.network;
|
||||
const bip65 = require('bip65');
|
||||
|
||||
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
|
||||
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
|
||||
console.warn = () => {} // Silence the Deprecation Warning
|
||||
const alice = bitcoin.ECPair.fromWIF(
|
||||
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
|
||||
regtest,
|
||||
);
|
||||
const bob = bitcoin.ECPair.fromWIF(
|
||||
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
|
||||
regtest,
|
||||
);
|
||||
console.warn = () => {}; // Silence the Deprecation Warning
|
||||
|
||||
describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||
// force update MTP
|
||||
before(async () => {
|
||||
await regtestUtils.mine(11)
|
||||
})
|
||||
await regtestUtils.mine(11);
|
||||
});
|
||||
|
||||
const hashType = bitcoin.Transaction.SIGHASH_ALL
|
||||
const hashType = bitcoin.Transaction.SIGHASH_ALL;
|
||||
|
||||
function cltvCheckSigOutput (aQ, bQ, lockTime) {
|
||||
return bitcoin.script.compile([
|
||||
bitcoin.opcodes.OP_IF,
|
||||
bitcoin.script.number.encode(lockTime),
|
||||
bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
|
||||
bitcoin.opcodes.OP_DROP,
|
||||
|
||||
bitcoin.opcodes.OP_ELSE,
|
||||
bQ.publicKey,
|
||||
bitcoin.opcodes.OP_CHECKSIGVERIFY,
|
||||
bitcoin.opcodes.OP_ENDIF,
|
||||
|
||||
aQ.publicKey,
|
||||
bitcoin.opcodes.OP_CHECKSIG
|
||||
])
|
||||
type keyPair = { publicKey: Buffer };
|
||||
function cltvCheckSigOutput(aQ: keyPair, bQ: keyPair, lockTime: number) {
|
||||
return bitcoin.script.fromASM(
|
||||
`
|
||||
OP_IF
|
||||
${bitcoin.script.number.encode(lockTime).toString('hex')}
|
||||
OP_CHECKLOCKTIMEVERIFY
|
||||
OP_DROP
|
||||
OP_ELSE
|
||||
${bQ.publicKey.toString('hex')}
|
||||
OP_CHECKSIGVERIFY
|
||||
OP_ENDIF
|
||||
${aQ.publicKey.toString('hex')}
|
||||
OP_CHECKSIG
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
function utcNow () {
|
||||
return Math.floor(Date.now() / 1000)
|
||||
function utcNow() {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
}
|
||||
|
||||
// expiry past, {Alice's signature} OP_TRUE
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', async () => {
|
||||
// 3 hours ago
|
||||
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) })
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||
const lockTime = bip65.encode({ utc: utcNow() - 3600 * 3 });
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: { output: redeemScript, network: regtest },
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// fund the P2SH(CLTV) address
|
||||
const unspent = await regtestUtils.faucet(address, 1e5)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.setLockTime(lockTime)
|
||||
const unspent = await regtestUtils.faucet(address!, 1e5);
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.setLockTime(lockTime);
|
||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
||||
|
||||
// {Alice's signature} OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
output: redeemScript
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
output: redeemScript,
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 7e4
|
||||
})
|
||||
})
|
||||
value: 7e4,
|
||||
});
|
||||
});
|
||||
|
||||
// expiry will pass, {Alice's signature} OP_TRUE
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', async () => {
|
||||
const height = await regtestUtils.height()
|
||||
const height = await regtestUtils.height();
|
||||
// 5 blocks from now
|
||||
const lockTime = bip65.encode({ blocks: height + 5 })
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||
const lockTime = bip65.encode({ blocks: height + 5 });
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: { output: redeemScript, network: regtest },
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// fund the P2SH(CLTV) address
|
||||
const unspent = await regtestUtils.faucet(address, 1e5)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.setLockTime(lockTime)
|
||||
const unspent = await regtestUtils.faucet(address!, 1e5);
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.setLockTime(lockTime);
|
||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
||||
|
||||
// {Alice's signature} OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
output: redeemScript
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
output: redeemScript,
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
||||
// ...
|
||||
// into the future!
|
||||
await regtestUtils.mine(5)
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.mine(5);
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 7e4
|
||||
})
|
||||
})
|
||||
value: 7e4,
|
||||
});
|
||||
});
|
||||
|
||||
// expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', async () => {
|
||||
// two hours ago
|
||||
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) })
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||
const lockTime = bip65.encode({ utc: utcNow() - 3600 * 2 });
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: { output: redeemScript, network: regtest },
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// fund the P2SH(CLTV) address
|
||||
const unspent = await regtestUtils.faucet(address, 2e5)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.setLockTime(lockTime)
|
||||
const unspent = await regtestUtils.faucet(address!, 2e5);
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.setLockTime(lockTime);
|
||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4)
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4);
|
||||
|
||||
// {Alice's signature} {Bob's signature} OP_FALSE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_FALSE
|
||||
bitcoin.opcodes.OP_FALSE,
|
||||
]),
|
||||
output: redeemScript
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
output: redeemScript,
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 8e4
|
||||
})
|
||||
})
|
||||
value: 8e4,
|
||||
});
|
||||
});
|
||||
|
||||
// expiry in the future, {Alice's signature} OP_TRUE
|
||||
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', async () => {
|
||||
// two hours from now
|
||||
const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) })
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||
const lockTime = bip65.encode({ utc: utcNow() + 3600 * 2 });
|
||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: { output: redeemScript, network: regtest },
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// fund the P2SH(CLTV) address
|
||||
const unspent = await regtestUtils.faucet(address, 2e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.setLockTime(lockTime)
|
||||
const unspent = await regtestUtils.faucet(address!, 2e4);
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.setLockTime(lockTime);
|
||||
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
|
||||
|
||||
// {Alice's signature} OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
output: redeemScript
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
output: redeemScript,
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex()).catch(err => {
|
||||
assert.throws(() => {
|
||||
if (err) throw err
|
||||
}, /Error: non-final \(code 64\)/)
|
||||
})
|
||||
})
|
||||
})
|
||||
if (err) throw err;
|
||||
}, /Error: non-final \(code 64\)/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
const { describe, it, before } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bitcoin = require('../../')
|
||||
const regtestUtils = require('./_regtest')
|
||||
const regtest = regtestUtils.network
|
||||
const bip68 = require('bip68')
|
||||
import * as assert from 'assert';
|
||||
import { before, describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
const regtest = regtestUtils.network;
|
||||
const bip68 = require('bip68');
|
||||
|
||||
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
|
||||
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
|
||||
const charles = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest)
|
||||
const dave = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest)
|
||||
console.warn = () => {} // Silence the Deprecation Warning
|
||||
const alice = bitcoin.ECPair.fromWIF(
|
||||
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
|
||||
regtest,
|
||||
);
|
||||
const bob = bitcoin.ECPair.fromWIF(
|
||||
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
|
||||
regtest,
|
||||
);
|
||||
const charles = bitcoin.ECPair.fromWIF(
|
||||
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf',
|
||||
regtest,
|
||||
);
|
||||
const dave = bitcoin.ECPair.fromWIF(
|
||||
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx',
|
||||
regtest,
|
||||
);
|
||||
console.warn = () => {}; // Silence the Deprecation Warning
|
||||
|
||||
describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||
// force update MTP
|
||||
before(async () => {
|
||||
await regtestUtils.mine(11)
|
||||
})
|
||||
await regtestUtils.mine(11);
|
||||
});
|
||||
|
||||
const hashType = bitcoin.Transaction.SIGHASH_ALL
|
||||
const hashType = bitcoin.Transaction.SIGHASH_ALL;
|
||||
|
||||
type keyPair = { publicKey: Buffer };
|
||||
// IF MTP (from when confirmed) > seconds, _alice can redeem
|
||||
function csvCheckSigOutput (_alice, _bob, sequence) {
|
||||
return bitcoin.script.fromASM(`
|
||||
function csvCheckSigOutput(_alice: keyPair, _bob: keyPair, sequence: number) {
|
||||
return bitcoin.script.fromASM(
|
||||
`
|
||||
OP_IF
|
||||
${bitcoin.script.number.encode(sequence).toString('hex')}
|
||||
OP_CHECKSEQUENCEVERIFY
|
||||
|
@ -32,7 +46,10 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
|||
OP_ENDIF
|
||||
${_alice.publicKey.toString('hex')}
|
||||
OP_CHECKSIG
|
||||
`.trim().replace(/\s+/g, ' '))
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
// 2 of 3 multisig of _bob, _charles, _dave,
|
||||
|
@ -41,8 +58,16 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
|||
// Ref: https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch07.asciidoc#complex-script-example
|
||||
// Note: bitcoinjs-lib will not offer specific support for problems with
|
||||
// advanced script usages such as below. Use at your own risk.
|
||||
function complexCsvOutput (_alice, _bob, _charles, _dave, sequence1, sequence2) {
|
||||
return bitcoin.script.fromASM(`
|
||||
function complexCsvOutput(
|
||||
_alice: keyPair,
|
||||
_bob: keyPair,
|
||||
_charles: keyPair,
|
||||
_dave: keyPair,
|
||||
sequence1: number,
|
||||
sequence2: number,
|
||||
) {
|
||||
return bitcoin.script.fromASM(
|
||||
`
|
||||
OP_IF
|
||||
OP_IF
|
||||
OP_2
|
||||
|
@ -66,253 +91,294 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
|||
${_alice.publicKey.toString('hex')}
|
||||
OP_CHECKSIG
|
||||
OP_ENDIF
|
||||
`.trim().replace(/\s+/g, ' '))
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
// expiry will pass, {Alice's signature} OP_TRUE
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', async () => {
|
||||
// 5 blocks from now
|
||||
const sequence = bip68.encode({ blocks: 5 })
|
||||
const sequence = bip68.encode({ blocks: 5 });
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
output: csvCheckSigOutput(alice, bob, sequence)
|
||||
output: csvCheckSigOutput(alice, bob, sequence),
|
||||
},
|
||||
network: regtest
|
||||
})
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// 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)
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
||||
|
||||
// {Alice's signature} OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(
|
||||
0,
|
||||
p2sh.redeem!.output!,
|
||||
hashType,
|
||||
);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
network: regtest,
|
||||
redeem: {
|
||||
network: regtest,
|
||||
output: p2sh.redeem.output,
|
||||
output: p2sh.redeem!.output,
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
])
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
||||
// ...
|
||||
// into the future!
|
||||
await regtestUtils.mine(10)
|
||||
await regtestUtils.mine(10);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 7e4
|
||||
})
|
||||
})
|
||||
value: 7e4,
|
||||
});
|
||||
});
|
||||
|
||||
// expiry in the future, {Alice's signature} OP_TRUE
|
||||
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', async () => {
|
||||
// two hours after confirmation
|
||||
const sequence = bip68.encode({ seconds: 7168 })
|
||||
const sequence = bip68.encode({ seconds: 7168 });
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
network: regtest,
|
||||
redeem: {
|
||||
output: csvCheckSigOutput(alice, bob, sequence)
|
||||
}
|
||||
})
|
||||
output: csvCheckSigOutput(alice, bob, sequence),
|
||||
},
|
||||
});
|
||||
|
||||
// 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)
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
|
||||
|
||||
// {Alice's signature} OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(
|
||||
0,
|
||||
p2sh.redeem!.output!,
|
||||
hashType,
|
||||
);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
network: regtest,
|
||||
redeem: {
|
||||
network: regtest,
|
||||
output: p2sh.redeem.output,
|
||||
output: p2sh.redeem!.output,
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
])
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex()).catch(err => {
|
||||
assert.throws(() => {
|
||||
if (err) throw err
|
||||
}, /Error: non-BIP68-final \(code 64\)/)
|
||||
})
|
||||
})
|
||||
if (err) throw err;
|
||||
}, /Error: non-BIP68-final \(code 64\)/);
|
||||
});
|
||||
});
|
||||
|
||||
// Check first combination of complex CSV, 2 of 3
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)', async () => {
|
||||
const height = await regtestUtils.height()
|
||||
|
||||
// 2 blocks from now
|
||||
const sequence1 = bip68.encode({ blocks: 2 })
|
||||
const sequence1 = bip68.encode({ blocks: 2 });
|
||||
// 5 blocks from now
|
||||
const sequence2 = bip68.encode({ blocks: 5 })
|
||||
const sequence2 = bip68.encode({ blocks: 5 });
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
||||
output: complexCsvOutput(
|
||||
alice,
|
||||
bob,
|
||||
charles,
|
||||
dave,
|
||||
sequence1,
|
||||
sequence2,
|
||||
),
|
||||
},
|
||||
network: regtest
|
||||
})
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// 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)
|
||||
txb.addInput(unspent.txId, unspent.vout)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
||||
|
||||
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(
|
||||
0,
|
||||
p2sh.redeem!.output!,
|
||||
hashType,
|
||||
);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
network: regtest,
|
||||
redeem: {
|
||||
network: regtest,
|
||||
output: p2sh.redeem.output,
|
||||
output: p2sh.redeem!.output,
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.opcodes.OP_0,
|
||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||
bitcoin.script.signature.encode(charles.sign(signatureHash), hashType),
|
||||
bitcoin.script.signature.encode(
|
||||
charles.sign(signatureHash),
|
||||
hashType,
|
||||
),
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
])
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 7e4
|
||||
})
|
||||
})
|
||||
value: 7e4,
|
||||
});
|
||||
});
|
||||
|
||||
// Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', async () => {
|
||||
const height = await regtestUtils.height()
|
||||
|
||||
// 2 blocks from now
|
||||
const sequence1 = bip68.encode({ blocks: 2 })
|
||||
const sequence1 = bip68.encode({ blocks: 2 });
|
||||
// 5 blocks from now
|
||||
const sequence2 = bip68.encode({ blocks: 5 })
|
||||
const sequence2 = bip68.encode({ blocks: 5 });
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
||||
output: complexCsvOutput(
|
||||
alice,
|
||||
bob,
|
||||
charles,
|
||||
dave,
|
||||
sequence1,
|
||||
sequence2,
|
||||
),
|
||||
},
|
||||
network: regtest
|
||||
})
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// 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)
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence1); // Set sequence1 for input
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
||||
|
||||
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(
|
||||
0,
|
||||
p2sh.redeem!.output!,
|
||||
hashType,
|
||||
);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
network: regtest,
|
||||
redeem: {
|
||||
network: regtest,
|
||||
output: p2sh.redeem.output,
|
||||
output: p2sh.redeem!.output,
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.opcodes.OP_0,
|
||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_0,
|
||||
bitcoin.opcodes.OP_TRUE
|
||||
])
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
bitcoin.opcodes.OP_TRUE,
|
||||
]),
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
// Wait 2 blocks
|
||||
await regtestUtils.mine(2)
|
||||
await regtestUtils.mine(2);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 7e4
|
||||
})
|
||||
})
|
||||
value: 7e4,
|
||||
});
|
||||
});
|
||||
|
||||
// Check first combination of complex CSV, mediator after 5 blocks
|
||||
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', async () => {
|
||||
const height = await regtestUtils.height()
|
||||
|
||||
// 2 blocks from now
|
||||
const sequence1 = bip68.encode({ blocks: 2 })
|
||||
const sequence1 = bip68.encode({ blocks: 2 });
|
||||
// 5 blocks from now
|
||||
const sequence2 = bip68.encode({ blocks: 5 })
|
||||
const sequence2 = bip68.encode({ blocks: 5 });
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: {
|
||||
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
||||
output: complexCsvOutput(
|
||||
alice,
|
||||
bob,
|
||||
charles,
|
||||
dave,
|
||||
sequence1,
|
||||
sequence2,
|
||||
),
|
||||
},
|
||||
network: regtest
|
||||
})
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
// 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)
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, sequence2); // Set sequence2 for input
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
|
||||
|
||||
// {Alice mediator sig} OP_FALSE
|
||||
const tx = txb.buildIncomplete()
|
||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||
const tx = txb.buildIncomplete();
|
||||
const signatureHash = tx.hashForSignature(
|
||||
0,
|
||||
p2sh.redeem!.output!,
|
||||
hashType,
|
||||
);
|
||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||
network: regtest,
|
||||
redeem: {
|
||||
network: regtest,
|
||||
output: p2sh.redeem.output,
|
||||
output: p2sh.redeem!.output,
|
||||
input: bitcoin.script.compile([
|
||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||
bitcoin.opcodes.OP_0
|
||||
])
|
||||
}
|
||||
}).input
|
||||
tx.setInputScript(0, redeemScriptSig)
|
||||
bitcoin.opcodes.OP_0,
|
||||
]),
|
||||
},
|
||||
}).input;
|
||||
tx.setInputScript(0, redeemScriptSig!);
|
||||
|
||||
// Wait 5 blocks
|
||||
await regtestUtils.mine(5)
|
||||
await regtestUtils.mine(5);
|
||||
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 7e4
|
||||
})
|
||||
})
|
||||
})
|
||||
value: 7e4,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
const bitcoin = require('../../')
|
||||
|
||||
const { describe, it } = require('mocha')
|
||||
const regtestUtils = require('./_regtest')
|
||||
const NETWORK = regtestUtils.network
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
const NETWORK = regtestUtils.network;
|
||||
const keyPairs = [
|
||||
bitcoin.ECPair.makeRandom({ network: NETWORK }),
|
||||
bitcoin.ECPair.makeRandom({ network: NETWORK })
|
||||
]
|
||||
console.warn = () => {} // Silence the Deprecation Warning
|
||||
bitcoin.ECPair.makeRandom({ network: NETWORK }),
|
||||
];
|
||||
console.warn = () => {}; // Silence the Deprecation Warning
|
||||
|
||||
async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
|
||||
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4)
|
||||
async function buildAndSign(
|
||||
depends: any,
|
||||
prevOutput: any,
|
||||
redeemScript: any,
|
||||
witnessScript: any,
|
||||
) {
|
||||
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4);
|
||||
|
||||
const txb = new bitcoin.TransactionBuilder(NETWORK)
|
||||
txb.addInput(unspent.txId, unspent.vout, null, prevOutput)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||
const txb = new bitcoin.TransactionBuilder(NETWORK);
|
||||
txb.addInput(unspent.txId, unspent.vout, undefined, prevOutput);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
|
||||
|
||||
const posType = depends.prevOutScriptType
|
||||
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh'
|
||||
const posType = depends.prevOutScriptType;
|
||||
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh';
|
||||
|
||||
if (depends.signatures) {
|
||||
keyPairs.forEach(keyPair => {
|
||||
|
@ -28,8 +32,8 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
|
|||
redeemScript,
|
||||
witnessValue: needsValue ? unspent.value : undefined,
|
||||
witnessScript,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
} else if (depends.signature) {
|
||||
txb.sign({
|
||||
prevOutScriptType: posType,
|
||||
|
@ -38,52 +42,94 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
|
|||
redeemScript,
|
||||
witnessValue: needsValue ? unspent.value : undefined,
|
||||
witnessScript,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return regtestUtils.broadcast(txb.build().toHex())
|
||||
return regtestUtils.broadcast(txb.build().toHex());
|
||||
}
|
||||
|
||||
;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
|
||||
const fixtures = require('../fixtures/' + k)
|
||||
const { depends } = fixtures.dynamic
|
||||
const fn = bitcoin.payments[k]
|
||||
['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
|
||||
const fixtures = require('../fixtures/' + k);
|
||||
const { depends } = fixtures.dynamic;
|
||||
const fn: any = (bitcoin.payments as any)[k];
|
||||
|
||||
const base = {}
|
||||
if (depends.pubkey) base.pubkey = keyPairs[0].publicKey
|
||||
if (depends.pubkeys) base.pubkeys = keyPairs.map(x => x.publicKey)
|
||||
if (depends.m) base.m = base.pubkeys.length
|
||||
const base: any = {};
|
||||
if (depends.pubkey) base.pubkey = keyPairs[0].publicKey;
|
||||
if (depends.pubkeys) base.pubkeys = keyPairs.map(x => x.publicKey);
|
||||
if (depends.m) base.m = base.pubkeys.length;
|
||||
|
||||
const { output } = fn(base)
|
||||
if (!output) throw new TypeError('Missing output')
|
||||
const { output } = fn(base);
|
||||
if (!output) throw new TypeError('Missing output');
|
||||
|
||||
describe('bitcoinjs-lib (payments - ' + k + ')', () => {
|
||||
it('can broadcast as an output, and be spent as an input', async () => {
|
||||
Object.assign(depends, { prevOutScriptType: k })
|
||||
await buildAndSign(depends, output, undefined, undefined)
|
||||
})
|
||||
Object.assign(depends, { prevOutScriptType: k });
|
||||
await buildAndSign(depends, output, undefined, undefined);
|
||||
});
|
||||
|
||||
it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', async () => {
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK })
|
||||
Object.assign(depends, { prevOutScriptType: 'p2sh-' + k })
|
||||
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, undefined)
|
||||
})
|
||||
it(
|
||||
'can (as P2SH(' +
|
||||
k +
|
||||
')) broadcast as an output, and be spent as an input',
|
||||
async () => {
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: { output },
|
||||
network: NETWORK,
|
||||
});
|
||||
Object.assign(depends, { prevOutScriptType: 'p2sh-' + k });
|
||||
await buildAndSign(
|
||||
depends,
|
||||
p2sh.output,
|
||||
p2sh.redeem!.output,
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail
|
||||
if (k === 'p2wpkh') return
|
||||
if (k === 'p2wpkh') return;
|
||||
|
||||
it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', async () => {
|
||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
|
||||
Object.assign(depends, { prevOutScriptType: 'p2wsh-' + k })
|
||||
await buildAndSign(depends, p2wsh.output, undefined, p2wsh.redeem.output)
|
||||
})
|
||||
it(
|
||||
'can (as P2WSH(' +
|
||||
k +
|
||||
')) broadcast as an output, and be spent as an input',
|
||||
async () => {
|
||||
const p2wsh = bitcoin.payments.p2wsh({
|
||||
redeem: { output },
|
||||
network: NETWORK,
|
||||
});
|
||||
Object.assign(depends, { prevOutScriptType: 'p2wsh-' + k });
|
||||
await buildAndSign(
|
||||
depends,
|
||||
p2wsh.output,
|
||||
undefined,
|
||||
p2wsh.redeem!.output,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', async () => {
|
||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK })
|
||||
it(
|
||||
'can (as P2SH(P2WSH(' +
|
||||
k +
|
||||
'))) broadcast as an output, and be spent as an input',
|
||||
async () => {
|
||||
const p2wsh = bitcoin.payments.p2wsh({
|
||||
redeem: { output },
|
||||
network: NETWORK,
|
||||
});
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: { output: p2wsh.output },
|
||||
network: NETWORK,
|
||||
});
|
||||
|
||||
Object.assign(depends, { prevOutScriptType: 'p2sh-p2wsh-' + k })
|
||||
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output)
|
||||
})
|
||||
})
|
||||
})
|
||||
Object.assign(depends, { prevOutScriptType: 'p2sh-p2wsh-' + k });
|
||||
await buildAndSign(
|
||||
depends,
|
||||
p2sh.output,
|
||||
p2sh.redeem!.output,
|
||||
p2wsh.redeem!.output,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const { describe, it } = require('mocha');
|
||||
const assert = require('assert');
|
||||
const bitcoin = require('../../');
|
||||
const bip32 = require('bip32');
|
||||
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 regtestUtils = require('./_regtest');
|
||||
const regtest = regtestUtils.network;
|
||||
|
||||
// See bottom of file for some helper functions used to make the payment objects needed.
|
||||
|
@ -174,7 +174,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||
.addInput(inputData1)
|
||||
.addOutput({
|
||||
script: embed.output,
|
||||
script: embed.output!,
|
||||
value: 1000,
|
||||
})
|
||||
.addOutput({
|
||||
|
@ -350,7 +350,12 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
// 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 inputData = await getInputData(
|
||||
5e4,
|
||||
p2wpkh.payment,
|
||||
false,
|
||||
'noredeem',
|
||||
);
|
||||
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||
.addInput(inputData)
|
||||
.addOutput({
|
||||
|
@ -486,7 +491,12 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
// 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 inputData = await getInputData(
|
||||
5e4,
|
||||
p2sh.payment,
|
||||
false,
|
||||
'p2sh-p2wsh',
|
||||
);
|
||||
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||
.addInput(inputData)
|
||||
.addOutput({
|
||||
|
@ -528,9 +538,9 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
masterFingerprint,
|
||||
path,
|
||||
pubkey,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
const p2wpkh = createPayment('p2wpkh', [childNode]);
|
||||
const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem');
|
||||
{
|
||||
|
@ -539,7 +549,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
}
|
||||
|
||||
// You can add extra attributes for updateData into the addInput(s) object(s)
|
||||
Object.assign(inputData, updateData)
|
||||
Object.assign(inputData, updateData);
|
||||
|
||||
const psbt = new bitcoin.Psbt({ network: regtest })
|
||||
.addInput(inputData)
|
||||
|
@ -551,7 +561,10 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
.signInputHD(0, hdRoot); // must sign with root!!!
|
||||
|
||||
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
|
||||
assert.strictEqual(psbt.validateSignaturesOfInput(0, childNode.publicKey), true);
|
||||
assert.strictEqual(
|
||||
psbt.validateSignaturesOfInput(0, childNode.publicKey),
|
||||
true,
|
||||
);
|
||||
psbt.finalizeAllInputs();
|
||||
|
||||
const tx = psbt.extractTransaction();
|
||||
|
@ -568,18 +581,18 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function createPayment(_type, myKeys, network) {
|
||||
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;
|
||||
let m: number | undefined;
|
||||
if (isMultisig) {
|
||||
const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/);
|
||||
m = parseInt(match[1]);
|
||||
let n = parseInt(match[2]);
|
||||
m = parseInt(match![1]);
|
||||
let n = parseInt(match![2]);
|
||||
if (keys.length > 0 && keys.length !== n) {
|
||||
throw new Error('Need n keys for multisig')
|
||||
throw new Error('Need n keys for multisig');
|
||||
}
|
||||
while (!myKeys && n > 1) {
|
||||
keys.push(bitcoin.ECPair.makeRandom({ network }));
|
||||
|
@ -588,7 +601,7 @@ function createPayment(_type, myKeys, network) {
|
|||
}
|
||||
if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network }));
|
||||
|
||||
let payment;
|
||||
let payment: any;
|
||||
splitType.forEach(type => {
|
||||
if (type.slice(0, 4) === 'p2ms') {
|
||||
payment = bitcoin.payments.p2ms({
|
||||
|
@ -597,12 +610,12 @@ function createPayment(_type, myKeys, network) {
|
|||
network,
|
||||
});
|
||||
} else if (['p2sh', 'p2wsh'].indexOf(type) > -1) {
|
||||
payment = bitcoin.payments[type]({
|
||||
payment = (bitcoin.payments as any)[type]({
|
||||
redeem: payment,
|
||||
network,
|
||||
});
|
||||
} else {
|
||||
payment = bitcoin.payments[type]({
|
||||
payment = (bitcoin.payments as any)[type]({
|
||||
pubkey: keys[0].publicKey,
|
||||
network,
|
||||
});
|
||||
|
@ -615,13 +628,18 @@ function createPayment(_type, myKeys, network) {
|
|||
};
|
||||
}
|
||||
|
||||
function getWitnessUtxo(out) {
|
||||
function getWitnessUtxo(out: any) {
|
||||
delete out.address;
|
||||
out.script = Buffer.from(out.script, 'hex');
|
||||
return out;
|
||||
}
|
||||
|
||||
async function getInputData(amount, payment, isSegwit, redeemType) {
|
||||
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
|
||||
|
@ -629,7 +647,7 @@ async function getInputData(amount, payment, isSegwit, redeemType) {
|
|||
// 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 = {};
|
||||
const mixin2: any = {};
|
||||
switch (redeemType) {
|
||||
case 'p2sh':
|
||||
mixin2.redeemScript = payment.redeem.output;
|
||||
|
|
|
@ -1,361 +1,421 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bitcoin = require('../../')
|
||||
const regtestUtils = require('./_regtest')
|
||||
const regtest = regtestUtils.network
|
||||
console.warn = () => {} // Silence the Deprecation Warning
|
||||
import * as assert from 'assert';
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
const regtest = regtestUtils.network;
|
||||
console.warn = () => {}; // Silence the Deprecation Warning
|
||||
|
||||
function rng () {
|
||||
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64')
|
||||
function rng() {
|
||||
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64');
|
||||
}
|
||||
|
||||
describe('bitcoinjs-lib (transactions)', () => {
|
||||
it('can create a 1-to-1 Transaction', () => {
|
||||
const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
|
||||
const txb = new bitcoin.TransactionBuilder()
|
||||
const alice = bitcoin.ECPair.fromWIF(
|
||||
'L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy',
|
||||
);
|
||||
const txb = new bitcoin.TransactionBuilder();
|
||||
|
||||
txb.setVersion(1)
|
||||
txb.addInput('61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', 0) // Alice's previous transaction output, has 15000 satoshis
|
||||
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000)
|
||||
txb.setVersion(1);
|
||||
txb.addInput(
|
||||
'61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d',
|
||||
0,
|
||||
); // Alice's previous transaction output, has 15000 satoshis
|
||||
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000);
|
||||
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
|
||||
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2pkh',
|
||||
vin: 0,
|
||||
keyPair: alice
|
||||
})
|
||||
keyPair: alice,
|
||||
});
|
||||
|
||||
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
|
||||
assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000')
|
||||
})
|
||||
assert.strictEqual(
|
||||
txb.build().toHex(),
|
||||
'01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create a 2-to-2 Transaction', () => {
|
||||
const alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1')
|
||||
const bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z')
|
||||
const alice = bitcoin.ECPair.fromWIF(
|
||||
'L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1',
|
||||
);
|
||||
const bob = bitcoin.ECPair.fromWIF(
|
||||
'KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z',
|
||||
);
|
||||
|
||||
const txb = new bitcoin.TransactionBuilder()
|
||||
txb.setVersion(1)
|
||||
txb.addInput('b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', 6) // Alice's previous transaction output, has 200000 satoshis
|
||||
txb.addInput('7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', 0) // Bob's previous transaction output, has 300000 satoshis
|
||||
txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000)
|
||||
txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000)
|
||||
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)
|
||||
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)
|
||||
keyPair: alice,
|
||||
}); // Alice signs her input, which was the first input (0th)
|
||||
|
||||
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
|
||||
assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000')
|
||||
})
|
||||
assert.strictEqual(
|
||||
txb.build().toHex(),
|
||||
'01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
|
||||
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
const aliceChange = bitcoin.ECPair.makeRandom({ network: regtest, rng: rng })
|
||||
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest });
|
||||
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest });
|
||||
const aliceChange = bitcoin.ECPair.makeRandom({
|
||||
network: regtest,
|
||||
rng: rng,
|
||||
});
|
||||
|
||||
const alice1pkh = bitcoin.payments.p2pkh({ pubkey: alice1.publicKey, network: regtest })
|
||||
const alice2pkh = bitcoin.payments.p2pkh({ pubkey: alice2.publicKey, network: regtest })
|
||||
const aliceCpkh = bitcoin.payments.p2pkh({ pubkey: aliceChange.publicKey, network: regtest })
|
||||
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
|
||||
const unspent0 = await regtestUtils.faucet(alice1pkh.address, 5e4)
|
||||
const unspent0 = await regtestUtils.faucet(alice1pkh.address!, 5e4);
|
||||
|
||||
const unspent1 = await regtestUtils.faucet(alice2pkh.address, 7e4)
|
||||
const unspent1 = await regtestUtils.faucet(alice2pkh.address!, 7e4);
|
||||
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.addInput(unspent0.txId, unspent0.vout) // alice1 unspent
|
||||
txb.addInput(unspent1.txId, unspent1.vout) // alice2 unspent
|
||||
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend"
|
||||
txb.addOutput(aliceCpkh.address, 1e4) // Alice's change
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent0.txId, unspent0.vout); // alice1 unspent
|
||||
txb.addInput(unspent1.txId, unspent1.vout); // alice2 unspent
|
||||
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4); // the actual "spend"
|
||||
txb.addOutput(aliceCpkh.address!, 1e4); // Alice's change
|
||||
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
|
||||
|
||||
// Alice signs each input with the respective private keys
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2pkh',
|
||||
vin: 0,
|
||||
keyPair: alice1
|
||||
})
|
||||
keyPair: alice1,
|
||||
});
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2pkh',
|
||||
vin: 1,
|
||||
keyPair: alice2
|
||||
})
|
||||
keyPair: alice2,
|
||||
});
|
||||
|
||||
// build and broadcast our RegTest network
|
||||
await regtestUtils.broadcast(txb.build().toHex())
|
||||
await regtestUtils.broadcast(txb.build().toHex());
|
||||
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
|
||||
})
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => {
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: regtest })
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
||||
const p2pkh = bitcoin.payments.p2pkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
const unspent = await regtestUtils.faucet(p2pkh.address, 2e5)
|
||||
const unspent = await regtestUtils.faucet(p2pkh.address!, 2e5);
|
||||
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
const data = Buffer.from('bitcoinjs-lib', 'utf8')
|
||||
const embed = bitcoin.payments.embed({ data: [data] })
|
||||
txb.addInput(unspent.txId, unspent.vout)
|
||||
txb.addOutput(embed.output, 1000)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
const data = Buffer.from('bitcoinjs-lib', 'utf8');
|
||||
const embed = bitcoin.payments.embed({ data: [data] });
|
||||
txb.addInput(unspent.txId, unspent.vout);
|
||||
txb.addOutput(embed.output!, 1000);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5);
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2pkh',
|
||||
vin: 0,
|
||||
keyPair,
|
||||
})
|
||||
});
|
||||
|
||||
// build and broadcast to the RegTest network
|
||||
await regtestUtils.broadcast(txb.build().toHex())
|
||||
})
|
||||
await regtestUtils.broadcast(txb.build().toHex());
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => {
|
||||
const keyPairs = [
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
]
|
||||
const pubkeys = keyPairs.map(x => x.publicKey)
|
||||
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest })
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest })
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
];
|
||||
const pubkeys = keyPairs.map(x => x.publicKey);
|
||||
const p2ms = bitcoin.payments.p2ms({
|
||||
m: 2,
|
||||
pubkeys: pubkeys,
|
||||
network: regtest,
|
||||
});
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest });
|
||||
|
||||
const unspent = await regtestUtils.faucet(p2sh.address, 2e4)
|
||||
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4);
|
||||
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.addInput(unspent.txId, unspent.vout)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
|
||||
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2sh-p2ms',
|
||||
vin: 0,
|
||||
keyPair: keyPairs[0],
|
||||
redeemScript: p2sh.redeem.output,
|
||||
})
|
||||
redeemScript: p2sh.redeem!.output,
|
||||
});
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2sh-p2ms',
|
||||
vin: 0,
|
||||
keyPair: keyPairs[2],
|
||||
redeemScript: p2sh.redeem.output,
|
||||
})
|
||||
const tx = txb.build()
|
||||
redeemScript: p2sh.redeem!.output,
|
||||
});
|
||||
const tx = txb.build();
|
||||
|
||||
// build and broadcast to the Bitcoin RegTest network
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 1e4
|
||||
})
|
||||
})
|
||||
value: 1e4,
|
||||
});
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => {
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest })
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
||||
const p2wpkh = bitcoin.payments.p2wpkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
network: regtest,
|
||||
});
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest });
|
||||
|
||||
const unspent = await regtestUtils.faucet(p2sh.address, 5e4)
|
||||
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)
|
||||
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,
|
||||
redeemScript: p2sh.redeem!.output,
|
||||
witnessValue: unspent.value,
|
||||
})
|
||||
});
|
||||
|
||||
const tx = txb.build()
|
||||
const tx = txb.build();
|
||||
|
||||
// build and broadcast to the Bitcoin RegTest network
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 2e4
|
||||
})
|
||||
})
|
||||
value: 2e4,
|
||||
});
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
||||
const p2wpkh = bitcoin.payments.p2wpkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
network: regtest,
|
||||
});
|
||||
|
||||
const unspent = await regtestUtils.faucetComplex(p2wpkh.output, 5e4)
|
||||
const unspent = await regtestUtils.faucetComplex(p2wpkh.output!, 5e4);
|
||||
|
||||
// XXX: build the Transaction w/ a P2WPKH input
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript!
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, undefined, p2wpkh.output); // NOTE: provide the prevOutScript!
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2wpkh',
|
||||
vin: 0,
|
||||
keyPair: keyPair,
|
||||
witnessValue: unspent.value,
|
||||
}) // NOTE: no redeem script
|
||||
const tx = txb.build()
|
||||
}); // NOTE: no redeem script
|
||||
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({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 2e4
|
||||
})
|
||||
})
|
||||
value: 2e4,
|
||||
});
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
const p2pk = bitcoin.payments.p2pk({ pubkey: keyPair.publicKey, network: regtest })
|
||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest })
|
||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
|
||||
const p2pk = bitcoin.payments.p2pk({
|
||||
pubkey: keyPair.publicKey,
|
||||
network: regtest,
|
||||
});
|
||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest });
|
||||
|
||||
const unspent = await regtestUtils.faucetComplex(p2wsh.output, 5e4)
|
||||
const unspent = await regtestUtils.faucetComplex(p2wsh.output!, 5e4);
|
||||
|
||||
// XXX: build the Transaction w/ a P2WSH input
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript!
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, undefined, p2wsh.output); // NOTE: provide the prevOutScript!
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2wsh-p2pk',
|
||||
vin: 0,
|
||||
keyPair: keyPair,
|
||||
witnessValue: 5e4,
|
||||
witnessScript: p2wsh.redeem.output,
|
||||
}) // NOTE: provide a witnessScript!
|
||||
const tx = txb.build()
|
||||
witnessScript: p2wsh.redeem!.output,
|
||||
}); // NOTE: provide a witnessScript!
|
||||
const tx = txb.build();
|
||||
|
||||
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 2e4
|
||||
})
|
||||
})
|
||||
value: 2e4,
|
||||
});
|
||||
});
|
||||
|
||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
|
||||
const keyPairs = [
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
bitcoin.ECPair.makeRandom({ network: regtest })
|
||||
]
|
||||
const pubkeys = keyPairs.map(x => x.publicKey)
|
||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||
];
|
||||
const pubkeys = keyPairs.map(x => x.publicKey);
|
||||
|
||||
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest })
|
||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest })
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest })
|
||||
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest });
|
||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest });
|
||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest });
|
||||
|
||||
const unspent = await regtestUtils.faucet(p2sh.address, 6e4)
|
||||
const unspent = await regtestUtils.faucet(p2sh.address!, 6e4);
|
||||
|
||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||
txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
|
||||
const txb = new bitcoin.TransactionBuilder(regtest);
|
||||
txb.addInput(unspent.txId, unspent.vout, undefined, p2sh.output);
|
||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4);
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2sh-p2wsh-p2ms',
|
||||
vin: 0,
|
||||
keyPair: keyPairs[0],
|
||||
redeemScript: p2sh.redeem.output,
|
||||
redeemScript: p2sh.redeem!.output,
|
||||
witnessValue: unspent.value,
|
||||
witnessScript: p2wsh.redeem.output,
|
||||
})
|
||||
witnessScript: p2wsh.redeem!.output,
|
||||
});
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2sh-p2wsh-p2ms',
|
||||
vin: 0,
|
||||
keyPair: keyPairs[2],
|
||||
redeemScript: p2sh.redeem.output,
|
||||
redeemScript: p2sh.redeem!.output,
|
||||
witnessValue: unspent.value,
|
||||
witnessScript: p2wsh.redeem.output,
|
||||
})
|
||||
witnessScript: p2wsh.redeem!.output,
|
||||
});
|
||||
txb.sign({
|
||||
prevOutScriptType: 'p2sh-p2wsh-p2ms',
|
||||
vin: 0,
|
||||
keyPair: keyPairs[3],
|
||||
redeemScript: p2sh.redeem.output,
|
||||
redeemScript: p2sh.redeem!.output,
|
||||
witnessValue: unspent.value,
|
||||
witnessScript: p2wsh.redeem.output,
|
||||
})
|
||||
witnessScript: p2wsh.redeem!.output,
|
||||
});
|
||||
|
||||
const tx = txb.build()
|
||||
const tx = txb.build();
|
||||
|
||||
// build and broadcast to the Bitcoin RegTest network
|
||||
await regtestUtils.broadcast(tx.toHex())
|
||||
await regtestUtils.broadcast(tx.toHex());
|
||||
|
||||
await regtestUtils.verify({
|
||||
txId: tx.getId(),
|
||||
address: regtestUtils.RANDOM_ADDRESS,
|
||||
vout: 0,
|
||||
value: 3e4
|
||||
})
|
||||
})
|
||||
value: 3e4,
|
||||
});
|
||||
});
|
||||
|
||||
it('can verify Transaction (P2PKH) signatures', () => {
|
||||
const txHex = '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'
|
||||
const txHex =
|
||||
'010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700';
|
||||
const keyPairs = [
|
||||
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d',
|
||||
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a',
|
||||
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f'
|
||||
].map(q => { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) })
|
||||
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f',
|
||||
].map(q => {
|
||||
return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex'));
|
||||
});
|
||||
|
||||
const tx = bitcoin.Transaction.fromHex(txHex)
|
||||
const tx = bitcoin.Transaction.fromHex(txHex);
|
||||
|
||||
tx.ins.forEach((input, i) => {
|
||||
const keyPair = keyPairs[i]
|
||||
const keyPair = keyPairs[i];
|
||||
const p2pkh = bitcoin.payments.p2pkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
input: input.script
|
||||
})
|
||||
input: input.script,
|
||||
});
|
||||
|
||||
const ss = bitcoin.script.signature.decode(p2pkh.signature)
|
||||
const hash = tx.hashForSignature(i, p2pkh.output, ss.hashType)
|
||||
const ss = bitcoin.script.signature.decode(p2pkh.signature!);
|
||||
const hash = tx.hashForSignature(i, p2pkh.output!, ss.hashType);
|
||||
|
||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
|
||||
})
|
||||
})
|
||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true);
|
||||
});
|
||||
});
|
||||
|
||||
it('can verify Transaction (P2SH(P2WPKH)) signatures', () => {
|
||||
const utxos = {
|
||||
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': {
|
||||
value: 50000
|
||||
}
|
||||
}
|
||||
value: 50000,
|
||||
},
|
||||
};
|
||||
|
||||
const txHex = '02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'
|
||||
const tx = bitcoin.Transaction.fromHex(txHex)
|
||||
const txHex =
|
||||
'02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000';
|
||||
const tx = bitcoin.Transaction.fromHex(txHex);
|
||||
|
||||
tx.ins.forEach((input, i) => {
|
||||
const txId = Buffer.from(input.hash).reverse().toString('hex')
|
||||
const utxo = utxos[`${txId}:${i}`]
|
||||
if (!utxo) throw new Error('Missing utxo')
|
||||
const txId = (Buffer.from(input.hash).reverse() as Buffer).toString(
|
||||
'hex',
|
||||
);
|
||||
const utxo = (utxos as any)[`${txId}:${i}`];
|
||||
if (!utxo) throw new Error('Missing utxo');
|
||||
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
input: input.script,
|
||||
witness: input.witness
|
||||
})
|
||||
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem)
|
||||
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }) // because P2WPKH is annoying
|
||||
witness: input.witness,
|
||||
});
|
||||
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem!);
|
||||
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }); // because P2WPKH is annoying
|
||||
|
||||
const ss = bitcoin.script.signature.decode(p2wpkh.signature)
|
||||
const hash = tx.hashForWitnessV0(i, p2pkh.output, utxo.value, ss.hashType)
|
||||
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey) // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
|
||||
const ss = bitcoin.script.signature.decode(p2wpkh.signature!);
|
||||
const hash = tx.hashForWitnessV0(
|
||||
i,
|
||||
p2pkh.output!,
|
||||
utxo.value,
|
||||
ss.hashType,
|
||||
);
|
||||
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey!); // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
|
||||
|
||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
|
||||
})
|
||||
})
|
||||
})
|
||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue