diff --git a/acir/src/circuit/black_box_functions.rs b/acir/src/circuit/black_box_functions.rs index f90c0a3c4..63cefd9cb 100644 --- a/acir/src/circuit/black_box_functions.rs +++ b/acir/src/circuit/black_box_functions.rs @@ -38,6 +38,9 @@ pub enum BlackBoxFunc { FixedBaseScalarMul, /// Calculates the Keccak256 hash of the inputs. Keccak256, + /// Compute a recursive aggregation object when verifying a proof inside another circuit. + /// This outputted aggregation object will then be either checked in a top-level verifier or aggregated upon again. + RecursiveAggregation, } impl std::fmt::Display for BlackBoxFunc { @@ -60,6 +63,7 @@ impl BlackBoxFunc { BlackBoxFunc::XOR => "xor", BlackBoxFunc::RANGE => "range", BlackBoxFunc::Keccak256 => "keccak256", + BlackBoxFunc::RecursiveAggregation => "recursive_aggregation", } } pub fn lookup(op_name: &str) -> Option { @@ -75,6 +79,7 @@ impl BlackBoxFunc { "xor" => Some(BlackBoxFunc::XOR), "range" => Some(BlackBoxFunc::RANGE), "keccak256" => Some(BlackBoxFunc::Keccak256), + "recursive_aggregation" => Some(BlackBoxFunc::RecursiveAggregation), _ => None, } } diff --git a/acir/src/circuit/opcodes/black_box_function_call.rs b/acir/src/circuit/opcodes/black_box_function_call.rs index b047bc36f..b7d10ad1f 100644 --- a/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/acir/src/circuit/opcodes/black_box_function_call.rs @@ -81,6 +81,29 @@ pub enum BlackBoxFuncCall { var_message_size: FunctionInput, outputs: Vec, }, + RecursiveAggregation { + verification_key: Vec, + proof: Vec, + /// These represent the public inputs of the proof we are verifying + /// They should be checked against in the circuit after construction + /// of a new aggregation state + public_inputs: Vec, + /// A key hash is used to check the validity of the verification key. + /// The circuit implementing this opcode can use this hash to ensure that the + /// key provided to the circuit matches the key produced by the circuit creator + key_hash: FunctionInput, + /// An aggregation object is blob of data that the top-level verifier must run some proof system specific + /// algorithm on to complete verification. The size is proof system specific and will be set by the backend integrating this opcode. + /// The input aggregation object is only not `None` when we are verifying a previous recursive aggregation in + /// the current circuit. If this is the first recursive aggregation there is no input aggregation object. + /// It is left to the backend to determine how to handle when there is no input aggregation object. + input_aggregation_object: Option>, + /// This is the result of a recursive aggregation and is what will be fed into the next verifier. + /// The next verifier can either perform a final verification (returning true or false) + /// or perform another recursive aggregation where this output aggregation object + /// will be the input aggregation object of the next recursive aggregation. + output_aggregation_object: Vec, + }, } impl BlackBoxFuncCall { @@ -126,6 +149,14 @@ impl BlackBoxFuncCall { BlackBoxFunc::Keccak256 => { BlackBoxFuncCall::Keccak256 { inputs: vec![], outputs: vec![] } } + BlackBoxFunc::RecursiveAggregation => BlackBoxFuncCall::RecursiveAggregation { + verification_key: vec![], + proof: vec![], + public_inputs: vec![], + key_hash: FunctionInput::dummy(), + input_aggregation_object: None, + output_aggregation_object: vec![], + }, } } @@ -143,6 +174,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, BlackBoxFuncCall::Keccak256 { .. } => BlackBoxFunc::Keccak256, BlackBoxFuncCall::Keccak256VariableLength { .. } => BlackBoxFunc::Keccak256, + BlackBoxFuncCall::RecursiveAggregation { .. } => BlackBoxFunc::RecursiveAggregation, } } @@ -200,6 +232,23 @@ impl BlackBoxFuncCall { inputs.push(*var_message_size); inputs } + BlackBoxFuncCall::RecursiveAggregation { + verification_key: key, + proof, + public_inputs, + key_hash, + .. + } => { + let mut inputs = Vec::new(); + inputs.extend(key.iter().copied()); + inputs.extend(proof.iter().copied()); + inputs.extend(public_inputs.iter().copied()); + inputs.push(*key_hash); + // NOTE: we do not return an input aggregation object as it will either be non-existent for the first recursive aggregation + // or the output aggregation object of a previous recursive aggregation. We do not simulate recursive aggregation + // thus the input aggregation object will always be unassigned until proving + inputs + } } } @@ -209,7 +258,10 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::Blake2s { outputs, .. } | BlackBoxFuncCall::FixedBaseScalarMul { outputs, .. } | BlackBoxFuncCall::Pedersen { outputs, .. } - | BlackBoxFuncCall::Keccak256 { outputs, .. } => outputs.to_vec(), + | BlackBoxFuncCall::Keccak256 { outputs, .. } + | BlackBoxFuncCall::RecursiveAggregation { + output_aggregation_object: outputs, .. + } => outputs.to_vec(), BlackBoxFuncCall::AND { output, .. } | BlackBoxFuncCall::XOR { output, .. } | BlackBoxFuncCall::HashToField128Security { output, .. } diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index d081b1d81..73baa9f5c 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -136,15 +136,23 @@ pub trait ProofSystemCompiler { /// Creates a Proof given the circuit description, the initial witness values, and the proving key /// It is important to note that the intermediate witnesses for black box functions will not generated /// This is the responsibility of the proof system. + /// + /// The `is_recursive` flag represents whether one wants to create proofs that are to be natively verified. + /// A proof system may use a certain hash type for the Fiat-Shamir normally that is not hash friendly (such as keccak to enable Solidity verification), + /// but may want to use a snark-friendly hash function when performing native verification. fn prove_with_pk( &self, common_reference_string: &[u8], circuit: &Circuit, witness_values: WitnessMap, proving_key: &[u8], + is_recursive: bool, ) -> Result, Self::Error>; /// Verifies a Proof, given the circuit description, the circuit's public inputs, and the verification key + /// + /// The `is_recursive` flag represents whether one wants to verify proofs that are to be natively verified. + /// The flag must match the `is_recursive` flag used to generate the proof passed into this method, otherwise verification will return false. fn verify_with_vk( &self, common_reference_string: &[u8], @@ -152,5 +160,22 @@ pub trait ProofSystemCompiler { public_inputs: WitnessMap, circuit: &Circuit, verification_key: &[u8], + is_recursive: bool, ) -> Result; + + /// When performing recursive aggregation in a circuit it is most efficient to use a proof formatted using a backend's native field. + /// This method is exposed to enable backends to integrate a native recursion format and optimize their recursive circuits. + fn proof_as_fields( + &self, + proof: &[u8], + public_inputs: WitnessMap, + ) -> Result, Self::Error>; + + /// When performing recursive aggregation in a circuit it is most efficient to use a verification key formatted using a backend's native field. + /// This method is exposed to enable backends to integrate a native recursion format and optimize their recursive circuits. + fn vk_as_fields( + &self, + common_reference_string: &[u8], + verification_key: &[u8], + ) -> Result<(Vec, FieldElement), Self::Error>; } diff --git a/acvm/src/pwg/blackbox.rs b/acvm/src/pwg/blackbox.rs index 05d301e43..d528e17da 100644 --- a/acvm/src/pwg/blackbox.rs +++ b/acvm/src/pwg/blackbox.rs @@ -99,5 +99,6 @@ pub(crate) fn solve( BlackBoxFuncCall::Keccak256VariableLength { inputs, var_message_size, outputs } => { keccak256_variable_length(initial_witness, inputs, *var_message_size, outputs) } + BlackBoxFuncCall::RecursiveAggregation { .. } => Ok(OpcodeResolution::Solved), } }