Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,39 @@ TEST(AvmSimulationEccTest, InfinityOnCurve)
EXPECT_EQ(result, q);
}

TEST(AvmSimulationEccTest, AddsUpToInfinity)
{
EventEmitter<EccAddEvent> ecc_event_emitter;
EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;

StrictMock<MockExecutionIdManager> execution_id_manager;
StrictMock<MockGreaterThan> gt;
StrictMock<MockToRadix> to_radix;
MemoryStore memory;

EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(Return(0));
EXPECT_CALL(gt, gt(0x1000 + 2, AVM_HIGHEST_MEM_ADDRESS)).WillOnce(Return(false));

Ecc ecc(
execution_id_manager, gt, to_radix, ecc_event_emitter, scalar_mul_event_emitter, ecc_add_memory_event_emitter);

// Both points on the curve, q = -p
FF p_x("0x04c95d1b26d63d46918a156cae92db1bcbc4072a27ec81dc82ea959abdbcf16a");
FF p_y("0x035b6dd9e63c1370462c74775765d07fc21fd1093cc988149d3aa763bb3dbb60");
EmbeddedCurvePoint p(p_x, p_y, false);

EmbeddedCurvePoint q = -p;

uint32_t dst_address = 0x1000;
ecc.add(memory, p, q, dst_address);

// Zero as coordinates
EXPECT_EQ(memory.get(dst_address).as_ff(), FF(0));
EXPECT_EQ(memory.get(dst_address + 1).as_ff(), FF(0));
// Infinity
EXPECT_EQ(memory.get(dst_address + 2).as_ff(), 1);
}

} // namespace
} // namespace bb::avm2::simulation
141 changes: 141 additions & 0 deletions yarn-project/simulator/src/public/avm/opcodes/ec_add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,147 @@ describe('EC Instructions', () => {
expect(actual).toEqual(G3);
expect(context.machineState.memory.get(8).toFr().equals(Fr.ZERO)).toBe(true);
});

it('Should add correctly with rhs being infinity', async () => {
const zero = new Uint1(0);
const one = new Uint1(1);

const x = new Field(Grumpkin.generator.x);
const y = new Field(Grumpkin.generator.y);

// Point 1 is not infinity
context.machineState.memory.set(0, x);
context.machineState.memory.set(1, y);
context.machineState.memory.set(2, zero);
// Point 2 is infinity
context.machineState.memory.set(3, x);
context.machineState.memory.set(4, y);
context.machineState.memory.set(5, one);
context.machineState.memory.set(6, new Uint32(6));

await new EcAdd(
/*addressing_mode=*/ 0,
/*p1X=*/ 0,
/*p1Y=*/ 1,
/*p1IsInfinite=*/ 2,
/*p2X=*/ 3,
/*p2Y=*/ 4,
/*p2IsInfinite=*/ 5,
/*dstOffset=*/ 6,
).execute(context);

expect([
context.machineState.memory.get(6).toFr(),
context.machineState.memory.get(7).toFr(),
context.machineState.memory.get(8).toNumber(),
]).toEqual([x.toFr(), y.toFr(), 0]);
});

it('Should add correctly with lhs being infinity', async () => {
const zero = new Uint1(0);
const one = new Uint1(1);

const x = new Field(Grumpkin.generator.x);
const y = new Field(Grumpkin.generator.y);

// Point 1 is infinity
context.machineState.memory.set(0, x);
context.machineState.memory.set(1, y);
context.machineState.memory.set(2, one);
// Point 2 is not infinity
context.machineState.memory.set(3, x);
context.machineState.memory.set(4, y);
context.machineState.memory.set(5, zero);
context.machineState.memory.set(6, new Uint32(6));

await new EcAdd(
/*addressing_mode=*/ 0,
/*p1X=*/ 0,
/*p1Y=*/ 1,
/*p1IsInfinite=*/ 2,
/*p2X=*/ 3,
/*p2Y=*/ 4,
/*p2IsInfinite=*/ 5,
/*dstOffset=*/ 6,
).execute(context);

expect([
context.machineState.memory.get(6).toFr(),
context.machineState.memory.get(7).toFr(),
context.machineState.memory.get(8).toNumber(),
]).toEqual([x.toFr(), y.toFr(), 0]);
});

it('Should add correctly with both being infinity', async () => {
const one = new Uint1(1);

const x = new Field(Grumpkin.generator.x);
const y = new Field(Grumpkin.generator.y);

// Point 1 is infinity
context.machineState.memory.set(0, x);
context.machineState.memory.set(1, y);
context.machineState.memory.set(2, one);
// Point 2 is infinity
context.machineState.memory.set(3, x);
context.machineState.memory.set(4, y);
context.machineState.memory.set(5, one);
context.machineState.memory.set(6, new Uint32(6));

await new EcAdd(
/*addressing_mode=*/ 0,
/*p1X=*/ 0,
/*p1Y=*/ 1,
/*p1IsInfinite=*/ 2,
/*p2X=*/ 3,
/*p2Y=*/ 4,
/*p2IsInfinite=*/ 5,
/*dstOffset=*/ 6,
).execute(context);

expect([
context.machineState.memory.get(6).toFr(),
context.machineState.memory.get(7).toFr(),
context.machineState.memory.get(8).toNumber(),
]).toEqual([Fr.ZERO, Fr.ZERO, 1]);
});

it('Should add correctly with none infinity adding up to infinity', async () => {
const zero = new Uint1(0);

// Point 1 is a "random" point on the curve
const x1 = new Field(2165030248772332382647339664685760681662697934905450801078761197378150920554n);
const y1 = new Field(1518479793551399970960577643223827307749147426195887130444945641264602004320n);
// Point 2 is negation of point 1
const x2 = new Field(2165030248772332382647339664685760681662697934905450801078761197378150920554n);
const y2 = new Field(20369763078287875251285828102033447780799216974220147213253258545311206491297n);

context.machineState.memory.set(0, x1);
context.machineState.memory.set(1, y1);
context.machineState.memory.set(2, zero);

context.machineState.memory.set(3, x2);
context.machineState.memory.set(4, y2);
context.machineState.memory.set(5, zero);
context.machineState.memory.set(6, new Uint32(6));

await new EcAdd(
/*addressing_mode=*/ 0,
/*p1X=*/ 0,
/*p1Y=*/ 1,
/*p1IsInfinite=*/ 2,
/*p2X=*/ 3,
/*p2Y=*/ 4,
/*p2IsInfinite=*/ 5,
/*dstOffset=*/ 6,
).execute(context);

expect([
context.machineState.memory.get(6).toFr(),
context.machineState.memory.get(7).toFr(),
context.machineState.memory.get(8).toNumber(),
]).toEqual([Fr.ZERO, Fr.ZERO, 1]);
});
});

describe('EcAdd should throw an error when a point is not on the curve', () => {
Expand Down
13 changes: 11 additions & 2 deletions yarn-project/simulator/src/public/avm/opcodes/ec_add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,21 @@ export class EcAdd extends Instruction {
}

let dest;
if (p1IsInfinite) {
if (p1IsInfinite && p2IsInfinite) {
dest = Point.ZERO;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ahh, I see why this all failed previously... Point.ZERO is not really the point at infinity (the Point class stores isInfinite = false for ZERO, which is never passed to bb anyway). Maybe tracking a destIsInfinite in this nested if would be better so we can override more robustly at the end?
Anyway! This is great for now, good find!!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah wanted to do a minimal fix since it's on the old ts codebase

} else if (p1IsInfinite) {
dest = p2;
} else if (p2IsInfinite) {
dest = p1;
} else {
dest = await Grumpkin.add(p1, p2);
// TS<>BB ecc add communication is broken for points that add up to infinity.
// However, here we know that both points are on the curve, and that none is infinity
// so we can check for the case where you add p + (-p) = infinity.
if (p1.x.equals(p2.x) && !p1.y.equals(p2.y)) {
dest = Point.ZERO;
} else {
dest = await Grumpkin.add(p1, p2);
}
}

// Important to use setSlice() and not set() in the two following statements as
Expand Down
Loading