Skip to content

Add taproot test with new CJS compatible tiny-secp256k1 #1747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
46 changes: 31 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
5 changes: 4 additions & 1 deletion test/integration/bip32.spec.ts
Original file line number Diff line number Diff line change
@@ -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!;
}
Expand Down
122 changes: 122 additions & 0 deletions test/integration/taproot.spec.ts
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 3 additions & 1 deletion test/integration/transactions.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
5 changes: 4 additions & 1 deletion test/psbt.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down