New schnorr exports. Simplify RFC6979 k gen, privkey checks
This commit is contained in:
parent
79100c2d47
commit
02b0b25147
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user