-
Notifications
You must be signed in to change notification settings - Fork 37
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
Changes from all commits
c95155c
8a9f38f
9af65fc
b7e79e4
e346646
0314e5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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], | ||
|
@@ -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>, | ||
|
@@ -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) | ||
|
@@ -410,7 +480,9 @@ where | |
Ok(Self { | ||
root, | ||
aggregation, | ||
two_to_one_aggregation, | ||
block, | ||
two_to_one_block, | ||
by_table, | ||
}) | ||
} | ||
|
@@ -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, | ||
} | ||
} | ||
|
@@ -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. | ||
/// | ||
|
@@ -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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.