Skip to content

Commit 2ab5c92

Browse files
committed
Remove need for ECC lib from PSBT
* Add basic isXOnlyPoint check to types. * Use basic isXOnlyPoint check in p2tr * Only use ECC lib for tweaking in p2tr * Remove ECC lib from PSBT * tweaking is done by provided `finalizeScriptsFunc` * Update test vectors to reflect these changes
1 parent ef5457e commit 2ab5c92

22 files changed

+240
-372
lines changed

src/address.d.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/// <reference types="node" />
22
import { Network } from './networks';
3-
import { TinySecp256k1Interface } from './types';
43
export interface Base58CheckResult {
54
hash: Buffer;
65
version: number;
@@ -14,5 +13,5 @@ export declare function fromBase58Check(address: string): Base58CheckResult;
1413
export declare function fromBech32(address: string): Bech32Result;
1514
export declare function toBase58Check(hash: Buffer, version: number): string;
1615
export declare function toBech32(data: Buffer, version: number, prefix: string): string;
17-
export declare function fromOutputScript(output: Buffer, network?: Network, eccLib?: TinySecp256k1Interface): string;
18-
export declare function toOutputScript(address: string, network?: Network, eccLib?: TinySecp256k1Interface): Buffer;
16+
export declare function fromOutputScript(output: Buffer, network?: Network): string;
17+
export declare function toOutputScript(address: string, network?: Network): Buffer;

src/address.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function toBech32(data, version, prefix) {
8686
: bech32_1.bech32m.encode(prefix, words);
8787
}
8888
exports.toBech32 = toBech32;
89-
function fromOutputScript(output, network, eccLib) {
89+
function fromOutputScript(output, network) {
9090
// TODO: Network
9191
network = network || networks.bitcoin;
9292
try {
@@ -102,15 +102,15 @@ function fromOutputScript(output, network, eccLib) {
102102
return payments.p2wsh({ output, network }).address;
103103
} catch (e) {}
104104
try {
105-
if (eccLib) return payments.p2tr({ output, network }, { eccLib }).address;
105+
return payments.p2tr({ output, network }).address;
106106
} catch (e) {}
107107
try {
108108
return _toFutureSegwitAddress(output, network);
109109
} catch (e) {}
110110
throw new Error(bscript.toASM(output) + ' has no matching Address');
111111
}
112112
exports.fromOutputScript = fromOutputScript;
113-
function toOutputScript(address, network, eccLib) {
113+
function toOutputScript(address, network) {
114114
network = network || networks.bitcoin;
115115
let decodeBase58;
116116
let decodeBech32;
@@ -135,9 +135,8 @@ function toOutputScript(address, network, eccLib) {
135135
if (decodeBech32.data.length === 32)
136136
return payments.p2wsh({ hash: decodeBech32.data }).output;
137137
} else if (decodeBech32.version === 1) {
138-
if (decodeBech32.data.length === 32 && eccLib)
139-
return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib })
140-
.output;
138+
if (decodeBech32.data.length === 32)
139+
return payments.p2tr({ pubkey: decodeBech32.data }).output;
141140
} else if (
142141
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
143142
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&

src/payments/index.d.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="node" />
22
import { Network } from '../networks';
3-
import { TinySecp256k1Interface, Taptree } from '../types';
3+
import { Taptree, XOnlyPointAddTweakResult } from '../types';
44
import { p2data as embed } from './embed';
55
import { p2ms } from './p2ms';
66
import { p2pk } from './p2pk';
@@ -31,10 +31,11 @@ export interface Payment {
3131
}
3232
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
3333
export declare type PaymentFunction = () => Payment;
34+
export declare type XOnlyTweakFunction = (p: Buffer, t: Buffer) => XOnlyPointAddTweakResult | null;
3435
export interface PaymentOpts {
3536
validate?: boolean;
3637
allowIncomplete?: boolean;
37-
eccLib?: TinySecp256k1Interface;
38+
tweakFn?: XOnlyTweakFunction;
3839
}
3940
export declare type StackElement = Buffer | number;
4041
export declare type Stack = StackElement[];

src/payments/p2tr.js

+52-13
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const types_1 = require('../types');
88
const taprootutils_1 = require('./taprootutils');
99
const lazy = require('./lazy');
1010
const bech32_1 = require('bech32');
11-
const verifyecc_1 = require('./verifyecc');
1211
const OPS = bscript.OPS;
1312
const TAPROOT_WITNESS_VERSION = 0x01;
1413
const ANNEX_PREFIX = 0x50;
@@ -22,10 +21,10 @@ function p2tr(a, opts) {
2221
)
2322
throw new TypeError('Not enough data');
2423
opts = Object.assign({ validate: true }, opts || {});
25-
const _ecc = lazy.value(() => {
26-
if (!opts.eccLib) throw new Error('ECC Library is missing for p2tr.');
27-
(0, verifyecc_1.verifyEcc)(opts.eccLib);
28-
return opts.eccLib;
24+
const _tweakFn = lazy.value(() => {
25+
if (!opts.tweakFn) throw new Error('Tweak function is missing for p2tr.');
26+
verifyTweakFn(opts.tweakFn);
27+
return opts.tweakFn;
2928
});
3029
(0, types_1.typeforce)(
3130
{
@@ -132,7 +131,7 @@ function p2tr(a, opts) {
132131
if (a.output) return a.output.slice(2);
133132
if (a.address) return _address().data;
134133
if (o.internalPubkey) {
135-
const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc());
134+
const tweakedKey = tweakKey(o.internalPubkey, o.hash, _tweakFn());
136135
if (tweakedKey) return tweakedKey.x;
137136
}
138137
});
@@ -157,7 +156,7 @@ function p2tr(a, opts) {
157156
});
158157
const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash);
159158
if (!path) return;
160-
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc());
159+
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _tweakFn());
161160
if (!outputKey) return;
162161
const controlBock = buffer_1.Buffer.concat(
163162
[
@@ -198,13 +197,13 @@ function p2tr(a, opts) {
198197
else pubkey = a.output.slice(2);
199198
}
200199
if (a.internalPubkey) {
201-
const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc());
200+
const tweakedKey = tweakKey(a.internalPubkey, o.hash, _tweakFn());
202201
if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x))
203202
throw new TypeError('Pubkey mismatch');
204203
else pubkey = tweakedKey.x;
205204
}
206205
if (pubkey && pubkey.length) {
207-
if (!_ecc().isXOnlyPoint(pubkey))
206+
if (!(0, types_1.isXOnlyPoint)(pubkey))
208207
throw new TypeError('Invalid pubkey for p2tr');
209208
}
210209
const hashTree = _hashTree();
@@ -267,7 +266,7 @@ function p2tr(a, opts) {
267266
const internalPubkey = controlBlock.slice(1, 33);
268267
if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey))
269268
throw new TypeError('Internal pubkey mismatch');
270-
if (!_ecc().isXOnlyPoint(internalPubkey))
269+
if (!(0, types_1.isXOnlyPoint)(internalPubkey))
271270
throw new TypeError('Invalid internalPubkey for p2tr witness');
272271
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
273272
const script = witness[witness.length - 2];
@@ -279,7 +278,7 @@ function p2tr(a, opts) {
279278
controlBlock,
280279
leafHash,
281280
);
282-
const outputKey = tweakKey(internalPubkey, hash, _ecc());
281+
const outputKey = tweakKey(internalPubkey, hash, _tweakFn());
283282
if (!outputKey)
284283
// todo: needs test data
285284
throw new TypeError('Invalid outputKey for p2tr witness');
@@ -293,12 +292,12 @@ function p2tr(a, opts) {
293292
return Object.assign(o, a);
294293
}
295294
exports.p2tr = p2tr;
296-
function tweakKey(pubKey, h, eccLib) {
295+
function tweakKey(pubKey, h, tweakFn) {
297296
if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
298297
if (pubKey.length !== 32) return null;
299298
if (h && h.length !== 32) return null;
300299
const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h);
301-
const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash);
300+
const res = tweakFn(pubKey, tweakHash);
302301
if (!res || res.xOnlyPubkey === null) return null;
303302
return {
304303
parity: res.parity,
@@ -311,3 +310,43 @@ function stacksEqual(a, b) {
311310
return x.equals(b[i]);
312311
});
313312
}
313+
function verifyTweakFn(tweakFn) {
314+
[
315+
{
316+
pubkey:
317+
'79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
318+
tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
319+
parity: -1,
320+
result: null,
321+
},
322+
{
323+
pubkey:
324+
'1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b',
325+
tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac',
326+
parity: 1,
327+
result:
328+
'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf',
329+
},
330+
{
331+
pubkey:
332+
'2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991',
333+
tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47',
334+
parity: 0,
335+
result:
336+
'9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c',
337+
},
338+
].forEach(t => {
339+
const r = tweakFn(
340+
Buffer.from(t.pubkey, 'hex'),
341+
Buffer.from(t.tweak, 'hex'),
342+
);
343+
if (t.result === null) {
344+
if (r !== null) throw new Error('Expected failed tweak');
345+
} else {
346+
if (r === null) throw new Error('Expected successful tweak');
347+
if (r.parity !== t.parity) throw new Error('Tweaked key parity mismatch');
348+
if (!Buffer.from(r.xOnlyPubkey).equals(Buffer.from(t.result, 'hex')))
349+
throw new Error('Tweaked key mismatch');
350+
}
351+
});
352+
}

