diff --git a/crypto/finite-field-examples.ts b/crypto/finite-field-examples.ts new file mode 100644 index 00000000..6aeca112 --- /dev/null +++ b/crypto/finite-field-examples.ts @@ -0,0 +1,31 @@ +/** + * This file contains some examples of finite fields, to be used for tests + */ +import { Fp, Fq, createField } from './finite_field.js'; + +export { exampleFields }; + +// some primes +let pSmall = 101n; +let pBabybear = (1n << 31n) - 1n; +let pGoldilocks = (1n << 64n) - (1n << 32n) + 1n; +let p25519 = (1n << 255n) - 19n; +let pSecp256k1 = (1n << 256n) - (1n << 32n) - 0b1111010001n; +let pSecq256k1 = (1n << 256n) - 0x14551231950b75fc4402da1732fc9bebfn; +let pBls12_381 = + 0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001n; +let qBls12_381 = + 0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001n; + +let exampleFields = { + Fp, + Fq, + small: createField(pSmall), + babybear: createField(pBabybear), + goldilocks: createField(pGoldilocks), + f25519: createField(p25519), + secp256k1: createField(pSecp256k1), + secq256k1: createField(pSecq256k1), + bls12_381_base: createField(pBls12_381), + bls12_381_scalar: createField(qBls12_381), +}; diff --git a/crypto/finite-field.unit-test.ts b/crypto/finite-field.unit-test.ts index c6f7136e..bbfdc577 100644 --- a/crypto/finite-field.unit-test.ts +++ b/crypto/finite-field.unit-test.ts @@ -1,21 +1,19 @@ import { Fp, Fq } from './finite_field.js'; import assert from 'node:assert/strict'; import { Random, test } from '../../lib/testing/property.js'; +import { exampleFields } from './finite-field-examples.js'; -for (let F of [Fp, Fq]) { - // t is computed correctly from p = 2^32 * t + 1 - assert(F.t * (1n << 32n) + 1n === F.modulus, 't is computed correctly'); +let fields = Object.values(exampleFields); - // the primitive root of unity is computed correctly as 5^t - let generator = 5n; - let rootFp = F.power(generator, F.t); - assert(rootFp === F.twoadicRoot, 'root of unity is computed correctly'); +for (let F of fields) { + // t is computed correctly from p = 2^M * t + 1 + assert(F.t * (1n << F.M) + 1n === F.modulus, 't, M are computed correctly'); // the primitive roots of unity `r` actually satisfy the equations defining them: - let shouldBe1 = F.power(F.twoadicRoot, 1n << 32n); - let shouldBeMinus1 = F.power(F.twoadicRoot, 1n << 31n); - assert(shouldBe1 === 1n, 'r^(2^32) === 1'); - assert(shouldBeMinus1 + 1n === F.modulus, 'r^(2^32) === 1'); + let shouldBe1 = F.power(F.twoadicRoot, 1n << F.M); + let shouldBeMinus1 = F.power(F.twoadicRoot, 1n << (F.M - 1n)); + assert(shouldBe1 === 1n, 'r^(2^M) === 1'); + assert(shouldBeMinus1 + 1n === F.modulus, 'r^(2^(M-1)) === -1'); // the primitive roots of unity are non-squares // -> verifies that the two-adicity is 32, and that they can be used as non-squares in the sqrt algorithm @@ -57,21 +55,27 @@ for (let F of [Fp, Fq]) { } else { assert.equal(F.inverse(3n), (p + 1n) / 3n, 'inverse 3'); } - let xInv = F.inverse(x); - assert(xInv !== undefined, 'random x is invertible'); - assert.equal(F.mul(xInv, x), 1n, 'inverse & mul'); + + if (x !== 0n) { + let xInv = F.inverse(x); + assert(xInv !== undefined, 'non-zero is invertible'); + assert.equal(F.mul(xInv, x), 1n, 'inverse & mul'); + assert.equal(F.div(y, x), F.mul(y, xInv), 'div & inverse'); + } assert.equal(F.div(1n, 0n), undefined, 'div'); assert.equal(F.div(21n, F.negate(7n)), p - 3n, 'div'); - assert.equal(F.div(y, x), F.mul(y, xInv), 'div & inverse'); assert.equal(F.square(F.negate(10n)), 100n, 'square'); let squareX = F.square(x); assert(F.isSquare(squareX), 'square + isSquare'); assert([x, F.negate(x)].includes(F.sqrt(squareX)!), 'square + sqrt'); - assert(F.isSquare(p - 1n), 'isSquare -1'); - let i = F.power(F.twoadicRoot, 1n << 30n); - assert([i, F.negate(i)].includes(F.sqrt(p - 1n)!), 'sqrt -1'); + + if (F.M >= 2n) { + assert(F.isSquare(p - 1n), 'isSquare -1'); + let i = F.power(F.twoadicRoot, 1n << (F.M - 2n)); + assert([i, F.negate(i)].includes(F.sqrt(p - 1n)!), 'sqrt -1'); + } assert.equal(F.power(F.negate(2n), 3n), F.negate(8n), 'power'); assert.equal(F.power(2n, p - 1n), 1n, 'power mod p-1'); @@ -86,8 +90,11 @@ for (let F of [Fp, Fq]) { assert.equal(F.dot([x, y], [y, x]), F.mul(2n, F.mul(x, y)), 'dot'); assert.equal(F.dot([x, y], [F.negate(y), x]), 0n, 'dot'); - assert(x > 1n << 128n, 'random x is large'); - assert(x < p - (1n << 128n), 'random x is not small negative'); + if (p >> 250n) { + assert(x > 1n << 128n, 'random x is large'); + assert(x < p - (1n << 128n), 'random x is not small negative'); + } + assert(x >= 0 && x < p, 'random x is in range'); assert.equal(F.fromNumber(-1), p - 1n, 'fromNumber'); assert.equal(F.fromBigint(-1n), p - 1n, 'fromBigint'); @@ -98,3 +105,11 @@ for (let F of [Fp, Fq]) { assert(!F.equal(5n, p - 5n), 'not equal'); }); } + +// test that is specialized to Fp, Fq +for (let F of [Fp, Fq]) { + // the primitive root of unity is computed correctly as 5^t + let generator = 5n; + let rootFp = F.power(generator, F.t); + assert(rootFp === F.twoadicRoot, 'root of unity is computed correctly'); +} diff --git a/crypto/finite_field.ts b/crypto/finite_field.ts index 3cf52e7a..ce77b867 100644 --- a/crypto/finite_field.ts +++ b/crypto/finite_field.ts @@ -1,7 +1,7 @@ import { bytesToBigInt } from './bigint-helpers.js'; import { randomBytes } from './random.js'; -export { Fp, Fq, FiniteField, p, q, mod, inverse }; +export { createField, Fp, Fq, FiniteField, p, q, mod, inverse }; // CONSTANTS @@ -65,13 +65,13 @@ function inverse(a: bigint, p: bigint) { return mod(x, p); } -function sqrt(n: bigint, p: bigint, Q: bigint, c: bigint) { +function sqrt(n: bigint, p: bigint, Q: bigint, c: bigint, M: bigint) { // https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm#The_algorithm // variable naming is the same as in that link ^ // Q is what we call `t` elsewhere - the odd factor in p - 1 // c is a known primitive root of unity + // M is the twoadicity = exponent of 2 in factorization of p - 1 if (n === 0n) return 0n; - let M = 32n; let t = power(n, (Q - 1n) >> 1n, p); // n^(Q - 1)/2 let R = mod(t * n, p); // n^((Q - 1)/2 + 1) = n^((Q + 1)/2) t = mod(t * R, p); // n^((Q - 1)/2 + (Q + 1)/2) = n^Q @@ -99,11 +99,11 @@ function isSquare(x: bigint, p: bigint) { return sqrt1 === 1n; } -function randomField(p: bigint) { +function randomField(p: bigint, sizeInBytes: number, hiBitMask: number) { // strategy: find random 255-bit bigints and use the first that's smaller than p while (true) { - let bytes = randomBytes(32); - bytes[31] &= 0x7f; // zero highest bit, so we get 255 random bits + let bytes = randomBytes(sizeInBytes); + bytes[sizeInBytes - 1] &= hiBitMask; // zero highest bit, so we get 255 random bits let x = bytesToBigInt(bytes); if (x < p) return x; } @@ -112,15 +112,34 @@ function randomField(p: bigint) { // SPECIALIZATIONS TO FP, FQ // these should be mostly trivial -const Fp = createField(p, pMinusOneOddFactor, twoadicRootFp); -const Fq = createField(q, qMinusOneOddFactor, twoadicRootFq); +const Fp = createField(p, { + oddFactor: pMinusOneOddFactor, + twoadicRoot: twoadicRootFp, + twoadicity: 32n, +}); +const Fq = createField(q, { + oddFactor: qMinusOneOddFactor, + twoadicRoot: twoadicRootFq, + twoadicity: 32n, +}); type FiniteField = ReturnType<typeof createField>; -function createField(p: bigint, t: bigint, twoadicRoot: bigint) { +function createField( + p: bigint, + constants?: { oddFactor: bigint; twoadicRoot: bigint; twoadicity: bigint } +) { + let { oddFactor, twoadicRoot, twoadicity } = + constants ?? computeFieldConstants(p); + let sizeInBits = p.toString(2).length; + let sizeInBytes = Math.ceil(sizeInBits / 8); + let sizeHighestByte = sizeInBits - 8 * (sizeInBytes - 1); + let hiBitMask = (1 << sizeHighestByte) - 1; + return { modulus: p, - sizeInBits: 255, - t, + sizeInBits, + t: oddFactor, + M: twoadicity, twoadicRoot, add(x: bigint, y: bigint) { return mod(x + y, p); @@ -152,7 +171,7 @@ function createField(p: bigint, t: bigint, twoadicRoot: bigint) { return isSquare(x, p); }, sqrt(x: bigint) { - return sqrt(x, p, t, twoadicRoot); + return sqrt(x, p, oddFactor, twoadicRoot, twoadicity); }, power(x: bigint, n: bigint) { return power(x, n, p); @@ -172,7 +191,7 @@ function createField(p: bigint, t: bigint, twoadicRoot: bigint) { return !(x & 1n); }, random() { - return randomField(p); + return randomField(p, sizeInBytes, hiBitMask); }, fromNumber(x: number) { return mod(BigInt(x), p); @@ -206,3 +225,26 @@ function createField(p: bigint, t: bigint, twoadicRoot: bigint) { }, }; } + +/** + * Compute constants to instantiate a finite field just from the modulus + */ +function computeFieldConstants(p: bigint) { + // figure out the factorization p - 1 = 2^M * t + let oddFactor = p - 1n; + let twoadicity = 0n; + while ((oddFactor & 1n) === 0n) { + oddFactor >>= 1n; + twoadicity++; + } + + // find z = non-square + // start with 2 and increment until we find one + let z = 2n; + while (isSquare(z, p)) z++; + + // primitive root of unity is z^t + let twoadicRoot = power(z, oddFactor, p); + + return { oddFactor, twoadicRoot, twoadicity }; +}