@@ -302,8 +302,16 @@ pub fn validate_witness<F: AcirField>(
302302 }
303303 }
304304 }
305- // Recursive aggregation is checked outside of ACVM
306- BlackBoxFuncCall :: RecursiveAggregation { .. } => ( ) ,
305+ // Recursive aggregation is verified by the backend rather than the ACVM, so
306+ // there is no constraint to evaluate here. Its operands must still be present
307+ // in the witness map though, matching the PWG solver which requires every
308+ // `get_inputs_vec()` operand to be assigned before treating the opcode as
309+ // backend-owned.
310+ BlackBoxFuncCall :: RecursiveAggregation { .. } => {
311+ for input in black_box_func_call. get_inputs_vec ( ) {
312+ input_to_value ( & witness_map, input) ?;
313+ }
314+ }
307315 BlackBoxFuncCall :: Poseidon2Permutation { inputs, outputs } => {
308316 let state = blackbox:: hash:: execute_poseidon2_permutation_opcode (
309317 backend,
@@ -913,4 +921,49 @@ mod tests {
913921 format ! ( "Attempted reinitialization of memory block {}" , block_id. 0 ) . as_str ( ) ,
914922 ) ;
915923 }
924+
925+ fn recursive_aggregation_opcode ( ) -> Opcode < FieldElement > {
926+ Opcode :: BlackBoxFuncCall ( BlackBoxFuncCall :: RecursiveAggregation {
927+ verification_key : vec ! [ FunctionInput :: Witness ( Witness ( 1 ) ) ] ,
928+ proof : vec ! [ FunctionInput :: Witness ( Witness ( 2 ) ) ] ,
929+ public_inputs : vec ! [ FunctionInput :: Witness ( Witness ( 3 ) ) ] ,
930+ key_hash : FunctionInput :: Witness ( Witness ( 4 ) ) ,
931+ proof_type : 0 ,
932+ predicate : FunctionInput :: Witness ( Witness ( 5 ) ) ,
933+ } )
934+ }
935+
936+ #[ test]
937+ fn test_recursive_aggregation_missing_inputs ( ) {
938+ // Recursive aggregation is verified by the backend, but its operands must still be
939+ // assigned. An empty witness map leaves every operand unassigned, so validation must
940+ // report the first missing operand rather than silently succeeding.
941+ let circuit = make_circuit ( vec ! [ recursive_aggregation_opcode( ) ] ) ;
942+
943+ let witness_map = WitnessMap :: default ( ) ;
944+
945+ let backend = Bn254BlackBoxSolver ;
946+ assert_eq ! (
947+ validate_witness( & backend, witness_map, & circuit) . unwrap_err( ) ,
948+ OpcodeResolutionError :: OpcodeNotSolvable ( OpcodeNotSolvable :: MissingAssignment ( 1 ) ) ,
949+ ) ;
950+ }
951+
952+ #[ test]
953+ fn test_recursive_aggregation_with_assigned_inputs ( ) {
954+ // With every operand assigned the opcode is treated as backend-owned and validation
955+ // succeeds without evaluating a constraint.
956+ let circuit = make_circuit ( vec ! [ recursive_aggregation_opcode( ) ] ) ;
957+
958+ let witness_map = WitnessMap :: from ( BTreeMap :: from_iter ( [
959+ ( Witness ( 1 ) , FieldElement :: zero ( ) ) ,
960+ ( Witness ( 2 ) , FieldElement :: zero ( ) ) ,
961+ ( Witness ( 3 ) , FieldElement :: zero ( ) ) ,
962+ ( Witness ( 4 ) , FieldElement :: zero ( ) ) ,
963+ ( Witness ( 5 ) , FieldElement :: one ( ) ) ,
964+ ] ) ) ;
965+
966+ let backend = Bn254BlackBoxSolver ;
967+ assert ! ( validate_witness( & backend, witness_map, & circuit) . is_ok( ) ) ;
968+ }
916969}
0 commit comments