New schnorr exports. Simplify RFC6979 k gen, privkey checks

This commit is contained in:
Paul Miller 2023-01-26 07:15:59 +00:00
parent 79100c2d47
commit 02b0b25147
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
3 changed files with 30 additions and 47 deletions

@ -162,7 +162,6 @@ export type AbstractCurve<T> = {
hEff?: bigint; // Number to multiply to clear cofactor hEff?: bigint; // Number to multiply to clear cofactor
Gx: T; // base point X coordinate Gx: T; // base point X coordinate
Gy: T; // base point Y coordinate Gy: T; // base point Y coordinate
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
}; };

@ -24,10 +24,9 @@ export type BasicCurve<T> = AbstractCurve<T> & {
b: T; b: T;
// Optional params // Optional params
// Executed before privkey validation. Useful for P521 with var-length priv key normalizePrivateKey?: (key: PrivKey) => PrivKey; // called before privkey validation. used w p521
normalizePrivateKey?: (key: PrivKey) => PrivKey;
// Whether to execute modular division on a private key, useful for bls curves with cofactor > 1 // Whether to execute modular division on a private key, useful for bls curves with cofactor > 1
wrapPrivateKey?: boolean; wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
// Endomorphism options for Koblitz curves // Endomorphism options for Koblitz curves
endo?: EndomorphismOpts; endo?: EndomorphismOpts;
// When a cofactor != 1, there can be an effective methods to: // When a cofactor != 1, there can be an effective methods to:
@ -207,32 +206,19 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
function assertGE(num: bigint) { function assertGE(num: bigint) {
if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n'); if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n');
} }
/** // Validates if priv key is valid and converts it to bigint.
* Validates if a private key is valid and converts it to bigint form. // Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey.
* Supports two options, that are passed when CURVE is initialized:
* - `normalizePrivateKey()` executed before all checks
* - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n`
*/
function normalizePrivateKey(key: PrivKey): bigint { function normalizePrivateKey(key: PrivKey): bigint {
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n } = CURVE; const { normalizePrivateKey: custom, nByteLength, wrapPrivateKey, n } = CURVE;
if (typeof custom === 'function') key = custom(key); if (typeof custom === 'function') key = custom(key); // CURVE.normalizePrivateKey()
let num: bigint; let num: bigint;
if (typeof key === 'bigint') { try {
// Curve order check is done below num = typeof key === 'bigint' ? key : ut.bytesToNumberBE(ensureBytes(key, nByteLength));
num = key; } catch (error) {
} else if (typeof key === 'string') { throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`);
if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`);
// Validates individual octets
num = ut.bytesToNumberBE(ensureBytes(key));
} else if (key instanceof Uint8Array) {
if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`);
num = ut.bytesToNumberBE(key);
} else {
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
} }
// Useful for curves with cofactor != 1 if (wrapPrivateKey) num = mod.mod(num, n); // disabled by default, enabled for BLS
if (wrapPrivateKey) num = mod.mod(num, n); assertGE(num); // num in range [1..N-1]
assertGE(num);
return num; return num;
} }
@ -967,32 +953,26 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521. // NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521.
// Also it can be bigger for P224 + SHA256 // Also it can be bigger for P224 + SHA256
function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) { function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) {
const { hash, randomBytes } = CURVE;
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`); if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
if (['recovered', 'canonical'].some((k) => k in opts)) if (['recovered', 'canonical'].some((k) => k in opts))
// Ban legacy options // Ban legacy options
throw new Error('sign() legacy options not supported'); throw new Error('sign() legacy options not supported');
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
if (prehash) msgHash = CURVE.hash(ensureBytes(msgHash)); if (prehash) msgHash = hash(ensureBytes(msgHash));
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash
// Step A is ignored, since we already provide hash instead of msg
// NOTE: instead of bits2int, we calling here truncateHash, since we need // We can't later call bits2octets, since nested bits2int is broken for curves
// custom truncation for stark. For other curves it is essentially same as calling bits2int + mod // with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
// However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken // const bits2octets = (bits) => int2octets(bits2int_modN(bits))
// for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call.
// const bits2octets = (bits)=>int2octets(bytesToNumberBE(truncateHash(bits)))
const h1int = bits2int_modN(ensureBytes(msgHash)); const h1int = bits2int_modN(ensureBytes(msgHash));
const h1octets = int2octets(h1int); const d = normalizePrivateKey(privateKey); // validate private key, convert to bigint
const seedArgs = [int2octets(d), int2octets(h1int)];
const d = normalizePrivateKey(privateKey); // extraEntropy. RFC6979 3.6: additional k' (optional).
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const seedArgs = [int2octets(d), h1octets];
if (ent != null) { if (ent != null) {
// RFC6979 3.6: additional k' (optional) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
if (ent === true) ent = CURVE.randomBytes(Fp.BYTES); // Either pass as-is, or generate random bytes. Then validate for being ui8a of size BYTES
const e = ensureBytes(ent); seedArgs.push(ensureBytes(ent === true ? randomBytes(Fp.BYTES) : ent, Fp.BYTES));
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
seedArgs.push(e);
} }
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2 const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!

@ -190,12 +190,16 @@ function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(3
return sig; return sig;
} }
function pointFromHex(publicKey: Hex) {
return lift_x(bytesToNum(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails
}
/** /**
* Verifies Schnorr signature synchronously. * Verifies Schnorr signature synchronously.
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
try { try {
const P = lift_x(bytesToNum(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails const P = pointFromHex(publicKey); // P = lift_x(int(pk)); fail if that fails
const sig = ensureBytes(signature, 64); const sig = ensureBytes(signature, 64);
const r = bytesToNum(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p. const r = bytesToNum(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
if (!fe(r)) return false; if (!fe(r)) return false;
@ -216,7 +220,7 @@ export const schnorr = {
getPublicKey: schnorrGetPublicKey, getPublicKey: schnorrGetPublicKey,
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
utils: { lift_x, int: bytesToNum, taggedHash }, utils: { lift_x, pointFromHex, int: bytesToNum, taggedHash, toRawX },
}; };
const isoMap = htf.isogenyMap( const isoMap = htf.isogenyMap(