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

Feat/batching circuit gadgets #396

Merged
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions groth16-framework/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ itertools.workspace = true
rand.workspace = true
serial_test.workspace = true
sha2.workspace = true
mp2_test = { path = "../mp2-test" }

recursion_framework = { path = "../recursion-framework" }
verifiable-db = { path = "../verifiable-db" }
7 changes: 6 additions & 1 deletion groth16-framework/tests/common/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
use super::{NUM_PREPROCESSING_IO, NUM_QUERY_IO};
use groth16_framework::{compile_and_generate_assets, utils::clone_circuit_data};
use mp2_common::{C, D, F};
use mp2_test::circuit::TestDummyCircuit;
use recursion_framework::framework_testing::TestingRecursiveCircuits;
use verifiable_db::{
api::WrapCircuitParams,
query::pi_len,
revelation::api::Parameters as RevelationParameters,
test_utils::{
INDEX_TREE_MAX_DEPTH, MAX_NUM_COLUMNS, MAX_NUM_ITEMS_PER_OUTPUT, MAX_NUM_OUTPUTS,
Expand Down Expand Up @@ -40,6 +42,8 @@ impl TestContext {

// Generate a fake query circuit set.
let query_circuits = TestingRecursiveCircuits::<F, C, D, NUM_QUERY_IO>::default();
let dummy_universal_circuit =
TestDummyCircuit::<{ pi_len::<MAX_NUM_ITEMS_PER_OUTPUT>() }>::build();

// Create the revelation parameters.
let revelation_params = RevelationParameters::<
Expand All @@ -52,7 +56,8 @@ impl TestContext {
MAX_NUM_ITEMS_PER_OUTPUT,
MAX_NUM_PLACEHOLDERS,
>::build(
query_circuits.get_recursive_circuit_set(),
query_circuits.get_recursive_circuit_set(), // unused, so we provide a dummy one
dummy_universal_circuit.circuit_data().verifier_data(),
preprocessing_circuits.get_recursive_circuit_set(),
preprocessing_circuits
.verifier_data_for_input_proofs::<1>()
Expand Down
35 changes: 1 addition & 34 deletions mp2-common/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ mod test {
types::MAX_BLOCK_LEN,
utils::{Endianness, Packer},
};
use mp2_test::eth::{get_mainnet_url, get_sepolia_url};
use mp2_test::eth::get_sepolia_url;

#[tokio::test]
#[ignore]
Expand Down Expand Up @@ -426,39 +426,6 @@ mod test {
Ok(())
}

#[tokio::test]
async fn test_pidgy_pinguin_mapping_slot() -> Result<()> {
// first pinguin holder https://dune.com/queries/2450476/4027653
// holder: 0x188b264aa1456b869c3a92eeed32117ebb835f47
// NFT id https://opensea.io/assets/ethereum/0xbd3531da5cf5857e7cfaa92426877b022e612cf8/1116
let mapping_value =
Address::from_str("0x188B264AA1456B869C3a92eeeD32117EbB835f47").unwrap();
let nft_id: u32 = 1116;
let mapping_key = left_pad32(&nft_id.to_be_bytes());
let url = get_mainnet_url();
let provider = ProviderBuilder::new().on_http(url.parse().unwrap());

// extracting from
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
// assuming it's using ERC731Enumerable that inherits ERC721
let mapping_slot = 2;
// pudgy pinguins
let pudgy_address = Address::from_str("0xBd3531dA5CF5857e7CfAA92426877b022e612cf8")?;
let query = ProofQuery::new_mapping_slot(pudgy_address, mapping_slot, mapping_key.to_vec());
let res = query
.query_mpt_proof(&provider, BlockNumberOrTag::Latest)
.await?;
let raw_address = ProofQuery::verify_storage_proof(&res)?;
// the value is actually RLP encoded !
let decoded_address: Vec<u8> = rlp::decode(&raw_address).unwrap();
let leaf_node: Vec<Vec<u8>> = rlp::decode_list(res.storage_proof[0].proof.last().unwrap());
println!("leaf_node[1].len() = {}", leaf_node[1].len());
// this is read in the same order
let found_address = Address::from_slice(&decoded_address.into_iter().collect::<Vec<u8>>());
assert_eq!(found_address, mapping_value);
Ok(())
}

#[tokio::test]
async fn test_kashish_contract_proof_query() -> Result<()> {
// https://sepolia.etherscan.io/address/0xd6a2bFb7f76cAa64Dad0d13Ed8A9EFB73398F39E#code
Expand Down
2 changes: 2 additions & 0 deletions mp2-common/src/group_hashing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ impl ToTargets for QuinticExtensionTarget {
}

impl FromTargets for CurveTarget {
const NUM_TARGETS: usize = CURVE_TARGET_LEN;

fn from_targets(t: &[Target]) -> Self {
nicholas-mainardi marked this conversation as resolved.
Show resolved Hide resolved
assert!(t.len() >= CURVE_TARGET_LEN);
let x = QuinticExtensionTarget(t[0..EXTENSION_DEGREE].try_into().unwrap());
Expand Down
2 changes: 2 additions & 0 deletions mp2-common/src/keccak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub type OutputHash = Array<U32Target, PACKED_HASH_LEN>;
pub type OutputByteHash = Array<Target, HASH_LEN>;

impl FromTargets for OutputHash {
const NUM_TARGETS: usize = PACKED_HASH_LEN;

fn from_targets(t: &[Target]) -> Self {
OutputHash::from_array(array::from_fn(|i| U32Target(t[i])))
}
Expand Down
1 change: 1 addition & 0 deletions mp2-common/src/u256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ impl ToTargets for UInt256Target {
}

impl FromTargets for UInt256Target {
const NUM_TARGETS: usize = NUM_LIMBS;
// Expects big endian limbs as the standard format for IO
fn from_targets(t: &[Target]) -> Self {
Self::new_from_be_target_limbs(&t[..NUM_LIMBS]).unwrap()
Expand Down
60 changes: 45 additions & 15 deletions mp2-common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result};
use itertools::Itertools;
use plonky2::field::extension::Extendable;
use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField, NUM_HASH_OUT_ELTS};
use plonky2::iop::target::{BoolTarget, Target};
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
Expand All @@ -19,26 +19,25 @@ use sha3::Keccak256;

use crate::array::Targetable;
use crate::poseidon::{HashableField, H};
use crate::serialization::circuit_data_serialization::SerializableRichField;
use crate::{group_hashing::EXTENSION_DEGREE, types::HashOutput, ProofTuple};
use crate::{D, F};

const TWO_POWER_8: usize = 256;
const TWO_POWER_16: usize = 65536;
const TWO_POWER_24: usize = 16777216;

#[allow(dead_code)]
trait ConnectSlice {
fn connect_slice(&mut self, a: &[Target], b: &[Target]);
// check that the closure $f actually panics, printing $msg as error message if the function
// did not panic; this macro is employed in tests in place of #[should_panic] to ensure that a
// panic occurred in the expected function rather than in other parts of the test
#[macro_export]
macro_rules! check_panic {
($f: expr, $msg: expr) => {{
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe($f));
assert!(result.is_err(), $msg);
}};
}

impl ConnectSlice for CircuitBuilder<F, D> {
fn connect_slice(&mut self, a: &[Target], b: &[Target]) {
assert_eq!(a.len(), b.len());
for (ai, bi) in a.iter().zip(b) {
self.connect(*ai, *bi);
}
}
}
pub use check_panic;

pub fn verify_proof_tuple<
F: RichField + Extendable<D>,
Expand Down Expand Up @@ -326,17 +325,20 @@ pub fn pack_and_compute_poseidon_target<F: HashableField + Extendable<D>, const
b.hash_n_to_hash_no_pad::<H>(packed)
}

pub trait SelectHashBuilder {
pub trait HashBuilder {
/// Select `first_hash` or `second_hash` as output depending on the Boolean `cond`
fn select_hash(
&mut self,
cond: BoolTarget,
first_hash: &HashOutTarget,
second_hash: &HashOutTarget,
) -> HashOutTarget;

/// Determine whether `first_hash == second_hash`
fn hash_eq(&mut self, first_hash: &HashOutTarget, second_hash: &HashOutTarget) -> BoolTarget;
}

impl<F: RichField + Extendable<D>, const D: usize> SelectHashBuilder for CircuitBuilder<F, D> {
impl<F: RichField + Extendable<D>, const D: usize> HashBuilder for CircuitBuilder<F, D> {
fn select_hash(
&mut self,
cond: BoolTarget,
Expand All @@ -352,6 +354,28 @@ impl<F: RichField + Extendable<D>, const D: usize> SelectHashBuilder for Circuit
.collect_vec(),
)
}

fn hash_eq(&mut self, first_hash: &HashOutTarget, second_hash: &HashOutTarget) -> BoolTarget {
let _true = self._true();
first_hash
.elements
.iter()
.zip(second_hash.elements.iter())
.fold(_true, |acc, (first, second)| {
let is_eq = self.is_equal(*first, *second);
self.and(acc, is_eq)
})
}
}

pub trait SelectTarget {
/// Return `first` if `cond` is true, `second` otherwise
fn select<F: SerializableRichField<D>, const D: usize>(
b: &mut CircuitBuilder<F, D>,
cond: &BoolTarget,
first: &Self,
second: &Self,
) -> Self;
}

pub trait ToFields<F: RichField> {
Expand Down Expand Up @@ -414,10 +438,16 @@ impl<F: RichField> Fieldable<F> for u64 {
}

pub trait FromTargets {
/// Number of targets necessary to instantiate `Self`
const NUM_TARGETS: usize;
nicholas-mainardi marked this conversation as resolved.
Show resolved Hide resolved

/// Number of targets in `t` must be at least `Self::NUM_TARGETS`
fn from_targets(t: &[Target]) -> Self;
}

impl FromTargets for HashOutTarget {
const NUM_TARGETS: usize = NUM_HASH_OUT_ELTS;

fn from_targets(t: &[Target]) -> Self {
HashOutTarget {
elements: create_array(|i| t[i]),
Expand Down
Loading