diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 83c019a3401..fa6ace85689 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -96,6 +96,19 @@ std::vector get_constraint_systems(std::string const& b return acir_format::program_buf_to_acir_format(bytecode); } +std::string proof_to_json(std::vector& proof) +{ + return format("[", join(map(proof, [](auto fr) { return format("\"", fr, "\""); })), "]"); +} + +std::string vk_to_json(std::vector& data) +{ + // We need to move vk_hash to the front... + std::rotate(data.begin(), data.end() - 1, data.end()); + + return format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); +} + /** * @brief Proves and Verifies an ACIR circuit * @@ -433,7 +446,7 @@ void proof_as_fields(const std::string& proof_path, std::string const& vk_path, auto acir_composer = verifier_init(); auto vk_data = from_buffer(read_file(vk_path)); auto data = acir_composer.serialize_proof_into_fields(read_file(proof_path), vk_data.num_public_inputs); - auto json = format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); + auto json = proof_to_json(data); if (output_path == "-") { writeStringToStdout(json); @@ -464,10 +477,7 @@ void vk_as_fields(const std::string& vk_path, const std::string& output_path) acir_composer.load_verification_key(std::move(vk_data)); auto data = acir_composer.serialize_verification_key_into_fields(); - // We need to move vk_hash to the front... - std::rotate(data.begin(), data.end() - 1, data.end()); - - auto json = format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); + auto json = vk_to_json(data); if (output_path == "-") { writeStringToStdout(json); vinfo("vk as fields written to stdout"); @@ -572,6 +582,58 @@ bool avm_verify(const std::filesystem::path& proof_path) return true; } +/** + * @brief Creates a proof for an ACIR circuit, outputs the proof and verification key in binary and 'field' format + * + * Communication: + * - Filesystem: The proof is written to the path specified by outputPath + * + * @param bytecodePath Path to the file containing the serialized circuit + * @param witnessPath Path to the file containing the serialized witness + * @param outputPath Directory into which we write the proof and verification key data + */ +void prove_output_all(const std::string& bytecodePath, const std::string& witnessPath, const std::string& outputPath) +{ + auto constraint_system = get_constraint_system(bytecodePath); + auto witness = get_witness(witnessPath); + + acir_proofs::AcirComposer acir_composer{ 0, verbose }; + acir_composer.create_circuit(constraint_system, witness); + init_bn254_crs(acir_composer.get_dyadic_circuit_size()); + acir_composer.init_proving_key(); + auto proof = acir_composer.create_proof(); + + // We have been given a directory, we will write the proof and verification key + // into the directory in both 'binary' and 'fields' formats + std::string vkOutputPath = outputPath + "/vk"; + std::string proofPath = outputPath + "/proof"; + std::string vkFieldsOutputPath = outputPath + "/vk_fields.json"; + std::string proofFieldsPath = outputPath + "/proof_fields.json"; + + std::shared_ptr vk = acir_composer.init_verification_key(); + + // Write the 'binary' proof + write_file(proofPath, proof); + vinfo("proof written to: ", proofPath); + + // Write the proof as fields + auto proofAsFields = acir_composer.serialize_proof_into_fields(proof, vk->as_data().num_public_inputs); + std::string proofJson = proof_to_json(proofAsFields); + write_file(proofFieldsPath, { proofJson.begin(), proofJson.end() }); + vinfo("proof as fields written to: ", proofFieldsPath); + + // Write the vk as binary + auto serialized_vk = to_buffer(*vk); + write_file(vkOutputPath, serialized_vk); + vinfo("vk written to: ", vkOutputPath); + + // Write the vk as fields + auto data = acir_composer.serialize_verification_key_into_fields(); + std::string vk_json = vk_to_json(data); + write_file(vkFieldsOutputPath, { vk_json.begin(), vk_json.end() }); + vinfo("vk as fields written to: ", vkFieldsOutputPath); +} + bool flag_present(std::vector& args, const std::string& flag) { return std::find(args.begin(), args.end(), flag) != args.end(); @@ -632,6 +694,9 @@ int main(int argc, char* argv[]) if (command == "prove") { std::string output_path = get_option(args, "-o", "./proofs/proof"); prove(bytecode_path, witness_path, output_path); + } else if (command == "prove_output_all") { + std::string output_path = get_option(args, "-o", "./proofs"); + prove_output_all(bytecode_path, witness_path, output_path); } else if (command == "gates") { gateCount(bytecode_path); } else if (command == "verify") { diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index e491271a5ca..d75ec0cc545 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -161,4 +161,7 @@ library Constants { uint256 internal constant LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP = 64; uint256 internal constant NUM_MSGS_PER_BASE_PARITY = 4; uint256 internal constant NUM_BASE_PARITY_PER_ROOT_PARITY = 4; + uint256 internal constant RECURSIVE_PROOF_LENGTH = 93; + uint256 internal constant NESTED_RECURSIVE_PROOF_LENGTH = 109; + uint256 internal constant VERIFICATION_KEY_LENGTH_IN_FIELDS = 114; } diff --git a/noir-projects/noir-protocol-circuits/crates/parity-base/src/main.nr b/noir-projects/noir-protocol-circuits/crates/parity-base/src/main.nr index 8fea34b8cf8..7c2f15d6f76 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-base/src/main.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-base/src/main.nr @@ -1,5 +1,6 @@ use dep::parity_lib::{BaseParityInputs, ParityPublicInputs}; -fn main(inputs: BaseParityInputs) -> pub ParityPublicInputs { +#[recursive] +fn main(inputs: BaseParityInputs) -> distinct pub ParityPublicInputs { inputs.base_parity_circuit() } diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/base/base_parity_inputs.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/base/base_parity_inputs.nr index f6d7fcfb9d4..71a9bcd52f4 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/base/base_parity_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/base/base_parity_inputs.nr @@ -1,8 +1,5 @@ use crate::{parity_public_inputs::ParityPublicInputs, utils::sha256_merkle_tree::Sha256MerkleTree}; -use dep::types::{ - constants::NUM_MSGS_PER_BASE_PARITY, merkle_tree::MerkleTree, mocked::AggregationObject, - utils::uint256::U256 -}; +use dep::types::{constants::NUM_MSGS_PER_BASE_PARITY, merkle_tree::MerkleTree, utils::uint256::U256}; struct BaseParityInputs { msgs: [Field; NUM_MSGS_PER_BASE_PARITY], @@ -13,11 +10,7 @@ impl BaseParityInputs { let sha_tree = Sha256MerkleTree::new(self.msgs); let pedersen_tree = MerkleTree::new(self.msgs); - ParityPublicInputs { - aggregation_object: AggregationObject {}, - sha_root: sha_tree.get_root(), - converted_root: pedersen_tree.get_root() - } + ParityPublicInputs { sha_root: sha_tree.get_root(), converted_root: pedersen_tree.get_root() } } } diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/lib.nr index a9e2e5d2cdc..4c2c07b1515 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/lib.nr @@ -6,4 +6,5 @@ mod utils; use crate::base::base_parity_inputs::BaseParityInputs; use crate::root::root_parity_input::RootParityInput; use crate::root::root_parity_inputs::RootParityInputs; +use crate::root::root_rollup_parity_input::RootRollupParityInput; use crate::parity_public_inputs::ParityPublicInputs; diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/parity_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/parity_public_inputs.nr index d30b7dc1935..7ad2f4647c6 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/parity_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/parity_public_inputs.nr @@ -1,17 +1,33 @@ -use dep::types::{mocked::AggregationObject, traits::Empty}; +use dep::types::{traits::{Empty, Serialize, Deserialize}}; struct ParityPublicInputs { - aggregation_object: AggregationObject, - sha_root: Field, - converted_root: Field, + sha_root: Field, + converted_root: Field, } impl Empty for ParityPublicInputs { fn empty() -> Self { - ParityPublicInputs { - aggregation_object: AggregationObject::empty(), - sha_root: 0, + ParityPublicInputs { + sha_root: 0, converted_root: 0, } } -} \ No newline at end of file +} + +impl Serialize<2> for ParityPublicInputs { + fn serialize(self) -> [Field; 2] { + let mut fields = [0; 2]; + fields[0] = self.sha_root; + fields[1] = self.converted_root; + fields + } +} + +impl Deserialize<2> for ParityPublicInputs { + fn deserialize(fields: [Field; 2]) -> Self { + ParityPublicInputs { + sha_root: fields[0], + converted_root: fields[1], + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root.nr index 09b3c1137ca..d8e5a1853bb 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root.nr @@ -1,2 +1,3 @@ mod root_parity_input; mod root_parity_inputs; +mod root_rollup_parity_input; diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_input.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_input.nr index 495a3a987b1..fc1090087d6 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_input.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_input.nr @@ -1,19 +1,18 @@ -use dep::types::{ - mocked::Proof, - traits::Empty -}; +use dep::types::{traits::Empty, recursion::{verification_key::VerificationKey, proof::RecursiveProof}}; use crate::parity_public_inputs::ParityPublicInputs; struct RootParityInput { - proof: Proof, + proof: RecursiveProof, + verification_key: VerificationKey, public_inputs: ParityPublicInputs, } impl Empty for RootParityInput { fn empty() -> Self { RootParityInput { - proof: Proof::empty(), + proof: RecursiveProof::empty(), + verification_key: VerificationKey::empty(), public_inputs: ParityPublicInputs::empty(), } } -} \ No newline at end of file +} diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_inputs.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_inputs.nr index ad7c3d6b990..8ccade7ccb3 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_parity_inputs.nr @@ -1,8 +1,12 @@ -use dep::types::{merkle_tree::MerkleTree, mocked::AggregationObject}; +use dep::types::{ + merkle_tree::MerkleTree, recursion::{verification_key::VerificationKey, proof::RecursiveProof}, + traits::Serialize +}; use crate::{ parity_public_inputs::ParityPublicInputs, root::root_parity_input::RootParityInput, utils::sha256_merkle_tree::Sha256MerkleTree }; +use dep::std; global NUM_BASE_PARITY_PER_ROOT_PARITY: u64 = 4; @@ -12,7 +16,7 @@ struct RootParityInputs { impl RootParityInputs { pub fn root_parity_circuit(self) -> ParityPublicInputs { - // TODO: verify proofs of inputs.children + self.verify_child_proofs(); let mut sha_roots = [0; NUM_BASE_PARITY_PER_ROOT_PARITY]; let mut converted_roots = [0; NUM_BASE_PARITY_PER_ROOT_PARITY]; @@ -24,10 +28,24 @@ impl RootParityInputs { let sha_tree = Sha256MerkleTree::new(sha_roots); let pedersen_tree = MerkleTree::new(converted_roots); - ParityPublicInputs { - aggregation_object: AggregationObject {}, - sha_root: sha_tree.get_root(), - converted_root: pedersen_tree.get_root() + ParityPublicInputs { sha_root: sha_tree.get_root(), converted_root: pedersen_tree.get_root() } + } + + fn verify_child_proofs(self) { + //TODO(@PhilWindle): Validate all keys against the actual BASE_PARITY_CIRCUIT_VK_HASH + //assert(self.children[0].verification_key.hash == BASE_PARITY_CIRCUIT_VK_HASH); + for i in 0..NUM_BASE_PARITY_PER_ROOT_PARITY { + assert( + self.children[i].verification_key.hash == self.children[0].verification_key.hash, "Inconsistent vk hashes across base parity circuits" + ); + //TODO: Do we need to validate this following hash + //assert(hash(self.children[i].verification_key) == self.children[i].verification_key.hash); + std::verify_proof( + self.children[i].verification_key.key.as_slice(), + self.children[i].proof.fields.as_slice(), + ParityPublicInputs::serialize(self.children[i].public_inputs).as_slice(), + self.children[i].verification_key.hash + ); } } } @@ -37,10 +55,9 @@ mod tests { parity_public_inputs::ParityPublicInputs, root::{root_parity_input::RootParityInput, root_parity_inputs::RootParityInputs} }; - use dep::types::mocked::{AggregationObject, Proof}; + use dep::types::recursion::{verification_key::VerificationKey, proof::RecursiveProof}; - #[test] - fn test_sha_root_matches_frontier_tree() { + fn test_setup() -> [RootParityInput; 4] { // 31 byte test SHA roots let children_sha_roots = [ 0xb3a3fc1968999f2c2d798b900bdf0de41311be2a4d20496a7e792a521fc8ab, @@ -49,25 +66,85 @@ mod tests { 0x53042d820859d80c474d4694e03778f8dc0ac88fc1c3a97b4369c1096e904a ]; + let mut vk1 = VerificationKey::empty(); + vk1.hash = 0x43f78e0ebc9633ce336a8c086064d898c32fb5d7d6011f5427459c0b8d14e9; + let children = [ RootParityInput { - proof: Proof {}, - public_inputs: ParityPublicInputs { aggregation_object: AggregationObject {}, sha_root: children_sha_roots[0], converted_root: 0 } + proof: RecursiveProof::empty(), + verification_key: vk1, + public_inputs: ParityPublicInputs { sha_root: children_sha_roots[0], converted_root: 0 } }, RootParityInput { - proof: Proof {}, - public_inputs: ParityPublicInputs { aggregation_object: AggregationObject {}, sha_root: children_sha_roots[1], converted_root: 0 } + proof: RecursiveProof::empty(), + verification_key: vk1, + public_inputs: ParityPublicInputs { sha_root: children_sha_roots[1], converted_root: 0 } }, RootParityInput { - proof: Proof {}, - public_inputs: ParityPublicInputs { aggregation_object: AggregationObject {}, sha_root: children_sha_roots[2], converted_root: 0 } + proof: RecursiveProof::empty(), + verification_key: vk1, + public_inputs: ParityPublicInputs { sha_root: children_sha_roots[2], converted_root: 0 } }, RootParityInput { - proof: Proof {}, - public_inputs: ParityPublicInputs { aggregation_object: AggregationObject {}, sha_root: children_sha_roots[3], converted_root: 0 } + proof: RecursiveProof::empty(), + verification_key: vk1, + public_inputs: ParityPublicInputs { sha_root: children_sha_roots[3], converted_root: 0 } } ]; + children + } + + #[test] + fn test_sha_root_matches_frontier_tree() { + let children = test_setup(); + let root_parity_inputs = RootParityInputs { children }; + + let public_inputs = root_parity_inputs.root_parity_circuit(); + + // 31 byte truncated root hash + let expected_sha_root = 0xa0c56543aa73140e5ca27231eee3107bd4e11d62164feb411d77c9d9b2da47; + + assert(public_inputs.sha_root == expected_sha_root, "sha root does not match"); + } + + #[test(should_fail_with = "Inconsistent vk hashes across base parity circuits")] + fn test_asserts_incorrect_vk_hash_1() { + let mut vk2 = VerificationKey::empty(); + vk2.hash = 0x53042d820859d80c474d4694e03778f8dc0ac88fc1c3a97b4369c1096e904a; + let mut children = test_setup(); + children[1].verification_key = vk2; + let root_parity_inputs = RootParityInputs { children }; + + let public_inputs = root_parity_inputs.root_parity_circuit(); + + // 31 byte truncated root hash + let expected_sha_root = 0xa0c56543aa73140e5ca27231eee3107bd4e11d62164feb411d77c9d9b2da47; + + assert(public_inputs.sha_root == expected_sha_root, "sha root does not match"); + } + + #[test(should_fail_with = "Inconsistent vk hashes across base parity circuits")] + fn test_asserts_incorrect_vk_hash_2() { + let mut vk2 = VerificationKey::empty(); + vk2.hash = 0x53042d820859d80c474d4694e03778f8dc0ac88fc1c3a97b4369c1096e904a; + let mut children = test_setup(); + children[2].verification_key = vk2; + let root_parity_inputs = RootParityInputs { children }; + + let public_inputs = root_parity_inputs.root_parity_circuit(); + + // 31 byte truncated root hash + let expected_sha_root = 0xa0c56543aa73140e5ca27231eee3107bd4e11d62164feb411d77c9d9b2da47; + + assert(public_inputs.sha_root == expected_sha_root, "sha root does not match"); + } + #[test(should_fail_with = "Inconsistent vk hashes across base parity circuits")] + fn test_asserts_incorrect_vk_hash_3() { + let mut vk2 = VerificationKey::empty(); + vk2.hash = 0x53042d820859d80c474d4694e03778f8dc0ac88fc1c3a97b4369c1096e904a; + let mut children = test_setup(); + children[3].verification_key = vk2; let root_parity_inputs = RootParityInputs { children }; let public_inputs = root_parity_inputs.root_parity_circuit(); diff --git a/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_rollup_parity_input.nr b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_rollup_parity_input.nr new file mode 100644 index 00000000000..ce0ec0f9dc0 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/parity-lib/src/root/root_rollup_parity_input.nr @@ -0,0 +1,18 @@ +use dep::types::{traits::Empty, recursion::{verification_key::VerificationKey, proof::NestedRecursiveProof}}; +use crate::parity_public_inputs::ParityPublicInputs; + +struct RootRollupParityInput { + proof: NestedRecursiveProof, + verification_key: VerificationKey, + public_inputs: ParityPublicInputs, +} + +impl Empty for RootRollupParityInput { + fn empty() -> Self { + RootRollupParityInput { + proof: NestedRecursiveProof::empty(), + verification_key: VerificationKey::empty(), + public_inputs: ParityPublicInputs::empty(), + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/parity-root/src/main.nr b/noir-projects/noir-protocol-circuits/crates/parity-root/src/main.nr index 11125777b66..b7bae47e468 100644 --- a/noir-projects/noir-protocol-circuits/crates/parity-root/src/main.nr +++ b/noir-projects/noir-protocol-circuits/crates/parity-root/src/main.nr @@ -1,5 +1,6 @@ use dep::parity_lib::{RootParityInputs, ParityPublicInputs}; -fn main(inputs: RootParityInputs) -> pub ParityPublicInputs { +#[recursive] +fn main(inputs: RootParityInputs) -> distinct pub ParityPublicInputs { inputs.root_parity_circuit() } diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr index 8fc8e60a70e..795e9507008 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr @@ -3,7 +3,7 @@ use crate::{ components, root::{root_rollup_public_inputs::RootRollupPublicInputs} }; use dep::{ - parity_lib::RootParityInput, + std, parity_lib::{root::root_rollup_parity_input::RootRollupParityInput, ParityPublicInputs}, types::{ abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, nullifier_leaf_preimage::NullifierLeafPreimage}, constants::{ @@ -12,8 +12,7 @@ use dep::{ }, header::Header, content_commitment::ContentCommitment, merkle_tree::{append_only_tree, calculate_subtree_root, calculate_empty_tree_root}, - state_reference::StateReference, - traits::Empty, + state_reference::StateReference, traits::Empty } }; @@ -21,7 +20,7 @@ struct RootRollupInputs { // All below are shared between the base and merge rollups previous_rollup_data : [PreviousRollupData; 2], - l1_to_l2_roots: RootParityInput, + l1_to_l2_roots: RootRollupParityInput, // inputs required to process l1 to l2 messages new_l1_to_l2_messages : [Field; NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP], @@ -85,15 +84,28 @@ impl RootRollupInputs { 0 ); + self.verify_root_parity(); + RootRollupPublicInputs { aggregation_object, archive, header } } + + fn verify_root_parity(self) { + // TODO: Verify that self.l1_to_l2_roots.verification_key.hash is the hash of the RootParityCircuit + //assert(self.l1_to_l2_roots.verification_key.hash == ROOT_PARITY_CIRCUIT_HASH) + std::verify_proof( + self.l1_to_l2_roots.verification_key.key.as_slice(), + self.l1_to_l2_roots.proof.fields.as_slice(), + ParityPublicInputs::serialize(self.l1_to_l2_roots.public_inputs).as_slice(), + self.l1_to_l2_roots.verification_key.hash + ); + } } impl Empty for RootRollupInputs { fn empty() -> Self { RootRollupInputs { previous_rollup_data : [PreviousRollupData::empty(); 2], - l1_to_l2_roots: RootParityInput::empty(), + l1_to_l2_roots: RootRollupParityInput::empty(), new_l1_to_l2_messages : [0; NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP], new_l1_to_l2_message_tree_root_sibling_path : [0; L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH], start_l1_to_l2_message_tree_snapshot : AppendOnlyTreeSnapshot::zero(), @@ -101,4 +113,4 @@ impl Empty for RootRollupInputs { new_archive_sibling_path : [0; ARCHIVE_HEIGHT], } } -} \ No newline at end of file +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 9f454eaeefe..1cc4dfdca85 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -183,6 +183,12 @@ global NUM_MSGS_PER_BASE_PARITY: u64 = 4; // FIX: Sadly, writing this as above causes a type error in type_conversion.ts. global NUM_BASE_PARITY_PER_ROOT_PARITY: u64 = 4; +// Lengths of the different types of proofs in fields +global RECURSIVE_PROOF_LENGTH = 93; +global NESTED_RECURSIVE_PROOF_LENGTH = 109; + +global VERIFICATION_KEY_LENGTH_IN_FIELDS = 114; + /** * Enumerate the hash_indices which are used for pedersen hashing. * We start from 1 to avoid the default generators. The generator indices are listed diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr index 5530e831352..fc8dbbbc449 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr @@ -31,3 +31,5 @@ mod public_data_tree_leaf; mod public_data_tree_leaf_preimage; use abis::kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}; + +mod recursion; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/recursion.nr b/noir-projects/noir-protocol-circuits/crates/types/src/recursion.nr new file mode 100644 index 00000000000..f2e54c0a05b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/recursion.nr @@ -0,0 +1,2 @@ +mod verification_key; +mod proof; \ No newline at end of file diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/recursion/proof.nr b/noir-projects/noir-protocol-circuits/crates/types/src/recursion/proof.nr new file mode 100644 index 00000000000..fd30f4469bf --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/recursion/proof.nr @@ -0,0 +1,52 @@ +use crate::{ + traits::{Serialize, Deserialize, Empty}, + constants::{RECURSIVE_PROOF_LENGTH, NESTED_RECURSIVE_PROOF_LENGTH} +}; + +struct RecursiveProof { + fields: [Field; RECURSIVE_PROOF_LENGTH], +} + +impl Serialize for RecursiveProof { + fn serialize(self) -> [Field; RECURSIVE_PROOF_LENGTH] { + self.fields + } +} + +impl Deserialize for RecursiveProof { + fn deserialize(fields: [Field; RECURSIVE_PROOF_LENGTH]) -> Self { + RecursiveProof { fields } + } +} + +impl Empty for RecursiveProof { + fn empty() -> Self { + RecursiveProof { + fields: [0; RECURSIVE_PROOF_LENGTH], + } + } +} + +struct NestedRecursiveProof { + fields: [Field; NESTED_RECURSIVE_PROOF_LENGTH], +} + +impl Serialize for NestedRecursiveProof { + fn serialize(self) -> [Field; NESTED_RECURSIVE_PROOF_LENGTH] { + self.fields + } +} + +impl Deserialize for NestedRecursiveProof { + fn deserialize(fields: [Field; NESTED_RECURSIVE_PROOF_LENGTH]) -> Self { + NestedRecursiveProof { fields } + } +} + +impl Empty for NestedRecursiveProof { + fn empty() -> Self { + NestedRecursiveProof { + fields: [0; NESTED_RECURSIVE_PROOF_LENGTH], + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/recursion/verification_key.nr b/noir-projects/noir-protocol-circuits/crates/types/src/recursion/verification_key.nr new file mode 100644 index 00000000000..3412fc4294b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/recursion/verification_key.nr @@ -0,0 +1,43 @@ + +use crate::{ + traits::{Serialize, Deserialize, Empty}, + constants::{ VERIFICATION_KEY_LENGTH_IN_FIELDS }, +}; + +global SERIALIZED_VERIFICATION_KEY_LENGTH = VERIFICATION_KEY_LENGTH_IN_FIELDS + 1; + +struct VerificationKey { + key: [Field; VERIFICATION_KEY_LENGTH_IN_FIELDS], + hash: Field, +} + +impl Serialize for VerificationKey { + fn serialize(self) -> [Field; SERIALIZED_VERIFICATION_KEY_LENGTH] { + let mut fields = [0; SERIALIZED_VERIFICATION_KEY_LENGTH]; + for i in 0..VERIFICATION_KEY_LENGTH_IN_FIELDS { + fields[i] = self.key[i]; + } + fields[VERIFICATION_KEY_LENGTH_IN_FIELDS] = self.hash; + fields + } +} + +impl Deserialize for VerificationKey { + fn deserialize(fields: [Field; SERIALIZED_VERIFICATION_KEY_LENGTH]) -> Self { + let mut key = VerificationKey::empty(); + for i in 0..VERIFICATION_KEY_LENGTH_IN_FIELDS { + key.key[i] = fields[i]; + } + key.hash = fields[VERIFICATION_KEY_LENGTH_IN_FIELDS]; + key + } +} + +impl Empty for VerificationKey { + fn empty() -> Self { + VerificationKey { + hash: 0, + key: [0; VERIFICATION_KEY_LENGTH_IN_FIELDS], + } + } +} diff --git a/yarn-project/circuit-types/src/interfaces/proving-job.ts b/yarn-project/circuit-types/src/interfaces/proving-job.ts index 503de6bd820..94c41afeb6f 100644 --- a/yarn-project/circuit-types/src/interfaces/proving-job.ts +++ b/yarn-project/circuit-types/src/interfaces/proving-job.ts @@ -4,9 +4,11 @@ import { type BaseRollupInputs, type KernelCircuitPublicInputs, type MergeRollupInputs, - type ParityPublicInputs, + type NESTED_RECURSIVE_PROOF_LENGTH, type Proof, type PublicKernelCircuitPublicInputs, + type RECURSIVE_PROOF_LENGTH, + type RootParityInput, type RootParityInputs, type RootRollupInputs, type RootRollupPublicInputs, @@ -14,6 +16,19 @@ import { import type { PublicKernelNonTailRequest, PublicKernelTailRequest } from '../tx/processed_tx.js'; +export type PublicInputsAndProof = { + inputs: T; + proof: Proof; +}; + +export function makePublicInputsAndProof(inputs: T, proof: Proof) { + const result: PublicInputsAndProof = { + inputs, + proof, + }; + return result; +} + export type ProvingJob = { id: string; request: T; @@ -71,20 +86,20 @@ export type ProvingRequest = }; export type ProvingRequestPublicInputs = { - [ProvingRequestType.PUBLIC_VM]: object; + [ProvingRequestType.PUBLIC_VM]: PublicInputsAndProof; - [ProvingRequestType.PUBLIC_KERNEL_NON_TAIL]: PublicKernelCircuitPublicInputs; - [ProvingRequestType.PUBLIC_KERNEL_TAIL]: KernelCircuitPublicInputs; + [ProvingRequestType.PUBLIC_KERNEL_NON_TAIL]: PublicInputsAndProof; + [ProvingRequestType.PUBLIC_KERNEL_TAIL]: PublicInputsAndProof; - [ProvingRequestType.BASE_ROLLUP]: BaseOrMergeRollupPublicInputs; - [ProvingRequestType.MERGE_ROLLUP]: BaseOrMergeRollupPublicInputs; - [ProvingRequestType.ROOT_ROLLUP]: RootRollupPublicInputs; + [ProvingRequestType.BASE_ROLLUP]: PublicInputsAndProof; + [ProvingRequestType.MERGE_ROLLUP]: PublicInputsAndProof; + [ProvingRequestType.ROOT_ROLLUP]: PublicInputsAndProof; - [ProvingRequestType.BASE_PARITY]: ParityPublicInputs; - [ProvingRequestType.ROOT_PARITY]: ParityPublicInputs; + [ProvingRequestType.BASE_PARITY]: RootParityInput; + [ProvingRequestType.ROOT_PARITY]: RootParityInput; }; -export type ProvingRequestResult = [ProvingRequestPublicInputs[T], Proof]; +export type ProvingRequestResult = ProvingRequestPublicInputs[T]; export interface ProvingJobSource { getProvingJob(): Promise | null>; diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index bdac9c533f0..096d8663dae 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -159,6 +159,9 @@ export const L2_TO_L1_MSGS_NUM_BYTES_PER_BASE_ROLLUP = 64; export const LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP = 64; export const NUM_MSGS_PER_BASE_PARITY = 4; export const NUM_BASE_PARITY_PER_ROOT_PARITY = 4; +export const RECURSIVE_PROOF_LENGTH = 93; +export const NESTED_RECURSIVE_PROOF_LENGTH = 109; +export const VERIFICATION_KEY_LENGTH_IN_FIELDS = 114; export enum GeneratorIndex { NOTE_HASH = 1, NOTE_HASH_NONCE = 2, diff --git a/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap b/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap index c257afb20c6..7ccffcf9bec 100644 --- a/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap +++ b/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap @@ -20,8 +20,6 @@ exports[`hash computes siloed nullifier 1`] = `Fr<0x1743145fde103eaa88af576e0562 exports[`hash computes unique commitment 1`] = `Fr<0x1cbdcecec4fe92f6638eb6a8dade96ca358ecba4954cf597c363199fae3d47e8>`; -exports[`hash hashes VK 1`] = `Buffer<0x048110667f80b02f77b7d744976657cea9a7c5f1dd2340ea1c579a7ebfd54e55>`; - exports[`hash hashes empty function args 1`] = `Fr<0x0000000000000000000000000000000000000000000000000000000000000000>`; exports[`hash hashes function args 1`] = `Fr<0x1a76e9750a1493d95ce48be1fa31831fc370d7e68f563fe5c781c6f58e1f1eac>`; diff --git a/yarn-project/circuits.js/src/hash/hash.test.ts b/yarn-project/circuits.js/src/hash/hash.test.ts index 477990a388b..57abd049674 100644 --- a/yarn-project/circuits.js/src/hash/hash.test.ts +++ b/yarn-project/circuits.js/src/hash/hash.test.ts @@ -2,7 +2,7 @@ import { times } from '@aztec/foundation/collection'; import { setupCustomSnapshotSerializers } from '@aztec/foundation/testing'; import { AztecAddress, Fr, SideEffect, SideEffectLinkedToNoteHash } from '../index.js'; -import { makeAztecAddress, makeVerificationKey } from '../tests/factories.js'; +import { makeAztecAddress } from '../tests/factories.js'; import { computeCommitmentNonce, computeCommitmentsHash, @@ -12,18 +12,12 @@ import { computeSecretHash, computeUniqueNoteHash, computeVarArgsHash, - hashVK, siloNoteHash, siloNullifier, } from './hash.js'; describe('hash', () => { setupCustomSnapshotSerializers(expect); - it('hashes VK', () => { - const vk = makeVerificationKey(); - const res = hashVK(vk.toBuffer()); - expect(res).toMatchSnapshot(); - }); it('computes commitment nonce', () => { const nullifierZero = new Fr(123n); diff --git a/yarn-project/circuits.js/src/hash/hash.ts b/yarn-project/circuits.js/src/hash/hash.ts index 7f3d489b7ba..f2a82eac89b 100644 --- a/yarn-project/circuits.js/src/hash/hash.ts +++ b/yarn-project/circuits.js/src/hash/hash.ts @@ -5,12 +5,10 @@ import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { numToUInt8, numToUInt16BE, numToUInt32BE } from '@aztec/foundation/serialize'; -import { Buffer } from 'buffer'; import chunk from 'lodash.chunk'; import { ARGS_HASH_CHUNK_COUNT, ARGS_HASH_CHUNK_LENGTH, GeneratorIndex } from '../constants.gen.js'; -import type { SideEffect, SideEffectLinkedToNoteHash } from '../structs/index.js'; -import { VerificationKey } from '../structs/verification_key.js'; +import { type SideEffect, type SideEffectLinkedToNoteHash, VerificationKey } from '../structs/index.js'; /** * Computes a hash of a given verification key. @@ -31,27 +29,6 @@ export function hashVK(vkBuf: Buffer) { Buffer.from('1418144d5b080fcac24cdb7649bdadf246a6cb2426e324bedb94fb05118f023a', 'hex'), ]); return pedersenHashBuffer(toHash); - // barretenberg::evaluation_domain eval_domain = barretenberg::evaluation_domain(circuit_size); - - // std::vector preimage_data; - - // preimage_data.push_back(static_cast(proof_system::CircuitType(circuit_type))); - - // const uint256_t domain = eval_domain.domain; // montgomery form of circuit_size - // const uint256_t generator = eval_domain.generator; //coset_generator(0) - // const uint256_t public_inputs = num_public_inputs; - - // write(preimage_data, static_cast(uint256_t(generator))); // maybe 1? - // write(preimage_data, static_cast(uint256_t(domain))); // try circuit_size - // write(preimage_data, static_cast(public_inputs)); - // for (const auto& [tag, selector] : commitments) { - // write(preimage_data, selector.y); - // write(preimage_data, selector.x); - // } - - // write(preimage_data, eval_domain.root); // fr::one() - - // return crypto::pedersen_hash::hash_buffer(preimage_data, hash_index); } /** diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 0daa44c581a..5f8869bb5f5 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -55,6 +55,7 @@ export * from './public_data_read_request_hints.js'; export * from './public_data_update_request.js'; export * from './read_request.js'; export * from './read_request_hints.js'; +export * from './recursive_proof.js'; export * from './revert_code.js'; export * from './rollup/append_only_tree_snapshot.js'; export * from './rollup/base_or_merge_rollup_public_inputs.js'; diff --git a/yarn-project/circuits.js/src/structs/parity/parity_public_inputs.ts b/yarn-project/circuits.js/src/structs/parity/parity_public_inputs.ts index e020019822a..f160be71bf5 100644 --- a/yarn-project/circuits.js/src/structs/parity/parity_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/parity/parity_public_inputs.ts @@ -2,12 +2,8 @@ import { Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; -import { AggregationObject } from '../aggregation_object.js'; - export class ParityPublicInputs { constructor( - /** Aggregated proof of all the parity circuit iterations. */ - public aggregationObject: AggregationObject, /** Root of the SHA256 tree. */ public shaRoot: Fr, /** Root of the converted tree. */ @@ -49,7 +45,7 @@ export class ParityPublicInputs { * @returns The instance fields. */ static getFields(fields: FieldsOf) { - return [fields.aggregationObject, fields.shaRoot, fields.convertedRoot] as const; + return [fields.shaRoot, fields.convertedRoot] as const; } /** @@ -59,7 +55,7 @@ export class ParityPublicInputs { */ static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); - return new ParityPublicInputs(reader.readObject(AggregationObject), reader.readObject(Fr), reader.readObject(Fr)); + return new ParityPublicInputs(reader.readObject(Fr), reader.readObject(Fr)); } /** diff --git a/yarn-project/circuits.js/src/structs/parity/root_parity_input.test.ts b/yarn-project/circuits.js/src/structs/parity/root_parity_input.test.ts index 1dd76321e0c..976cbf4d198 100644 --- a/yarn-project/circuits.js/src/structs/parity/root_parity_input.test.ts +++ b/yarn-project/circuits.js/src/structs/parity/root_parity_input.test.ts @@ -1,11 +1,19 @@ +import { NESTED_RECURSIVE_PROOF_LENGTH, RECURSIVE_PROOF_LENGTH } from '../../constants.gen.js'; import { makeRootParityInput } from '../../tests/factories.js'; import { RootParityInput } from './root_parity_input.js'; describe('RootParityInput', () => { - it(`serializes a RootParityInput to buffer and deserializes it back`, () => { - const expected = makeRootParityInput(); + it(`serializes a recursive proof RootParityInput to buffer and deserializes it back`, () => { + const expected = makeRootParityInput(RECURSIVE_PROOF_LENGTH); const buffer = expected.toBuffer(); - const res = RootParityInput.fromBuffer(buffer); + const res = RootParityInput.fromBuffer(buffer, RECURSIVE_PROOF_LENGTH); + expect(res).toEqual(expected); + }); + + it(`serializes a nested recursive proof RootParityInput to buffer and deserializes it back`, () => { + const expected = makeRootParityInput(NESTED_RECURSIVE_PROOF_LENGTH); + const buffer = expected.toBuffer(); + const res = RootParityInput.fromBuffer(buffer, NESTED_RECURSIVE_PROOF_LENGTH); expect(res).toEqual(expected); }); }); diff --git a/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts b/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts index cab611415d6..bcdafe5aa2b 100644 --- a/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts +++ b/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts @@ -1,13 +1,16 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; -import { Proof } from '../proof.js'; +import { RecursiveProof } from '../recursive_proof.js'; +import { VerificationKeyAsFields } from '../verification_key.js'; import { ParityPublicInputs } from './parity_public_inputs.js'; -export class RootParityInput { +export class RootParityInput { constructor( /** The proof of the execution of the parity circuit. */ - public readonly proof: Proof, + public readonly proof: RecursiveProof, + /** The circuit's verification key */ + public readonly verificationKey: VerificationKeyAsFields, /** The public inputs of the parity circuit. */ public readonly publicInputs: ParityPublicInputs, ) {} @@ -16,16 +19,22 @@ export class RootParityInput { return serializeToBuffer(...RootParityInput.getFields(this)); } - static from(fields: FieldsOf): RootParityInput { + static from( + fields: FieldsOf>, + ): RootParityInput { return new RootParityInput(...RootParityInput.getFields(fields)); } - static getFields(fields: FieldsOf) { - return [fields.proof, fields.publicInputs] as const; + static getFields(fields: FieldsOf>) { + return [fields.proof, fields.verificationKey, fields.publicInputs] as const; } - static fromBuffer(buffer: Buffer | BufferReader) { + static fromBuffer(buffer: Buffer | BufferReader, size: PROOF_LENGTH) { const reader = BufferReader.asReader(buffer); - return new RootParityInput(reader.readObject(Proof), reader.readObject(ParityPublicInputs)); + return new RootParityInput( + RecursiveProof.fromBuffer(reader, size), + reader.readObject(VerificationKeyAsFields), + reader.readObject(ParityPublicInputs), + ); } } diff --git a/yarn-project/circuits.js/src/structs/parity/root_parity_inputs.ts b/yarn-project/circuits.js/src/structs/parity/root_parity_inputs.ts index fec8aa4ebbc..8dc2928f9c4 100644 --- a/yarn-project/circuits.js/src/structs/parity/root_parity_inputs.ts +++ b/yarn-project/circuits.js/src/structs/parity/root_parity_inputs.ts @@ -1,12 +1,15 @@ import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; -import { NUM_BASE_PARITY_PER_ROOT_PARITY } from '../../constants.gen.js'; +import { NUM_BASE_PARITY_PER_ROOT_PARITY, RECURSIVE_PROOF_LENGTH } from '../../constants.gen.js'; import { RootParityInput } from './root_parity_input.js'; export class RootParityInputs { constructor( /** Public inputs of children and their proofs. */ - public readonly children: Tuple, + public readonly children: Tuple< + RootParityInput, + typeof NUM_BASE_PARITY_PER_ROOT_PARITY + >, ) {} /** @@ -14,7 +17,7 @@ export class RootParityInputs { * @returns The inputs serialized to a buffer. */ toBuffer() { - return serializeToBuffer(this.children); + return serializeToBuffer(...this.children); } /** @@ -32,7 +35,12 @@ export class RootParityInputs { */ static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); - return new RootParityInputs(reader.readArray(NUM_BASE_PARITY_PER_ROOT_PARITY, RootParityInput)); + const tuple = Array.from({ length: NUM_BASE_PARITY_PER_ROOT_PARITY }, () => + RootParityInput.fromBuffer(reader, RECURSIVE_PROOF_LENGTH), + ); + return new RootParityInputs( + tuple as Tuple, typeof NUM_BASE_PARITY_PER_ROOT_PARITY>, + ); } /** diff --git a/yarn-project/circuits.js/src/structs/recursive_proof.ts b/yarn-project/circuits.js/src/structs/recursive_proof.ts new file mode 100644 index 00000000000..ada30995616 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/recursive_proof.ts @@ -0,0 +1,81 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; + +import { Proof, makeEmptyProof } from './proof.js'; + +/** + * The Recursive proof class is a wrapper around the circuit's proof. + * We store the proof in 2 forms for convenience. The first is in the 'fields' format. + * This is a list of fields, for which there are distinct lengths based on the level of recursion. + * This 'fields' version does not contain the circuits public inputs + * We also store the raw binary proof which van be directly verified. + */ +export class RecursiveProof { + constructor( + /** + * Holds the serialized proof data in an array of fields, this is without the public inputs + */ + public proof: Tuple, + + /** + * Holds the serialized proof data in a binary buffer, this contains the public inputs + */ + public binaryProof: Proof, + ) {} + + /** + * Create a Proof from a Buffer or BufferReader. + * Expects a length-encoding. + * + * @param buffer - A Buffer or BufferReader containing the length-encoded proof data. + * @returns A Proof instance containing the decoded proof data. + */ + static fromBuffer(buffer: Buffer | BufferReader, size: N): RecursiveProof { + const reader = BufferReader.asReader(buffer); + return new RecursiveProof(reader.readArray(size, Fr), Proof.fromBuffer(reader)); + } + + /** + * Convert the Proof instance to a custom Buffer format. + * This function serializes the Proof's buffer length and data sequentially into a new Buffer. + * + * @returns A Buffer containing the serialized proof data in custom format. + */ + public toBuffer() { + return serializeToBuffer(this.proof, this.binaryProof); + } + + /** + * Serialize the Proof instance to a hex string. + * @returns The hex string representation of the proof data. + */ + public toString() { + return this.toBuffer().toString('hex'); + } + + /** + * Deserialize a Proof instance from a hex string. + * @param str - A hex string to deserialize from. + * @returns - A new Proof instance. + */ + static fromString(str: string, size: N) { + return RecursiveProof.fromBuffer(Buffer.from(str, 'hex'), size); + } +} + +/** + * Makes an empty proof. + * Note: Used for local devnet milestone where we are not proving anything yet. + * @returns The empty "proof". + */ +export function makeEmptyRecursiveProof(size: N) { + return new RecursiveProof(makeTuple(size, Fr.zero), makeEmptyProof()); +} + +export function makeRecursiveProof(size: PROOF_LENGTH, seed = 1) { + return new RecursiveProof( + makeTuple(size, (i: number) => new Fr(i), seed), + makeEmptyProof(), + ); +} diff --git a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts index 1f4c5d8f61a..3a1af63c040 100644 --- a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts @@ -5,6 +5,7 @@ import { type FieldsOf } from '@aztec/foundation/types'; import { ARCHIVE_HEIGHT, L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, + NESTED_RECURSIVE_PROOF_LENGTH, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, } from '../../constants.gen.js'; import { AggregationObject } from '../aggregation_object.js'; @@ -27,7 +28,7 @@ export class RootRollupInputs { /** * The original and converted roots of the L1 to L2 messages subtrees. */ - public l1ToL2Roots: RootParityInput, + public l1ToL2Roots: RootParityInput, /** * New L1 to L2 messages. */ @@ -101,7 +102,7 @@ export class RootRollupInputs { const reader = BufferReader.asReader(buffer); return new RootRollupInputs( [reader.readObject(PreviousRollupData), reader.readObject(PreviousRollupData)], - reader.readObject(RootParityInput), + RootParityInput.fromBuffer(reader, NESTED_RECURSIVE_PROOF_LENGTH), reader.readArray(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, Fr), reader.readArray(L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, Fr), reader.readObject(AppendOnlyTreeSnapshot), diff --git a/yarn-project/circuits.js/src/structs/verification_key.test.ts b/yarn-project/circuits.js/src/structs/verification_key.test.ts index 7dd17b375dc..dd273dab169 100644 --- a/yarn-project/circuits.js/src/structs/verification_key.test.ts +++ b/yarn-project/circuits.js/src/structs/verification_key.test.ts @@ -1,7 +1,16 @@ -import { VerificationKey } from './verification_key.js'; +import { VerificationKey, VerificationKeyAsFields } from './verification_key.js'; + +describe('structs/verification_key_as_fields', () => { + it(`can serialise and deserialise a verification key as fields`, () => { + const vk = VerificationKeyAsFields.makeFake(); + const serialised = vk.toBuffer(); + const deserialised = VerificationKeyAsFields.fromBuffer(serialised); + expect(vk).toEqual(deserialised); + expect(vk).not.toBe(deserialised); + }); +}); describe('structs/verification_key', () => { - // The VK below was grabbed from the Aztec.nr contract artifact child_contract.json it(`can deserialize vk built by noir`, () => { const serialized = `0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f`; const vk = VerificationKey.fromBuffer(Buffer.from(serialized, 'hex')); diff --git a/yarn-project/circuits.js/src/structs/verification_key.ts b/yarn-project/circuits.js/src/structs/verification_key.ts index b89d0d0b4bf..b7f47e23162 100644 --- a/yarn-project/circuits.js/src/structs/verification_key.ts +++ b/yarn-project/circuits.js/src/structs/verification_key.ts @@ -1,7 +1,9 @@ +import { makeTuple } from '@aztec/foundation/array'; import { times } from '@aztec/foundation/collection'; -import { Fq } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { Fq, Fr } from '@aztec/foundation/fields'; +import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { VERIFICATION_KEY_LENGTH_IN_FIELDS } from '../constants.gen.js'; import { CircuitType } from './shared.js'; /** @@ -73,9 +75,41 @@ export class CommitmentMap { } /** - * Kate commitment key object for verifying pairing equations. - * @see proof_system/verification_key/verification_key.hpp + * Provides a 'fields' representation of a circuit's verification key */ +export class VerificationKeyAsFields { + constructor(public key: Tuple, public hash: Fr) {} + + /** + * Serialize as a buffer. + * @returns The buffer. + */ + toBuffer() { + return serializeToBuffer(this.key, this.hash); + } + toFields() { + return [...this.key, this.hash]; + } + + /** + * Deserializes from a buffer or reader, corresponding to a write in cpp. + * @param buffer - Buffer to read from. + * @returns The VerificationKeyAsFields. + */ + static fromBuffer(buffer: Buffer | BufferReader): VerificationKeyAsFields { + const reader = BufferReader.asReader(buffer); + return new VerificationKeyAsFields(reader.readArray(VERIFICATION_KEY_LENGTH_IN_FIELDS, Fr), reader.readObject(Fr)); + } + + /** + * Builds a fake verification key that should be accepted by circuits. + * @returns A fake verification key. + */ + static makeFake(seed = 1): VerificationKeyAsFields { + return new VerificationKeyAsFields(makeTuple(VERIFICATION_KEY_LENGTH_IN_FIELDS, Fr.random, seed), Fr.random()); + } +} + export class VerificationKey { constructor( /** @@ -120,9 +154,7 @@ export class VerificationKey { } /** - * Deserializes from a buffer or reader, corresponding to a write in cpp. - * @param buffer - Buffer to read from. - * @returns The VerificationKey. + @@ -126,28 +97,14 @@ export class VerificationKeyAsFields { */ static fromBuffer(buffer: Buffer | BufferReader): VerificationKey { const reader = BufferReader.asReader(buffer); diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 52a9b1e1d0a..7fdb5eb4f56 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -22,7 +22,6 @@ import { CallContext, CallRequest, CallerContext, - CircuitType, CombinedAccumulatedData, CombinedConstantData, ConstantRollupData, @@ -68,6 +67,7 @@ import { MaxBlockNumber, MembershipWitness, MergeRollupInputs, + NESTED_RECURSIVE_PROOF_LENGTH, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, @@ -112,6 +112,7 @@ import { PublicKernelCircuitPublicInputs, PublicKernelData, PublicKernelTailCircuitPrivateInputs, + RECURSIVE_PROOF_LENGTH, ROLLUP_VK_TREE_HEIGHT, ReadRequest, ReadRequestContext, @@ -129,8 +130,10 @@ import { TxRequest, VK_TREE_HEIGHT, VerificationKey, + VerificationKeyAsFields, computeContractClassId, computePublicBytecodeCommitment, + makeRecursiveProof, packBytecode, } from '../index.js'; import { ContentCommitment, NUM_BYTES_PER_SHA256 } from '../structs/content_commitment.js'; @@ -611,21 +614,20 @@ export function makeEmptyNoteHashReadRequestMembershipWitness(): NoteHashReadReq ); } +/** + * Creates arbitrary/mocked verification key in fields format. + * @returns A verification key as fields object + */ +export function makeVerificationKeyAsFields(): VerificationKeyAsFields { + return VerificationKeyAsFields.makeFake(); +} + /** * Creates arbitrary/mocked verification key. - * @returns A verification key. + * @returns A verification key object */ export function makeVerificationKey(): VerificationKey { - return new VerificationKey( - CircuitType.STANDARD, - 101, // arbitrary - 102, // arbitrary - { - A: new G1AffineElement(new Fq(0x200), new Fq(0x300)), - }, - /* recursive proof */ true, - range(5, 400), - ); + return VerificationKey.makeFake(); } /** @@ -1067,7 +1069,7 @@ export function makePreviousRollupData( export function makeRootRollupInputs(seed = 0, globalVariables?: GlobalVariables): RootRollupInputs { return new RootRollupInputs( [makePreviousRollupData(seed, globalVariables), makePreviousRollupData(seed + 0x1000, globalVariables)], - makeRootParityInput(seed + 0x2000), + makeRootParityInput(NESTED_RECURSIVE_PROOF_LENGTH, seed + 0x2000), makeTuple(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, fr, 0x2100), makeTuple(L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, fr, 0x2100), makeAppendOnlyTreeSnapshot(seed + 0x2200), @@ -1076,16 +1078,19 @@ export function makeRootRollupInputs(seed = 0, globalVariables?: GlobalVariables ); } -export function makeRootParityInput(seed = 0): RootParityInput { - return new RootParityInput(makeProof(seed), makeParityPublicInputs(seed + 0x100)); +export function makeRootParityInput( + proofSize: PROOF_LENGTH, + seed = 0, +): RootParityInput { + return new RootParityInput( + makeRecursiveProof(proofSize, seed), + VerificationKeyAsFields.makeFake(seed + 0x100), + makeParityPublicInputs(seed + 0x200), + ); } export function makeParityPublicInputs(seed = 0): ParityPublicInputs { - return new ParityPublicInputs( - makeAggregationObject(seed), - new Fr(BigInt(seed + 0x200)), - new Fr(BigInt(seed + 0x300)), - ); + return new ParityPublicInputs(new Fr(BigInt(seed + 0x200)), new Fr(BigInt(seed + 0x300))); } export function makeBaseParityInputs(seed = 0): BaseParityInputs { @@ -1093,7 +1098,13 @@ export function makeBaseParityInputs(seed = 0): BaseParityInputs { } export function makeRootParityInputs(seed = 0): RootParityInputs { - return new RootParityInputs(makeTuple(NUM_BASE_PARITY_PER_ROOT_PARITY, makeRootParityInput, seed + 0x4000)); + return new RootParityInputs( + makeTuple( + NUM_BASE_PARITY_PER_ROOT_PARITY, + () => makeRootParityInput(RECURSIVE_PROOF_LENGTH), + seed + 0x4100, + ), + ); } /** diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index 3b3e0a2385a..818ea14d4d0 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -47,6 +47,7 @@ import { MaxBlockNumber, type MembershipWitness, type MergeRollupInputs, + type NESTED_RECURSIVE_PROOF_LENGTH, type NULLIFIER_TREE_HEIGHT, NUM_BYTES_PER_SHA256, type NonMembershipHint, @@ -88,9 +89,11 @@ import { PublicKernelCircuitPublicInputs, type PublicKernelData, type PublicKernelTailCircuitPrivateInputs, + type RECURSIVE_PROOF_LENGTH, ReadRequest, ReadRequestContext, type ReadRequestStatus, + type RecursiveProof, RevertCode, RollupValidationRequests, type RootParityInput, @@ -105,12 +108,16 @@ import { TxContext, type TxRequest, ValidationRequests, + type VerificationKeyAsFields, } from '@aztec/circuits.js'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { type Tuple, mapTuple, toTruncField } from '@aztec/foundation/serialize'; import { type BaseParityInputs as BaseParityInputsNoir } from './types/parity_base_types.js'; -import { type RootParityInputs as RootParityInputsNoir } from './types/parity_root_types.js'; +import { + type RootParityInput as ParityRootParityInputNoir, + type RootParityInputs as RootParityInputsNoir, +} from './types/parity_root_types.js'; import { type CallContext as CallContextNoir, type CallRequest as CallRequestNoir, @@ -205,8 +212,8 @@ import { type ParityPublicInputs as ParityPublicInputsNoir, type PartialStateReference as PartialStateReferenceNoir, type PreviousRollupData as PreviousRollupDataNoir, - type RootParityInput as RootParityInputNoir, type RootRollupInputs as RootRollupInputsNoir, + type RootRollupParityInput as RootRollupParityInputNoir, type RootRollupPublicInputs as RootRollupPublicInputsNoir, type StateReference as StateReferenceNoir, } from './types/rollup_root_types.js'; @@ -1263,6 +1270,13 @@ export function mapKernelDataToNoir(kernelData: KernelData): KernelDataNoir { }; } +export function mapVerificationKeyToNoir(key: VerificationKeyAsFields) { + return { + key: mapTuple(key.key, mapFieldToNoir), + hash: mapFieldToNoir(key.hash), + }; +} + export function mapPrivateKernelCircuitPublicInputsFromNoir( inputs: PrivateKernelCircuitPublicInputsNoir, ): PrivateKernelCircuitPublicInputs { @@ -1681,6 +1695,22 @@ export function mapAppendOnlyTreeSnapshotToNoir(snapshot: AppendOnlyTreeSnapshot }; } +export function mapRootRollupRecursiveProofToNoir(proof: RecursiveProof) { + return { + fields: mapTuple(proof.proof, mapFieldToNoir), + }; +} + +export function mapRootRollupParityInputToNoir( + rootParityInput: RootParityInput, +): RootRollupParityInputNoir { + return { + proof: mapRootRollupRecursiveProofToNoir(rootParityInput.proof), + verification_key: mapVerificationKeyToNoir(rootParityInput.verificationKey), + public_inputs: mapParityPublicInputsToNoir(rootParityInput.publicInputs), + }; +} + /** * Naos the root rollup inputs to noir. * @param rootRollupInputs - The circuits.js root rollup inputs. @@ -1689,7 +1719,7 @@ export function mapAppendOnlyTreeSnapshotToNoir(snapshot: AppendOnlyTreeSnapshot export function mapRootRollupInputsToNoir(rootRollupInputs: RootRollupInputs): RootRollupInputsNoir { return { previous_rollup_data: mapTuple(rootRollupInputs.previousRollupData, mapPreviousRollupDataToNoir), - l1_to_l2_roots: mapRootParityInputToNoir(rootRollupInputs.l1ToL2Roots), + l1_to_l2_roots: mapRootRollupParityInputToNoir(rootRollupInputs.l1ToL2Roots), new_l1_to_l2_messages: mapTuple(rootRollupInputs.newL1ToL2Messages, mapFieldToNoir), new_l1_to_l2_message_tree_root_sibling_path: mapTuple( rootRollupInputs.newL1ToL2MessageTreeRootSiblingPath, @@ -1703,16 +1733,24 @@ export function mapRootRollupInputsToNoir(rootRollupInputs: RootRollupInputs): R }; } -export function mapRootParityInputToNoir(rootParityInput: RootParityInput): RootParityInputNoir { +export function mapRecursiveProofToNoir(proof: RecursiveProof) { return { - proof: {}, + fields: mapTuple(proof.proof, mapFieldToNoir), + }; +} + +export function mapRootParityInputToNoir( + rootParityInput: RootParityInput, +): ParityRootParityInputNoir { + return { + proof: mapRecursiveProofToNoir(rootParityInput.proof), + verification_key: mapVerificationKeyToNoir(rootParityInput.verificationKey), public_inputs: mapParityPublicInputsToNoir(rootParityInput.publicInputs), }; } export function mapParityPublicInputsToNoir(parityPublicInputs: ParityPublicInputs): ParityPublicInputsNoir { return { - aggregation_object: {}, sha_root: mapFieldToNoir(parityPublicInputs.shaRoot), converted_root: mapFieldToNoir(parityPublicInputs.convertedRoot), }; @@ -1740,7 +1778,6 @@ export function mapRootRollupPublicInputsFromNoir( */ export function mapParityPublicInputsFromNoir(parityPublicInputs: ParityPublicInputsNoir): ParityPublicInputs { return new ParityPublicInputs( - AggregationObject.makeFake(), mapFieldFromNoir(parityPublicInputs.sha_root), mapFieldFromNoir(parityPublicInputs.converted_root), ); diff --git a/yarn-project/prover-client/Dockerfile.test b/yarn-project/prover-client/Dockerfile.test index 173b71a893a..606323420df 100644 --- a/yarn-project/prover-client/Dockerfile.test +++ b/yarn-project/prover-client/Dockerfile.test @@ -41,7 +41,7 @@ RUN cd prover-client && \ BB_BINARY_PATH='/usr/src/barretenberg/cpp/build/bin/bb' \ ACVM_BINARY_PATH='/usr/src/noir/noir-repo/target/release/acvm' \ LOG_LEVEL=info \ - yarn generate-and-test + yarn test # Avoid pushing some huge container back to ecr. FROM scratch diff --git a/yarn-project/prover-client/src/bb/cli.ts b/yarn-project/prover-client/src/bb/cli.ts index 64a1b5d8be0..e8f2314ba61 100644 --- a/yarn-project/prover-client/src/bb/cli.ts +++ b/yarn-project/prover-client/src/bb/cli.ts @@ -4,7 +4,7 @@ import { type ProtocolArtifact, ProtocolCircuitArtifacts } from '@aztec/noir-pro import { Command } from 'commander'; import * as fs from 'fs/promises'; -import { generateAllServerVks, generateKeyForNoirCircuit } from './execute.js'; +import { generateKeyForNoirCircuit } from './execute.js'; const { BB_WORKING_DIRECTORY, BB_BINARY_PATH } = process.env; @@ -88,24 +88,5 @@ export function getProgram(log: LogFn): Command { log, ); }); - - program - .command('write-server-vks') - .description('Generates all verification keys require for server protocol circuits') - .requiredOption( - '-w, --working-directory ', - 'The directory to use for writing the keys', - BB_WORKING_DIRECTORY, - ) - .requiredOption('-b, --bb-path ', 'The path to the BB binary', BB_BINARY_PATH) - .action(async options => { - try { - await fs.access(options.workingDirectory, fs.constants.W_OK); - } catch (error) { - log(`Working directory does not exist`); - return; - } - await generateAllServerVks(options.bbPath, options.workingDirectory, log); - }); return program; } diff --git a/yarn-project/prover-client/src/bb/execute.ts b/yarn-project/prover-client/src/bb/execute.ts index ebf24b50c81..f53950dd0f5 100644 --- a/yarn-project/prover-client/src/bb/execute.ts +++ b/yarn-project/prover-client/src/bb/execute.ts @@ -6,7 +6,10 @@ import { type NoirCompiledCircuit } from '@aztec/types/noir'; import * as proc from 'child_process'; import * as fs from 'fs/promises'; -import { BBNativeRollupProver, type BBProverConfig } from '../prover/bb_prover.js'; +export const VK_FILENAME = 'vk'; +export const VK_FIELDS_FILENAME = 'vk_fields.json'; +export const PROOF_FILENAME = 'proof'; +export const PROOF_FIELDS_FILENAME = 'proof_fields.json'; export enum BB_RESULT { SUCCESS, @@ -17,7 +20,9 @@ export enum BB_RESULT { export type BBSuccess = { status: BB_RESULT.SUCCESS | BB_RESULT.ALREADY_PRESENT; duration: number; - path?: string; + pkPath?: string; + vkPath?: string; + proofPath?: string; }; export type BBFailure = { @@ -66,7 +71,6 @@ export function executeBB( const bytecodeHashFilename = 'bytecode_hash'; const bytecodeFilename = 'bytecode'; -const proofFileName = 'proof'; /** * Used for generating either a proving or verification key, will exit early if the key already exists @@ -101,7 +105,7 @@ export async function generateKeyForNoirCircuit( const bytecodePath = `${circuitOutputDirectory}/${bytecodeFilename}`; const bytecodeHash = sha256(bytecode); - const outputPath = `${circuitOutputDirectory}/${key}`; + const outputPath = `${circuitOutputDirectory}`; // ensure the directory exists await fs.mkdir(circuitOutputDirectory, { recursive: true }); @@ -122,7 +126,13 @@ export async function generateKeyForNoirCircuit( if (!mustRegenerate) { // No need to generate, early out - return { status: BB_RESULT.ALREADY_PRESENT, duration: 0, path: outputPath }; + return { + status: BB_RESULT.ALREADY_PRESENT, + duration: 0, + pkPath: key === 'pk' ? outputPath : undefined, + vkPath: key === 'vk' ? outputPath : undefined, + proofPath: undefined, + }; } // Check we have access to bb @@ -142,14 +152,25 @@ export async function generateKeyForNoirCircuit( // args are the output path and the input bytecode path const args = ['-o', outputPath, '-b', bytecodePath]; const timer = new Timer(); - const result = await executeBB(pathToBB, `write_${key}`, args, log); + let result = await executeBB(pathToBB, `write_${key}`, args, log); + // If we succeeded and the type of key if verification, have bb write the 'fields' version too + if (result == BB_RESULT.SUCCESS && key === 'vk') { + const asFieldsArgs = ['-k', `${outputPath}/${VK_FILENAME}`, '-o', `${outputPath}/${VK_FIELDS_FILENAME}`, '-v']; + result = await executeBB(pathToBB, `vk_as_fields`, asFieldsArgs, log); + } const duration = timer.ms(); // Cleanup the bytecode file await fs.rm(bytecodePath, { force: true }); if (result == BB_RESULT.SUCCESS) { // Store the bytecode hash so we don't need to regenerate at a later time await fs.writeFile(bytecodeHashPath, bytecodeHash); - return { status: BB_RESULT.SUCCESS, duration, path: outputPath }; + return { + status: BB_RESULT.SUCCESS, + duration, + pkPath: key === 'pk' ? outputPath : undefined, + vkPath: key === 'vk' ? outputPath : undefined, + proofPath: undefined, + }; } // Not a great error message here but it is difficult to decipher what comes from bb return { status: BB_RESULT.FAILURE, reason: `Failed to generate key` }; @@ -189,7 +210,7 @@ export async function generateProof( const bytecode = Buffer.from(compiledCircuit.bytecode, 'base64'); // The proof is written to e.g. /workingDirectory/proof - const outputPath = `${workingDirectory}/${proofFileName}`; + const outputPath = `${workingDirectory}`; const binaryPresent = await fs .access(pathToBB, fs.constants.R_OK) @@ -202,18 +223,23 @@ export async function generateProof( try { // Write the bytecode to the working directory await fs.writeFile(bytecodePath, bytecode); - const args = ['-o', outputPath, '-b', bytecodePath, '-w', inputWitnessFile]; - const command = 'prove'; + const args = ['-o', outputPath, '-b', bytecodePath, '-w', inputWitnessFile, '-v']; const timer = new Timer(); const logFunction = (message: string) => { log(`${circuitName} BB out - ${message}`); }; - const result = await executeBB(pathToBB, command, args, logFunction); + const result = await executeBB(pathToBB, 'prove_output_all', args, logFunction); const duration = timer.ms(); // cleanup the bytecode await fs.rm(bytecodePath, { force: true }); if (result == BB_RESULT.SUCCESS) { - return { status: BB_RESULT.SUCCESS, duration, path: outputPath }; + return { + status: BB_RESULT.SUCCESS, + duration, + proofPath: `${outputPath}`, + pkPath: undefined, + vkPath: `${outputPath}`, + }; } // Not a great error message here but it is difficult to decipher what comes from bb return { status: BB_RESULT.FAILURE, reason: `Failed to generate proof` }; @@ -260,25 +286,75 @@ export async function verifyProof( } /** - * Used for generating all verification keys required by server protocol circuits + * Used for verifying proofs of noir circuits * @param pathToBB - The full path to the bb binary - * @param workingDirectory - The directory to be used for the keys + * @param verificationKeyPath - The directory containing the binary verification key + * @param verificationKeyFilename - The filename of the verification key * @param log - A logging function + * @returns An object containing a result indication and duration taken */ -export async function generateAllServerVks(pathToBB: string, workingDirectory: string, log: LogFn) { - const bbConfig: BBProverConfig = { - bbBinaryPath: pathToBB, - bbWorkingDirectory: workingDirectory, +export async function writeVkAsFields( + pathToBB: string, + verificationKeyPath: string, + verificationKeyFilename: string, + log: LogFn, +): Promise { + const binaryPresent = await fs + .access(pathToBB, fs.constants.R_OK) + .then(_ => true) + .catch(_ => false); + if (!binaryPresent) { + return { status: BB_RESULT.FAILURE, reason: `Failed to find bb binary at ${pathToBB}` }; + } - // These aren't needed for this - acvmBinaryPath: '', - acvmWorkingDirectory: '', - circuitFilter: [], - }; - // This will generate all of the server circuit verification keys for us try { - await BBNativeRollupProver.generateVerificationKeys(bbConfig); + const args = ['-k', `${verificationKeyPath}/${verificationKeyFilename}`, '-v']; + const timer = new Timer(); + const result = await executeBB(pathToBB, 'vk_as_fields', args, log); + const duration = timer.ms(); + if (result == BB_RESULT.SUCCESS) { + return { status: BB_RESULT.SUCCESS, duration, vkPath: verificationKeyPath }; + } + // Not a great error message here but it is difficult to decipher what comes from bb + return { status: BB_RESULT.FAILURE, reason: `Failed to create vk as fields` }; } catch (error) { - log(`Failed to generate verification keys: ${error}`); + return { status: BB_RESULT.FAILURE, reason: `${error}` }; + } +} + +/** + * Used for verifying proofs of noir circuits + * @param pathToBB - The full path to the bb binary + * @param proofPath - The directory containing the binary proof + * @param proofFileName - The filename of the proof + * @param log - A logging function + * @returns An object containing a result indication and duration taken + */ +export async function writeProofAsFields( + pathToBB: string, + proofPath: string, + proofFileName: string, + log: LogFn, +): Promise { + const binaryPresent = await fs + .access(pathToBB, fs.constants.R_OK) + .then(_ => true) + .catch(_ => false); + if (!binaryPresent) { + return { status: BB_RESULT.FAILURE, reason: `Failed to find bb binary at ${pathToBB}` }; + } + + try { + const args = ['-p', `${proofPath}/${proofFileName}`, '-v']; + const timer = new Timer(); + const result = await executeBB(pathToBB, 'proof_as_fields', args, log); + const duration = timer.ms(); + if (result == BB_RESULT.SUCCESS) { + return { status: BB_RESULT.SUCCESS, duration, proofPath: proofPath }; + } + // Not a great error message here but it is difficult to decipher what comes from bb + return { status: BB_RESULT.FAILURE, reason: `Failed to create proof as fields` }; + } catch (error) { + return { status: BB_RESULT.FAILURE, reason: `${error}` }; } } diff --git a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts index fd727af7ed6..ae7a9604d41 100644 --- a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts +++ b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts @@ -13,6 +13,7 @@ import { MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MembershipWitness, MergeRollupInputs, + type NESTED_RECURSIVE_PROOF_LENGTH, NOTE_HASH_SUBTREE_HEIGHT, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT, @@ -208,7 +209,7 @@ export async function getRootRollupInput( rollupProofLeft: Proof, rollupOutputRight: BaseOrMergeRollupPublicInputs, rollupProofRight: Proof, - l1ToL2Roots: RootParityInput, + l1ToL2Roots: RootParityInput, newL1ToL2Messages: Tuple, messageTreeSnapshot: AppendOnlyTreeSnapshot, messageTreeRootSiblingPath: Tuple, diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator.ts b/yarn-project/prover-client/src/orchestrator/orchestrator.ts index 84e5554361d..d7fada2ebb5 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator.ts @@ -13,6 +13,7 @@ import { PROVING_STATUS, type ProvingResult, type ProvingTicket, + type PublicInputsAndProof, } from '@aztec/circuit-types/interfaces'; import { type CircuitSimulationStats } from '@aztec/circuit-types/stats'; import { @@ -28,7 +29,8 @@ import { NUM_BASE_PARITY_PER_ROOT_PARITY, type Proof, type PublicKernelCircuitPublicInputs, - RootParityInput, + type RECURSIVE_PROOF_LENGTH, + type RootParityInput, RootParityInputs, makeEmptyProof, } from '@aztec/circuits.js'; @@ -440,17 +442,17 @@ export class ProvingOrchestrator { this.deferredProving( provingState, () => this.prover.getBaseRollupProof(tx.baseRollupInputs), - ([publicInputs, proof], duration) => { + (result, duration) => { this.emitCircuitSimulationStats( 'base-rollup', tx.baseRollupInputs.toBuffer().length, - publicInputs.toBuffer().length, + result.inputs.toBuffer().length, duration, ); - validatePartialState(publicInputs.end, tx.treeSnapshots); + validatePartialState(result.inputs.end, tx.treeSnapshots); const currentLevel = provingState.numMergeLevels + 1n; - this.storeAndExecuteNextMergeLevel(provingState, currentLevel, index, [publicInputs, proof]); + this.storeAndExecuteNextMergeLevel(provingState, currentLevel, index, [result.inputs, result.proof]); }, ); } @@ -471,14 +473,14 @@ export class ProvingOrchestrator { this.deferredProving( provingState, () => this.prover.getMergeRollupProof(inputs), - ([publicInputs, proof], duration) => { + (result, duration) => { this.emitCircuitSimulationStats( 'merge-rollup', inputs.toBuffer().length, - publicInputs.toBuffer().length, + result.inputs.toBuffer().length, duration, ); - this.storeAndExecuteNextMergeLevel(provingState, level, index, [publicInputs, proof]); + this.storeAndExecuteNextMergeLevel(provingState, level, index, [result.inputs, result.proof]); }, ); } @@ -507,16 +509,16 @@ export class ProvingOrchestrator { this.deferredProving( provingState, () => this.prover.getRootRollupProof(inputs), - ([publicInputs, proof], duration) => { + (result, duration) => { this.emitCircuitSimulationStats( 'root-rollup', inputs.toBuffer().length, - publicInputs.toBuffer().length, + result.inputs.toBuffer().length, duration, ); - provingState.rootRollupPublicInputs = publicInputs; - provingState.finalProof = proof; + provingState.rootRollupPublicInputs = result.inputs; + provingState.finalProof = result.proof; const provingResult: ProvingResult = { status: PROVING_STATUS.SUCCESS, @@ -532,19 +534,23 @@ export class ProvingOrchestrator { this.deferredProving( provingState, () => this.prover.getBaseParityProof(inputs), - ([publicInputs, proof], duration) => { + (rootInput, duration) => { this.emitCircuitSimulationStats( 'base-parity', inputs.toBuffer().length, - publicInputs.toBuffer().length, + rootInput.publicInputs.toBuffer().length, duration, ); - const rootInput = new RootParityInput(proof, publicInputs); provingState.setRootParityInputs(rootInput, index); - const rootParityInputs = new RootParityInputs( - provingState.rootParityInput as Tuple, - ); - this.enqueueRootParityCircuit(provingState, rootParityInputs); + if (provingState.areRootParityInputsReady()) { + const rootParityInputs = new RootParityInputs( + provingState.rootParityInput as Tuple< + RootParityInput, + typeof NUM_BASE_PARITY_PER_ROOT_PARITY + >, + ); + this.enqueueRootParityCircuit(provingState, rootParityInputs); + } }, ); } @@ -555,14 +561,13 @@ export class ProvingOrchestrator { this.deferredProving( provingState, () => this.prover.getRootParityProof(inputs), - async ([publicInputs, proof], duration) => { + async (rootInput, duration) => { this.emitCircuitSimulationStats( 'root-parity', inputs.toBuffer().length, - publicInputs.toBuffer().length, + rootInput.publicInputs.toBuffer().length, duration, ); - const rootInput = new RootParityInput(proof, publicInputs); provingState!.finalRootParityInput = rootInput; await this.checkAndEnqueueRootRollup(provingState); }, @@ -669,14 +674,14 @@ export class ProvingOrchestrator { this.deferredProving( provingState, - (): Promise<[KernelCircuitPublicInputs | PublicKernelCircuitPublicInputs, Proof]> => { + (): Promise> => { if (request.type === PublicKernelType.TAIL) { return this.prover.getPublicTailProof(request); } else { return this.prover.getPublicKernelProof(request); } }, - ([_, proof], duration) => { + (result, duration) => { this.emitCircuitSimulationStats( this.getPublicKernelCircuitName(request), request.inputs.toBuffer().length, @@ -684,7 +689,7 @@ export class ProvingOrchestrator { duration, ); - const nextKernelRequest = txProvingState.getNextPublicKernelFromKernelProof(functionIndex, proof); + const nextKernelRequest = txProvingState.getNextPublicKernelFromKernelProof(functionIndex, result.proof); // What's the status of the next kernel? if (nextKernelRequest.code === TX_PROVING_CODE.NOT_READY) { // Must be waiting on a VM proof diff --git a/yarn-project/prover-client/src/orchestrator/proving-state.ts b/yarn-project/prover-client/src/orchestrator/proving-state.ts index f5098debd6e..9a8b8564ddd 100644 --- a/yarn-project/prover-client/src/orchestrator/proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/proving-state.ts @@ -5,8 +5,10 @@ import { type Fr, type GlobalVariables, type L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, + type NESTED_RECURSIVE_PROOF_LENGTH, type NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, type Proof, + type RECURSIVE_PROOF_LENGTH, type RootParityInput, type RootRollupPublicInputs, } from '@aztec/circuits.js'; @@ -36,8 +38,8 @@ enum PROVING_STATE_LIFECYCLE { export class ProvingState { private provingStateLifecycle = PROVING_STATE_LIFECYCLE.PROVING_STATE_CREATED; private mergeRollupInputs: MergeRollupInputData[] = []; - private rootParityInputs: Array = []; - private finalRootParityInputs: RootParityInput | undefined; + private rootParityInputs: Array | undefined> = []; + private finalRootParityInputs: RootParityInput | undefined; public rootRollupPublicInputs: RootRollupPublicInputs | undefined; public finalProof: Proof | undefined; public block: L2Block | undefined; @@ -82,7 +84,7 @@ export class ProvingState { } // Sets the final set of root parity inputs - public set finalRootParityInput(input: RootParityInput | undefined) { + public set finalRootParityInput(input: RootParityInput | undefined) { this.finalRootParityInputs = input; } @@ -157,7 +159,7 @@ export class ProvingState { } // Stores a set of root parity inputs at the given index - public setRootParityInputs(inputs: RootParityInput, index: number) { + public setRootParityInputs(inputs: RootParityInput, index: number) { this.rootParityInputs[index] = inputs; } diff --git a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts index 57c730afa95..2a7b00b347e 100644 --- a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts +++ b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts @@ -1,10 +1,11 @@ import { ProvingRequestType } from '@aztec/circuit-types'; import { - makeBaseParityInputs, - makeBaseRollupInputs, - makeParityPublicInputs, - makeProof, -} from '@aztec/circuits.js/testing'; + RECURSIVE_PROOF_LENGTH, + RootParityInput, + VerificationKeyAsFields, + makeRecursiveProof, +} from '@aztec/circuits.js'; +import { makeBaseParityInputs, makeBaseRollupInputs, makeParityPublicInputs } from '@aztec/circuits.js/testing'; import { MemoryProvingQueue } from './memory-proving-queue.js'; @@ -38,9 +39,10 @@ describe('MemoryProvingQueue', () => { expect(job?.request.inputs).toEqual(inputs); const publicInputs = makeParityPublicInputs(); - const proof = makeProof(); - await queue.resolveProvingJob(job!.id, [publicInputs, proof]); - await expect(promise).resolves.toEqual([publicInputs, proof]); + const proof = makeRecursiveProof(RECURSIVE_PROOF_LENGTH); + const vk = VerificationKeyAsFields.makeFake(); + await queue.resolveProvingJob(job!.id, new RootParityInput(proof, vk, publicInputs)); + await expect(promise).resolves.toEqual(new RootParityInput(proof, vk, publicInputs)); }); it('notifies of errors', async () => { diff --git a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts index ce8fd7f2057..773af0cfd64 100644 --- a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts +++ b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts @@ -4,6 +4,7 @@ import { type ProvingRequest, type ProvingRequestResult, ProvingRequestType, + type PublicInputsAndProof, type PublicKernelNonTailRequest, type PublicKernelTailRequest, } from '@aztec/circuit-types'; @@ -13,9 +14,10 @@ import type { BaseRollupInputs, KernelCircuitPublicInputs, MergeRollupInputs, - ParityPublicInputs, - Proof, + NESTED_RECURSIVE_PROOF_LENGTH, PublicKernelCircuitPublicInputs, + RECURSIVE_PROOF_LENGTH, + RootParityInput, RootParityInputs, RootRollupInputs, RootRollupPublicInputs, @@ -104,7 +106,7 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Creates a proof for the given input. * @param input - Input to the circuit. */ - getBaseParityProof(inputs: BaseParityInputs): Promise<[ParityPublicInputs, Proof]> { + getBaseParityProof(inputs: BaseParityInputs): Promise> { return this.enqueue({ type: ProvingRequestType.BASE_PARITY, inputs, @@ -115,7 +117,7 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Creates a proof for the given input. * @param input - Input to the circuit. */ - getRootParityProof(inputs: RootParityInputs): Promise<[ParityPublicInputs, Proof]> { + getRootParityProof(inputs: RootParityInputs): Promise> { return this.enqueue({ type: ProvingRequestType.ROOT_PARITY, inputs, @@ -126,7 +128,7 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Creates a proof for the given input. * @param input - Input to the circuit. */ - getBaseRollupProof(input: BaseRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + getBaseRollupProof(input: BaseRollupInputs): Promise> { return this.enqueue({ type: ProvingRequestType.BASE_ROLLUP, inputs: input, @@ -137,7 +139,7 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Creates a proof for the given input. * @param input - Input to the circuit. */ - getMergeRollupProof(input: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + getMergeRollupProof(input: MergeRollupInputs): Promise> { return this.enqueue({ type: ProvingRequestType.MERGE_ROLLUP, inputs: input, @@ -148,7 +150,7 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Creates a proof for the given input. * @param input - Input to the circuit. */ - getRootRollupProof(input: RootRollupInputs): Promise<[RootRollupPublicInputs, Proof]> { + getRootRollupProof(input: RootRollupInputs): Promise> { return this.enqueue({ type: ProvingRequestType.ROOT_ROLLUP, inputs: input, @@ -159,7 +161,9 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Create a public kernel proof. * @param kernelRequest - Object containing the details of the proof required */ - getPublicKernelProof(kernelRequest: PublicKernelNonTailRequest): Promise<[PublicKernelCircuitPublicInputs, Proof]> { + getPublicKernelProof( + kernelRequest: PublicKernelNonTailRequest, + ): Promise> { return this.enqueue({ type: ProvingRequestType.PUBLIC_KERNEL_NON_TAIL, kernelType: kernelRequest.type, @@ -171,7 +175,7 @@ export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { * Create a public kernel tail proof. * @param kernelRequest - Object containing the details of the proof required */ - getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise<[KernelCircuitPublicInputs, Proof]> { + getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise> { return this.enqueue({ type: ProvingRequestType.PUBLIC_KERNEL_TAIL, kernelType: kernelRequest.type, diff --git a/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts b/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts index 7c9bab82d9c..5692141c75c 100644 --- a/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts +++ b/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts @@ -1,4 +1,10 @@ -import { makeBaseParityInputs, makeParityPublicInputs, makeProof } from '@aztec/circuits.js/testing'; +import { + RECURSIVE_PROOF_LENGTH, + RootParityInput, + VerificationKeyAsFields, + makeRecursiveProof, +} from '@aztec/circuits.js'; +import { makeBaseParityInputs, makeParityPublicInputs } from '@aztec/circuits.js/testing'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -27,12 +33,17 @@ describe('ProverAgent', () => { it('takes jobs from the queue', async () => { const publicInputs = makeParityPublicInputs(); - const proof = makeProof(); - prover.getBaseParityProof.mockResolvedValue([publicInputs, proof]); + const proof = makeRecursiveProof(RECURSIVE_PROOF_LENGTH); + const vk = VerificationKeyAsFields.makeFake(); + prover.getBaseParityProof.mockResolvedValue( + new RootParityInput(proof, vk, publicInputs), + ); const inputs = makeBaseParityInputs(); + const promise = queue.getBaseParityProof(inputs); - await expect(promise).resolves.toEqual([publicInputs, proof]); + await expect(promise).resolves.toEqual(new RootParityInput(proof, vk, publicInputs)); + expect(prover.getBaseParityProof).toHaveBeenCalledWith(inputs); }); @@ -49,18 +60,25 @@ describe('ProverAgent', () => { it('continues to process jobs', async () => { const publicInputs = makeParityPublicInputs(); - const proof = makeProof(); - prover.getBaseParityProof.mockResolvedValue([publicInputs, proof]); + const proof = makeRecursiveProof(RECURSIVE_PROOF_LENGTH); + const vk = VerificationKeyAsFields.makeFake(); + prover.getBaseParityProof.mockResolvedValue( + new RootParityInput(proof, vk, publicInputs), + ); const inputs = makeBaseParityInputs(); const promise1 = queue.getBaseParityProof(inputs); - await expect(promise1).resolves.toEqual([publicInputs, proof]); + await expect(promise1).resolves.toEqual( + new RootParityInput(proof, vk, publicInputs), + ); const inputs2 = makeBaseParityInputs(); const promise2 = queue.getBaseParityProof(inputs2); - await expect(promise2).resolves.toEqual([publicInputs, proof]); + await expect(promise2).resolves.toEqual( + new RootParityInput(proof, vk, publicInputs), + ); expect(prover.getBaseParityProof).toHaveBeenCalledTimes(2); expect(prover.getBaseParityProof).toHaveBeenCalledWith(inputs); diff --git a/yarn-project/prover-client/src/prover-pool/prover-agent.ts b/yarn-project/prover-client/src/prover-pool/prover-agent.ts index 030bcc35f90..b5dce971dbc 100644 --- a/yarn-project/prover-client/src/prover-pool/prover-agent.ts +++ b/yarn-project/prover-client/src/prover-pool/prover-agent.ts @@ -3,6 +3,7 @@ import { type ProvingRequest, type ProvingRequestResult, ProvingRequestType, + makePublicInputsAndProof, } from '@aztec/circuit-types'; import { makeEmptyProof } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -65,7 +66,7 @@ export class ProverAgent { const { type, inputs } = request; switch (type) { case ProvingRequestType.PUBLIC_VM: { - return Promise.resolve([{}, makeEmptyProof()] as const); + return Promise.resolve(makePublicInputsAndProof({}, makeEmptyProof())); } case ProvingRequestType.PUBLIC_KERNEL_NON_TAIL: { diff --git a/yarn-project/prover-client/src/prover/bb_prover.ts b/yarn-project/prover-client/src/prover/bb_prover.ts index 524343b27a3..f75d6fb1685 100644 --- a/yarn-project/prover-client/src/prover/bb_prover.ts +++ b/yarn-project/prover-client/src/prover/bb_prover.ts @@ -1,22 +1,36 @@ /* eslint-disable require-await */ -import { type PublicKernelNonTailRequest, type PublicKernelTailRequest, PublicKernelType } from '@aztec/circuit-types'; +import { + type PublicInputsAndProof, + type PublicKernelNonTailRequest, + type PublicKernelTailRequest, + PublicKernelType, + makePublicInputsAndProof, +} from '@aztec/circuit-types'; import { type BaseOrMergeRollupPublicInputs, type BaseParityInputs, type BaseRollupInputs, + Fr, type KernelCircuitPublicInputs, type MergeRollupInputs, + type NESTED_RECURSIVE_PROOF_LENGTH, type ParityPublicInputs, type PreviousRollupData, Proof, type PublicKernelCircuitPublicInputs, + type RECURSIVE_PROOF_LENGTH, + RecursiveProof, RollupTypes, + RootParityInput, type RootParityInputs, type RootRollupInputs, type RootRollupPublicInputs, + type VERIFICATION_KEY_LENGTH_IN_FIELDS, + VerificationKeyAsFields, } from '@aztec/circuits.js'; import { randomBytes } from '@aztec/foundation/crypto'; import { createDebugLogger } from '@aztec/foundation/log'; +import { type Tuple } from '@aztec/foundation/serialize'; import { ServerCircuitArtifacts, type ServerProtocolArtifact, @@ -38,11 +52,26 @@ import { NativeACVMSimulator } from '@aztec/simulator'; import { type WitnessMap } from '@noir-lang/types'; import * as fs from 'fs/promises'; -import { BB_RESULT, generateKeyForNoirCircuit, generateProof, verifyProof } from '../bb/execute.js'; +import { + BB_RESULT, + PROOF_FIELDS_FILENAME, + PROOF_FILENAME, + VK_FIELDS_FILENAME, + VK_FILENAME, + generateKeyForNoirCircuit, + generateProof, + verifyProof, +} from '../bb/execute.js'; import { type CircuitProver, KernelArtifactMapping } from './interface.js'; const logger = createDebugLogger('aztec:bb-prover'); +const CIRCUITS_WITHOUT_AGGREGATION: Set = new Set(['BaseParityArtifact']); +const AGGREGATION_OBJECT_SIZE = 16; +const CIRCUIT_SIZE_INDEX = 3; +const CIRCUIT_PUBLIC_INPUTS_INDEX = 4; +const CIRCUIT_RECURSIVE_INDEX = 5; + export type BBProverConfig = { bbBinaryPath: string; bbWorkingDirectory: string; @@ -52,14 +81,24 @@ export type BBProverConfig = { circuitFilter?: ServerProtocolArtifact[]; }; +type VerificationKeyData = { + hash: Fr; + keyAsFields: Tuple; + keyAsBytes: Buffer; + numPublicInputs: number; + circuitSize: number; + isRecursive: boolean; +}; + /** * Prover implementation that uses barretenberg native proving */ export class BBNativeRollupProver implements CircuitProver { - constructor( - private config: BBProverConfig, - private verificationKeyDirectories: Map, - ) {} + private verificationKeys: Map> = new Map< + ServerProtocolArtifact, + Promise + >(); + constructor(private config: BBProverConfig) {} static async new(config: BBProverConfig) { await fs.access(config.acvmBinaryPath, fs.constants.R_OK); @@ -69,43 +108,7 @@ export class BBNativeRollupProver implements CircuitProver { logger.info(`Using native BB at ${config.bbBinaryPath} and working directory ${config.bbWorkingDirectory}`); logger.info(`Using native ACVM at ${config.acvmBinaryPath} and working directory ${config.acvmWorkingDirectory}`); - const mappings = await BBNativeRollupProver.generateVerificationKeys(config); - - return new BBNativeRollupProver(config, mappings); - } - - public static async generateVerificationKeys(bbConfig: BBProverConfig) { - const promises = []; - const directories = new Map(); - for (const circuitName in ServerCircuitArtifacts) { - if (bbConfig.circuitFilter?.length && bbConfig.circuitFilter.findIndex((c: string) => c === circuitName) === -1) { - // circuit is not supported - continue; - } - const verificationKeyPromise = generateKeyForNoirCircuit( - bbConfig.bbBinaryPath, - bbConfig.bbWorkingDirectory, - circuitName, - ServerCircuitArtifacts[circuitName as ServerProtocolArtifact], - 'vk', - logger.debug, - ).then(result => { - if (result.status == BB_RESULT.FAILURE) { - const message = `Failed to generate verification key for circuit ${circuitName}, message: ${result.reason}`; - logger.error(message); - throw new Error(message); - } - if (result.status == BB_RESULT.ALREADY_PRESENT) { - logger.info(`Verification key for circuit ${circuitName} was already present at ${result.path!}`); - } else { - logger.info(`Generated verification key for circuit ${circuitName} at ${result.path!}`); - } - directories.set(circuitName as ServerProtocolArtifact, result.path!); - }); - promises.push(verificationKeyPromise); - } - await Promise.all(promises); - return directories; + return new BBNativeRollupProver(config); } /** @@ -113,14 +116,20 @@ export class BBNativeRollupProver implements CircuitProver { * @param inputs - Inputs to the circuit. * @returns The public inputs of the parity circuit. */ - public async getBaseParityProof(inputs: BaseParityInputs): Promise<[ParityPublicInputs, Proof]> { + public async getBaseParityProof(inputs: BaseParityInputs): Promise> { const witnessMap = convertBaseParityInputsToWitnessMap(inputs); - const [outputWitness, proof] = await this.createProof(witnessMap, 'BaseParityArtifact'); + const [circuitOutput, proof] = await this.createRecursiveProof( + witnessMap, + 'BaseParityArtifact', + convertBaseParityOutputsFromWitnessMap, + ); + + const verificationKey = await this.getVerificationKeyDataForCircuit('BaseParityArtifact'); - const result = convertBaseParityOutputsFromWitnessMap(outputWitness); + const vk = new VerificationKeyAsFields(verificationKey.keyAsFields, verificationKey.hash); - return Promise.resolve([result, proof]); + return new RootParityInput(proof, vk, circuitOutput); } /** @@ -128,17 +137,57 @@ export class BBNativeRollupProver implements CircuitProver { * @param inputs - Inputs to the circuit. * @returns The public inputs of the parity circuit. */ - public async getRootParityProof(inputs: RootParityInputs): Promise<[ParityPublicInputs, Proof]> { - // verify all base parity inputs - await Promise.all(inputs.children.map(child => this.verifyProof('BaseParityArtifact', child.proof))); - + public async getRootParityProof( + inputs: RootParityInputs, + ): Promise> { const witnessMap = convertRootParityInputsToWitnessMap(inputs); - const [outputWitness, proof] = await this.createProof(witnessMap, 'RootParityArtifact'); + const [circuitOutput, proof] = await this.createRecursiveProof< + typeof NESTED_RECURSIVE_PROOF_LENGTH, + ParityPublicInputs + >(witnessMap, 'RootParityArtifact', convertRootParityOutputsFromWitnessMap); + + const verificationKey = await this.getVerificationKeyDataForCircuit('RootParityArtifact'); - const result = convertRootParityOutputsFromWitnessMap(outputWitness); + const vk = new VerificationKeyAsFields(verificationKey.keyAsFields, verificationKey.hash); - return Promise.resolve([result, proof]); + return new RootParityInput(proof, vk, circuitOutput); + } + + /** + * Requests that a public kernel circuit be executed and the proof generated + * @param kernelRequest - The object encapsulating the request for a proof + * @returns The requested circuit's public inputs and proof + */ + public async getPublicKernelProof( + kernelRequest: PublicKernelNonTailRequest, + ): Promise> { + const kernelOps = KernelArtifactMapping[kernelRequest.type]; + if (kernelOps === undefined) { + throw new Error(`Unable to prove kernel type ${PublicKernelType[kernelRequest.type]}`); + } + const witnessMap = kernelOps.convertInputs(kernelRequest.inputs); + + const [outputWitness, proof] = await this.createProof(witnessMap, kernelOps.artifact); + + const result = kernelOps.convertOutputs(outputWitness); + return makePublicInputsAndProof(result, proof); + } + + /** + * Requests that the public kernel tail circuit be executed and the proof generated + * @param kernelRequest - The object encapsulating the request for a proof + * @returns The requested circuit's public inputs and proof + */ + public async getPublicTailProof( + kernelRequest: PublicKernelTailRequest, + ): Promise> { + const witnessMap = convertPublicTailInputsToWitnessMap(kernelRequest.inputs); + + const [outputWitness, proof] = await this.createProof(witnessMap, 'PublicKernelTailArtifact'); + + const result = convertPublicTailOutputFromWitnessMap(outputWitness); + return makePublicInputsAndProof(result, proof); } /** @@ -146,21 +195,25 @@ export class BBNativeRollupProver implements CircuitProver { * @param input - Inputs to the circuit. * @returns The public inputs as outputs of the simulation. */ - public async getBaseRollupProof(input: BaseRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + public async getBaseRollupProof( + input: BaseRollupInputs, + ): Promise> { const witnessMap = convertBaseRollupInputsToWitnessMap(input); const [outputWitness, proof] = await this.createProof(witnessMap, 'BaseRollupArtifact'); const result = convertBaseRollupOutputsFromWitnessMap(outputWitness); - return Promise.resolve([result, proof]); + return makePublicInputsAndProof(result, proof); } /** * Simulates the merge rollup circuit from its inputs. * @param input - Inputs to the circuit. * @returns The public inputs as outputs of the simulation. */ - public async getMergeRollupProof(input: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + public async getMergeRollupProof( + input: MergeRollupInputs, + ): Promise> { // verify both inputs await Promise.all(input.previousRollupData.map(prev => this.verifyPreviousRollupProof(prev))); @@ -170,7 +223,7 @@ export class BBNativeRollupProver implements CircuitProver { const result = convertMergeRollupOutputsFromWitnessMap(outputWitness); - return Promise.resolve([result, proof]); + return makePublicInputsAndProof(result, proof); } /** @@ -178,7 +231,7 @@ export class BBNativeRollupProver implements CircuitProver { * @param input - Inputs to the circuit. * @returns The public inputs as outputs of the simulation. */ - public async getRootRollupProof(input: RootRollupInputs): Promise<[RootRollupPublicInputs, Proof]> { + public async getRootRollupProof(input: RootRollupInputs): Promise> { // verify the inputs await Promise.all(input.previousRollupData.map(prev => this.verifyPreviousRollupProof(prev))); @@ -189,9 +242,10 @@ export class BBNativeRollupProver implements CircuitProver { await this.verifyProof('RootRollupArtifact', proof); const result = convertRootRollupOutputsFromWitnessMap(outputWitness); - return Promise.resolve([result, proof]); + return makePublicInputsAndProof(result, proof); } + // TODO(@PhilWindle): Delete when no longer required public async createProof(witnessMap: WitnessMap, circuitType: ServerProtocolArtifact): Promise<[WitnessMap, Proof]> { // Create random directory to be used for temp files const bbWorkingDirectory = `${this.config.bbWorkingDirectory}/${randomBytes(8).toString('hex')}`; @@ -233,27 +287,108 @@ export class BBNativeRollupProver implements CircuitProver { throw new Error(provingResult.reason); } + // Ensure our vk cache is up to date + await this.updateVerificationKeyAfterProof(provingResult.vkPath!, circuitType); + // Read the proof and then cleanup up our temporary directory - const proofBuffer = await fs.readFile(provingResult.path!); + const proof = await fs.readFile(`${provingResult.proofPath!}/${PROOF_FILENAME}`); await fs.rm(bbWorkingDirectory, { recursive: true, force: true }); - logger.info( - `Generated proof for ${circuitType} in ${provingResult.duration} ms, size: ${proofBuffer.length} bytes`, - ); + logger.info(`Generated proof for ${circuitType} in ${provingResult.duration} ms, size: ${proof.length} fields`); + + return [outputWitness, new Proof(proof)]; + } + + /** + * Executes a circuit and returns it's outputs and corresponding proof with embedded aggregation object + * @param witnessMap - The input witness + * @param circuitType - The type of circuit to be executed + * @param convertOutput - Function for parsing the output witness to it's corresponding object + * @returns The circuits output object and it's proof + */ + public async createRecursiveProof( + witnessMap: WitnessMap, + circuitType: ServerProtocolArtifact, + convertOutput: (outputWitness: WitnessMap) => CircuitOutputType, + ): Promise<[CircuitOutputType, RecursiveProof]> { + // Create random directory to be used for temp files + const bbWorkingDirectory = `${this.config.bbWorkingDirectory}/${randomBytes(8).toString('hex')}`; + await fs.mkdir(bbWorkingDirectory, { recursive: true }); + + await fs.access(bbWorkingDirectory); + + try { + // Have the ACVM write the partial witness here + const outputWitnessFile = `${bbWorkingDirectory}/partial-witness.gz`; + + // Generate the partial witness using the ACVM + // A further temp directory will be created beneath ours and then cleaned up after the partial witness has been copied to our specified location + const simulator = new NativeACVMSimulator( + this.config.acvmWorkingDirectory, + this.config.acvmBinaryPath, + outputWitnessFile, + ); + + const artifact = ServerCircuitArtifacts[circuitType]; + + logger.debug(`Generating witness data for ${circuitType}`); + + const outputWitness = await simulator.simulateCircuit(witnessMap, artifact); + + const outputType = convertOutput(outputWitness); + + // Now prove the circuit from the generated witness + logger.debug(`Proving ${circuitType}...`); + + const provingResult = await generateProof( + this.config.bbBinaryPath, + bbWorkingDirectory, + circuitType, + artifact, + outputWitnessFile, + logger.debug, + ); + + if (provingResult.status === BB_RESULT.FAILURE) { + logger.error(`Failed to generate proof for ${circuitType}: ${provingResult.reason}`); + throw new Error(provingResult.reason); + } + + // Ensure our vk cache is up to date + await this.updateVerificationKeyAfterProof(provingResult.vkPath!, circuitType); + + // Read the proof and then cleanup up our temporary directory + const proof = await this.readProofAsFields(provingResult.proofPath!, circuitType); - return [outputWitness, new Proof(proofBuffer)]; + logger.info( + `Generated proof for ${circuitType} in ${provingResult.duration} ms, size: ${proof.proof.length} fields`, + ); + + return [outputType, proof]; + } finally { + await fs.rm(bbWorkingDirectory, { recursive: true, force: true }); + } } + /** + * Verifies a proof, will generate the verification key if one is not cached internally + * @param circuitType - The type of circuit whose proof is to be verified + * @param proof - The proof to be verified + */ public async verifyProof(circuitType: ServerProtocolArtifact, proof: Proof) { // Create random directory to be used for temp files const bbWorkingDirectory = `${this.config.bbWorkingDirectory}/${randomBytes(8).toString('hex')}`; await fs.mkdir(bbWorkingDirectory, { recursive: true }); const proofFileName = `${bbWorkingDirectory}/proof`; - const verificationKeyPath = this.verificationKeyDirectories.get(circuitType); + const verificationKeyPath = `${bbWorkingDirectory}/vk`; + const verificationKey = await this.getVerificationKeyDataForCircuit(circuitType); + + logger.debug(`Verifying with key: ${verificationKey.hash.toString()}`); await fs.writeFile(proofFileName, proof.buffer); + await fs.writeFile(verificationKeyPath, verificationKey.keyAsBytes); const logFunction = (message: string) => { logger.debug(`${circuitType} BB out - ${message}`); @@ -271,6 +406,16 @@ export class BBNativeRollupProver implements CircuitProver { logger.info(`Successfully verified ${circuitType} proof in ${result.duration} ms`); } + /** + * Returns the verification key for a circuit, will generate it if not cached internally + * @param circuitType - The type of circuit for which the verification key is required + * @returns The verification key + */ + public async getVerificationKeyForCircuit(circuitType: ServerProtocolArtifact): Promise { + const vkData = await this.getVerificationKeyDataForCircuit(circuitType); + return new VerificationKeyAsFields(vkData.keyAsFields, vkData.hash); + } + private async verifyPreviousRollupProof(previousRollupData: PreviousRollupData) { const proof = previousRollupData.proof; const circuitType = @@ -280,27 +425,100 @@ export class BBNativeRollupProver implements CircuitProver { await this.verifyProof(circuitType, proof); } - public async getPublicKernelProof( - kernelRequest: PublicKernelNonTailRequest, - ): Promise<[PublicKernelCircuitPublicInputs, Proof]> { - const kernelOps = KernelArtifactMapping[kernelRequest.type]; - if (kernelOps === undefined) { - throw new Error(`Unable to prove kernel type ${PublicKernelType[kernelRequest.type]}`); + /** + * Returns the verification key data for a circuit, will generate and cache it if not cached internally + * @param circuitType - The type of circuit for which the verification key is required + * @returns The verification key data + */ + private async getVerificationKeyDataForCircuit(circuitType: ServerProtocolArtifact): Promise { + let promise = this.verificationKeys.get(circuitType); + if (!promise) { + promise = generateKeyForNoirCircuit( + this.config.bbBinaryPath, + this.config.bbWorkingDirectory, + circuitType, + ServerCircuitArtifacts[circuitType], + 'vk', + logger.debug, + ).then(result => { + if (result.status === BB_RESULT.FAILURE) { + throw new Error(`Failed to generate verification key for ${circuitType}, ${result.reason}`); + } + return this.convertVk(result.vkPath!); + }); + this.verificationKeys.set(circuitType, promise); } - const witnessMap = kernelOps.convertInputs(kernelRequest.inputs); - - const [outputWitness, proof] = await this.createProof(witnessMap, kernelOps.artifact); - - const result = kernelOps.convertOutputs(outputWitness); - return Promise.resolve([result, proof]); + return await promise; } - public async getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise<[KernelCircuitPublicInputs, Proof]> { - const witnessMap = convertPublicTailInputsToWitnessMap(kernelRequest.inputs); + /** + * Reads the verification key data stored at the specified location and parses into a VerificationKeyData + * @param filePath - The directory containing the verification key data files + * @returns The verification key data + */ + private async convertVk(filePath: string): Promise { + const [rawFields, rawBinary] = await Promise.all([ + fs.readFile(`${filePath}/${VK_FIELDS_FILENAME}`, { encoding: 'utf-8' }), + fs.readFile(`${filePath}/${VK_FILENAME}`), + ]); + const fieldsJson = JSON.parse(rawFields); + const fields = fieldsJson.map(Fr.fromString); + // The first item is the hash, this is not part of the actual VK + const vkHash = fields[0]; + const actualVk = fields.slice(1); + const vk: VerificationKeyData = { + hash: vkHash, + keyAsFields: actualVk as Tuple, + keyAsBytes: rawBinary, + numPublicInputs: Number(actualVk[CIRCUIT_PUBLIC_INPUTS_INDEX]), + circuitSize: Number(actualVk[CIRCUIT_SIZE_INDEX]), + isRecursive: actualVk[CIRCUIT_RECURSIVE_INDEX] == Fr.ONE, + }; + return vk; + } - const [outputWitness, proof] = await this.createProof(witnessMap, 'PublicKernelTailArtifact'); + /** + * Ensures our verification key cache includes the key data located at the specified directory + * @param filePath - The directory containing the verification key data files + * @param circuitType - The type of circuit to which the verification key corresponds + */ + private async updateVerificationKeyAfterProof(filePath: string, circuitType: ServerProtocolArtifact) { + let promise = this.verificationKeys.get(circuitType); + if (!promise) { + promise = this.convertVk(filePath); + this.verificationKeys.set(circuitType, promise); + } + await promise; + } - const result = convertPublicTailOutputFromWitnessMap(outputWitness); - return [result, proof]; + /** + * Parses and returns the proof data stored at the specified directory + * @param filePath - The directory containing the proof data + * @param circuitType - The type of circuit proven + * @returns The proof + */ + private async readProofAsFields( + filePath: string, + circuitType: ServerProtocolArtifact, + ): Promise> { + const [binaryProof, proofString] = await Promise.all([ + fs.readFile(`${filePath}/${PROOF_FILENAME}`), + fs.readFile(`${filePath}/${PROOF_FIELDS_FILENAME}`, { encoding: 'utf-8' }), + ]); + const json = JSON.parse(proofString); + const fields = json.map(Fr.fromString); + const vkData = await this.verificationKeys.get(circuitType); + if (!vkData) { + throw new Error(`Invalid verification key for ${circuitType}`); + } + const numPublicInputs = CIRCUITS_WITHOUT_AGGREGATION.has(circuitType) + ? vkData.numPublicInputs + : vkData.numPublicInputs - AGGREGATION_OBJECT_SIZE; + const fieldsWithoutPublicInputs = fields.slice(numPublicInputs); + logger.debug( + `Circuit type: ${circuitType}, complete proof length: ${fields.length}, without public inputs: ${fieldsWithoutPublicInputs.length}, num public inputs: ${numPublicInputs}, circuit size: ${vkData.circuitSize}, is recursive: ${vkData.isRecursive}, raw length: ${binaryProof.length}`, + ); + const proof = new RecursiveProof(fieldsWithoutPublicInputs, new Proof(binaryProof)); + return proof; } } diff --git a/yarn-project/prover-client/src/prover/bb_prover_base_rollup.test.ts b/yarn-project/prover-client/src/prover/bb_prover_base_rollup.test.ts index 4b1e0337337..7e05cba9871 100644 --- a/yarn-project/prover-client/src/prover/bb_prover_base_rollup.test.ts +++ b/yarn-project/prover-client/src/prover/bb_prover_base_rollup.test.ts @@ -30,6 +30,6 @@ describe('prover/bb_prover/base-rollup', () => { logger.verbose('Proving base rollups'); const proofOutputs = await context.prover.getBaseRollupProof(baseRollupInputs); logger.verbose('Verifying base rollups'); - await expect(context.prover.verifyProof('BaseRollupArtifact', proofOutputs[1])).resolves.not.toThrow(); + await expect(context.prover.verifyProof('BaseRollupArtifact', proofOutputs.proof)).resolves.not.toThrow(); }, 200_000); }); diff --git a/yarn-project/prover-client/src/prover/bb_prover_full_rollup.test.ts b/yarn-project/prover-client/src/prover/bb_prover_full_rollup.test.ts index 5d8fbc7b8dd..dca24b599fb 100644 --- a/yarn-project/prover-client/src/prover/bb_prover_full_rollup.test.ts +++ b/yarn-project/prover-client/src/prover/bb_prover_full_rollup.test.ts @@ -1,5 +1,6 @@ import { PROVING_STATUS, makeEmptyProcessedTx, mockTx } from '@aztec/circuit-types'; -import { Fr, Header } from '@aztec/circuits.js'; +import { Fr, Header, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js'; +import { makeTuple } from '@aztec/foundation/array'; import { times } from '@aztec/foundation/collection'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -31,10 +32,15 @@ describe('prover/bb_prover/full-rollup', () => { tx.data.constants.historicalHeader = await context.actualDb.buildInitialHeader(); } + const l1ToL2Messages = makeTuple( + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, + Fr.random, + ); + const provingTicket = await context.orchestrator.startNewBlock( numTransactions, context.globalVariables, - [], + l1ToL2Messages, makeEmptyProcessedTx(Header.empty(), new Fr(1234), new Fr(1)), ); diff --git a/yarn-project/prover-client/src/prover/bb_prover_parity.test.ts b/yarn-project/prover-client/src/prover/bb_prover_parity.test.ts index 8f112fd9988..2300450f495 100644 --- a/yarn-project/prover-client/src/prover/bb_prover_parity.test.ts +++ b/yarn-project/prover-client/src/prover/bb_prover_parity.test.ts @@ -3,10 +3,15 @@ import { Fr, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUM_BASE_PARITY_PER_ROOT_PARITY, + ParityPublicInputs, + RECURSIVE_PROOF_LENGTH, RootParityInput, RootParityInputs, + VerificationKeyAsFields, + makeRecursiveProof, } from '@aztec/circuits.js'; import { makeTuple } from '@aztec/foundation/array'; +import { randomBytes } from '@aztec/foundation/crypto'; import { createDebugLogger } from '@aztec/foundation/log'; import { type Tuple } from '@aztec/foundation/serialize'; @@ -17,11 +22,13 @@ const logger = createDebugLogger('aztec:bb-prover-parity'); describe('prover/bb_prover/parity', () => { let context: TestContext; + let bbProver: BBNativeRollupProver; beforeAll(async () => { - const buildProver = (bbConfig: BBProverConfig) => { + const buildProver = async (bbConfig: BBProverConfig) => { bbConfig.circuitFilter = ['BaseParityArtifact', 'RootParityArtifact']; - return BBNativeRollupProver.new(bbConfig); + bbProver = await BBNativeRollupProver.new(bbConfig); + return bbProver; }; context = await TestContext.new(logger, 1, buildProver); }, 60_000); @@ -46,20 +53,86 @@ describe('prover/bb_prover/parity', () => { // Verify the base parity proofs await expect( - Promise.all(rootInputs.map(input => context.prover.verifyProof('BaseParityArtifact', input[1]))), + Promise.all(rootInputs.map(input => context.prover.verifyProof('BaseParityArtifact', input.proof.binaryProof))), ).resolves.not.toThrow(); // Now generate the root parity proof - const rootChildrenInputs = rootInputs.map(rootInput => { - const child: RootParityInput = new RootParityInput(rootInput[1], rootInput[0]); - return child; - }); const rootParityInputs: RootParityInputs = new RootParityInputs( - rootChildrenInputs as Tuple, + rootInputs as Tuple, typeof NUM_BASE_PARITY_PER_ROOT_PARITY>, ); const rootOutput = await context.prover.getRootParityProof(rootParityInputs); // Verify the root parity proof - await expect(context.prover.verifyProof('RootParityArtifact', rootOutput[1])).resolves.not.toThrow(); - }, 100_000); + await expect(context.prover.verifyProof('RootParityArtifact', rootOutput.proof.binaryProof)).resolves.not.toThrow(); + + // Now test for negative cases. We will try and generate 3 invalid proofs. + // One where a single child has an invalid proof + // One where a child has incorrect public inputs + // One where a child has an invalid verification key + // In each case either the proof should fail to generate or verify + + const validVk = rootParityInputs.children[0].verificationKey; + const validPublicInputs = rootParityInputs.children[0].publicInputs; + const validProof = rootParityInputs.children[0].proof; + + const defectiveProofInput = new RootParityInput( + makeRecursiveProof(RECURSIVE_PROOF_LENGTH, 0x500), + validVk, + validPublicInputs, + ); + + const shaRoot = randomBytes(32); + shaRoot[0] = 0; + + const defectivePublicInputs = new RootParityInput( + validProof, + validVk, + new ParityPublicInputs(Fr.fromBuffer(shaRoot), Fr.random()), + ); + + const defectiveVerificationKey = new RootParityInput( + validProof, + VerificationKeyAsFields.makeFake(), + validPublicInputs, + ); + + const tupleWithDefectiveProof = makeTuple(NUM_BASE_PARITY_PER_ROOT_PARITY, (i: number) => { + if (i == 0) { + return defectiveProofInput; + } + return rootParityInputs.children[i]; + }); + + const tupleWithDefectiveInputs = makeTuple(NUM_BASE_PARITY_PER_ROOT_PARITY, (i: number) => { + if (i == 0) { + return defectivePublicInputs; + } + return rootParityInputs.children[i]; + }); + + const tupleWithDefectiveVK = makeTuple(NUM_BASE_PARITY_PER_ROOT_PARITY, (i: number) => { + if (i == 0) { + return defectiveVerificationKey; + } + return rootParityInputs.children[i]; + }); + + // Check the invalid VK scenario with an invalid witness assertion + await expect(context.prover.getRootParityProof(new RootParityInputs(tupleWithDefectiveVK))).rejects.toThrow(); + + const defectiveTuples = [tupleWithDefectiveProof, tupleWithDefectiveInputs]; + + for (const t of defectiveTuples) { + try { + const result = await context.prover.getRootParityProof(new RootParityInputs(t)); + await context.prover.verifyProof('RootParityArtifact', result.proof.binaryProof); + fail('Proof should not be generated and verified'); + } catch (error) { + expect([ + new Error('Failed to generate proof'), + new Error('Failed to verify RootParityArtifact proof!'), + ]).toContainEqual(error); + } + } + }, 200_000); }); diff --git a/yarn-project/prover-client/src/prover/bb_prover_public_kernel.test.ts b/yarn-project/prover-client/src/prover/bb_prover_public_kernel.test.ts index 17bb3e55a42..251b1f19706 100644 --- a/yarn-project/prover-client/src/prover/bb_prover_public_kernel.test.ts +++ b/yarn-project/prover-client/src/prover/bb_prover_public_kernel.test.ts @@ -69,13 +69,13 @@ describe('prover/bb_prover/public-kernel', () => { if (request.type === PublicKernelType.TAIL) { await expect( context.prover.getPublicTailProof(request).then(result => { - proof = result[1]; + proof = result.proof; }), ).resolves.not.toThrow(); } else { await expect( context.prover.getPublicKernelProof(request).then(result => { - proof = result[1]; + proof = result.proof; }), ).resolves.not.toThrow(); } diff --git a/yarn-project/prover-client/src/prover/interface.ts b/yarn-project/prover-client/src/prover/interface.ts index 7ed8dad98a9..aa24cf6eda8 100644 --- a/yarn-project/prover-client/src/prover/interface.ts +++ b/yarn-project/prover-client/src/prover/interface.ts @@ -1,15 +1,22 @@ -import { type PublicKernelNonTailRequest, type PublicKernelTailRequest, PublicKernelType } from '@aztec/circuit-types'; +import { + type PublicInputsAndProof, + type PublicKernelNonTailRequest, + type PublicKernelTailRequest, + PublicKernelType, +} from '@aztec/circuit-types'; import { type BaseOrMergeRollupPublicInputs, type BaseParityInputs, type BaseRollupInputs, type KernelCircuitPublicInputs, type MergeRollupInputs, - type ParityPublicInputs, + type NESTED_RECURSIVE_PROOF_LENGTH, type Proof, type PublicCircuitPublicInputs, type PublicKernelCircuitPrivateInputs, type PublicKernelCircuitPublicInputs, + type RECURSIVE_PROOF_LENGTH, + type RootParityInput, type RootParityInputs, type RootRollupInputs, type RootRollupPublicInputs, @@ -62,43 +69,45 @@ export interface CircuitProver { * Creates a proof for the given input. * @param input - Input to the circuit. */ - getBaseParityProof(inputs: BaseParityInputs): Promise<[ParityPublicInputs, Proof]>; + getBaseParityProof(inputs: BaseParityInputs): Promise>; /** * Creates a proof for the given input. * @param input - Input to the circuit. */ - getRootParityProof(inputs: RootParityInputs): Promise<[ParityPublicInputs, Proof]>; + getRootParityProof(inputs: RootParityInputs): Promise>; /** * Creates a proof for the given input. * @param input - Input to the circuit. */ - getBaseRollupProof(input: BaseRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]>; + getBaseRollupProof(input: BaseRollupInputs): Promise>; /** * Creates a proof for the given input. * @param input - Input to the circuit. */ - getMergeRollupProof(input: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]>; + getMergeRollupProof(input: MergeRollupInputs): Promise>; /** * Creates a proof for the given input. * @param input - Input to the circuit. */ - getRootRollupProof(input: RootRollupInputs): Promise<[RootRollupPublicInputs, Proof]>; + getRootRollupProof(input: RootRollupInputs): Promise>; /** * Create a public kernel proof. * @param kernelRequest - Object containing the details of the proof required */ - getPublicKernelProof(kernelRequest: PublicKernelNonTailRequest): Promise<[PublicKernelCircuitPublicInputs, Proof]>; + getPublicKernelProof( + kernelRequest: PublicKernelNonTailRequest, + ): Promise>; /** * Create a public kernel tail proof. * @param kernelRequest - Object containing the details of the proof required */ - getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise<[KernelCircuitPublicInputs, Proof]>; + getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise>; /** * Verifies a circuit proof diff --git a/yarn-project/prover-client/src/prover/test_circuit_prover.ts b/yarn-project/prover-client/src/prover/test_circuit_prover.ts index 63a1f74a1c2..47c15be17dc 100644 --- a/yarn-project/prover-client/src/prover/test_circuit_prover.ts +++ b/yarn-project/prover-client/src/prover/test_circuit_prover.ts @@ -1,4 +1,10 @@ -import { type PublicKernelNonTailRequest, type PublicKernelTailRequest, PublicKernelType } from '@aztec/circuit-types'; +import { + type PublicInputsAndProof, + type PublicKernelNonTailRequest, + type PublicKernelTailRequest, + PublicKernelType, + makePublicInputsAndProof, +} from '@aztec/circuit-types'; import { type CircuitSimulationStats } from '@aztec/circuit-types/stats'; import { type BaseOrMergeRollupPublicInputs, @@ -6,13 +12,17 @@ import { type BaseRollupInputs, type KernelCircuitPublicInputs, type MergeRollupInputs, - type ParityPublicInputs, + NESTED_RECURSIVE_PROOF_LENGTH, type Proof, type PublicKernelCircuitPublicInputs, + RECURSIVE_PROOF_LENGTH, + RootParityInput, type RootParityInputs, type RootRollupInputs, type RootRollupPublicInputs, + VerificationKeyAsFields, makeEmptyProof, + makeRecursiveProof, } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { elapsed } from '@aztec/foundation/timer'; @@ -41,6 +51,18 @@ import { type SimulationProvider, WASMSimulator } from '@aztec/simulator'; import { type CircuitProver, KernelArtifactMapping } from './interface.js'; +const VERIFICATION_KEYS: Record = { + BaseParityArtifact: VerificationKeyAsFields.makeFake(), + RootParityArtifact: VerificationKeyAsFields.makeFake(), + PublicKernelAppLogicArtifact: VerificationKeyAsFields.makeFake(), + PublicKernelSetupArtifact: VerificationKeyAsFields.makeFake(), + PublicKernelTailArtifact: VerificationKeyAsFields.makeFake(), + PublicKernelTeardownArtifact: VerificationKeyAsFields.makeFake(), + BaseRollupArtifact: VerificationKeyAsFields.makeFake(), + MergeRollupArtifact: VerificationKeyAsFields.makeFake(), + RootRollupArtifact: VerificationKeyAsFields.makeFake(), +}; + /** * A class for use in testing situations (e2e, unit test etc) * Simulates circuits using the most efficient method and performs no proving @@ -58,7 +80,7 @@ export class TestCircuitProver implements CircuitProver { * @param inputs - Inputs to the circuit. * @returns The public inputs of the parity circuit. */ - public async getBaseParityProof(inputs: BaseParityInputs): Promise<[ParityPublicInputs, Proof]> { + public async getBaseParityProof(inputs: BaseParityInputs): Promise> { const witnessMap = convertBaseParityInputsToWitnessMap(inputs); // use WASM here as it is faster for small circuits @@ -66,7 +88,13 @@ export class TestCircuitProver implements CircuitProver { const result = convertBaseParityOutputsFromWitnessMap(witness); - return Promise.resolve([result, makeEmptyProof()]); + const rootParityInput = new RootParityInput( + makeRecursiveProof(RECURSIVE_PROOF_LENGTH), + VERIFICATION_KEYS['BaseParityArtifact'], + result, + ); + + return Promise.resolve(rootParityInput); } /** @@ -74,7 +102,9 @@ export class TestCircuitProver implements CircuitProver { * @param inputs - Inputs to the circuit. * @returns The public inputs of the parity circuit. */ - public async getRootParityProof(inputs: RootParityInputs): Promise<[ParityPublicInputs, Proof]> { + public async getRootParityProof( + inputs: RootParityInputs, + ): Promise> { const witnessMap = convertRootParityInputsToWitnessMap(inputs); // use WASM here as it is faster for small circuits @@ -82,7 +112,13 @@ export class TestCircuitProver implements CircuitProver { const result = convertRootParityOutputsFromWitnessMap(witness); - return Promise.resolve([result, makeEmptyProof()]); + const rootParityInput = new RootParityInput( + makeRecursiveProof(NESTED_RECURSIVE_PROOF_LENGTH), + VERIFICATION_KEYS['RootParityArtifact'], + result, + ); + + return Promise.resolve(rootParityInput); } /** @@ -90,21 +126,25 @@ export class TestCircuitProver implements CircuitProver { * @param input - Inputs to the circuit. * @returns The public inputs as outputs of the simulation. */ - public async getBaseRollupProof(input: BaseRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + public async getBaseRollupProof( + input: BaseRollupInputs, + ): Promise> { const witnessMap = convertSimulatedBaseRollupInputsToWitnessMap(input); const witness = await this.simulationProvider.simulateCircuit(witnessMap, SimulatedBaseRollupArtifact); const result = convertSimulatedBaseRollupOutputsFromWitnessMap(witness); - return Promise.resolve([result, makeEmptyProof()]); + return makePublicInputsAndProof(result, makeEmptyProof()); } /** * Simulates the merge rollup circuit from its inputs. * @param input - Inputs to the circuit. * @returns The public inputs as outputs of the simulation. */ - public async getMergeRollupProof(input: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + public async getMergeRollupProof( + input: MergeRollupInputs, + ): Promise> { const witnessMap = convertMergeRollupInputsToWitnessMap(input); // use WASM here as it is faster for small circuits @@ -112,7 +152,7 @@ export class TestCircuitProver implements CircuitProver { const result = convertMergeRollupOutputsFromWitnessMap(witness); - return Promise.resolve([result, makeEmptyProof()]); + return makePublicInputsAndProof(result, makeEmptyProof()); } /** @@ -120,7 +160,7 @@ export class TestCircuitProver implements CircuitProver { * @param input - Inputs to the circuit. * @returns The public inputs as outputs of the simulation. */ - public async getRootRollupProof(input: RootRollupInputs): Promise<[RootRollupPublicInputs, Proof]> { + public async getRootRollupProof(input: RootRollupInputs): Promise> { const witnessMap = convertRootRollupInputsToWitnessMap(input); // use WASM here as it is faster for small circuits @@ -135,12 +175,12 @@ export class TestCircuitProver implements CircuitProver { inputSize: input.toBuffer().length, outputSize: result.toBuffer().length, } satisfies CircuitSimulationStats); - return Promise.resolve([result, makeEmptyProof()]); + return makePublicInputsAndProof(result, makeEmptyProof()); } public async getPublicKernelProof( kernelRequest: PublicKernelNonTailRequest, - ): Promise<[PublicKernelCircuitPublicInputs, Proof]> { + ): Promise> { const kernelOps = KernelArtifactMapping[kernelRequest.type]; if (kernelOps === undefined) { throw new Error(`Unable to prove for kernel type ${PublicKernelType[kernelRequest.type]}`); @@ -150,10 +190,12 @@ export class TestCircuitProver implements CircuitProver { const witness = await this.wasmSimulator.simulateCircuit(witnessMap, ServerCircuitArtifacts[kernelOps.artifact]); const result = kernelOps.convertOutputs(witness); - return [result, makeEmptyProof()]; + return makePublicInputsAndProof(result, makeEmptyProof()); } - public async getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise<[KernelCircuitPublicInputs, Proof]> { + public async getPublicTailProof( + kernelRequest: PublicKernelTailRequest, + ): Promise> { const witnessMap = convertPublicTailInputsToWitnessMap(kernelRequest.inputs); // use WASM here as it is faster for small circuits const witness = await this.wasmSimulator.simulateCircuit( @@ -162,7 +204,7 @@ export class TestCircuitProver implements CircuitProver { ); const result = convertPublicTailOutputFromWitnessMap(witness); - return [result, makeEmptyProof()]; + return makePublicInputsAndProof(result, makeEmptyProof()); } // Not implemented for test circuits