abstract/modular: add more comments everywhere

This commit is contained in:
Paul Miller 2023-08-11 10:23:52 +00:00
parent d92c9d14ad
commit e8b9509c16
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B

@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
return mod(x, modulo); return mod(x, modulo);
} }
// Tonelli-Shanks algorithm /**
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12) * Tonelli-Shanks square root search algorithm.
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks * 1. https://eprint.iacr.org/2012/685.pdf (page 12)
* 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
* Will start an infinite loop if field order P is not prime.
* @param P field order
* @returns function that takes field Fp (created from P) and number n
*/
export function tonelliShanks(P: bigint) { export function tonelliShanks(P: bigint) {
// Legendre constant: used to calculate Legendre symbol (a | p), // Legendre constant: used to calculate Legendre symbol (a | p),
// which denotes the value of a^((p-1)/2) (mod p). // which denotes the value of a^((p-1)/2) (mod p).
@ -198,7 +203,7 @@ export function FpSqrt(P: bigint) {
// Little-endian check for first LE bit (last BE bit); // Little-endian check for first LE bit (last BE bit);
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n; export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m // Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
export interface IField<T> { export interface IField<T> {
ORDER: bigint; ORDER: bigint;
BYTES: number; BYTES: number;
@ -276,7 +281,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
return p; return p;
} }
// 0 is non-invertible: non-batched version will throw on 0 /**
* Efficiently invert an array of Field elements.
* `inv(0)` will return `undefined` here: make sure to throw an error.
*/
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] { export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
const tmp = new Array(nums.length); const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p // Walk from first to last, multiply them by each other MOD p
@ -319,12 +327,12 @@ export function nLength(n: bigint, nBitLength?: number) {
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>; type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
/** /**
* Initializes a galois field over prime. Non-primes are not supported for now. * Initializes a finite field over prime. **Non-primes are not supported.**
* Do not init in loop: slow. Very fragile: always run a benchmark on change. * Do not init in loop: slow. Very fragile: always run a benchmark on a change.
* Major performance gains: * Major performance optimizations:
* a) non-normalized operations like mulN instead of mul * * a) denormalized operations like mulN instead of mul
* b) `Object.freeze` * * b) same object shape: never add or remove keys
* c) Same object shape: never add or remove keys * * c) Object.freeze
* @param ORDER prime positive bigint * @param ORDER prime positive bigint
* @param bitLen how many bits the field consumes * @param bitLen how many bits the field consumes
* @param isLE (def: false) if encoding / decoding should be in little-endian * @param isLE (def: false) if encoding / decoding should be in little-endian
@ -336,7 +344,7 @@ export function Field(
isLE = false, isLE = false,
redef: Partial<IField<bigint>> = {} redef: Partial<IField<bigint>> = {}
): Readonly<FpField> { ): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen); const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported'); if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
const sqrtP = FpSqrt(ORDER); const sqrtP = FpSqrt(ORDER);
@ -400,13 +408,15 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
} }
/** /**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. * "Constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF * Can take (n + 8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being negligible. * and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 40 bytes of input for 32-byte private key. * Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
* hash-to-curve, https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-hashing-to-a-finite-field
* @param hash hash output from SHA3 or a similar function * @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n) * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
* @param isLE interpret hash bytes as LE num * @param isLE interpret hash bytes as LE num
* @returns valid private scalar * @returns valid private scalar
*/ */
@ -417,9 +427,12 @@ export function hashToPrivateScalar(
): bigint { ): bigint {
hash = ensureBytes('privateHash', hash); hash = ensureBytes('privateHash', hash);
const hashLen = hash.length; const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8; const minLen = nLength(groupOrder).nByteLength + 8; // 8b (64 bits) gives 2^-64 bias
// Small numbers aren't supported: need to understand their security / bias story first.
// Huge numbers aren't supported for security: it's easier to detect timings of their ops in JS.
if (minLen < 24 || hashLen < minLen || hashLen > 1024) if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); throw new Error(`expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
return mod(num, groupOrder - _1n) + _1n; return mod(num, groupOrder - _1n) + _1n;
} }