Merge branch 'master' into addComplexScript

This commit is contained in:
junderw 2019-05-21 16:11:45 +09:00
commit 9f96fd097b
No known key found for this signature in database
GPG key ID: B256185D3A971908
179 changed files with 11314 additions and 4583 deletions

View file

@ -1,99 +1,129 @@
const assert = require('assert')
const bitcoin = require('../../')
const dhttp = require('dhttp/200')
const dhttpCallback = require('dhttp/200')
// use Promises
const dhttp = options => new Promise((resolve, reject) => {
return dhttpCallback(options, (err, data) => {
if (err) return reject(err)
else return resolve(data)
})
})
const APIPASS = process.env.APIPASS || 'satoshi'
const APIURL = 'https://api.dcousens.cloud/1'
const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1'
const NETWORK = bitcoin.networks.testnet
function broadcast (txHex, callback) {
dhttp({
method: 'PUT',
function broadcast (txHex) {
return dhttp({
method: 'POST',
url: APIURL + '/t/push',
body: txHex
}, callback)
}
function mine (count, callback) {
dhttp({
method: 'POST',
url: APIURL + '/r/generate?count=' + count + '&key=' + APIPASS
}, callback)
}
function height (callback) {
dhttp({
method: 'GET',
url: APIURL + '/b/best/height'
}, callback)
}
function faucet (address, value, callback) {
dhttp({
method: 'POST',
url: APIURL + '/r/faucet?address=' + address + '&value=' + value + '&key=' + APIPASS
}, function (err, txId) {
if (err) return callback(err)
unspents(address, function (err, results) {
if (err) return callback(err)
const unspents = results.filter(x => x.txId === txId)
if (unspents.length === 0) return callback(new Error('Missing unspent'))
callback(null, unspents.pop())
})
})
}
function faucetComplex (output, value, callback) {
function mine (count) {
return dhttp({
method: 'POST',
url: APIURL + '/r/generate?count=' + count + '&key=' + APIPASS
})
}
function height () {
return dhttp({
method: 'GET',
url: APIURL + '/b/best/height'
})
}
function _faucetRequest (address, value) {
return dhttp({
method: 'POST',
url: APIURL + '/r/faucet?address=' + address + '&value=' + value + '&key=' + APIPASS
})
}
async function faucet (address, value) {
let count = 0
let _unspents = []
const sleep = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))
const randInt = (min, max) => min + Math.floor((max - min + 1) * Math.random())
while (_unspents.length === 0) {
if (count > 0) {
if (count >= 5) throw new Error('Missing Inputs')
console.log('Missing Inputs, retry #' + count)
await sleep(randInt(150, 250))
}
const txId = await _faucetRequest(address, value)
.then(
v => v, // Pass success value as is
async err => {
// Bad Request error is fixed by making sure height is >= 432
const currentHeight = await height()
if (err.message === 'Bad Request' && currentHeight < 432) {
await mine(432 - currentHeight)
return _faucetRequest(address, value)
} else if (err.message === 'Bad Request' && currentHeight >= 432) {
return _faucetRequest(address, value)
} else {
throw err
}
}
)
await sleep(randInt(10, 40))
const results = await unspents(address)
_unspents = results.filter(x => x.txId === txId)
count++
}
return _unspents.pop()
}
async function faucetComplex (output, value) {
const keyPair = bitcoin.ECPair.makeRandom({ network: NETWORK })
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: NETWORK })
faucet(p2pkh.address, value * 2, (err, unspent) => {
if (err) return callback(err)
const unspent = await faucet(p2pkh.address, value * 2)
const txvb = new bitcoin.TransactionBuilder(NETWORK)
txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output)
txvb.addOutput(output, value)
txvb.sign(0, keyPair)
const txv = txvb.build()
const txvb = new bitcoin.TransactionBuilder(NETWORK)
txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output)
txvb.addOutput(output, value)
txvb.sign(0, keyPair)
const txv = txvb.build()
broadcast(txv.toHex(), function (err) {
if (err) return callback(err)
await broadcast(txv.toHex())
return callback(null, {
txId: txv.getId(),
vout: 0,
value
})
})
})
return {
txId: txv.getId(),
vout: 0,
value
}
}
function fetch (txId, callback) {
dhttp({
function fetch (txId) {
return dhttp({
method: 'GET',
url: APIURL + '/t/' + txId + '/json'
}, callback)
})
}
function unspents (address, callback) {
dhttp({
function unspents (address) {
return dhttp({
method: 'GET',
url: APIURL + '/a/' + address + '/unspents'
}, callback)
})
}
function verify (txo, callback) {
fetch(txo.txId, function (err, tx) {
if (err) return callback(err)
async function verify (txo) {
const tx = await fetch(txo.txId)
const txoActual = tx.outs[txo.vout]
if (txo.address) assert.strictEqual(txoActual.address, txo.address)
if (txo.value) assert.strictEqual(txoActual.value, txo.value)
callback()
})
const txoActual = tx.outs[txo.vout]
if (txo.address) assert.strictEqual(txoActual.address, txo.address)
if (txo.value) assert.strictEqual(txoActual.value, txo.value)
}
function getAddress (node, network) {
@ -108,6 +138,7 @@ function randomAddress () {
module.exports = {
broadcast,
dhttp,
faucet,
faucetComplex,
fetch,

View file

@ -1,50 +1,36 @@
/* global describe, it */
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const dhttp = require('dhttp/200')
const dhttp = require('./_regtest').dhttp
const TESTNET = bitcoin.networks.testnet
const LITECOIN = {
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0
}
// deterministic RNG for testing only
function rng () { return Buffer.from('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz') }
describe('bitcoinjs-lib (addresses)', function () {
it('can generate a random address', function () {
const keyPair = bitcoin.ECPair.makeRandom({ rng: rng })
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 })
assert.strictEqual(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64')
// bitcoin P2PKH addresses start with a '1'
assert.strictEqual(address.startsWith('1'), true)
const result = await dhttp({
method: 'GET',
url: 'https://blockchain.info/rawaddr/' + address
})
// 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)
})
it('can generate an address from a SHA256 hash', function () {
const hash = bitcoin.crypto.sha256(Buffer.from('correct horse battery staple'))
const keyPair = bitcoin.ECPair.fromPrivateKey(hash)
it('can import an address via WIF', () => {
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
// Generating addresses from SHA256 hashes is not secure if the input to the hash function is predictable
// Do not use with predictable inputs
assert.strictEqual(address, '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8')
assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH')
})
it('can import an address via WIF', function () {
const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
assert.strictEqual(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31')
})
it('can generate a P2SH, pay-to-multisig (2-of-3) address', function () {
it('can generate a P2SH, pay-to-multisig (2-of-3) address', () => {
const pubkeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
@ -57,23 +43,23 @@ describe('bitcoinjs-lib (addresses)', function () {
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
})
it('can generate a SegWit address', function () {
const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
it('can generate a SegWit address', () => {
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
assert.strictEqual(address, 'bc1qt97wqg464zrhnx23upykca5annqvwkwujjglky')
assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4')
})
it('can generate a SegWit address (via P2SH)', function () {
const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
it('can generate a SegWit address (via P2SH)', () => {
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
})
assert.strictEqual(address, '34AgLJhwXrvmkZS1o5TrcdeevMt22Nar53')
assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN')
})
it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', function () {
it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', () => {
const pubkeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
@ -87,7 +73,7 @@ describe('bitcoinjs-lib (addresses)', function () {
assert.strictEqual(address, 'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul')
})
it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', function () {
it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', () => {
const pubkeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9'
@ -101,41 +87,31 @@ describe('bitcoinjs-lib (addresses)', function () {
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN')
})
it('can support the retrieval of transactions for an address (via 3PBP)', function (done) {
const keyPair = bitcoin.ECPair.makeRandom()
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
// 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 })
dhttp({
method: 'GET',
url: 'https://blockchain.info/rawaddr/' + address
}, function (err, result) {
if (err) return done(err)
// 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)
done()
})
// bitcoin testnet P2PKH addresses start with a 'm' or 'n'
assert.strictEqual(address.startsWith('m') || address.startsWith('n'), true)
})
// other networks
it('can generate a Testnet address', function () {
const testnet = bitcoin.networks.testnet
const keyPair = bitcoin.ECPair.makeRandom({ network: testnet, rng: rng })
const wif = keyPair.toWIF()
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: testnet })
it('can generate a Litecoin address', () => {
// WARNING: although possible, bitcoinjs is NOT necessarily compatible with Litecoin
const LITECOIN = {
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0
}
assert.strictEqual(address, 'mubSzQNtZfDj1YdNP6pNDuZy6zs6GDn61L')
assert.strictEqual(wif, 'cRgnQe9MUu1JznntrLaoQpB476M8PURvXVQB5R2eqms5tXnzNsrr')
})
it('can generate a Litecoin address', function () {
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN, rng: rng })
const wif = keyPair.toWIF()
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN })
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN })
assert.strictEqual(address, 'LZJSxZbjqJ2XVEquqfqHg1RQTDdfST5PTn')
assert.strictEqual(wif, 'T7A4PUSgTDHecBxW1ZiYFrDNRih2o7M8Gf9xpoCgudPF9gDiNvuS')
assert.strictEqual(address.startsWith('L'), true)
})
})

View file

@ -1,5 +1,4 @@
/* global describe, it */
const { describe, it } = require('mocha')
const assert = require('assert')
const bip32 = require('bip32')
const bip39 = require('bip39')
@ -9,35 +8,35 @@ function getAddress (node, network) {
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
}
describe('bitcoinjs-lib (BIP32)', function () {
it('can import a BIP32 testnet xpriv and export to WIF', function () {
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)
assert.equal(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7')
assert.strictEqual(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7')
})
it('can export a BIP32 xpriv, then import it', function () {
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)
assert.equal(getAddress(node), getAddress(restored)) // same public key
assert.equal(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', function () {
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()
assert.equal(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n')
assert.strictEqual(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n')
})
it('can create a BIP32, bitcoin, account 0, external address', function () {
it('can create a BIP32, bitcoin, account 0, external address', () => {
const path = "m/0'/0/0"
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
@ -48,11 +47,11 @@ describe('bitcoinjs-lib (BIP32)', function () {
.derive(0)
.derive(0)
assert.equal(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
assert.equal(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
assert.strictEqual(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
assert.strictEqual(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
})
it('can create a BIP44, bitcoin, account 0, external address', function () {
it('can create a BIP44, bitcoin, account 0, external address', () => {
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
const child1 = root.derivePath("m/44'/0'/0'/0/0")
@ -64,11 +63,11 @@ describe('bitcoinjs-lib (BIP32)', function () {
.derive(0)
.derive(0)
assert.equal(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
assert.equal(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
assert.strictEqual(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
assert.strictEqual(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
})
it('can create a BIP49, bitcoin testnet, account 0, external address', function () {
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)
@ -80,10 +79,10 @@ describe('bitcoinjs-lib (BIP32)', function () {
redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }),
network: bitcoin.networks.testnet
})
assert.equal(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
})
it('can use BIP39 to generate BIP32 addresses', function () {
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))

View file

@ -1,11 +1,11 @@
/* global describe, it */
'use strict'
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
describe('bitcoinjs-lib (blocks)', function () {
it('can extract a height from a CoinBase transaction', function () {
describe('bitcoinjs-lib (blocks)', () => {
it('can extract a height from a CoinBase transaction', () => {
// from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6
const txHex = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000'
const tx = bitcoin.Transaction.fromHex(txHex)

View file

@ -1,5 +1,4 @@
/* global describe, it, before */
const { describe, it, before } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
@ -9,10 +8,10 @@ const bip65 = require('bip65')
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// force update MTP
before(function (done) {
regtestUtils.mine(11, done)
before(async () => {
await regtestUtils.mine(11)
})
const hashType = bitcoin.Transaction.SIGHASH_ALL
@ -39,184 +38,160 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
}
// 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)', function (done) {
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', async () => {
// 3 hours ago
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
// fund the P2SH(CLTV) address
regtestUtils.faucet(address, 1e5, function (err, unspent) {
if (err) return done(err)
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)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
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 redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
await regtestUtils.broadcast(tx.toHex())
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
}, done)
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
// expiry will pass, {Alice's signature} OP_TRUE
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', function (done) {
regtestUtils.height(function (err, height) {
if (err) return done(err)
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', async () => {
const height = await regtestUtils.height()
// 5 blocks from now
const lockTime = bip65.encode({ blocks: height + 5 })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
// 5 blocks from now
const lockTime = bip65.encode({ blocks: height + 5 })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
// fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
// fund the P2SH(CLTV) address
regtestUtils.faucet(address, 1e5, function (err, unspent) {
if (err) return done(err)
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
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 redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
regtestUtils.mine(5, function (err) {
if (err) return done(err)
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
}, done)
})
})
})
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
await regtestUtils.mine(5)
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
// expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE
it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', function (done) {
it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', async () => {
// two hours ago
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
// fund the P2SH(CLTV) address
regtestUtils.faucet(address, 2e5, function (err, unspent) {
if (err) return done(err)
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)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
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 redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_FALSE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
// {Alice's signature} {Bob's signature} OP_FALSE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_FALSE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 8e4
}, done)
})
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 8e4
})
})
// expiry in the future, {Alice's signature} OP_TRUE
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', function (done) {
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', async () => {
// two hours from now
const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
// fund the P2SH(CLTV) address
regtestUtils.faucet(address, 2e4, function (err, unspent) {
if (err) return done(err)
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)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
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 redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
regtestUtils.broadcast(tx.toHex(), function (err) {
assert.throws(function () {
if (err) throw err
}, /Error: 64: non-final/)
done()
})
await regtestUtils.broadcast(tx.toHex()).catch(err => {
assert.throws(() => {
if (err) throw err
}, /Error: non-final \(code 64\)/)
})
})
})

View file

@ -1,104 +0,0 @@
/* global describe, it */
const assert = require('assert')
const BN = require('bn.js')
const bitcoin = require('../../')
const bip32 = require('bip32')
const crypto = require('crypto')
const tinysecp = require('tiny-secp256k1')
describe('bitcoinjs-lib (crypto)', function () {
it('can recover a private key from duplicate R values', function () {
// https://blockchain.info/tx/f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50
const tx = bitcoin.Transaction.fromHex('01000000020b668015b32a6178d8524cfef6dc6fc0a4751915c2e9b2ed2d2eab02424341c8000000006a47304402205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022024bf5f506968f5f23f1835574d5afe0e9021b4a5b65cf9742332d5e4acb68f41012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffffa95fa69f11dc1cbb77ef64f25a95d4b12ebda57d19d843333819d95c9172ff89000000006b48304502205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022100832176b59e8f50c56631acbc824bcba936c9476c559c42a4468be98975d07562012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffff02b000eb04000000001976a91472956eed9a8ecb19ae7e3ebd7b06cae4668696a788ac303db000000000001976a9146c0bd55dd2592287cd9992ce3ba3fc1208fb76da88ac00000000')
tx.ins.forEach(function (input, vin) {
const { output: prevOutput, pubkey, signature } = bitcoin.payments.p2pkh({ input: input.script })
const scriptSignature = bitcoin.script.signature.decode(signature)
const m = tx.hashForSignature(vin, prevOutput, scriptSignature.hashType)
assert(bitcoin.ECPair.fromPublicKey(pubkey).verify(m, scriptSignature.signature), 'Invalid m')
// store the required information
input.signature = scriptSignature.signature
input.z = new BN(m)
})
const n = new BN('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16)
for (var i = 0; i < tx.ins.length; ++i) {
for (var j = i + 1; j < tx.ins.length; ++j) {
const inputA = tx.ins[i]
const inputB = tx.ins[j]
// enforce matching r values
const r = inputA.signature.slice(0, 32)
const rB = inputB.signature.slice(0, 32)
assert.strictEqual(r.toString('hex'), rB.toString('hex'))
const rInv = new BN(r).invm(n)
const s1 = new BN(inputA.signature.slice(32, 64))
const s2 = new BN(inputB.signature.slice(32, 64))
const z1 = inputA.z
const z2 = inputB.z
const zz = z1.sub(z2).mod(n)
const ss = s1.sub(s2).mod(n)
// k = (z1 - z2) / (s1 - s2)
// d1 = (s1 * k - z1) / r
// d2 = (s2 * k - z2) / r
const k = zz.mul(ss.invm(n)).mod(n)
const d1 = ((s1.mul(k).mod(n)).sub(z1).mod(n)).mul(rInv).mod(n)
const d2 = ((s2.mul(k).mod(n)).sub(z2).mod(n)).mul(rInv).mod(n)
// enforce matching private keys
assert.strictEqual(d1.toString(), d2.toString())
}
}
})
it('can recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key', function () {
function recoverParent (master, child) {
assert(master.isNeutered(), 'You already have the parent private key')
assert(!child.isNeutered(), 'Missing child private key')
const serQP = master.publicKey
const d1 = child.privateKey
const data = Buffer.alloc(37)
serQP.copy(data, 0)
// search index space until we find it
let d2
for (var i = 0; i < 0x80000000; ++i) {
data.writeUInt32BE(i, 33)
// calculate I
const I = crypto.createHmac('sha512', master.chainCode).update(data).digest()
const IL = I.slice(0, 32)
// See bip32.js:273 to understand
d2 = tinysecp.privateSub(d1, IL)
const Qp = bip32.fromPrivateKey(d2, Buffer.alloc(32, 0)).publicKey
if (Qp.equals(serQP)) break
}
const node = bip32.fromPrivateKey(d2, master.chainCode, master.network)
node.depth = master.depth
node.index = master.index
node.masterFingerprint = master.masterFingerprint
return node
}
const seed = crypto.randomBytes(32)
const master = bip32.fromSeed(seed)
const child = master.derive(6) // m/6
// now for the recovery
const neuteredMaster = master.neutered()
const recovered = recoverParent(neuteredMaster, child)
assert.strictEqual(recovered.toBase58(), master.toBase58())
})
})

View file

@ -1,5 +1,4 @@
/* global describe, it, before */
const { describe, it, before } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
@ -11,10 +10,10 @@ const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZs
const charles = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest)
const dave = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest)
describe('bitcoinjs-lib (transactions w/ CSV)', function () {
describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// force update MTP
before(function (done) {
regtestUtils.mine(11, done)
before(async () => {
await regtestUtils.mine(11)
})
const hashType = bitcoin.Transaction.SIGHASH_ALL
@ -70,66 +69,56 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () {
}
// 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)', function (done) {
regtestUtils.height(function (err, height) {
if (err) return done(err)
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', async () => {
// 5 blocks from now
const sequence = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: csvCheckSigOutput(alice, bob, sequence)
},
network: regtest
})
// 5 blocks from now
const sequence = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: csvCheckSigOutput(alice, bob, sequence)
},
network: regtest
})
// fund the P2SH(CSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
// fund the P2SH(CSV) address
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
if (err) return done(err)
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 redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
await regtestUtils.mine(10)
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
regtestUtils.mine(10, function (err) {
if (err) return done(err)
await regtestUtils.broadcast(tx.toHex())
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
}, done)
})
})
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
// expiry in the future, {Alice's signature} OP_TRUE
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', function (done) {
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', async () => {
// two hours after confirmation
const sequence = bip68.encode({ seconds: 7168 })
const p2sh = bitcoin.payments.p2sh({
@ -140,215 +129,189 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () {
})
// fund the P2SH(CSV) address
regtestUtils.faucet(p2sh.address, 2e4, function (err, unspent) {
if (err) return done(err)
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 redeemScriptSig = bitcoin.payments.p2sh({
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
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)
regtestUtils.broadcast(tx.toHex(), function (err) {
assert.throws(function () {
if (err) throw err
}, /Error: 64: non-BIP68-final/)
done()
})
await regtestUtils.broadcast(tx.toHex()).catch(err => {
assert.throws(() => {
if (err) throw err
}, /Error: non-BIP68-final \(code 64\)/)
})
})
// Check first combination of complex CSV, 2 of 3
it('can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)', function (done) {
regtestUtils.height(function (err, height) {
if (err) return done(err)
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 })
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
},
network: regtest
})
// 2 blocks from now
const sequence1 = bip68.encode({ blocks: 2 })
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
},
network: regtest
})
// fund the P2SH(CCSV) address
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
if (err) return done(err)
// fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.script.signature.encode(charles.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE,
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.script.signature.encode(charles.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE,
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
}, done)
})
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
// Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', function (done) {
regtestUtils.height(function (err, height) {
if (err) return done(err)
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 })
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
},
network: regtest
})
// 2 blocks from now
const sequence1 = bip68.encode({ blocks: 2 })
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
},
network: regtest
})
// fund the P2SH(CCSV) address
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
if (err) return done(err)
// fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_0,
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_0,
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// Wait 2 blocks
regtestUtils.mine(2, function (err) {
if (err) return done(err)
// Wait 2 blocks
await regtestUtils.mine(2)
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
}, done)
})
})
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
// Check first combination of complex CSV, mediator after 5 blocks
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', function (done) {
regtestUtils.height(function (err, height) {
if (err) return done(err)
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 })
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
},
network: regtest
})
// 2 blocks from now
const sequence1 = bip68.encode({ blocks: 2 })
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
},
network: regtest
})
// fund the P2SH(CCSV) address
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
if (err) return done(err)
// fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
// {Alice mediator sig} OP_FALSE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_0
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// {Alice mediator sig} OP_FALSE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_0
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
// Wait 5 blocks
regtestUtils.mine(5, function (err) {
if (err) return done(err)
// Wait 5 blocks
await regtestUtils.mine(5)
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
}, done)
})
})
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
})

