From 24e5cc061699cf66e63df7b371d4d1171646d529 Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Fri, 12 Nov 2021 12:39:56 +0900 Subject: [PATCH 1/8] Add Taproot example --- CHANGELOG.md | 13 +++++ README.md | 7 +-- test/integration/taproot.md | 111 ++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 test/integration/taproot.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e1eb7..f7d8f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 6.0.0 +__removed__ +- bip32: Removed the re-export. Please add as dependency to your app instead. +- ECPair: Please use bip32 moving forward. ecpair package was created for those who need it. +- TransactionBuilder: Any internal files used only in TB (classify, templates, etc.) were also removed. + +__added__ +- taproot segwit v1 address support (bech32m) via address module (#1676) +- hashForWitnessV1 method on Transaction class (#1745) + +__fixed__ +- Transaction version read/write differed. (#1717) + # 5.2.0 __changed__ - Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563) diff --git a/README.md b/README.md index d974624..9389a73 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # BitcoinJS (bitcoinjs-lib) -[](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) -[](https://www.npmjs.org/package/bitcoinjs-lib) - -[](https://github.com/prettier/prettier) +[](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml) [](https://www.npmjs.org/package/bitcoinjs-lib) [](https://github.com/prettier/prettier) A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify. @@ -94,6 +91,8 @@ The below examples are implemented as integration tests, they should be very eas Otherwise, pull requests are appreciated. Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). +- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md) + - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) diff --git a/test/integration/taproot.md b/test/integration/taproot.md new file mode 100644 index 0000000..2db6eef --- /dev/null +++ b/test/integration/taproot.md @@ -0,0 +1,111 @@ +# Taproot + +A simple keyspend example that is possible with the current API is below. + +## Current state of taproot support + +- [x] segwit v1 address support via bech32m +- [x] segwit v1 sighash calculation on Transaction class + +## TODO + +- [ ] p2tr payment API to make script spends easier +- [ ] Support within the Psbt class + +## Example + +### Requirements +- npm dependencies + - bitcoinjs-lib v6.x.x + - bip32 v3.x.x + - tiny-secp256k1 v2.x.x + - regtest-client vx.x.x +- local regtest-server docker container running + - `docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server` +- node >= v14 + +```js +const crypto = require('crypto'); + +// bitcoinjs-lib v6 +const bitcoin = require('bitcoinjs-lib'); +// bip32 v3 wraps tiny-secp256k1 +const BIP32Wrapper = require('bip32').default; +const RegtestUtils = require('regtest-client').RegtestUtils; +// tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async +import('tiny-secp256k1') + .then(async (ecc) => { + // End imports + + // set up dependencies + const APIPASS = process.env.APIPASS || 'satoshi'; + // docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server + const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1'; + const regtestUtils = new RegtestUtils({ APIPASS, APIURL }); + + const bip32 = BIP32Wrapper(ecc); + + const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network); + // scriptPubkey + const output = Buffer.concat([ + // witness v1, PUSH_DATA 32 bytes + Buffer.from([0x51, 0x20]), + // x-only pubkey (remove 1 byte y parity) + myKey.publicKey.slice(1, 33), + ]); + const address = bitcoin.address.fromOutputScript( + output, + regtestUtils.network + ); + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output, amount); + + const tx = createSigned( + myKey, + unspent.txId, + unspent.vout, + sendAmount, + [output], + [amount] + ); + + const hex = tx.toHex(); + console.log('Valid tx sent from:'); + console.log(address); + console.log('tx hex:'); + console.log(hex); + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address, + vout: 0, + value: sendAmount, + }); + }) + .catch(console.error); + +// Function for creating signed tx +function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) { + const tx = new bitcoin.Transaction(); + tx.version = 2; + // Add input + tx.addInput(Buffer.from(txid, 'hex').reverse(), vout); + // Add output + tx.addOutput(scriptPubkeys[0], amountToSend); + const sighash = tx.hashForWitnessV1( + 0, // which input + scriptPubkeys, // All previous outputs of all inputs + values, // All previous values of all inputs + bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) + ); + const signature = Buffer.from(key.signSchnorr(sighash)); + // witness stack for keypath spend is just the signature. + // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value + tx.ins[0].witness = [signature]; + return tx; +} +``` \ No newline at end of file From 31e512e63f0dac3af4f9cfe72e886baa1b768467 Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Fri, 12 Nov 2021 12:40:09 +0900 Subject: [PATCH 2/8] 6.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f07cf3c..c66d2ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "5.2.0", + "version": "6.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0eb793e..e420511 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "5.2.0", + "version": "6.0.0", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 424abf2376772bb57b7668bc35b29ed18879fa0a Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Mon, 15 Nov 2021 08:25:22 +0900 Subject: [PATCH 3/8] Fix taproot example to follow the suggestion in BIP341 --- test/integration/taproot.md | 147 +++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 51 deletions(-) diff --git a/test/integration/taproot.md b/test/integration/taproot.md index 2db6eef..4010340 100644 --- a/test/integration/taproot.md +++ b/test/integration/taproot.md @@ -25,68 +25,108 @@ A simple keyspend example that is possible with the current API is below. - node >= v14 ```js -const crypto = require('crypto'); +// Run this whole file as async +// Catch any errors at the bottom of the file +// and exit the process with 1 error code +(async () => { +// Order of the curve (N) - 1 +const N_LESS_1 = Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', + 'hex' +); +// 1 represented as 32 bytes BE +const ONE = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex' +); + +const crypto = require('crypto'); // bitcoinjs-lib v6 const bitcoin = require('bitcoinjs-lib'); // bip32 v3 wraps tiny-secp256k1 const BIP32Wrapper = require('bip32').default; const RegtestUtils = require('regtest-client').RegtestUtils; // tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async -import('tiny-secp256k1') - .then(async (ecc) => { - // End imports +const ecc = await import('tiny-secp256k1'); +// wrap the bip32 library +const bip32 = BIP32Wrapper(ecc); +// set up dependencies +const APIPASS = process.env.APIPASS || 'satoshi'; +// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server +const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1'; +const regtestUtils = new RegtestUtils({ APIPASS, APIURL }); +// End imports - // set up dependencies - const APIPASS = process.env.APIPASS || 'satoshi'; - // docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server - const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1'; - const regtestUtils = new RegtestUtils({ APIPASS, APIURL }); +const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network); - const bip32 = BIP32Wrapper(ecc); +const output = createKeySpendOutput(myKey.publicKey); +const address = bitcoin.address.fromOutputScript( + output, + regtestUtils.network +); +// amount from faucet +const amount = 42e4; +// amount to send +const sendAmount = amount - 1e4; +// get faucet +const unspent = await regtestUtils.faucetComplex(output, amount); - const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network); - // scriptPubkey - const output = Buffer.concat([ - // witness v1, PUSH_DATA 32 bytes - Buffer.from([0x51, 0x20]), - // x-only pubkey (remove 1 byte y parity) - myKey.publicKey.slice(1, 33), - ]); - const address = bitcoin.address.fromOutputScript( - output, - regtestUtils.network - ); - // amount from faucet - const amount = 42e4; - // amount to send - const sendAmount = amount - 1e4; - // get faucet - const unspent = await regtestUtils.faucetComplex(output, amount); +const tx = createSigned( + myKey, + unspent.txId, + unspent.vout, + sendAmount, + [output], + [amount] +); - const tx = createSigned( - myKey, - unspent.txId, - unspent.vout, - sendAmount, - [output], - [amount] - ); +const hex = tx.toHex(); +console.log('Valid tx sent from:'); +console.log(address); +console.log('tx hex:'); +console.log(hex); +await regtestUtils.broadcast(hex); +await regtestUtils.verify({ + txId: tx.getId(), + address, + vout: 0, + value: sendAmount, +}); - const hex = tx.toHex(); - console.log('Valid tx sent from:'); - console.log(address); - console.log('tx hex:'); - console.log(hex); - await regtestUtils.broadcast(hex); - await regtestUtils.verify({ - txId: tx.getId(), - address, - vout: 0, - value: sendAmount, - }); - }) - .catch(console.error); +// Function for creating a tweaked p2tr key-spend only address +// (This is recommended by BIP341) +function createKeySpendOutput(publicKey) { + // x-only pubkey (remove 1 byte y parity) + const myXOnlyPubkey = publicKey.slice(1, 33); + const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey); + const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash); + if (tweakResult === null) throw new Error('Invalid Tweak'); + const { xOnlyPubkey: tweaked } = tweakResult; + // scriptPubkey + return Buffer.concat([ + // witness v1, PUSH_DATA 32 bytes + Buffer.from([0x51, 0x20]), + // x-only tweaked pubkey + tweaked, + ]); +} + +// Function for signing for a tweaked p2tr key-spend only address +// (Required for the above address) +function signTweaked(messageHash, key) { + const privateKey = + key.publicKey[0] === 2 + ? key.privateKey + : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE); + const tweakHash = bitcoin.crypto.taggedHash( + 'TapTweak', + key.publicKey.slice(1, 33) + ); + const newPrivateKey = ecc.privateAdd(privateKey, tweakHash); + if (newPrivateKey === null) throw new Error('Invalid Tweak'); + return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32)); +} // Function for creating signed tx function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) { @@ -102,10 +142,15 @@ function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) { values, // All previous values of all inputs bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) ); - const signature = Buffer.from(key.signSchnorr(sighash)); + const signature = Buffer.from(signTweaked(sighash, key)); // witness stack for keypath spend is just the signature. // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value tx.ins[0].witness = [signature]; return tx; } + +})().catch((err) => { + console.error(err); + process.exit(1); +}); ``` \ No newline at end of file From 191b9e857341521507b35e21f748bf148ebb5301 Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Wed, 17 Nov 2021 16:01:08 +0900 Subject: [PATCH 4/8] Add taproot test with new CJS compatible tiny-secp256k1 --- package-lock.json | 46 ++++++---- package.json | 3 +- test/integration/bip32.spec.ts | 5 +- test/integration/taproot.spec.ts | 122 ++++++++++++++++++++++++++ test/integration/transactions.spec.ts | 4 +- test/psbt.spec.ts | 5 +- 6 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 test/integration/taproot.spec.ts diff --git a/package-lock.json b/package-lock.json index c66d2ad..d6b9ce8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -556,16 +556,15 @@ "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==" }, "bip32": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz", + "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", "dev": true, "requires": { "@types/node": "10.12.18", "bs58check": "^2.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", "typeforce": "^1.11.5", "wif": "^2.0.6" }, @@ -941,6 +940,21 @@ "tiny-secp256k1": "^1.1.6", "typeforce": "^1.11.3", "wif": "^2.0.1" + }, + "dependencies": { + "tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "dev": true, + "requires": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + } + } } }, "elliptic": { @@ -1707,9 +1721,9 @@ "dev": true }, "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "dev": true }, "node-environment-flags": { @@ -2465,16 +2479,12 @@ } }, "tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.1.tgz", + "integrity": "sha512-pdENPcbI4l3Br6sPVuC5RWONHojcPjBiXljIBvQ5UIN/MD6wPzmJ8mpDnkps3O7FFfT+fLqGXo2MdFdRQaPWUg==", "dev": true, "requires": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" + "uint8array-tools": "0.0.6" } }, "to-fast-properties": { @@ -2583,6 +2593,12 @@ "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, + "uint8array-tools": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz", + "integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==", + "dev": true + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", diff --git a/package.json b/package.json index e420511..36d9003 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", "@types/wif": "^2.0.2", - "bip32": "^2.0.6", + "bip32": "^3.0.1", "bip39": "^3.0.2", "bip65": "^1.0.1", "bip68": "^1.0.3", @@ -84,6 +84,7 @@ "randombytes": "^2.1.0", "regtest-client": "0.2.0", "rimraf": "^2.6.3", + "tiny-secp256k1": "^2.1.1", "ts-node": "^8.3.0", "tslint": "^6.1.3", "typescript": "^4.4.4" diff --git a/test/integration/bip32.spec.ts b/test/integration/bip32.spec.ts index 7cd9e2f..938c281 100644 --- a/test/integration/bip32.spec.ts +++ b/test/integration/bip32.spec.ts @@ -1,9 +1,12 @@ import * as assert from 'assert'; -import * as bip32 from 'bip32'; +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; import * as bip39 from 'bip39'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; +const bip32 = BIP32Factory(ecc); + function getAddress(node: any, network?: any): string { return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!; } diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts new file mode 100644 index 0000000..f7b3733 --- /dev/null +++ b/test/integration/taproot.spec.ts @@ -0,0 +1,122 @@ +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; +import { describe, it } from 'mocha'; +import * as bitcoin from '../..'; +import { regtestUtils } from './_regtest'; +const rng = require('randombytes'); +const regtest = regtestUtils.network; +const bip32 = BIP32Factory(ecc); + +describe('bitcoinjs-lib (transaction with taproot)', () => { + it('can create (and broadcast via 3PBP) a taproot keyspend Transaction', async () => { + const myKey = bip32.fromSeed(rng(64), regtest); + + const output = createKeySpendOutput(myKey.publicKey); + const address = bitcoin.address.fromOutputScript(output, regtest); + // amount from faucet + const amount = 42e4; + // amount to send + const sendAmount = amount - 1e4; + // get faucet + const unspent = await regtestUtils.faucetComplex(output, amount); + + const tx = createSigned( + myKey, + unspent.txId, + unspent.vout, + sendAmount, + [output], + [amount], + ); + + const hex = tx.toHex(); + // console.log('Valid tx sent from:'); + // console.log(address); + // console.log('tx hex:'); + // console.log(hex); + await regtestUtils.broadcast(hex); + await regtestUtils.verify({ + txId: tx.getId(), + address, + vout: 0, + value: sendAmount, + }); + }); +}); + +// Order of the curve (N) - 1 +const N_LESS_1 = Buffer.from( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', + 'hex', +); +// 1 represented as 32 bytes BE +const ONE = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex', +); + +// Function for creating a tweaked p2tr key-spend only address +// (This is recommended by BIP341) +function createKeySpendOutput(publicKey: Buffer): Buffer { + // x-only pubkey (remove 1 byte y parity) + const myXOnlyPubkey = publicKey.slice(1, 33); + const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey); + const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash); + if (tweakResult === null) throw new Error('Invalid Tweak'); + const { xOnlyPubkey: tweaked } = tweakResult; + // scriptPubkey + return Buffer.concat([ + // witness v1, PUSH_DATA 32 bytes + Buffer.from([0x51, 0x20]), + // x-only tweaked pubkey + tweaked, + ]); +} + +// Function for signing for a tweaked p2tr key-spend only address +// (Required for the above address) +interface KeyPair { + publicKey: Buffer; + privateKey?: Buffer; +} +function signTweaked(messageHash: Buffer, key: KeyPair): Uint8Array { + const privateKey = + key.publicKey[0] === 2 + ? key.privateKey + : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey!)!, ONE)!; + const tweakHash = bitcoin.crypto.taggedHash( + 'TapTweak', + key.publicKey.slice(1, 33), + ); + const newPrivateKey = ecc.privateAdd(privateKey!, tweakHash); + if (newPrivateKey === null) throw new Error('Invalid Tweak'); + return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32)); +} + +// Function for creating signed tx +function createSigned( + key: KeyPair, + txid: string, + vout: number, + amountToSend: number, + scriptPubkeys: Buffer[], + values: number[], +): bitcoin.Transaction { + const tx = new bitcoin.Transaction(); + tx.version = 2; + // Add input + tx.addInput(Buffer.from(txid, 'hex').reverse(), vout); + // Add output + tx.addOutput(scriptPubkeys[0], amountToSend); + const sighash = tx.hashForWitnessV1( + 0, // which input + scriptPubkeys, // All previous outputs of all inputs + values, // All previous values of all inputs + bitcoin.Transaction.SIGHASH_DEFAULT, // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) + ); + const signature = Buffer.from(signTweaked(sighash, key)); + // witness stack for keypath spend is just the signature. + // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value + tx.ins[0].witness = [signature]; + return tx; +} diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index d4788dc..51e44a0 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,11 +1,13 @@ import * as assert from 'assert'; -import * as bip32 from 'bip32'; +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; const rng = require('randombytes'); const regtest = regtestUtils.network; +const bip32 = BIP32Factory(ecc); const validator = ( pubkey: Buffer, diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 05d4468..32d81ba 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1,9 +1,12 @@ import * as assert from 'assert'; -import * as bip32 from 'bip32'; +import BIP32Factory from 'bip32'; +import * as ecc from 'tiny-secp256k1'; import * as crypto from 'crypto'; import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; +const bip32 = BIP32Factory(ecc); + import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; import * as preFixtures from './fixtures/psbt.json'; From 4674433bb9333cc50858f44aa92b64fb643ed386 Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Wed, 17 Nov 2021 16:06:45 +0900 Subject: [PATCH 5/8] Update container --- .github/workflows/main_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index a9fdc58..7bc62cb 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest services: regtest: - image: junderw/bitcoinjs-regtest-server@sha256:a46ec1a651ca5b1a5408f2b2526ea5f435421dd2bc2f28fae3bc33e1fd614552 + image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463 ports: - 8080:8080 steps: From 93af5afe67dbc491e36bdfc8d48a00179093f7d1 Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Sat, 27 Nov 2021 08:35:19 +0900 Subject: [PATCH 6/8] Add warning to future segwit version address generation/parsing --- src/address.js | 10 +++++++++- ts_src/address.ts | 12 +++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/address.js b/src/address.js index 12938fc..164bf7e 100644 --- a/src/address.js +++ b/src/address.js @@ -13,6 +13,11 @@ const FUTURE_SEGWIT_MIN_SIZE = 2; const FUTURE_SEGWIT_MAX_VERSION = 16; const FUTURE_SEGWIT_MIN_VERSION = 1; const FUTURE_SEGWIT_VERSION_DIFF = 0x50; +const FUTURE_SEGWIT_VERSION_WARNING = + 'WARNING: Sending to a future segwit version address can lead to loss of funds. ' + + 'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' + + 'with caution. Wallets should verify the segwit version from the output of fromBech32, ' + + 'then decide when it is safe to use which version of segwit.'; function _toFutureSegwitAddress(output, network) { const data = output.slice(2); if ( @@ -28,6 +33,7 @@ function _toFutureSegwitAddress(output, network) { throw new TypeError('Invalid version for segwit address'); if (output[1] !== data.length) throw new TypeError('Invalid script for segwit address'); + console.warn(FUTURE_SEGWIT_VERSION_WARNING); return toBech32(data, version, network.bech32); } function fromBase58Check(address) { @@ -128,11 +134,13 @@ function toOutputScript(address, network) { decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE && decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE - ) + ) { + console.warn(FUTURE_SEGWIT_VERSION_WARNING); return bscript.compile([ decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF, decodeBech32.data, ]); + } } } throw new Error(address + ' has no matching Script'); diff --git a/ts_src/address.ts b/ts_src/address.ts index d8111a7..753589d 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -23,6 +23,11 @@ const FUTURE_SEGWIT_MIN_SIZE: number = 2; const FUTURE_SEGWIT_MAX_VERSION: number = 16; const FUTURE_SEGWIT_MIN_VERSION: number = 1; const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50; +const FUTURE_SEGWIT_VERSION_WARNING: string = + 'WARNING: Sending to a future segwit version address can lead to loss of funds. ' + + 'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' + + 'with caution. Wallets should verify the segwit version from the output of fromBech32, ' + + 'then decide when it is safe to use which version of segwit.'; function _toFutureSegwitAddress(output: Buffer, network: Network): string { const data = output.slice(2); @@ -44,6 +49,8 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string { if (output[1] !== data.length) throw new TypeError('Invalid script for segwit address'); + console.warn(FUTURE_SEGWIT_VERSION_WARNING); + return toBech32(data, version, network.bech32); } @@ -163,11 +170,14 @@ export function toOutputScript(address: string, network?: Network): Buffer { decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE && decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE - ) + ) { + console.warn(FUTURE_SEGWIT_VERSION_WARNING); + return bscript.compile([ decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF, decodeBech32.data, ]); + } } } From 11202eb74cc9d9a7338ef4aa04c78061f0544289 Mon Sep 17 00:00:00 2001 From: junderw <junderwood@bitcoinbank.co.jp> Date: Sat, 27 Nov 2021 08:42:12 +0900 Subject: [PATCH 7/8] 6.0.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6b9ce8..2ea42ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.0", + "version": "6.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 36d9003..dc93c53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.0", + "version": "6.0.1", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./src/index.d.ts", From 2edfb992fa09761e09490b5efa27ba13a77de374 Mon Sep 17 00:00:00 2001 From: Vlad Stan <stan.v.vlad@gmail.com> Date: Fri, 17 Dec 2021 13:27:35 +0200 Subject: [PATCH 8/8] test: upgrade ecpair lib to version 2.0.1 --- package-lock.json | 123 ++------------------------ package.json | 4 +- test/integration/addresses.spec.ts | 5 +- test/integration/cltv.spec.ts | 5 +- test/integration/csv.spec.ts | 5 +- test/integration/payments.spec.ts | 5 +- test/integration/transactions.spec.ts | 4 +- test/psbt.spec.ts | 3 +- 8 files changed, 32 insertions(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ea42ae..3e5bd51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -541,15 +541,6 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bip174": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz", @@ -640,12 +631,6 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -931,59 +916,14 @@ "dev": true }, "ecpair": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-1.0.0.tgz", - "integrity": "sha512-1L+P/ivLC3eKHgqcX1M9tFYQWXDoqwJ3zQnN7zDaTtLpiCQKpFTaAZvnsPC5PkWB4q3EPFAHffCLvjfCqRjuwQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", + "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", "dev": true, "requires": { - "randombytes": "^2.0.1", - "tiny-secp256k1": "^1.1.6", - "typeforce": "^1.11.3", - "wif": "^2.0.1" - }, - "dependencies": { - "tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "dev": true, - "requires": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - } - } - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - } + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" } }, "emoji-regex": { @@ -1040,12 +980,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1218,16 +1152,6 @@ "safe-buffer": "^5.0.1" } }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -1244,17 +1168,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "hoodwink": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", @@ -1640,18 +1553,6 @@ "pushdata-bitcoin": "^1.0.1" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1720,12 +1621,6 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true - }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -2479,9 +2374,9 @@ } }, "tiny-secp256k1": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.1.tgz", - "integrity": "sha512-pdENPcbI4l3Br6sPVuC5RWONHojcPjBiXljIBvQ5UIN/MD6wPzmJ8mpDnkps3O7FFfT+fLqGXo2MdFdRQaPWUg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz", + "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==", "dev": true, "requires": { "uint8array-tools": "0.0.6" diff --git a/package.json b/package.json index dc93c53..c5553d5 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "bn.js": "^4.11.8", "bs58": "^4.0.0", "dhttp": "^3.0.0", - "ecpair": "^1.0.0", + "ecpair": "^2.0.1", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", "mocha": "^7.1.1", @@ -84,7 +84,7 @@ "randombytes": "^2.1.0", "regtest-client": "0.2.0", "rimraf": "^2.6.3", - "tiny-secp256k1": "^2.1.1", + "tiny-secp256k1": "^2.1.2", "ts-node": "^8.3.0", "tslint": "^6.1.3", "typescript": "^4.4.4" diff --git a/test/integration/addresses.spec.ts b/test/integration/addresses.spec.ts index 2b24ef5..d6e758b 100644 --- a/test/integration/addresses.spec.ts +++ b/test/integration/addresses.spec.ts @@ -1,8 +1,11 @@ import * as assert from 'assert'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const dhttp = regtestUtils.dhttp; const TESTNET = bitcoin.networks.testnet; diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index c1a52de..04944eb 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -1,8 +1,11 @@ import * as assert from 'assert'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { before, describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const regtest = regtestUtils.network; const bip65 = require('bip65'); diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 9993d5c..742d68f 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -1,9 +1,12 @@ import * as assert from 'assert'; import { PsbtInput } from 'bip174/src/lib/interfaces'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { before, describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const regtest = regtestUtils.network; const bip68 = require('bip68'); const varuint = require('varuint-bitcoin'); diff --git a/test/integration/payments.spec.ts b/test/integration/payments.spec.ts index ea7294e..d9d7fde 100644 --- a/test/integration/payments.spec.ts +++ b/test/integration/payments.spec.ts @@ -1,7 +1,10 @@ -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; +import * as ecc from 'tiny-secp256k1'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const NETWORK = regtestUtils.network; const keyPairs = [ ECPair.makeRandom({ network: NETWORK }), diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index 51e44a0..ba7f2fe 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,10 +1,12 @@ import * as assert from 'assert'; import BIP32Factory from 'bip32'; import * as ecc from 'tiny-secp256k1'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; + +const ECPair = ECPairFactory(ecc); const rng = require('randombytes'); const regtest = regtestUtils.network; const bip32 = BIP32Factory(ecc); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index 32d81ba..f583e80 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -2,10 +2,11 @@ import * as assert from 'assert'; import BIP32Factory from 'bip32'; import * as ecc from 'tiny-secp256k1'; import * as crypto from 'crypto'; -import { ECPair } from 'ecpair'; +import ECPairFactory from 'ecpair'; import { describe, it } from 'mocha'; const bip32 = BIP32Factory(ecc); +const ECPair = ECPairFactory(ecc); import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..';