weierstrass: rename normalizePrivateKey to allowedPrivateKeyLengths

This commit is contained in:
Paul Miller 2023-01-27 22:45:55 +00:00
parent fcd422d246
commit f39fb80c52
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
3 changed files with 32 additions and 21 deletions

@ -24,11 +24,9 @@ export type BasicCurve<T> = AbstractCurve<T> & {
b: T; b: T;
// Optional params // Optional params
normalizePrivateKey?: (key: PrivKey) => PrivKey; // called before privkey validation. used w p521 allowedPrivateKeyLengths?: readonly number[]; // for P521
// Whether to execute modular division on a private key, useful for bls curves with cofactor > 1
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
// Endomorphism options for Koblitz curves endo?: EndomorphismOpts; // Endomorphism options for Koblitz curves
endo?: EndomorphismOpts;
// When a cofactor != 1, there can be an effective methods to: // When a cofactor != 1, there can be an effective methods to:
// 1. Determine whether a point is torsion-free // 1. Determine whether a point is torsion-free
isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean; isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
@ -101,6 +99,14 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
if (!Fp.isValid(curve[i])) if (!Fp.isValid(curve[i]))
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[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) { for (const i of ['isTorsionFree', 'clearCofactor'] as const) {
if (curve[i] === undefined) continue; // Optional if (curve[i] === undefined) continue; // Optional
if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`); if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`);
@ -209,8 +215,13 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Validates if priv key is valid and converts it to bigint. // Validates if priv key is valid and converts it to bigint.
// Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey. // Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey.
function normalizePrivateKey(key: PrivKey): bigint { function normalizePrivateKey(key: PrivKey): bigint {
const { normalizePrivateKey: custom, nByteLength, wrapPrivateKey, n } = CURVE; const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
if (typeof custom === 'function') key = custom(key); // CURVE.normalizePrivateKey() 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; let num: bigint;
try { try {
num = typeof key === 'bigint' ? key : ut.bytesToNumberBE(ensureBytes(key, nByteLength)); num = typeof key === 'bigint' ? key : ut.bytesToNumberBE(ensureBytes(key, nByteLength));

@ -1,7 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, PrivKey } from './abstract/utils.js';
import { Fp as Field } from './abstract/modular.js'; import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
@ -38,16 +37,7 @@ export const P521 = createCurve({
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'), Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
// P521 keys could be 130, 131, 132 bytes. We normalize to 132 bytes. allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
// 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
},
} as const, sha512); } as const, sha512);
export const secp521r1 = P521; export const secp521r1 = P521;

@ -436,9 +436,16 @@ for (const name in CURVES) {
throws(() => G[1][op](0n), '0n'); throws(() => G[1][op](0n), '0n');
G[1][op](G[2]); G[1][op](G[2]);
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER'); 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](123.456), '123.456');
throws(() => G[1][op](true), 'true'); 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]('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( throws(
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), () => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
'{ 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', () => { 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(-123n), '-123n');
throws(() => C.getPublicKey(123), '123'); throws(() => C.getPublicKey(123), '123');
throws(() => C.getPublicKey(123.456), '123.456'); throws(() => C.getPublicKey(123.456), '123.456');
throws(() => C.getPublicKey(true), 'true'); throws(() => C.getPublicKey(true), 'true');
throws(() => C.getPublicKey(false), 'false');
throws(() => C.getPublicKey(null), 'null');
throws(() => C.getPublicKey(undefined), 'undefined');
throws(() => C.getPublicKey(''), "''"); throws(() => C.getPublicKey(''), "''");
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable? // NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
// throws(() => C.getPublicKey('1'), "'1'"); // throws(() => C.getPublicKey('1'), "'1'");
@ -560,25 +570,25 @@ for (const name in CURVES) {
should('.sign() edge cases', () => { should('.sign() edge cases', () => {
throws(() => C.sign()); throws(() => C.sign());
throws(() => C.sign('')); throws(() => C.sign(''));
throws(() => C.sign('', ''));
throws(() => C.sign(new Uint8Array(), new Uint8Array()));
}); });
describe('verify()', () => { describe('verify()', () => {
const msg = '01'.repeat(32);
should('true for proper signatures', () => { should('true for proper signatures', () => {
const msg = '01'.repeat(32);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv); const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, msg, pub), true); deepStrictEqual(C.verify(sig, msg, pub), true);
}); });
should('false for wrong messages', () => { should('false for wrong messages', () => {
const msg = '01'.repeat(32);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv); const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false); deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false);
}); });
should('false for wrong keys', () => { should('false for wrong keys', () => {
const msg = '01'.repeat(32);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false); deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);