View file

@ -1,7 +1,6 @@
/* global describe, it */
const bitcoin = require('../../')
const { describe, it } = require('mocha')
const regtestUtils = require('./_regtest')
const NETWORK = regtestUtils.network
const keyPairs = [
@ -9,27 +8,25 @@ const keyPairs = [
bitcoin.ECPair.makeRandom({ network: NETWORK })
]
function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) {
regtestUtils.faucetComplex(prevOutput, 5e4, (err, unspent) => {
if (err) return done(err)
async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
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, null, prevOutput)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
if (depends.signatures) {
keyPairs.forEach((keyPair) => {
txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript)
})
} else if (depends.signature) {
txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript)
}
if (depends.signatures) {
keyPairs.forEach(keyPair => {
txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript)
})
} else if (depends.signature) {
txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript)
}
regtestUtils.broadcast(txb.build().toHex(), done)
})
return regtestUtils.broadcast(txb.build().toHex())
}
;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach((k) => {
;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
const fixtures = require('../fixtures/' + k)
const { depends } = fixtures.dynamic
const fn = bitcoin.payments[k]
@ -42,29 +39,29 @@ function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) {
const { output } = fn(base)
if (!output) throw new TypeError('Missing output')
describe('bitcoinjs-lib (payments - ' + k + ')', function () {
it('can broadcast as an output, and be spent as an input', (done) => {
buildAndSign(depends, output, null, null, done)
describe('bitcoinjs-lib (payments - ' + k + ')', () => {
it('can broadcast as an output, and be spent as an input', async () => {
await buildAndSign(depends, output, null, null)
})
it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', (done) => {
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 })
buildAndSign(depends, p2sh.output, p2sh.redeem.output, null, done)
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, null)
})
// NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail
if (k === 'p2wpkh') return
it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', (done) => {
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 })
buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output, done)
await buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output)
})
it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', (done) => {
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 })
buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output, done)
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output)
})
})
})

