Skip to content

refactor: add explicit initialisation of the ecc library #5

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
Mar 28, 2022
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
5 changes: 2 additions & 3 deletions src/address.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference types="node" />
import { Network } from './networks';
import { TinySecp256k1Interface } from './types';
export interface Base58CheckResult {
hash: Buffer;
version: number;
Expand All @@ -14,5 +13,5 @@ export declare function fromBase58Check(address: string): Base58CheckResult;
export declare function fromBech32(address: string): Bech32Result;
export declare function toBase58Check(hash: Buffer, version: number): string;
export declare function toBech32(data: Buffer, version: number, prefix: string): string;
export declare function fromOutputScript(output: Buffer, network?: Network, eccLib?: TinySecp256k1Interface): string;
export declare function toOutputScript(address: string, network?: Network, eccLib?: TinySecp256k1Interface): Buffer;
export declare function fromOutputScript(output: Buffer, network?: Network): string;
export declare function toOutputScript(address: string, network?: Network): Buffer;
11 changes: 5 additions & 6 deletions src/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function toBech32(data, version, prefix) {
: bech32_1.bech32m.encode(prefix, words);
}
exports.toBech32 = toBech32;
function fromOutputScript(output, network, eccLib) {
function fromOutputScript(output, network) {
// TODO: Network
network = network || networks.bitcoin;
try {
Expand All @@ -102,15 +102,15 @@ function fromOutputScript(output, network, eccLib) {
return payments.p2wsh({ output, network }).address;
} catch (e) {}
try {
if (eccLib) return payments.p2tr({ output, network }, { eccLib }).address;
return payments.p2tr({ output, network }).address;
} catch (e) {}
try {
return _toFutureSegwitAddress(output, network);
} catch (e) {}
throw new Error(bscript.toASM(output) + ' has no matching Address');
}
exports.fromOutputScript = fromOutputScript;
function toOutputScript(address, network, eccLib) {
function toOutputScript(address, network) {
network = network || networks.bitcoin;
let decodeBase58;
let decodeBech32;
Expand All @@ -135,9 +135,8 @@ function toOutputScript(address, network, eccLib) {
if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output;
} else if (decodeBech32.version === 1) {
if (decodeBech32.data.length === 32 && eccLib)
return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib })
.output;
if (decodeBech32.data.length === 32)
return payments.p2tr({ pubkey: decodeBech32.data }).output;
} else if (
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
Expand Down
3 changes: 3 additions & 0 deletions src/ecc_lib.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { TinySecp256k1Interface } from './types';
export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void;
export declare function getEccLib(): TinySecp256k1Interface;
23 changes: 21 additions & 2 deletions src/payments/verifyecc.js → src/ecc_lib.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.verifyEcc = void 0;
exports.getEccLib = exports.initEccLib = void 0;
const _ECCLIB_CACHE = {};
function initEccLib(eccLib) {
if (!eccLib) {
// allow clearing the library
_ECCLIB_CACHE.eccLib = eccLib;
} else if (eccLib !== _ECCLIB_CACHE.eccLib) {
// new instance, verify it
verifyEcc(eccLib);
_ECCLIB_CACHE.eccLib = eccLib;
}
}
exports.initEccLib = initEccLib;
function getEccLib() {
if (!_ECCLIB_CACHE.eccLib)
throw new Error(
'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance',
);
return _ECCLIB_CACHE.eccLib;
}
exports.getEccLib = getEccLib;
const h = hex => Buffer.from(hex, 'hex');
function verifyEcc(ecc) {
assert(typeof ecc.isXOnlyPoint === 'function');
Expand Down Expand Up @@ -46,7 +66,6 @@ function verifyEcc(ecc) {
}
});
}
exports.verifyEcc = verifyEcc;
function assert(bool) {
if (!bool) throw new Error('ecc library invalid');
}
Expand Down
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { Transaction } from './transaction';
export { Network } from './networks';
export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments';
export { Input as TxInput, Output as TxOutput } from './transaction';
export { initEccLib } from './ecc_lib';
9 changes: 8 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
exports.initEccLib = exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
const address = require('./address');
exports.address = address;
const crypto = require('./crypto');
Expand Down Expand Up @@ -39,3 +39,10 @@ Object.defineProperty(exports, 'Transaction', {
return transaction_1.Transaction;
},
});
var ecc_lib_1 = require('./ecc_lib');
Object.defineProperty(exports, 'initEccLib', {
enumerable: true,
get: function() {
return ecc_lib_1.initEccLib;
},
});
3 changes: 1 addition & 2 deletions src/payments/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="node" />
import { Network } from '../networks';
import { TinySecp256k1Interface, Taptree } from '../types';
import { Taptree } from '../types';
import { p2data as embed } from './embed';
import { p2ms } from './p2ms';
import { p2pk } from './p2pk';
Expand Down Expand Up @@ -34,7 +34,6 @@ export declare type PaymentFunction = () => Payment;
export interface PaymentOpts {
validate?: boolean;
allowIncomplete?: boolean;
eccLib?: TinySecp256k1Interface;
}
export declare type StackElement = Buffer | number;
export declare type Stack = StackElement[];
Expand Down
23 changes: 9 additions & 14 deletions src/payments/p2tr.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ const buffer_1 = require('buffer');
const networks_1 = require('../networks');
const bscript = require('../script');
const types_1 = require('../types');
const ecc_lib_1 = require('../ecc_lib');
const taprootutils_1 = require('./taprootutils');
const lazy = require('./lazy');
const bech32_1 = require('bech32');
const verifyecc_1 = require('./verifyecc');
const OPS = bscript.OPS;
const TAPROOT_WITNESS_VERSION = 0x01;
const ANNEX_PREFIX = 0x50;
Expand All @@ -22,11 +22,6 @@ function p2tr(a, opts) {
)
throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {});
const _ecc = lazy.value(() => {
if (!opts.eccLib) throw new Error('ECC Library is missing for p2tr.');
(0, verifyecc_1.verifyEcc)(opts.eccLib);
return opts.eccLib;
});
(0, types_1.typeforce)(
{
address: types_1.typeforce.maybe(types_1.typeforce.String),
Expand Down Expand Up @@ -132,7 +127,7 @@ function p2tr(a, opts) {
if (a.output) return a.output.slice(2);
if (a.address) return _address().data;
if (o.internalPubkey) {
const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc());
const tweakedKey = tweakKey(o.internalPubkey, o.hash);
if (tweakedKey) return tweakedKey.x;
}
});
Expand All @@ -157,7 +152,7 @@ function p2tr(a, opts) {
});
const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash);
if (!path) return;
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc());
const outputKey = tweakKey(a.internalPubkey, hashTree.hash);
if (!outputKey) return;
const controlBock = buffer_1.Buffer.concat(
[
Expand Down Expand Up @@ -198,13 +193,13 @@ function p2tr(a, opts) {
else pubkey = a.output.slice(2);
}
if (a.internalPubkey) {
const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc());
const tweakedKey = tweakKey(a.internalPubkey, o.hash);
if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x))
throw new TypeError('Pubkey mismatch');
else pubkey = tweakedKey.x;
}
if (pubkey && pubkey.length) {
if (!_ecc().isXOnlyPoint(pubkey))
if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(pubkey))
throw new TypeError('Invalid pubkey for p2tr');
}
const hashTree = _hashTree();
Expand Down Expand Up @@ -267,7 +262,7 @@ function p2tr(a, opts) {
const internalPubkey = controlBlock.slice(1, 33);
if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey))
throw new TypeError('Internal pubkey mismatch');
if (!_ecc().isXOnlyPoint(internalPubkey))
if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(internalPubkey))
throw new TypeError('Invalid internalPubkey for p2tr witness');
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
const script = witness[witness.length - 2];
Expand All @@ -279,7 +274,7 @@ function p2tr(a, opts) {
controlBlock,
leafHash,
);
const outputKey = tweakKey(internalPubkey, hash, _ecc());
const outputKey = tweakKey(internalPubkey, hash);
if (!outputKey)
// todo: needs test data
throw new TypeError('Invalid outputKey for p2tr witness');
Expand All @@ -293,12 +288,12 @@ function p2tr(a, opts) {
return Object.assign(o, a);
}
exports.p2tr = p2tr;
function tweakKey(pubKey, h, eccLib) {
function tweakKey(pubKey, h) {
if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
if (pubKey.length !== 32) return null;
if (h && h.length !== 32) return null;
const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h);
const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash);
const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash);
if (!res || res.xOnlyPubkey === null) return null;
return {
parity: res.parity,
Expand Down
2 changes: 0 additions & 2 deletions src/payments/verifyecc.d.ts

This file was deleted.

5 changes: 1 addition & 4 deletions src/psbt.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Psbt as PsbtBase } from 'bip174';
import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces';
import { Network } from './networks';
import { Transaction } from './transaction';
import { TinySecp256k1Interface } from './types';
export interface TransactionInput {
hash: string | Buffer;
index: number;
Expand Down Expand Up @@ -111,7 +110,6 @@ export declare class Psbt {
interface PsbtOptsOptional {
network?: Network;
maximumFeeRate?: number;
eccLib?: TinySecp256k1Interface;
}
interface PsbtInputExtended extends PsbtInput, TransactionInput {
}
Expand Down Expand Up @@ -181,8 +179,7 @@ script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH
isSegwit: boolean, // Is it segwit?
isTapscript: boolean, // Is taproot script path?
isP2SH: boolean, // Is it P2SH?
isP2WSH: boolean, // Is it P2WSH?
eccLib?: TinySecp256k1Interface) => {
isP2WSH: boolean) => {
finalScriptSig: Buffer | undefined;
finalScriptWitness: Buffer | Buffer[] | undefined;
};
Expand Down
36 changes: 10 additions & 26 deletions src/psbt.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class Psbt {
// We will disable exporting the Psbt when unsafe sign is active.
// because it is not BIP174 compliant.
__UNSAFE_SIGN_NONSEGWIT: false,
__EC_LIB: opts.eccLib,
};
if (this.data.inputs.length === 0) this.setVersion(2);
// Make data hidden when enumerating
Expand Down Expand Up @@ -134,7 +133,6 @@ class Psbt {
address = (0, address_1.fromOutputScript)(
output.script,
this.opts.network,
this.__CACHE.__EC_LIB,
);
} catch (_) {}
return {
Expand Down Expand Up @@ -237,11 +235,7 @@ class Psbt {
const { address } = outputData;
if (typeof address === 'string') {
const { network } = this.opts;
const script = (0, address_1.toOutputScript)(
address,
network,
this.__CACHE.__EC_LIB,
);
const script = (0, address_1.toOutputScript)(address, network);
outputData = Object.assign(outputData, { script });
}
const c = this.__CACHE;
Expand Down Expand Up @@ -297,7 +291,6 @@ class Psbt {
isP2SH,
isP2WSH,
isTapscript,
this.__CACHE.__EC_LIB,
);
if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig });
if (finalScriptWitness) {
Expand Down Expand Up @@ -326,13 +319,9 @@ class Psbt {
input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
input.witnessScript ||
redeemFromFinalWitnessScript(input.finalScriptWitness),
this.__CACHE,
);
const type = result.type === 'raw' ? '' : result.type + '-';
const mainType = classifyScript(
result.meaningfulScript,
this.__CACHE.__EC_LIB,
);
const mainType = classifyScript(result.meaningfulScript);
return type + mainType;
}
inputHasPubkey(inputIndex, pubkey) {
Expand Down Expand Up @@ -769,9 +758,9 @@ function isFinalized(input) {
return !!input.finalScriptSig || !!input.finalScriptWitness;
}
function isPaymentFactory(payment) {
return (script, eccLib) => {
return script => {
try {
payment({ output: script }, { eccLib });
payment({ output: script });
return true;
} catch (err) {
return false;
Expand Down Expand Up @@ -935,9 +924,8 @@ function getFinalScripts(
isP2SH,
isP2WSH,
isTapscript = false,
eccLib,
) {
const scriptType = classifyScript(script, eccLib);
const scriptType = classifyScript(script);
if (isTapscript || !canFinalize(input, script, scriptType))
throw new Error(`Can not finalize input #${inputIndex}`);
return prepareFinalScripts(
Expand Down Expand Up @@ -1053,7 +1041,6 @@ function getHashForSig(
'input',
input.redeemScript,
input.witnessScript,
cache,
);
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
hash = unsignedTx.hashForWitnessV0(
Expand All @@ -1072,7 +1059,7 @@ function getHashForSig(
prevout.value,
sighashType,
);
} else if (isP2TR(prevout.script, cache.__EC_LIB)) {
} else if (isP2TR(prevout.script)) {
const prevOuts = inputs.map((i, index) =>
getScriptAndAmountFromUtxo(index, i, cache),
);
Expand Down Expand Up @@ -1204,7 +1191,7 @@ function getScriptFromInput(inputIndex, input, cache) {
} else {
res.script = utxoScript;
}
const isTaproot = utxoScript && isP2TR(utxoScript, cache.__EC_LIB);
const isTaproot = utxoScript && isP2TR(utxoScript);
// Segregated Witness versions 0 or 1
if (input.witnessScript || isP2WPKH(res.script) || isTaproot) {
res.isSegwit = true;
Expand Down Expand Up @@ -1410,7 +1397,6 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
'input',
input.redeemScript,
input.witnessScript,
cache,
);
return pubkeyInScript(pubkey, meaningfulScript);
}
Expand All @@ -1422,7 +1408,6 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) {
'output',
output.redeemScript,
output.witnessScript,
cache,
);
return pubkeyInScript(pubkey, meaningfulScript);
}
Expand Down Expand Up @@ -1471,12 +1456,11 @@ function getMeaningfulScript(
ioType,
redeemScript,
witnessScript,
cache,
) {
const isP2SH = isP2SHScript(script);
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
const isP2WSH = isP2WSHScript(script);
const isP2TRScript = isP2TR(script, cache && cache.__EC_LIB);
const isP2TRScript = isP2TR(script);
if (isP2SH && redeemScript === undefined)
throw new Error('scriptPubkey is P2SH but redeemScript missing');
if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
Expand Down Expand Up @@ -1539,12 +1523,12 @@ function isTaprootSpend(scriptType) {
!!scriptType && (scriptType === 'taproot' || scriptType.startsWith('p2tr-'))
);
}
function classifyScript(script, eccLib) {
function classifyScript(script) {
if (isP2WPKH(script)) return 'witnesspubkeyhash';
if (isP2PKH(script)) return 'pubkeyhash';
if (isP2MS(script)) return 'multisig';
if (isP2PK(script)) return 'pubkey';
if (isP2TR(script, eccLib)) return 'taproot';
if (isP2TR(script)) return 'taproot';
return 'nonstandard';
}
function range(n) {
Expand Down
Loading