Skip to content

Commit e110090

Browse files
committed
Add dummy circuit to final extraction.
1 parent 1067757 commit e110090

File tree

15 files changed

+814
-170
lines changed

15 files changed

+814
-170
lines changed

mp2-v1/src/api.rs

+21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use anyhow::Result;
2020
use itertools::Itertools;
2121
use mp2_common::{
2222
digest::Digest,
23+
group_hashing::map_to_curve_point,
2324
poseidon::H,
2425
types::HashOutput,
2526
utils::{Fieldable, ToFields},
@@ -141,6 +142,9 @@ pub fn generate_proof(params: &PublicParameters, input: CircuitInput) -> Result<
141142
length_circuit_set,
142143
)
143144
}
145+
final_extraction::CircuitInput::NoProvable(input) => {
146+
params.final_extraction.generate_no_provable_proof(input)
147+
}
144148
}
145149
}
146150
CircuitInput::CellsTree(input) => verifiable_db::api::generate_proof(
@@ -243,3 +247,20 @@ pub fn metadata_hash(
243247
// compute final hash
244248
combine_digest_and_block(contract_digest + value_digest)
245249
}
250+
251+
/// Compute the metadata hash for a table including no provable extraction data.
252+
/// The input is a metadata digest set by the caller, then generate the metadata hash
253+
/// same as the DB computation.
254+
pub fn no_provable_metadata_hash(metadata_digest: &Digest) -> MetadataHash {
255+
// Add the prefix to the metadata digest to ensure the metadata digest
256+
// will keep track of whether we use this dummy circuit or not.
257+
// It's similar logic as the dummy circuit of final extraction.
258+
let prefix = final_extraction::DUMMY_METADATA_DIGEST_PREFIX.to_fields();
259+
let inputs = prefix
260+
.into_iter()
261+
.chain(metadata_digest.to_fields())
262+
.collect_vec();
263+
let digest = map_to_curve_point(&inputs);
264+
265+
combine_digest_and_block(digest)
266+
}

mp2-v1/src/final_extraction/api.rs

+56-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
1-
use mp2_common::{self, default_config, digest::TableDimension, proof::ProofWithVK, C, D, F};
2-
use plonky2::{iop::target::Target, plonk::circuit_data::VerifierCircuitData};
1+
use alloy::primitives::U256;
2+
use anyhow::Result;
3+
use itertools::Itertools;
4+
use mp2_common::{
5+
self, default_config,
6+
digest::{Digest, TableDimension},
7+
proof::ProofWithVK,
8+
types::HashOutput,
9+
utils::Packer,
10+
C, D, F,
11+
};
12+
use plonky2::{field::types::Field, iop::target::Target, plonk::circuit_data::VerifierCircuitData};
313
use recursion_framework::{
414
circuit_builder::{CircuitWithUniversalVerifier, CircuitWithUniversalVerifierBuilder},
515
framework::{prepare_recursive_circuit_for_circuit_set, RecursiveCircuits},
616
};
7-
817
use serde::{Deserialize, Serialize};
918

1019
use super::{
1120
base_circuit::BaseCircuitInput,
21+
dummy_circuit::DummyWires,
1222
lengthed_circuit::LengthedRecursiveWires,
1323
merge_circuit::{MergeTable, MergeTableRecursiveWires},
1424
simple_circuit::SimpleCircuitRecursiveWires,
15-
BaseCircuitProofInputs, LengthedCircuit, MergeCircuit, PublicInputs, SimpleCircuit,
25+
BaseCircuitProofInputs, DummyCircuit, LengthedCircuit, MergeCircuit, PublicInputs,
26+
SimpleCircuit,
1627
};
1728

18-
use anyhow::Result;
1929
pub enum CircuitInput {
2030
Simple(SimpleCircuitInput),
2131
Lengthed(LengthedCircuitInput),
2232
MergeTable(MergeCircuitInput),
33+
NoProvable(DummyCircuit),
2334
}
2435
#[derive(Clone, Debug)]
2536
pub struct FinalExtractionBuilderParams {
@@ -51,10 +62,11 @@ pub struct PublicParameters {
5162
simple: CircuitWithUniversalVerifier<F, C, D, 0, SimpleCircuitRecursiveWires>,
5263
lengthed: CircuitWithUniversalVerifier<F, C, D, 0, LengthedRecursiveWires>,
5364
merge: CircuitWithUniversalVerifier<F, C, D, 0, MergeTableRecursiveWires>,
65+
dummy: CircuitWithUniversalVerifier<F, C, D, 0, DummyWires>,
5466
circuit_set: RecursiveCircuits<F, C, D>,
5567
}
5668

57-
const FINAL_EXTRACTION_CIRCUIT_SET_SIZE: usize = 2;
69+
const FINAL_EXTRACTION_CIRCUIT_SET_SIZE: usize = 4;
5870
pub(super) const NUM_IO: usize = PublicInputs::<Target>::TOTAL_LEN;
5971

6072
impl PublicParameters {
@@ -76,12 +88,14 @@ impl PublicParameters {
7688
);
7789
let simple = builder.build_circuit(builder_params.clone());
7890
let lengthed = builder.build_circuit(builder_params.clone());
79-
let merge = builder.build_circuit(builder_params);
91+
let merge = builder.build_circuit(builder_params.clone());
92+
let dummy = builder.build_circuit(builder_params);
8093

8194
let circuits = vec![
8295
prepare_recursive_circuit_for_circuit_set(&simple),
8396
prepare_recursive_circuit_for_circuit_set(&lengthed),
8497
prepare_recursive_circuit_for_circuit_set(&merge),
98+
prepare_recursive_circuit_for_circuit_set(&dummy),
8599
];
86100

87101
let circuit_set = RecursiveCircuits::new(circuits);
@@ -90,6 +104,7 @@ impl PublicParameters {
90104
simple,
91105
lengthed,
92106
merge,
107+
dummy,
93108
circuit_set,
94109
}
95110
}
@@ -160,6 +175,13 @@ impl PublicParameters {
160175
ProofWithVK::serialize(&(proof, self.lengthed.circuit_data().verifier_only.clone()).into())
161176
}
162177

178+
pub(crate) fn generate_no_provable_proof(&self, input: DummyCircuit) -> Result<Vec<u8>> {
179+
let proof = self
180+
.circuit_set
181+
.generate_proof(&self.dummy, [], [], input)?;
182+
ProofWithVK::serialize(&(proof, self.dummy.circuit_data().verifier_only.clone()).into())
183+
}
184+
163185
pub(crate) fn get_circuit_set(&self) -> &RecursiveCircuits<F, C, D> {
164186
&self.circuit_set
165187
}
@@ -230,6 +252,33 @@ impl CircuitInput {
230252
let length_proof = ProofWithVK::deserialize(&length_proof)?;
231253
Ok(Self::Lengthed(LengthedCircuitInput { base, length_proof }))
232254
}
255+
/// Instantiate inputs for the dummy circuit dealing with no provable extraction case
256+
pub fn new_no_provable_input(
257+
is_merge: bool,
258+
block_number: U256,
259+
block_hash: HashOutput,
260+
prev_block_hash: HashOutput,
261+
metadata_digest: Digest,
262+
row_digest: Digest,
263+
) -> Self {
264+
let [block_hash, prev_block_hash] = [block_hash, prev_block_hash].map(|h| {
265+
h.pack(mp2_common::utils::Endianness::Little)
266+
.into_iter()
267+
.map(F::from_canonical_u32)
268+
.collect_vec()
269+
.try_into()
270+
.unwrap()
271+
});
272+
273+
Self::NoProvable(DummyCircuit::new(
274+
is_merge,
275+
block_number,
276+
block_hash,
277+
prev_block_hash,
278+
metadata_digest,
279+
row_digest,
280+
))
281+
}
233282
}
234283

235284
#[cfg(test)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
//! The dummy circuit used for generating the proof of no provable indexing data
2+
3+
use super::{
4+
api::{FinalExtractionBuilderParams, NUM_IO},
5+
PublicInputs, DUMMY_METADATA_DIGEST_PREFIX,
6+
};
7+
use alloy::primitives::U256;
8+
use anyhow::Result;
9+
use derive_more::derive::Constructor;
10+
use itertools::Itertools;
11+
use mp2_common::{
12+
digest::Digest,
13+
group_hashing::CircuitBuilderGroupHashing,
14+
keccak::PACKED_HASH_LEN,
15+
public_inputs::PublicInputCommon,
16+
serialization::{deserialize, serialize},
17+
types::CBuilder,
18+
u256::{CircuitBuilderU256, UInt256Target, WitnessWriteU256},
19+
utils::{ToFields, ToTargets},
20+
D, F,
21+
};
22+
use plonky2::{
23+
iop::{
24+
target::{BoolTarget, Target},
25+
witness::{PartialWitness, WitnessWrite},
26+
},
27+
plonk::proof::ProofWithPublicInputsTarget,
28+
};
29+
use plonky2_ecgfp5::gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget, PartialWitnessCurve};
30+
use recursion_framework::circuit_builder::CircuitLogicWires;
31+
use serde::{Deserialize, Serialize};
32+
use std::array;
33+
34+
#[derive(Clone, Debug, Constructor)]
35+
pub struct DummyCircuit {
36+
/// Merge flag
37+
is_merge: bool,
38+
/// Block number
39+
block_number: U256,
40+
/// Packed block hash
41+
block_hash: [F; PACKED_HASH_LEN],
42+
/// Packed block hash of the previous block
43+
prev_block_hash: [F; PACKED_HASH_LEN],
44+
/// Metadata digest for the rows extracted
45+
/// This value can be computed outside of the circuit depending on the data source,
46+
/// the circuits don’t care how it is computed given that we are not proving the
47+
/// provenance of the data.
48+
metadata_digest: Digest,
49+
/// Row values digest of all the rows extracted
50+
/// This must corresponds to the value digest that will be re-computed when
51+
/// constructing the rows tree for the current block.
52+
row_digest: Digest,
53+
}
54+
55+
#[derive(Clone, Debug, Serialize, Deserialize)]
56+
pub struct DummyWires {
57+
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
58+
is_merge: BoolTarget,
59+
block_number: UInt256Target,
60+
block_hash: [Target; PACKED_HASH_LEN],
61+
prev_block_hash: [Target; PACKED_HASH_LEN],
62+
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
63+
metadata_digest: CurveTarget,
64+
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
65+
row_digest: CurveTarget,
66+
}
67+
68+
impl DummyCircuit {
69+
fn build(b: &mut CBuilder) -> DummyWires {
70+
let is_merge = b.add_virtual_bool_target_unsafe();
71+
let block_number = b.add_virtual_u256_unsafe();
72+
let [block_hash, prev_block_hash] = array::from_fn(|_| b.add_virtual_target_arr());
73+
let [metadata_digest, row_digest] = array::from_fn(|_| b.add_virtual_curve_target());
74+
75+
// Add the prefix to the metadata digest to ensure the metadata digest
76+
// will keep track of whether we use this dummy circuit or not.
77+
let prefix = b.constants(&DUMMY_METADATA_DIGEST_PREFIX.to_fields());
78+
let inputs = prefix
79+
.into_iter()
80+
.chain(metadata_digest.to_targets())
81+
.collect_vec();
82+
let encoded_metadata_digest = b.map_to_curve_point(&inputs);
83+
84+
PublicInputs::new(
85+
&block_hash,
86+
&prev_block_hash,
87+
&row_digest.to_targets(),
88+
&encoded_metadata_digest.to_targets(),
89+
&block_number.to_targets(),
90+
&[is_merge.target],
91+
)
92+
.register_args(b);
93+
94+
DummyWires {
95+
is_merge,
96+
block_number,
97+
block_hash,
98+
prev_block_hash,
99+
metadata_digest,
100+
row_digest,
101+
}
102+
}
103+
104+
fn assign(&self, pw: &mut PartialWitness<F>, wires: &DummyWires) {
105+
pw.set_bool_target(wires.is_merge, self.is_merge);
106+
pw.set_u256_target(&wires.block_number, self.block_number);
107+
[
108+
(wires.block_hash, self.block_hash),
109+
(wires.prev_block_hash, self.prev_block_hash),
110+
]
111+
.iter()
112+
.for_each(|(t, v)| {
113+
pw.set_target_arr(t, v);
114+
});
115+
[
116+
(wires.metadata_digest, self.metadata_digest),
117+
(wires.row_digest, self.row_digest),
118+
]
119+
.iter()
120+
.for_each(|(t, v)| {
121+
pw.set_curve_target(*t, v.to_weierstrass());
122+
});
123+
}
124+
}
125+
126+
impl CircuitLogicWires<F, D, 0> for DummyWires {
127+
type CircuitBuilderParams = FinalExtractionBuilderParams;
128+
type Inputs = DummyCircuit;
129+
130+
const NUM_PUBLIC_INPUTS: usize = NUM_IO;
131+
132+
fn circuit_logic(
133+
builder: &mut CBuilder,
134+
_verified_proofs: [&ProofWithPublicInputsTarget<D>; 0],
135+
_builder_parameters: Self::CircuitBuilderParams,
136+
) -> Self {
137+
DummyCircuit::build(builder)
138+
}
139+
140+
fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness<F>) -> Result<()> {
141+
inputs.assign(pw, self);
142+
Ok(())
143+
}
144+
}
145+
146+
#[cfg(test)]
147+
mod test {
148+
use super::*;
149+
use mp2_common::{group_hashing::map_to_curve_point, C};
150+
use mp2_test::circuit::{run_circuit, UserCircuit};
151+
use plonky2::field::types::Sample;
152+
use rand::{thread_rng, Rng};
153+
154+
impl UserCircuit<F, D> for DummyCircuit {
155+
type Wires = DummyWires;
156+
157+
fn build(cb: &mut CBuilder) -> Self::Wires {
158+
DummyCircuit::build(cb)
159+
}
160+
161+
fn prove(&self, pw: &mut PartialWitness<F>, wires: &Self::Wires) {
162+
self.assign(pw, wires);
163+
}
164+
}
165+
166+
#[test]
167+
fn test_final_extraction_dummy_circuit() {
168+
let rng = &mut thread_rng();
169+
170+
let is_merge = rng.gen();
171+
let block_number = U256::from(rng.gen::<u64>());
172+
let [block_hash, prev_block_hash] = array::from_fn(|_| F::rand_array());
173+
let [metadata_digest, row_digest] = array::from_fn(|_| Digest::sample(rng));
174+
175+
let test_circuit = DummyCircuit::new(
176+
is_merge,
177+
block_number,
178+
block_hash,
179+
prev_block_hash,
180+
metadata_digest,
181+
row_digest,
182+
);
183+
184+
let proof = run_circuit::<F, D, C, _>(test_circuit);
185+
let pi = PublicInputs::from_slice(&proof.public_inputs);
186+
187+
// Check the public inputs.
188+
assert_eq!(pi.is_merge_case(), is_merge);
189+
assert_eq!(U256::from(pi.block_number()), block_number);
190+
assert_eq!(pi.block_hash_raw(), block_hash);
191+
assert_eq!(pi.prev_block_hash_raw(), prev_block_hash);
192+
assert_eq!(pi.value_point(), row_digest.to_weierstrass());
193+
{
194+
let prefix = DUMMY_METADATA_DIGEST_PREFIX.to_fields();
195+
let inputs = prefix
196+
.into_iter()
197+
.chain(metadata_digest.to_fields())
198+
.collect_vec();
199+
let expected_metadata_digest = map_to_curve_point(&inputs);
200+
assert_eq!(
201+
pi.metadata_point(),
202+
expected_metadata_digest.to_weierstrass()
203+
);
204+
}
205+
}
206+
}

mp2-v1/src/final_extraction/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub(crate) mod api;
22
mod base_circuit;
3+
mod dummy_circuit;
34
mod lengthed_circuit;
45
mod merge_circuit;
56
mod public_inputs;
@@ -9,6 +10,11 @@ pub use api::{CircuitInput, PublicParameters};
910
pub use public_inputs::PublicInputs;
1011

1112
pub(crate) use base_circuit::BaseCircuitProofInputs;
13+
pub(crate) use dummy_circuit::DummyCircuit;
1214
pub(crate) use lengthed_circuit::LengthedCircuitInput as LengthedCircuit;
1315
pub(crate) use merge_circuit::MergeCircuitInput as MergeCircuit;
1416
pub(crate) use simple_circuit::SimpleCircuitInput as SimpleCircuit;
17+
18+
/// The prefix to ensure the metadata digest will keep track of whether
19+
/// we use this dummy circuit or not
20+
pub(crate) const DUMMY_METADATA_DIGEST_PREFIX: &[u8] = b"DUMMY_EXTRACTION";

0 commit comments

Comments
 (0)