Skip to content

Commit

Permalink
feat: Implement recursive verification in the parity circuits (#6006)
Browse files Browse the repository at this point in the history
This PR introduces recusive verification to the parity circuits. The
base parity circuits are verified in the root parity circuit and the
root parity circuit is verified by the root rollup.
  • Loading branch information
PhilWindle authored Apr 26, 2024
1 parent 0ebee28 commit a5b6dac
Show file tree
Hide file tree
Showing 50 changed files with 1,293 additions and 383 deletions.
75 changes: 70 additions & 5 deletions barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ std::vector<acir_format::AcirFormat> get_constraint_systems(std::string const& b
return acir_format::program_buf_to_acir_format(bytecode);
}

std::string proof_to_json(std::vector<bb::fr>& proof)
{
return format("[", join(map(proof, [](auto fr) { return format("\"", fr, "\""); })), "]");
}

std::string vk_to_json(std::vector<bb::fr>& 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
*
Expand Down Expand Up @@ -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<plonk::verification_key_data>(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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -569,6 +579,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<bb::plonk::verification_key> 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<std::string>& args, const std::string& flag)
{
return std::find(args.begin(), args.end(), flag) != args.end();
Expand Down Expand Up @@ -629,6 +691,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") {
Expand Down
3 changes: 3 additions & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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],
Expand All @@ -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() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
}

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],
}
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod root_parity_input;
mod root_parity_inputs;
mod root_rollup_parity_input;
Original file line number Diff line number Diff line change
@@ -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(),
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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];
Expand All @@ -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
);
}
}
}
Expand All @@ -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,
Expand All @@ -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();
Expand Down
Loading

0 comments on commit a5b6dac

Please sign in to comment.