forked from tornado-packages/noble-curves
More refactoring
This commit is contained in:
parent
0fb78b7097
commit
9465e60d30
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user