Skip to content

Commit 5671681

Browse files
committed
fix(acvm): require recursive aggregation operands in validate_witness
1 parent 955209c commit 5671681

1 file changed

Lines changed: 55 additions & 2 deletions

File tree

acvm-repo/acvm/src/compiler/validator.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)