View file

@ -1,168 +0,0 @@
/* global describe, it */
const assert = require('assert')
const bitcoin = require('../../')
const ecc = require('tiny-secp256k1')
function getAddress (node, network) {
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
}
// vG = (dG \+ sha256(e * dG)G)
function stealthSend (e, Q) {
const eQ = ecc.pointMultiply(Q, e, true) // shared secret
const c = bitcoin.crypto.sha256(eQ)
const Qc = ecc.pointAddScalar(Q, c)
const vG = bitcoin.ECPair.fromPublicKey(Qc)
return vG
}
// v = (d + sha256(eG * d))
function stealthReceive (d, eG) {
const eQ = ecc.pointMultiply(eG, d) // shared secret
const c = bitcoin.crypto.sha256(eQ)
const dc = ecc.privateAdd(d, c)
const v = bitcoin.ECPair.fromPrivateKey(dc)
return v
}
// d = (v - sha256(e * dG))
function stealthRecoverLeaked (v, e, Q) {
const eQ = ecc.pointMultiply(Q, e) // shared secret
const c = bitcoin.crypto.sha256(eQ)
const vc = ecc.privateSub(v, c)
const d = bitcoin.ECPair.fromPrivateKey(vc)
return d
}
// vG = (rG \+ sha256(e * dG)G)
function stealthDualSend (e, R, Q) {
const eQ = ecc.pointMultiply(Q, e) // shared secret
const c = bitcoin.crypto.sha256(eQ)
const Rc = ecc.pointAddScalar(R, c)
const vG = bitcoin.ECPair.fromPublicKey(Rc)
return vG
}
// vG = (rG \+ sha256(eG * d)G)
function stealthDualScan (d, R, eG) {
const eQ = ecc.pointMultiply(eG, d) // shared secret
const c = bitcoin.crypto.sha256(eQ)
const Rc = ecc.pointAddScalar(R, c)
const vG = bitcoin.ECPair.fromPublicKey(Rc)
return vG
}
// v = (r + sha256(eG * d))
function stealthDualReceive (d, r, eG) {
const eQ = ecc.pointMultiply(eG, d) // shared secret
const c = bitcoin.crypto.sha256(eQ)
const rc = ecc.privateAdd(r, c)
const v = bitcoin.ECPair.fromPrivateKey(rc)
return v
}
describe('bitcoinjs-lib (crypto)', function () {
it('can generate a single-key stealth address', function () {
// XXX: should be randomly generated, see next test for example
const recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient
const nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender
// ... recipient reveals public key (recipient.Q) to sender
const forSender = stealthSend(nonce.privateKey, recipient.publicKey)
assert.equal(getAddress(forSender), '1CcZWwCpACJL3AxqoDbwEt4JgDFuTHUspE')
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
// ... sender reveals nonce public key (nonce.Q) to recipient
const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey)
assert.equal(getAddress(forRecipient), '1CcZWwCpACJL3AxqoDbwEt4JgDFuTHUspE')
assert.equal(forRecipient.toWIF(), 'L1yjUN3oYyCXV3LcsBrmxCNTa62bZKWCybxVJMvqjMmmfDE8yk7n')
// sender and recipient, both derived same address
assert.equal(getAddress(forSender), getAddress(forRecipient))
})
it('can generate a single-key stealth address (randomly)', function () {
const recipient = bitcoin.ECPair.makeRandom() // private to recipient
const nonce = bitcoin.ECPair.makeRandom() // private to sender
// ... recipient reveals public key (recipient.Q) to sender
const forSender = stealthSend(nonce.privateKey, recipient.publicKey)
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
// ... sender reveals nonce public key (nonce.Q) to recipient
const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey)
assert.doesNotThrow(function () { forRecipient.toWIF() })
// sender and recipient, both derived same address
assert.equal(getAddress(forSender), getAddress(forRecipient))
})
it('can recover parent recipient.d, if a derived private key is leaked [and nonce was revealed]', function () {
const recipient = bitcoin.ECPair.makeRandom() // private to recipient
const nonce = bitcoin.ECPair.makeRandom() // private to sender
// ... recipient reveals public key (recipient.Q) to sender
const forSender = stealthSend(nonce.privateKey, recipient.publicKey)
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
// ... sender reveals nonce public key (nonce.Q) to recipient
const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey)
assert.doesNotThrow(function () { forRecipient.toWIF() })
// ... recipient accidentally leaks forRecipient.d on the blockchain
const leaked = stealthRecoverLeaked(forRecipient.privateKey, nonce.privateKey, recipient.publicKey)
assert.equal(leaked.toWIF(), recipient.toWIF())
})
it('can generate a dual-key stealth address', function () {
// XXX: should be randomly generated, see next test for example
const recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient
const scan = bitcoin.ECPair.fromWIF('L5DkCk3xLLoGKncqKsWQTdaPSR4V8gzc14WVghysQGkdryRudjBM') // private to scanner/recipient
const nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender
// ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender
const forSender = stealthDualSend(nonce.privateKey, recipient.publicKey, scan.publicKey)
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
// ... sender reveals nonce public key (nonce.Q) to scanner
const forScanner = stealthDualScan(scan.privateKey, recipient.publicKey, nonce.publicKey)
assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/)
// ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient
const forRecipient = stealthDualReceive(scan.privateKey, recipient.privateKey, nonce.publicKey)
assert.doesNotThrow(function () { forRecipient.toWIF() })
// scanner, sender and recipient, all derived same address
assert.equal(getAddress(forSender), getAddress(forScanner))
assert.equal(getAddress(forSender), getAddress(forRecipient))
})
it('can generate a dual-key stealth address (randomly)', function () {
const recipient = bitcoin.ECPair.makeRandom() // private to recipient
const scan = bitcoin.ECPair.makeRandom() // private to scanner/recipient
const nonce = bitcoin.ECPair.makeRandom() // private to sender
// ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender
const forSender = stealthDualSend(nonce.privateKey, recipient.publicKey, scan.publicKey)
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
// ... sender reveals nonce public key (nonce.Q) to scanner
const forScanner = stealthDualScan(scan.privateKey, recipient.publicKey, nonce.publicKey)
assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/)
// ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient
const forRecipient = stealthDualReceive(scan.privateKey, recipient.privateKey, nonce.publicKey)
assert.doesNotThrow(function () { forRecipient.toWIF() })
// scanner, sender and recipient, all derived same address
assert.equal(getAddress(forSender), getAddress(forScanner))
assert.equal(getAddress(forSender), getAddress(forRecipient))
})
})

