15 Commits
0.5.1 ... 0.5.2

Author SHA1 Message Date
Paul Miller
b9482bb17d Release 0.5.2. 2023-01-13 16:23:52 +01:00
Paul Miller
74475dca68 Fix lint 2023-01-13 16:02:07 +01:00
Paul Miller
f4cf21b9c8 tests: Use describe() 2023-01-13 16:00:13 +01:00
Paul Miller
5312d92b2c edwards: Fix isTorsionFree() 2023-01-13 15:58:04 +01:00
Paul Miller
d1770c0ac7 Rename test 2023-01-13 01:29:54 +01:00
Paul Miller
2d37edf7d1 Remove utils.mod(), utils.invert() 2023-01-13 01:26:00 +01:00
Paul Miller
36998fede8 Fix sqrt 2023-01-13 01:21:51 +01:00
Paul Miller
83960d445d Refactor: weierstrass assertValidity and others 2023-01-12 21:18:51 +01:00
Paul Miller
23cc2aa5d1 edwards, montgomery, weierstrass: refactor 2023-01-12 20:40:16 +01:00
Paul Miller
e45d7c2d25 utils: new util; ed448: small adjustment 2023-01-12 20:39:43 +01:00
Paul Miller
bfe929aac3 modular: Tonneli-Shanks refactoring 2023-01-12 20:38:42 +01:00
Paul Miller
069452dbe7 BLS, jubjub refactoring 2023-01-12 20:38:10 +01:00
Paul Miller
2e81f31d2e ECDSA: signUnhashed(), support for key recovery from bits 2/3 2023-01-08 20:02:04 +01:00
Paul Miller
9f7df0f13b ECDSA adjustments 2023-01-08 18:46:55 +01:00
Paul Miller
5600629bca Refactor 2023-01-08 18:02:54 +01:00
22 changed files with 3513 additions and 3404 deletions

View File

