diff --git a/SECURITY.md b/SECURITY.md index 8a26af7..b98b6d1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,8 @@ | Version | Supported | | ------- | ------------------ | -| >=0.5.0 | :white_check_mark: | -| <0.5.0 | :x: | +| >=1.0.0 | :white_check_mark: | +| <1.0.0 | :x: | ## Reporting a Vulnerability diff --git a/src/abstract/montgomery.ts b/src/abstract/montgomery.ts index e85b388..2b01898 100644 --- a/src/abstract/montgomery.ts +++ b/src/abstract/montgomery.ts @@ -73,6 +73,7 @@ export function montgomery(curveDef: CurveType): CurveFn { return [x_2, x_3]; } + // Accepts 0 as well function assertFieldElement(n: bigint): bigint { if (typeof n === 'bigint' && _0n <= n && n < P) return n; throw new Error('Expected valid scalar 0 < scalar < CURVE.P'); diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index f7ce59e..35397ce 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -642,7 +642,6 @@ export type CurveFn = { utils: { normPrivateKeyToScalar: (key: PrivKey) => bigint; isValidPrivateKey(privateKey: PrivKey): boolean; - hashToPrivateKey: (hash: Hex) => Uint8Array; randomPrivateKey: () => Uint8Array; precompute: (windowSize?: number, point?: ProjPointType) => ProjPointType; }; @@ -677,7 +676,6 @@ export function weierstrass(curveDef: CurveType): CurveFn { const x = Fp.toBytes(a.x); const cat = ut.concatBytes; if (isCompressed) { - // TODO: hasEvenY return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); } else { return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); @@ -809,17 +807,15 @@ export function weierstrass(curveDef: CurveType): CurveFn { }, normPrivateKeyToScalar: normalizePrivateKey, - /** - * Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. - */ - hashToPrivateKey: (hash: Hex): Uint8Array => - ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength), - /** * Produces cryptographically secure private key from random of size (nBitLength+64) * as per FIPS 186 B.4.1 with modulo bias being neglible. */ - randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(Fp.BYTES + 8)), + randomPrivateKey: (): Uint8Array => { + const rand = CURVE.randomBytes(Fp.BYTES + 8); + const num = mod.hashToPrivateScalar(rand, CURVE_ORDER); + return ut.numberToBytesBE(num, CURVE.nByteLength); + }, /** * 1. Returns cached point which you can use to pass to `getSharedSecret` or `#multiply` by it. @@ -862,7 +858,8 @@ export function weierstrass(curveDef: CurveType): CurveFn { /** * ECDH (Elliptic Curve Diffie Hellman). * Computes shared public key from private key and public key. - * Checks: 1) private key validity 2) shared key is on-curve + * Checks: 1) private key validity 2) shared key is on-curve. + * Does NOT hash the result. * @param privateA private key * @param publicB different public key * @param isCompressed whether to return compact (default), or full key @@ -895,10 +892,12 @@ export function weierstrass(curveDef: CurveType): CurveFn { }; // NOTE: pads output with zero as per spec const ORDER_MASK = ut.bitMask(CURVE.nBitLength); + /** + * Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`. + */ function int2octets(num: bigint): Uint8Array { if (typeof num !== 'bigint') throw new Error('bigint expected'); if (!(_0n <= num && num < ORDER_MASK)) - // n in [0..ORDER_MASK-1] throw new Error(`bigint expected < 2^${CURVE.nBitLength}`); // works with order, can have different size than numToField! return ut.numberToBytesBE(num, CURVE.nByteLength); diff --git a/src/secp256k1.ts b/src/secp256k1.ts index 7b44cd3..4e94bae 100644 --- a/src/secp256k1.ts +++ b/src/secp256k1.ts @@ -107,6 +107,7 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array { return sha256(concatBytes(tagP, ...messages)); } +// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03 const pointToBytes = (point: PointType) => point.toRawBytes(true).slice(1); const numTo32b = (n: bigint) => numberToBytesBE(n, 32); const modP = (x: bigint) => mod(x, secp256k1P); @@ -114,12 +115,17 @@ const modN = (x: bigint) => mod(x, secp256k1N); const Point = secp256k1.ProjectivePoint; const GmulAdd = (Q: PointType, a: bigint, b: bigint) => Point.BASE.multiplyAndAddUnsafe(Q, a, b); +// Calculate point, scalar and bytes function schnorrGetExtPubKey(priv: PrivKey) { - const d = secp256k1.utils.normPrivateKeyToScalar(priv); + const d = secp256k1.utils.normPrivateKeyToScalar(priv); // same method executed in fromPrivateKey const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d' return { point, scalar, bytes: pointToBytes(point) }; } +/** + * lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point. + * @returns valid point checked for being on-curve + */ function lift_x(x: bigint): PointType { if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. const xx = modP(x * x); @@ -130,6 +136,9 @@ function lift_x(x: bigint): PointType { p.assertValidity(); return p; } +/** + * Create tagged hash, convert it to bigint, reduce modulo-n. + */ function challenge(...args: Uint8Array[]): bigint { return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args))); } @@ -169,6 +178,7 @@ function schnorrSign( /** * Verifies Schnorr signature. + * Will swallow errors & return false except for initial type validation of arguments. */ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { const sig = ensureBytes('signature', signature, 64);