ecpair
Version:
Client-side Bitcoin JavaScript library ECPair
255 lines (254 loc) • 8.42 kB
JavaScript
'use strict';
var __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (
!desc ||
('get' in desc ? !m.__esModule : desc.writable || desc.configurable)
) {
desc = {
enumerable: true,
get: function () {
return m[k];
},
};
}
Object.defineProperty(o, k2, desc);
}
: function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
var __setModuleDefault =
(this && this.__setModuleDefault) ||
(Object.create
? function (o, v) {
Object.defineProperty(o, 'default', { enumerable: true, value: v });
}
: function (o, v) {
o['default'] = v;
});
var __importStar =
(this && this.__importStar) ||
function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null)
for (var k in mod)
if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, '__esModule', { value: true });
exports.networks = void 0;
exports.ECPairFactory = ECPairFactory;
const networks = __importStar(require('./networks.cjs'));
exports.networks = networks;
const types = __importStar(require('./types.cjs'));
const wif = __importStar(require('wif'));
const testecc_1 = require('./testecc.cjs');
const v = __importStar(require('valibot'));
const tools = __importStar(require('uint8array-tools'));
const ECPairOptionsSchema = v.optional(
v.object({
compressed: v.optional(v.boolean()),
network: v.optional(types.NetworkSchema),
// https://github.com/fabian-hiller/valibot/issues/243#issuecomment-2182514063
rng: v.optional(
v.pipe(
v.instance(Function),
v.transform((func) => {
return (arg) => {
const parsedArg = v.parse(v.optional(v.number()), arg);
const returnedValue = func(parsedArg);
const parsedReturn = v.parse(v.instance(Uint8Array), returnedValue);
return parsedReturn;
};
}),
),
),
}),
);
const toXOnly = (pubKey) =>
pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33);
function ECPairFactory(ecc) {
(0, testecc_1.testEcc)(ecc);
function isPoint(maybePoint) {
return ecc.isPoint(maybePoint);
}
function fromPrivateKey(buffer, options) {
v.parse(types.Buffer256Bit, buffer);
if (!ecc.isPrivate(buffer))
throw new TypeError('Private key not in range [1, n)');
v.parse(ECPairOptionsSchema, options);
return new ECPair(buffer, undefined, options);
}
function fromPublicKey(buffer, options) {
if (!ecc.isPoint(buffer)) {
throw new Error('Point not on the curve');
}
v.parse(ECPairOptionsSchema, options);
return new ECPair(undefined, buffer, options);
}
function fromWIF(wifString, network) {
const decoded = wif.decode(wifString);
const version = decoded.version;
// list of networks?
if (Array.isArray(network)) {
network = network
.filter((x) => {
return version === x.wif;
})
.pop();
if (!network) throw new Error('Unknown network version');
// otherwise, assume a network object (or default to bitcoin)
} else {
network = network || networks.bitcoin;
if (version !== network.wif) throw new Error('Invalid network version');
}
return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed,
network: network,
});
}
/**
* Generates a random ECPairInterface.
*
* Uses `crypto.getRandomValues` under the hood for options.rng function, which is still an experimental feature as of Node.js 18.19.0. To work around this you can do one of the following:
* 1. Use a polyfill for crypto.getRandomValues()
* 2. Use the `--experimental-global-webcrypto` flag when running node.js.
* 3. Pass in a custom rng function to generate random values.
*
* @param {ECPairOptions} options - Options for the ECPairInterface.
* @return {ECPairInterface} A random ECPairInterface.
*/
function makeRandom(options) {
v.parse(ECPairOptionsSchema, options);
if (options === undefined) options = {};
const rng =
options.rng || ((size) => crypto.getRandomValues(new Uint8Array(size)));
let d;
do {
d = rng(32);
v.parse(types.Buffer256Bit, d);
} while (!ecc.isPrivate(d));
return fromPrivateKey(d, options);
}
class ECPair {
__D;
__Q;
compressed;
network;
lowR;
constructor(__D, __Q, options) {
this.__D = __D;
this.__Q = __Q;
this.lowR = false;
if (options === undefined) options = {};
this.compressed =
options.compressed === undefined ? true : options.compressed;
this.network = options.network || networks.bitcoin;
if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed);
}
get privateKey() {
return this.__D;
}
get publicKey() {
if (!this.__Q) {
// It is not possible for both `__Q` and `__D` to be `undefined` at the same time.
// The factory methods guard for this.
const p = ecc.pointFromScalar(this.__D, this.compressed);
// It is not possible for `p` to be null.
// `fromPrivateKey()` checks that `__D` is a valid scalar.
this.__Q = p;
}
return this.__Q;
}
toWIF() {
if (!this.__D) throw new Error('Missing private key');
return wif.encode({
compressed: this.compressed,
privateKey: this.__D,
version: this.network.wif,
});
}
tweak(t) {
if (this.privateKey) return this.tweakFromPrivateKey(t);
return this.tweakFromPublicKey(t);
}
sign(hash, lowR) {
if (!this.__D) throw new Error('Missing private key');
if (lowR === undefined) lowR = this.lowR;
if (lowR === false) {
return ecc.sign(hash, this.__D);
} else {
let sig = ecc.sign(hash, this.__D);
const extraData = new Uint8Array(32);
let counter = 0;
// if first try is lowR, skip the loop
// for second try and on, add extra entropy counting up
while (sig[0] > 0x7f) {
counter++;
tools.writeUInt32(extraData, 0, counter, 'LE');
sig = ecc.sign(hash, this.__D, extraData);
}
return sig;
}
}
signSchnorr(hash) {
if (!this.privateKey) throw new Error('Missing private key');
if (!ecc.signSchnorr)
throw new Error('signSchnorr not supported by ecc library');
return ecc.signSchnorr(hash, this.privateKey);
}
verify(hash, signature) {
return ecc.verify(hash, this.publicKey, signature);
}
verifySchnorr(hash, signature) {
if (!ecc.verifySchnorr)
throw new Error('verifySchnorr not supported by ecc library');
return ecc.verifySchnorr(hash, this.publicKey.subarray(1, 33), signature);
}
tweakFromPublicKey(t) {
const xOnlyPubKey = toXOnly(this.publicKey);
const tweakedPublicKey = ecc.xOnlyPointAddTweak(xOnlyPubKey, t);
if (!tweakedPublicKey || tweakedPublicKey.xOnlyPubkey === null)
throw new Error('Cannot tweak public key!');
const parityByte = Uint8Array.from([
tweakedPublicKey.parity === 0 ? 0x02 : 0x03,
]);
return fromPublicKey(
tools.concat([parityByte, tweakedPublicKey.xOnlyPubkey]),
{
network: this.network,
compressed: this.compressed,
},
);
}
tweakFromPrivateKey(t) {
const hasOddY =
this.publicKey[0] === 3 ||
(this.publicKey[0] === 4 && (this.publicKey[64] & 1) === 1);
const privateKey = hasOddY
? ecc.privateNegate(this.privateKey)
: this.privateKey;
const tweakedPrivateKey = ecc.privateAdd(privateKey, t);
if (!tweakedPrivateKey) throw new Error('Invalid tweaked private key!');
return fromPrivateKey(tweakedPrivateKey, {
network: this.network,
compressed: this.compressed,
});
}
}
return {
isPoint,
fromPrivateKey,
fromPublicKey,
fromWIF,
makeRandom,
};
}