Merge branch 'master' into addComplexScript
This commit is contained in:
commit
9f96fd097b
179 changed files with 11314 additions and 4583 deletions
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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\)/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
|
@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue