-
Notifications
You must be signed in to change notification settings - Fork 610
feat: Point::fromXandSign(...) #7455
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
Changes from all commits
4c2ccc0
3e7f425
fb5aa8b
0f71bb7
606e3af
35b69c9
918cebc
26cbf19
18fdcc8
140429a
46db3de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #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<bb::fr>(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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| 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(); | ||
| }); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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'; | ||||||
|
|
||||||
|
|
@@ -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,8 +38,17 @@ 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); | ||||||
| while (true) { | ||||||
| try { | ||||||
| return Point.fromXAndSign(Fr.random(), randomBoolean()); | ||||||
| } catch (e: any) { | ||||||
| if (!(e instanceof NotOnCurveError)) { | ||||||
| throw e; | ||||||
| } | ||||||
| // The random point is not on the curve - we try again | ||||||
| continue; | ||||||
| } | ||||||
| } | ||||||
|
nventuro marked this conversation as resolved.
|
||||||
| } | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -53,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. | ||||||
|
|
@@ -78,6 +100,46 @@ 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 - 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) { | ||||||
| // Calculate y^2 = x^3 - 17 | ||||||
| const ySquared = x.square().mul(x).sub(new Fr(17)); | ||||||
|
|
||||||
| // 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 NotOnCurveError(); | ||||||
| } | ||||||
|
|
||||||
| const yPositiveBigInt = y.toBigInt() > (Fr.MODULUS - 1n) / 2n ? Fr.MODULUS - y.toBigInt() : y.toBigInt(); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Nit, but this way the sign condition is the same in both places we use it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah silly automerge
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will sneak it in a PR up the stack 👍 |
||||||
| const yNegativeBigInt = Fr.MODULUS - yPositiveBigInt; | ||||||
|
|
||||||
| // Choose the positive or negative root based on isPositive | ||||||
| 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 | ||||||
|
|
@@ -111,6 +173,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, | ||||||
|
|
@@ -194,3 +264,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'; | ||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.