From 191b9e857341521507b35e21f748bf148ebb5301 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 17 Nov 2021 16:01:08 +0900 Subject: [PATCH] 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';