diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/gadgets/ecc.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/gadgets/ecc.test.cpp index 136f442a4ed7..5b3bdc3b639e 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/gadgets/ecc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/gadgets/ecc.test.cpp @@ -240,5 +240,39 @@ TEST(AvmSimulationEccTest, InfinityOnCurve) EXPECT_EQ(result, q); } +TEST(AvmSimulationEccTest, AddsUpToInfinity) +{ + EventEmitter ecc_event_emitter; + EventEmitter scalar_mul_event_emitter; + EventEmitter ecc_add_memory_event_emitter; + + StrictMock execution_id_manager; + StrictMock gt; + StrictMock 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 diff --git a/yarn-project/simulator/src/public/avm/opcodes/ec_add.test.ts b/yarn-project/simulator/src/public/avm/opcodes/ec_add.test.ts index 7db6fa83a904..8e144efaed91 100644 --- a/yarn-project/simulator/src/public/avm/opcodes/ec_add.test.ts +++ b/yarn-project/simulator/src/public/avm/opcodes/ec_add.test.ts @@ -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', () => { diff --git a/yarn-project/simulator/src/public/avm/opcodes/ec_add.ts b/yarn-project/simulator/src/public/avm/opcodes/ec_add.ts index 9dcdc4909c53..19e3c1adc6ca 100644 --- a/yarn-project/simulator/src/public/avm/opcodes/ec_add.ts +++ b/yarn-project/simulator/src/public/avm/opcodes/ec_add.ts @@ -79,12 +79,21 @@ export class EcAdd extends Instruction { } let dest; - if (p1IsInfinite) { + if (p1IsInfinite && p2IsInfinite) { + dest = Point.ZERO; + } 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