Merge pull request #1747 from bitcoinjs/tests/taproot
Add taproot test with new CJS compatible tiny-secp256k1
This commit is contained in:
commit
ac411e241b
7 changed files with 167 additions and 20 deletions
2
.github/workflows/main_ci.yml
vendored
2
.github/workflows/main_ci.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
regtest:
|
regtest:
|
||||||
image: junderw/bitcoinjs-regtest-server@sha256:a46ec1a651ca5b1a5408f2b2526ea5f435421dd2bc2f28fae3bc33e1fd614552
|
image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
steps:
|
steps:
|
||||||
|
|
46
package-lock.json
generated
46
package-lock.json
generated
|
@ -556,16 +556,15 @@
|
||||||
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
|
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
|
||||||
},
|
},
|
||||||
"bip32": {
|
"bip32": {
|
||||||
"version": "2.0.6",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz",
|
||||||
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
|
"integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "10.12.18",
|
"@types/node": "10.12.18",
|
||||||
"bs58check": "^2.1.1",
|
"bs58check": "^2.1.1",
|
||||||
"create-hash": "^1.2.0",
|
"create-hash": "^1.2.0",
|
||||||
"create-hmac": "^1.1.7",
|
"create-hmac": "^1.1.7",
|
||||||
"tiny-secp256k1": "^1.1.3",
|
|
||||||
"typeforce": "^1.11.5",
|
"typeforce": "^1.11.5",
|
||||||
"wif": "^2.0.6"
|
"wif": "^2.0.6"
|
||||||
},
|
},
|
||||||
|
@ -941,6 +940,21 @@
|
||||||
"tiny-secp256k1": "^1.1.6",
|
"tiny-secp256k1": "^1.1.6",
|
||||||
"typeforce": "^1.11.3",
|
"typeforce": "^1.11.3",
|
||||||
"wif": "^2.0.1"
|
"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": {
|
"elliptic": {
|
||||||
|
@ -1707,9 +1721,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.14.2",
|
"version": "2.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
|
||||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node-environment-flags": {
|
"node-environment-flags": {
|
||||||
|
@ -2465,16 +2479,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tiny-secp256k1": {
|
"tiny-secp256k1": {
|
||||||
"version": "1.1.6",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.1.tgz",
|
||||||
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
|
"integrity": "sha512-pdENPcbI4l3Br6sPVuC5RWONHojcPjBiXljIBvQ5UIN/MD6wPzmJ8mpDnkps3O7FFfT+fLqGXo2MdFdRQaPWUg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "^1.3.0",
|
"uint8array-tools": "0.0.6"
|
||||||
"bn.js": "^4.11.8",
|
|
||||||
"create-hmac": "^1.1.7",
|
|
||||||
"elliptic": "^6.4.0",
|
|
||||||
"nan": "^2.13.2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"to-fast-properties": {
|
"to-fast-properties": {
|
||||||
|
@ -2583,6 +2593,12 @@
|
||||||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
|
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
|
||||||
"dev": true
|
"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": {
|
"uuid": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
"@types/proxyquire": "^1.3.28",
|
"@types/proxyquire": "^1.3.28",
|
||||||
"@types/randombytes": "^2.0.0",
|
"@types/randombytes": "^2.0.0",
|
||||||
"@types/wif": "^2.0.2",
|
"@types/wif": "^2.0.2",
|
||||||
"bip32": "^2.0.6",
|
"bip32": "^3.0.1",
|
||||||
"bip39": "^3.0.2",
|
"bip39": "^3.0.2",
|
||||||
"bip65": "^1.0.1",
|
"bip65": "^1.0.1",
|
||||||
"bip68": "^1.0.3",
|
"bip68": "^1.0.3",
|
||||||
|
@ -84,6 +84,7 @@
|
||||||
"randombytes": "^2.1.0",
|
"randombytes": "^2.1.0",
|
||||||
"regtest-client": "0.2.0",
|
"regtest-client": "0.2.0",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^2.6.3",
|
||||||
|
"tiny-secp256k1": "^2.1.1",
|
||||||
"ts-node": "^8.3.0",
|
"ts-node": "^8.3.0",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"typescript": "^4.4.4"
|
"typescript": "^4.4.4"
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import * as assert from 'assert';
|
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 * as bip39 from 'bip39';
|
||||||
import { describe, it } from 'mocha';
|
import { describe, it } from 'mocha';
|
||||||
import * as bitcoin from '../..';
|
import * as bitcoin from '../..';
|
||||||
|
|
||||||
|
const bip32 = BIP32Factory(ecc);
|
||||||
|
|
||||||
function getAddress(node: any, network?: any): string {
|
function getAddress(node: any, network?: any): string {
|
||||||
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
|
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
|
||||||
}
|
}
|
||||||
|
|
122
test/integration/taproot.spec.ts
Normal file
122
test/integration/taproot.spec.ts
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import * as assert from 'assert';
|
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 { ECPair } from 'ecpair';
|
||||||
import { describe, it } from 'mocha';
|
import { describe, it } from 'mocha';
|
||||||
import * as bitcoin from '../..';
|
import * as bitcoin from '../..';
|
||||||
import { regtestUtils } from './_regtest';
|
import { regtestUtils } from './_regtest';
|
||||||
const rng = require('randombytes');
|
const rng = require('randombytes');
|
||||||
const regtest = regtestUtils.network;
|
const regtest = regtestUtils.network;
|
||||||
|
const bip32 = BIP32Factory(ecc);
|
||||||
|
|
||||||
const validator = (
|
const validator = (
|
||||||
pubkey: Buffer,
|
pubkey: Buffer,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import * as assert from 'assert';
|
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 * as crypto from 'crypto';
|
||||||
import { ECPair } from 'ecpair';
|
import { ECPair } from 'ecpair';
|
||||||
import { describe, it } from 'mocha';
|
import { describe, it } from 'mocha';
|
||||||
|
|
||||||
|
const bip32 = BIP32Factory(ecc);
|
||||||
|
|
||||||
import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..';
|
import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..';
|
||||||
|
|
||||||
import * as preFixtures from './fixtures/psbt.json';
|
import * as preFixtures from './fixtures/psbt.json';
|
||||||
|
|
Loading…
Reference in a new issue