|
| 1 | +import { AvmTestContractArtifact } from '@aztec/noir-test-contracts.js/AvmTest'; |
| 2 | +import { defaultGlobals } from '@aztec/simulator/public/fixtures'; |
| 3 | +import { AztecAddress } from '@aztec/stdlib/aztec-address'; |
| 4 | +import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; |
| 5 | +import { NativeWorldStateService } from '@aztec/world-state'; |
| 6 | + |
| 7 | +import { AvmProvingTester } from './avm_proving_tester.js'; |
| 8 | + |
| 9 | +// Regression for the AVM-transpiled MSM zero-scalar invalid-point gap, exercised through the full bb-prover |
| 10 | +// proving path. |
| 11 | +// |
| 12 | +// The hand-written MSM procedure (avm-transpiler/src/procedures/msm.rs) used to skip a term as soon as both |
| 13 | +// scalar limbs were zero, before validating the corresponding point. Native Brillig/BN254 `multi_scalar_mul` |
| 14 | +// validates every input point regardless of its scalar, and there is a native test that rejects |
| 15 | +// (x=1, y=1, is_infinite=false) even with a zero scalar |
| 16 | +// (noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/embedded_curve_ops.rs). The fix forces an on-curve |
| 17 | +// ECADD check for every input point before the zero-scalar skip, so this off-curve point must now revert in |
| 18 | +// the public AVM proving path instead of silently returning the point at infinity. |
| 19 | +// |
| 20 | +// The AvmTestContract `variable_base_msm_with_point` function takes the point coordinates as arguments, so |
| 21 | +// we can hand it the off-curve point (1, 1) directly without patching bytecode. |
| 22 | + |
| 23 | +describe('AVM MSM zero-scalar invalid-point regression', () => { |
| 24 | + let tester: AvmProvingTester; |
| 25 | + let worldStateService: NativeWorldStateService; |
| 26 | + |
| 27 | + const sender = AztecAddress.fromNumberUnsafe(42); |
| 28 | + let contract: ContractInstanceWithAddress; |
| 29 | + |
| 30 | + beforeEach(async () => { |
| 31 | + worldStateService = await NativeWorldStateService.tmp(); |
| 32 | + // FULL PROVING (not check-circuit) so the gap is exercised through the real bb-prover path. |
| 33 | + tester = await AvmProvingTester.new(worldStateService, /*checkCircuitOnly=*/ false, /*globals=*/ defaultGlobals()); |
| 34 | + contract = await tester.registerAndDeployContract( |
| 35 | + /*constructorArgs=*/ [], |
| 36 | + sender, |
| 37 | + /*contractArtifact=*/ AvmTestContractArtifact, |
| 38 | + ); |
| 39 | + }); |
| 40 | + |
| 41 | + afterEach(async () => { |
| 42 | + await worldStateService.close(); |
| 43 | + }); |
| 44 | + |
| 45 | + it('reverts on an off-curve point even when its scalar is zero', async () => { |
| 46 | + await tester.simProveVerifyAppLogic( |
| 47 | + { |
| 48 | + address: contract.address, |
| 49 | + fnName: 'variable_base_msm_with_point', |
| 50 | + // Off-curve point (1, 1) with a zero scalar: the term contributes nothing but must still be validated. |
| 51 | + args: [/*px=*/ 1, /*py=*/ 1, /*scalar_lo=*/ 0, /*scalar_hi=*/ 0, /*scalar2_lo=*/ 20, /*scalar2_hi=*/ 0], |
| 52 | + }, |
| 53 | + /*expectRevert=*/ true, |
| 54 | + /*txLabel=*/ 'msm-zero-scalar-invalid-point', |
| 55 | + ); |
| 56 | + }, 180_000); |
| 57 | +}); |
0 commit comments