View file

@ -1,5 +1,4 @@
/* global describe, it */
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
@ -9,8 +8,8 @@ function rng () {
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64')
}
describe('bitcoinjs-lib (transactions)', function () {
it('can create a 1-to-1 Transaction', function () {
describe('bitcoinjs-lib (transactions)', () => {
it('can create a 1-to-1 Transaction', () => {
const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
const txb = new bitcoin.TransactionBuilder()
@ -25,7 +24,7 @@ describe('bitcoinjs-lib (transactions)', function () {
assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000')
})
it('can create a 2-to-2 Transaction', function () {
it('can create a 2-to-2 Transaction', () => {
const alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1')
const bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z')
@ -44,7 +43,7 @@ describe('bitcoinjs-lib (transactions)', function () {
assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000')
})
it('can create (and broadcast via 3PBP) a typical Transaction', function (done) {
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 })
@ -54,51 +53,45 @@ describe('bitcoinjs-lib (transactions)', function () {
const aliceCpkh = bitcoin.payments.p2pkh({ pubkey: aliceChange.publicKey, network: regtest })
// give Alice 2 unspent outputs
regtestUtils.faucet(alice1pkh.address, 5e4, function (err, unspent0) {
if (err) return done(err)
const unspent0 = await regtestUtils.faucet(alice1pkh.address, 5e4)
regtestUtils.faucet(alice2pkh.address, 7e4, function (err, unspent1) {
if (err) return done(err)
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
// (in)(4e4 + 2e4) - (out)(1e4 + 3e4) = (fee)2e4 = 20000, this is the miner fee
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(0, alice1)
txb.sign(1, alice2)
// Alice signs each input with the respective private keys
txb.sign(0, alice1)
txb.sign(1, alice2)
// build and broadcast our RegTest network
regtestUtils.broadcast(txb.build().toHex(), done)
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
})
})
// build and broadcast our RegTest network
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', function (done) {
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 })
regtestUtils.faucet(p2pkh.address, 2e5, function (err, unspent) {
if (err) return done(err)
const unspent = await regtestUtils.faucet(p2pkh.address, 2e5)
const txb = new bitcoin.TransactionBuilder(regtest)
const data = Buffer.from('bitcoinjs-lib', 'utf8')
const embed = bitcoin.payments.embed({ data: [data] })
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(embed.output, 1000)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5)
txb.sign(0, keyPair)
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(0, keyPair)
// build and broadcast to the RegTest network
regtestUtils.broadcast(txb.build().toHex(), done)
})
// build and broadcast to the RegTest network
await regtestUtils.broadcast(txb.build().toHex())
})
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', function (done) {
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 }),
@ -109,118 +102,102 @@ describe('bitcoinjs-lib (transactions)', function () {
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest })
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest })
regtestUtils.faucet(p2sh.address, 2e4, function (err, unspent) {
if (err) return done(err)
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(0, keyPairs[0], p2sh.redeem.output)
txb.sign(0, keyPairs[2], p2sh.redeem.output)
const tx = txb.build()
txb.sign(0, keyPairs[0], p2sh.redeem.output)
txb.sign(0, keyPairs[2], p2sh.redeem.output)
const tx = txb.build()
// build and broadcast to the Bitcoin RegTest network
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 1e4
}, done)
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 1e4
})
})
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', function (done) {
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 })
regtestUtils.faucet(p2sh.address, 5e4, function (err, unspent) {
if (err) return done(err)
const unspent = await regtestUtils.faucet(p2sh.address, 5e4)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
txb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
txb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value)
const tx = txb.build()
const tx = txb.build()
// build and broadcast to the Bitcoin RegTest network
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
}, done)
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
})
})
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', function (done) {
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 })
regtestUtils.faucetComplex(p2wpkh.address, 5e4, function (err, unspent) {
if (err) return done(err)
const unspent = await regtestUtils.faucetComplex(p2wpkh.address, 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)
txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script
const tx = txb.build()
// 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)
txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script
const tx = txb.build()
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
}, done)
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
})
})
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', function (done) {
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 })
regtestUtils.faucetComplex(p2wsh.address, 5e4, function (err, unspent) {
if (err) return done(err)
const unspent = await regtestUtils.faucetComplex(p2wsh.address, 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)
txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript!
const tx = txb.build()
// 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)
txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript!
const tx = txb.build()
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
}, done)
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
})
})
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', function (done) {
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 }),
@ -233,43 +210,39 @@ describe('bitcoinjs-lib (transactions)', function () {
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest })
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest })
regtestUtils.faucet(p2sh.address, 6e4, function (err, unspent) {
if (err) return done(err)
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)
txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
const tx = txb.build()
const tx = txb.build()
// build and broadcast to the Bitcoin RegTest network
regtestUtils.broadcast(tx.toHex(), function (err) {
if (err) return done(err)
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 3e4
}, done)
})
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 3e4
})
})
it('can verify Transaction signatures', function () {
it('can verify Transaction (P2PKH) signatures', () => {
const txHex = '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'
const keyPairs = [
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d',
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a',
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f'
].map(function (q) { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) })
].map(q => { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) })
const tx = bitcoin.Transaction.fromHex(txHex)
tx.ins.forEach(function (input, i) {
tx.ins.forEach((input, i) => {
const keyPair = keyPairs[i]
const p2pkh = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
@ -282,4 +255,34 @@ describe('bitcoinjs-lib (transactions)', function () {
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
})
})
it('can verify Transaction (P2SH(P2WPKH)) signatures', () => {
const utxos = {
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': {
value: 50000
}
}
const txHex = '02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'
const tx = bitcoin.Transaction.fromHex(txHex)
tx.ins.forEach((input, i) => {
const txId = Buffer.from(input.hash).reverse().toString('hex')
const utxo = utxos[`${txId}:${i}`]
if (!utxo) throw new Error('Missing utxo')
const p2sh = bitcoin.payments.p2sh({
input: input.script,
witness: input.witness
})
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem)
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }) // because P2WPKH is annoying
const ss = bitcoin.script.signature.decode(p2wpkh.signature)
const hash = tx.hashForWitnessV0(i, p2pkh.output, utxo.value, ss.hashType)
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey) // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
})
})
})