From f39fb80c52c2e33d3184362d117c79ae4d07a1ba Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 27 Jan 2023 22:45:55 +0000 Subject: [PATCH] weierstrass: rename normalizePrivateKey to allowedPrivateKeyLengths --- src/abstract/weierstrass.ts | 23 +++++++++++++++++------ src/p521.ts | 12 +----------- test/basic.test.js | 18 ++++++++++++++---- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index 9ecfafa..f865473 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -24,11 +24,9 @@ export type BasicCurve = AbstractCurve & { b: T; // Optional params - normalizePrivateKey?: (key: PrivKey) => PrivKey; // called before privkey validation. used w p521 - // Whether to execute modular division on a private key, useful for bls curves with cofactor > 1 + allowedPrivateKeyLengths?: readonly number[]; // for P521 wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n - // Endomorphism options for Koblitz curves - endo?: EndomorphismOpts; + endo?: EndomorphismOpts; // Endomorphism options for Koblitz curves // When a cofactor != 1, there can be an effective methods to: // 1. Determine whether a point is torsion-free isTorsionFree?: (c: ProjConstructor, point: ProjPointType) => boolean; @@ -101,6 +99,14 @@ function validatePointOpts(curve: CurvePointsType) { if (!Fp.isValid(curve[i])) throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`); } + for (const i of ['allowedPrivateKeyLengths'] as const) { + if (curve[i] === undefined) continue; // Optional + if (!Array.isArray(curve[i])) throw new Error(`Invalid ${i} array`); + } + for (const i of ['wrapPrivateKey'] as const) { + if (curve[i] === undefined) continue; // Optional + if (typeof curve[i] !== 'boolean') throw new Error(`Invalid ${i} boolean`); + } for (const i of ['isTorsionFree', 'clearCofactor'] as const) { if (curve[i] === undefined) continue; // Optional if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`); @@ -209,8 +215,13 @@ export function weierstrassPoints(opts: CurvePointsType) { // Validates if priv key is valid and converts it to bigint. // Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey. function normalizePrivateKey(key: PrivKey): bigint { - const { normalizePrivateKey: custom, nByteLength, wrapPrivateKey, n } = CURVE; - if (typeof custom === 'function') key = custom(key); // CURVE.normalizePrivateKey() + const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE; + if (lengths && typeof key !== 'bigint') { + if (key instanceof Uint8Array) key = ut.bytesToHex(key); + // Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes + if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key'); + key = key.padStart(nByteLength * 2, '0'); + } let num: bigint; try { num = typeof key === 'bigint' ? key : ut.bytesToNumberBE(ensureBytes(key, nByteLength)); diff --git a/src/p521.ts b/src/p521.ts index 7af0928..5dacbd9 100644 --- a/src/p521.ts +++ b/src/p521.ts @@ -1,7 +1,6 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ import { createCurve } from './_shortw_utils.js'; import { sha512 } from '@noble/hashes/sha512'; -import { bytesToHex, PrivKey } from './abstract/utils.js'; import { Fp as Field } from './abstract/modular.js'; import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import * as htf from './abstract/hash-to-curve.js'; @@ -38,16 +37,7 @@ export const P521 = createCurve({ Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'), h: BigInt(1), lowS: false, - // P521 keys could be 130, 131, 132 bytes. We normalize to 132 bytes. - // Does not replace validation; invalid keys would still be rejected. - normalizePrivateKey(key: PrivKey) { - if (typeof key === 'bigint') return key; - if (key instanceof Uint8Array) key = bytesToHex(key); - if (typeof key !== 'string' || !([130, 131, 132].includes(key.length))) { - throw new Error('Invalid key'); - } - return key.padStart(66 * 2, '0'); // ensure it's always 132 bytes - }, + allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b } as const, sha512); export const secp521r1 = P521; diff --git a/test/basic.test.js b/test/basic.test.js index 25c5d63..ad36d1a 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -436,9 +436,16 @@ for (const name in CURVES) { throws(() => G[1][op](0n), '0n'); G[1][op](G[2]); throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER'); + throws(() => G[1][op](-123n), '-123n'); + throws(() => G[1][op](123), '123'); throws(() => G[1][op](123.456), '123.456'); throws(() => G[1][op](true), 'true'); + throws(() => G[1][op](false), 'false'); + throws(() => G[1][op](null), 'null'); + throws(() => G[1][op](undefined), 'undefined'); throws(() => G[1][op]('1'), "'1'"); + throws(() => G[1][op]({ x: 1n, y: 1n }), '{ x: 1n, y: 1n }'); + throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n }), '{ x: 1n, y: 1n, z: 1n }'); throws( () => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }' @@ -527,10 +534,13 @@ for (const name in CURVES) { should('.getPublicKey() type check', () => { throws(() => C.getPublicKey(0), '0'); throws(() => C.getPublicKey(0n), '0n'); - throws(() => C.getPublicKey(false), 'false'); + throws(() => C.getPublicKey(-123n), '-123n'); throws(() => C.getPublicKey(123), '123'); throws(() => C.getPublicKey(123.456), '123.456'); throws(() => C.getPublicKey(true), 'true'); + throws(() => C.getPublicKey(false), 'false'); + throws(() => C.getPublicKey(null), 'null'); + throws(() => C.getPublicKey(undefined), 'undefined'); throws(() => C.getPublicKey(''), "''"); // NOTE: passes because of disabled hex padding checks for starknet, maybe enable? // throws(() => C.getPublicKey('1'), "'1'"); @@ -560,25 +570,25 @@ for (const name in CURVES) { should('.sign() edge cases', () => { throws(() => C.sign()); throws(() => C.sign('')); + throws(() => C.sign('', '')); + throws(() => C.sign(new Uint8Array(), new Uint8Array())); }); describe('verify()', () => { + const msg = '01'.repeat(32); should('true for proper signatures', () => { - const msg = '01'.repeat(32); const priv = C.utils.randomPrivateKey(); const sig = C.sign(msg, priv); const pub = C.getPublicKey(priv); deepStrictEqual(C.verify(sig, msg, pub), true); }); should('false for wrong messages', () => { - const msg = '01'.repeat(32); const priv = C.utils.randomPrivateKey(); const sig = C.sign(msg, priv); const pub = C.getPublicKey(priv); deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false); }); should('false for wrong keys', () => { - const msg = '01'.repeat(32); const priv = C.utils.randomPrivateKey(); const sig = C.sign(msg, priv); deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);