diff --git a/src/edwards.ts b/src/edwards.ts index c9c0fa4..8cb829e 100644 --- a/src/edwards.ts +++ b/src/edwards.ts @@ -11,7 +11,14 @@ // 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 { bytesToHex, concatBytes, ensureBytes, numberToBytesLE, nLength } from './utils.js'; +import { + bytesToHex, + concatBytes, + ensureBytes, + numberToBytesLE, + nLength, + hashToPrivateScalar, +} from './utils.js'; import { wNAF } from './group.js'; // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n @@ -42,16 +49,21 @@ export type CurveType = { // Base point (x, y) aka generator point Gx: bigint; Gy: bigint; - // Other constants - a24: bigint; - // ECDH bits (can be different from N bits) - scalarBits: number; // Hashes hash: CHash; // Because we need outputLen for DRBG randomBytes: (bytesLength?: number) => Uint8Array; - adjustScalarBytes: (bytes: Uint8Array) => Uint8Array; - domain: (data: Uint8Array, ctx: Uint8Array, hflag: boolean) => Uint8Array; - uvRatio: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; + adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; + domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; + uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; + preHash?: CHash; + // ECDH related + // Other constants + a24: bigint; // Related to d, but cannot be derived from it + // ECDH bits (can be different from N bits) + montgomeryBits?: number; + basePointU?: string; // TODO: why not bigint? + powPminus2?: (x: bigint) => bigint; + UfromPoint?: (p: PointType) => Uint8Array; }; // We accept hex strings besides Uint8Array for simplicity @@ -67,18 +79,29 @@ function validateOpts(curve: CurveType) { if (typeof curve[i] !== 'bigint') throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); } - for (const i of ['scalarBits'] as const) { - if (typeof curve[i] !== 'number') - throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); - } - for (const i of ['nBitLength', 'nByteLength'] as const) { + for (const i of ['nBitLength', 'nByteLength', 'montgomeryBits'] as const) { if (curve[i] === undefined) continue; // Optional if (!Number.isSafeInteger(curve[i])) throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); } - for (const fn of ['randomBytes', 'adjustScalarBytes', 'domain', 'uvRatio'] as const) { + for (const fn of ['randomBytes'] as const) { if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`); } + for (const fn of [ + 'adjustScalarBytes', + 'domain', + 'uvRatio', + 'powPminus2', + 'UfromPoint', + ] as const) { + if (curve[fn] === undefined) continue; // Optional + if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`); + } + for (const i of ['basePointU'] as const) { + if (curve[i] === undefined) continue; // Optional + if (typeof curve[i] !== 'string') + throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); + } // Set defaults return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const); } @@ -131,7 +154,6 @@ export interface PointType { _setWindowSize(windowSize: number): void; toRawBytes(isCompressed?: boolean): Uint8Array; toHex(isCompressed?: boolean): string; - // toX25519(): Uint8Array; isTorsionFree(): boolean; equals(other: PointType): boolean; negate(): PointType; @@ -154,11 +176,20 @@ export type SigType = Hex | SignatureType; export type CurveFn = { CURVE: ReturnType; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; + getSharedSecret: (privateKey: PrivKey, publicKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex) => Uint8Array; verify: (sig: SigType, message: Hex, publicKey: PubKey) => boolean; Point: PointConstructor; ExtendedPoint: ExtendedPointConstructor; Signature: SignatureConstructor; + montgomeryCurve: { + BASE_POINT_U: string; + UfromPoint: (p: PointType) => Uint8Array; + scalarMult: (u: Hex, scalar: Hex) => Uint8Array; + scalarMultBase: (scalar: Hex) => Uint8Array; + getPublicKey: (privateKey: Hex) => Uint8Array; + getSharedSecret: (privateKey: Hex, publicKey: Hex) => Uint8Array; + }; utils: { mod: (a: bigint, b?: bigint) => bigint; invert: (number: bigint, modulo?: bigint) => bigint; @@ -184,11 +215,29 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const maxGroupElement = _2n ** BigInt(groupLen * 8); // previous POW_2_256 // Function overrides - const { adjustScalarBytes, randomBytes, uvRatio } = CURVE; + const { P, randomBytes } = CURVE; + const modP = (a: bigint) => mod.mod(a, P); - function modP(a: bigint) { - return mod.mod(a, CURVE.P); + // sqrt(u/v) + function _uvRatio(u: bigint, v: bigint) { + try { + const value = mod.sqrt(u * mod.invert(v, P), P); + return { isValid: true, value }; + } catch (e) { + return { isValid: false, value: _0n }; + } } + const uvRatio = CURVE.uvRatio || _uvRatio; + + const _powPminus2 = (x: bigint) => mod.pow(x, P - _2n, P); + const powPminus2 = CURVE.powPminus2 || _powPminus2; + const _adjustScalarBytes = (bytes: Uint8Array) => bytes; // NOOP + const adjustScalarBytes = CURVE.adjustScalarBytes || _adjustScalarBytes; + function _domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) { + if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported'); + return data; + } + const domain = CURVE.domain || _domain; // NOOP /** * Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). @@ -213,7 +262,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { static toAffineBatch(points: ExtendedPoint[]): Point[] { const toInv = mod.invertBatch( points.map((p) => p.z), - CURVE.P + P ); return points.map((p, i) => p.toAffine(toInv[i])); } @@ -341,7 +390,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { toAffine(invZ?: bigint): Point { const { x, y, z } = this; const is0 = this.equals(ExtendedPoint.ZERO); - if (invZ == null) invZ = is0 ? _8n : mod.invert(z, CURVE.P); // 8 was chosen arbitrarily + if (invZ == null) invZ = is0 ? _8n : mod.invert(z, P); // 8 was chosen arbitrarily const ax = modP(x * invZ); const ay = modP(y * invZ); const zz = modP(z * invZ); @@ -419,9 +468,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { // 2, set x <-- p - x. Return the decoded point (x,y). const isXOdd = (x & _1n) === _1n; const isLastByteOdd = (lastByte & 0x80) !== 0; - if (isLastByteOdd !== isXOdd) { - x = modP(-x); - } + if (isLastByteOdd !== isXOdd) x = modP(-x); return new Point(x, y); } @@ -575,26 +622,36 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { return getExtendedPublicKey(privateKey).pointBytes; } + const EMPTY = new Uint8Array(); + function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) { + context = ensureBytes(context); + return modlLE(CURVE.hash(domain(message, context, !!CURVE.preHash))); + } + /** Signs message with privateKey. RFC8032 5.1.6 */ - function sign(message: Hex, privateKey: Hex): Uint8Array { + function sign(message: Hex, privateKey: Hex, context?: Hex): Uint8Array { message = ensureBytes(message); + if (CURVE.preHash) message = CURVE.preHash(message); const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey); - const rDomain = CURVE.domain(concatBytes(prefix, message), new Uint8Array(), false); - const r = modlLE(CURVE.hash(rDomain)); // r = hash(prefix + msg) + const r = hashDomainToScalar(concatBytes(prefix, message), context); const R = Point.BASE.multiply(r); // R = rG - const kDomain = CURVE.domain( - concatBytes(R.toRawBytes(), pointBytes, message), - new Uint8Array(), - false - ); - const k = modlLE(CURVE.hash(kDomain)); // k = hash(R+P+msg) + const k = hashDomainToScalar(concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg) const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp return new Signature(R, s).toRawBytes(); } - // Helper functions because we have async and sync methods. - function prepareVerification(sig: SigType, message: Hex, publicKey: PubKey) { + /** + * Verifies EdDSA signature against message and public key. + * An extended group equation is checked. + * RFC8032 5.1.7 + * Compliant with ZIP215: + * 0 <= sig.R/publicKey < 2**256 (can be >= curve.P) + * 0 <= sig.s < l + * Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time. + */ + function verify(sig: SigType, message: Hex, publicKey: PubKey, context?: Hex): boolean { message = ensureBytes(message); + if (CURVE.preHash) message = CURVE.preHash(message); // When hex is passed, we check public key fully. // When Point instance is passed, we assume it has already been checked, for performance. // If user passes Point/Sig instance, we assume it has been already verified. @@ -614,40 +671,189 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const { r, s } = sig; const SB = ExtendedPoint.BASE.multiplyUnsafe(s); - return { r, s, SB, pub: publicKey, msg: message }; - } - - function finishVerification(publicKey: Point, r: Point, SB: ExtendedPoint, hashed: Uint8Array) { - const k = modlLE(hashed); + const k = hashDomainToScalar( + concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message), + context + ); const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k); const RkA = ExtendedPoint.fromAffine(r).add(kA); // [8][S]B = [8]R + [8][k]A' return RkA.subtract(SB).multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); } - /** - * Verifies EdDSA signature against message and public key. - * An extended group equation is checked. - * RFC8032 5.1.7 - * Compliant with ZIP215: - * 0 <= sig.R/publicKey < 2**256 (can be >= curve.P) - * 0 <= sig.s < l - * Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time. - */ - function verify(sig: SigType, message: Hex, publicKey: PubKey): boolean { - const { r, SB, msg, pub } = prepareVerification(sig, message, publicKey); - const domain = CURVE.domain( - concatBytes(r.toRawBytes(), pub.toRawBytes(), msg), - new Uint8Array([]), - false - ); - const hashed = CURVE.hash(domain); - return finishVerification(pub, r, SB, hashed); - } - // Enable precomputes. Slows down first publicKey computation by 20ms. Point.BASE._setWindowSize(8); + // ECDH (X22519/X448) + // https://datatracker.ietf.org/doc/html/rfc7748 + // Every twisted Edwards curve is birationally equivalent to an elliptic curve in Montgomery form and vice versa. + const montgomeryBits = CURVE.montgomeryBits || CURVE.nBitLength; + const montgomeryBytes = Math.ceil(montgomeryBits / 8); + + // cswap from RFC7748 + function cswap(swap: bigint, x_2: bigint, x_3: bigint): [bigint, bigint] { + const dummy = modP(swap * (x_2 - x_3)); + x_2 = modP(x_2 - dummy); + x_3 = modP(x_3 + dummy); + return [x_2, x_3]; + } + + // x25519 from 4 + /** + * + * @param pointU u coordinate (x) on Montgomery Curve 25519 + * @param scalar by which the point would be multiplied + * @returns new Point on Montgomery curve + */ + function montgomeryLadder(pointU: bigint, scalar: bigint): bigint { + const { P } = CURVE; + const u = normalizeScalar(pointU, P); + // Section 5: Implementations MUST accept non-canonical values and process them as + // if they had been reduced modulo the field prime. + const k = normalizeScalar(scalar, P); + // The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 + const a24 = CURVE.a24; + const x_1 = u; + let x_2 = _1n; + let z_2 = _0n; + let x_3 = u; + let z_3 = _1n; + let swap = _0n; + let sw: [bigint, bigint]; + for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) { + const k_t = (k >> t) & _1n; + swap ^= k_t; + sw = cswap(swap, x_2, x_3); + x_2 = sw[0]; + x_3 = sw[1]; + sw = cswap(swap, z_2, z_3); + z_2 = sw[0]; + z_3 = sw[1]; + swap = k_t; + + const A = x_2 + z_2; + const AA = modP(A * A); + const B = x_2 - z_2; + const BB = modP(B * B); + const E = AA - BB; + const C = x_3 + z_3; + const D = x_3 - z_3; + const DA = modP(D * A); + const CB = modP(C * B); + const dacb = DA + CB; + const da_cb = DA - CB; + x_3 = modP(dacb * dacb); + z_3 = modP(x_1 * modP(da_cb * da_cb)); + x_2 = modP(AA * BB); + z_2 = modP(E * (AA + modP(a24 * E))); + } + // (x_2, x_3) = cswap(swap, x_2, x_3) + sw = cswap(swap, x_2, x_3); + x_2 = sw[0]; + x_3 = sw[1]; + // (z_2, z_3) = cswap(swap, z_2, z_3) + sw = cswap(swap, z_2, z_3); + z_2 = sw[0]; + z_3 = sw[1]; + // z_2^(p - 2) + const z2 = powPminus2(z_2); + // Return x_2 * (z_2^(p - 2)) + return modP(x_2 * z2); + } + + function encodeUCoordinate(u: bigint): Uint8Array { + return numberToBytesLE(modP(u), montgomeryBytes); + } + + function decodeUCoordinate(uEnc: Hex): bigint { + const u = ensureBytes(uEnc, montgomeryBytes); + // Section 5: When receiving such an array, implementations of X25519 + // MUST mask the most significant bit in the final byte. + // This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP + // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519 + u[fieldLen - 1] &= 127; // 0b0111_1111 + return bytesToNumberLE(u); + } + + function decodeScalar(n: Hex): bigint { + const bytes = ensureBytes(n); + if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen) + throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`); + return bytesToNumberLE(adjustScalarBytes(bytes)); + } + /* + Converts Point to Montgomery Curve + - u, v: curve25519 coordinates + - x, y: ed25519 coordinates + RFC 7748 (https://www.rfc-editor.org/rfc/rfc7748) says + - The birational maps are (25519): + (u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x) + (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1)) + - The birational maps are (448): + (u, v) = ((y-1)/(y+1), sqrt(156324)*u/x) + (x, y) = (sqrt(156324)*u/v, (1+u)/(1-u)) + + But original Twisted Edwards paper (https://eprint.iacr.org/2008/013.pdf) and hyperelliptics (http://hyperelliptic.org/EFD/g1p/data/twisted/coordinates) + says that mapping is always: + - u = (1+y)/(1-y) + - v = 2 (1+y)/(x(1-y)) + - x = 2 u/v + - y = (u-1)/(u+1) + + Which maps correctly, but to completely different curve. There is different mapping for ed448 (which done with replaceble function). + Returns 'u' coordinate of curve25519 point. + + NOTE: jubjub will need full mapping, for now only Point -> U is enough + */ + function _UfromPoint(p: Point): Uint8Array { + if (!(p instanceof Point)) throw new Error('Wrong point'); + const { y } = p; + const u = modP((y + _1n) * mod.invert(_1n - y, P)); + return numberToBytesLE(u, montgomeryBytes); + } + const UfromPoint = CURVE.UfromPoint || _UfromPoint; + + const BASE_POINT_U = CURVE.basePointU || bytesToHex(UfromPoint(Point.BASE)); + // Multiply point u by scalar + function scalarMult(u: Hex, scalar: Hex): Uint8Array { + const pointU = decodeUCoordinate(u); + const _scalar = decodeScalar(scalar); + const pu = montgomeryLadder(pointU, _scalar); + // The result was not contributory + // https://cr.yp.to/ecdh.html#validate + if (pu === _0n) throw new Error('Invalid private or public key received'); + return encodeUCoordinate(pu); + } + // Multiply base point by scalar + const scalarMultBase = (scalar: Hex): Uint8Array => + montgomeryCurve.scalarMult(montgomeryCurve.BASE_POINT_U, scalar); + + const montgomeryCurve = { + BASE_POINT_U, + UfromPoint, + // NOTE: we can get 'y' coordinate from 'u', but Point.fromHex also wants 'x' coordinate oddity flag, and we cannot get 'x' without knowing 'v' + // Need to add generic conversion between twisted edwards and complimentary curve for JubJub + scalarMult, + scalarMultBase, + // NOTE: these function work on complimentary montgomery curve + getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(publicKey, privateKey), + getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey), + }; + + /** + * Calculates X25519 DH shared secret from ed25519 private & public keys. + * Curve25519 used in X25519 consumes private keys as-is, while ed25519 hashes them with sha512. + * Which means we will need to normalize ed25519 seeds to "hashed repr". + * @param privateKey ed25519 private key + * @param publicKey ed25519 public key + * @returns X25519 shared key + */ + function getSharedSecret(privateKey: PrivKey, publicKey: Hex): Uint8Array { + const { head } = getExtendedPublicKey(privateKey); + const u = montgomeryCurve.UfromPoint(Point.fromHex(publicKey)); + return montgomeryCurve.getSharedSecret(head, u); + } + const utils = { getExtendedPublicKey, mod: modP, @@ -661,12 +867,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { * @param hash hash output from sha512, or a similar function * @returns valid private scalar */ - hashToPrivateScalar: (hash: Hex): bigint => { - hash = ensureBytes(hash); - if (hash.length < 40 || hash.length > 1024) - throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); - return mod.mod(bytesToNumberLE(hash), CURVE_ORDER - _1n) + _1n; - }, + hashToPrivateScalar: (hash: Hex): bigint => hashToPrivateScalar(hash, CURVE_ORDER, true), /** * ed25519 private keys are uniform 32-bit strings. We do not need to check for @@ -690,6 +891,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { return { CURVE, + montgomeryCurve, + getSharedSecret, ExtendedPoint, Point, Signature, diff --git a/src/modular.ts b/src/modular.ts index 1ce038b..575d1fd 100644 --- a/src/modular.ts +++ b/src/modular.ts @@ -99,7 +99,6 @@ export function legendre(num: bigint, fieldPrime: bigint): bigint { /** * Calculates square root of a number in a finite field. - * Used to calculate y - the square root of y². */ export function sqrt(number: bigint, modulo: bigint): bigint { const n = number; @@ -109,6 +108,7 @@ export function sqrt(number: bigint, modulo: bigint): bigint { // P = 3 (mod 4) // sqrt n = n^((P+1)/4) if (P % _4n === _3n) return pow(n, p1div4, P); + // P = 5 (mod 8) if (P % _8n === _5n) { const n2 = mod(n * _2n, P); diff --git a/src/utils.ts b/src/utils.ts index 3a36c25..c9a67b1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,9 @@ // Convert between types // --------------------- +type Hex = string | Uint8Array; +import * as mod from './modular.js'; + const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); export function bytesToHex(uint8a: Uint8Array): string { if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array'); @@ -44,15 +47,19 @@ export function hexToBytes(hex: string): Uint8Array { } // Big Endian -export function bytesToNumber(bytes: Uint8Array): bigint { +export function bytesToNumberBE(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } +export function bytesToNumberLE(uint8a: Uint8Array): bigint { + if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array'); + return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse())); +} export const numberToBytesBE = (n: bigint, len: number) => hexToBytes(n.toString(16).padStart(len * 2, '0')); export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse(); -export function ensureBytes(hex: string | Uint8Array, expectedLength?: number): Uint8Array { +export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array { // Uint8Array.from() instead of hash.slice() because node.js Buffer // is instance of Uint8Array, and its slice() creates **mutable** copy const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex); @@ -82,3 +89,19 @@ export function nLength(n: bigint, nBitLength?: number) { const nByteLength = Math.ceil(_nBitLength / 8); return { nBitLength: _nBitLength, nByteLength }; } + +/** + * 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. + * As per FIPS 186 B.4.1. + * @param hash hash output from sha512, or a similar function + * @returns valid private scalar + */ +const _1n = BigInt(1); +export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint { + hash = ensureBytes(hash); + if (hash.length < 40 || hash.length > 1024) + throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); + const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); + return mod.mod(num, CURVE_ORDER - _1n) + _1n; +} diff --git a/src/weierstrass.ts b/src/weierstrass.ts index aad6ca1..2c7f423 100644 --- a/src/weierstrass.ts +++ b/src/weierstrass.ts @@ -12,13 +12,14 @@ import * as mod from './modular.js'; import { bytesToHex, - bytesToNumber, + bytesToNumberBE, concatBytes, ensureBytes, hexToBytes, hexToNumber, numberToHexUnpadded, nLength, + hashToPrivateScalar, } from './utils.js'; import { wNAF } from './group.js'; @@ -131,7 +132,7 @@ function parseDERInt(data: Uint8Array) { if (res[0] === 0x00 && res[1] <= 0x7f) { throw new DERError('Invalid signature integer: trailing length'); } - return { data: bytesToNumber(res), left: data.subarray(len + 2) }; + return { data: bytesToNumberBE(res), left: data.subarray(len + 2) }; } function parseDERSignature(data: Uint8Array) { @@ -214,8 +215,8 @@ export interface JacobianPointType { double(): JacobianPointType; add(other: JacobianPointType): JacobianPointType; subtract(other: JacobianPointType): JacobianPointType; - multiplyUnsafe(scalar: bigint): JacobianPointType; multiply(scalar: number | bigint, affinePoint?: PointType): JacobianPointType; + multiplyUnsafe(scalar: bigint): JacobianPointType; toAffine(invZ?: bigint): PointType; } // Static methods @@ -392,7 +393,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { num = hexToNumber(key); } else if (key instanceof Uint8Array) { if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`); - num = bytesToNumber(key); + num = bytesToNumberBE(key); } else { throw new TypeError('Expected valid private key'); } @@ -435,7 +436,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { const { n, nBitLength } = CURVE; const byteLength = hash.length; const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits) - let h = bytesToNumber(hash); + let h = bytesToNumberBE(hash); if (delta > 0) h = h >> BigInt(delta); if (!truncateOnly && h >= n) h -= n; return h; @@ -720,7 +721,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { */ private static fromCompressedHex(bytes: Uint8Array) { const P = CURVE.P; - const x = bytesToNumber(bytes.subarray(1)); + const x = bytesToNumberBE(bytes.subarray(1)); if (!isValidFieldElement(x)) throw new Error('Point is not on curve'); const y2 = weierstrassEquation(x); // y² = x³ + ax + b let y = sqrtModCurve(y2, P); // y = y² ^ (p+1)/4 @@ -734,8 +735,8 @@ export function weierstrass(curveDef: CurveType): CurveFn { } private static fromUncompressedHex(bytes: Uint8Array) { - const x = bytesToNumber(bytes.subarray(1, fieldLen + 1)); - const y = bytesToNumber(bytes.subarray(fieldLen + 1, 2 * fieldLen + 1)); + const x = bytesToNumberBE(bytes.subarray(1, fieldLen + 1)); + const y = bytesToNumberBE(bytes.subarray(fieldLen + 1, 2 * fieldLen + 1)); const point = new Point(x, y); point.assertValidity(); return point; @@ -962,15 +963,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { * @param hash hash output from sha512, or a similar function * @returns valid private key */ - hashToPrivateKey: (hash: Hex): Uint8Array => { - hash = ensureBytes(hash); - const minLen = fieldLen + 8; - if (hash.length < minLen || hash.length > 1024) { - throw new Error(`Expected ${minLen}-1024 bytes of private key as per FIPS 186`); - } - const num = mod.mod(bytesToNumber(hash), CURVE_ORDER - _1n) + _1n; - return numToField(num); - }, + hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)), // Takes curve order + 64 bits from CSPRNG // so that modulo bias is neglible, matches FIPS 186 B.4.1. @@ -1035,7 +1028,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { // RFC6979 methods function bits2int(bytes: Uint8Array) { const slice = bytes.length > fieldLen ? bytes.slice(0, fieldLen) : bytes; - return bytesToNumber(slice); + return bytesToNumberBE(slice); } function bits2octets(bytes: Uint8Array): Uint8Array { const z1 = bits2int(bytes);