Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two-to-one aggregation circuits #216

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions evm_arithmetization/src/fixed_recursive_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ where
/// The aggregation circuit, which verifies two proofs that can either be
/// root or aggregation proofs.
pub aggregation: AggregationCircuitData<F, C, D>,
/// The two-to-one aggregation circuit, which verifies two unrelated
/// aggregation proofs.
pub two_to_one_aggregation: TwoToOneAggCircuitData<F, C, D>,
/// The block circuit, which verifies an aggregation root proof and an
/// optional previous block proof.
pub block: BlockCircuitData<F, C, D>,
/// The two-to-one block aggregation circuit, which verifies two unrelated
/// block proofs.
pub two_to_one_block: TwoToOneAggCircuitData<F, C, D>,
Comment on lines +77 to +83
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused about the two_to_one_aggregation.
I see the use of aggregating two final block proofs together, but why aggregating intermediary layers that don't share anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be useful in some cases. A "block proof" is just an "aggregation proof" along with a recursively verified previous block proof. But in some cases, the previous batch is already verified elsewhere (e.g. on L1), so the recursive proof is redundant.
I might be missing something though.

Comment on lines +77 to +83
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is the goal behind? This will only support one additional layer of aggregation, not an arbitrary one, which I would have thought was the desired goal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so too. I think this is just a temporary solution. @muursh ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aye it is just a place holder for now. We will have Einar make some modifications to the current approach.

/// Holds chains of circuits for each table and for each initial
/// `degree_bits`.
pub by_table: [RecursiveCircuitsForTable<F, C, D>; NUM_TABLES],
Expand Down Expand Up @@ -301,6 +307,60 @@ where
}
}

/// Data for the two-to-one aggregation circuit, which is used to generate a
/// proof of two unrelated proofs.
#[derive(Eq, PartialEq, Debug)]
pub struct TwoToOneAggCircuitData<F, C, const D: usize>
where
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
{
pub circuit: CircuitData<F, C, D>,
proof0: ProofWithPublicInputsTarget<D>,
proof1: ProofWithPublicInputsTarget<D>,
pv0: PublicValuesTarget,
pv1: PublicValuesTarget,
}

impl<F, C, const D: usize> TwoToOneAggCircuitData<F, C, D>
where
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
{
fn to_buffer(
&self,
buffer: &mut Vec<u8>,
gate_serializer: &dyn GateSerializer<F, D>,
generator_serializer: &dyn WitnessGeneratorSerializer<F, D>,
) -> IoResult<()> {
buffer.write_circuit_data(&self.circuit, gate_serializer, generator_serializer)?;
buffer.write_target_proof_with_public_inputs(&self.proof0)?;
buffer.write_target_proof_with_public_inputs(&self.proof1)?;
self.pv0.to_buffer(buffer)?;
self.pv1.to_buffer(buffer)?;
Ok(())
}

fn from_buffer(
buffer: &mut Buffer,
gate_serializer: &dyn GateSerializer<F, D>,
generator_serializer: &dyn WitnessGeneratorSerializer<F, D>,
) -> IoResult<Self> {
let circuit = buffer.read_circuit_data(gate_serializer, generator_serializer)?;
let agg_proof0 = buffer.read_target_proof_with_public_inputs()?;
let agg_proof1 = buffer.read_target_proof_with_public_inputs()?;
let pv0 = PublicValuesTarget::from_buffer(buffer)?;
let pv1 = PublicValuesTarget::from_buffer(buffer)?;
Ok(Self {
circuit,
proof0: agg_proof0,
proof1: agg_proof1,
pv0,
pv1,
})
}
}

impl<F, C, const D: usize> AllRecursiveCircuits<F, C, D>
where
F: RichField + Extendable<D>,
Expand Down Expand Up @@ -374,8 +434,18 @@ where
gate_serializer,
generator_serializer,
)?;
let two_to_one_aggregation = TwoToOneAggCircuitData::from_buffer(
&mut buffer,
gate_serializer,
generator_serializer,
)?;
let block =
BlockCircuitData::from_buffer(&mut buffer, gate_serializer, generator_serializer)?;
let two_to_one_block = TwoToOneAggCircuitData::from_buffer(
&mut buffer,
gate_serializer,
generator_serializer,
)?;

let by_table = match skip_tables {
true => (0..NUM_TABLES)
Expand Down Expand Up @@ -410,7 +480,9 @@ where
Ok(Self {
root,
aggregation,
two_to_one_aggregation,
block,
two_to_one_block,
by_table,
})
}
Expand Down Expand Up @@ -500,11 +572,15 @@ where
];
let root = Self::create_root_circuit(&by_table, stark_config);
let aggregation = Self::create_aggregation_circuit(&root);
let two_to_one_aggregation = Self::create_two_to_one_agg_circuit(&aggregation);
let block = Self::create_block_circuit(&aggregation);
let two_to_one_block = Self::create_two_to_one_block_circuit(&block);
Self {
root,
aggregation,
two_to_one_aggregation,
block,
two_to_one_block,
by_table,
}
}
Expand Down Expand Up @@ -1284,6 +1360,49 @@ where
)
}