src/payments/verifyecc.d.ts

-2
This file was deleted.

src/payments/verifyecc.js

-72
This file was deleted.

src/psbt.d.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Psbt as PsbtBase } from 'bip174';
33
import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces';
44
import { Network } from './networks';
55
import { Transaction } from './transaction';
6-
import { TinySecp256k1Interface } from './types';
76
export interface TransactionInput {
87
hash: string | Buffer;
98
index: number;
@@ -111,7 +110,6 @@ export declare class Psbt {
111110
interface PsbtOptsOptional {
112111
network?: Network;
113112
maximumFeeRate?: number;
114-
eccLib?: TinySecp256k1Interface;
115113
}
116114
interface PsbtInputExtended extends PsbtInput, TransactionInput {
117115
}
@@ -181,8 +179,7 @@ script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH
181179
isSegwit: boolean, // Is it segwit?
182180
isTapscript: boolean, // Is taproot script path?
183181
isP2SH: boolean, // Is it P2SH?
184-
isP2WSH: boolean, // Is it P2WSH?
185-
eccLib?: TinySecp256k1Interface) => {
182+
isP2WSH: boolean) => {
186183
finalScriptSig: Buffer | undefined;
187184
finalScriptWitness: Buffer | Buffer[] | undefined;
188185
};

0 commit comments

Comments
 (0)