Skip to content

Commit

Permalink
Add dummy circuit to final extraction.
Browse files Browse the repository at this point in the history
  • Loading branch information
silathdiir committed Dec 1, 2024
1 parent 1067757 commit e110090
Show file tree
Hide file tree
Showing 15 changed files with 814 additions and 170 deletions.
21 changes: 21 additions & 0 deletions mp2-v1/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use anyhow::Result;
use itertools::Itertools;
use mp2_common::{
digest::Digest,
group_hashing::map_to_curve_point,
poseidon::H,
types::HashOutput,
utils::{Fieldable, ToFields},
Expand Down Expand Up @@ -141,6 +142,9 @@ pub fn generate_proof(params: &PublicParameters, input: CircuitInput) -> Result<
length_circuit_set,
)
}
final_extraction::CircuitInput::NoProvable(input) => {
params.final_extraction.generate_no_provable_proof(input)
}
}
}
CircuitInput::CellsTree(input) => verifiable_db::api::generate_proof(
Expand Down Expand Up @@ -243,3 +247,20 @@ pub fn metadata_hash(
// compute final hash
combine_digest_and_block(contract_digest + value_digest)
}

/// Compute the metadata hash for a table including no provable extraction data.
/// The input is a metadata digest set by the caller, then generate the metadata hash
/// same as the DB computation.
pub fn no_provable_metadata_hash(metadata_digest: &Digest) -> MetadataHash {
// Add the prefix to the metadata digest to ensure the metadata digest
// will keep track of whether we use this dummy circuit or not.
// It's similar logic as the dummy circuit of final extraction.
let prefix = final_extraction::DUMMY_METADATA_DIGEST_PREFIX.to_fields();
let inputs = prefix
.into_iter()
.chain(metadata_digest.to_fields())
.collect_vec();
let digest = map_to_curve_point(&inputs);

combine_digest_and_block(digest)
}
63 changes: 56 additions & 7 deletions mp2-v1/src/final_extraction/api.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
use mp2_common::{self, default_config, digest::TableDimension, proof::ProofWithVK, C, D, F};
use plonky2::{iop::target::Target, plonk::circuit_data::VerifierCircuitData};
use alloy::primitives::U256;
use anyhow::Result;
use itertools::Itertools;
use mp2_common::{
self, default_config,
digest::{Digest, TableDimension},
proof::ProofWithVK,
types::HashOutput,
utils::Packer,
C, D, F,
};
use plonky2::{field::types::Field, iop::target::Target, plonk::circuit_data::VerifierCircuitData};
use recursion_framework::{
circuit_builder::{CircuitWithUniversalVerifier, CircuitWithUniversalVerifierBuilder},
framework::{prepare_recursive_circuit_for_circuit_set, RecursiveCircuits},
};

use serde::{Deserialize, Serialize};

use super::{
base_circuit::BaseCircuitInput,
dummy_circuit::DummyWires,
lengthed_circuit::LengthedRecursiveWires,
merge_circuit::{MergeTable, MergeTableRecursiveWires},
simple_circuit::SimpleCircuitRecursiveWires,
BaseCircuitProofInputs, LengthedCircuit, MergeCircuit, PublicInputs, SimpleCircuit,
BaseCircuitProofInputs, DummyCircuit, LengthedCircuit, MergeCircuit, PublicInputs,
SimpleCircuit,
};

use anyhow::Result;
pub enum CircuitInput {
Simple(SimpleCircuitInput),
Lengthed(LengthedCircuitInput),
MergeTable(MergeCircuitInput),
NoProvable(DummyCircuit),
}
#[derive(Clone, Debug)]
pub struct FinalExtractionBuilderParams {
Expand Down Expand Up @@ -51,10 +62,11 @@ pub struct PublicParameters {
simple: CircuitWithUniversalVerifier<F, C, D, 0, SimpleCircuitRecursiveWires>,
lengthed: CircuitWithUniversalVerifier<F, C, D, 0, LengthedRecursiveWires>,
merge: CircuitWithUniversalVerifier<F, C, D, 0, MergeTableRecursiveWires>,
dummy: CircuitWithUniversalVerifier<F, C, D, 0, DummyWires>,
circuit_set: RecursiveCircuits<F, C, D>,
}

const FINAL_EXTRACTION_CIRCUIT_SET_SIZE: usize = 2;
const FINAL_EXTRACTION_CIRCUIT_SET_SIZE: usize = 4;
pub(super) const NUM_IO: usize = PublicInputs::<Target>::TOTAL_LEN;

impl PublicParameters {
Expand All @@ -76,12 +88,14 @@ impl PublicParameters {
);
let simple = builder.build_circuit(builder_params.clone());
let lengthed = builder.build_circuit(builder_params.clone());
let merge = builder.build_circuit(builder_params);
let merge = builder.build_circuit(builder_params.clone());
let dummy = builder.build_circuit(builder_params);

let circuits = vec![
prepare_recursive_circuit_for_circuit_set(&simple),
prepare_recursive_circuit_for_circuit_set(&lengthed),
prepare_recursive_circuit_for_circuit_set(&merge),
prepare_recursive_circuit_for_circuit_set(&dummy),
];

let circuit_set = RecursiveCircuits::new(circuits);
Expand All @@ -90,6 +104,7 @@ impl PublicParameters {
simple,
lengthed,
merge,
dummy,
circuit_set,
}
}
Expand Down Expand Up @@ -160,6 +175,13 @@ impl PublicParameters {
ProofWithVK::serialize(&(proof, self.lengthed.circuit_data().verifier_only.clone()).into())
}

pub(crate) fn generate_no_provable_proof(&self, input: DummyCircuit) -> Result<Vec<u8>> {
let proof = self
.circuit_set
.generate_proof(&self.dummy, [], [], input)?;
ProofWithVK::serialize(&(proof, self.dummy.circuit_data().verifier_only.clone()).into())
}

pub(crate) fn get_circuit_set(&self) -> &RecursiveCircuits<F, C, D> {
&self.circuit_set
}
Expand Down Expand Up @@ -230,6 +252,33 @@ impl CircuitInput {
let length_proof = ProofWithVK::deserialize(&length_proof)?;
Ok(Self::Lengthed(LengthedCircuitInput { base, length_proof }))
}
/// Instantiate inputs for the dummy circuit dealing with no provable extraction case
pub fn new_no_provable_input(
is_merge: bool,
block_number: U256,
block_hash: HashOutput,
prev_block_hash: HashOutput,
metadata_digest: Digest,
row_digest: Digest,
) -> Self {
let [block_hash, prev_block_hash] = [block_hash, prev_block_hash].map(|h| {
h.pack(mp2_common::utils::Endianness::Little)
.into_iter()
.map(F::from_canonical_u32)
.collect_vec()
.try_into()
.unwrap()
});

Self::NoProvable(DummyCircuit::new(
is_merge,
block_number,
block_hash,
prev_block_hash,
metadata_digest,
row_digest,
))
}
}

#[cfg(test)]
Expand Down
206 changes: 206 additions & 0 deletions mp2-v1/src/final_extraction/dummy_circuit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
//! The dummy circuit used for generating the proof of no provable indexing data

use super::{
api::{FinalExtractionBuilderParams, NUM_IO},
PublicInputs, DUMMY_METADATA_DIGEST_PREFIX,
};
use alloy::primitives::U256;
use anyhow::Result;
use derive_more::derive::Constructor;
use itertools::Itertools;
use mp2_common::{
digest::Digest,
group_hashing::CircuitBuilderGroupHashing,
keccak::PACKED_HASH_LEN,
public_inputs::PublicInputCommon,
serialization::{deserialize, serialize},
types::CBuilder,
u256::{CircuitBuilderU256, UInt256Target, WitnessWriteU256},
utils::{ToFields, ToTargets},
D, F,
};
use plonky2::{
iop::{
target::{BoolTarget, Target},
witness::{PartialWitness, WitnessWrite},
},
plonk::proof::ProofWithPublicInputsTarget,
};
use plonky2_ecgfp5::gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget, PartialWitnessCurve};
use recursion_framework::circuit_builder::CircuitLogicWires;
use serde::{Deserialize, Serialize};
use std::array;