@@ -201,8 +201,6 @@ export type CurveFn = {
ExtendedPoint: ExtendedPointConstructor; ExtendedPoint: ExtendedPointConstructor;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
mod: (a: bigint, b?: bigint) => bigint;
invert: (number: bigint, modulo?: bigint) => bigint;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => { getExtendedPublicKey: (key: PrivKey) => {
head: Uint8Array; head: Uint8Array;
@@ -306,6 +304,7 @@ export type CurveFn = {
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType;
verify: ( verify: (
signature: Hex | SignatureType, signature: Hex | SignatureType,
msgHash: Hex, msgHash: Hex,
@@ -316,8 +315,6 @@ export type CurveFn = {
ProjectivePoint: ProjectivePointConstructor; ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
mod: (a: bigint) => bigint;
invert: (number: bigint) => bigint;
isValidPrivateKey(privateKey: PrivKey): boolean; isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array; hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.5.1", "version": "0.5.2",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"lib" "lib"
@@ -31,7 +31,7 @@
"@types/node": "18.11.3", "@types/node": "18.11.3",
"fast-check": "3.0.0", "fast-check": "3.0.0",
"micro-bmark": "0.2.0", "micro-bmark": "0.2.0",
"micro-should": "0.2.0", "micro-should": "0.3.0",
"prettier": "2.6.2", "prettier": "2.6.2",
"rollup": "2.75.5", "rollup": "2.75.5",
"typescript": "4.7.3" "typescript": "4.7.3"

View File

@@ -1,13 +1,26 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Barreto-Lynn-Scott Curves. A family of pairing friendly curves, with embedding degree = 12 or 24 /**
// NOTE: only 12 supported for now * BLS (Barreto-Lynn-Scott) family of pairing-friendly curves.
// Constructed from pair of weierstrass curves, based pairing logic * Implements BLS (Boneh-Lynn-Shacham) signatures.
* Consists of two curves: G1 and G2:
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
* - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in
* Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not.
* Pairing is used to aggregate and verify signatures.
* 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 mod from './modular.js';
import { ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitGet } from './utils.js'; import * as ut from './utils.js';
import * as utils from './utils.js'; // Types require separate import
// Types import { Hex, PrivKey } from './utils.js';
import { hexToBytes, bytesToHex, Hex, PrivKey } from './utils.js'; import {
import { htfOpts, stringToBytes, hash_to_field, expand_message_xmd } from './hash-to-curve.js'; htfOpts,
stringToBytes,
hash_to_field as hashToField,
expand_message_xmd as expandMessageXMD,
} from './hash-to-curve.js';
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js'; import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
type Fp = bigint; // Can be different field? type Fp = bigint; // Can be different field?
@@ -39,7 +52,7 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
finalExponentiate(num: Fp12): Fp12; finalExponentiate(num: Fp12): Fp12;
}; };
htfDefaults: htfOpts; htfDefaults: htfOpts;
hash: utils.CHash; // Because we need outputLen for DRBG hash: ut.CHash; // Because we need outputLen for DRBG
randomBytes: (bytesLength?: number) => Uint8Array; randomBytes: (bytesLength?: number) => Uint8Array;
}; };
@@ -80,12 +93,9 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
publicKeys: (Hex | PointType<Fp>)[] publicKeys: (Hex | PointType<Fp>)[]
) => boolean; ) => boolean;
utils: { utils: {
bytesToHex: typeof utils.bytesToHex;
hexToBytes: typeof utils.hexToBytes;
stringToBytes: typeof stringToBytes; stringToBytes: typeof stringToBytes;
hashToField: typeof hash_to_field; hashToField: typeof hashToField;
expandMessageXMD: typeof expand_message_xmd; expandMessageXMD: typeof expandMessageXMD;
mod: typeof mod.mod;
getDSTLabel: () => string; getDSTLabel: () => string;
setDSTLabel(newLabel: string): void; setDSTLabel(newLabel: string): void;
}; };
@@ -95,12 +105,9 @@ export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12> CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> { ): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options // Fields looks pretty specific for curve, so for now we need to pass them with options
const Fp = CURVE.Fp; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const Fr = CURVE.Fr; const BLS_X_LEN = ut.bitLen(CURVE.x);
const Fp2 = CURVE.Fp2; const groupLen = 32; // TODO: calculate; hardcoded for now
const Fp6 = CURVE.Fp6;
const Fp12 = CURVE.Fp12;
const BLS_X_LEN = bitLen(CURVE.x);
// Pre-compute coefficients for sparse multiplication // Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients // Point addition and point double calculations is reused for coefficients
@@ -125,7 +132,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 Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rz = Fp2.mul(t0, t4); // T0 * T4 Rz = Fp2.mul(t0, t4); // T0 * T4
if (bitGet(CURVE.x, i)) { if (ut.bitGet(CURVE.x, i)) {
// Addition // Addition
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
@@ -147,13 +154,14 @@ export function bls<Fp2, Fp6, Fp12>(
} }
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 { function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
const { x } = CURVE;
const Px = g1[0]; const Px = g1[0];
const Py = g1[1]; const Py = g1[1];
let f12 = Fp12.ONE; let f12 = Fp12.ONE;
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) { for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
const E = ell[j]; const E = ell[j];
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py)); f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
if (bitGet(CURVE.x, i)) { if (ut.bitGet(x, i)) {
j += 1; j += 1;
const F = ell[j]; const F = ell[j];
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py)); f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
@@ -163,81 +171,31 @@ export function bls<Fp2, Fp6, Fp12>(
return Fp12.conjugate(f12); return Fp12.conjugate(f12);
} }
// bls12-381 is a construction of two curves:
// 1. Fp: (x, y)
// 2. Fp₂: ((x₁, x₂+i), (y₁, y₂+i)) - (complex numbers)
//
// Bilinear Pairing (ate pairing) is used to combine both elements into a paired one:
// Fp₁₂ = e(Fp, Fp2)
// where Fp₁₂ = 12-degree polynomial
// Pairing is used to verify signatures.
//
// We are using Fp for private keys (shorter) and Fp2 for signatures (longer).
// Some projects may prefer to swap this relation, it is not supported for now.
const htfDefaults = { ...CURVE.htfDefaults };
function isWithinCurveOrder(num: bigint): boolean {
return 0 < num && num < CURVE.r;
}
const utils = { const utils = {
hexToBytes: hexToBytes, hexToBytes: ut.hexToBytes,
bytesToHex: bytesToHex, bytesToHex: ut.bytesToHex,
mod: mod.mod, stringToBytes: stringToBytes,
stringToBytes,
// TODO: do we need to export it here? // TODO: do we need to export it here?
hashToField: (msg: Uint8Array, count: number, options: Partial<typeof htfDefaults> = {}) => hashToField: (
hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }), msg: Uint8Array,
count: number,
options: Partial<typeof CURVE.htfDefaults> = {}
) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) => expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
expand_message_xmd(msg, DST, lenInBytes, H), expandMessageXMD(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)),
/** randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
* Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
* and convert them into private key, with the modulo bias being negligible. getDSTLabel: () => CURVE.htfDefaults.DST,
* As per FIPS 186 B.1.1.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from sha512, or a similar function
* @returns valid private key
*/
hashToPrivateKey: (hash: Hex): Uint8Array => {
hash = ensureBytes(hash);
if (hash.length < 40 || hash.length > 1024)
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
// hashToPrivateScalar(hash, CURVE.r)
// NOTE: doesn't add +/-1
const num = mod.mod(bytesToNumberBE(hash), CURVE.r);
// This should never happen
if (num === 0n || num === 1n) throw new Error('Invalid private key');
return numberToBytesBE(num, 32);
},
randomBytes: (bytesLength: number = 32): Uint8Array => CURVE.randomBytes(bytesLength),
// NIST SP 800-56A rev 3, section 5.6.1.2.2
// https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(40)),
getDSTLabel: () => htfDefaults.DST,
setDSTLabel(newLabel: string) { setDSTLabel(newLabel: string) {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) { if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
throw new TypeError('Invalid DST'); throw new TypeError('Invalid DST');
} }
htfDefaults.DST = newLabel; CURVE.htfDefaults.DST = newLabel;
}, },
}; };
function normalizePrivKey(key: PrivKey): bigint {
let int: bigint;
if (key instanceof Uint8Array && key.length === 32) int = bytesToNumberBE(key);
else if (typeof key === 'string' && key.length === 64) int = BigInt(`0x${key}`);
else if (typeof key === 'number' && key > 0 && Number.isSafeInteger(key)) int = BigInt(key);
else if (typeof key === 'bigint' && key > 0n) int = key;
else throw new TypeError('Expected valid private key');
int = mod.mod(int, CURVE.r);
if (!isWithinCurveOrder(int)) throw new Error('Private key must be 0 < key < CURVE.r');
return int;
}
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
const G1 = weierstrassPoints({ const G1 = weierstrassPoints({
n: Fr.ORDER, n: Fr.ORDER,
@@ -309,7 +267,7 @@ export function bls<Fp2, Fp6, Fp12>(
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 { function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
const msgPoint = normP2Hash(message); const msgPoint = normP2Hash(message);
msgPoint.assertValidity(); msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(normalizePrivKey(privateKey)); const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
if (message instanceof G2.Point) return sigPoint; if (message instanceof G2.Point) return sigPoint;
return Signature.encode(sigPoint); return Signature.encode(sigPoint);
} }

View File

@@ -2,28 +2,18 @@
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
// Differences from @noble/ed25519 1.7: // Differences from @noble/ed25519 1.7:
// 1. Different field element lengths in ed448: // 1. Variable field element lengths between EDDSA/ECDH:
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes // EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
// 2. Different addition formula (doubling is same) // 2. Different addition formula (doubling is same)
// 3. uvRatio differs between curves (half-expected, not only pow fn changes) // 3. uvRatio differs between curves (half-expected, not only pow fn changes)
// 4. Point decompression code is different too (unexpected), now using generalized formula // 4. Point decompression code is different (unexpected), now using generalized formula
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448 // 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
import * as mod from './modular.js'; import * as mod from './modular.js';
import { import * as ut from './utils.js';
bytesToHex, import { ensureBytes, Hex, PrivKey } from './utils.js';
concatBytes,
ensureBytes,
numberToBytesLE,
bytesToNumberLE,
hashToPrivateScalar,
BasicCurve,
validateOpts as utilOpts,
Hex,
PrivKey,
} from './utils.js'; // TODO: import * as u from './utils.js'?
import { Group, GroupConstructor, wNAF } from './group.js'; import { Group, GroupConstructor, wNAF } from './group.js';
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js'; import { hash_to_field as hashToField, htfOpts, validateHTFOpts } from './hash-to-curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0); const _0n = BigInt(0);
@@ -31,49 +21,41 @@ const _1n = BigInt(1);
const _2n = BigInt(2); const _2n = BigInt(2);
const _8n = BigInt(8); const _8n = BigInt(8);
export type CHash = { // Edwards curves must declare params a & d.
(message: Uint8Array | string): Uint8Array; export type CurveType = ut.BasicCurve<bigint> & {
blockLen: number;
outputLen: number;
create(): any;
};
export type CurveType = BasicCurve<bigint> & {
// Params: a, d // Params: a, d
a: bigint; a: bigint;
d: bigint; d: bigint;
// Hashes // Hashes
hash: CHash; // Because we need outputLen for DRBG // The interface, because we need outputLen for DRBG
hash: ut.CHash;
// CSPRNG
randomBytes: (bytesLength?: number) => Uint8Array; randomBytes: (bytesLength?: number) => Uint8Array;
// Probably clears bits in a byte array to produce a valid field element
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
// Used during hashing
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
// Ratio √(u/v)
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
preHash?: CHash; // RFC 8032 pre-hashing of messages to sign() / verify()
clearCofactor?: (c: ExtendedPointConstructor, point: ExtendedPointType) => ExtendedPointType; preHash?: ut.CHash;
// Hash to field opts // Hash to field options
htfDefaults?: htfOpts; htfDefaults?: htfOpts;
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint }; mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
}; };
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = utilOpts(curve); const opts = ut.validateOpts(curve);
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen)) if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen))
throw new Error('Invalid hash function'); throw new Error('Invalid hash function');
for (const i of ['a', 'd'] as const) { for (const i of ['a', 'd'] as const) {
if (typeof opts[i] !== 'bigint') const val = opts[i];
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`); if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
} }
for (const fn of ['randomBytes'] as const) { for (const fn of ['randomBytes'] as const) {
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`); if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
} }
for (const fn of [ for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio', 'mapToCurve'] as const) {
'adjustScalarBytes',
'domain',
'uvRatio',
'mapToCurve',
'clearCofactor',
] as const) {
if (opts[fn] === undefined) continue; // Optional if (opts[fn] === undefined) continue; // Optional
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`); if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
} }
@@ -96,7 +78,7 @@ export type SignatureConstructor = {
fromHex(hex: Hex): SignatureType; fromHex(hex: Hex): SignatureType;
}; };
// Instance // Instance of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointType extends Group<ExtendedPointType> { export interface ExtendedPointType extends Group<ExtendedPointType> {
readonly x: bigint; readonly x: bigint;
readonly y: bigint; readonly y: bigint;
@@ -109,7 +91,7 @@ export interface ExtendedPointType extends Group<ExtendedPointType> {
toAffine(invZ?: bigint): PointType; toAffine(invZ?: bigint): PointType;
clearCofactor(): ExtendedPointType; clearCofactor(): ExtendedPointType;
} }
// Static methods // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> { export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType;
fromAffine(p: PointType): ExtendedPointType; fromAffine(p: PointType): ExtendedPointType;
@@ -117,7 +99,7 @@ export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPoint
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[]; normalizeZ(points: ExtendedPointType[]): ExtendedPointType[];
} }
// Instance // Instance of Affine Point with coordinates in X, Y
export interface PointType extends Group<PointType> { export interface PointType extends Group<PointType> {
readonly x: bigint; readonly x: bigint;
readonly y: bigint; readonly y: bigint;
@@ -127,7 +109,7 @@ export interface PointType extends Group<PointType> {
isTorsionFree(): boolean; isTorsionFree(): boolean;
clearCofactor(): PointType; clearCofactor(): PointType;
} }
// Static methods // Static methods of Affine Point with coordinates in X, Y
export interface PointConstructor extends GroupConstructor<PointType> { export interface PointConstructor extends GroupConstructor<PointType> {
new (x: bigint, y: bigint): PointType; new (x: bigint, y: bigint): PointType;
fromHex(hex: Hex): PointType; fromHex(hex: Hex): PointType;
@@ -148,8 +130,6 @@ export type CurveFn = {
ExtendedPoint: ExtendedPointConstructor; ExtendedPoint: ExtendedPointConstructor;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
mod: (a: bigint) => bigint;
invert: (number: bigint) => bigint;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => { getExtendedPublicKey: (key: PrivKey) => {
head: Uint8Array; head: Uint8Array;
@@ -164,36 +144,31 @@ export type CurveFn = {
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation // NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation
export function twistedEdwards(curveDef: CurveType): CurveFn { export function twistedEdwards(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const Fp = CURVE.Fp as mod.Field<bigint>; const Fp = CURVE.Fp;
const CURVE_ORDER = CURVE.n; const CURVE_ORDER = CURVE.n;
const fieldLen = Fp.BYTES; // 32 (length of one field element) const maxGroupElement = _2n ** BigInt(CURVE.nByteLength * 8);
if (fieldLen > 2048) throw new Error('Field lengths over 2048 are not supported');
const groupLen = CURVE.nByteLength;
// (2n ** 256n).toString(16);
const maxGroupElement = _2n ** BigInt(groupLen * 8); // previous POW_2_256
// Function overrides // Function overrides
const { randomBytes } = CURVE; const { randomBytes } = CURVE;
const modP = Fp.create; const modP = Fp.create;
// sqrt(u/v) // sqrt(u/v)
function _uvRatio(u: bigint, v: bigint) { const uvRatio =
CURVE.uvRatio ||
((u: bigint, v: bigint) => {
try { try {
const value = Fp.sqrt(u * Fp.invert(v)); return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) };
return { isValid: true, value };
} catch (e) { } catch (e) {
return { isValid: false, value: _0n }; return { isValid: false, value: _0n };
} }
} });
const uvRatio = CURVE.uvRatio || _uvRatio; const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes); // NOOP
const domain =
const _adjustScalarBytes = (bytes: Uint8Array) => bytes; // NOOP CURVE.domain ||
const adjustScalarBytes = CURVE.adjustScalarBytes || _adjustScalarBytes; ((data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {
function _domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported'); if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
return data; return data;
} }); // NOOP
const domain = CURVE.domain || _domain; // NOOP
/** /**
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). * Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
@@ -336,25 +311,27 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Non-constant-time multiplication. Uses double-and-add algorithm. // Non-constant-time multiplication. Uses double-and-add algorithm.
// It's faster, but should only be used when you don't care about // It's faster, but should only be used when you don't care about
// an exposed private key e.g. sig verification. // an exposed private key e.g. sig verification.
// Allows scalar bigger than curve order, but less than 2^256
multiplyUnsafe(scalar: number | bigint): ExtendedPoint { multiplyUnsafe(scalar: number | bigint): ExtendedPoint {
let n = normalizeScalar(scalar, CURVE_ORDER, false); let n = normalizeScalar(scalar, CURVE_ORDER, false);
const G = ExtendedPoint.BASE;
const P0 = ExtendedPoint.ZERO; const P0 = ExtendedPoint.ZERO;
if (n === _0n) return P0; if (n === _0n) return P0;
if (this.equals(P0) || n === _1n) return this; if (this.equals(P0) || n === _1n) return this;
if (this.equals(G)) return this.wNAF(n); if (this.equals(ExtendedPoint.BASE)) return this.wNAF(n);
return wnaf.unsafeLadder(this, n); return wnaf.unsafeLadder(this, n);
} }
// Checks if point is of small order.
// If you add something to small order point, you will have "dirty"
// point with torsion component.
// Multiplies point by cofactor and checks if the result is 0. // Multiplies point by cofactor and checks if the result is 0.
isSmallOrder(): boolean { isSmallOrder(): boolean {
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO);
} }
// Multiplies point by a very big scalar n and checks if the result is 0. // Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0.
// Returns `false` is the point is dirty.
isTorsionFree(): boolean { isTorsionFree(): boolean {
return this.multiplyUnsafe(CURVE_ORDER).equals(ExtendedPoint.ZERO); return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO);
} }
// Converts Extended point to default (x, y) coordinates. // Converts Extended point to default (x, y) coordinates.
@@ -371,14 +348,12 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
return new Point(ax, ay); return new Point(ax, ay);
} }
clearCofactor(): ExtendedPoint { clearCofactor(): ExtendedPoint {
if (CURVE.h === _1n) return this; // Fast-path const { h: cofactor } = CURVE;
// clear_cofactor(P) := h_eff * P if (cofactor === _1n) return this;
// hEff = h for ed25519/ed448. Maybe worth moving to params? return this.multiplyUnsafe(cofactor);
if (CURVE.clearCofactor) return CURVE.clearCofactor(ExtendedPoint, this) as ExtendedPoint;
return this.multiplyUnsafe(CURVE.h);
} }
} }
const wnaf = wNAF(ExtendedPoint, groupLen * 8); const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8);
function assertExtPoint(other: unknown) { function assertExtPoint(other: unknown) {
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected'); if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
@@ -413,19 +388,20 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Uses algo from RFC8032 5.1.3. // Uses algo from RFC8032 5.1.3.
static fromHex(hex: Hex, strict = true) { static fromHex(hex: Hex, strict = true) {
const { d, a } = CURVE; const { d, a } = CURVE;
hex = ensureBytes(hex, fieldLen); const len = Fp.BYTES;
hex = ensureBytes(hex, len);
// 1. First, interpret the string as an integer in little-endian // 1. First, interpret the string as an integer in little-endian
// representation. Bit 255 of this number is the least significant // representation. Bit 255 of this number is the least significant
// bit of the x-coordinate and denote this value x_0. The // bit of the x-coordinate and denote this value x_0. The
// y-coordinate is recovered simply by clearing this bit. If the // y-coordinate is recovered simply by clearing this bit. If the
// resulting value is >= p, decoding fails. // resulting value is >= p, decoding fails.
const normed = hex.slice(); const normed = hex.slice();
const lastByte = hex[fieldLen - 1]; const lastByte = hex[len - 1];
normed[fieldLen - 1] = lastByte & ~0x80; normed[len - 1] = lastByte & ~0x80;
const y = bytesToNumberLE(normed); const y = ut.bytesToNumberLE(normed);
if (strict && y >= Fp.ORDER) throw new Error('Expected 0 < hex < P'); if (strict && y >= Fp.ORDER) throw new Error('Expected 0 < hex < P');
if (!strict && y >= maxGroupElement) throw new Error('Expected 0 < hex < 2**256'); if (!strict && y >= maxGroupElement) throw new Error('Expected 0 < hex < CURVE.n');
// 2. To recover the x-coordinate, the curve equation implies // 2. To recover the x-coordinate, the curve equation implies
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p). // Ed25519: x² = (y² - 1) / (d y² + 1) (mod p).
@@ -459,16 +435,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// When compressing point, it's enough to only store its y coordinate // When compressing point, it's enough to only store its y coordinate
// and use the last byte to encode sign of x. // and use the last byte to encode sign of x.
toRawBytes(): Uint8Array { toRawBytes(): Uint8Array {
const bytes = numberToBytesLE(this.y, fieldLen); const bytes = ut.numberToBytesLE(this.y, Fp.BYTES);
bytes[fieldLen - 1] |= this.x & _1n ? 0x80 : 0; bytes[Fp.BYTES - 1] |= this.x & _1n ? 0x80 : 0;
return bytes; return bytes;
} }
// Same as toRawBytes, but returns string. // Same as toRawBytes, but returns string.
toHex(): string { toHex(): string {
return bytesToHex(this.toRawBytes()); return ut.bytesToHex(this.toRawBytes());
} }
// Determines if point is in prime-order subgroup.
// Returns `false` is the point is dirty.
isTorsionFree(): boolean { isTorsionFree(): boolean {
return ExtendedPoint.fromAffine(this).isTorsionFree(); return ExtendedPoint.fromAffine(this).isTorsionFree();
} }
@@ -509,20 +487,20 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Encodes byte string to elliptic curve // Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) { static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve'); const { mapToCurve, htfDefaults } = CURVE;
msg = ensureBytes(msg); if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts); const u = hashToField(ensureBytes(msg), 2, { ...htfDefaults, ...options } as htfOpts);
const { x: x0, y: y0 } = CURVE.mapToCurve(u[0]); const { x: x0, y: y0 } = mapToCurve(u[0]);
const { x: x1, y: y1 } = CURVE.mapToCurve(u[1]); const { x: x1, y: y1 } = mapToCurve(u[1]);
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor(); const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
return p; return p;
} }
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) { static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve'); const { mapToCurve, htfDefaults } = CURVE;
msg = ensureBytes(msg); if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts); const u = hashToField(ensureBytes(msg), 1, { ...htfDefaults, ...options } as htfOpts);
const { x, y } = CURVE.mapToCurve(u[0]); const { x, y } = mapToCurve(u[0]);
return new Point(x, y).clearCofactor(); return new Point(x, y).clearCofactor();
} }
} }
@@ -536,9 +514,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
static fromHex(hex: Hex) { static fromHex(hex: Hex) {
const bytes = ensureBytes(hex, 2 * fieldLen); const len = Fp.BYTES;
const r = Point.fromHex(bytes.slice(0, fieldLen), false); const bytes = ensureBytes(hex, 2 * len);
const s = bytesToNumberLE(bytes.slice(fieldLen, 2 * fieldLen)); const r = Point.fromHex(bytes.slice(0, len), false);
const s = ut.bytesToNumberLE(bytes.slice(len, 2 * len));
return new Signature(r, s); return new Signature(r, s);
} }
@@ -551,17 +530,17 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
toRawBytes() { toRawBytes() {
return concatBytes(this.r.toRawBytes(), numberToBytesLE(this.s, fieldLen)); return ut.concatBytes(this.r.toRawBytes(), ut.numberToBytesLE(this.s, Fp.BYTES));
} }
toHex() { toHex() {
return bytesToHex(this.toRawBytes()); return ut.bytesToHex(this.toRawBytes());
} }
} }
// Little-endian SHA512 with modulo n // Little-endian SHA512 with modulo n
function modlLE(hash: Uint8Array): bigint { function modnLE(hash: Uint8Array): bigint {
return mod.mod(bytesToNumberLE(hash), CURVE_ORDER); return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER);
} }
/** /**
@@ -572,7 +551,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
*/ */
function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint { function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint {
if (!max) throw new TypeError('Specify max value'); if (!max) throw new TypeError('Specify max value');
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num); if (ut.isPositiveInt(num)) num = BigInt(num);
if (typeof num === 'bigint' && num < max) { if (typeof num === 'bigint' && num < max) {
if (strict) { if (strict) {
if (_0n < num) return num; if (_0n < num) return num;
@@ -580,37 +559,32 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (_0n <= num) return num; if (_0n <= num) return num;
} }
} }
throw new TypeError('Expected valid scalar: 0 < scalar < max'); throw new TypeError(`Expected valid scalar: 0 < scalar < ${max}`);
}
function checkPrivateKey(key: PrivKey) {
// Normalize bigint / number / string to Uint8Array
key =
typeof key === 'bigint' || typeof key === 'number'
? numberToBytesLE(normalizeScalar(key, maxGroupElement), groupLen)
: ensureBytes(key);
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes, got ${key.length}`);
return key;
}
// Takes 64 bytes
function getKeyFromHash(hashed: Uint8Array) {
// First 32 bytes of 64b uniformingly random input are taken,
// clears 3 bits of it to produce a random field element.
const head = adjustScalarBytes(hashed.slice(0, groupLen));
// Second 32 bytes is called key prefix (5.1.6)
const prefix = hashed.slice(groupLen, 2 * groupLen);
// The actual private scalar
const scalar = modlLE(head);
// Point on Edwards curve aka public key
const point = Point.BASE.multiply(scalar);
const pointBytes = point.toRawBytes();
return { head, prefix, scalar, point, pointBytes };
} }
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */ /** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
function getExtendedPublicKey(key: PrivKey) { function getExtendedPublicKey(key: PrivKey) {
return getKeyFromHash(CURVE.hash(checkPrivateKey(key))); const groupLen = CURVE.nByteLength;
// Normalize bigint / number / string to Uint8Array
const keyb =
typeof key === 'bigint' || typeof key === 'number'
? ut.numberToBytesLE(normalizeScalar(key, maxGroupElement), groupLen)
: key;
// Hash private key with curve's hash function to produce uniformingly random input
// We check byte lengths e.g.: ensureBytes(64, hash(ensureBytes(32, key)))
const hashed = ensureBytes(CURVE.hash(ensureBytes(keyb, groupLen)), 2 * groupLen);
// First half's bits are cleared to produce a random field element.
const head = adjustScalarBytes(hashed.slice(0, groupLen));
// Second half is called key prefix (5.1.6)
const prefix = hashed.slice(groupLen, 2 * groupLen);
// The actual private scalar
const scalar = modnLE(head);
// Point on Edwards curve aka public key
const point = Point.BASE.multiply(scalar);
// Uint8Array representation
const pointBytes = point.toRawBytes();
return { head, prefix, scalar, point, pointBytes };
} }
/** /**
@@ -625,7 +599,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const EMPTY = new Uint8Array(); const EMPTY = new Uint8Array();
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) { function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) {
context = ensureBytes(context); context = ensureBytes(context);
return modlLE(CURVE.hash(domain(message, context, !!CURVE.preHash))); return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash)));
} }
/** Signs message with privateKey. RFC8032 5.1.6 */ /** Signs message with privateKey. RFC8032 5.1.6 */
@@ -633,9 +607,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
message = ensureBytes(message); message = ensureBytes(message);
if (CURVE.preHash) message = CURVE.preHash(message); if (CURVE.preHash) message = CURVE.preHash(message);
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey); const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey);
const r = hashDomainToScalar(concatBytes(prefix, message), context); const r = hashDomainToScalar(ut.concatBytes(prefix, message), context);
const R = Point.BASE.multiply(r); // R = rG const R = Point.BASE.multiply(r); // R = rG
const k = hashDomainToScalar(concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg) const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg)
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp
return new Signature(R, s).toRawBytes(); return new Signature(R, s).toRawBytes();
} }
@@ -672,13 +646,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const { r, s } = sig; const { r, s } = sig;
const SB = ExtendedPoint.BASE.multiplyUnsafe(s); const SB = ExtendedPoint.BASE.multiplyUnsafe(s);
const k = hashDomainToScalar( const k = hashDomainToScalar(
concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message), ut.concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message),
context context
); );
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k); const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k);
const RkA = ExtendedPoint.fromAffine(r).add(kA); const RkA = ExtendedPoint.fromAffine(r).add(kA);
// [8][S]B = [8]R + [8][k]A' // [8][S]B = [8]R + [8][k]A'
return RkA.subtract(SB).multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO);
} }
// Enable precomputes. Slows down first publicKey computation by 20ms. // Enable precomputes. Slows down first publicKey computation by 20ms.
@@ -686,19 +660,16 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const utils = { const utils = {
getExtendedPublicKey, getExtendedPublicKey,
mod: modP,
invert: Fp.invert,
/** /**
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare). * Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
*/ */
hashToPrivateScalar: (hash: Hex): bigint => hashToPrivateScalar(hash, CURVE_ORDER, true), hashToPrivateScalar: (hash: Hex): bigint => ut.hashToPrivateScalar(hash, CURVE_ORDER, true),
/** /**
* ed25519 private keys are uniform 32-bit strings. We do not need to check for * ed25519 private keys are uniform 32-bit strings. We do not need to check for
* modulo bias like we do in secp256k1 randomPrivateKey() * modulo bias like we do in secp256k1 randomPrivateKey()
*/ */
randomPrivateKey: (): Uint8Array => randomBytes(fieldLen), randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),
/** /**
* We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT * We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT

View File

@@ -55,6 +55,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
// prettier-ignore // prettier-ignore
let x = _0n, y = _1n, u = _1n, v = _0n; let x = _0n, y = _1n, u = _1n, v = _0n;
while (a !== _0n) { while (a !== _0n) {
// JIT applies optimization if those two lines follow each other
const q = b / a; const q = b / a;
const r = b % a; const r = b % a;
const m = x - u * q; const m = x - u * q;
@@ -68,7 +69,8 @@ export function invert(number: bigint, modulo: bigint): bigint {
} }
// Tonelli-Shanks algorithm // Tonelli-Shanks algorithm
// https://eprint.iacr.org/2012/685.pdf (page 12) // Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
export function tonelliShanks(P: bigint) { export function tonelliShanks(P: bigint) {
// Legendre constant: used to calculate Legendre symbol (a | p), // Legendre constant: used to calculate Legendre symbol (a | p),
// which denotes the value of a^((p-1)/2) (mod p). // which denotes the value of a^((p-1)/2) (mod p).
@@ -79,7 +81,7 @@ export function tonelliShanks(P: bigint) {
let Q: bigint, S: number, Z: bigint; let Q: bigint, S: number, Z: bigint;
// Step 1: By factoring out powers of 2 from p - 1, // Step 1: By factoring out powers of 2 from p - 1,
// find q and s such that p - 1 = q2s with q odd // find q and s such that p - 1 = q*(2^s) with q odd
for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++); for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
// Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq // Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
@@ -98,31 +100,30 @@ export function tonelliShanks(P: bigint) {
// Slow-path // Slow-path
const Q1div2 = (Q + _1n) / _2n; const Q1div2 = (Q + _1n) / _2n;
return function tonelliSlow<T>(Fp: Field<T>, n: T): T { return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
// Step 0: Check that n is indeed a square: (n | p) must be ≡ 1 // Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
if (Fp.pow(n, legendreC) !== Fp.ONE) throw new Error('Cannot find square root'); if (Fp.pow(n, legendreC) === Fp.negate(Fp.ONE)) throw new Error('Cannot find square root');
let s = S; let r = S;
// TODO: will fail at Fp2/etc
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
let x = Fp.pow(n, Q1div2); // first guess at the square root
let b = Fp.pow(n, Q); // first guess at the fudge factor
let c = pow(Z, Q, P); while (!Fp.equals(b, Fp.ONE)) {
let r = Fp.pow(n, Q1div2); if (Fp.equals(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
let t = Fp.pow(n, Q); // Find m such b^(2^m)==1
let m = 1;
let t2 = Fp.ZERO; for (let t2 = Fp.square(b); m < r; m++) {
while (!Fp.equals(Fp.sub(t, Fp.ONE), Fp.ZERO)) { if (Fp.equals(t2, Fp.ONE)) break;
t2 = Fp.square(t); t2 = Fp.square(t2); // t2 *= t2
let i;
for (i = 1; i < s; i++) {
// stop if t2-1 == 0
if (Fp.equals(Fp.sub(t2, Fp.ONE), Fp.ZERO)) break;
// t2 *= t2
t2 = Fp.square(t2);
} }
let b = pow(c, BigInt(1 << (s - i - 1)), P); // NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
r = Fp.mul(r, b); const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
c = mod(b * b, P); g = Fp.square(ge); // g = ge * ge
t = Fp.mul(t, c); x = Fp.mul(x, ge); // x *= ge
s = i; b = Fp.mul(b, g); // b *= g
r = m;
} }
return r; return x;
}; };
} }

View File

@@ -1,11 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js'; import * as mod from './modular.js';
import { import { ensureBytes, numberToBytesLE, bytesToNumberLE, isPositiveInt } from './utils.js';
ensureBytes,
numberToBytesLE,
bytesToNumberLE,
// nLength,
} from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
@@ -38,7 +33,7 @@ function validateOpts(curve: CurveType) {
} }
for (const i of ['montgomeryBits', 'nByteLength'] as const) { for (const i of ['montgomeryBits', 'nByteLength'] as const) {
if (curve[i] === undefined) continue; // Optional if (curve[i] === undefined) continue; // Optional
if (!Number.isSafeInteger(curve[i])) if (!isPositiveInt(curve[i]))
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
} }
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) { for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {

View File

@@ -40,19 +40,24 @@ export type BasicCurve<T> = {
allowInfinityPoint?: boolean; allowInfinityPoint?: boolean;
}; };
// Bans floats and integers above 2^53-1
export function isPositiveInt(num: any): num is number {
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0;
}
export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) { export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) {
mod.validateField(curve.Fp); mod.validateField(curve.Fp);
for (const i of ['n', 'h'] as const) { for (const i of ['n', 'h'] as const) {
if (typeof curve[i] !== 'bigint') const val = curve[i];
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
} }
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element'); if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element'); if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
for (const i of ['nBitLength', 'nByteLength'] as const) { for (const i of ['nBitLength', 'nByteLength'] as const) {
if (curve[i] === undefined) continue; // Optional const val = curve[i];
if (!Number.isSafeInteger(curve[i])) if (val === undefined) continue; // Optional
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); if (!isPositiveInt(val)) throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
} }
// Set defaults // Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const); return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
@@ -144,21 +149,22 @@ export function nLength(n: bigint, nBitLength?: number) {
} }
/** /**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF * Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being neglible. * and convert them into private scalar, with the modulo bias being neglible.
* As per FIPS 186 B.4.1. * Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from sha512, or a similar function * @param hash hash output from SHA3 or a similar function
* @returns valid private scalar * @returns valid private scalar
*/ */
export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint { export function hashToPrivateScalar(hash: Hex, groupOrder: bigint, isLE = false): bigint {
hash = ensureBytes(hash); hash = ensureBytes(hash);
const orderLen = nLength(CURVE_ORDER).nByteLength; const hashLen = hash.length;
const minLen = orderLen + 8; const minLen = nLength(groupOrder).nByteLength + 8;
if (orderLen < 16 || hash.length < minLen || hash.length > 1024) if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error('Expected valid bytes of private key as per FIPS 186'); throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod.mod(num, CURVE_ORDER - _1n) + _1n; return mod.mod(num, groupOrder - _1n) + _1n;
} }
export function equalBytes(b1: Uint8Array, b2: Uint8Array) { export function equalBytes(b1: Uint8Array, b2: Uint8Array) {

View File

@@ -1,28 +1,16 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Short Weierstrass curve. The formula is: y² = x³ + ax + b // Short Weierstrass curve. The formula is: y² = x³ + ax + b
// TODO: sync vs async naming
// TODO: default randomBytes
// Differences from @noble/secp256k1 1.7: // Differences from @noble/secp256k1 1.7:
// 1. Different double() formula (but same addition) // 1. Different double() formula (but same addition)
// 2. Different sqrt() function // 2. Different sqrt() function
// 3. truncateHash() truncateOnly mode // 3. truncateHash() truncateOnly mode
// 4. DRBG supports outputLen bigger than outputLen of hmac // 4. DRBG supports outputLen bigger than outputLen of hmac
// 5. Support for different hash functions
import * as mod from './modular.js'; import * as mod from './modular.js';
import { import * as ut from './utils.js';
bytesToHex, import { bytesToHex, Hex, PrivKey } from './utils.js';
bytesToNumberBE,
concatBytes,
ensureBytes,
hexToBytes,
hexToNumber,
numberToHexUnpadded,
hashToPrivateScalar,
Hex,
PrivKey,
} from './utils.js';
import * as utils from './utils.js';
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js'; import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js';
import { Group, GroupConstructor, wNAF } from './group.js'; import { Group, GroupConstructor, wNAF } from './group.js';
@@ -31,39 +19,45 @@ type EndomorphismOpts = {
beta: bigint; beta: bigint;
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint }; splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
}; };
export type BasicCurve<T> = utils.BasicCurve<T> & { export type BasicCurve<T> = ut.BasicCurve<T> & {
// Params: a, b // Params: a, b
a: T; a: T;
b: T; b: T;
// TODO: move into options?
// Optional params
// Executed before privkey validation. Useful for P521 with var-length priv key
normalizePrivateKey?: (key: PrivKey) => PrivKey; normalizePrivateKey?: (key: PrivKey) => PrivKey;
// Whether to execute modular division on a private key, useful for bls curves with cofactor > 1
wrapPrivateKey?: boolean;
// Endomorphism options for Koblitz curves // Endomorphism options for Koblitz curves
endo?: EndomorphismOpts; endo?: EndomorphismOpts;
// Torsions, can be optimized via endomorphisms // When a cofactor != 1, there can be an effective methods to:
// 1. Determine whether a point is torsion-free
isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean; isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean;
// 2. Clear torsion component
clearCofactor?: ( clearCofactor?: (
c: ProjectiveConstructor<T>, c: ProjectiveConstructor<T>,
point: ProjectivePointType<T> point: ProjectivePointType<T>
) => ProjectivePointType<T>; ) => ProjectivePointType<T>;
// Hash to field opts // Hash to field options
htfDefaults?: htfOpts; htfDefaults?: htfOpts;
mapToCurve?: (scalar: bigint[]) => { x: T; y: T }; mapToCurve?: (scalar: bigint[]) => { x: T; y: T };
}; };
// DER encoding utilities
// ASN.1 DER encoding utilities
class DERError extends Error { class DERError extends Error {
constructor(message: string) { constructor(message: string) {
super(message); super(message);
} }
} }
function sliceDER(s: string): string { 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)]) // Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)])
// Padding done by numberToHex // Padding done by numberToHex
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s; return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s;
} },
parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } {
function parseDERInt(data: Uint8Array) {
if (data.length < 2 || data[0] !== 0x02) { if (data.length < 2 || data[0] !== 0x02) {
throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`); throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`);
} }
@@ -76,31 +70,26 @@ function parseDERInt(data: Uint8Array) {
if (res[0] === 0x00 && res[1] <= 0x7f) { if (res[0] === 0x00 && res[1] <= 0x7f) {
throw new DERError('Invalid signature integer: trailing length'); throw new DERError('Invalid signature integer: trailing length');
} }
return { data: bytesToNumberBE(res), left: data.subarray(len + 2) }; return { data: ut.bytesToNumberBE(res), left: data.subarray(len + 2) };
} },
parseSig(data: Uint8Array): { r: bigint; s: bigint } {
function parseDERSignature(data: Uint8Array) {
if (data.length < 2 || data[0] != 0x30) { if (data.length < 2 || data[0] != 0x30) {
throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`); throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`);
} }
if (data[1] !== data.length - 2) { if (data[1] !== data.length - 2) {
throw new DERError('Invalid signature: incorrect length'); throw new DERError('Invalid signature: incorrect length');
} }
const { data: r, left: sBytes } = parseDERInt(data.subarray(2)); const { data: r, left: sBytes } = DER.parseInt(data.subarray(2));
const { data: s, left: rBytesLeft } = parseDERInt(sBytes); const { data: s, left: rBytesLeft } = DER.parseInt(sBytes);
if (rBytesLeft.length) { if (rBytesLeft.length) {
throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`); throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`);
} }
return { r, s }; return { r, s };
} },
};
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0);
const _1n = BigInt(1);
const _3n = BigInt(3);
type Entropy = Hex | true; type Entropy = Hex | true;
type SignOpts = { lowS?: boolean; extraEntropy?: Entropy }; export type SignOpts = { lowS?: boolean; extraEntropy?: Entropy };
/** /**
* ### Design rationale for types * ### Design rationale for types
@@ -124,7 +113,7 @@ type SignOpts = { lowS?: boolean; extraEntropy?: Entropy };
* TODO: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol * TODO: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol
*/ */
// Instance // Instance for 3d XYZ points
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> { export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
readonly x: T; readonly x: T;
readonly y: T; readonly y: T;
@@ -133,14 +122,14 @@ export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>; multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
toAffine(invZ?: T): PointType<T>; toAffine(invZ?: T): PointType<T>;
} }
// Static methods // Static methods for 3d XYZ points
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> { export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
new (x: T, y: T, z: T): ProjectivePointType<T>; new (x: T, y: T, z: T): ProjectivePointType<T>;
fromAffine(p: PointType<T>): ProjectivePointType<T>; fromAffine(p: PointType<T>): ProjectivePointType<T>;
toAffineBatch(points: ProjectivePointType<T>[]): PointType<T>[]; toAffineBatch(points: ProjectivePointType<T>[]): PointType<T>[];
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[]; normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
} }
// Instance // Instance for 2d XY points
export interface PointType<T> extends Group<PointType<T>> { export interface PointType<T> extends Group<PointType<T>> {
readonly x: T; readonly x: T;
readonly y: T; readonly y: T;
@@ -151,7 +140,7 @@ export interface PointType<T> extends Group<PointType<T>> {
assertValidity(): void; assertValidity(): void;
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined; multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined;
} }
// Static methods // Static methods for 2d XY points
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> { export interface PointConstructor<T> extends GroupConstructor<PointType<T>> {
new (x: T, y: T): PointType<T>; new (x: T, y: T): PointType<T>;
fromHex(hex: Hex): PointType<T>; fromHex(hex: Hex): PointType<T>;
@@ -167,7 +156,7 @@ export type CurvePointsType<T> = BasicCurve<T> & {
}; };
function validatePointOpts<T>(curve: CurvePointsType<T>) { function validatePointOpts<T>(curve: CurvePointsType<T>) {
const opts = utils.validateOpts(curve); const opts = ut.validateOpts(curve);
const Fp = opts.Fp; const Fp = opts.Fp;
for (const i of ['a', 'b'] as const) { for (const i of ['a', 'b'] as const) {
if (!Fp.isValid(curve[i])) if (!Fp.isValid(curve[i]))
@@ -206,22 +195,14 @@ export type CurvePointsRes<T> = {
isWithinCurveOrder: (num: bigint) => boolean; isWithinCurveOrder: (num: bigint) => boolean;
}; };
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0);
const _1n = BigInt(1);
const _3n = BigInt(3);
export function weierstrassPoints<T>(opts: CurvePointsType<T>) { export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const CURVE = validatePointOpts(opts); const CURVE = validatePointOpts(opts);
const Fp = CURVE.Fp; const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
// Lengths
// All curves has same field / group length as for now, but it can be different for other curves
const { nByteLength, nBitLength } = CURVE;
const groupLen = nByteLength;
// Not using ** operator with bigints for old engines.
// 2n ** (8n * 32n) == 2n << (8n * 32n - 1n)
//const FIELD_MASK = _2n << (_8n * BigInt(fieldLen) - _1n);
// function numToFieldStr(num: bigint): string {
// if (typeof num !== 'bigint') throw new Error('Expected bigint');
// if (!(_0n <= num && num < FIELD_MASK)) throw new Error(`Expected number < 2^${fieldLen * 8}`);
// return num.toString(16).padStart(2 * fieldLen, '0');
// }
/** /**
* y² = x³ + ax + b: Short weierstrass curve formula * y² = x³ + ax + b: Short weierstrass curve formula
@@ -234,35 +215,48 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
} }
// Valid group elements reside in range 1..n-1
function isWithinCurveOrder(num: bigint): boolean { function isWithinCurveOrder(num: bigint): boolean {
return _0n < num && num < CURVE.n; return _0n < num && num < CURVE.n;
} }
/**
* Validates if a private key is valid and converts it to bigint form.
* Supports two options, that are passed when CURVE is initialized:
* - `normalizePrivateKey()` executed before all checks
* - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n`
*/
function normalizePrivateKey(key: PrivKey): bigint { function normalizePrivateKey(key: PrivKey): bigint {
if (typeof CURVE.normalizePrivateKey === 'function') { const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n: order } = CURVE;
key = CURVE.normalizePrivateKey(key); if (typeof custom === 'function') key = custom(key);
}
let num: bigint; let num: bigint;
if (typeof key === 'bigint') { if (typeof key === 'bigint') {
// Curve order check is done below
num = key; num = key;
} else if (typeof key === 'number' && Number.isSafeInteger(key) && key > 0) { } else if (ut.isPositiveInt(key)) {
num = BigInt(key); num = BigInt(key);
} else if (typeof key === 'string') { } else if (typeof key === 'string') {
if (key.length !== 2 * groupLen) throw new Error(`Expected ${groupLen} bytes of private key`); if (key.length !== 2 * groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
num = hexToNumber(key); // Validates individual octets
num = ut.hexToNumber(key);
} else if (key instanceof Uint8Array) { } else if (key instanceof Uint8Array) {
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`); if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
num = bytesToNumberBE(key); num = ut.bytesToNumberBE(key);
} else { } else {
throw new TypeError('Expected valid private key'); throw new TypeError('Expected valid private key');
} }
if (CURVE.wrapPrivateKey) num = mod.mod(num, CURVE.n); // Useful for curves with cofactor != 1
if (wrapPrivateKey) num = mod.mod(num, order);
if (!isWithinCurveOrder(num)) throw new Error('Expected private key: 0 < key < n'); if (!isWithinCurveOrder(num)) throw new Error('Expected private key: 0 < key < n');
return num; return num;
} }
/**
* Validates if a scalar ("private number") is valid.
* Scalars are valid only if they are less than curve order.
*/
function normalizeScalar(num: number | bigint): bigint { function normalizeScalar(num: number | bigint): bigint {
if (typeof num === 'number' && Number.isSafeInteger(num) && num > 0) return BigInt(num); if (ut.isPositiveInt(num)) return BigInt(num);
if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num; if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num;
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n');
} }
@@ -289,7 +283,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/** /**
* Takes a bunch of Projective Points but executes only one * Takes a bunch of Projective Points but executes only one
* invert on all of them. invert is very slow operation, * inversion on all of them. Inversion is very slow operation,
* so this improves performance massively. * so this improves performance massively.
*/ */
static toAffineBatch(points: ProjectivePoint[]): Point[] { static toAffineBatch(points: ProjectivePoint[]): Point[] {
@@ -297,6 +291,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return points.map((p, i) => p.toAffine(toInv[i])); return points.map((p, i) => p.toAffine(toInv[i]));
} }
/**
* Optimization: converts a list of projective points to a list of identical points with Z=1.
*/
static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] { static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] {
return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine); return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine);
} }
@@ -320,10 +317,6 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return new ProjectivePoint(this.x, Fp.negate(this.y), this.z); return new ProjectivePoint(this.x, Fp.negate(this.y), this.z);
} }
doubleAdd(): ProjectivePoint {
return this.add(this);
}
// Renes-Costello-Batina exception-free doubling formula. // Renes-Costello-Batina exception-free doubling formula.
// There is 30% faster Jacobian formula, but it is not complete. // There is 30% faster Jacobian formula, but it is not complete.
// https://eprint.iacr.org/2015/1060, algorithm 3 // https://eprint.iacr.org/2015/1060, algorithm 3
@@ -525,24 +518,25 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return new Point(ax, ay); return new Point(ax, ay);
} }
isTorsionFree(): boolean { isTorsionFree(): boolean {
if (CURVE.h === _1n) return true; // No subgroups, always torsion fee const { h: cofactor, isTorsionFree } = CURVE;
if (CURVE.isTorsionFree) return CURVE.isTorsionFree(ProjectivePoint, this); if (cofactor === _1n) return true; // No subgroups, always torsion-free
// is multiplyUnsafe(CURVE.n) is always ok, same as for edwards? if (isTorsionFree) return isTorsionFree(ProjectivePoint, this);
throw new Error('Unsupported!'); throw new Error('isTorsionFree() has not been declared for the elliptic curve');
} }
// Clear cofactor of G1
// https://eprint.iacr.org/2019/403
clearCofactor(): ProjectivePoint { clearCofactor(): ProjectivePoint {
if (CURVE.h === _1n) return this; // Fast-path const { h: cofactor, clearCofactor } = CURVE;
if (CURVE.clearCofactor) return CURVE.clearCofactor(ProjectivePoint, this) as ProjectivePoint; if (cofactor === _1n) return this; // Fast-path
if (clearCofactor) return clearCofactor(ProjectivePoint, this) as ProjectivePoint;
return this.multiplyUnsafe(CURVE.h); return this.multiplyUnsafe(CURVE.h);
} }
} }
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? nBitLength / 2 : nBitLength); const _bits = CURVE.nBitLength;
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
function assertPrjPoint(other: unknown) { function assertPrjPoint(other: unknown) {
if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected'); if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected');
} }
// Stores precomputed values for points. // Stores precomputed values for points.
const pointPrecomputes = new WeakMap<Point, ProjectivePoint[]>(); const pointPrecomputes = new WeakMap<Point, ProjectivePoint[]>();
@@ -551,11 +545,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
*/ */
class Point implements PointType<T> { class Point implements PointType<T> {
/** /**
* Base point aka generator. public_key = Point.BASE * private_key * Base point aka generator. Any public_key = Point.BASE * private_key
*/ */
static BASE: Point = new Point(CURVE.Gx, CURVE.Gy); static BASE: Point = new Point(CURVE.Gx, CURVE.Gy);
/** /**
* Identity point aka point at infinity. point = point + zero_point * Identity point aka point at infinity. p - p = zero_p; p + zero_p = p
*/ */
static ZERO: Point = new Point(Fp.ZERO, Fp.ZERO); static ZERO: Point = new Point(Fp.ZERO, Fp.ZERO);
@@ -583,7 +577,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param hex short/long ECDSA hex * @param hex short/long ECDSA hex
*/ */
static fromHex(hex: Hex): Point { static fromHex(hex: Hex): Point {
const { x, y } = CURVE.fromBytes(ensureBytes(hex)); const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex));
const point = new Point(x, y); const point = new Point(x, y);
point.assertValidity(); point.assertValidity();
return point; return point;
@@ -607,16 +601,16 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Zero is valid point too! // Zero is valid point too!
if (this.equals(Point.ZERO)) { if (this.equals(Point.ZERO)) {
if (CURVE.allowInfinityPoint) return; if (CURVE.allowInfinityPoint) return;
throw new Error('Point is infinity'); throw new Error('Point at infinity');
} }
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
const msg = 'Point is not on elliptic curve'; const msg = 'Point is not on elliptic curve';
const { x, y } = this; const { x, y } = this;
// Check if x, y are valid field elements
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg); if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg);
const left = Fp.square(y); const left = Fp.square(y); // y²
const right = weierstrassEquation(x); const right = weierstrassEquation(x); // x³ + ax + b
if (!Fp.equals(left, right)) throw new Error(msg); if (!Fp.equals(left, right)) throw new Error(msg);
// TODO: flag to disable this?
if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup');
} }
@@ -630,34 +624,37 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return new Point(this.x, Fp.negate(this.y)); return new Point(this.x, Fp.negate(this.y));
} }
protected toProj() {
return ProjectivePoint.fromAffine(this);
}
// Adds point to itself // Adds point to itself
double() { double() {
return ProjectivePoint.fromAffine(this).double().toAffine(); return this.toProj().double().toAffine();
} }
// Adds point to other point
add(other: Point) { add(other: Point) {
return ProjectivePoint.fromAffine(this).add(ProjectivePoint.fromAffine(other)).toAffine(); return this.toProj().add(ProjectivePoint.fromAffine(other)).toAffine();
} }
// Subtracts other point from the point
subtract(other: Point) { subtract(other: Point) {
return this.add(other.negate()); return this.add(other.negate());
} }
multiply(scalar: number | bigint) { multiply(scalar: number | bigint) {
return ProjectivePoint.fromAffine(this).multiply(scalar, this).toAffine(); return this.toProj().multiply(scalar, this).toAffine();
} }
multiplyUnsafe(scalar: bigint) { multiplyUnsafe(scalar: bigint) {
return ProjectivePoint.fromAffine(this).multiplyUnsafe(scalar).toAffine(); return this.toProj().multiplyUnsafe(scalar).toAffine();
} }
clearCofactor() { clearCofactor() {
return ProjectivePoint.fromAffine(this).clearCofactor().toAffine(); return this.toProj().clearCofactor().toAffine();
} }
isTorsionFree(): boolean { isTorsionFree(): boolean {
return ProjectivePoint.fromAffine(this).isTorsionFree(); return this.toProj().isTorsionFree();
} }
/** /**
@@ -667,7 +664,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @returns non-zero affine point * @returns non-zero affine point
*/ */
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined { multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
const P = ProjectivePoint.fromAffine(this); const P = this.toProj();
const aP = const aP =
a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a); a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a);
const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b);
@@ -678,23 +675,26 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Encodes byte string to elliptic curve // Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) { static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve'); const { mapToCurve } = CURVE;
msg = ensureBytes(msg); if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts); const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts);
const { x: x0, y: y0 } = CURVE.mapToCurve(u[0]); const { x: x0, y: y0 } = mapToCurve(u[0]);
const { x: x1, y: y1 } = CURVE.mapToCurve(u[1]); const { x: x1, y: y1 } = mapToCurve(u[1]);
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor(); return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
return p;
} }
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) { static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve'); const { mapToCurve } = CURVE;
msg = ensureBytes(msg); if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ut.ensureBytes(msg);
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts); const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts);
const { x, y } = CURVE.mapToCurve(u[0]); const { x, y } = mapToCurve(u[0]);
return new Point(x, y).clearCofactor(); return new Point(x, y).clearCofactor();
} }
} }
return { return {
Point: Point as PointConstructor<T>, Point: Point as PointConstructor<T>,
ProjectivePoint: ProjectivePoint as ProjectiveConstructor<T>, ProjectivePoint: ProjectivePoint as ProjectiveConstructor<T>,
@@ -733,15 +733,15 @@ export type CurveType = BasicCurve<bigint> & {
// Default options // Default options
lowS?: boolean; lowS?: boolean;
// Hashes // Hashes
hash: utils.CHash; // Because we need outputLen for DRBG hash: ut.CHash; // Because we need outputLen for DRBG
hmac: HmacFnSync; hmac: HmacFnSync;
randomBytes: (bytesLength?: number) => Uint8Array; randomBytes: (bytesLength?: number) => Uint8Array;
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint; truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = utils.validateOpts(curve); const opts = ut.validateOpts(curve);
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen)) if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen))
throw new Error('Invalid hash function'); throw new Error('Invalid hash function');
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function'); if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function'); if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
@@ -754,6 +754,7 @@ export type CurveFn = {
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType;
verify: ( verify: (
signature: Hex | SignatureType, signature: Hex | SignatureType,
msgHash: Hex, msgHash: Hex,
@@ -766,8 +767,6 @@ export type CurveFn = {
ProjectivePoint: ProjectiveConstructor<bigint>; ProjectivePoint: ProjectiveConstructor<bigint>;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
mod: (a: bigint, b?: bigint) => bigint;
invert: (number: bigint, modulo?: bigint) => bigint;
_bigintToBytes: (num: bigint) => Uint8Array; _bigintToBytes: (num: bigint) => Uint8Array;
_bigintToString: (num: bigint) => string; _bigintToString: (num: bigint) => string;
_normalizePrivateKey: (key: PrivKey) => bigint; _normalizePrivateKey: (key: PrivKey) => bigint;
@@ -824,19 +823,17 @@ class HmacDrbg {
out.push(sl); out.push(sl);
len += this.v.length; len += this.v.length;
} }
return concatBytes(...out); return ut.concatBytes(...out);
} }
// There is no need in clean() method // There are no guarantees with JS GC whether bigints are removed even if you clean Uint8Arrays.
// It's useless, there are no guarantees with JS GC
// whether bigints are removed even if you clean Uint8Arrays.
} }
export function weierstrass(curveDef: CurveType): CurveFn { export function weierstrass(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const CURVE_ORDER = CURVE.n; const CURVE_ORDER = CURVE.n;
const Fp = CURVE.Fp; const Fp = CURVE.Fp;
const compressedLen = Fp.BYTES + 1; // 33 const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32
const uncompressedLen = 2 * Fp.BYTES + 1; // 65 const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
function isValidFieldElement(num: bigint): boolean { function isValidFieldElement(num: bigint): boolean {
// 0 is disallowed by arbitrary reasons. Probably because infinity point? // 0 is disallowed by arbitrary reasons. Probably because infinity point?
@@ -847,10 +844,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
weierstrassPoints({ weierstrassPoints({
...CURVE, ...CURVE,
toBytes(c, point, isCompressed: boolean): Uint8Array { toBytes(c, point, isCompressed: boolean): Uint8Array {
const x = Fp.toBytes(point.x);
const cat = ut.concatBytes;
if (isCompressed) { if (isCompressed) {
return concatBytes(new Uint8Array([point.hasEvenY() ? 0x02 : 0x03]), Fp.toBytes(point.x)); return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
} else { } else {
return concatBytes(new Uint8Array([0x04]), Fp.toBytes(point.x), Fp.toBytes(point.y)); return cat(Uint8Array.from([0x04]), x, Fp.toBytes(point.y));
} }
}, },
fromBytes(bytes: Uint8Array) { fromBytes(bytes: Uint8Array) {
@@ -858,7 +857,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const header = bytes[0]; const header = bytes[0];
// this.assertValidity() is done inside of fromHex // this.assertValidity() is done inside of fromHex
if (len === compressedLen && (header === 0x02 || header === 0x03)) { if (len === compressedLen && (header === 0x02 || header === 0x03)) {
const x = bytesToNumberBE(bytes.subarray(1)); const x = ut.bytesToNumberBE(bytes.subarray(1));
if (!isValidFieldElement(x)) throw new Error('Point is not on curve'); if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = weierstrassEquation(x); // y² = x³ + ax + b const y2 = weierstrassEquation(x); // y² = x³ + ax + b
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4 let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
@@ -910,15 +909,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s; return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
} }
function bits2int_2(bytes: Uint8Array): bigint {
const delta = bytes.length * 8 - CURVE.nBitLength;
const num = ut.bytesToNumberBE(bytes);
return delta > 0 ? num >> BigInt(delta) : num;
}
// Ensures ECDSA message hashes are 32 bytes and < curve order // Ensures ECDSA message hashes are 32 bytes and < curve order
function _truncateHash(hash: Uint8Array, truncateOnly = false): bigint { function _truncateHash(hash: Uint8Array, truncateOnly = false): bigint {
const { n, nBitLength } = CURVE; const h = bits2int_2(hash);
const byteLength = hash.length; if (truncateOnly) return h;
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits) const { n } = CURVE;
let h = bytesToNumberBE(hash); return h >= n ? h - n : h;
if (delta > 0) h = h >> BigInt(delta);
if (!truncateOnly && h >= n) h -= n;
return h;
} }
const truncateHash = CURVE.truncateHash || _truncateHash; const truncateHash = CURVE.truncateHash || _truncateHash;
@@ -930,15 +932,17 @@ export function weierstrass(curveDef: CurveType): CurveFn {
this.assertValidity(); this.assertValidity();
} }
// pair (32 bytes of r, 32 bytes of s) // pair (bytes of r, bytes of s)
static fromCompact(hex: Hex) { static fromCompact(hex: Hex) {
const arr = hex instanceof Uint8Array; const arr = hex instanceof Uint8Array;
const name = 'Signature.fromCompact'; const name = 'Signature.fromCompact';
if (typeof hex !== 'string' && !arr) if (typeof hex !== 'string' && !arr)
throw new TypeError(`${name}: Expected string or Uint8Array`); throw new TypeError(`${name}: Expected string or Uint8Array`);
const str = arr ? bytesToHex(hex) : hex; const str = arr ? bytesToHex(hex) : hex;
if (str.length !== 128) throw new Error(`${name}: Expected 64-byte hex`); const gl = CURVE.nByteLength * 2; // group length in hex, not ui8a
return new Signature(hexToNumber(str.slice(0, 64)), hexToNumber(str.slice(64, 128))); if (str.length !== 2 * gl) throw new Error(`${name}: Expected ${gl / 2}-byte hex`);
const slice = (from: number, to: number) => ut.hexToNumber(str.slice(from, to));
return new Signature(slice(0, gl), slice(gl, 2 * gl));
} }
// DER encoded ECDSA signature // DER encoded ECDSA signature
@@ -947,7 +951,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const arr = hex instanceof Uint8Array; const arr = hex instanceof Uint8Array;
if (typeof hex !== 'string' && !arr) if (typeof hex !== 'string' && !arr)
throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`); throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`);
const { r, s } = parseDERSignature(arr ? hex : hexToBytes(hex)); const { r, s } = DER.parseSig(arr ? hex : ut.hexToBytes(hex));
return new Signature(r, s); return new Signature(r, s);
} }
@@ -964,6 +968,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
/** /**
* Recovers public key from signature with recovery bit. Throws on invalid hash. * Recovers public key from signature with recovery bit. Throws on invalid hash.
* https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery * https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery
* It's also possible to recover key without bit: try all 4 bit values and check for sig match.
* *
* ``` * ```
* recover(r, s, h) where * recover(r, s, h) where
@@ -978,16 +983,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
recoverPublicKey(msgHash: Hex): Point { recoverPublicKey(msgHash: Hex): Point {
const { r, s, recovery } = this; const { r, s, recovery } = this;
if (recovery == null) throw new Error('Cannot recover: recovery bit is not present'); if (recovery == null) throw new Error('Cannot recover: recovery bit is not present');
if (recovery !== 0 && recovery !== 1) throw new Error('Cannot recover: invalid recovery bit'); if (![0, 1, 2, 3].includes(recovery)) throw new Error('Cannot recover: invalid recovery bit');
const h = truncateHash(ensureBytes(msgHash)); const h = truncateHash(ut.ensureBytes(msgHash));
const { n } = CURVE; const { n } = CURVE;
const rinv = mod.invert(r, n); const radj = recovery === 2 || recovery === 3 ? r + n : r;
if (radj >= Fp.ORDER) throw new Error('Cannot recover: bit 2/3 is invalid with current r');
const rinv = mod.invert(radj, n);
// Q = u1⋅G + u2⋅R // Q = u1⋅G + u2⋅R
const u1 = mod.mod(-h * rinv, n); const u1 = mod.mod(-h * rinv, n);
const u2 = mod.mod(s * rinv, n); const u2 = mod.mod(s * rinv, n);
const prefix = recovery & 1 ? '03' : '02'; const prefix = recovery & 1 ? '03' : '02';
const R = Point.fromHex(prefix + numToFieldStr(r)); const R = Point.fromHex(prefix + numToFieldStr(radj));
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked
if (!Q) throw new Error('Cannot recover: point at infinify'); if (!Q) throw new Error('Cannot recover: point at infinify');
Q.assertValidity(); Q.assertValidity();
return Q; return Q;
@@ -1009,22 +1016,24 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
// DER-encoded // DER-encoded
toDERRawBytes(isCompressed = false) { toDERRawBytes() {
return hexToBytes(this.toDERHex(isCompressed)); return ut.hexToBytes(this.toDERHex());
} }
toDERHex(isCompressed = false) { toDERHex() {
const sHex = sliceDER(numberToHexUnpadded(this.s)); const { numberToHexUnpadded: toHex } = ut;
if (isCompressed) return sHex; const sHex = DER.slice(toHex(this.s));
const rHex = sliceDER(numberToHexUnpadded(this.r)); const rHex = DER.slice(toHex(this.r));
const rLen = numberToHexUnpadded(rHex.length / 2); const sHexL = sHex.length / 2;
const sLen = numberToHexUnpadded(sHex.length / 2); const rHexL = rHex.length / 2;
const length = numberToHexUnpadded(rHex.length / 2 + sHex.length / 2 + 4); const sLen = toHex(sHexL);
const rLen = toHex(rHexL);
const length = toHex(rHexL + sHexL + 4);
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
} }
// 32 bytes of r, then 32 bytes of s // padded bytes of r, then padded bytes of s
toCompactRawBytes() { toCompactRawBytes() {
return hexToBytes(this.toCompactHex()); return ut.hexToBytes(this.toCompactHex());
} }
toCompactHex() { toCompactHex() {
return numToFieldStr(this.r) + numToFieldStr(this.s); return numToFieldStr(this.r) + numToFieldStr(this.s);
@@ -1032,8 +1041,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
const utils = { const utils = {
mod: (n: bigint, modulo = Fp.ORDER) => mod.mod(n, modulo),
invert: Fp.invert,
isValidPrivateKey(privateKey: PrivKey) { isValidPrivateKey(privateKey: PrivKey) {
try { try {
normalizePrivateKey(privateKey); normalizePrivateKey(privateKey);
@@ -1053,7 +1060,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
/** /**
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. * Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
*/ */
hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)), hashToPrivateKey: (hash: Hex): Uint8Array =>
numToField(ut.hashToPrivateScalar(hash, CURVE_ORDER)),
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Produces cryptographically secure private key from random of size (nBitLength+64)
@@ -1078,10 +1086,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
/** /**
* Computes public key for a private key. * Computes public key for a private key. Checks for validity of the private key.
* @param privateKey private key * @param privateKey private key
* @param isCompressed whether to return compact, or full key * @param isCompressed whether to return compact (default), or full key
* @returns Public key, full by default; short when isCompressed=true * @returns Public key, full when isCompressed=false; short when isCompressed=true
*/ */
function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array { function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array {
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed); return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed);
@@ -1101,12 +1109,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
/** /**
* ECDH (Elliptic Curve Diffie Hellman) implementation. * ECDH (Elliptic Curve Diffie Hellman).
* 1. Checks for validity of private key * Computes shared public key from private key and public key.
* 2. Checks for the public key of being on-curve * Checks: 1) private key validity 2) shared key is on-curve
* @param privateA private key * @param privateA private key
* @param publicB different public key * @param publicB different public key
* @param isCompressed whether to return compact (33-byte), or full (65-byte) key * @param isCompressed whether to return compact (default), or full key
* @returns shared public key * @returns shared public key
*/ */
function getSharedSecret(privateA: PrivKey, publicB: PubKey, isCompressed = false): Uint8Array { function getSharedSecret(privateA: PrivKey, publicB: PubKey, isCompressed = false): Uint8Array {
@@ -1118,9 +1126,21 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
// RFC6979 methods // RFC6979 methods
function bits2int(bytes: Uint8Array) { function bits2int(bytes: Uint8Array): bigint {
const slice = bytes.length > Fp.BYTES ? bytes.slice(0, Fp.BYTES) : bytes; const { nByteLength } = CURVE;
return bytesToNumberBE(slice); if (!(bytes instanceof Uint8Array)) throw new Error('Expected Uint8Array');
const slice = bytes.length > nByteLength ? bytes.slice(0, nByteLength) : bytes;
// const slice = bytes; nByteLength; nBitLength;
let num = ut.bytesToNumberBE(slice);
// const { nBitLength } = CURVE;
// const delta = (bytes.length * 8) - nBitLength;
// if (delta > 0) {
// // console.log('bits=', bytes.length*8, 'CURVE n=', nBitLength, 'delta=', delta);
// // console.log(bytes.length, nBitLength, delta);
// // console.log(bytes, new Error().stack);
// num >>= BigInt(delta);
// }
return num;
} }
function bits2octets(bytes: Uint8Array): Uint8Array { function bits2octets(bytes: Uint8Array): Uint8Array {
const z1 = bits2int(bytes); const z1 = bits2int(bytes);
@@ -1128,28 +1148,28 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return int2octets(z2 < _0n ? z1 : z2); return int2octets(z2 < _0n ? z1 : z2);
} }
function int2octets(num: bigint): Uint8Array { function int2octets(num: bigint): Uint8Array {
return numToField(num); // prohibits >32 bytes return numToField(num); // prohibits >nByteLength bytes
} }
// Steps A, D of RFC6979 3.2 // Steps A, D of RFC6979 3.2
// Creates RFC6979 seed; converts msg/privKey to numbers. // Creates RFC6979 seed; converts msg/privKey to numbers.
function initSigArgs(msgHash: Hex, privateKey: PrivKey, extraEntropy?: Entropy) { function initSigArgs(msgHash: Hex, privateKey: PrivKey, extraEntropy?: Entropy) {
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`); if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
// Step A is ignored, since we already provide hash instead of msg // Step A is ignored, since we already provide hash instead of msg
const h1 = numToField(truncateHash(ensureBytes(msgHash))); const h1 = numToField(truncateHash(ut.ensureBytes(msgHash)));
const d = normalizePrivateKey(privateKey); const d = normalizePrivateKey(privateKey);
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const seedArgs = [int2octets(d), bits2octets(h1)]; const seedArgs = [int2octets(d), bits2octets(h1)];
// RFC6979 3.6: additional k' could be provided // RFC6979 3.6: additional k' could be provided
if (extraEntropy != null) { if (extraEntropy != null) {
if (extraEntropy === true) extraEntropy = CURVE.randomBytes(Fp.BYTES); if (extraEntropy === true) extraEntropy = CURVE.randomBytes(Fp.BYTES);
const e = ensureBytes(extraEntropy); const e = ut.ensureBytes(extraEntropy);
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`); if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
seedArgs.push(e); seedArgs.push(e);
} }
// seed is constructed from private key and message // seed is constructed from private key and message
// Step D // Step D
// V, 0x00 are done in HmacDRBG constructor. // V, 0x00 are done in HmacDRBG constructor.
const seed = concatBytes(...seedArgs); const seed = ut.concatBytes(...seedArgs);
const m = bits2int(h1); const m = bits2int(h1);
return { seed, m, d }; return { seed, m, d };
} }
@@ -1172,9 +1192,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// r = x mod n // r = x mod n
const r = mod.mod(q.x, n); const r = mod.mod(q.x, n);
if (r === _0n) return; if (r === _0n) return;
// s = (1/k * (m + dr) mod n // s = (m + dr)/k mod n where x/k == x*inv(k)
const s = mod.mod(kinv * mod.mod(m + mod.mod(d * r, n), n), n); const s = mod.mod(kinv * mod.mod(m + mod.mod(d * r, n), n), n);
if (s === _0n) return; if (s === _0n) return;
// recovery bit is usually 0 or 1; rarely it's 2 or 3, when q.x > n
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n);
let normS = s; let normS = s;
if (lowS && isBiggerThanHalfOrder(s)) { if (lowS && isBiggerThanHalfOrder(s)) {
@@ -1184,11 +1205,19 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return new Signature(r, normS, recovery); return new Signature(r, normS, recovery);
} }
const defaultSigOpts: SignOpts = { lowS: CURVE.lowS };
/** /**
* Signs message hash (not message: you need to hash it by yourself). * Signs message hash (not message: you need to hash it by yourself).
* ```
* sign(m, d, k) where
* (x, y) = G × k
* r = x mod n
* s = (m + dr)/k mod n
* ```
* @param opts `lowS, extraEntropy` * @param opts `lowS, extraEntropy`
*/ */
function sign(msgHash: Hex, privKey: PrivKey, opts: SignOpts = { lowS: CURVE.lowS }): Signature { function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
// Steps A, D of RFC6979 3.2. // Steps A, D of RFC6979 3.2.
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy); const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy);
// Steps B, C, D, E, F, G // Steps B, C, D, E, F, G
@@ -1199,6 +1228,14 @@ export function weierstrass(curveDef: CurveType): CurveFn {
while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.lowS))) drbg.reseedSync(); while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.lowS))) drbg.reseedSync();
return sig; return sig;
} }
/**
* Signs a message (not message hash).
*/
function signUnhashed(msg: Uint8Array, privKey: PrivKey, opts = defaultSigOpts): Signature {
return sign(CURVE.hash(ut.ensureBytes(msg)), privKey, opts);
}
// Enable precomputes. Slows down first publicKey computation by 20ms. // Enable precomputes. Slows down first publicKey computation by 20ms.
Point.BASE._setWindowSize(8); Point.BASE._setWindowSize(8);
@@ -1234,7 +1271,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
signature = Signature.fromCompact(signature as Hex); signature = Signature.fromCompact(signature as Hex);
} }
} }
msgHash = ensureBytes(msgHash); msgHash = ut.ensureBytes(msgHash);
} catch (error) { } catch (error) {
return false; return false;
} }
@@ -1265,6 +1302,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
getPublicKey, getPublicKey,
getSharedSecret, getSharedSecret,
sign, sign,
signUnhashed,
verify, verify,
Point, Point,
ProjectivePoint, ProjectivePoint,

View File

@@ -1,4 +1,16 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
// - Construct zk-SNARKs at the 128-bit security
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
// Differences from @noble/bls12-381 1.4:
// - PointG1 -> G1.Point
// - PointG2 -> G2.Point
// - PointG2.fromSignature -> Signature.decode
// - PointG2.toSignature -> Signature.encode
// - Fixed Fp2 ORDER
// - Points now have only two coordinates
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { randomBytes } from '@noble/hashes/utils'; import { randomBytes } from '@noble/hashes/utils';
import { bls, CurveFn } from './abstract/bls.js'; import { bls, CurveFn } from './abstract/bls.js';
@@ -23,14 +35,6 @@ import {
} from './abstract/weierstrass.js'; } from './abstract/weierstrass.js';
import { isogenyMap } from './abstract/hash-to-curve.js'; import { isogenyMap } from './abstract/hash-to-curve.js';
// Differences from bls12-381:
// - PointG1 -> G1.Point
// - PointG2 -> G2.Point
// - PointG2.fromSignature -> Signature.decode
// - PointG2.toSignature -> Signature.encode
// - Fixed Fp2 ORDER
// Points now have only two coordinates
// CURVE FIELDS // CURVE FIELDS
// Finite field over p. // Finite field over p.
const Fp = const Fp =
@@ -131,6 +135,7 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) }; return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
}, },
sqrt: (num) => { sqrt: (num) => {
if (Fp2.equals(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case
// TODO: Optimize this line. It's extremely slow. // TODO: Optimize this line. It's extremely slow.
// Speeding this up would boost aggregateSignatures. // Speeding this up would boost aggregateSignatures.
// https://eprint.iacr.org/2012/685.pdf applicable? // https://eprint.iacr.org/2012/685.pdf applicable?

View File

@@ -260,7 +260,7 @@ const invertSqrt = (number: bigint) => uvRatio(_1n, number);
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const bytes255ToNumberLE = (bytes: Uint8Array) => const bytes255ToNumberLE = (bytes: Uint8Array) =>
ed25519.utils.mod(bytesToNumberLE(bytes) & MAX_255B); ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
type ExtendedPoint = ExtendedPointType; type ExtendedPoint = ExtendedPointType;
@@ -269,7 +269,7 @@ type ExtendedPoint = ExtendedPointType;
function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint { function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
const { d } = ed25519.CURVE; const { d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER; const P = ed25519.CURVE.Fp.ORDER;
const { mod } = ed25519.utils; const mod = ed25519.CURVE.Fp.create;
const r = mod(SQRT_M1 * r0 * r0); // 1 const r = mod(SQRT_M1 * r0 * r0); // 1
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2 const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2
let c = BigInt(-1); // 3 let c = BigInt(-1); // 3
@@ -327,7 +327,7 @@ export class RistrettoPoint {
hex = ensureBytes(hex, 32); hex = ensureBytes(hex, 32);
const { a, d } = ed25519.CURVE; const { a, d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER; const P = ed25519.CURVE.Fp.ORDER;
const { mod } = ed25519.utils; const mod = ed25519.CURVE.Fp.create;
const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint'; const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';
const s = bytes255ToNumberLE(hex); const s = bytes255ToNumberLE(hex);
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort. // 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
@@ -357,7 +357,7 @@ export class RistrettoPoint {
toRawBytes(): Uint8Array { toRawBytes(): Uint8Array {
let { x, y, z, t } = this.ep; let { x, y, z, t } = this.ep;
const P = ed25519.CURVE.Fp.ORDER; const P = ed25519.CURVE.Fp.ORDER;
const { mod } = ed25519.utils; const mod = ed25519.CURVE.Fp.create;
const u1 = mod(mod(z + y) * mod(z - y)); // 1 const u1 = mod(mod(z + y) * mod(z - y)); // 1
const u2 = mod(x * y); // 2 const u2 = mod(x * y); // 2
// Square root always exists // Square root always exists
@@ -395,7 +395,7 @@ export class RistrettoPoint {
assertRstPoint(other); assertRstPoint(other);
const a = this.ep; const a = this.ep;
const b = other.ep; const b = other.ep;
const { mod } = ed25519.utils; const mod = ed25519.CURVE.Fp.create;
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
const one = mod(a.x * b.y) === mod(a.y * b.x); const one = mod(a.x * b.y) === mod(a.y * b.x);
const two = mod(a.y * b.y) === mod(a.x * b.x); const two = mod(a.y * b.y) === mod(a.x * b.x);

View File

@@ -138,7 +138,8 @@ const ED448_DEF = {
), ),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n // Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
Fp, Fp,
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n // Subgroup order: how many points curve has;
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
n: BigInt( n: BigInt(
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779' '181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
), ),

View File

@@ -1,5 +1,5 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256'; import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js'; import { twistedEdwards } from './abstract/edwards.js';
import { blake2s } from '@noble/hashes/blake2s'; import { blake2s } from '@noble/hashes/blake2s';
@@ -8,6 +8,7 @@ import { Fp } from './abstract/modular.js';
/** /**
* jubjub Twisted Edwards curve. * jubjub Twisted Edwards curve.
* https://neuromancer.sk/std/other/JubJub * https://neuromancer.sk/std/other/JubJub
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
*/ */
export const jubjub = twistedEdwards({ export const jubjub = twistedEdwards({
@@ -15,16 +16,16 @@ export const jubjub = twistedEdwards({
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'), a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'), d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
// Finite field 𝔽p over which we'll do calculations // Finite field 𝔽p over which we'll do calculations
// Same value as bls12-381 Fr (not Fp)
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')), Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
// Subgroup order: how many points ed25519 has // Subgroup order: how many points curve has
// 2n ** 252n + 27742317777372353535851937790883648493n;
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'), n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
// Cofactor // Cofactor
h: BigInt(8), h: BigInt(8),
// Base point (x, y) aka generator point // Base point (x, y) aka generator point
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'), Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'), Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
hash: sha256, hash: sha512,
randomBytes, randomBytes,
} as const); } as const);

View File

@@ -15,10 +15,8 @@ import { randomBytes } from '@noble/hashes/utils';
import { isogenyMap } from './abstract/hash-to-curve.js'; import { isogenyMap } from './abstract/hash-to-curve.js';
/** /**
* secp256k1 belongs to Koblitz curves: it has * secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
* efficiently computable Frobenius endomorphism. * Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* Endomorphism improves efficiency:
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
* Should always be used for Projective's double-and-add multiplication. * Should always be used for Projective's double-and-add multiplication.
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit. * For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066 * https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
@@ -129,7 +127,7 @@ export const secp256k1 = createCurve(
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3'); const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8'); const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
const b2 = a1; const b2 = a1;
const POW_2_128 = BigInt('0x100000000000000000000000000000000'); const POW_2_128 = BigInt('0x100000000000000000000000000000000'); // (2n**128n).toString(16)
const c1 = divNearest(b2 * k, n); const c1 = divNearest(b2 * k, n);
const c2 = divNearest(-b1 * k, n); const c2 = divNearest(-b1 * k, n);
@@ -175,7 +173,7 @@ function normalizePublicKey(publicKey: Hex | PointType<bigint>): PointType<bigin
} else { } else {
const bytes = ensureBytes(publicKey); const bytes = ensureBytes(publicKey);
// Schnorr is 32 bytes // Schnorr is 32 bytes
if (bytes.length === 32) { if (bytes.length !== 32) throw new Error('Schnorr pubkeys must be 32 bytes');
const x = bytesToNumberBE(bytes); const x = bytesToNumberBE(bytes);
if (!isValidFieldElement(x)) throw new Error('Point is not on curve'); if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
@@ -187,9 +185,6 @@ function normalizePublicKey(publicKey: Hex | PointType<bigint>): PointType<bigin
point.assertValidity(); point.assertValidity();
return point; return point;
} }
// Do we need that in schnorr at all?
return secp256k1.Point.fromHex(publicKey);
}
} }
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder; const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
@@ -227,10 +222,13 @@ class SchnorrSignature {
} }
static fromHex(hex: Hex) { static fromHex(hex: Hex) {
const bytes = ensureBytes(hex); const bytes = ensureBytes(hex);
if (bytes.length !== 64) const len = 32; // group length
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`); if (bytes.length !== 2 * len)
const r = bytesToNumberBE(bytes.subarray(0, 32)); throw new TypeError(
const s = bytesToNumberBE(bytes.subarray(32, 64)); `SchnorrSignature.fromHex: expected ${2 * len} bytes, not ${bytes.length}`
);
const r = bytesToNumberBE(bytes.subarray(0, len));
const s = bytesToNumberBE(bytes.subarray(len, 2 * len));
return new SchnorrSignature(r, s); return new SchnorrSignature(r, s);
} }
assertValidity() { assertValidity() {

View File

@@ -138,11 +138,12 @@ function hashKeyWithIndex(key: Uint8Array, index: number) {
export function grindKey(seed: Hex) { export function grindKey(seed: Hex) {
const _seed = ensureBytes0x(seed); const _seed = ensureBytes0x(seed);
const sha256mask = 2n ** 256n; const sha256mask = 2n ** 256n;
const limit = sha256mask - starkCurve.utils.mod(sha256mask, starkCurve.CURVE.n); const Fn = Fp(CURVE.n);
const limit = sha256mask - Fn.create(sha256mask);
for (let i = 0; ; i++) { for (let i = 0; ; i++) {
const key = hashKeyWithIndex(_seed, i); const key = hashKeyWithIndex(_seed, i);
// key should be in [0, limit) // key should be in [0, limit)
if (key < limit) return starkCurve.utils.mod(key, starkCurve.CURVE.n).toString(16); if (key < limit) return Fn.create(key).toString(16);
} }
} }

View File

@@ -1,7 +1,8 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should'; import { should, describe } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import * as mod from '../lib/esm/abstract/modular.js'; import * as mod from '../lib/esm/abstract/modular.js';
import { bytesToHex as toHex } from '../lib/esm/abstract/utils.js';
// Generic tests for all curves in package // Generic tests for all curves in package
import { secp192r1 } from '../lib/esm/p192.js'; import { secp192r1 } from '../lib/esm/p192.js';
import { secp224r1 } from '../lib/esm/p224.js'; import { secp224r1 } from '../lib/esm/p224.js';
@@ -61,7 +62,8 @@ for (const c in FIELDS) {
const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n);
const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num); const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num);
should(`${name} equality`, () => { describe(name, () => {
should('equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -71,7 +73,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} non-equality`, () => { should('non-equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1); const a = create(num1);
@@ -81,7 +83,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} add/subtract/commutativity`, () => { should('add/subtract/commutativity', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1); const a = create(num1);
@@ -90,7 +92,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} add/subtract/associativity`, () => { should('add/subtract/associativity', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => { fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1); const a = create(num1);
@@ -100,7 +102,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} add/subtract/x+0=x`, () => { should('add/subtract/x+0=x', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -108,7 +110,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} add/subtract/x-0=x`, () => { should('add/subtract/x-0=x', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -117,7 +119,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} add/subtract/negate equality`, () => { should('add/subtract/negate equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num1) => { fc.property(FC_BIGINT, (num1) => {
const a = create(num1); const a = create(num1);
@@ -128,7 +130,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} add/subtract/negate`, () => { should('add/subtract/negate', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -137,8 +139,11 @@ for (const c in FIELDS) {
}) })
); );
}); });
should('negate(0)', () => {
deepStrictEqual(Fp.negate(Fp.ZERO), Fp.ZERO);
});
should(`${name} multiply/commutativity`, () => { should('multiply/commutativity', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1); const a = create(num1);
@@ -147,7 +152,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} multiply/associativity`, () => { should('multiply/associativity', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => { fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1); const a = create(num1);
@@ -157,7 +162,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} multiply/distributivity`, () => { should('multiply/distributivity', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => { fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1); const a = create(num1);
@@ -167,7 +172,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} multiply/add equality`, () => { should('multiply/add equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -181,7 +186,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} multiply/square equality`, () => { should('multiply/square equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -189,7 +194,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} multiply/pow equality`, () => { should('multiply/pow equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -200,9 +205,27 @@ for (const c in FIELDS) {
}) })
); );
}); });
should('square(0)', () => {
deepStrictEqual(Fp.square(Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO);
});
should('square(1)', () => {
deepStrictEqual(Fp.square(Fp.ONE), Fp.ONE);
deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE);
});
should('square(-1)', () => {
const minus1 = Fp.negate(Fp.ONE);
deepStrictEqual(Fp.square(minus1), Fp.ONE);
deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE);
});
const isSquare = mod.FpIsSquare(Fp); const isSquare = mod.FpIsSquare(Fp);
should(`${name} multiply/sqrt`, () => { // Not implemented
if (Fp === bls12_381.CURVE.Fp12) return; // Not implemented if (Fp !== bls12_381.CURVE.Fp12) {
should('multiply/sqrt', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -220,7 +243,18 @@ for (const c in FIELDS) {
); );
}); });
should(`${name} div/division by one equality`, () => { should('sqrt(0)', () => {
deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO);
const sqrt1 = Fp.sqrt(Fp.ONE);
deepStrictEqual(
Fp.equals(sqrt1, Fp.ONE) || Fp.equals(sqrt1, Fp.negate(Fp.ONE)),
true,
'sqrt(1) = 1 or -1'
);
});
}
should('div/division by one equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -230,7 +264,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} zero division equality`, () => { should('zero division equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
@@ -238,7 +272,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} div/division distributivity`, () => { should('div/division distributivity', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => { fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1); const a = create(num1);
@@ -248,7 +282,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
should(`${name} div/division and multiplication equality`, () => { should('div/division and multiplication equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1); const a = create(num1);
@@ -257,6 +291,7 @@ for (const c in FIELDS) {
}) })
); );
}); });
});
} }
} }
@@ -278,12 +313,12 @@ const NUM_RUNS = 5;
const getXY = (p) => ({ x: p.x, y: p.y }); const getXY = (p) => ({ x: p.x, y: p.y });
function equal(a, b, comment) { function equal(a, b, comment) {
deepStrictEqual(a.equals(b), true, `eq(${comment})`); deepStrictEqual(a.equals(b), true, 'eq(${comment})');
if (a.toAffine && b.toAffine) { if (a.toAffine && b.toAffine) {
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`); deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), 'eqToAffine(${comment})');
} else if (!a.toAffine && !b.toAffine) { } else if (!a.toAffine && !b.toAffine) {
// Already affine // Already affine
deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`); deepStrictEqual(getXY(a), getXY(b), 'eqAffine(${comment})');
} else throw new Error('Different point types'); } else throw new Error('Different point types');
} }
@@ -308,46 +343,49 @@ for (const name in CURVES) {
const G = [p.ZERO, p.BASE]; const G = [p.ZERO, p.BASE];
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i)); for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
const title = `${name}/${pointName}`;
describe(title, () => {
describe('basic group laws', () => {
// Here we check basic group laws, to verify that points works as group // Here we check basic group laws, to verify that points works as group
should(`${name}/${pointName}/Basic group laws (zero)`, () => { should('(zero)', () => {
equal(G[0].double(), G[0], '(0*G).double() = 0'); equal(G[0].double(), G[0], '(0*G).double() = 0');
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0'); equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0'); equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
equal(G[0].negate(), G[0], '-0 = 0'); equal(G[0].negate(), G[0], '-0 = 0');
for (let i = 0; i < G.length; i++) { for (let i = 0; i < G.length; i++) {
const p = G[i]; const p = G[i];
equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`); equal(p, p.add(G[0]), '${i}*G + 0 = ${i}*G');
equal(G[0].multiply(i + 1), G[0], `${i + 1}*0 = 0`); equal(G[0].multiply(i + 1), G[0], '${i + 1}*0 = 0');
} }
}); });
should(`${name}/${pointName}/Basic group laws (one)`, () => { should('(one)', () => {
equal(G[1].double(), G[2], '(1*G).double() = 2*G'); equal(G[1].double(), G[2], '(1*G).double() = 2*G');
equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0'); equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0');
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G'); equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
}); });
should(`${name}/${pointName}/Basic group laws (sanity tests)`, () => { should('(sanity tests)', () => {
equal(G[2].double(), G[4], `(2*G).double() = 4*G`); equal(G[2].double(), G[4], '(2*G).double() = 4*G');
equal(G[2].add(G[2]), G[4], `2*G + 2*G = 4*G`); equal(G[2].add(G[2]), G[4], '2*G + 2*G = 4*G');
equal(G[7].add(G[3].negate()), G[4], `7*G - 3*G = 4*G`); equal(G[7].add(G[3].negate()), G[4], '7*G - 3*G = 4*G');
}); });
should(`${name}/${pointName}/Basic group laws (addition commutativity)`, () => { should('(addition commutativity)', () => {
equal(G[4].add(G[3]), G[3].add(G[4]), `4*G + 3*G = 3*G + 4*G`); equal(G[4].add(G[3]), G[3].add(G[4]), '4*G + 3*G = 3*G + 4*G');
equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), `4*G + 3*G = 3*G + 2*G + 2*G`); equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), '4*G + 3*G = 3*G + 2*G + 2*G');
}); });
should(`${name}/${pointName}/Basic group laws (double)`, () => { should('(double)', () => {
equal(G[3].double(), G[6], '(3*G).double() = 6*G'); equal(G[3].double(), G[6], '(3*G).double() = 6*G');
}); });
should(`${name}/${pointName}/Basic group laws (multiply)`, () => { should('(multiply)', () => {
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G'); equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G');
}); });
should(`${name}/${pointName}/Basic group laws (same point addition)`, () => { should('(same point addition)', () => {
equal(G[3].add(G[3]), G[6], `3*G + 3*G = 6*G`); equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G');
}); });
should(`${name}/${pointName}/Basic group laws (same point (negative) addition)`, () => { should('(same point (negative) addition)', () => {
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G'); equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G'); equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
}); });
should(`${name}/${pointName}/Basic group laws (curve order)`, () => { should('(curve order)', () => {
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0'); equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G'); equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G');
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0'); equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
@@ -355,7 +393,7 @@ for (const name in CURVES) {
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0]; const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0];
equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0'); equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
}); });
should(`${name}/${pointName}/Basic group laws (inversion)`, () => { should('(inversion)', () => {
const a = 1234n; const a = 1234n;
const b = 5678n; const b = 5678n;
const c = a * b; const c = a * b;
@@ -363,7 +401,7 @@ for (const name in CURVES) {
const inv = mod.invert(b, CURVE_ORDER); const inv = mod.invert(b, CURVE_ORDER);
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G'); equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
}); });
should(`${name}/${pointName}/Basic group laws (multiply, rand)`, () => should('(multiply, rand)', () =>
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => { fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a + b, CURVE_ORDER); const c = mod.mod(a + b, CURVE_ORDER);
@@ -371,27 +409,29 @@ for (const name in CURVES) {
const pA = G[1].multiply(a); const pA = G[1].multiply(a);
const pB = G[1].multiply(b); const pB = G[1].multiply(b);
const pC = G[1].multiply(c); const pC = G[1].multiply(c);
equal(pA.add(pB), pB.add(pA), `pA + pB = pB + pA`); equal(pA.add(pB), pB.add(pA), 'pA + pB = pB + pA');
equal(pA.add(pB), pC, `pA + pB = pC`); equal(pA.add(pB), pC, 'pA + pB = pC');
}), }),
{ numRuns: NUM_RUNS } { numRuns: NUM_RUNS }
) )
); );
should(`${name}/${pointName}/Basic group laws (multiply2, rand)`, () => should('(multiply2, rand)', () =>
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => { fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a * b, CURVE_ORDER); const c = mod.mod(a * b, CURVE_ORDER);
const pA = G[1].multiply(a); const pA = G[1].multiply(a);
const pB = G[1].multiply(b); const pB = G[1].multiply(b);
equal(pA.multiply(b), pB.multiply(a), `b*pA = a*pB`); equal(pA.multiply(b), pB.multiply(a), 'b*pA = a*pB');
equal(pA.multiply(b), G[1].multiply(c), `b*pA = c*G`); equal(pA.multiply(b), G[1].multiply(c), 'b*pA = c*G');
}), }),
{ numRuns: NUM_RUNS } { numRuns: NUM_RUNS }
) )
); );
});
for (const op of ['add', 'subtract']) { for (const op of ['add', 'subtract']) {
should(`${name}/${pointName}/${op} type check`, () => { describe(op, () => {
should('type check', () => {
throws(() => G[1][op](0), '0'); throws(() => G[1][op](0), '0');
throws(() => G[1][op](0n), '0n'); throws(() => G[1][op](0n), '0n');
G[1][op](G[2]); G[1][op](G[2]);
@@ -399,17 +439,21 @@ for (const name in CURVES) {
throws(() => G[1][op](123.456), '123.456'); throws(() => G[1][op](123.456), '123.456');
throws(() => G[1][op](true), 'true'); throws(() => G[1][op](true), 'true');
throws(() => G[1][op]('1'), "'1'"); throws(() => G[1][op]('1'), "'1'");
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }'); throws(
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
'{ x: 1n, y: 1n, z: 1n, t: 1n }'
);
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])'); throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])'); throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])'); throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])'); throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`); if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), 'Point ${op} ${pointName}');
throws(() => G[1][op](o.BASE), `${op}/other curve point`); throws(() => G[1][op](o.BASE), '${op}/other curve point');
});
}); });
} }
should(`${name}/${pointName}/equals type check`, () => { should('equals type check', () => {
throws(() => G[1].equals(0), '0'); throws(() => G[1].equals(0), '0');
throws(() => G[1].equals(0n), '0n'); throws(() => G[1].equals(0n), '0n');
deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G'); deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G');
@@ -424,13 +468,14 @@ for (const name in CURVES) {
throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])'); throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])'); throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])'); throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`); if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), 'Point.equals(${pointName})');
throws(() => G[1].equals(o.BASE), 'other curve point'); throws(() => G[1].equals(o.BASE), 'other curve point');
}); });
for (const op of ['multiply', 'multiplyUnsafe']) { for (const op of ['multiply', 'multiplyUnsafe']) {
if (!p.BASE[op]) continue; if (!p.BASE[op]) continue;
should(`${name}/${pointName}/${op} type check`, () => { describe(op, () => {
should('type check', () => {
if (op !== 'multiplyUnsafe') { if (op !== 'multiplyUnsafe') {
throws(() => G[1][op](0), '0'); throws(() => G[1][op](0), '0');
throws(() => G[1][op](0n), '0n'); throws(() => G[1][op](0n), '0n');
@@ -449,23 +494,24 @@ for (const name in CURVES) {
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])'); throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
throws(() => G[1][op](o.BASE), 'other curve point'); throws(() => G[1][op](o.BASE), 'other curve point');
}); });
});
} }
// Complex point (Extended/Jacobian/Projective?) // Complex point (Extended/Jacobian/Projective?)
if (p.BASE.toAffine) { if (p.BASE.toAffine) {
should(`${name}/${pointName}/toAffine()`, () => { should('toAffine()', () => {
equal(p.ZERO.toAffine(), C.Point.ZERO, `0 = 0`); equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0');
equal(p.BASE.toAffine(), C.Point.BASE, `1 = 1`); equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1');
}); });
} }
if (p.fromAffine) { if (p.fromAffine) {
should(`${name}/${pointName}/fromAffine()`, () => { should('fromAffine()', () => {
equal(p.ZERO, p.fromAffine(C.Point.ZERO), `0 = 0`); equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0');
equal(p.BASE, p.fromAffine(C.Point.BASE), `1 = 1`); equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1');
}); });
} }
// toHex/fromHex (if available) // toHex/fromHex (if available)
if (p.fromHex && p.BASE.toHex) { if (p.fromHex && p.BASE.toHex) {
should(`${name}/${pointName}/fromHex(toHex()) roundtrip`, () => { should('fromHex(toHex()) roundtrip', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (x) => { fc.property(FC_BIGINT, (x) => {
const hex = p.BASE.multiply(x).toHex(); const hex = p.BASE.multiply(x).toHex();
@@ -474,9 +520,11 @@ for (const name in CURVES) {
); );
}); });
} }
});
} }
describe(name, () => {
// Generic complex things (getPublicKey/sign/verify/getSharedSecret) // Generic complex things (getPublicKey/sign/verify/getSharedSecret)
should(`${name}/getPublicKey type check`, () => { should('getPublicKey type check', () => {
throws(() => C.getPublicKey(0), '0'); throws(() => C.getPublicKey(0), '0');
throws(() => C.getPublicKey(0n), '0n'); throws(() => C.getPublicKey(0n), '0n');
throws(() => C.getPublicKey(false), 'false'); throws(() => C.getPublicKey(false), 'false');
@@ -491,23 +539,27 @@ for (const name in CURVES) {
throws(() => C.getPublicKey(new Uint8Array([1]))); throws(() => C.getPublicKey(new Uint8Array([1])));
throws(() => C.getPublicKey(new Uint8Array(4096).fill(1))); throws(() => C.getPublicKey(new Uint8Array(4096).fill(1)));
}); });
should(`${name}.verify()/should verify random signatures`, () => should('.verify() should verify random signatures', () =>
fc.assert( fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => { fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv); const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
deepStrictEqual(C.verify(sig, msg, pub), true); deepStrictEqual(
C.verify(sig, msg, pub),
true,
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
);
}), }),
{ numRuns: NUM_RUNS } { numRuns: NUM_RUNS }
) )
); );
should(`${name}.sign()/edge cases`, () => { should('.sign() edge cases', () => {
throws(() => C.sign()); throws(() => C.sign());
throws(() => C.sign('')); throws(() => C.sign(''));
}); });
should(`${name}.verify()/should not verify signature with wrong hash`, () => { should('.verify() should not verify signature with wrong hash', () => {
const MSG = '01'.repeat(32); const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n; const PRIV_KEY = 0x2n;
const WRONG_MSG = '11'.repeat(32); const WRONG_MSG = '11'.repeat(32);
@@ -517,7 +569,7 @@ for (const name in CURVES) {
}); });
// NOTE: fails for ed, because of empty message. Since we convert it to scalar, // NOTE: fails for ed, because of empty message. Since we convert it to scalar,
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case? // need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
// should(`${name}/should not verify signature with wrong message`, () => { // should('should not verify signature with wrong message', () => {
// fc.assert( // fc.assert(
// fc.property( // fc.property(
// fc.array(fc.integer({ min: 0x00, max: 0xff })), // fc.array(fc.integer({ min: 0x00, max: 0xff })),
@@ -539,7 +591,7 @@ for (const name in CURVES) {
// }); // });
if (C.getSharedSecret) { if (C.getSharedSecret) {
should(`${name}/getSharedSecret() should be commutative`, () => { should('getSharedSecret() should be commutative', () => {
for (let i = 0; i < NUM_RUNS; i++) { for (let i = 0; i < NUM_RUNS; i++) {
const asec = C.utils.randomPrivateKey(); const asec = C.utils.randomPrivateKey();
const apub = C.getPublicKey(asec); const apub = C.getPublicKey(asec);
@@ -554,7 +606,24 @@ for (const name in CURVES) {
} }
}); });
} }
});
} }
should('secp224k1 sqrt bug', () => {
const { Fp } = secp224r1.CURVE;
const sqrtMinus1 = Fp.sqrt(-1n);
// Verified against sage
deepStrictEqual(
sqrtMinus1,
23621584063597419797792593680131996961517196803742576047493035507225n
);
deepStrictEqual(
Fp.negate(sqrtMinus1),
3338362603553219996874421406887633712040719456283732096017030791656n
);
deepStrictEqual(Fp.square(sqrtMinus1), Fp.create(-1n));
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,5 +1,5 @@
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../lib/esm/bls12-381.js';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert'; import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
@@ -38,11 +38,11 @@ const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
const getPubKey = (priv) => bls.getPublicKey(priv); const getPubKey = (priv) => bls.getPublicKey(priv);
// Fp // Fp
{ describe('bls12-381 Fp', () => {
const Fp = bls.Fp; const Fp = bls.Fp;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
should('bls12-381/Fp/multiply/sqrt', () => { should('multiply/sqrt', () => {
let sqr1 = Fp.sqrt(Fp.create(300855555557n)); let sqr1 = Fp.sqrt(Fp.create(300855555557n));
deepStrictEqual( deepStrictEqual(
sqr1 && sqr1.toString(), sqr1 && sqr1.toString(),
@@ -50,16 +50,16 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
throws(() => Fp.sqrt(Fp.create(72057594037927816n))); throws(() => Fp.sqrt(Fp.create(72057594037927816n)));
}); });
} });
// Fp2 // Fp2
{ describe('bls12-381 Fp2', () => {
const Fp = bls.Fp; const Fp = bls.Fp;
const Fp2 = bls.Fp2; const Fp2 = bls.Fp2;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }); const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 });
should('bls12-381 Fp2/non-equality', () => { should('non-equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => { fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]); const a = Fp2.fromBigTuple([num1[0], num1[1]]);
@@ -70,7 +70,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
}); });
should('bls12-381 Fp2/div/x/1=x', () => { should('div/x/1=x', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT_2, (num) => { fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]); const a = Fp2.fromBigTuple([num[0], num[1]]);
@@ -81,7 +81,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
}); });
should('bls12-381 Fp2/frobenius', () => { should('frobenius', () => {
// expect(Fp2.FROBENIUS_COEFFICIENTS[0].equals(Fp.ONE)).toBe(true); // expect(Fp2.FROBENIUS_COEFFICIENTS[0].equals(Fp.ONE)).toBe(true);
// expect( // expect(
// Fp2.FROBENIUS_COEFFICIENTS[1].equals( // Fp2.FROBENIUS_COEFFICIENTS[1].equals(
@@ -139,16 +139,17 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
true true
); );
}); });
} });
// Point // Point
{ describe('bls12-381 Point', () => {
const Fp = bls.Fp; const Fp = bls.Fp;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const PointG1 = bls.G1.Point; const PointG1 = bls.G1.Point;
const PointG2 = bls.G2.Point; const PointG2 = bls.G2.Point;
should('bls12-381 Point/Point with Fp coordinates/Point equality', () => { describe('with Fp coordinates', () => {
should('Point equality', () => {
fc.assert( fc.assert(
fc.property( fc.property(
fc.array(FC_BIGINT, { minLength: 3, maxLength: 3 }), fc.array(FC_BIGINT, { minLength: 3, maxLength: 3 }),
@@ -164,16 +165,16 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
) )
); );
}); });
should('bls12-381 Point/Point with Fp coordinates/should be placed on curve vector 1', () => { should('be placed on curve vector 1', () => {
const a = new PointG1(Fp.create(0n), Fp.create(0n)); const a = new PointG1(Fp.create(0n), Fp.create(0n));
a.assertValidity(); a.assertValidity();
}); });
should('bls12-381 Point/Point with Fp coordinates/should not be placed on curve vector 1', () => { should('not be placed on curve vector 1', () => {
const a = new PointG1(Fp.create(0n), Fp.create(1n)); const a = new PointG1(Fp.create(0n), Fp.create(1n));
throws(() => a.assertValidity()); throws(() => a.assertValidity());
}); });
should('bls12-381 Point/Point with Fp coordinates/should be placed on curve vector 2', () => { should('be placed on curve vector 2', () => {
const a = new PointG1( const a = new PointG1(
Fp.create( Fp.create(
0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn
@@ -184,7 +185,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
a.assertValidity(); a.assertValidity();
}); });
should('bls12-381 Point/Point with Fp coordinates/should be placed on curve vector 3', () => { should('be placed on curve vector 3', () => {
const a = new PointG1( const a = new PointG1(
Fp.create( Fp.create(
3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n
@@ -196,7 +197,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
a.assertValidity(); a.assertValidity();
}); });
should('bls12-381 Point/Point with Fp coordinates/should not be placed on curve vector 3', () => { should('not be placed on curve vector 3', () => {
const a = new PointG1( const a = new PointG1(
Fp.create( Fp.create(
622186380008502900120948444810967255157373993223369845903602988014033704418470621816206856882891545628885272576827n 622186380008502900120948444810967255157373993223369845903602988014033704418470621816206856882891545628885272576827n
@@ -207,7 +208,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
throws(() => a.assertValidity()); throws(() => a.assertValidity());
}); });
should('bls12-381 Point/Point with Fp coordinates/should not be placed on curve vector 2', () => { should('not be placed on curve vector 2', () => {
const a = new PointG1( const a = new PointG1(
Fp.create( Fp.create(
0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6ban 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6ban
@@ -219,9 +220,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
throws(() => a.assertValidity()); throws(() => a.assertValidity());
}); });
should( should('be doubled and placed on curve vector 1', () => {
'bls12-381 Point/Point with Fp coordinates/should be doubled and placed on curve vector 1',
() => {
const a = new PointG1( const a = new PointG1(
Fp.create( Fp.create(
0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn
@@ -245,11 +244,8 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
deepStrictEqual(double, a.multiply(2n)); deepStrictEqual(double, a.multiply(2n));
deepStrictEqual(double, a.add(a)); deepStrictEqual(double, a.add(a));
} });
); should('be pdoubled and placed on curve vector 2', () => {
should(
'bls12-381 Point/Point with Fp coordinates/should be pdoubled and placed on curve vector 2',
() => {
const a = new PointG1( const a = new PointG1(
Fp.create( Fp.create(
3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n
@@ -273,9 +269,8 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
deepStrictEqual(double, a.multiply(2n)); deepStrictEqual(double, a.multiply(2n));
deepStrictEqual(double, a.add(a)); deepStrictEqual(double, a.add(a));
} });
); should('not validate incorrect point', () => {
should('bls12-381 Point/Point with Fp coordinates/should not validate incorrect point', () => {
const x = const x =
499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n; 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n;
const y = const y =
@@ -284,8 +279,10 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
const p = new PointG1(Fp.create(x), Fp.create(y)); const p = new PointG1(Fp.create(x), Fp.create(y));
throws(() => p.assertValidity()); throws(() => p.assertValidity());
}); });
});
should('bls12-381 Point/Point with Fp2 coordinates/Point equality', () => { describe('with Fp2 coordinates', () => {
should('Point equality', () => {
fc.assert( fc.assert(
fc.property( fc.property(
fc.array(fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }), { fc.array(fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }), {
@@ -297,8 +294,16 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
maxLength: 3, maxLength: 3,
}), }),
([x1, y1, z1], [x2, y2, z2]) => { ([x1, y1, z1], [x2, y2, z2]) => {
const p1 = new PointG2(Fp2.fromBigTuple(x1), Fp2.fromBigTuple(y1), Fp2.fromBigTuple(z1)); const p1 = new PointG2(
const p2 = new PointG2(Fp2.fromBigTuple(x2), Fp2.fromBigTuple(y2), Fp2.fromBigTuple(z2)); Fp2.fromBigTuple(x1),
Fp2.fromBigTuple(y1),
Fp2.fromBigTuple(z1)
);
const p2 = new PointG2(
Fp2.fromBigTuple(x2),
Fp2.fromBigTuple(y2),
Fp2.fromBigTuple(z2)
);
deepStrictEqual(p1.equals(p1), true); deepStrictEqual(p1.equals(p1), true);
deepStrictEqual(p2.equals(p2), true); deepStrictEqual(p2.equals(p2), true);
deepStrictEqual(p1.equals(p2), false); deepStrictEqual(p1.equals(p2), false);
@@ -307,11 +312,11 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
) )
); );
}); });
// should('bls12-381 Point/Point with Fp2 coordinates/should be placed on curve vector 1', () => { // should('be placed on curve vector 1', () => {
// const a = new PointG2(Fp2.fromBigTuple([0n, 0n]), Fp2.fromBigTuple([0n, 0n])); // const a = new PointG2(Fp2.fromBigTuple([0n, 0n]), Fp2.fromBigTuple([0n, 0n]));
// a.assertValidity(); // a.assertValidity();
// }); // });
should('bls12-381 Point/Point with Fp2 coordinates/should be placed on curve vector 2', () => { should('be placed on curve vector 2', () => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n,
@@ -325,7 +330,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
a.assertValidity(); a.assertValidity();
}); });
should('bls12-381 Point/Point with Fp2 coordinates/should be placed on curve vector 3', () => { should('be placed on curve vector 3', () => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n,
@@ -338,20 +343,15 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
a.assertValidity(); a.assertValidity();
}); });
should( should('not be placed on curve vector 1', () => {
'bls12-381 Point/Point with Fp2 coordinates/should not be placed on curve vector 1',
() => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([0n, 0n]), Fp2.fromBigTuple([0n, 0n]),
Fp2.fromBigTuple([1n, 0n]), Fp2.fromBigTuple([1n, 0n]),
Fp2.fromBigTuple([1n, 0n]) Fp2.fromBigTuple([1n, 0n])
); );
throws(() => a.assertValidity()); throws(() => a.assertValidity());
} });
); should('not be placed on curve vector 2', () => {
should(
'bls12-381 Point/Point with Fp2 coordinates/should not be placed on curve vector 2',
() => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4410b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4410b647ae3d1770bac0326a805bbefd48056c8c121bdb8n,
@@ -364,11 +364,8 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
Fp2.fromBigTuple([1n, 0n]) Fp2.fromBigTuple([1n, 0n])
); );
throws(() => a.assertValidity()); throws(() => a.assertValidity());
} });
); should('not be placed on curve vector 3', () => {
should(
'bls12-381 Point/Point with Fp2 coordinates/should not be placed on curve vector 3',
() => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
0x877d52dd65245f8908a03288adcd396f489ef87ae23fe110c5aa48bc208fbd1a0ed403df5b1ac137922b915f1f38ec37n, 0x877d52dd65245f8908a03288adcd396f489ef87ae23fe110c5aa48bc208fbd1a0ed403df5b1ac137922b915f1f38ec37n,
@@ -384,10 +381,10 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
]) ])
); );
throws(() => a.assertValidity()); throws(() => a.assertValidity());
} });
); });
should('bls12-381 Point/should be doubled and placed on curve vector 1', () => { should('be doubled and placed on curve vector 1', () => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n,
@@ -417,7 +414,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
deepStrictEqual(double, a.multiply(2n)); deepStrictEqual(double, a.multiply(2n));
deepStrictEqual(double, a.add(a)); deepStrictEqual(double, a.add(a));
}); });
should('bls12-381 Point/should be doubled and placed on curve vector 2', () => { should('be doubled and placed on curve vector 2', () => {
const a = new PointG2( const a = new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n,
@@ -455,49 +452,51 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
0x53eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n, 0x53eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n,
0x63eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000n, 0x63eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000n,
]; ];
should('bls12-381 Point/wNAF multiplication same as unsafe (G1, W=1)', () => { describe('wNAF multiplication same as unsafe', () => {
should('(G1, W=1)', () => {
let G = PointG1.BASE.negate().negate(); // create new point let G = PointG1.BASE.negate().negate(); // create new point
G._setWindowSize(1); G._setWindowSize(1);
for (let k of wNAF_VECTORS) { for (let k of wNAF_VECTORS) {
deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true);
} }
}); });
should('bls12-381 Point/wNAF multiplication same as unsafe (G1, W=4)', () => { should('(G1, W=4)', () => {
let G = PointG1.BASE.negate().negate(); let G = PointG1.BASE.negate().negate();
G._setWindowSize(4); G._setWindowSize(4);
for (let k of wNAF_VECTORS) { for (let k of wNAF_VECTORS) {
deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true);
} }
}); });
should('bls12-381 Point/wNAF multiplication same as unsafe (G1, W=5)', () => { should('(G1, W=5)', () => {
let G = PointG1.BASE.negate().negate(); let G = PointG1.BASE.negate().negate();
G._setWindowSize(5); G._setWindowSize(5);
for (let k of wNAF_VECTORS) { for (let k of wNAF_VECTORS) {
deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true);
} }
}); });
should('bls12-381 Point/wNAF multiplication same as unsafe (G2, W=1)', () => { should('(G2, W=1)', () => {
let G = PointG2.BASE.negate().negate(); let G = PointG2.BASE.negate().negate();
G._setWindowSize(1); G._setWindowSize(1);
for (let k of wNAF_VECTORS) { for (let k of wNAF_VECTORS) {
deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true);
} }
}); });
should('bls12-381 Point/wNAF multiplication same as unsafe (G2, W=4)', () => { should('(G2, W=4)', () => {
let G = PointG2.BASE.negate().negate(); let G = PointG2.BASE.negate().negate();
G._setWindowSize(4); G._setWindowSize(4);
for (let k of wNAF_VECTORS) { for (let k of wNAF_VECTORS) {
deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true);
} }
}); });
should('bls12-381 Point/wNAF multiplication same as unsafe (G2, W=5)', () => { should('(G2, W=5)', () => {
let G = PointG2.BASE.negate().negate(); let G = PointG2.BASE.negate().negate();
G._setWindowSize(5); G._setWindowSize(5);
for (let k of wNAF_VECTORS) { for (let k of wNAF_VECTORS) {
deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true);
} }
}); });
should('bls12-381 Point/PSI cofactor cleaning same as multiplication', () => { });
should('PSI cofactor cleaning same as multiplication', () => {
const points = [ const points = [
new PointG2( new PointG2(
Fp2.fromBigTuple([ Fp2.fromBigTuple([
@@ -558,14 +557,15 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
deepStrictEqual(ours.equals(shouldBe), true, 'clearLast'); deepStrictEqual(ours.equals(shouldBe), true, 'clearLast');
} }
}); });
} });
// index.ts // index.ts
// bls.PointG1.BASE.clearMultiplyPrecomputes(); // bls.PointG1.BASE.clearMultiplyPrecomputes();
// bls.PointG1.BASE.calcMultiplyPrecomputes(4); // bls.PointG1.BASE.calcMultiplyPrecomputes(4);
should('bls12-381/basic/should construct point G1 from its uncompressed form (Raw Bytes)', () => { describe('bls12-381/basic', () => {
should('construct point G1 from its uncompressed form (Raw Bytes)', () => {
// Test Zero // Test Zero
const g1 = bls.G1.Point.fromHex(B_192_40); const g1 = bls.G1.Point.fromHex(B_192_40);
deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); deepStrictEqual(g1.x, bls.G1.Point.ZERO.x);
@@ -590,7 +590,7 @@ should('bls12-381/basic/should construct point G1 from its uncompressed form (Ra
deepStrictEqual(g1_.y, y); deepStrictEqual(g1_.y, y);
}); });
should('bls12-381/basic/should construct point G1 from its uncompressed form (Hex)', () => { should('construct point G1 from its uncompressed form (Hex)', () => {
// Test Zero // Test Zero
const g1 = bls.G1.Point.fromHex(B_192_40); const g1 = bls.G1.Point.fromHex(B_192_40);
@@ -616,7 +616,7 @@ should('bls12-381/basic/should construct point G1 from its uncompressed form (He
deepStrictEqual(g1_.y, y); deepStrictEqual(g1_.y, y);
}); });
should('bls12-381/basic/should construct point G2 from its uncompressed form (Raw Bytes)', () => { should('construct point G2 from its uncompressed form (Raw Bytes)', () => {
// Test Zero // Test Zero
const g2 = bls.G2.Point.fromHex(B_384_40); const g2 = bls.G2.Point.fromHex(B_384_40);
deepStrictEqual(g2.x, bls.G2.Point.ZERO.x, 'zero(x)'); deepStrictEqual(g2.x, bls.G2.Point.ZERO.x, 'zero(x)');
@@ -647,7 +647,7 @@ should('bls12-381/basic/should construct point G2 from its uncompressed form (Ra
deepStrictEqual(g2_.y, y); deepStrictEqual(g2_.y, y);
}); });
should('bls12-381/basic/should construct point G2 from its uncompressed form (Hex)', () => { should('construct point G2 from its uncompressed form (Hex)', () => {
// Test Zero // Test Zero
const g2 = bls.G2.Point.fromHex(B_384_40); const g2 = bls.G2.Point.fromHex(B_384_40);
@@ -679,7 +679,7 @@ should('bls12-381/basic/should construct point G2 from its uncompressed form (He
deepStrictEqual(g2_.y, y); deepStrictEqual(g2_.y, y);
}); });
should('bls12-381/basic/should get uncompressed form of point G1 (Raw Bytes)', () => { should('get uncompressed form of point G1 (Raw Bytes)', () => {
// Test Zero // Test Zero
deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40);
// Test Non-Zero // Test Non-Zero
@@ -700,7 +700,7 @@ should('bls12-381/basic/should get uncompressed form of point G1 (Raw Bytes)', (
); );
}); });
should('bls12-381/basic/should get uncompressed form of point G1 (Hex)', () => { should('get uncompressed form of point G1 (Hex)', () => {
// Test Zero // Test Zero
deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40);
// Test Non-Zero // Test Non-Zero
@@ -721,7 +721,7 @@ should('bls12-381/basic/should get uncompressed form of point G1 (Hex)', () => {
); );
}); });
should('bls12-381/basic/should get uncompressed form of point G2 (Raw Bytes)', () => { should('get uncompressed form of point G2 (Raw Bytes)', () => {
// Test Zero // Test Zero
deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40);
// Test Non-Zero // Test Non-Zero
@@ -748,7 +748,7 @@ should('bls12-381/basic/should get uncompressed form of point G2 (Raw Bytes)', (
); );
}); });
should('bls12-381/basic/should get uncompressed form of point G2 (Hex)', () => { should('get uncompressed form of point G2 (Hex)', () => {
// Test Zero // Test Zero
deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40);
@@ -776,22 +776,22 @@ should('bls12-381/basic/should get uncompressed form of point G2 (Hex)', () => {
); );
}); });
should('bls12-381/basic/should compress and decompress G1 points', async () => { should('compress and decompress G1 points', async () => {
const priv = bls.G1.Point.fromPrivateKey(42n); const priv = bls.G1.Point.fromPrivateKey(42n);
const publicKey = priv.toHex(true); const publicKey = priv.toHex(true);
const decomp = bls.G1.Point.fromHex(publicKey); const decomp = bls.G1.Point.fromHex(publicKey);
deepStrictEqual(publicKey, decomp.toHex(true)); deepStrictEqual(publicKey, decomp.toHex(true));
}); });
should('bls12-381/basic/should not compress and decompress zero G1 point', () => { should('not compress and decompress zero G1 point', () => {
throws(() => bls.G1.Point.fromPrivateKey(0n)); throws(() => bls.G1.Point.fromPrivateKey(0n));
}); });
should('bls12-381/basic/should compress and decompress G2 points', () => { should('compress and decompress G2 points', () => {
const priv = bls.G2.Point.fromPrivateKey(42n); const priv = bls.G2.Point.fromPrivateKey(42n);
const publicKey = priv.toHex(true); const publicKey = priv.toHex(true);
const decomp = bls.G2.Point.fromHex(publicKey); const decomp = bls.G2.Point.fromHex(publicKey);
deepStrictEqual(publicKey, decomp.toHex(true)); deepStrictEqual(publicKey, decomp.toHex(true));
}); });
should('bls12-381/basic/should not compress and decompress zero G2 point', () => { should('not compress and decompress zero G2 point', () => {
throws(() => bls.G2.Point.fromPrivateKey(0n)); throws(() => bls.G2.Point.fromPrivateKey(0n));
}); });
const VALID_G1 = new bls.G1.Point( const VALID_G1 = new bls.G1.Point(
@@ -820,7 +820,7 @@ const INVALID_G1 = new bls.G1.Point(
) )
); );
should('bls12-381/basic/should aggregate pubkeys', () => { should('aggregate pubkeys', () => {
const agg = bls.aggregatePublicKeys([VALID_G1, VALID_G1_2]); const agg = bls.aggregatePublicKeys([VALID_G1, VALID_G1_2]);
deepStrictEqual( deepStrictEqual(
agg.x, agg.x,
@@ -832,19 +832,19 @@ should('bls12-381/basic/should aggregate pubkeys', () => {
); );
}); });
should('bls12-381/basic/should not aggregate invalid pubkeys', () => { should('not aggregate invalid pubkeys', () => {
throws(() => bls.aggregatePublicKeys([VALID_G1, INVALID_G1])); throws(() => bls.aggregatePublicKeys([VALID_G1, INVALID_G1]));
}); });
// should aggregate signatures // should aggregate signatures
should(`should produce correct signatures (${G2_VECTORS.length} vectors)`, async () => { should(`produce correct signatures (${G2_VECTORS.length} vectors)`, async () => {
for (let vector of G2_VECTORS) { for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector; const [priv, msg, expected] = vector;
const sig = await bls.sign(msg, priv); const sig = await bls.sign(msg, priv);
deepStrictEqual(bls.utils.bytesToHex(sig), expected); deepStrictEqual(bls.utils.bytesToHex(sig), expected);
} }
}); });
should(`should produce correct scalars (${SCALAR_VECTORS.length} vectors)`, async () => { should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, async () => {
const options = { const options = {
p: bls.CURVE.r, p: bls.CURVE.r,
m: 1, m: 1,
@@ -858,7 +858,7 @@ should(`should produce correct scalars (${SCALAR_VECTORS.length} vectors)`, asyn
deepStrictEqual(scalars[0][0], expected); deepStrictEqual(scalars[0][0], expected);
} }
}); });
should('bls12-381/basic/should verify signed message', async () => { should('verify signed message', async () => {
for (let i = 0; i < NUM_RUNS; i++) { for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G2_VECTORS[i]; const [priv, msg] = G2_VECTORS[i];
const sig = await bls.sign(msg, priv); const sig = await bls.sign(msg, priv);
@@ -867,7 +867,7 @@ should('bls12-381/basic/should verify signed message', async () => {
deepStrictEqual(res, true); deepStrictEqual(res, true);
} }
}); });
should('bls12-381/basic/should not verify signature with wrong message', async () => { should('not verify signature with wrong message', async () => {
for (let i = 0; i < NUM_RUNS; i++) { for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G2_VECTORS[i]; const [priv, msg] = G2_VECTORS[i];
const invMsg = G2_VECTORS[i + 1][1]; const invMsg = G2_VECTORS[i + 1][1];
@@ -877,7 +877,7 @@ should('bls12-381/basic/should not verify signature with wrong message', async (
deepStrictEqual(res, false); deepStrictEqual(res, false);
} }
}); });
should('bls12-381/basic/should not verify signature with wrong key', async () => { should('not verify signature with wrong key', async () => {
for (let i = 0; i < NUM_RUNS; i++) { for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G2_VECTORS[i]; const [priv, msg] = G2_VECTORS[i];
const sig = await bls.sign(msg, priv); const sig = await bls.sign(msg, priv);
@@ -887,7 +887,7 @@ should('bls12-381/basic/should not verify signature with wrong key', async () =>
deepStrictEqual(res, false); deepStrictEqual(res, false);
} }
}); });
should('bls12-381/basic/should verify multi-signature', async () => { should('verify multi-signature', async () => {
await fc.assert( await fc.assert(
fc.asyncProperty(FC_MSG_5, FC_BIGINT_5, async (messages, privateKeys) => { fc.asyncProperty(FC_MSG_5, FC_BIGINT_5, async (messages, privateKeys) => {
privateKeys = privateKeys.slice(0, messages.length); privateKeys = privateKeys.slice(0, messages.length);
@@ -901,7 +901,7 @@ should('bls12-381/basic/should verify multi-signature', async () => {
}) })
); );
}); });
should('bls12-381/basic/should batch verify multi-signatures', async () => { should('batch verify multi-signatures', async () => {
await fc.assert( await fc.assert(
fc.asyncProperty( fc.asyncProperty(
FC_MSG_5, FC_MSG_5,
@@ -926,7 +926,7 @@ should('bls12-381/basic/should batch verify multi-signatures', async () => {
) )
); );
}); });
should('bls12-381/basic/should not verify multi-signature with wrong public keys', async () => { should('not verify multi-signature with wrong public keys', async () => {
await fc.assert( await fc.assert(
fc.asyncProperty( fc.asyncProperty(
FC_MSG_5, FC_MSG_5,
@@ -951,7 +951,7 @@ should('bls12-381/basic/should not verify multi-signature with wrong public keys
) )
); );
}); });
should('bls12-381/basic/should verify multi-signature as simple signature', async () => { should('verify multi-signature as simple signature', async () => {
await fc.assert( await fc.assert(
fc.asyncProperty(FC_MSG, FC_BIGINT_5, async (message, privateKeys) => { fc.asyncProperty(FC_MSG, FC_BIGINT_5, async (message, privateKeys) => {
const publicKey = await Promise.all(privateKeys.map(getPubKey)); const publicKey = await Promise.all(privateKeys.map(getPubKey));
@@ -964,7 +964,7 @@ should('bls12-381/basic/should verify multi-signature as simple signature', asyn
}) })
); );
}); });
should('bls12-381/basic/should not verify wrong multi-signature as simple signature', async () => { should('not verify wrong multi-signature as simple signature', async () => {
await fc.assert( await fc.assert(
fc.asyncProperty(FC_MSG, FC_MSG, FC_BIGINT_5, async (message, wrongMessage, privateKeys) => { fc.asyncProperty(FC_MSG, FC_MSG, FC_BIGINT_5, async (message, wrongMessage, privateKeys) => {
const publicKey = await Promise.all(privateKeys.map(getPubKey)); const publicKey = await Promise.all(privateKeys.map(getPubKey));
@@ -980,34 +980,35 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat
}) })
); );
}); });
});
// Pairing // Pairing
{ describe('pairing', () => {
const { pairing, Fp12 } = bls; const { pairing, Fp12 } = bls;
const G1 = bls.G1.Point.BASE; const G1 = bls.G1.Point.BASE;
const G2 = bls.G2.Point.BASE; const G2 = bls.G2.Point.BASE;
should('pairing/creates negative G1 pairing', () => { should('creates negative G1 pairing', () => {
const p1 = pairing(G1, G2); const p1 = pairing(G1, G2);
const p2 = pairing(G1.negate(), G2); const p2 = pairing(G1.negate(), G2);
deepStrictEqual(Fp12.mul(p1, p2), Fp12.ONE); deepStrictEqual(Fp12.mul(p1, p2), Fp12.ONE);
}); });
should('pairing/creates negative G2 pairing', () => { should('creates negative G2 pairing', () => {
const p2 = pairing(G1.negate(), G2); const p2 = pairing(G1.negate(), G2);
const p3 = pairing(G1, G2.negate()); const p3 = pairing(G1, G2.negate());
deepStrictEqual(p2, p3); deepStrictEqual(p2, p3);
}); });
should('pairing/creates proper pairing output order', () => { should('creates proper pairing output order', () => {
const p1 = pairing(G1, G2); const p1 = pairing(G1, G2);
const p2 = Fp12.pow(p1, CURVE_ORDER); const p2 = Fp12.pow(p1, CURVE_ORDER);
deepStrictEqual(p2, Fp12.ONE); deepStrictEqual(p2, Fp12.ONE);
}); });
should('pairing/G1 billinearity', () => { should('G1 billinearity', () => {
const p1 = pairing(G1, G2); const p1 = pairing(G1, G2);
const p2 = pairing(G1.multiply(2n), G2); const p2 = pairing(G1.multiply(2n), G2);
deepStrictEqual(Fp12.mul(p1, p1), p2); deepStrictEqual(Fp12.mul(p1, p1), p2);
}); });
should('pairing/should not degenerate', () => { should('should not degenerate', () => {
const p1 = pairing(G1, G2); const p1 = pairing(G1, G2);
const p2 = pairing(G1.multiply(2n), G2); const p2 = pairing(G1.multiply(2n), G2);
const p3 = pairing(G1, G2.negate()); const p3 = pairing(G1, G2.negate());
@@ -1015,17 +1016,17 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat
notDeepStrictEqual(p1, p3); notDeepStrictEqual(p1, p3);
notDeepStrictEqual(p2, p3); notDeepStrictEqual(p2, p3);
}); });
should('pairing/G2 billinearity', () => { should('G2 billinearity', () => {
const p1 = pairing(G1, G2); const p1 = pairing(G1, G2);
const p2 = pairing(G1, G2.multiply(2n)); const p2 = pairing(G1, G2.multiply(2n));
deepStrictEqual(Fp12.mul(p1, p1), p2); deepStrictEqual(Fp12.mul(p1, p1), p2);
}); });
should('pairing/proper pairing composite check', () => { should('proper pairing composite check', () => {
const p1 = pairing(G1.multiply(37n), G2.multiply(27n)); const p1 = pairing(G1.multiply(37n), G2.multiply(27n));
const p2 = pairing(G1.multiply(999n), G2); const p2 = pairing(G1.multiply(999n), G2);
deepStrictEqual(p1, p2); deepStrictEqual(p1, p2);
}); });
should('pairing/vectors from https://github.com/zkcrypto/pairing', () => { should('vectors from https://github.com/zkcrypto/pairing', () => {
const p1 = pairing(G1, G2); const p1 = pairing(G1, G2);
deepStrictEqual( deepStrictEqual(
p1, p1,
@@ -1045,7 +1046,7 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat
]) ])
); );
}); });
should('pairing/finalExponentiate is correct', () => { should('finalExponentiate is correct', () => {
const p1 = Fp12.fromBigTwelve([ const p1 = Fp12.fromBigTwelve([
690392658038414015999440694435086329841032295415825549843130960252222448232974816207293269712691075396080336239827n, 690392658038414015999440694435086329841032295415825549843130960252222448232974816207293269712691075396080336239827n,
1673244384695948045466836192250093912021245353707563547917201356526057153141766171738038843400145227470982267854187n, 1673244384695948045466836192250093912021245353707563547917201356526057153141766171738038843400145227470982267854187n,
@@ -1078,9 +1079,9 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat
]) ])
); );
}); });
} });
// hashToCurve // hashToCurve
{ describe('hash-to-curve', () => {
const DST = 'QUUX-V01-CS02-with-expander-SHA256-128'; const DST = 'QUUX-V01-CS02-with-expander-SHA256-128';
const VECTORS = [ const VECTORS = [
{ {
@@ -1824,9 +1825,9 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat
deepStrictEqual(p.toHex(), t.expected); deepStrictEqual(p.toHex(), t.expected);
}); });
} }
} });
// Deterministic // Deterministic
{ describe('bls12-381 deterministic', () => {
// NOTE: Killic returns all items in reversed order, which looks strange: // NOTE: Killic returns all items in reversed order, which looks strange:
// instead of `Fp2(${this.c0} + ${this.c1}×i)`; it returns `Fp2(${this.c0}×i + ${this.c1})`; // instead of `Fp2(${this.c0} + ${this.c1}×i)`; it returns `Fp2(${this.c0}×i + ${this.c1})`;
const killicHex = (lst) => const killicHex = (lst) =>
@@ -1934,7 +1935,7 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat
} }
} }
}); });
} });
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';

