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

Receipt Leaf Circuit #405

Open
wants to merge 15 commits into
base: feat/receipt-trie
Choose a base branch
from
Prev Previous commit
Next Next commit
Added testing for final extraction API
Zyouell committed Dec 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit d644c701f6b8a9d6de7fb2968fc8b7145161406a
9 changes: 6 additions & 3 deletions mp2-common/src/array.rs
Original file line number Diff line number Diff line change
@@ -631,7 +631,7 @@ where
let arrays: Vec<Array<T, RANDOM_ACCESS_SIZE>> = (0..padded_size)
.map(|i| Array {
arr: create_array(|j| {
let index = 64 * i + j;
let index = RANDOM_ACCESS_SIZE * i + j;
if index < self.arr.len() {
self.arr[index]
} else {
@@ -647,7 +647,7 @@ where
let less_than_check = less_than_unsafe(b, at, array_size, 12);
Copy link
Collaborator

Choose a reason for hiding this comment

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

why unsafe is safe there ? Can you put that in a comment ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it was because on line 637 I range check at to be a 12-bit number anyway so it seemed pointless to do it twice.

let true_target = b._true();
b.connect(less_than_check.target, true_target.target);
b.range_check(at, 12);

let (low_bits, high_bits) = b.split_low_high(at, 6, 12);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry i'm a bit lost in this function. Can you write an example or just more doc about how you approach the decomposition and how you do the random access and the recombination at the end ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah sure thing


// Search each of the smaller arrays for the target at `low_bits`
@@ -1293,7 +1293,10 @@ mod test {
};
run_circuit::<F, D, C, _>(circuit);

arr2[0] += 1; // ensure arr2 is different from arr
arr2[0] = match arr2[0].checked_add(1) {
Some(num) => num,
None => arr2[0] - 1,
};
let res = panic::catch_unwind(|| {
let circuit = TestSliceEqual {
arr,
543 changes: 166 additions & 377 deletions mp2-common/src/eth.rs

Large diffs are not rendered by default.

37 changes: 29 additions & 8 deletions mp2-test/src/mpt_sequential.rs
Original file line number Diff line number Diff line change
@@ -50,9 +50,27 @@ pub fn generate_random_storage_mpt<const DEPTH: usize, const VALUE_LEN: usize>(
(trie, keys[right_key_idx].to_vec())
}

#[derive(Debug, Clone)]
pub struct ReceiptTestInfo<const NO_TOPICS: usize, const MAX_DATA: usize> {
/// The query which we have returned proofs for
pub query: ReceiptQuery<NO_TOPICS, MAX_DATA>,
/// The proofs for receipts relating to `self.query`
pub proofs: Vec<ReceiptProofInfo>,
}

impl<const NO_TOPICS: usize, const MAX_DATA: usize> ReceiptTestInfo<NO_TOPICS, MAX_DATA> {
/// Getter for the proofs
pub fn proofs(&self) -> Vec<ReceiptProofInfo> {
self.proofs.clone()
}
/// Getter for the query
pub fn query(&self) -> &ReceiptQuery<NO_TOPICS, MAX_DATA> {
&self.query
}
}
/// This function is used so that we can generate a Receipt Trie for a blog with varying transactions
/// (i.e. some we are interested in and some we are not).
pub fn generate_receipt_proofs() -> Vec<ReceiptProofInfo> {
pub fn generate_receipt_test_info() -> ReceiptTestInfo<1, 0> {
// Make a contract that emits events so we can pick up on them
sol! {
#[allow(missing_docs)]
@@ -78,11 +96,7 @@ pub fn generate_receipt_proofs() -> Vec<ReceiptProofInfo> {
number++;
}
}
}

sol! {
#[allow(missing_docs)]
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
#[sol(rpc, abi, bytecode="6080604052348015600e575f80fd5b506102288061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c8063488814e01461004e5780637229db15146100585780638381f58a14610062578063d09de08a14610080575b5f80fd5b61005661008a565b005b6100606100f8565b005b61006a610130565b6040516100779190610165565b60405180910390f35b610088610135565b005b5f547fbe3cbcfa5d4a62a595b4a15f51de63c11797bbef2ff687873efb0bb2852ee20f60405160405180910390a26100c0610135565b5f547fbe3cbcfa5d4a62a595b4a15f51de63c11797bbef2ff687873efb0bb2852ee20f60405160405180910390a26100f6610135565b565b5f547fbe3cbcfa5d4a62a595b4a15f51de63c11797bbef2ff687873efb0bb2852ee20f60405160405180910390a261012e610135565b565b5f5481565b5f80815480929190610146906101ab565b9190505550565b5f819050919050565b61015f8161014d565b82525050565b5f6020820190506101785f830184610156565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6101b58261014d565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101e7576101e661017e565b5b60018201905091905056fea26469706673582212203b7602644bfff2df89c2fe9498cd533326876859a0df7b96ac10be1fdc09c3a064736f6c634300081a0033")]

contract OtherEmitter {
@@ -170,11 +184,18 @@ pub fn generate_receipt_proofs() -> Vec<ReceiptProofInfo> {
let all_events = EventEmitter::abi::events();

let events = all_events.get("testEvent").unwrap();
let receipt_query = ReceiptQuery::new(*event_contract.address(), events[0].clone());

receipt_query
let receipt_query =
ReceiptQuery::<1, 0>::new(*event_contract.address(), &events[0].signature());

let proofs = receipt_query
.query_receipt_proofs(rpc.root(), BlockNumberOrTag::Number(block_number))
.await
.unwrap()
.unwrap();

ReceiptTestInfo {
query: receipt_query,
proofs,
}
})
}
35 changes: 35 additions & 0 deletions mp2-v1/src/final_extraction/api.rs
Original file line number Diff line number Diff line change
@@ -273,6 +273,7 @@ mod tests {
final_extraction::{
base_circuit::{test::ProofsPi, CONTRACT_SET_NUM_IO, VALUE_SET_NUM_IO},
lengthed_circuit::LENGTH_SET_NUM_IO,
receipt_circuit::test::ReceiptsProofsPi,
},
length_extraction,
};
@@ -296,6 +297,7 @@ mod tests {
);

let proof_pis = ProofsPi::random();
let receipt_proof_pis = ReceiptsProofsPi::generate_from_proof_pi_value(&proof_pis);
let length_pis = proof_pis.length_inputs();
let len_dm = length_extraction::PublicInputs::<F>::from_slice(&length_pis).metadata_point();
let block_proof = block_circuit
@@ -310,6 +312,13 @@ mod tests {
let length_proof = &length_params
.generate_input_proofs::<1>([length_pis.try_into().unwrap()])
.unwrap()[0];
let receipt_proof = &values_params
.generate_input_proofs::<1>([receipt_proof_pis
.value_inputs()
.proof_inputs
.try_into()
.unwrap()])
.unwrap()[0];

let contract_proof: ProofWithVK = (
contract_proof.clone(),
@@ -374,5 +383,31 @@ mod tests {
)
.unwrap();
proof_pis.check_proof_public_inputs(proof.proof(), TableDimension::Compound, Some(len_dm));

let receipt_proof: ProofWithVK = (
receipt_proof.clone(),
values_params.verifier_data_for_input_proofs::<1>()[0].clone(),
)
.into();

let circuit_input = CircuitInput::new_receipt_input(
serialize_proof(&block_proof).unwrap(),
receipt_proof.serialize().unwrap(),
)
.unwrap();
let proof = ProofWithVK::deserialize(
&params
.generate_receipt_proof(
match circuit_input {
CircuitInput::Receipt(input) => input,
_ => unreachable!(),
},
values_params.get_recursive_circuit_set(),
)
.unwrap(),
)
.unwrap();

receipt_proof_pis.check_proof_public_inputs(proof.proof());
}
}
210 changes: 207 additions & 3 deletions mp2-v1/src/final_extraction/receipt_circuit.rs
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@ use mp2_common::{
default_config,
keccak::{OutputHash, PACKED_HASH_LEN},
proof::{deserialize_proof, verify_proof_fixed_circuit, ProofWithVK},
public_inputs::PublicInputCommon,
serialization::{deserialize, serialize},
u256::UInt256Target,
utils::FromTargets,
utils::{FromTargets, ToTargets},
C, D, F,
};
use plonky2::{
@@ -29,7 +30,10 @@ use serde::{Deserialize, Serialize};

use crate::{block_extraction, values_extraction};

use super::api::{FinalExtractionBuilderParams, NUM_IO};
use super::{
api::{FinalExtractionBuilderParams, NUM_IO},
PublicInputs,
};

use anyhow::Result;

@@ -67,8 +71,21 @@ impl ReceiptExtractionCircuit {

// enforce block_pi.state_root == contract_pi.state_root
block_pi
.state_root()
.receipt_root()
.enforce_equal(b, &OutputHash::from_targets(value_pi.root_hash_info()));

PublicInputs::new(
block_pi.bh,
block_pi.prev_bh,
// here the value digest is the same since for length proof, it is assumed the table
// digest is in Compound format (i.e. multiple rows inside digest already).
&value_pi.values_digest_target().to_targets(),
&value_pi.metadata_digest_target().to_targets(),
&block_pi.bn.to_targets(),
&[b._false().target],
)
.register_args(b);

ReceiptExtractionWires {
dm: value_pi.metadata_digest_target(),
dv: value_pi.values_digest_target(),
@@ -211,3 +228,190 @@ impl ReceiptCircuitProofWires {
.get_public_input_targets::<F, VALUE_SET_NUM_IO>()
}
}

#[cfg(test)]
pub(crate) mod test {
use std::iter::once;

use crate::final_extraction::{base_circuit::test::ProofsPi, PublicInputs};

use super::*;
use alloy::primitives::U256;
use anyhow::Result;
use itertools::Itertools;
use mp2_common::{
keccak::PACKED_HASH_LEN,
utils::{Endianness, Packer, ToFields},
};
use mp2_test::{
circuit::{run_circuit, UserCircuit},
utils::random_vector,
};
use plonky2::{
field::types::{PrimeField64, Sample},
hash::hash_types::HashOut,
iop::witness::WitnessWrite,
plonk::config::GenericHashOut,
};
use plonky2_ecgfp5::curve::curve::Point;
use values_extraction::public_inputs::tests::new_extraction_public_inputs;

#[derive(Clone, Debug)]
struct TestReceiptCircuit {
pis: ReceiptsProofsPi,
}

struct TestReceiptWires {
pis: ReceiptsProofsPiTarget,
}

impl UserCircuit<F, D> for TestReceiptCircuit {
type Wires = TestReceiptWires;
fn build(c: &mut CircuitBuilder<F, D>) -> Self::Wires {
let proofs_pi = ReceiptsProofsPiTarget::new(c);
let _ = ReceiptExtractionCircuit::build(c, &proofs_pi.blocks_pi, &proofs_pi.values_pi);
TestReceiptWires { pis: proofs_pi }
}
fn prove(&self, pw: &mut PartialWitness<GoldilocksField>, wires: &Self::Wires) {
wires.pis.assign(pw, &self.pis);
}
}

#[derive(Clone, Debug)]
pub(crate) struct ReceiptsProofsPiTarget {
pub(crate) blocks_pi: Vec<Target>,
pub(crate) values_pi: Vec<Target>,
}

impl ReceiptsProofsPiTarget {
pub(crate) fn new(b: &mut CircuitBuilder<F, D>) -> Self {
Self {
blocks_pi: b.add_virtual_targets(
block_extraction::public_inputs::PublicInputs::<Target>::TOTAL_LEN,
),
values_pi: b
.add_virtual_targets(values_extraction::PublicInputs::<Target>::TOTAL_LEN),
}
}
pub(crate) fn assign(&self, pw: &mut PartialWitness<F>, pis: &ReceiptsProofsPi) {
pw.set_target_arr(&self.values_pi, pis.values_pi.as_ref());
pw.set_target_arr(&self.blocks_pi, pis.blocks_pi.as_ref());
}
}

/// TODO: refactor this struct to mimick exactly the base circuit wires in that it can contain
/// multiple values
#[derive(Clone, Debug)]
pub(crate) struct ReceiptsProofsPi {
pub(crate) blocks_pi: Vec<F>,
pub(crate) values_pi: Vec<F>,
}

impl ReceiptsProofsPi {
/// Function takes in a [`ProofsPi`] instance and generates a set of values public inputs
/// that agree with the provided receipts root from the `blocks_pi`.
pub(crate) fn generate_from_proof_pi_value(base_info: &ProofsPi) -> ReceiptsProofsPi {
let original = base_info.value_inputs();
let block_pi = base_info.block_inputs();
let (k, t) = original.mpt_key_info();
let new_value_digest = Point::rand();
let new_metadata_digest = Point::rand();
let new_values_pi = block_pi
.receipt_root_raw()
.iter()
.chain(k.iter())
.chain(once(&t))
.chain(new_value_digest.to_weierstrass().to_fields().iter())
.chain(new_metadata_digest.to_weierstrass().to_fields().iter())
.chain(once(&original.n()))
.cloned()
.collect_vec();
Zyouell marked this conversation as resolved.
Show resolved Hide resolved
Self {
blocks_pi: base_info.blocks_pi.clone(),
values_pi: new_values_pi,
}
}

pub(crate) fn block_inputs(&self) -> block_extraction::PublicInputs<F> {
block_extraction::PublicInputs::from_slice(&self.blocks_pi)
}

pub(crate) fn value_inputs(&self) -> values_extraction::PublicInputs<F> {
values_extraction::PublicInputs::new(&self.values_pi)
}

/// check public inputs of the proof match with the ones in `self`.
/// `compound_type` is a flag to specify whether `proof` is generated for a simple or compound type
/// `length_dm` is the metadata digest of a length proof, which is provided only for proofs related
/// to a compound type with a length slot
Zyouell marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn check_proof_public_inputs(&self, proof: &ProofWithPublicInputs<F, C, D>) {
let proof_pis = PublicInputs::from_slice(&proof.public_inputs);
let block_pi = self.block_inputs();

assert_eq!(proof_pis.bn, block_pi.bn);
assert_eq!(proof_pis.h, block_pi.bh);
assert_eq!(proof_pis.ph, block_pi.prev_bh);

// check digests
let value_pi = self.value_inputs();

assert_eq!(proof_pis.value_point(), value_pi.values_digest());

assert_eq!(proof_pis.metadata_point(), value_pi.metadata_digest());
}

pub(crate) fn random() -> Self {
let value_h = HashOut::<F>::rand().to_bytes().pack(Endianness::Little);
let key = random_vector(64);
let ptr = usize::MAX;
let value_dv = Point::rand();
let value_dm = Point::rand();
let n = 10;
let values_pi = new_extraction_public_inputs(
&value_h,
&key,
ptr,
&value_dv.to_weierstrass(),
&value_dm.to_weierstrass(),
n,
);

let th = &random_vector::<u32>(PACKED_HASH_LEN).to_fields();
let sh = &random_vector::<u32>(PACKED_HASH_LEN).to_fields();

// The receipts root and value root need to agree
let rh = &value_h.to_fields();

let block_number = U256::from(F::rand().to_canonical_u64()).to_fields();
let block_hash = HashOut::<F>::rand()
.to_bytes()
.pack(Endianness::Little)
.to_fields();
let parent_block_hash = HashOut::<F>::rand()
.to_bytes()
.pack(Endianness::Little)
.to_fields();
let blocks_pi = block_extraction::public_inputs::PublicInputs {
bh: &block_hash,
prev_bh: &parent_block_hash,
bn: &block_number,
sh,
th,
rh,
}
.to_vec();
ReceiptsProofsPi {
blocks_pi,
values_pi,
}
}
}

#[test]
fn final_simple_value() -> Result<()> {
let pis = ReceiptsProofsPi::random();
let test_circuit = TestReceiptCircuit { pis };
run_circuit::<F, D, C, _>(test_circuit);
Ok(())
Zyouell marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading