Skip to content

Commit bbf5cda

Browse files
authored
refactor: add explicit initialisation of the ecc library (#5)
* refactor: explicit initialization on the ecc library - remove optional `eccLib` parameter for `p2tr` and `psbt`
1 parent 6ea2f8b commit bbf5cda

22 files changed

+184
-224
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/ecc_lib.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { TinySecp256k1Interface } from './types';
2+
export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void;
3+
export declare function getEccLib(): TinySecp256k1Interface;

src/payments/verifyecc.js renamed to src/ecc_lib.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.verifyEcc = void 0;
3+
exports.getEccLib = exports.initEccLib = void 0;
4+
const _ECCLIB_CACHE = {};
5+
function initEccLib(eccLib) {
6+
if (!eccLib) {
7+
// allow clearing the library
8+
_ECCLIB_CACHE.eccLib = eccLib;
9+
} else if (eccLib !== _ECCLIB_CACHE.eccLib) {
10+
// new instance, verify it
11+
verifyEcc(eccLib);
12+
_ECCLIB_CACHE.eccLib = eccLib;
13+
}
14+
}
15+
exports.initEccLib = initEccLib;
16+
function getEccLib() {
17+
if (!_ECCLIB_CACHE.eccLib)
18+
throw new Error(
19+
'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance',
20+
);
21+
return _ECCLIB_CACHE.eccLib;
22+
}
23+
exports.getEccLib = getEccLib;
424
const h = hex => Buffer.from(hex, 'hex');
525
function verifyEcc(ecc) {
626
assert(typeof ecc.isXOnlyPoint === 'function');
@@ -46,7 +66,6 @@ function verifyEcc(ecc) {
4666
}
4767
});
4868
}
49-
exports.verifyEcc = verifyEcc;
5069
function assert(bool) {
5170
if (!bool) throw new Error('ecc library invalid');
5271
}

src/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export { Transaction } from './transaction';
1212
export { Network } from './networks';
1313
export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments';
1414
export { Input as TxInput, Output as TxOutput } from './transaction';
15+
export { initEccLib } from './ecc_lib';

src/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
3+
exports.initEccLib = exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
44
const address = require('./address');
55
exports.address = address;
66
const crypto = require('./crypto');
@@ -39,3 +39,10 @@ Object.defineProperty(exports, 'Transaction', {
3939
return transaction_1.Transaction;
4040
},
4141
});
42+
var ecc_lib_1 = require('./ecc_lib');
43+
Object.defineProperty(exports, 'initEccLib', {
44+
enumerable: true,
45+
get: function() {
46+
return ecc_lib_1.initEccLib;
47+
},
48+
});

src/payments/index.d.ts

+1-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 } from '../types';
44
import { p2data as embed } from './embed';
55
import { p2ms } from './p2ms';
66
import { p2pk } from './p2pk';
@@ -34,7 +34,6 @@ export declare type PaymentFunction = () => Payment;
3434
export interface PaymentOpts {
3535
validate?: boolean;
3636
allowIncomplete?: boolean;
37-
eccLib?: TinySecp256k1Interface;
3837
}
3938
export declare type StackElement = Buffer | number;
4039
export declare type Stack = StackElement[];

src/payments/p2tr.js

