Skip to content
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

Merge master #6

Merged
merged 10 commits into from
Jan 29, 2024
8 changes: 1 addition & 7 deletions src/payments/embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ const bscript = require('../script');
const types_1 = require('../types');
const lazy = require('./lazy');
const OPS = bscript.OPS;
function stacksEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// output: OP_RETURN ...
function p2data(a, opts) {
if (!a.data && !a.output) throw new TypeError('Not enough data');
Expand Down Expand Up @@ -43,7 +37,7 @@ function p2data(a, opts) {
if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid');
if (!chunks.slice(1).every(types_1.typeforce.Buffer))
throw new TypeError('Output is invalid');
if (a.data && !stacksEqual(a.data, o.data))
if (a.data && !(0, types_1.stacksEqual)(a.data, o.data))
throw new TypeError('Data mismatch');
}
}
Expand Down
10 changes: 2 additions & 8 deletions src/payments/p2ms.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ const types_1 = require('../types');
const lazy = require('./lazy');
const OPS = bscript.OPS;
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
function stacksEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: OP_0 [signatures ...]
// output: m [pubKeys ...] n OP_CHECKMULTISIG
function p2ms(a, opts) {
Expand Down Expand Up @@ -117,7 +111,7 @@ function p2ms(a, opts) {
throw new TypeError('Output is invalid');
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch');
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch');
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys))
if (a.pubkeys && !(0, types_1.stacksEqual)(a.pubkeys, o.pubkeys))
throw new TypeError('Pubkeys mismatch');
}
if (a.pubkeys) {
Expand All @@ -139,7 +133,7 @@ function p2ms(a, opts) {
!o.signatures.every(isAcceptableSignature)
)
throw new TypeError('Input has invalid signature(s)');
if (a.signatures && !stacksEqual(a.signatures, o.signatures))
if (a.signatures && !(0, types_1.stacksEqual)(a.signatures, o.signatures))
throw new TypeError('Signature mismatch');
if (a.m !== undefined && a.m !== a.signatures.length)
throw new TypeError('Signature count mismatch');
Expand Down
8 changes: 1 addition & 7 deletions src/payments/p2sh.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ const types_1 = require('../types');
const lazy = require('./lazy');
const bs58grscheck = require('bs58grscheck');
const OPS = bscript.OPS;
function stacksEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
// input: [redeemScriptSig ...] {redeemScript}
// witness: <?>
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
Expand Down Expand Up @@ -188,7 +182,7 @@ function p2sh(a, opts) {
if (
a.redeem &&
a.redeem.witness &&
!stacksEqual(a.redeem.witness, a.witness)
!(0, types_1.stacksEqual)(a.redeem.witness, a.witness)
)
throw new TypeError('Witness and redeem.witness mismatch');
}
Expand Down
18 changes: 3 additions & 15 deletions src/payments/p2tr.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ecc_lib_1 = require('../ecc_lib');
const bip341_1 = require('./bip341');
const lazy = require('./lazy');
const bech32_1 = require('bech32');
const address_1 = require('../address');
const OPS = bscript.OPS;
const TAPROOT_WITNESS_VERSION = 0x01;
const ANNEX_PREFIX = 0x50;
Expand Down Expand Up @@ -53,14 +54,7 @@ function p2tr(a, opts) {
a,
);
const _address = lazy.value(() => {
const result = bech32_1.bech32m.decode(a.address);
const version = result.words.shift();
const data = bech32_1.bech32m.fromWords(result.words);
return {
version,
prefix: result.prefix,
data: buffer_1.Buffer.from(data),
};
return (0, address_1.fromBech32)(a.address);
});
// remove annex if present, ignored by taproot
const _witness = lazy.value(() => {
Expand Down Expand Up @@ -237,7 +231,7 @@ function p2tr(a, opts) {
if (a.redeem.witness) {
if (
o.redeem.witness &&
!stacksEqual(a.redeem.witness, o.redeem.witness)
!(0, types_1.stacksEqual)(a.redeem.witness, o.redeem.witness)
)
throw new TypeError('Redeem.witness and witness mismatch');
}
Expand Down Expand Up @@ -289,9 +283,3 @@ function p2tr(a, opts) {
return Object.assign(o, a);
}
exports.p2tr = p2tr;
function stacksEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
8 changes: 1 addition & 7 deletions src/payments/p2wsh.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ const lazy = require('./lazy');
const bech32_1 = require('bech32');
const OPS = bscript.OPS;
const EMPTY_BUFFER = Buffer.alloc(0);
function stacksEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
function chunkHasUncompressedPubkey(chunk) {
if (
Buffer.isBuffer(chunk) &&
Expand Down Expand Up @@ -190,7 +184,7 @@ function p2wsh(a, opts) {
if (
a.witness &&
a.redeem.witness &&
!stacksEqual(a.witness, a.redeem.witness)
!(0, types_1.stacksEqual)(a.witness, a.redeem.witness)
)
throw new TypeError('Witness and redeem.witness mismatch');
if (
Expand Down
9 changes: 5 additions & 4 deletions src/script_signature.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Object.defineProperty(exports, '__esModule', { value: true });
exports.encode = exports.decode = void 0;
const bip66 = require('./bip66');
const script_1 = require('./script');
const types = require('./types');
const { typeforce } = types;
const ZERO = Buffer.alloc(1, 0);
Expand All @@ -23,9 +24,9 @@ function fromDER(x) {
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
function decode(buffer) {
const hashType = buffer.readUInt8(buffer.length - 1);
const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4)
if (!(0, script_1.isDefinedHashType)(hashType)) {
throw new Error('Invalid hashType ' + hashType);
}
const decoded = bip66.decode(buffer.slice(0, -1));
const r = fromDER(decoded.r);
const s = fromDER(decoded.s);
Expand All @@ -41,9 +42,9 @@ function encode(signature, hashType) {
},
{ signature, hashType },
);
const hashTypeMod = hashType & ~0x80;
if (hashTypeMod <= 0 || hashTypeMod >= 4)
if (!(0, script_1.isDefinedHashType)(hashType)) {
throw new Error('Invalid hashType ' + hashType);
}
const hashTypeBuffer = Buffer.allocUnsafe(1);
hashTypeBuffer.writeUInt8(hashType, 0);
const r = toDER(signature.slice(0, 32));
Expand Down
1 change: 1 addition & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="node" />
export declare const typeforce: any;
export declare function stacksEqual(a: Buffer[], b: Buffer[]): boolean;
export declare function isPoint(p: Buffer | number | undefined | null): boolean;
export declare function UInt31(value: number): boolean;
export declare function BIP32Path(value: string): boolean;
Expand Down
8 changes: 8 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ exports.oneOf =
exports.BIP32Path =
exports.UInt31 =
exports.isPoint =
exports.stacksEqual =
exports.typeforce =
void 0;
const buffer_1 = require('buffer');
Expand All @@ -36,6 +37,13 @@ const EC_P = buffer_1.Buffer.from(
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f',
'hex',
);
function stacksEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
exports.stacksEqual = stacksEqual;
function isPoint(p) {
if (!buffer_1.Buffer.isBuffer(p)) return false;
if (p.length < 33) return false;
Expand Down
64 changes: 17 additions & 47 deletions test/integration/taproot.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import * as assert from 'assert';
import BIP32Factory from 'bip32grs';
import * as bip39 from 'bip39';
import ECPairFactory from 'ecpairgrs';
import * as ecc from 'tiny-secp256k1';
import { describe, it } from 'mocha';
import { PsbtInput, TapLeafScript } from 'bip174/src/lib/interfaces';
import { PsbtInput, TapLeaf, TapLeafScript } from 'bip174/src/lib/interfaces';
import { regtestUtils } from './_regtest';
import * as bitcoin from '../..';
import { Taptree } from '../../src/types';
import { LEAF_VERSION_TAPSCRIPT } from '../../src/payments/bip341';
import { toXOnly, tapTreeToList, tapTreeFromList } from '../../src/psbt/bip371';
import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils';
import { TapLeaf } from 'bip174/src/lib/interfaces';

const rng = require('randombytes');
const regtest = regtestUtils.network;
bitcoin.initEccLib(ecc);
const bip32 = BIP32Factory(ecc);
const ECPair = ECPairFactory(ecc);

describe('groestlcoinjs-lib (transaction with taproot)', () => {
it.skip('can verify the BIP86 HD wallet vectors for taproot single sig (& sending example)', async () => {
Expand All @@ -39,7 +37,7 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
assert.strictEqual(rootKey.toBase58(), xprv);
const childNode = rootKey.derivePath(path);
// Since internalKey is an xOnly pubkey, we drop the DER header byte
const childNodeXOnlyPubkey = childNode.publicKey.slice(1, 33);
const childNodeXOnlyPubkey = toXOnly(childNode.publicKey);
assert.deepEqual(childNodeXOnlyPubkey, internalPubkey);

// This is new for taproot
Expand Down Expand Up @@ -139,7 +137,9 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
tapInternalKey: sendPubKey,
});

const tweakedSigner = tweakSigner(internalKey!, { network: regtest });
const tweakedSigner = internalKey.tweak(
bitcoin.crypto.taggedHash('TapTweak', toXOnly(internalKey.publicKey)),
);
await psbt.signInputAsync(0, tweakedSigner);
await psbt.signInputAsync(1, p2pkhKey);

Expand Down Expand Up @@ -194,10 +194,12 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
});
psbt.addOutput({ value: sendAmount, address: address! });

const tweakedSigner = tweakSigner(internalKey!, {
tweakHash: hash,
network: regtest,
});
const tweakedSigner = internalKey.tweak(
bitcoin.crypto.taggedHash(
'TapTweak',
Buffer.concat([toXOnly(internalKey.publicKey), hash!]),
),
);
psbt.signInput(0, tweakedSigner);

psbt.finalizeAllInputs();
Expand Down Expand Up @@ -271,7 +273,7 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
];
const redeem = {
output: leafScript,
redeemVersion: 192,
redeemVersion: LEAF_VERSION_TAPSCRIPT,
};

const { output, witness } = bitcoin.payments.p2tr({
Expand Down Expand Up @@ -361,7 +363,7 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
];
const redeem = {
output: leafScript,
redeemVersion: 192,
redeemVersion: LEAF_VERSION_TAPSCRIPT,
};

const { output, witness } = bitcoin.payments.p2tr({
Expand Down Expand Up @@ -472,7 +474,7 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
];
const redeem = {
output: leafScript,
redeemVersion: 192,
redeemVersion: LEAF_VERSION_TAPSCRIPT,
};

const { output, address, witness } = bitcoin.payments.p2tr({
Expand Down Expand Up @@ -532,7 +534,7 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
(_, index) =>
({
depth: 3,
leafVersion: 192,
leafVersion: LEAF_VERSION_TAPSCRIPT,
script: bitcoin.script.fromASM(`OP_ADD OP_${index * 2} OP_EQUAL`),
} as TapLeaf),
);
Expand All @@ -541,7 +543,7 @@ describe('groestlcoinjs-lib (transaction with taproot)', () => {
for (let leafIndex = 1; leafIndex < leafCount; leafIndex++) {
const redeem = {
output: bitcoin.script.fromASM(`OP_ADD OP_${leafIndex * 2} OP_EQUAL`),
redeemVersion: 192,
redeemVersion: LEAF_VERSION_TAPSCRIPT,
};

const internalKey = bip32.fromSeed(rng(64), regtest);
Expand Down Expand Up @@ -691,35 +693,3 @@ function buildLeafIndexFinalizer(
}
};
}

// This logic will be extracted to ecpair
function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
let privateKey: Uint8Array | undefined = signer.privateKey!;
if (!privateKey) {
throw new Error('Private key is required for tweaking signer!');
}
if (signer.publicKey[0] === 3) {
privateKey = ecc.privateNegate(privateKey);
}

const tweakedPrivateKey = ecc.privateAdd(
privateKey,
tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash),
);
if (!tweakedPrivateKey) {
throw new Error('Invalid tweaked private key!');
}

return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: opts.network,
});
}

function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
return bitcoin.crypto.taggedHash(
'TapTweak',
Buffer.concat(h ? [pubKey, h] : [pubKey]),
);
}
10 changes: 1 addition & 9 deletions ts_src/payments/embed.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script';
import { typeforce as typef } from '../types';
import { typeforce as typef, stacksEqual } from '../types';
import { Payment, PaymentOpts, Stack } from './index';
import * as lazy from './lazy';

const OPS = bscript.OPS;

function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
if (a.length !== b.length) return false;

return a.every((x, i) => {
return x.equals(b[i]);
});
}

// output: OP_RETURN ...
export function p2data(a: Payment, opts?: PaymentOpts): Payment {
if (!a.data && !a.output) throw new TypeError('Not enough data');
Expand Down
10 changes: 1 addition & 9 deletions ts_src/payments/p2ms.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script';
import { isPoint, typeforce as typef } from '../types';
import { isPoint, typeforce as typef, stacksEqual } from '../types';
import { Payment, PaymentOpts, Stack } from './index';
import * as lazy from './lazy';
const OPS = bscript.OPS;

const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1

function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
if (a.length !== b.length) return false;

return a.every((x, i) => {
return x.equals(b[i]);
});
}

// input: OP_0 [signatures ...]
// output: m [pubKeys ...] n OP_CHECKMULTISIG
export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
Expand Down
Loading
Loading