#[derive(Clone, Debug, Constructor)]
pub struct DummyCircuit {
/// Merge flag
is_merge: bool,
/// Block number
block_number: U256,
/// Packed block hash
block_hash: [F; PACKED_HASH_LEN],
/// Packed block hash of the previous block
prev_block_hash: [F; PACKED_HASH_LEN],
/// Metadata digest for the rows extracted
/// This value can be computed outside of the circuit depending on the data source,
/// the circuits don’t care how it is computed given that we are not proving the
/// provenance of the data.
metadata_digest: Digest,
/// Row values digest of all the rows extracted
/// This must corresponds to the value digest that will be re-computed when
/// constructing the rows tree for the current block.
row_digest: Digest,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DummyWires {
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
is_merge: BoolTarget,
block_number: UInt256Target,
block_hash: [Target; PACKED_HASH_LEN],
prev_block_hash: [Target; PACKED_HASH_LEN],
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
metadata_digest: CurveTarget,
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
row_digest: CurveTarget,
}

impl DummyCircuit {
fn build(b: &mut CBuilder) -> DummyWires {
let is_merge = b.add_virtual_bool_target_unsafe();
let block_number = b.add_virtual_u256_unsafe();
let [block_hash, prev_block_hash] = array::from_fn(|_| b.add_virtual_target_arr());
let [metadata_digest, row_digest] = array::from_fn(|_| b.add_virtual_curve_target());

// Add the prefix to the metadata digest to ensure the metadata digest
// will keep track of whether we use this dummy circuit or not.
let prefix = b.constants(&DUMMY_METADATA_DIGEST_PREFIX.to_fields());
let inputs = prefix
.into_iter()
.chain(metadata_digest.to_targets())
.collect_vec();
let encoded_metadata_digest = b.map_to_curve_point(&inputs);

PublicInputs::new(
&block_hash,
&prev_block_hash,
&row_digest.to_targets(),
&encoded_metadata_digest.to_targets(),
&block_number.to_targets(),
&[is_merge.target],
)
.register_args(b);

DummyWires {
is_merge,
block_number,
block_hash,
prev_block_hash,
metadata_digest,
row_digest,
}
}

fn assign(&self, pw: &mut PartialWitness<F>, wires: &DummyWires) {
pw.set_bool_target(wires.is_merge, self.is_merge);
pw.set_u256_target(&wires.block_number, self.block_number);
[
(wires.block_hash, self.block_hash),
(wires.prev_block_hash, self.prev_block_hash),
]
.iter()
.for_each(|(t, v)| {
pw.set_target_arr(t, v);
});
[
(wires.metadata_digest, self.metadata_digest),
(wires.row_digest, self.row_digest),
]
.iter()
.for_each(|(t, v)| {
pw.set_curve_target(*t, v.to_weierstrass());
});
}
}

impl CircuitLogicWires<F, D, 0> for DummyWires {
type CircuitBuilderParams = FinalExtractionBuilderParams;
type Inputs = DummyCircuit;

const NUM_PUBLIC_INPUTS: usize = NUM_IO;

fn circuit_logic(
builder: &mut CBuilder,
_verified_proofs: [&ProofWithPublicInputsTarget<D>; 0],
_builder_parameters: Self::CircuitBuilderParams,
) -> Self {
DummyCircuit::build(builder)
}

fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness<F>) -> Result<()> {
inputs.assign(pw, self);
Ok(())
}
}

