Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and finite field operations (isSquare, sqrt) #287

Merged
merged 7 commits into from
Jul 31, 2024
15 changes: 10 additions & 5 deletions crypto/finite-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,13 @@ function fastInverse(
return s;
}

function sqrt(n: bigint, p: bigint, Q: bigint, c: bigint, M: 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
let n = mod(n_, p);
if (n === 0n) return 0n;
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)
Expand All @@ -212,7 +213,8 @@ function sqrt(n: bigint, p: bigint, Q: bigint, c: bigint, M: bigint) {
}
}

function isSquare(x: bigint, p: bigint) {
function isSquare(x_: bigint, p: bigint) {
let x = mod(x_, p);
if (x === 0n) return true;
let sqrt1 = power(x, (p - 1n) / 2n, p);
return sqrt1 === 1n;
Expand Down Expand Up @@ -282,7 +284,7 @@ function createField(
return mod(2n ** BigInt(bits) - (x + 1n), p);
},
negate(x: bigint) {
return x === 0n ? 0n : p - x;
return x === 0n ? 0n : mod(-x, p);
},
sub(x: bigint, y: bigint) {
return mod(x - y, p);
Expand Down Expand Up @@ -317,10 +319,13 @@ function createField(
return mod(z, p);
},
equal(x: bigint, y: bigint) {
return mod(x - y, p) === 0n;
// We check if x and y are both in the range [0, p). If they are, can do a simple comparison. Otherwise, we need to reduce them to the proper canonical field range.
let x_ = x >= 0n && x < p ? x : mod(x, p);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am okay with this, but let's add a comment here explaining what and why we do it this particular way, i can imagine it might seem a little weird to the naked eye

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here! 71f2e69

let y_ = y >= 0n && y < p ? y : mod(y, p);
return x_ === y_;
},
isEven(x: bigint) {
return !(x & 1n);
return !(mod(x, p) & 1n);
},
random() {
return randomField(p, sizeInBytes, hiBitMask);
Expand Down
8 changes: 8 additions & 0 deletions crypto/finite-field.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ for (let F of fields) {
assert.equal(F.sub(3n, 3n), 0n, 'sub');
assert.equal(F.sub(3n, 8n), p - 5n, 'sub');
assert.equal(F.negate(5n), p - 5n, 'negate');
assert.equal(F.negate(p), 0n, 'non-canonical 0 is negated');
assert.equal(F.add(x, F.negate(x)), 0n, 'add & negate');
assert.equal(F.sub(F.add(x, y), x), y, 'add & sub');
assert.equal(F.isEven(17n), false, 'isEven');
assert.equal(F.isEven(p), true, 'non-canonical 0 is even');
assert.equal(F.isEven(p - 1n), true, 'isEven');

assert.equal(F.mul(p - 1n, 2n), p - 2n, 'mul');
Expand Down Expand Up @@ -70,9 +72,15 @@ for (let F of fields) {
let squareX = F.square(x);
assert(F.isSquare(squareX), 'square + isSquare');
assert([x, F.negate(x)].includes(F.sqrt(squareX)!), 'square + sqrt');
assert.equal(F.sqrt(0n), F.sqrt(p), 'sqrt handles non-canonical 0');

if (F.M >= 2n) {
assert(F.isSquare(p - 1n), 'isSquare -1');
assert.equal(
F.isSquare(0n),
F.isSquare(p),
'isSquare handles non-canonical 0'
);
let i = F.power(F.twoadicRoot, 1n << (F.M - 2n));
assert([i, F.negate(i)].includes(F.sqrt(p - 1n)!), 'sqrt -1');
}
Expand Down
Loading