View File

@@ -1,7 +1,14 @@
import { deepStrictEqual, throws } from 'assert'; import { deepEqual, deepStrictEqual, strictEqual, throws } from 'assert';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { ed25519, ed25519ctx, ed25519ph, x25519, RistrettoPoint } from '../lib/esm/ed25519.js'; import {
ed25519,
ed25519ctx,
ed25519ph,
x25519,
RistrettoPoint,
ED25519_TORSION_SUBGROUP,
} from '../lib/esm/ed25519.js';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' }; import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
@@ -10,6 +17,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' }; import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
describe('ed25519', () => {
const ed = ed25519; const ed = ed25519;
const hex = bytesToHex; const hex = bytesToHex;
@@ -27,12 +35,12 @@ function utf8ToBytes(str) {
ed.utils.precompute(8); ed.utils.precompute(8);
should('ed25519/should not accept >32byte private keys', () => { should('not accept >32byte private keys', () => {
const invalidPriv = const invalidPriv =
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
throws(() => ed.getPublicKey(invalidPriv)); throws(() => ed.getPublicKey(invalidPriv));
}); });
should('ed25519/should verify recent signature', () => { should('verify recent signature', () => {
fc.assert( fc.assert(
fc.property( fc.property(
fc.hexaString({ minLength: 2, maxLength: 32 }), fc.hexaString({ minLength: 2, maxLength: 32 }),
@@ -48,7 +56,7 @@ should('ed25519/should verify recent signature', () => {
{ numRuns: 5 } { numRuns: 5 }
); );
}); });
should('ed25519/should not verify signature with wrong message', () => { should('not verify signature with wrong message', () => {
fc.assert( fc.assert(
fc.property( fc.property(
fc.array(fc.integer({ min: 0x00, max: 0xff })), fc.array(fc.integer({ min: 0x00, max: 0xff })),
@@ -72,38 +80,40 @@ should('ed25519/should not verify signature with wrong message', () => {
const privKey = to32Bytes('a665a45920422f9d417e4867ef'); const privKey = to32Bytes('a665a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
should('ed25519/basic methods/should sign and verify', () => { describe('basic methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
should('ed25519/basic methods/should not verify signature with wrong public key', () => { should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12); const publicKey = ed.getPublicKey(12);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false); deepStrictEqual(ed.verify(signature, msg, publicKey), false);
}); });
should('ed25519/basic methods/should not verify signature with wrong hash', () => { should('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
}); });
});
should('ed25519/sync methods/should sign and verify', () => { describe('sync methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
should('ed25519/sync methods/should not verify signature with wrong public key', () => { should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12); const publicKey = ed.getPublicKey(12);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false); deepStrictEqual(ed.verify(signature, msg, publicKey), false);
}); });
should('ed25519/sync methods/should not verify signature with wrong hash', () => { should('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
}); });
});
// https://xmr.llcoins.net/addresstests.html // https://xmr.llcoins.net/addresstests.html
should( should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1', 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1',
@@ -646,10 +656,23 @@ for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
should('X25519 base point', () => { should('X25519 base point', () => {
const { y } = ed25519.Point.BASE; const { y } = ed25519.Point.BASE;
const u = ed25519.utils.mod((y + 1n) * ed25519.utils.invert(1n - y, ed25519.CURVE.P)); const { Fp } = ed25519.CURVE;
const u = Fp.create((y + 1n) * Fp.invert(1n - y));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
}); });
should('isTorsionFree()', () => {
const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point;
for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) {
const dirty = orig.add(ed.Point.fromHex(hex));
const cleared = dirty.clearCofactor();
strictEqual(orig.isTorsionFree(), true, `orig must be torsionFree: ${hex}`);
strictEqual(dirty.isTorsionFree(), false, `dirty must not be torsionFree: ${hex}`);
strictEqual(cleared.isTorsionFree(), true, `cleared must be torsionFree: ${hex}`);
}
});
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,5 +1,5 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js'; import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
@@ -7,6 +7,7 @@ import { numberToBytesLE } from '../lib/esm/abstract/utils.js';
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' }; import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' }; import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
describe('ed448', () => {
const ed = ed448; const ed = ed448;
const hex = bytesToHex; const hex = bytesToHex;
ed.utils.precompute(4); ed.utils.precompute(4);
@@ -323,7 +324,7 @@ for (let i = 0; i < VECTORS_RFC8032.length; i++) {
}); });
} }
should('ed448/should not accept >57byte private keys', async () => { should('not accept >57byte private keys', async () => {
const invalidPriv = const invalidPriv =
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
throws(() => ed.getPublicKey(invalidPriv)); throws(() => ed.getPublicKey(invalidPriv));
@@ -334,7 +335,7 @@ function to57Bytes(numOrStr) {
return hexToBytes(hex.padStart(114, '0')); return hexToBytes(hex.padStart(114, '0'));
} }
should('ed448/should verify recent signature', () => { should('verify recent signature', () => {
fc.assert( fc.assert(
fc.property( fc.property(
fc.hexaString({ minLength: 2, maxLength: 57 }), fc.hexaString({ minLength: 2, maxLength: 57 }),
@@ -350,7 +351,7 @@ should('ed448/should verify recent signature', () => {
{ numRuns: 5 } { numRuns: 5 }
); );
}); });
should('ed448/should not verify signature with wrong message', () => { should('not verify signature with wrong message', () => {
fc.assert( fc.assert(
fc.property( fc.property(
fc.array(fc.integer({ min: 0x00, max: 0xff })), fc.array(fc.integer({ min: 0x00, max: 0xff })),
@@ -374,39 +375,43 @@ should('ed448/should not verify signature with wrong message', () => {
const privKey = to57Bytes('a665a45920422f9d417e4867ef'); const privKey = to57Bytes('a665a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
should('ed25519/basic methods/should sign and verify', () => { describe('basic methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
should('ed25519/basic methods/should not verify signature with wrong public key', () => { should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12); const publicKey = ed.getPublicKey(12);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false); deepStrictEqual(ed.verify(signature, msg, publicKey), false);
}); });
should('ed25519/basic methods/should not verify signature with wrong hash', () => { should('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
}); });
});
should('ed25519/sync methods/should sign and verify', () => { describe('sync methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
should('ed25519/sync methods/should not verify signature with wrong public key', async () => { should('not verify signature with wrong public key', async () => {
const publicKey = ed.getPublicKey(12); const publicKey = ed.getPublicKey(12);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false); deepStrictEqual(ed.verify(signature, msg, publicKey), false);
}); });
should('ed25519/sync methods/should not verify signature with wrong hash', async () => { should('not verify signature with wrong hash', async () => {
const publicKey = ed.getPublicKey(privKey); const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
}); });
});
should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => { should('BASE_POINT.multiply() throws in Point#multiply on TEST 5', () => {
for (const num of [0n, 0, -1n, -1, 1.1]) { for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => ed.Point.BASE.multiply(num)); throws(() => ed.Point.BASE.multiply(num));
} }
@@ -651,11 +656,13 @@ for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
should('X448 base point', () => { should('X448 base point', () => {
const { x, y } = ed448.Point.BASE; const { x, y } = ed448.Point.BASE;
const { P } = ed448.CURVE; const { Fp } = ed448.CURVE;
const invX = ed448.utils.invert(x * x, P); // x² // const invX = Fp.invert(x * x); // x²
const u = ed448.utils.mod(y * y * invX, P); // (y²/x²) const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)
// const u = Fp.create(y * y * invX);
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu);
}); });
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';

View File

@@ -1,5 +1,5 @@
import { deepStrictEqual } from 'assert'; import { deepStrictEqual } from 'assert';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import { bytesToHex } from '@noble/hashes/utils'; import { bytesToHex } from '@noble/hashes/utils';
// Generic tests for all curves in package // Generic tests for all curves in package
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
@@ -51,9 +51,10 @@ import { default as ed448_ro } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL
import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' }; import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' };
function testExpandXMD(hash, vectors) { function testExpandXMD(hash, vectors) {
describe(`${vectors.hash}/${vectors.DST.length}`, () => {
for (let i = 0; i < vectors.tests.length; i++) { for (let i = 0; i < vectors.tests.length; i++) {
const t = vectors.tests[i]; const t = vectors.tests[i];
should(`expand_message_xmd/${vectors.hash}/${vectors.DST.length}/${i}`, () => { should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
const p = expand_message_xmd( const p = expand_message_xmd(
stringToBytes(t.msg), stringToBytes(t.msg),
stringToBytes(vectors.DST), stringToBytes(vectors.DST),
@@ -63,16 +64,20 @@ function testExpandXMD(hash, vectors) {
deepStrictEqual(bytesToHex(p), t.uniform_bytes); deepStrictEqual(bytesToHex(p), t.uniform_bytes);
}); });
} }
});
} }
describe('expand_message_xmd', () => {
testExpandXMD(sha256, xmd_sha256_38); testExpandXMD(sha256, xmd_sha256_38);
testExpandXMD(sha256, xmd_sha256_256); testExpandXMD(sha256, xmd_sha256_256);
testExpandXMD(sha512, xmd_sha512_38); testExpandXMD(sha512, xmd_sha512_38);
});
function testExpandXOF(hash, vectors) { function testExpandXOF(hash, vectors) {
describe(`${vectors.hash}/${vectors.DST.length}`, () => {
for (let i = 0; i < vectors.tests.length; i++) { for (let i = 0; i < vectors.tests.length; i++) {
const t = vectors.tests[i]; const t = vectors.tests[i];
should(`expand_message_xof/${vectors.hash}/${vectors.DST.length}/${i}`, () => { should(`${i}`, () => {
const p = expand_message_xof( const p = expand_message_xof(
stringToBytes(t.msg), stringToBytes(t.msg),
stringToBytes(vectors.DST), stringToBytes(vectors.DST),
@@ -83,11 +88,14 @@ function testExpandXOF(hash, vectors) {
deepStrictEqual(bytesToHex(p), t.uniform_bytes); deepStrictEqual(bytesToHex(p), t.uniform_bytes);
}); });
} }
});
} }
describe('expand_message_xof', () => {
testExpandXOF(shake128, xof_shake128_36); testExpandXOF(shake128, xof_shake128_36);
testExpandXOF(shake128, xof_shake128_256); testExpandXOF(shake128, xof_shake128_256);
testExpandXOF(shake256, xof_shake256_36); testExpandXOF(shake256, xof_shake256_36);
});
function stringToFp(s) { function stringToFp(s) {
// bls-G2 support // bls-G2 support
@@ -99,9 +107,10 @@ function stringToFp(s) {
} }
function testCurve(curve, ro, nu) { function testCurve(curve, ro, nu) {
describe(`${ro.curve}/${ro.ciphersuite}`, () => {
for (let i = 0; i < ro.vectors.length; i++) { for (let i = 0; i < ro.vectors.length; i++) {
const t = ro.vectors[i]; const t = ro.vectors[i];
should(`${ro.curve}/${ro.ciphersuite}(${i})`, () => { should(`(${i})`, () => {
const p = curve.Point.hashToCurve(stringToBytes(t.msg), { const p = curve.Point.hashToCurve(stringToBytes(t.msg), {
DST: ro.dst, DST: ro.dst,
}); });
@@ -109,9 +118,11 @@ function testCurve(curve, ro, nu) {
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
}); });
} }
});
describe(`${nu.curve}/${nu.ciphersuite}`, () => {
for (let i = 0; i < nu.vectors.length; i++) { for (let i = 0; i < nu.vectors.length; i++) {
const t = nu.vectors[i]; const t = nu.vectors[i];
should(`${nu.curve}/${nu.ciphersuite}(${i})`, () => { should(`(${i})`, () => {
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), { const p = curve.Point.encodeToCurve(stringToBytes(t.msg), {
DST: nu.dst, DST: nu.dst,
}); });
@@ -119,6 +130,7 @@ function testCurve(curve, ro, nu) {
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
}); });
} }
});
} }
testCurve(secp256r1, p256_ro, p256_nu); testCurve(secp256r1, p256_ro, p256_nu);

View File

@@ -1,5 +1,5 @@
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js'; import { jubjub, findGroupHash } from '../lib/esm/jubjub.js';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
@@ -18,6 +18,7 @@ const G_PROOF = new jubjub.ExtendedPoint(
const getXY = (p) => ({ x: p.x, y: p.y }); const getXY = (p) => ({ x: p.x, y: p.y });
describe('jubjub', () => {
should('toHex/fromHex', () => { should('toHex/fromHex', () => {
// More than field // More than field
throws(() => throws(() =>
@@ -32,8 +33,8 @@ should('toHex/fromHex', () => {
throws(() => throws(() =>
jubjub.Point.fromHex( jubjub.Point.fromHex(
new Uint8Array([ new Uint8Array([
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
]) ])
) )
); );
@@ -61,11 +62,18 @@ should('toHex/fromHex', () => {
}); });
should('Find generators', () => { should('Find generators', () => {
const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95])); const spend = findGroupHash(
const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95])); new Uint8Array(),
new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95])
);
const proof = findGroupHash(
new Uint8Array(),
new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95])
);
deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine())); deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine()));
deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine())); deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine()));
}); });
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';

View File

@@ -1,5 +1,5 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import { secp192r1, P192 } from '../lib/esm/p192.js'; import { secp192r1, P192 } from '../lib/esm/p192.js';
import { secp224r1, P224 } from '../lib/esm/p224.js'; import { secp224r1, P224 } from '../lib/esm/p224.js';
import { secp256r1, P256 } from '../lib/esm/p256.js'; import { secp256r1, P256 } from '../lib/esm/p256.js';
@@ -344,10 +344,11 @@ function runWycheproof(name, CURVE, group, index) {
for (const name in WYCHEPROOF_ECDSA) { for (const name in WYCHEPROOF_ECDSA) {
const { curve, hashes } = WYCHEPROOF_ECDSA[name]; const { curve, hashes } = WYCHEPROOF_ECDSA[name];
describe('Wycheproof/WYCHEPROOF_ECDSA', () => {
for (const hName in hashes) { for (const hName in hashes) {
const { hash, tests } = hashes[hName]; const { hash, tests } = hashes[hName];
const CURVE = curve.create(hash); const CURVE = curve.create(hash);
should(`Wycheproof/WYCHEPROOF_ECDSA ${name}/${hName}`, () => { should(`${name}/${hName}`, () => {
for (let i = 0; i < tests.length; i++) { for (let i = 0; i < tests.length; i++) {
const groups = tests[i].testGroups; const groups = tests[i].testGroups;
for (let j = 0; j < groups.length; j++) { for (let j = 0; j < groups.length; j++) {
@@ -357,6 +358,7 @@ for (const name in WYCHEPROOF_ECDSA) {
} }
}); });
} }
});
} }
const hexToBigint = (hex) => BigInt(`0x${hex}`); const hexToBigint = (hex) => BigInt(`0x${hex}`);

View File

@@ -1,12 +1,13 @@
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { secp256k1, schnorr } from '../lib/esm/secp256k1.js'; import { secp256k1, schnorr } from '../lib/esm/secp256k1.js';
import { Fp } from '../lib/esm/abstract/modular.js';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' }; import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' }; import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
import { default as privates } from './vectors/privates.json' assert { type: 'json' }; import { default as privates } from './vectors/privates.json' assert { type: 'json' };
import { default as points } from './vectors/points.json' assert { type: 'json' }; import { default as points } from './vectors/points.json' assert { type: 'json' };
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' }; import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
import { should } from 'micro-should'; import { should, describe } from 'micro-should';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
@@ -16,7 +17,6 @@ const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8'); const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n); const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
const P = secp.CURVE.Fp.ORDER;
// prettier-ignore // prettier-ignore
const INVALID_ITEMS = ['deadbeef', Math.pow(2, 53), [1], 'xyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxy', secp.CURVE.n + 2n]; const INVALID_ITEMS = ['deadbeef', Math.pow(2, 53), [1], 'xyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxy', secp.CURVE.n + 2n];
@@ -30,7 +30,8 @@ function hexToNumber(hex) {
return BigInt(`0x${hex}`); return BigInt(`0x${hex}`);
} }
should('secp256k1.getPublicKey()', () => { describe('secp256k1', () => {
should('getPublicKey()', () => {
const data = privatesTxt const data = privatesTxt
.split('\n') .split('\n')
.filter((line) => line) .filter((line) => line)
@@ -49,12 +50,12 @@ should('secp256k1.getPublicKey()', () => {
deepStrictEqual(toBEHex(point3.y), y); deepStrictEqual(toBEHex(point3.y), y);
} }
}); });
should('secp256k1.getPublicKey() rejects invalid keys', () => { should('getPublicKey() rejects invalid keys', () => {
// for (const item of INVALID_ITEMS) { for (const item of INVALID_ITEMS) {
// throws(() => secp.getPublicKey(item)); throws(() => secp.getPublicKey(item));
// } }
}); });
should('secp256k1.precompute', () => { should('precompute', () => {
secp.utils.precompute(4); secp.utils.precompute(4);
const data = privatesTxt const data = privatesTxt
.split('\n') .split('\n')
@@ -75,7 +76,7 @@ should('secp256k1.precompute', () => {
} }
}); });
should('secp256k1.Point.isValidPoint()', () => { should('Point.isValidPoint()', () => {
for (const vector of points.valid.isPoint) { for (const vector of points.valid.isPoint) {
const { P, expected } = vector; const { P, expected } = vector;
if (expected) { if (expected) {
@@ -86,7 +87,7 @@ should('secp256k1.Point.isValidPoint()', () => {
} }
}); });
should('secp256k1.Point.fromPrivateKey()', () => { should('Point.fromPrivateKey()', () => {
for (const vector of points.valid.pointFromScalar) { for (const vector of points.valid.pointFromScalar) {
const { d, expected } = vector; const { d, expected } = vector;
let p = secp.Point.fromPrivateKey(d); let p = secp.Point.fromPrivateKey(d);
@@ -94,7 +95,7 @@ should('secp256k1.Point.fromPrivateKey()', () => {
} }
}); });
should('secp256k1.Point#toHex(compressed)', () => { should('Point#toHex(compressed)', () => {
for (const vector of points.valid.pointCompress) { for (const vector of points.valid.pointCompress) {
const { P, compress, expected } = vector; const { P, compress, expected } = vector;
let p = secp.Point.fromHex(P); let p = secp.Point.fromHex(P);
@@ -102,7 +103,7 @@ should('secp256k1.Point#toHex(compressed)', () => {
} }
}); });
should('secp256k1.Point#toHex() roundtrip (failed case)', () => { should('Point#toHex() roundtrip (failed case)', () => {
const point1 = const point1 =
secp.Point.fromPrivateKey( secp.Point.fromPrivateKey(
88572218780422190464634044548753414301110513745532121983949500266768436236425n 88572218780422190464634044548753414301110513745532121983949500266768436236425n
@@ -111,7 +112,7 @@ should('secp256k1.Point#toHex() roundtrip (failed case)', () => {
// deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); // deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
}); });
should('secp256k1.Point#toHex() roundtrip', () => { should('Point#toHex() roundtrip', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (x) => { fc.property(FC_BIGINT, (x) => {
const point1 = secp.Point.fromPrivateKey(x); const point1 = secp.Point.fromPrivateKey(x);
@@ -121,7 +122,7 @@ should('secp256k1.Point#toHex() roundtrip', () => {
); );
}); });
should('secp256k1.Point#add(other)', () => { should('Point#add(other)', () => {
for (const vector of points.valid.pointAdd) { for (const vector of points.valid.pointAdd) {
const { P, Q, expected } = vector; const { P, Q, expected } = vector;
let p = secp.Point.fromHex(P); let p = secp.Point.fromHex(P);
@@ -136,7 +137,7 @@ should('secp256k1.Point#add(other)', () => {
} }
}); });
should('secp256k1.Point#multiply(privateKey)', () => { should('Point#multiply(privateKey)', () => {
for (const vector of points.valid.pointMultiply) { for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector; const { P, d, expected } = vector;
const p = secp.Point.fromHex(P); const p = secp.Point.fromHex(P);
@@ -175,7 +176,7 @@ should('secp256k1.Point#multiply(privateKey)', () => {
// console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0]) // console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0])
// }); // });
should('secp256k1.Signature.fromCompactHex() roundtrip', () => { should('Signature.fromCompactHex() roundtrip', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
@@ -184,7 +185,7 @@ should('secp256k1.Signature.fromCompactHex() roundtrip', () => {
); );
}); });
should('secp256k1.Signature.fromDERHex() roundtrip', () => { should('Signature.fromDERHex() roundtrip', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
@@ -193,7 +194,7 @@ should('secp256k1.Signature.fromDERHex() roundtrip', () => {
); );
}); });
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', () => { should('sign()/should create deterministic signatures with RFC 6979', () => {
for (const vector of ecdsa.valid) { for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d); let usig = secp.sign(vector.m, vector.d);
let sig = usig.toCompactHex(); let sig = usig.toCompactHex();
@@ -203,18 +204,21 @@ should('secp256k1.sign()/should create deterministic signatures with RFC 6979',
} }
}); });
should('secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979', () => { should(
'secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979',
() => {
for (const vector of ecdsa.invalid.sign) { for (const vector of ecdsa.invalid.sign) {
throws(() => secp.sign(vector.m, vector.d)); throws(() => secp.sign(vector.m, vector.d));
} }
}); }
);
should('secp256k1.sign()/edge cases', () => { should('sign()/edge cases', () => {
throws(() => secp.sign()); throws(() => secp.sign());
throws(() => secp.sign('')); throws(() => secp.sign(''));
}); });
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', () => { should('sign()/should create correct DER encoding against libsecp256k1', () => {
const CASES = [ const CASES = [
[ [
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b', 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
@@ -237,7 +241,7 @@ should('secp256k1.sign()/should create correct DER encoding against libsecp256k1
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp); deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
} }
}); });
should('secp256k1.sign()/sign ecdsa extraData', () => { should('sign()/sign ecdsa extraData', () => {
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000'; const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001'; const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33'; const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
@@ -258,7 +262,7 @@ should('secp256k1.sign()/sign ecdsa extraData', () => {
} }
}); });
should('secp256k1.verify()/should verify signature', () => { should('verify()/should verify signature', () => {
const MSG = '01'.repeat(32); const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n; const PRIV_KEY = 0x2n;
const signature = secp.sign(MSG, PRIV_KEY); const signature = secp.sign(MSG, PRIV_KEY);
@@ -266,7 +270,7 @@ should('secp256k1.verify()/should verify signature', () => {
deepStrictEqual(publicKey.length, 65); deepStrictEqual(publicKey.length, 65);
deepStrictEqual(secp.verify(signature, MSG, publicKey), true); deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
}); });
should('secp256k1.verify()/should not verify signature with wrong public key', () => { should('verify()/should not verify signature with wrong public key', () => {
const MSG = '01'.repeat(32); const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n; const PRIV_KEY = 0x2n;
const WRONG_PRIV_KEY = 0x22n; const WRONG_PRIV_KEY = 0x22n;
@@ -275,7 +279,7 @@ should('secp256k1.verify()/should not verify signature with wrong public key', (
deepStrictEqual(publicKey.length, 130); deepStrictEqual(publicKey.length, 130);
deepStrictEqual(secp.verify(signature, MSG, publicKey), false); deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
}); });
should('secp256k1.verify()/should not verify signature with wrong hash', () => { should('verify()/should not verify signature with wrong hash', () => {
const MSG = '01'.repeat(32); const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n; const PRIV_KEY = 0x2n;
const WRONG_MSG = '11'.repeat(32); const WRONG_MSG = '11'.repeat(32);
@@ -284,7 +288,7 @@ should('secp256k1.verify()/should not verify signature with wrong hash', () => {
deepStrictEqual(publicKey.length, 65); deepStrictEqual(publicKey.length, 65);
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false); deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
}); });
should('secp256k1.verify()/should verify random signatures', () => should('verify()/should verify random signatures', () =>
fc.assert( fc.assert(
fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => { fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => {
const pub = secp.getPublicKey(privKey); const pub = secp.getPublicKey(privKey);
@@ -293,10 +297,11 @@ should('secp256k1.verify()/should verify random signatures', () =>
}) })
) )
); );
should('secp256k1.verify()/should not verify signature with invalid r/s', () => { should('verify()/should not verify signature with invalid r/s', () => {
const msg = new Uint8Array([ const msg = new Uint8Array([
0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1, 0xe3, 0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1,
0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60, 0x50, 0x23, 0xe3, 0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60,
0x50, 0x23,
]); ]);
const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n; const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n;
const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n; const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n;
@@ -312,7 +317,7 @@ should('secp256k1.verify()/should not verify signature with invalid r/s', () =>
// Verifies, but it shouldn't, because signature S > curve order // Verifies, but it shouldn't, because signature S > curve order
deepStrictEqual(verified, false); deepStrictEqual(verified, false);
}); });
should('secp256k1.verify()/should not verify msg = curve order', () => { should('verify()/should not verify msg = curve order', () => {
const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'; const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n; const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n; const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
@@ -322,7 +327,7 @@ should('secp256k1.verify()/should not verify msg = curve order', () => {
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub), false); deepStrictEqual(secp.verify(sig, msg, pub), false);
}); });
should('secp256k1.verify()/should verify non-strict msg bb5a...', () => { should('verify()/should verify non-strict msg bb5a...', () => {
const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023'; const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n; const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
@@ -362,7 +367,7 @@ for (let vec of vectors) {
}); });
} }
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', () => { should('recoverPublicKey()/should recover public key from recovery bit', () => {
const message = '00000000000000000000000000000000000000000000000000000000deadbeef'; const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
const privateKey = 123456789n; const privateKey = 123456789n;
const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false); const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
@@ -370,17 +375,17 @@ should('secp256k1.recoverPublicKey()/should recover public key from recovery bit
const recoveredPubkey = sig.recoverPublicKey(message); const recoveredPubkey = sig.recoverPublicKey(message);
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery); // const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
deepStrictEqual(recoveredPubkey !== null, true); deepStrictEqual(recoveredPubkey !== null, true);
deepStrictEqual(recoveredPubkey.toHex(), publicKey); deepStrictEqual(recoveredPubkey.toHex(false), publicKey);
deepStrictEqual(secp.verify(sig, message, publicKey), true); deepStrictEqual(secp.verify(sig, message, publicKey), true);
}); });
should('secp256k1.recoverPublicKey()/should not recover zero points', () => { should('recoverPublicKey()/should not recover zero points', () => {
const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9'; const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const sig = const sig =
'79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9'; '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const recovery = 0; const recovery = 0;
throws(() => secp.recoverPublicKey(msgHash, sig, recovery)); throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
}); });
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', () => { should('recoverPublicKey()/should handle all-zeros msghash', () => {
const privKey = secp.utils.randomPrivateKey(); const privKey = secp.utils.randomPrivateKey();
const pub = secp.getPublicKey(privKey); const pub = secp.getPublicKey(privKey);
const zeros = '0000000000000000000000000000000000000000000000000000000000000000'; const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
@@ -388,7 +393,7 @@ should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', () => {
const recoveredKey = sig.recoverPublicKey(zeros); const recoveredKey = sig.recoverPublicKey(zeros);
deepStrictEqual(recoveredKey.toRawBytes(), pub); deepStrictEqual(recoveredKey.toRawBytes(), pub);
}); });
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', () => { should('recoverPublicKey()/should handle RFC 6979 vectors', () => {
for (const vector of ecdsa.valid) { for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d); let usig = secp.sign(vector.m, vector.d);
let sig = usig.toDERHex(); let sig = usig.toDERHex();
@@ -402,7 +407,7 @@ should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', () => {
function derToPub(der) { function derToPub(der) {
return der.slice(46); return der.slice(46);
} }
should('secp256k1.getSharedSecret()/should produce correct results', () => { should('getSharedSecret()/should produce correct results', () => {
// TODO: Once der is there, run all tests. // TODO: Once der is there, run all tests.
for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) { for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) {
if (vector.result === 'invalid' || vector.private.length !== 64) { if (vector.result === 'invalid' || vector.private.length !== 64) {
@@ -415,7 +420,7 @@ should('secp256k1.getSharedSecret()/should produce correct results', () => {
} }
} }
}); });
should('secp256k1.getSharedSecret()/priv/pub order matters', () => { should('getSharedSecret()/priv/pub order matters', () => {
for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) { for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) {
if (vector.result === 'valid') { if (vector.result === 'valid') {
let priv = vector.private; let priv = vector.private;
@@ -424,27 +429,36 @@ should('secp256k1.getSharedSecret()/priv/pub order matters', () => {
} }
} }
}); });
should('secp256k1.getSharedSecret()/rejects invalid keys', () => { should('getSharedSecret()/rejects invalid keys', () => {
throws(() => secp.getSharedSecret('01', '02')); throws(() => secp.getSharedSecret('01', '02'));
}); });
should('secp256k1.utils.isValidPrivateKey()', () => { should('utils.isValidPrivateKey()', () => {
for (const vector of privates.valid.isPrivate) { for (const vector of privates.valid.isPrivate) {
const { d, expected } = vector; const { d, expected } = vector;
deepStrictEqual(secp.utils.isValidPrivateKey(d), expected); deepStrictEqual(secp.utils.isValidPrivateKey(d), expected);
} }
}); });
should('have proper curve equation in assertValidity()', () => {
throws(() => {
const { Fp } = secp.CURVE;
let point = new secp.Point(Fp.create(-2n), Fp.create(-1n));
point.assertValidity();
});
});
const Fn = Fp(secp.CURVE.n);
const normal = secp.utils._normalizePrivateKey; const normal = secp.utils._normalizePrivateKey;
const tweakUtils = { const tweakUtils = {
privateAdd: (privateKey, tweak) => { privateAdd: (privateKey, tweak) => {
const p = normal(privateKey); const p = normal(privateKey);
const t = normal(tweak); const t = normal(tweak);
return secp.utils._bigintToBytes(secp.utils.mod(p + t, secp.CURVE.n)); return secp.utils._bigintToBytes(Fn.create(p + t));
}, },
privateNegate: (privateKey) => { privateNegate: (privateKey) => {
const p = normal(privateKey); const p = normal(privateKey);
return secp.utils._bigintToBytes(secp.CURVE.n - p); return secp.utils._bigintToBytes(Fn.negate(p));
}, },
pointAddScalar: (p, tweak, isCompressed) => { pointAddScalar: (p, tweak, isCompressed) => {
@@ -463,45 +477,45 @@ const tweakUtils = {
}, },
}; };
should('secp256k1.privateAdd()', () => { should('privateAdd()', () => {
for (const vector of privates.valid.add) { for (const vector of privates.valid.add) {
const { a, b, expected } = vector; const { a, b, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected); deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
} }
}); });
should('secp256k1.privateNegate()', () => { should('privateNegate()', () => {
for (const vector of privates.valid.negate) { for (const vector of privates.valid.negate) {
const { a, expected } = vector; const { a, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected); deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
} }
}); });
should('secp256k1.pointAddScalar()', () => { should('pointAddScalar()', () => {
for (const vector of points.valid.pointAddScalar) { for (const vector of points.valid.pointAddScalar) {
const { description, P, d, expected } = vector; const { description, P, d, expected } = vector;
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected); deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
} }
}); });
should('secp256k1.pointAddScalar() invalid', () => { should('pointAddScalar() invalid', () => {
for (const vector of points.invalid.pointAddScalar) { for (const vector of points.invalid.pointAddScalar) {
const { P, d, exception } = vector; const { P, d, exception } = vector;
throws(() => tweakUtils.pointAddScalar(P, d)); throws(() => tweakUtils.pointAddScalar(P, d));
} }
}); });
should('secp256k1.pointMultiply()', () => { should('pointMultiply()', () => {
for (const vector of points.valid.pointMultiply) { for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector; const { P, d, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected); deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
} }
}); });
should('secp256k1.pointMultiply() invalid', () => { should('pointMultiply() invalid', () => {
for (const vector of points.invalid.pointMultiply) { for (const vector of points.invalid.pointMultiply) {
const { P, d, exception } = vector; const { P, d, exception } = vector;
throws(() => tweakUtils.pointMultiply(P, d)); throws(() => tweakUtils.pointMultiply(P, d));
} }
}); });
should('secp256k1.wychenproof vectors', () => { should('wychenproof vectors', () => {
for (let group of wp.testGroups) { for (let group of wp.testGroups) {
const pubKey = secp.Point.fromHex(group.key.uncompressed); const pubKey = secp.Point.fromHex(group.key.uncompressed);
for (let test of group.tests) { for (let test of group.tests) {
@@ -528,6 +542,7 @@ should('secp256k1.wychenproof vectors', () => {
} }
} }
}); });
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';