This commit is contained in:
Paul Miller 2023-01-08 17:02:53 +00:00
parent 2bd5e9ac16
commit 5600629bca
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
2 changed files with 46 additions and 31 deletions

@ -8,6 +8,7 @@
// 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 {
@ -238,6 +239,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
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. Useful for P521 with var-length priv key
* - `wrapPrivateKey()` executed after most checks, but before `0 < key < n` check. Useful for BLS12-381 with cofactor higher than 1.
*/
function normalizePrivateKey(key: PrivKey): bigint { function normalizePrivateKey(key: PrivKey): bigint {
if (typeof CURVE.normalizePrivateKey === 'function') { if (typeof CURVE.normalizePrivateKey === 'function') {
key = CURVE.normalizePrivateKey(key); key = CURVE.normalizePrivateKey(key);
@ -261,6 +268,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
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 (typeof num === 'number' && Number.isSafeInteger(num) && num > 0) return BigInt(num);
if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num; if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num;
@ -289,7 +300,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 +308,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 +334,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
@ -551,11 +561,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);
@ -835,8 +845,8 @@ 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?
@ -910,13 +920,16 @@ 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 big = bytesToNumberBE(bytes);
return delta > 0 ? big >> BigInt(delta) : big;
}
// 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; let h = bits2int_2(hash);
const byteLength = hash.length; const { n } = CURVE;
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
let h = bytesToNumberBE(hash);
if (delta > 0) h = h >> BigInt(delta);
if (!truncateOnly && h >= n) h -= n; if (!truncateOnly && h >= n) h -= n;
return h; return h;
} }
@ -1009,16 +1022,17 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
// DER-encoded // DER-encoded
toDERRawBytes(isCompressed = false) { toDERRawBytes() {
return hexToBytes(this.toDERHex(isCompressed)); return hexToBytes(this.toDERHex());
} }
toDERHex(isCompressed = false) { toDERHex() {
const sHex = sliceDER(numberToHexUnpadded(this.s)); const sHex = sliceDER(numberToHexUnpadded(this.s));
if (isCompressed) return sHex;
const rHex = sliceDER(numberToHexUnpadded(this.r)); const rHex = sliceDER(numberToHexUnpadded(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 = numberToHexUnpadded(sHexL);
const rLen = numberToHexUnpadded(rHexL);
const length = numberToHexUnpadded(rHexL + sHexL + 4);
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
} }
@ -1078,10 +1092,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 +1115,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,8 +1132,9 @@ 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;
const slice = bytes.length > nByteLength ? bytes.slice(0, nByteLength) : bytes;
return bytesToNumberBE(slice); return bytesToNumberBE(slice);
} }
function bits2octets(bytes: Uint8Array): Uint8Array { function bits2octets(bytes: Uint8Array): Uint8Array {

@ -370,7 +370,7 @@ 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('secp256k1.recoverPublicKey()/should not recover zero points', () => {