+9-14
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ const buffer_1 = require('buffer');
55
const networks_1 = require('../networks');
66
const bscript = require('../script');
77
const types_1 = require('../types');
8+
const ecc_lib_1 = require('../ecc_lib');
89
const taprootutils_1 = require('./taprootutils');
910
const lazy = require('./lazy');
1011
const bech32_1 = require('bech32');
11-
const verifyecc_1 = require('./verifyecc');
1212
const OPS = bscript.OPS;
1313
const TAPROOT_WITNESS_VERSION = 0x01;
1414
const ANNEX_PREFIX = 0x50;
@@ -22,11 +22,6 @@ function p2tr(a, opts) {
2222
)
2323
throw new TypeError('Not enough data');
2424
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;
29-
});
3025
(0, types_1.typeforce)(
3126
{
3227
address: types_1.typeforce.maybe(types_1.typeforce.String),
@@ -132,7 +127,7 @@ function p2tr(a, opts) {
132127
if (a.output) return a.output.slice(2);
133128
if (a.address) return _address().data;
134129
if (o.internalPubkey) {
135-
const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc());
130+
const tweakedKey = tweakKey(o.internalPubkey, o.hash);
136131
if (tweakedKey) return tweakedKey.x;
137132
}
138133
});
@@ -157,7 +152,7 @@ function p2tr(a, opts) {
157152
});
158153
const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash);
159154
if (!path) return;
160-
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc());
155+
const outputKey = tweakKey(a.internalPubkey, hashTree.hash);
161156
if (!outputKey) return;
162157
const controlBock = buffer_1.Buffer.concat(
163158
[
@@ -198,13 +193,13 @@ function p2tr(a, opts) {
198193
else pubkey = a.output.slice(2);
199194
}
200195
if (a.internalPubkey) {
201-
const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc());
196+
const tweakedKey = tweakKey(a.internalPubkey, o.hash);
202197
if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x))
203198
throw new TypeError('Pubkey mismatch');
204199
else pubkey = tweakedKey.x;
205200
}
206201
if (pubkey && pubkey.length) {
207-
if (!_ecc().isXOnlyPoint(pubkey))
202+
if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(pubkey))
208203
throw new TypeError('Invalid pubkey for p2tr');
209204
}
210205
const hashTree = _hashTree();
@@ -267,7 +262,7 @@ function p2tr(a, opts) {
267262
const internalPubkey = controlBlock.slice(1, 33);
268263
if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey))
269264
throw new TypeError('Internal pubkey mismatch');
270-
if (!_ecc().isXOnlyPoint(internalPubkey))
265+
if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(internalPubkey))
271266
throw new TypeError('Invalid internalPubkey for p2tr witness');
272267
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
273268
const script = witness[witness.length - 2];
@@ -279,7 +274,7 @@ function p2tr(a, opts) {
279274
controlBlock,
280275
leafHash,
281276
);
282-
const outputKey = tweakKey(internalPubkey, hash, _ecc());
277+
const outputKey = tweakKey(internalPubkey, hash);
283278
if (!outputKey)
284279
// todo: needs test data
285280
throw new TypeError('Invalid outputKey for p2tr witness');
@@ -293,12 +288,12 @@ function p2tr(a, opts) {
293288
return Object.assign(o, a);
294289
}
295290
exports.p2tr = p2tr;
296-
function tweakKey(pubKey, h, eccLib) {
291+
function tweakKey(pubKey, h) {
297292
if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
298293
if (pubKey.length !== 32) return null;
299294
if (h && h.length !== 32) return null;
300295
const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h);
301-
const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash);
296+
const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash);
302297
if (!res || res.xOnlyPubkey === null) return null;
303298
return {
304299
parity: res.parity,

src/payments/verifyecc.d.ts

-2
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
};

src/psbt.js

