More refactoring

This commit is contained in:
Paul Miller 2023-01-26 04:24:41 +00:00
parent 0fb78b7097
commit 9465e60d30
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
10 changed files with 208 additions and 249 deletions

@ -44,9 +44,8 @@ const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt
let p1, p2, oldp1, oldp2;
// /BLS
for (let item of [secp256k1, ed25519, ed448, P256, P384, P521, old_secp, noble_ed25519]) {
item.utils.precompute(8);
}
for (let item of [secp256k1, ed25519, ed448, P256, P384, P521]) item.utils.precompute(8);
for (let item of [old_secp, noble_ed25519]) item.utils.precompute(8);
const ONLY_NOBLE = process.argv[2] === 'noble';
@ -76,14 +75,14 @@ export const CURVES = {
sign: {
samples: 5000,
secp256k1_old: ({ msg, priv }) => old_secp.signSync(msg, priv),
secp256k1: ({ msg, priv }) => secp256k1.sign(msg, priv),
secp256k1: ({ msg, priv }) => secp256k1.sign(msg, priv).toCompactRawBytes(),
},
verify: {
samples: 1000,
secp256k1_old: ({ sig, msg, pub }) => {
return old_secp.verify(new old_secp.Signature(sig.r, sig.s), msg, pub);
},
secp256k1: ({ sig, msg, pub }) => secp256k1.verify(sig, msg, pub),
secp256k1: ({ sig, msg, pub }) => secp256k1.verify(sig.toCompactRawBytes(), msg, pub),
},
getSharedSecret: {
samples: 1000,

@ -11,10 +11,8 @@
* We are using Fp for private keys (shorter) and Fp for signatures (longer).
* Some projects may prefer to swap this relation, it is not supported for now.
*/
import * as mod from './modular.js';
import * as ut from './utils.js';
// Types require separate import
import { Hex, PrivKey } from './utils.js';
import { Field, hashToPrivateScalar } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, hexToBytes, bytesToHex } from './utils.js';
import * as htf from './hash-to-curve.js';
import {
CurvePointsType,
@ -43,32 +41,32 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
htfDefaults: htf.Opts;
};
x: bigint;
Fp: mod.Field<Fp>;
Fr: mod.Field<bigint>;
Fp2: mod.Field<Fp2> & {
Fp: Field<Fp>;
Fr: Field<bigint>;
Fp2: Field<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2;
};
Fp6: mod.Field<Fp6>;
Fp12: mod.Field<Fp12> & {
Fp6: Field<Fp6>;
Fp12: Field<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12;
};
htfDefaults: htf.Opts;
hash: ut.CHash; // Because we need outputLen for DRBG
hash: CHash; // Because we need outputLen for DRBG
randomBytes: (bytesLength?: number) => Uint8Array;
};
export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>;
Fr: mod.Field<bigint>;
Fp: mod.Field<Fp>;
Fp2: mod.Field<Fp2>;
Fp6: mod.Field<Fp6>;
Fp12: mod.Field<Fp12>;
Fr: Field<bigint>;
Fp: Field<Fp>;
Fp2: Field<Fp2>;
Fp6: Field<Fp6>;
Fp12: Field<Fp12>;
G1: CurvePointsRes<Fp>;
G2: CurvePointsRes<Fp2>;
Signature: SignatureCoder<Fp2>;
@ -115,7 +113,7 @@ export function bls<Fp2, Fp6, Fp12>(
): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const BLS_X_LEN = ut.bitLen(CURVE.x);
const BLS_X_LEN = bitLen(CURVE.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication
@ -142,7 +140,7 @@ export function bls<Fp2, Fp6, Fp12>(
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rz = Fp2.mul(t0, t4); // T0 * T4
if (ut.bitGet(CURVE.x, i)) {
if (bitGet(CURVE.x, i)) {
// Addition
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
@ -171,7 +169,7 @@ export function bls<Fp2, Fp6, Fp12>(
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
const E = ell[j];
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
if (ut.bitGet(x, i)) {
if (bitGet(x, i)) {
j += 1;
const F = ell[j];
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
@ -182,8 +180,8 @@ export function bls<Fp2, Fp6, Fp12>(
}
const utils = {
hexToBytes: ut.hexToBytes,
bytesToHex: ut.bytesToHex,
hexToBytes: hexToBytes,
bytesToHex: bytesToHex,
stringToBytes: htf.stringToBytes,
// TODO: do we need to export it here?
hashToField: (
@ -193,7 +191,7 @@ export function bls<Fp2, Fp6, Fp12>(
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(mod.hashToPrivateScalar(hash, CURVE.r)),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
};
@ -342,7 +340,7 @@ export function bls<Fp2, Fp6, Fp12>(
htfOpts?: htf.htfBasicOpts
): boolean {
// @ts-ignore
// console.log('verifyBatch', ut.bytesToHex(signature as any), messages, publicKeys.map(ut.bytesToHex));
// console.log('verifyBatch', bytesToHex(signature as any), messages, publicKeys.map(bytesToHex));
if (!messages.length) throw new Error('Expected non-empty messages array');
if (publicKeys.length !== messages.length)

@ -148,7 +148,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
// Though generator can be different (Fp2 / Fp6 for BLS).
export type BasicCurve<T> = {
export type AbstractCurve<T> = {
Fp: Field<T>; // Field over which we'll do calculations (Fp)
n: bigint; // Curve order, total count of valid points in the field
nBitLength?: number; // bit length of curve order
@ -161,7 +161,7 @@ export type BasicCurve<T> = {
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
};
export function validateBasicCurveOpts<FP, T>(curve: BasicCurve<FP> & T) {
export function validateAbsOpts<FP, T>(curve: AbstractCurve<FP> & T) {
validateField(curve.Fp);
for (const i of ['n', 'h'] as const) {
const val = curve[i];

@ -10,7 +10,7 @@ import {
Hex,
numberToBytesLE,
} from './utils.js';
import { Group, GroupConstructor, wNAF, BasicCurve, validateBasicCurveOpts } from './curve.js';
import { Group, GroupConstructor, wNAF, AbstractCurve, validateAbsOpts } from './curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0);
@ -19,7 +19,7 @@ const _2n = BigInt(2);
const _8n = BigInt(8);
// Edwards curves must declare params a & d.
export type CurveType = BasicCurve<bigint> & {
export type CurveType = AbstractCurve<bigint> & {
a: bigint; // curve param a
d: bigint; // curve param d
hash: FHash; // Hashing
@ -32,7 +32,7 @@ export type CurveType = BasicCurve<bigint> & {
};
function validateOpts(curve: CurveType) {
const opts = validateBasicCurveOpts(curve);
const opts = validateAbsOpts(curve);
if (typeof opts.hash !== 'function') throw new Error('Invalid hash function');
for (const i of ['a', 'd'] as const) {
const val = opts[i];

@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as ut from './utils.js';
import * as mod from './modular.js';
import type { Group, GroupConstructor } from './curve.js';
import { mod, Field } from './modular.js';
import { CHash, Hex, concatBytes, ensureBytes } from './utils.js';
export type Opts = {
// DST: a domain separation tag
@ -24,7 +24,7 @@ export type Opts = {
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: ut.CHash;
hash: CHash;
};
export function validateOpts(opts: Opts) {
@ -87,25 +87,25 @@ export function expand_message_xmd(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: ut.CHash
H: CHash
): Uint8Array {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
if (DST.length > 255) DST = H(ut.concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
if (DST.length > 255) DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen;
const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = ut.concatBytes(DST, i2osp(DST.length, 1));
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
const Z_pad = i2osp(0, r_in_bytes);
const l_i_b_str = i2osp(lenInBytes, 2);
const b = new Array<Uint8Array>(ell);
const b_0 = H(ut.concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(ut.concatBytes(b_0, i2osp(1, 1), DST_prime));
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
for (let i = 1; i <= ell; i++) {
const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
b[i] = H(ut.concatBytes(...args));
b[i] = H(concatBytes(...args));
}
const pseudo_random_bytes = ut.concatBytes(...b);
const pseudo_random_bytes = concatBytes(...b);
return pseudo_random_bytes.slice(0, lenInBytes);
}
@ -114,7 +114,7 @@ export function expand_message_xof(
DST: Uint8Array,
lenInBytes: number,
k: number,
H: ut.CHash
H: CHash
): Uint8Array {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
@ -162,14 +162,14 @@ export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bi
for (let j = 0; j < options.m; j++) {
const elm_offset = L * (j + i * options.m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
e[j] = mod.mod(os2ip(tv), options.p);
e[j] = mod(os2ip(tv), options.p);
}
u[i] = e;
}
return u;
}
export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[], T[], T[]]) {
export function isogenyMap<T, F extends Field<T>>(field: F, map: [T[], T[], T[], T[]]) {
// Make same order as in spec
const COEFF = map.map((i) => Array.from(i).reverse());
return (x: T, y: T) => {
@ -212,9 +212,9 @@ export function hashToCurve<T>(
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
hashToCurve(msg: ut.Hex, options?: htfBasicOpts) {
hashToCurve(msg: Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
msg = ensureBytes(msg);
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
return Point.fromAffine(mapToCurve(u[0]))
.add(Point.fromAffine(mapToCurve(u[1])))
@ -222,9 +222,9 @@ export function hashToCurve<T>(
},
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: ut.Hex, options?: htfBasicOpts) {
encodeToCurve(msg: Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
msg = ensureBytes(msg);
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
return Point.fromAffine(mapToCurve(u[0])).clearCofactor();
},

@ -1,5 +1,5 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js';
import { mod, pow } from './modular.js';
import { ensureBytes, numberToBytesLE, bytesToNumberLE } from './utils.js';
const _0n = BigInt(0);
@ -54,12 +54,12 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef);
const { P } = CURVE;
const modP = (a: bigint) => mod.mod(a, P);
const modP = (a: bigint) => mod(a, P);
const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength;
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => mod.pow(x, P - BigInt(2), P));
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
/**
* Checks for num to be in range:

@ -1,12 +1,10 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Poseidon Hash (https://eprint.iacr.org/2019/458.pdf)
// Website: https://www.poseidon-hash.info
import * as mod from './modular.js';
// NOTE: we currently don't provide any constants, since different implementations use diffferent constants
// For reference constants see './test/poseidon.test.js'
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
import { Field, validateField, FpPow } from './modular.js';
// We don't provide any constants, since different implementations use different constants.
// For reference constants see './test/poseidon.test.js'.
export type PoseidonOpts = {
Fp: mod.Field<bigint>;
Fp: Field<bigint>;
t: number;
roundsFull: number;
roundsPartial: number;
@ -18,7 +16,7 @@ export type PoseidonOpts = {
export function validateOpts(opts: PoseidonOpts) {
const { Fp } = opts;
mod.validateField(Fp);
validateField(Fp);
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
@ -32,7 +30,7 @@ export function validateOpts(opts: PoseidonOpts) {
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
const _sboxPower = BigInt(sboxPower);
let sboxFn = (n: bigint) => mod.FpPow(Fp, n, _sboxPower);
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
// Unwrapped sbox power for common cases (195->142μs)
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);

@ -2,8 +2,6 @@
const _0n = BigInt(0);
const _1n = BigInt(1);
const _2n = BigInt(2);
const str = (a: any): a is string => typeof a === 'string';
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
// We accept hex strings besides Uint8Array for simplicity
@ -35,14 +33,14 @@ export function numberToHexUnpadded(num: number | bigint): string {
}
export function hexToNumber(hex: string): bigint {
if (!str(hex)) throw new Error('hexToNumber: expected string, got ' + typeof hex);
if (typeof hex !== 'string') throw new Error('hexToNumber: expected string, got ' + typeof hex);
// Big Endian
return BigInt(`0x${hex}`);
}
// Caching slows it down 2-3x
export function hexToBytes(hex: string): Uint8Array {
if (!str(hex)) throw new Error('hexToBytes: expected string, got ' + typeof hex);
if (typeof hex !== 'string') throw new Error('hexToBytes: expected string, got ' + typeof hex);
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {

@ -1,34 +1,16 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Short Weierstrass curve. The formula is: y² = x³ + ax + b
import * as mod from './modular.js';
// import * as ut from './utils.js';
import {
Hex,
PrivKey,
bytesToHex,
bytesToNumberBE,
ensureBytes,
hexToBytes,
concatBytes,
bitMask,
numberToBytesBE,
CHash,
numberToHexUnpadded,
} from './utils.js';
import {
Group,
GroupConstructor,
wNAF,
BasicCurve as CBasicCurve,
validateBasicCurveOpts,
} from './curve.js';
import * as ut from './utils.js';
import { Hex, PrivKey, ensureBytes, CHash } from './utils.js';
import { Group, GroupConstructor, wNAF, AbstractCurve, validateAbsOpts } from './curve.js';
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
type EndomorphismOpts = {
beta: bigint;
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
};
export type BasicCurve<T> = CBasicCurve<T> & {
export type BasicCurve<T> = AbstractCurve<T> & {
// Params: a, b
a: T;
b: T;
@ -47,50 +29,6 @@ export type BasicCurve<T> = CBasicCurve<T> & {
clearCofactor?: (c: ProjConstructor<T>, point: ProjPointType<T>) => ProjPointType<T>;
};
// ASN.1 DER encoding utilities
class DERError extends Error {
constructor(message: string) {
super(message);
}
}
const DER = {
slice(s: string): string {
// Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)])
// Padding done by numberToHex
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s;
},
parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } {
if (data.length < 2 || data[0] !== 0x02) {
throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`);
}
const len = data[1];
const res = data.subarray(2, len + 2);
if (!len || res.length !== len) {
throw new DERError(`Invalid signature integer: wrong length`);
}
// Strange condition, its not about length, but about first bytes of number.
if (res[0] === 0x00 && res[1] <= 0x7f) {
throw new DERError('Invalid signature integer: trailing length');
}
return { data: bytesToNumberBE(res), left: data.subarray(len + 2) };
},
parseSig(data: Uint8Array): { r: bigint; s: bigint } {
if (data.length < 2 || data[0] != 0x30) {
throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`);
}
if (data[1] !== data.length - 2) {
throw new DERError('Invalid signature: incorrect length');
}
const { data: r, left: sBytes } = DER.parseInt(data.subarray(2));
const { data: s, left: rBytesLeft } = DER.parseInt(sBytes);
if (rBytesLeft.length) {
throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`);
}
return { r, s };
},
};
type Entropy = Hex | true;
export type SignOpts = { lowS?: boolean; extraEntropy?: Entropy; prehash?: boolean };
export type VerOpts = { lowS?: boolean; prehash?: boolean };
@ -154,7 +92,7 @@ export type CurvePointsType<T> = BasicCurve<T> & {
};
function validatePointOpts<T>(curve: CurvePointsType<T>) {
const opts = validateBasicCurveOpts(curve);
const opts = validateAbsOpts(curve);
const Fp = opts.Fp;
for (const i of ['a', 'b'] as const) {
if (!Fp.isValid(curve[i]))
@ -190,6 +128,55 @@ export type CurvePointsRes<T> = {
isWithinCurveOrder: (num: bigint) => boolean;
};
// ASN.1 DER encoding utilities
const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut;
const DER = {
// asn.1 DER encoding utils
Err: class DERErr extends Error {
constructor(m = '') {
super(m);
}
},
_parseInt(data: Uint8Array): { d: bigint; l: Uint8Array } {
const { Err: E } = DER;
if (data.length < 2 || data[0] !== 0x02) throw new E('Invalid signature integer tag');
const len = data[1];
const res = data.subarray(2, len + 2);
if (!len || res.length !== len) throw new E('Invalid signature integer: wrong length');
if (res[0] === 0x00 && res[1] <= 0x7f)
throw new E('Invalid signature integer: trailing length');
// ^ Weird condition: not about length, but about first bytes of number.
return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left
},
toSig(hex: string | Uint8Array): { r: bigint; s: bigint } {
// parse DER signature
const { Err: E } = DER;
const data = typeof hex === 'string' ? h2b(hex) : hex;
if (!(data instanceof Uint8Array)) throw new Error('ui8a expected');
let l = data.length;
if (l < 2 || data[0] != 0x30) throw new E('Invalid signature tag');
if (data[1] !== l - 2) throw new E('Invalid signature: incorrect length');
const { d: r, l: sBytes } = DER._parseInt(data.subarray(2));
const { d: s, l: rBytesLeft } = DER._parseInt(sBytes);
if (rBytesLeft.length) throw new E('Invalid signature: left bytes after parsing');
return { r, s };
},
hexFromSig(sig: { r: bigint; s: bigint }): string {
const slice = (s: string): string => (Number.parseInt(s[0], 16) >= 8 ? '00' + s : s); // slice DER
const h = (num: number | bigint) => {
const hex = num.toString(16);
return hex.length & 1 ? `0${hex}` : hex;
};
const s = slice(h(sig.s));
const r = slice(h(sig.r));
const shl = s.length / 2;
const rhl = r.length / 2;
const sl = h(shl);
const rl = h(rhl);
return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${s}`;
},
};
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0);
const _1n = BigInt(1);
@ -232,10 +219,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
} else if (typeof key === 'string') {
if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`);
// Validates individual octets
num = bytesToNumberBE(ensureBytes(key));
num = ut.bytesToNumberBE(ensureBytes(key));
} else if (key instanceof Uint8Array) {
if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`);
num = bytesToNumberBE(key);
num = ut.bytesToNumberBE(key);
} else {
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
}
@ -245,18 +232,18 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return num;
}
const pointPrecomputes = new Map<ProjectivePoint, ProjectivePoint[]>();
const pointPrecomputes = new Map<Point, Point[]>();
function assertPrjPoint(other: unknown) {
if (!(other instanceof ProjectivePoint)) throw new Error('ProjectivePoint expected');
if (!(other instanceof Point)) throw new Error('ProjectivePoint expected');
}
/**
* Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) (x=x/z, y=y/z)
* Default Point works in 2d / affine coordinates: (x, y)
* We're doing calculations in projective, because its operations don't require costly inversion.
*/
class ProjectivePoint implements ProjPointType<T> {
static readonly BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE);
static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO);
class Point implements ProjPointType<T> {
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
constructor(readonly px: T, readonly py: T, readonly pz: T) {
if (px == null || !Fp.isValid(px)) throw new Error('x required');
@ -264,14 +251,14 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
}
static fromAffine(p: AffinePoint<T>): ProjectivePoint {
static fromAffine(p: AffinePoint<T>): Point {
const { x, y } = p || {};
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');
if (p instanceof ProjectivePoint) throw new Error('projective point not allowed');
if (p instanceof Point) throw new Error('projective point not allowed');
const is0 = (i: T) => Fp.eql(i, Fp.ZERO);
// fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0)
if (is0(x) && is0(y)) return ProjectivePoint.ZERO;
return new ProjectivePoint(x, y, Fp.ONE);
if (is0(x) && is0(y)) return Point.ZERO;
return new Point(x, y, Fp.ONE);
}
get x(): T {
@ -287,24 +274,24 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* so this improves performance massively.
* Optimization: converts a list of projective points to a list of identical points with Z=1.
*/
static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] {
static normalizeZ(points: Point[]): Point[] {
const toInv = Fp.invertBatch(points.map((p) => p.pz));
return points.map((p, i) => p.toAffine(toInv[i])).map(ProjectivePoint.fromAffine);
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
}
/**
* Converts hash string or Uint8Array to Point.
* @param hex short/long ECDSA hex
*/
static fromHex(hex: Hex): ProjectivePoint {
const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ensureBytes(hex)));
static fromHex(hex: Hex): Point {
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes(hex)));
P.assertValidity();
return P;
}
// Multiplies generator point by privateKey.
static fromPrivateKey(privateKey: PrivKey) {
return ProjectivePoint.BASE.multiply(normalizePrivateKey(privateKey));
return Point.BASE.multiply(normalizePrivateKey(privateKey));
}
// We calculate precomputes for elliptic curve point multiplication
@ -343,7 +330,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/**
* Compare one point to another.
*/
equals(other: ProjectivePoint): boolean {
equals(other: Point): boolean {
assertPrjPoint(other);
const { px: X1, py: Y1, pz: Z1 } = this;
const { px: X2, py: Y2, pz: Z2 } = other;
@ -355,8 +342,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/**
* Flips point to one corresponding to (x, -y) in Affine coordinates.
*/
negate(): ProjectivePoint {
return new ProjectivePoint(this.px, Fp.neg(this.py), this.pz);
negate(): Point {
return new Point(this.px, Fp.neg(this.py), this.pz);
}
// Renes-Costello-Batina exception-free doubling formula.
@ -399,14 +386,14 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
Z3 = Fp.mul(t2, t1);
Z3 = Fp.add(Z3, Z3); // step 30
Z3 = Fp.add(Z3, Z3);
return new ProjectivePoint(X3, Y3, Z3);
return new Point(X3, Y3, Z3);
}
// Renes-Costello-Batina exception-free addition formula.
// There is 30% faster Jacobian formula, but it is not complete.
// https://eprint.iacr.org/2015/1060, algorithm 1
// Cost: 12M + 0S + 3*a + 3*b3 + 23add.
add(other: ProjectivePoint): ProjectivePoint {
add(other: Point): Point {
assertPrjPoint(other);
const { px: X1, py: Y1, pz: Z1 } = this;
const { px: X2, py: Y2, pz: Z2 } = other;
@ -453,20 +440,20 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
t0 = Fp.mul(t3, t1);
Z3 = Fp.mul(t5, Z3);
Z3 = Fp.add(Z3, t0); // step 40
return new ProjectivePoint(X3, Y3, Z3);
return new Point(X3, Y3, Z3);
}
subtract(other: ProjectivePoint) {
subtract(other: Point) {
return this.add(other.negate());
}
private is0() {
return this.equals(ProjectivePoint.ZERO);
return this.equals(Point.ZERO);
}
private wNAF(n: bigint): { p: ProjectivePoint; f: ProjectivePoint } {
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp: ProjectivePoint[]) => {
private wNAF(n: bigint): { p: Point; f: Point } {
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp: Point[]) => {
const toInv = Fp.invertBatch(comp.map((p) => p.pz));
return comp.map((p, i) => p.toAffine(toInv[i])).map(ProjectivePoint.fromAffine);
return comp.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
});
}
@ -475,8 +462,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* It's faster, but should only be used when you don't care about
* an exposed private key e.g. sig verification, which works over *public* keys.
*/
multiplyUnsafe(n: bigint): ProjectivePoint {
const I = ProjectivePoint.ZERO;
multiplyUnsafe(n: bigint): Point {
const I = Point.ZERO;
if (n === _0n) return I;
assertGE(n); // Will throw on 0
if (n === _1n) return this;
@ -487,7 +474,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
let k1p = I;
let k2p = I;
let d: ProjectivePoint = this;
let d: Point = this;
while (k1 > _0n || k2 > _0n) {
if (k1 & _1n) k1p = k1p.add(d);
if (k2 & _1n) k2p = k2p.add(d);
@ -497,7 +484,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
}
if (k1neg) k1p = k1p.negate();
if (k2neg) k2p = k2p.negate();
k2p = new ProjectivePoint(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
return k1p.add(k2p);
}
@ -509,10 +496,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param affinePoint optional point ot save cached precompute windows on it
* @returns New point
*/
multiply(scalar: bigint): ProjectivePoint {
multiply(scalar: bigint): Point {
assertGE(scalar);
let n = scalar;
let point: ProjectivePoint, fake: ProjectivePoint; // Fake point is used to const-time mult
let point: Point, fake: Point; // Fake point is used to const-time mult
const { endo } = CURVE;
if (endo) {
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
@ -520,7 +507,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
let { p: k2p, f: f2p } = this.wNAF(k2);
k1p = wnaf.constTimeNegate(k1neg, k1p);
k2p = wnaf.constTimeNegate(k2neg, k2p);
k2p = new ProjectivePoint(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
point = k1p.add(k2p);
fake = f1p.add(f2p);
} else {
@ -529,17 +516,17 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
fake = f;
}
// Normalize `z` for both points, but return only real one
return ProjectivePoint.normalizeZ([point, fake])[0];
return Point.normalizeZ([point, fake])[0];
}
/**
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly.
* @returns non-zero affine point
*/
multiplyAndAddUnsafe(Q: ProjectivePoint, a: bigint, b: bigint): ProjectivePoint | undefined {
const G = ProjectivePoint.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
const G = Point.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes
const mul = (
P: ProjectivePoint,
P: Point,
a: bigint // Select faster multiply() method
) => (a === _0n || a === _1n || !P.equals(G) ? P.multiplyUnsafe(a) : P.multiply(a));
const sum = mul(this, a).add(mul(Q, b));
@ -565,30 +552,30 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
isTorsionFree(): boolean {
const { h: cofactor, isTorsionFree } = CURVE;
if (cofactor === _1n) return true; // No subgroups, always torsion-free
if (isTorsionFree) return isTorsionFree(ProjectivePoint, this);
if (isTorsionFree) return isTorsionFree(Point, this);
throw new Error('isTorsionFree() has not been declared for the elliptic curve');
}
clearCofactor(): ProjectivePoint {
clearCofactor(): Point {
const { h: cofactor, clearCofactor } = CURVE;
if (cofactor === _1n) return this; // Fast-path
if (clearCofactor) return clearCofactor(ProjectivePoint, this) as ProjectivePoint;
if (clearCofactor) return clearCofactor(Point, this) as Point;
return this.multiplyUnsafe(CURVE.h);
}
toRawBytes(isCompressed = true): Uint8Array {
this.assertValidity();
return CURVE.toBytes(ProjectivePoint, this, isCompressed);
return CURVE.toBytes(Point, this, isCompressed);
}
toHex(isCompressed = true): string {
return bytesToHex(this.toRawBytes(isCompressed));
return ut.bytesToHex(this.toRawBytes(isCompressed));
}
}
const _bits = CURVE.nBitLength;
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
const wnaf = wNAF(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
return {
ProjectivePoint: ProjectivePoint as ProjConstructor<T>,
ProjectivePoint: Point as ProjConstructor<T>,
normalizePrivateKey,
weierstrassEquation,
isWithinCurveOrder,
@ -605,11 +592,11 @@ export interface SignatureType {
hasHighS(): boolean;
normalizeS(): SignatureType;
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
toCompactRawBytes(): Uint8Array;
toCompactHex(): string;
// DER-encoded
toDERRawBytes(isCompressed?: boolean): Uint8Array;
toDERHex(isCompressed?: boolean): string;
toCompactRawBytes(): Uint8Array;
toCompactHex(): string;
}
// Static methods
export type SignatureConstructor = {
@ -633,7 +620,7 @@ export type CurveType = BasicCurve<bigint> & {
};
function validateOpts(curve: CurveType) {
const opts = validateBasicCurveOpts(curve);
const opts = validateAbsOpts(curve);
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
throw new Error('Invalid hash function');
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
@ -699,7 +686,7 @@ function hmacDrbg<T>(
out.push(sl);
len += v.length;
}
return concatBytes(...out);
return ut.concatBytes(...out);
};
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset();
@ -721,6 +708,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
function isValidFieldElement(num: bigint): boolean {
return _0n < num && num < Fp.ORDER; // 0 is banned since it's not invertible FE
}
function modN(a: bigint) {
return mod.mod(a, CURVE_ORDER);
}
function invN(a: bigint) {
return mod.invert(a, CURVE_ORDER);
}
const {
ProjectivePoint: Point,
@ -732,7 +725,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
toBytes(c, point, isCompressed: boolean): Uint8Array {
const a = point.toAffine();
const x = Fp.toBytes(a.x);
const cat = concatBytes;
const cat = ut.concatBytes;
if (isCompressed) {
// TODO: hasEvenY
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
@ -746,7 +739,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const tail = bytes.subarray(1);
// this.assertValidity() is done inside of fromHex
if (len === compressedLen && (head === 0x02 || head === 0x03)) {
const x = bytesToNumberBE(tail);
const x = ut.bytesToNumberBE(tail);
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
@ -767,7 +760,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
},
});
const numToNByteStr = (num: bigint): string =>
bytesToHex(numberToBytesBE(num, CURVE.nByteLength));
ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength));
function isBiggerThanHalfOrder(number: bigint) {
const HALF = CURVE_ORDER >> _1n;
@ -775,10 +768,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}
function normalizeS(s: bigint) {
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
return isBiggerThanHalfOrder(s) ? modN(-s) : s;
}
// slice bytes num
const slcNum = (b: Uint8Array, from: number, to: number) => bytesToNumberBE(b.slice(from, to));
const slcNum = (b: Uint8Array, from: number, to: number) => ut.bytesToNumberBE(b.slice(from, to));
/**
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
@ -798,10 +791,9 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// DER encoded ECDSA signature
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
static fromDER(hex: Hex) {
const arr = hex instanceof Uint8Array;
if (typeof hex !== 'string' && !arr)
if (typeof hex !== 'string' && !(hex instanceof Uint8Array))
throw new Error(`Signature.fromDER: Expected string or Uint8Array`);
const { r, s } = DER.parseSig(ensureBytes(hex));
const { r, s } = DER.toSig(ensureBytes(hex));
return new Signature(r, s);
}
@ -824,10 +816,9 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
const prefix = (rec & 1) === 0 ? '02' : '03';
const R = Point.fromHex(prefix + numToNByteStr(radj));
const Fn = mod.Fp(N);
const ir = Fn.inv(radj); // r^-1
const u1 = Fn.mul(-h, ir); // -hr^-1
const u2 = Fn.mul(s, ir); // sr^-1
const ir = invN(radj); // r^-1
const u1 = modN(-h * ir); // -hr^-1
const u2 = modN(s * ir); // sr^-1
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
Q.assertValidity();
@ -840,38 +831,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}
normalizeS() {
return this.hasHighS()
? new Signature(this.r, mod.mod(-this.s, CURVE_ORDER), this.recovery)
: this;
return this.hasHighS() ? new Signature(this.r, modN(-this.s), this.recovery) : this;
}
// DER-encoded
toDERRawBytes() {
return hexToBytes(this.toDERHex());
return ut.hexToBytes(this.toDERHex());
}
toDERHex() {
const toHex = numberToHexUnpadded;
const sHex = DER.slice(toHex(this.s));
const rHex = DER.slice(toHex(this.r));
const sHexL = sHex.length / 2;
const rHexL = rHex.length / 2;
const sLen = toHex(sHexL);
const rLen = toHex(rHexL);
const length = toHex(rHexL + sHexL + 4);
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
return DER.hexFromSig({ r: this.r, s: this.s });
}
// padded bytes of r, then padded bytes of s
toCompactRawBytes() {
// const l = CURVE.nByteLength;
// const a = new Uint8Array(2 * l);
// a.set(numberToBytesBE(this.r, l), 0);
// a.set(numberToBytesBE(this.s, l), l);
// return a;
return hexToBytes(this.toCompactHex());
return ut.hexToBytes(this.toCompactHex());
}
toCompactHex() {
// return bytesToHex(this.toCompactRawBytes());
return numToNByteStr(this.r) + numToNByteStr(this.s);
}
}
@ -891,7 +866,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
*/
hashToPrivateKey: (hash: Hex): Uint8Array =>
numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength),
ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength),
/**
* Produces cryptographically secure private key from random of size (nBitLength+64)
@ -963,22 +938,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
// for some cases, since bytes.length * 8 is not actual bitLength.
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
const num = bytesToNumberBE(bytes); // check for == u8 done here
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here
return delta > 0 ? num >> BigInt(delta) : num;
};
const bits2int_modN =
CURVE.bits2int_modN ||
function (bytes: Uint8Array): bigint {
return mod.mod(bits2int(bytes), CURVE_ORDER); // can't use bytesToNumberBE here
return modN(bits2int(bytes)); // can't use bytesToNumberBE here
};
// NOTE: pads output with zero as per spec
const ORDER_MASK = bitMask(CURVE.nBitLength);
const ORDER_MASK = ut.bitMask(CURVE.nBitLength);
function int2octets(num: bigint): Uint8Array {
if (typeof num !== 'bigint') throw new Error('Expected bigint');
if (!(_0n <= num && num < ORDER_MASK))
throw new Error(`Expected number < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField!
return numberToBytesBE(num, CURVE.nByteLength);
return ut.numberToBytesBE(num, CURVE.nByteLength);
}
// Steps A, D of RFC6979 3.2
// Creates RFC6979 seed; converts msg/privKey to numbers.
@ -1013,31 +988,24 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
seedArgs.push(e);
}
// seed is constructed from private key and message
// Step D
// V, 0x00 are done in HmacDRBG constructor.
const seed = concatBytes(...seedArgs);
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
// Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes: Uint8Array): Signature | undefined {
// const { n } = CURVE;
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
if (!isWithinCurveOrder(k)) return;
// Important: all mod() calls in the function must be done over `n`
const Fn = mod.Fp(CURVE.n);
const ik = Fn.inv(k); // k^-1 mod n
if (!isWithinCurveOrder(k)) return; // Important: all mod() calls here must be done over N
const ik = invN(k); // k^-1 mod n
const q = Point.BASE.multiply(k).toAffine(); // q = Gk
const r = Fn.create(q.x); // r = q.x mod n
if (r === _0n) return; // r=0 invalid
const s = Fn.mul(ik, Fn.add(m, Fn.mul(d, r))); // s = k^-1(m + rd) mod n
if (s === _0n) return; // s=0 invalid
const r = modN(q.x); // r = q.x mod n
if (r === _0n) return;
const s = modN(ik * modN(m + modN(d * r))); // s = k^-1(m + rd) mod n
if (s === _0n) return;
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
let normS = s;
if (lowS && isBiggerThanHalfOrder(s)) {
// if lowS was passed, ensure s is always
normS = normalizeS(s); // in the bottom half of CURVE.n
recovery ^= 1;
normS = normalizeS(s); // if lowS was passed, ensure s is always
recovery ^= 1; // // in the bottom half of N
}
return new Signature(r, normS, recovery); // use normS, not s
}
@ -1093,12 +1061,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
signature.assertValidity();
_sig = signature;
} else {
// Signature can be represented in 2 ways: compact (64-byte) & DER (variable-length).
// Since DER can also be 64 bytes, we check for it first.
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
// Since DER can also be 2*nByteLength bytes, we check for it first.
try {
_sig = Signature.fromDER(signature as Hex);
} catch (derError) {
if (!(derError instanceof DERError)) throw derError;
if (!(derError instanceof DER.Err)) throw derError;
_sig = Signature.fromCompact(signature as Hex);
}
}
@ -1109,16 +1077,14 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}
if (opts.lowS && _sig.hasHighS()) return false;
if (opts.prehash) msgHash = CURVE.hash(msgHash);
const { n: N } = CURVE;
const { r, s } = _sig;
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
const Fn = mod.Fp(N);
const is = Fn.inv(s); // s^-1
const u1 = Fn.mul(h, is); // u1 = hs^-1 mod n
const u2 = Fn.mul(r, is); // u2 = rs^-1 mod n
const is = invN(s); // s^-1
const u1 = modN(h * is); // u1 = hs^-1 mod n
const u2 = modN(r * is); // u2 = rs^-1 mod n
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P
if (!R) return false;
const v = Fn.create(R.x);
const v = modN(R.x);
return v === r;
}
return {

@ -120,7 +120,7 @@ const TAGS = {
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
let tagP = TAGGED_HASH_PREFIXES[tag];
if (tagP === undefined) {
const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
@ -130,7 +130,6 @@ export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
return sha256(concatBytes(tagP, ...messages));
}
const tag = taggedHash;
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modN = (x: bigint) => mod(x, secp256k1N);
@ -148,7 +147,7 @@ function schnorrGetScalar(priv: bigint) {
return { point, scalar, x: toRawX(point) };
}
function lift_x(x: bigint): PointType<bigint> {
if (!fe(x)) throw new Error('not fe'); // Fail if x ≥ p.
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p.
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and
@ -157,7 +156,7 @@ function lift_x(x: bigint): PointType<bigint> {
return p;
}
function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNum(tag(TAGS.challenge, ...args)));
return modN(bytesToNum(taggedHash(TAGS.challenge, ...args)));
}
function schnorrGetPublicKey(privateKey: PrivKey): Uint8Array {
return toRawX(Gmul(privateKey)); // Let d' = int(sk). Fail if d' = 0 or d' ≥ n. Return bytes(d'⋅G)
@ -177,8 +176,8 @@ function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(3
const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32)));
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
// TODO: replace with proper xor?
const t = numTo32b(d ^ bytesToNum(tag(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
const rand = tag(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const t = numTo32b(d ^ bytesToNum(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const k_ = modN(bytesToNum(rand)); // Let k' = int(rand) mod n
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G.
@ -217,6 +216,7 @@ export const schnorr = {
getPublicKey: schnorrGetPublicKey,
sign: schnorrSign,
verify: schnorrVerify,
utils: { lift_x, int: bytesToNum, taggedHash },
};
const isoMap = htf.isogenyMap(