/// Create a two-to-one aggregation proof, combining two unrelated
/// aggregation proofs into a single one.
///
/// # Arguments
///
/// - `proof0`: the first aggregation proof.
/// - `proof1`: the second aggregation proof.
/// - `pv0`: the public values associated to first proof.
/// - `pv1`: the public values associated to second proof.
///
/// # Outputs
///
/// This method outputs a [`ProofWithPublicInputs<F, C, D>`].
pub fn prove_two_to_one_aggregation(
&self,
proof0: &ProofWithPublicInputs<F, C, D>,
proof1: &ProofWithPublicInputs<F, C, D>,
pv0: PublicValues,
pv1: PublicValues,
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
let mut inputs = PartialWitness::new();

inputs.set_proof_with_pis_target(&self.two_to_one_aggregation.proof0, proof0);
inputs.set_proof_with_pis_target(&self.two_to_one_aggregation.proof1, proof1);

set_public_value_targets(&mut inputs, &self.two_to_one_aggregation.pv0, &pv0).map_err(
|_| anyhow::Error::msg("Invalid conversion when setting public values targets."),
)?;
set_public_value_targets(&mut inputs, &self.two_to_one_aggregation.pv1, &pv1).map_err(
|_| anyhow::Error::msg("Invalid conversion when setting public values targets."),
)?;

let proof = self.two_to_one_aggregation.circuit.prove(inputs)?;
Ok(proof)
}

pub fn verify_two_to_one_aggregation(
&self,
proof: &ProofWithPublicInputs<F, C, D>,
) -> anyhow::Result<()> {
self.two_to_one_aggregation.circuit.verify(proof.clone())
}

/// Create a final block proof, once all transactions of a given block have
/// been combined into a single aggregation proof.
///
Expand Down Expand Up @@ -1454,6 +1573,95 @@ where
&self.block.circuit.common,
)
}

/// Create a two-to-one block aggregation proof, combining two unrelated
/// block proofs into a single one.
///
/// # Arguments
///
/// - `proof0`: the first block proof.
/// - `proof1`: the second block proof.
/// - `pv0`: the public values associated to first proof.
/// - `pv1`: the public values associated to second proof.
///
/// # Outputs
///
/// This method outputs a [`ProofWithPublicInputs<F, C, D>`].
pub fn prove_two_to_one_block(
&self,
proof0: &ProofWithPublicInputs<F, C, D>,
proof1: &ProofWithPublicInputs<F, C, D>,
pv0: PublicValues,
pv1: PublicValues,
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
let mut inputs = PartialWitness::new();

inputs.set_proof_with_pis_target(&self.two_to_one_block.proof0, proof0);
inputs.set_proof_with_pis_target(&self.two_to_one_block.proof1, proof1);

set_public_value_targets(&mut inputs, &self.two_to_one_block.pv0, &pv0).map_err(|_| {
anyhow::Error::msg("Invalid conversion when setting public values targets.")
})?;
set_public_value_targets(&mut inputs, &self.two_to_one_block.pv1, &pv1).map_err(|_| {
anyhow::Error::msg("Invalid conversion when setting public values targets.")
})?;

let proof = self.two_to_one_block.circuit.prove(inputs)?;
Ok(proof)
}

pub fn verify_two_to_one_block(
&self,
proof: &ProofWithPublicInputs<F, C, D>,
) -> anyhow::Result<()> {
self.two_to_one_block.circuit.verify(proof.clone())
}

fn create_two_to_one_agg_circuit(
agg: &AggregationCircuitData<F, C, D>,
) -> TwoToOneAggCircuitData<F, C, D> {
let mut builder = CircuitBuilder::<F, D>::new(CircuitConfig::standard_recursion_config());
let pv0 = add_virtual_public_values(&mut builder);
let pv1 = add_virtual_public_values(&mut builder);
let agg_proof0 = builder.add_virtual_proof_with_pis(&agg.circuit.common);
let agg_proof1 = builder.add_virtual_proof_with_pis(&agg.circuit.common);
let agg_verifier_data = builder.constant_verifier_data(&agg.circuit.verifier_only);
builder.verify_proof::<C>(&agg_proof0, &agg_verifier_data, &agg.circuit.common);
builder.verify_proof::<C>(&agg_proof1, &agg_verifier_data, &agg.circuit.common);

let circuit = builder.build::<C>();

TwoToOneAggCircuitData {
circuit,
proof0: agg_proof0,
proof1: agg_proof1,
pv0,
pv1,
}
}

fn create_two_to_one_block_circuit(
block: &BlockCircuitData<F, C, D>,
) -> TwoToOneAggCircuitData<F, C, D> {
let mut builder = CircuitBuilder::<F, D>::new(CircuitConfig::standard_recursion_config());
let pv0 = add_virtual_public_values(&mut builder);
let pv1 = add_virtual_public_values(&mut builder);
let block_proof0 = builder.add_virtual_proof_with_pis(&block.circuit.common);
let block_proof1 = builder.add_virtual_proof_with_pis(&block.circuit.common);
let block_verifier_data = builder.constant_verifier_data(&block.circuit.verifier_only);
builder.verify_proof::<C>(&block_proof0, &block_verifier_data, &block.circuit.common);
builder.verify_proof::<C>(&block_proof1, &block_verifier_data, &block.circuit.common);

let circuit = builder.build::<C>();

TwoToOneAggCircuitData {
circuit,
proof0: block_proof0,
proof1: block_proof1,
pv0,
pv1,
}
}
}

/// A map between initial degree sizes and their associated shrinking recursion
Expand Down
2 changes: 1 addition & 1 deletion evm_arithmetization/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl PublicValues {
}

/// Trie hashes.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrieRoots {
/// State trie hash.
pub state_root: H256,
Expand Down
2 changes: 1 addition & 1 deletion evm_arithmetization/tests/log_opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> {
signed_txn: Some(txn_2.to_vec()),
withdrawals: vec![],
tries: tries_before,
trie_roots_after: trie_roots_after.clone(),
trie_roots_after,
contract_code,
checkpoint_state_trie_root,
block_metadata: block_1_metadata,
Expand Down
Loading