+10-26
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ class Psbt {
7979
// We will disable exporting the Psbt when unsafe sign is active.
8080
// because it is not BIP174 compliant.
8181
__UNSAFE_SIGN_NONSEGWIT: false,
82-
__EC_LIB: opts.eccLib,
8382
};
8483
if (this.data.inputs.length === 0) this.setVersion(2);
8584
// Make data hidden when enumerating
@@ -134,7 +133,6 @@ class Psbt {
134133
address = (0, address_1.fromOutputScript)(
135134
output.script,
136135
this.opts.network,
137-
this.__CACHE.__EC_LIB,
138136
);
139137
} catch (_) {}
140138
return {
@@ -237,11 +235,7 @@ class Psbt {
237235
const { address } = outputData;
238236
if (typeof address === 'string') {
239237
const { network } = this.opts;
240-
const script = (0, address_1.toOutputScript)(
241-
address,
242-
network,
243-
this.__CACHE.__EC_LIB,
244-
);
238+
const script = (0, address_1.toOutputScript)(address, network);
245239
outputData = Object.assign(outputData, { script });
246240
}
247241
const c = this.__CACHE;
@@ -297,7 +291,6 @@ class Psbt {
297291
isP2SH,
298292
isP2WSH,
299293
isTapscript,
300-
this.__CACHE.__EC_LIB,
301294
);
302295
if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig });
303296
if (finalScriptWitness) {
@@ -326,13 +319,9 @@ class Psbt {
326319
input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
327320
input.witnessScript ||
328321
redeemFromFinalWitnessScript(input.finalScriptWitness),
329-
this.__CACHE,
330322
);
331323
const type = result.type === 'raw' ? '' : result.type + '-';
332-
const mainType = classifyScript(
333-
result.meaningfulScript,
334-
this.__CACHE.__EC_LIB,
335-
);
324+
const mainType = classifyScript(result.meaningfulScript);
336325
return type + mainType;
337326
}
338327
inputHasPubkey(inputIndex, pubkey) {
@@ -769,9 +758,9 @@ function isFinalized(input) {
769758
return !!input.finalScriptSig || !!input.finalScriptWitness;
770759
}
771760
function isPaymentFactory(payment) {
772-
return (script, eccLib) => {
761+
return script => {
773762
try {
774-
payment({ output: script }, { eccLib });
763+
payment({ output: script });
775764
return true;
776765
} catch (err) {
777766
return false;
@@ -935,9 +924,8 @@ function getFinalScripts(
935924
isP2SH,
936925
isP2WSH,
937926
isTapscript = false,
938-
eccLib,
939927
) {
940-
const scriptType = classifyScript(script, eccLib);
928+
const scriptType = classifyScript(script);
941929
if (isTapscript || !canFinalize(input, script, scriptType))
942930
throw new Error(`Can not finalize input #${inputIndex}`);
943931
return prepareFinalScripts(
@@ -1053,7 +1041,6 @@ function getHashForSig(
10531041
'input',
10541042
input.redeemScript,
10551043
input.witnessScript,
1056-
cache,
10571044
);
10581045
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
10591046
hash = unsignedTx.hashForWitnessV0(
@@ -1072,7 +1059,7 @@ function getHashForSig(
10721059
prevout.value,
10731060
sighashType,
10741061
);
1075-
} else if (isP2TR(prevout.script, cache.__EC_LIB)) {
1062+
} else if (isP2TR(prevout.script)) {
10761063
const prevOuts = inputs.map((i, index) =>
10771064
getScriptAndAmountFromUtxo(index, i, cache),
10781065
);
@@ -1204,7 +1191,7 @@ function getScriptFromInput(inputIndex, input, cache) {
12041191
} else {
12051192
res.script = utxoScript;
12061193
}
1207-
const isTaproot = utxoScript && isP2TR(utxoScript, cache.__EC_LIB);
1194+
const isTaproot = utxoScript && isP2TR(utxoScript);
12081195
// Segregated Witness versions 0 or 1
12091196
if (input.witnessScript || isP2WPKH(res.script) || isTaproot) {
12101197
res.isSegwit = true;
@@ -1410,7 +1397,6 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
14101397
'input',
14111398
input.redeemScript,
14121399
input.witnessScript,
1413-
cache,
14141400
);
14151401
return pubkeyInScript(pubkey, meaningfulScript);
14161402
}
@@ -1422,7 +1408,6 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) {
14221408
'output',
14231409
output.redeemScript,
14241410
output.witnessScript,
1425-
cache,
14261411
);
14271412
return pubkeyInScript(pubkey, meaningfulScript);
14281413
}
@@ -1471,12 +1456,11 @@ function getMeaningfulScript(
14711456
ioType,
14721457
redeemScript,
14731458
witnessScript,
1474-
cache,
14751459
) {
14761460
const isP2SH = isP2SHScript(script);
14771461
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
14781462
const isP2WSH = isP2WSHScript(script);
1479-
const isP2TRScript = isP2TR(script, cache && cache.__EC_LIB);
1463+
const isP2TRScript = isP2TR(script);
14801464
if (isP2SH && redeemScript === undefined)
14811465
throw new Error('scriptPubkey is P2SH but redeemScript missing');
14821466
if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
@@ -1539,12 +1523,12 @@ function isTaprootSpend(scriptType) {
15391523
!!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-'))
15401524
);
15411525
}
1542-
function classifyScript(script, eccLib) {
1526+
function classifyScript(script) {
15431527
if (isP2WPKH(script)) return 'witnesspubkeyhash';
15441528
if (isP2PKH(script)) return 'pubkeyhash';
15451529
if (isP2MS(script)) return 'multisig';
15461530
if (isP2PK(script)) return 'pubkey';
1547-
if (isP2TR(script, eccLib)) return 'taproot';
1531+
if (isP2TR(script)) return 'taproot';
15481532
return 'nonstandard';
15491533
}
15501534
function range(n) {

0 commit comments

Comments
 (0)