From 4c2ccc0882471d67829667d28e778864d1cd6b64 Mon Sep 17 00:00:00 2001 From: benesjan Date: Fri, 12 Jul 2024 09:15:53 +0000 Subject: [PATCH 01/11] feat: Point::fromXandSign(...) --- .../ecc/curves/grumpkin/c_bind.cpp | 8 +++++ yarn-project/foundation/src/fields/fields.ts | 8 +++++ yarn-project/foundation/src/fields/point.ts | 31 +++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp index 78bb4a04fc54..a989d45f5c6b 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp @@ -63,4 +63,12 @@ WASM_EXPORT void ecc_grumpkin__reduce512_buffer_mod_circuit_modulus(uint8_t* inp write(result, target_output.lo); } +WASM_EXPORT void grumpkin_fr_sqrt(uint8_t const* field_buf, uint8_t* result) +{ + using serialize::write; + auto fr = from_buffer(field_buf); + auto [is_sqr, root] = fr.sqrt(); + write(result, root); +} + // NOLINTEND(cert-dcl37-c, cert-dcl51-cpp, bugprone-reserved-identifier) \ No newline at end of file diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 436003adbfda..587c590fef07 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -4,6 +4,7 @@ import { toBigIntBE, toBufferBE } from '../bigint-buffer/index.js'; import { randomBytes } from '../crypto/random/index.js'; import { BufferReader } from '../serialize/buffer_reader.js'; import { TypeRegistry } from '../serialize/type_registry.js'; +import { BarretenbergSync } from '@aztec/bb.js'; const ZERO_BUFFER = Buffer.alloc(32); @@ -280,6 +281,13 @@ export class Fr extends BaseField { return new Fr(this.toBigInt() / rhs.toBigInt()); } + sqrt() { + const wasm = BarretenbergSync.getSingleton().getWasm(); + wasm.writeMemory(0, this.toBuffer()); + wasm.call('grumpkin_fr_sqrt', 0, Fr.SIZE_IN_BYTES); + return Fr.fromBuffer(Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES, Fr.SIZE_IN_BYTES * 2))); + } + toJSON() { return { type: 'Fr', diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index 26c84d88ec06..846bed5bcc7e 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -78,6 +78,37 @@ export class Point { return new this(reader.readField(), reader.readField(), reader.readBoolean()); } + /** + * Uses the x coordinate and isPositive flag (+/-) to reconstruct the point. + * @dev The y coordinate can be derived from the x coordinate and the sign flag by solving the grumpkin curve + * equation for y. + * @param x - The x coordinate of the point + * @param sign - The sign of the y coordinate + * @returns The point as an array of 2 fields + */ + static fromXAndSign(x: Fr, sign: boolean) { + // Define the constant A for the Grumpkin curve equation (y^2 = x^3 - A) + const A = new Fr(17); + + // Calculate y^2 = x^3 - 17 + const ySquared = x.square().mul(x).sub(A) + + // Calculate the square root of ySquared + const y = ySquared.sqrt(); + + // If y is null, the x-coordinate is not on the curve + if (y === null) { + throw new Error('The given x-coordinate is not on the Grumpkin curve'); + } + + // Choose the positive or negative root based on isPositive + // const finalY = sign ? y : y.neg(); + const finalY = y; + + // Create and return the new Point + return new this(x, finalY, false); + } + /** * Returns the contents of the point as BigInts. * @returns The point as BigInts From 3e7f425ccbc51a08d077d6bbf7ec37df2935ef87 Mon Sep 17 00:00:00 2001 From: benesjan Date: Fri, 12 Jul 2024 10:32:58 +0000 Subject: [PATCH 02/11] WIP --- yarn-project/foundation/src/fields/fields.ts | 3 ++- .../foundation/src/fields/point.test.ts | 17 +++++++++++++++++ yarn-project/foundation/src/fields/point.ts | 17 ++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 yarn-project/foundation/src/fields/point.test.ts diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 587c590fef07..54abae530133 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -1,10 +1,11 @@ +import { BarretenbergSync } from '@aztec/bb.js'; + import { inspect } from 'util'; import { toBigIntBE, toBufferBE } from '../bigint-buffer/index.js'; import { randomBytes } from '../crypto/random/index.js'; import { BufferReader } from '../serialize/buffer_reader.js'; import { TypeRegistry } from '../serialize/type_registry.js'; -import { BarretenbergSync } from '@aztec/bb.js'; const ZERO_BUFFER = Buffer.alloc(32); diff --git a/yarn-project/foundation/src/fields/point.test.ts b/yarn-project/foundation/src/fields/point.test.ts new file mode 100644 index 000000000000..f71a2a1cbec2 --- /dev/null +++ b/yarn-project/foundation/src/fields/point.test.ts @@ -0,0 +1,17 @@ +import { Fr } from './fields.js'; +import { Point } from './point.js'; + +describe('Point', () => { + it('converts to and from x and sign of y coordinate', () => { + const p = new Point( + new Fr(0x30426e64aee30e998c13c8ceecda3a77807dbead52bc2f3bf0eae851b4b710c1n), + new Fr(0x113156a068f603023240c96b4da5474667db3b8711c521c748212a15bc034ea6n), + false, + ); + + const [x, sign] = p.toXAndSign(); + const p2 = Point.fromXAndSign(x, sign); + + expect(p.equals(p2)).toBeTruthy(); + }); +}); diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index 846bed5bcc7e..8faacd3fe0b9 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -91,7 +91,7 @@ export class Point { const A = new Fr(17); // Calculate y^2 = x^3 - 17 - const ySquared = x.square().mul(x).sub(A) + const ySquared = x.square().mul(x).sub(A); // Calculate the square root of ySquared const y = ySquared.sqrt(); @@ -101,14 +101,25 @@ export class Point { throw new Error('The given x-coordinate is not on the Grumpkin curve'); } + const yPositiveBigInt = y.toBigInt() > (Fr.MODULUS - 1n) / 2n ? Fr.MODULUS - y.toBigInt() : y.toBigInt(); + const yNegativeBigInt = Fr.MODULUS - yPositiveBigInt; + // Choose the positive or negative root based on isPositive - // const finalY = sign ? y : y.neg(); - const finalY = y; + const finalY = sign ? new Fr(yPositiveBigInt) : new Fr(yNegativeBigInt); // Create and return the new Point return new this(x, finalY, false); } + /** + * Returns the x coordinate and the sign of the y coordinate. + * @dev The y sign can be determined by checking if the y coordinate is greater than half of the modulus. + * @returns The x coordinate and the sign of the y coordinate. + */ + toXAndSign(): [Fr, boolean] { + return [this.x, this.y.toBigInt() <= (Fr.MODULUS - 1n) / 2n]; + } + /** * Returns the contents of the point as BigInts. * @returns The point as BigInts From fb5aa8b1831d41f89e33b527ef216cf121e41d21 Mon Sep 17 00:00:00 2001 From: benesjan Date: Fri, 12 Jul 2024 11:23:34 +0000 Subject: [PATCH 03/11] WIP --- .../barretenberg/ecc/curves/grumpkin/c_bind.cpp | 13 +++++++++---- yarn-project/foundation/src/fields/fields.test.ts | 12 +++++++++++- yarn-project/foundation/src/fields/fields.ts | 15 +++++++++++++-- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp index a989d45f5c6b..b206430a3f33 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp @@ -63,12 +63,17 @@ WASM_EXPORT void ecc_grumpkin__reduce512_buffer_mod_circuit_modulus(uint8_t* inp write(result, target_output.lo); } -WASM_EXPORT void grumpkin_fr_sqrt(uint8_t const* field_buf, uint8_t* result) +WASM_EXPORT void grumpkin_fr_sqrt(uint8_t const* input, uint8_t* result) { using serialize::write; - auto fr = from_buffer(field_buf); - auto [is_sqr, root] = fr.sqrt(); - write(result, root); + auto input_fr = from_buffer(input); + auto [is_sqr, root] = input_fr.sqrt(); + + uint8_t* is_sqrt_result_ptr = result; + uint8_t* root_result_ptr = result + 1; + + write(is_sqrt_result_ptr, is_sqr); + write(root_result_ptr, root); } // NOLINTEND(cert-dcl37-c, cert-dcl51-cpp, bugprone-reserved-identifier) \ No newline at end of file diff --git a/yarn-project/foundation/src/fields/fields.test.ts b/yarn-project/foundation/src/fields/fields.test.ts index 2f8686bb4c5b..d1a728e67d77 100644 --- a/yarn-project/foundation/src/fields/fields.test.ts +++ b/yarn-project/foundation/src/fields/fields.test.ts @@ -109,7 +109,7 @@ describe('Bn254 arithmetic', () => { expect(actual).toEqual(expected); }); - it('High Bonudary', () => { + it('High Boundary', () => { // -1 - (-1) = 0 const a = new Fr(Fr.MODULUS - 1n); const b = new Fr(Fr.MODULUS - 1n); @@ -184,6 +184,16 @@ describe('Bn254 arithmetic', () => { }); }); + describe('Square root', () => { + it('Should return the correct square root', () => { + const a = new Fr(16); + const expected = new Fr(4); + + const actual = a.sqrt(); + expect(actual).toEqual(expected); + }); + }); + describe('Comparison', () => { it.each([ [new Fr(5), new Fr(10), -1], diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 54abae530133..a27b5fad26a3 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -282,11 +282,22 @@ export class Fr extends BaseField { return new Fr(this.toBigInt() / rhs.toBigInt()); } - sqrt() { + /** + * Computes the square root of the field element. + * @returns The square root of the field element if it exists (undefined if not). + */ + sqrt(): Fr | undefined { const wasm = BarretenbergSync.getSingleton().getWasm(); wasm.writeMemory(0, this.toBuffer()); wasm.call('grumpkin_fr_sqrt', 0, Fr.SIZE_IN_BYTES); - return Fr.fromBuffer(Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES, Fr.SIZE_IN_BYTES * 2))); + const isSqrtBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES, Fr.SIZE_IN_BYTES + 1)); + const isSqrt = isSqrtBuf[0] === 1; + if (!isSqrt) { + return undefined; + } + + const rootBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES + 1, Fr.SIZE_IN_BYTES * 2 + 1)) + return Fr.fromBuffer(rootBuf); } toJSON() { From 0f71bb70ba5d7fd6a10c00dfd4f4c84143e3cf50 Mon Sep 17 00:00:00 2001 From: benesjan Date: Fri, 12 Jul 2024 12:53:59 +0000 Subject: [PATCH 04/11] WIP --- yarn-project/foundation/src/fields/fields.ts | 7 ++++--- yarn-project/foundation/src/fields/point.ts | 5 +---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index a27b5fad26a3..7718d9d0abc4 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -284,16 +284,17 @@ export class Fr extends BaseField { /** * Computes the square root of the field element. - * @returns The square root of the field element if it exists (undefined if not). + * @returns The square root of the field element (null if it does not exist). */ - sqrt(): Fr | undefined { + sqrt(): Fr | null { const wasm = BarretenbergSync.getSingleton().getWasm(); wasm.writeMemory(0, this.toBuffer()); wasm.call('grumpkin_fr_sqrt', 0, Fr.SIZE_IN_BYTES); const isSqrtBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES, Fr.SIZE_IN_BYTES + 1)); const isSqrt = isSqrtBuf[0] === 1; if (!isSqrt) { - return undefined; + // Field element is not a quadratic residue mod p so it has no square root. + return null; } const rootBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES + 1, Fr.SIZE_IN_BYTES * 2 + 1)) diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index 8faacd3fe0b9..4d42af83795f 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -87,11 +87,8 @@ export class Point { * @returns The point as an array of 2 fields */ static fromXAndSign(x: Fr, sign: boolean) { - // Define the constant A for the Grumpkin curve equation (y^2 = x^3 - A) - const A = new Fr(17); - // Calculate y^2 = x^3 - 17 - const ySquared = x.square().mul(x).sub(A); + const ySquared = x.square().mul(x).sub(new Fr(17)); // Calculate the square root of ySquared const y = ySquared.sqrt(); From 606e3af2d16d631aab74d3827209378958d6e2e3 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 10:30:01 +0000 Subject: [PATCH 05/11] WIP --- .../barretenberg/ecc/curves/bn254/c_bind.cpp | 33 +++++++++++++++++++ .../ecc/curves/grumpkin/c_bind.cpp | 13 -------- .../foundation/src/fields/fields.test.ts | 20 +++++++---- yarn-project/foundation/src/fields/fields.ts | 31 ++++++++++++++--- 4 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp new file mode 100644 index 000000000000..85070ee080f1 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp @@ -0,0 +1,33 @@ +#include "../bn254/fq.hpp" +#include "../bn254/fr.hpp" +#include "barretenberg/common/wasm_export.hpp" + +using namespace bb; + +WASM_EXPORT void bn254_fr_sqrt(uint8_t const* input, uint8_t* result) +{ + using serialize::write; + auto input_fr = from_buffer(input); + auto [is_sqr, root] = input_fr.sqrt(); + + uint8_t* is_sqrt_result_ptr = result; + uint8_t* root_result_ptr = result + 1; + + write(is_sqrt_result_ptr, is_sqr); + write(root_result_ptr, root); +} + +WASM_EXPORT void bn254_fq_sqrt(uint8_t const* input, uint8_t* result) +{ + using serialize::write; + auto input_fq = from_buffer(input); + auto [is_sqr, root] = input_fq.sqrt(); + + uint8_t* is_sqrt_result_ptr = result; + uint8_t* root_result_ptr = result + 1; + + write(is_sqrt_result_ptr, is_sqr); + write(root_result_ptr, root); +} + +// NOLINTEND(cert-dcl37-c, cert-dcl51-cpp, bugprone-reserved-identifier) \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp index b206430a3f33..78bb4a04fc54 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp @@ -63,17 +63,4 @@ WASM_EXPORT void ecc_grumpkin__reduce512_buffer_mod_circuit_modulus(uint8_t* inp write(result, target_output.lo); } -WASM_EXPORT void grumpkin_fr_sqrt(uint8_t const* input, uint8_t* result) -{ - using serialize::write; - auto input_fr = from_buffer(input); - auto [is_sqr, root] = input_fr.sqrt(); - - uint8_t* is_sqrt_result_ptr = result; - uint8_t* root_result_ptr = result + 1; - - write(is_sqrt_result_ptr, is_sqr); - write(root_result_ptr, root); -} - // NOLINTEND(cert-dcl37-c, cert-dcl51-cpp, bugprone-reserved-identifier) \ No newline at end of file diff --git a/yarn-project/foundation/src/fields/fields.test.ts b/yarn-project/foundation/src/fields/fields.test.ts index d1a728e67d77..bd31319bcc83 100644 --- a/yarn-project/foundation/src/fields/fields.test.ts +++ b/yarn-project/foundation/src/fields/fields.test.ts @@ -1,4 +1,4 @@ -import { Fr, GrumpkinScalar } from './fields.js'; +import { Fq, Fr, GrumpkinScalar } from './fields.js'; describe('GrumpkinScalar Serialization', () => { // Test case for GrumpkinScalar.fromHighLow @@ -185,12 +185,20 @@ describe('Bn254 arithmetic', () => { }); describe('Square root', () => { - it('Should return the correct square root', () => { - const a = new Fr(16); - const expected = new Fr(4); + it('Should return the correct square root for Fr', () => { + const a = Fr.random(); + const squared = a.mul(a); - const actual = a.sqrt(); - expect(actual).toEqual(expected); + const actual = squared.sqrt(); + expect(actual!.mul(actual!)).toEqual(squared); + }); + + it('Should return the correct square root for Fq', () => { + const a = Fq.random(); + const squared = a.mul(a); + + const actual = squared.sqrt(); + expect(actual!.mul(actual!)).toEqual(squared); }); }); diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 7718d9d0abc4..c84b547cf3ef 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -283,13 +283,13 @@ export class Fr extends BaseField { } /** - * Computes the square root of the field element. - * @returns The square root of the field element (null if it does not exist). + * Computes a square root of the field element. + * @returns A square root of the field element (null if it does not exist). */ sqrt(): Fr | null { const wasm = BarretenbergSync.getSingleton().getWasm(); wasm.writeMemory(0, this.toBuffer()); - wasm.call('grumpkin_fr_sqrt', 0, Fr.SIZE_IN_BYTES); + wasm.call('bn254_fr_sqrt', 0, Fr.SIZE_IN_BYTES); const isSqrtBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES, Fr.SIZE_IN_BYTES + 1)); const isSqrt = isSqrtBuf[0] === 1; if (!isSqrt) { @@ -297,7 +297,7 @@ export class Fr extends BaseField { return null; } - const rootBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES + 1, Fr.SIZE_IN_BYTES * 2 + 1)) + const rootBuf = Buffer.from(wasm.getMemorySlice(Fr.SIZE_IN_BYTES + 1, Fr.SIZE_IN_BYTES * 2 + 1)); return Fr.fromBuffer(rootBuf); } @@ -380,6 +380,29 @@ export class Fq extends BaseField { return new Fq((high.toBigInt() << Fq.HIGH_SHIFT) + low.toBigInt()); } + mul(rhs: Fq) { + return new Fq((this.toBigInt() * rhs.toBigInt()) % Fq.MODULUS); + } + + /** + * Computes a square root of the field element. + * @returns A square root of the field element (null if it does not exist). + */ + sqrt(): Fq | null { + const wasm = BarretenbergSync.getSingleton().getWasm(); + wasm.writeMemory(0, this.toBuffer()); + wasm.call('bn254_fq_sqrt', 0, Fq.SIZE_IN_BYTES); + const isSqrtBuf = Buffer.from(wasm.getMemorySlice(Fq.SIZE_IN_BYTES, Fq.SIZE_IN_BYTES + 1)); + const isSqrt = isSqrtBuf[0] === 1; + if (!isSqrt) { + // Field element is not a quadratic residue mod p so it has no square root. + return null; + } + + const rootBuf = Buffer.from(wasm.getMemorySlice(Fq.SIZE_IN_BYTES + 1, Fq.SIZE_IN_BYTES * 2 + 1)); + return Fq.fromBuffer(rootBuf); + } + toJSON() { return { type: 'Fq', From 35b69c9c39558fea8a359e8a9a6ffb0c90083f06 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 10:41:24 +0000 Subject: [PATCH 06/11] cleanup --- .../barretenberg/ecc/curves/bn254/c_bind.cpp | 14 ----------- .../foundation/src/fields/fields.test.ts | 12 ++-------- yarn-project/foundation/src/fields/fields.ts | 23 ------------------- 3 files changed, 2 insertions(+), 47 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp index 85070ee080f1..bf0807a4e68e 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/c_bind.cpp @@ -1,4 +1,3 @@ -#include "../bn254/fq.hpp" #include "../bn254/fr.hpp" #include "barretenberg/common/wasm_export.hpp" @@ -17,17 +16,4 @@ WASM_EXPORT void bn254_fr_sqrt(uint8_t const* input, uint8_t* result) write(root_result_ptr, root); } -WASM_EXPORT void bn254_fq_sqrt(uint8_t const* input, uint8_t* result) -{ - using serialize::write; - auto input_fq = from_buffer(input); - auto [is_sqr, root] = input_fq.sqrt(); - - uint8_t* is_sqrt_result_ptr = result; - uint8_t* root_result_ptr = result + 1; - - write(is_sqrt_result_ptr, is_sqr); - write(root_result_ptr, root); -} - // NOLINTEND(cert-dcl37-c, cert-dcl51-cpp, bugprone-reserved-identifier) \ No newline at end of file diff --git a/yarn-project/foundation/src/fields/fields.test.ts b/yarn-project/foundation/src/fields/fields.test.ts index bd31319bcc83..2813be152a02 100644 --- a/yarn-project/foundation/src/fields/fields.test.ts +++ b/yarn-project/foundation/src/fields/fields.test.ts @@ -1,4 +1,4 @@ -import { Fq, Fr, GrumpkinScalar } from './fields.js'; +import { Fr, GrumpkinScalar } from './fields.js'; describe('GrumpkinScalar Serialization', () => { // Test case for GrumpkinScalar.fromHighLow @@ -185,21 +185,13 @@ describe('Bn254 arithmetic', () => { }); describe('Square root', () => { - it('Should return the correct square root for Fr', () => { + it('Should return the correct square root', () => { const a = Fr.random(); const squared = a.mul(a); const actual = squared.sqrt(); expect(actual!.mul(actual!)).toEqual(squared); }); - - it('Should return the correct square root for Fq', () => { - const a = Fq.random(); - const squared = a.mul(a); - - const actual = squared.sqrt(); - expect(actual!.mul(actual!)).toEqual(squared); - }); }); describe('Comparison', () => { diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index c84b547cf3ef..5fa3c9cac295 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -380,29 +380,6 @@ export class Fq extends BaseField { return new Fq((high.toBigInt() << Fq.HIGH_SHIFT) + low.toBigInt()); } - mul(rhs: Fq) { - return new Fq((this.toBigInt() * rhs.toBigInt()) % Fq.MODULUS); - } - - /** - * Computes a square root of the field element. - * @returns A square root of the field element (null if it does not exist). - */ - sqrt(): Fq | null { - const wasm = BarretenbergSync.getSingleton().getWasm(); - wasm.writeMemory(0, this.toBuffer()); - wasm.call('bn254_fq_sqrt', 0, Fq.SIZE_IN_BYTES); - const isSqrtBuf = Buffer.from(wasm.getMemorySlice(Fq.SIZE_IN_BYTES, Fq.SIZE_IN_BYTES + 1)); - const isSqrt = isSqrtBuf[0] === 1; - if (!isSqrt) { - // Field element is not a quadratic residue mod p so it has no square root. - return null; - } - - const rootBuf = Buffer.from(wasm.getMemorySlice(Fq.SIZE_IN_BYTES + 1, Fq.SIZE_IN_BYTES * 2 + 1)); - return Fq.fromBuffer(rootBuf); - } - toJSON() { return { type: 'Fq', From 918cebc45a07da550da527e7eaef8137f4a4ab51 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 10:56:55 +0000 Subject: [PATCH 07/11] fixing Point.random --- yarn-project/foundation/src/crypto/random/index.ts | 9 +++++++++ yarn-project/foundation/src/fields/point.test.ts | 4 ++++ yarn-project/foundation/src/fields/point.ts | 5 ++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/yarn-project/foundation/src/crypto/random/index.ts b/yarn-project/foundation/src/crypto/random/index.ts index a64dc4f4a957..76ee5a9a6a9b 100644 --- a/yarn-project/foundation/src/crypto/random/index.ts +++ b/yarn-project/foundation/src/crypto/random/index.ts @@ -74,3 +74,12 @@ export const randomBigInt = (max: bigint) => { const randomBigInt = BigInt(`0x${randomBuffer.toString('hex')}`); // Convert buffer to a large integer. return randomBigInt % max; // Use modulo to ensure the result is less than max. }; + +/** + * Generate a random boolean value. + * @returns A random boolean value. + */ +export const randomBoolean = () => { + const randomByte = randomBytes(1)[0]; // Generate a single random byte. + return randomByte % 2 === 0; // Use modulo to determine if the byte is even or odd. +}; diff --git a/yarn-project/foundation/src/fields/point.test.ts b/yarn-project/foundation/src/fields/point.test.ts index f71a2a1cbec2..48d452a16ade 100644 --- a/yarn-project/foundation/src/fields/point.test.ts +++ b/yarn-project/foundation/src/fields/point.test.ts @@ -14,4 +14,8 @@ describe('Point', () => { expect(p.equals(p2)).toBeTruthy(); }); + + it('creates a valid random point', () => { + expect(Point.random().isOnGrumpkin()).toBeTruthy(); + }); }); diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index 4d42af83795f..faf5fbb7efad 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -1,4 +1,4 @@ -import { poseidon2Hash } from '../crypto/index.js'; +import { poseidon2Hash, randomBoolean } from '../crypto/index.js'; import { BufferReader, FieldReader, serializeToBuffer } from '../serialize/index.js'; import { Fr } from './fields.js'; @@ -37,8 +37,7 @@ export class Point { * @returns A randomly generated Point instance. */ static random() { - // TODO make this return an actual point on curve. - return new Point(Fr.random(), Fr.random(), false); + return Point.fromXAndSign(Fr.random(), randomBoolean()); } /** From 26cbf19eaedb456d5609432715f0f542bdf99ced Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 12:36:29 +0000 Subject: [PATCH 08/11] WIP --- .../foundation/src/fields/point.test.ts | 14 ++++++++ yarn-project/foundation/src/fields/point.ts | 33 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/yarn-project/foundation/src/fields/point.test.ts b/yarn-project/foundation/src/fields/point.test.ts index 48d452a16ade..6fa64160b414 100644 --- a/yarn-project/foundation/src/fields/point.test.ts +++ b/yarn-project/foundation/src/fields/point.test.ts @@ -18,4 +18,18 @@ describe('Point', () => { it('creates a valid random point', () => { expect(Point.random().isOnGrumpkin()).toBeTruthy(); }); + + it('converts to and from buffer', () => { + const p = Point.random(); + const p2 = Point.fromBuffer(p.toBuffer()); + + expect(p.equals(p2)).toBeTruthy(); + }); + + it('converts to and from compressed buffer', () => { + const p = Point.random(); + const p2 = Point.fromCompressedBuffer(p.toCompressedBuffer()); + + expect(p.equals(p2)).toBeTruthy(); + }); }); diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index faf5fbb7efad..3c4d197d617d 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -10,6 +10,7 @@ import { Fr } from './fields.js'; export class Point { static ZERO = new Point(Fr.ZERO, Fr.ZERO, false); static SIZE_IN_BYTES = Fr.SIZE_IN_BYTES * 2; + static COMPRESSED_SIZE_IN_BYTES = Fr.SIZE_IN_BYTES + 1; /** Used to differentiate this class from AztecAddress */ public readonly kind = 'point'; @@ -37,7 +38,17 @@ export class Point { * @returns A randomly generated Point instance. */ static random() { - return Point.fromXAndSign(Fr.random(), randomBoolean()); + while (true) { + try { + return Point.fromXAndSign(Fr.random(), randomBoolean()); + } catch (e: any) { + if (e.message !== 'The given x-coordinate is not on the Grumpkin curve') { + throw e; + } + // The random point is not on the curve - we try again + continue; + } + } } /** @@ -52,6 +63,18 @@ export class Point { return new this(Fr.fromBuffer(reader), Fr.fromBuffer(reader), false); } + /** + * Create a Point instance from a compressed buffer. + * The input 'buffer' should have exactly 33 bytes representing the x coordinate and the sign of the y coordinate. + * + * @param buffer - The buffer containing the x coordinate and the sign of the y coordinate. + * @returns A Point instance. + */ + static fromCompressedBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return this.fromXAndSign(Fr.fromBuffer(reader), reader.readBoolean()); + } + /** * Create a Point instance from a hex-encoded string. * The input 'address' should be prefixed with '0x' or not, and have exactly 128 hex characters representing the x and y coordinates. @@ -149,6 +172,14 @@ export class Point { return buf; } + /** + * Converts the Point instance to a compressed Buffer representation of the coordinates. + * @returns A Buffer representation of the Point instance + */ + toCompressedBuffer() { + return serializeToBuffer(this.toXAndSign()); + } + /** * Convert the Point instance to a hexadecimal string representation. * The output string is prefixed with '0x' and consists of exactly 128 hex characters, From 18fdcc822d387b69a54ee6f6e634c6db17be3184 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 12:38:47 +0000 Subject: [PATCH 09/11] WIP --- yarn-project/foundation/src/fields/point.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index 3c4d197d617d..d8be2ae78c63 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -42,7 +42,7 @@ export class Point { try { return Point.fromXAndSign(Fr.random(), randomBoolean()); } catch (e: any) { - if (e.message !== 'The given x-coordinate is not on the Grumpkin curve') { + if (!(e instanceof NotOnCurveError)) { throw e; } // The random point is not on the curve - we try again @@ -117,7 +117,7 @@ export class Point { // If y is null, the x-coordinate is not on the curve if (y === null) { - throw new Error('The given x-coordinate is not on the Grumpkin curve'); + throw new NotOnCurveError(); } const yPositiveBigInt = y.toBigInt() > (Fr.MODULUS - 1n) / 2n ? Fr.MODULUS - y.toBigInt() : y.toBigInt(); @@ -263,3 +263,10 @@ export function isPoint(obj: object): obj is Point { const point = obj as Point; return point.kind === 'point' && point.x !== undefined && point.y !== undefined; } + +class NotOnCurveError extends Error { + constructor() { + super('The given x-coordinate is not on the Grumpkin curve'); + this.name = 'NotOnCurveError'; + } +} From 140429af51828f9ddab603ec16e22b1a33c44bd2 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 18:26:40 +0000 Subject: [PATCH 10/11] more tests --- .../foundation/src/fields/fields.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/yarn-project/foundation/src/fields/fields.test.ts b/yarn-project/foundation/src/fields/fields.test.ts index 2813be152a02..f07db9fe8992 100644 --- a/yarn-project/foundation/src/fields/fields.test.ts +++ b/yarn-project/foundation/src/fields/fields.test.ts @@ -185,7 +185,21 @@ describe('Bn254 arithmetic', () => { }); describe('Square root', () => { - it('Should return the correct square root', () => { + it.each([ + [new Fr(0), 0n], + [new Fr(4), 2n], + [new Fr(9), 3n], + [new Fr(16), 4n], + ])('Should return the correct square root for %p', (input, expected) => { + const actual = input.sqrt()!.toBigInt(); + + // The square root can be either the expected value or the modulus - expected value + const isValid = actual == expected || actual == Fr.MODULUS - expected; + + expect(isValid).toBeTruthy(); + }); + + it('Should return the correct square root for random value', () => { const a = Fr.random(); const squared = a.mul(a); From 46db3deafc5247e627e79cf19d5572cfd6dd071c Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 15 Jul 2024 18:29:28 +0000 Subject: [PATCH 11/11] more docs --- yarn-project/foundation/src/fields/point.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index d8be2ae78c63..3bcf4a00ede1 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -102,10 +102,11 @@ export class Point { /** * Uses the x coordinate and isPositive flag (+/-) to reconstruct the point. - * @dev The y coordinate can be derived from the x coordinate and the sign flag by solving the grumpkin curve + * @dev The y coordinate can be derived from the x coordinate and the "sign" flag by solving the grumpkin curve * equation for y. * @param x - The x coordinate of the point - * @param sign - The sign of the y coordinate + * @param sign - The "sign" of the y coordinate - note that this is not a sign as is known in integer arithmetic. + * Instead it is a boolean flag that determines whether the y coordinate is <= (Fr.MODULUS - 1) / 2 * @returns The point as an array of 2 fields */ static fromXAndSign(x: Fr, sign: boolean) {