#[cfg(test)]
mod test {
use super::*;
use mp2_common::{group_hashing::map_to_curve_point, C};
use mp2_test::circuit::{run_circuit, UserCircuit};
use plonky2::field::types::Sample;
use rand::{thread_rng, Rng};

impl UserCircuit<F, D> for DummyCircuit {
type Wires = DummyWires;

fn build(cb: &mut CBuilder) -> Self::Wires {
DummyCircuit::build(cb)
}

fn prove(&self, pw: &mut PartialWitness<F>, wires: &Self::Wires) {
self.assign(pw, wires);
}
}

#[test]
fn test_final_extraction_dummy_circuit() {
let rng = &mut thread_rng();

let is_merge = rng.gen();
let block_number = U256::from(rng.gen::<u64>());
let [block_hash, prev_block_hash] = array::from_fn(|_| F::rand_array());
let [metadata_digest, row_digest] = array::from_fn(|_| Digest::sample(rng));

let test_circuit = DummyCircuit::new(
is_merge,
block_number,
block_hash,
prev_block_hash,
metadata_digest,
row_digest,
);

let proof = run_circuit::<F, D, C, _>(test_circuit);
let pi = PublicInputs::from_slice(&proof.public_inputs);

// Check the public inputs.
assert_eq!(pi.is_merge_case(), is_merge);
assert_eq!(U256::from(pi.block_number()), block_number);
assert_eq!(pi.block_hash_raw(), block_hash);
assert_eq!(pi.prev_block_hash_raw(), prev_block_hash);
assert_eq!(pi.value_point(), row_digest.to_weierstrass());
{
let prefix = DUMMY_METADATA_DIGEST_PREFIX.to_fields();
let inputs = prefix
.into_iter()
.chain(metadata_digest.to_fields())
.collect_vec();
let expected_metadata_digest = map_to_curve_point(&inputs);
assert_eq!(
pi.metadata_point(),
expected_metadata_digest.to_weierstrass()
);
}
}
}
6 changes: 6 additions & 0 deletions mp2-v1/src/final_extraction/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod api;
mod base_circuit;
mod dummy_circuit;
mod lengthed_circuit;
mod merge_circuit;
mod public_inputs;
Expand All @@ -9,6 +10,11 @@ pub use api::{CircuitInput, PublicParameters};
pub use public_inputs::PublicInputs;

pub(crate) use base_circuit::BaseCircuitProofInputs;
pub(crate) use dummy_circuit::DummyCircuit;
pub(crate) use lengthed_circuit::LengthedCircuitInput as LengthedCircuit;
pub(crate) use merge_circuit::MergeCircuitInput as MergeCircuit;
pub(crate) use simple_circuit::SimpleCircuitInput as SimpleCircuit;

/// The prefix to ensure the metadata digest will keep track of whether
/// we use this dummy circuit or not
pub(crate) const DUMMY_METADATA_DIGEST_PREFIX: &[u8] = b"DUMMY_EXTRACTION";
Loading

0 comments on commit e110090

Please sign in to comment.