Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c36cf43
Implement non-native extension field arithmetic
ax0 Apr 24, 2025
4798d48
Schnorr signature verification (#221)
dgulotta May 13, 2025
8fb0af0
Merge branch 'main' into ec-sig
ax0 May 14, 2025
97edb98
Use Schnorr signatures for signed PODs
ax0 May 16, 2025
e56515d
add custom gates (#237)
dgulotta May 23, 2025
61ca534
Merge branch 'main' into ec-sig
ax0 May 23, 2025
1d0e220
Clippy
ax0 May 23, 2025
d48d3a5
Formatting
ax0 May 23, 2025
6fca530
Apply suggestions from code review
ax0 Jun 3, 2025
540a7cc
Merge branch 'main' into ec-sig
ax0 Jun 3, 2025
ac07ad6
Fix typo
ax0 Jun 3, 2025
c19c60f
Merge branch 'main' into ec-sig
ax0 Jun 3, 2025
a8b8ee9
Fix tests
ax0 Jun 3, 2025
747239f
Point -> PublicKey
ax0 Jun 3, 2025
686976a
Remove default nnf_div implementation for clarity
ax0 Jun 3, 2025
ebbb710
Code review & edits for clarity
ax0 Jun 3, 2025
5bc9950
Remove suspicious mutation
ax0 Jun 3, 2025
5d5da97
Simplify computation
ax0 Jun 3, 2025
dc2bd95
Fix division
ax0 Jun 3, 2025
2ad2370
Fix
ax0 Jun 3, 2025
475f5e9
Update src/backends/plonky2/primitives/ec/curve.rs
ax0 Jun 4, 2025
dbd6f51
Update src/backends/plonky2/primitives/ec/curve.rs
ax0 Jun 4, 2025
6cae8af
Fixes
ax0 Jun 3, 2025
13c2952
Add public key to signed POD struct
ax0 Jun 4, 2025
237019c
Style
ax0 Jun 4, 2025
6932590
Elaborate on in-circuit field->biguint conversion
ax0 Jun 6, 2025
9d996df
Add missing gates
ax0 Jun 6, 2025
48cc578
Comments
ax0 Jun 6, 2025
c63f7b1
Merge branch 'main' into ec-sig
ax0 Jun 6, 2025
1f8230b
Add bits to biguint struct
ax0 Jun 9, 2025
ab72984
Comments
ax0 Jun 9, 2025
6d2e5b8
Comment
ax0 Jun 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ serde = "1.0.219"
serde_json = "1.0.140"
base64 = "0.22.1"
schemars = "0.8.22"
num = { version = "0.4.3", features = ["num-bigint"] }
num-bigint = { version = "0.4.6", features = ["rand"] }
# num-bigint 0.4 requires rand 0.8
rand = "0.8.5"
hashbrown = { version = "0.14.3", default-features = false, features = ["serde"] }

# Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory.
Expand Down
2 changes: 1 addition & 1 deletion book/src/merklestatements.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ all of which are represented as `MerkleTree` on the back end.

The frontend compound types and their implementation as Merkle trees is explained under [POD value types](./values.md#dictionary-array-set). The backend structure of a MerkleTree is explained on [the Merkle tree page](./merkletree.md).

The POD2 interface provides statements for working with Merkle trees and compond types at all layers of the stack:
The POD2 interface provides statements for working with Merkle trees and compound types at all layers of the stack:
- Primitive statements for Merkle trees
- General derived statements for Merkle trees
- Specialized `ContainsKey`, `NotContainsKey`, and `ContainsValue` statements for the three front-end types.
Expand Down
2 changes: 1 addition & 1 deletion src/backends/plonky2/circuits/mainpod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2965,7 +2965,7 @@ mod tests {

// Input
let statements = statements
.into_iter()
.iter()
.map(|st| {
let mut st = mainpod::Statement::from(st.clone());
pad_statement(params, &mut st);
Expand Down
11 changes: 6 additions & 5 deletions src/backends/plonky2/circuits/signedpod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
merkletree::{
MerkleClaimAndProof, MerkleProofExistenceGadget, MerkleProofExistenceTarget,
},
signature::{PublicKey, SignatureVerifyGadget, SignatureVerifyTarget},
signature::{SignatureVerifyGadget, SignatureVerifyTarget},
},
signedpod::SignedPod,
},
Expand Down Expand Up @@ -58,11 +58,12 @@ impl SignedPodVerifyGadget {
// 3.a. Verify signature
let signature = SignatureVerifyGadget {}.eval(builder)?;

// 3.b. Verify signer (ie. signature.pk == merkletree.signer_leaf)
// 3.b. Verify signer (ie. hash(signature.pk) == merkletree.signer_leaf)
let signer_mt_proof = &mt_proofs[1];
let key_signer = builder.constant_value(Key::from(KEY_SIGNER).raw());
let pk_hash = signature.pk.to_value(builder);
builder.connect_values(signer_mt_proof.key, key_signer);
builder.connect_values(signer_mt_proof.value, signature.pk);
builder.connect_values(signer_mt_proof.value, pk_hash);

// 3.c. connect signed message to pod.id
builder.connect_values(ValueTarget::from_slice(&id.elements), signature.msg);
Expand Down Expand Up @@ -174,7 +175,7 @@ impl SignedPodVerifyTarget {
}

// get the signer pk
let pk = PublicKey(key_signer_value.raw());
let pk = key_signer_value.typed().clone().try_into()?;
// the msg signed is the pod.id
let msg = RawValue::from(pod.id.0);

Expand All @@ -199,7 +200,7 @@ pub mod tests {
use crate::{
backends::plonky2::{
basetypes::C,
primitives::signature::SecretKey,
primitives::ec::schnorr::SecretKey,
signedpod::{SignedPod, Signer},
},
middleware::F,
Expand Down
1 change: 1 addition & 0 deletions src/backends/plonky2/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl Error {
pub(crate) fn custom(s: String) -> Self {
new!(Custom(s))
}
#[allow(dead_code)]
pub(crate) fn plonky2_proof_fail(e: anyhow::Error) -> Self {
Self::Plonky2ProofFail(e)
}
Expand Down
40 changes: 20 additions & 20 deletions src/backends/plonky2/mainpod/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ pub use statement::*;
use crate::{
backends::plonky2::{
basetypes::{Proof, ProofWithPublicInputs, VerifierOnlyCircuitData, D},
circuits::mainpod::{
CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget, NUM_PUBLIC_INPUTS,
},
circuits::mainpod::{CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget},
emptypod::EmptyPod,
error::{Error, Result},
mock::emptypod::MockEmptyPod,
primitives::merkletree::MerkleClaimAndProof,
recursion::{self, RecursiveCircuit, RecursiveParams},
recursion::{RecursiveCircuit, RecursiveParams},
signedpod::SignedPod,
STANDARD_REC_MAIN_POD_CIRCUIT_DATA,
},
Expand Down Expand Up @@ -550,12 +548,13 @@ pub struct MainPod {
fn get_common_data(params: &Params) -> Result<CommonCircuitData<F, D>, Error> {
// TODO: Cache this somehow
// https://github.com/0xPARC/pod2/issues/247
let rec_params = recursion::new_params::<MainPodVerifyTarget>(
let rec_circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
let (_, circuit_data) = RecursiveCircuit::<MainPodVerifyTarget>::circuit_data_padded(
params.max_input_recursive_pods,
NUM_PUBLIC_INPUTS,
&rec_circuit_data.common,
params,
)?;
Ok(rec_params.common_data().clone())
Ok(circuit_data.common.clone())
}

impl MainPod {
Expand Down Expand Up @@ -681,11 +680,13 @@ impl RecursivePod for MainPod {

#[cfg(test)]
pub mod tests {
use num::{BigUint, One};

use super::*;
use crate::{
backends::plonky2::{
mock::mainpod::{MockMainPod, MockProver},
primitives::signature::SecretKey,
primitives::ec::schnorr::SecretKey,
signedpod::Signer,
},
examples::{
Expand All @@ -697,7 +698,7 @@ pub mod tests {
{self},
},
middleware,
middleware::{CustomPredicateRef, NativePredicate as NP, RawValue},
middleware::{CustomPredicateRef, NativePredicate as NP},
op,
};

Expand All @@ -715,11 +716,11 @@ pub mod tests {

let (gov_id_builder, pay_stub_builder, sanction_list_builder) =
zu_kyc_sign_pod_builders(&params);
let mut signer = Signer(SecretKey(RawValue::from(1)));
let mut signer = Signer(SecretKey(BigUint::one()));
let gov_id_pod = gov_id_builder.sign(&mut signer)?;
let mut signer = Signer(SecretKey(RawValue::from(2)));
let mut signer = Signer(SecretKey(2u64.into()));
let pay_stub_pod = pay_stub_builder.sign(&mut signer)?;
let mut signer = Signer(SecretKey(RawValue::from(3)));
let mut signer = Signer(SecretKey(3u64.into()));
let sanction_list_pod = sanction_list_builder.sign(&mut signer)?;
let kyc_builder =
zu_kyc_pod_builder(&params, &gov_id_pod, &pay_stub_pod, &sanction_list_pod)?;
Expand Down Expand Up @@ -748,7 +749,7 @@ pub mod tests {
gov_id_builder.insert("idNumber", "4242424242");
gov_id_builder.insert("dateOfBirth", 1169909384);
gov_id_builder.insert("socialSecurityNumber", "G2121210");
let mut signer = Signer(SecretKey(RawValue::from(42)));
let mut signer = Signer(SecretKey(42u64.into()));
let gov_id = gov_id_builder.sign(&mut signer).unwrap();
let now_minus_18y: i64 = 1169909388;
let mut kyc_builder = frontend::MainPodBuilder::new(&params);
Expand Down Expand Up @@ -830,24 +831,23 @@ pub mod tests {
};
println!("{:#?}", params);

let mut alice = Signer(SecretKey(RawValue::from(1)));
let bob = Signer(SecretKey(RawValue::from(2)));
let mut charlie = Signer(SecretKey(RawValue::from(3)));
let mut alice = Signer(SecretKey(1u32.into()));
let bob = Signer(SecretKey(2u32.into()));
let mut charlie = Signer(SecretKey(3u32.into()));

// Alice attests that she is ETH friends with Charlie and Charlie
// attests that he is ETH friends with Bob.
let alice_attestation =
eth_friend_signed_pod_builder(&params, charlie.public_key().0.into())
.sign(&mut alice)?;
eth_friend_signed_pod_builder(&params, charlie.public_key().into()).sign(&mut alice)?;
let charlie_attestation =
eth_friend_signed_pod_builder(&params, bob.public_key().0.into()).sign(&mut charlie)?;
eth_friend_signed_pod_builder(&params, bob.public_key().into()).sign(&mut charlie)?;

let alice_bob_ethdos_builder = eth_dos_pod_builder(
&params,
false,
&alice_attestation,
&charlie_attestation,
bob.public_key().0.into(),
bob.public_key().into(),
)?;

let mut prover = MockProver {};
Expand Down
200 changes: 200 additions & 0 deletions src/backends/plonky2/primitives/ec/bits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
use std::{array, marker::PhantomData};

use num::BigUint;
use plonky2::{
field::{
extension::Extendable,
goldilocks_field::GoldilocksField,
types::{Field, Field64},
},
hash::hash_types::RichField,
iop::{
generator::{GeneratedValues, SimpleGenerator},
target::{BoolTarget, Target},
witness::{PartitionWitness, Witness, WitnessWrite},
},
plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData},
util::serialization::{IoResult, Read, Write},
};

#[derive(Debug)]
struct ConditionalZeroGenerator<F: RichField + Extendable<D>, const D: usize> {
if_zero: Target,
then_zero: Target,
quot: Target,
_phantom: PhantomData<F>,
}

impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D>
for ConditionalZeroGenerator<F, D>
{
fn id(&self) -> String {
"ConditionalZeroGenerator".to_string()
}

fn dependencies(&self) -> Vec<Target> {
vec![self.if_zero, self.then_zero]
}

fn run_once(
&self,
witness: &PartitionWitness<F>,
out_buffer: &mut GeneratedValues<F>,
) -> anyhow::Result<()> {
let if_zero = witness.get_target(self.if_zero);
let then_zero = witness.get_target(self.then_zero);
if if_zero.is_zero() {
out_buffer.set_target(self.quot, F::ZERO)?;
} else {
out_buffer.set_target(self.quot, then_zero / if_zero)?;
}

Ok(())
}

fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData<F, D>) -> IoResult<()> {
dst.write_target(self.if_zero)?;
dst.write_target(self.then_zero)?;
dst.write_target(self.quot)
}

fn deserialize(
src: &mut plonky2::util::serialization::Buffer,
_common_data: &CommonCircuitData<F, D>,
) -> IoResult<Self>
where
Self: Sized,
{
Ok(Self {
if_zero: src.read_target()?,
then_zero: src.read_target()?,
quot: src.read_target()?,
_phantom: PhantomData,
})
}
}

/// A big integer, represented in base `2^32` with 10 digits, in little endian
/// form.
#[derive(Clone, Debug)]
pub struct BigUInt320Target(pub(super) [Target; 10]);

pub trait CircuitBuilderBits {
/// Enforces the constraint that `then_zero` must be zero if `if_zero`
/// is zero.
///
/// The prover is required to exhibit a solution to the equation
/// `if_zero * x == then_zero`. If both `if_zero` and `then_zero`
/// are zero, then it chooses the solution `x = 0`.
fn conditional_zero(&mut self, if_zero: Target, then_zero: Target);

/// Returns the binary representation of the target, in little-endian order.
fn biguint_bits(&mut self, x: &BigUInt320Target) -> [BoolTarget; 320];

/// Decomposes the target x as `y + 2^32 z`, where `0 < y,z < 2**32`, and
/// `y=0` if `z=2**32-1`. Note that calling [`CircuitBuilder::split_le`]
/// with `num_bits = 64` will not check the latter condition.
fn split_32_bit(&mut self, x: Target) -> [Target; 2];

/// Interprets `arr` as an integer in base `[GoldilocksField::ORDER]`,
/// with the digits in little endian order. The length of `arr` must be at
/// most 5.
fn field_elements_to_biguint(&mut self, arr: &[Target]) -> BigUInt320Target;

fn normalize_bigint(
&mut self,
x: &mut BigUInt320Target,
max_digit_bits: usize,
max_num_digits: usize,
);

fn constant_biguint320(&mut self, n: &BigUint) -> BigUInt320Target;
fn add_virtual_biguint320_target(&mut self) -> BigUInt320Target;
fn connect_biguint320(&mut self, x: &BigUInt320Target, y: &BigUInt320Target);
}

impl CircuitBuilderBits for CircuitBuilder<GoldilocksField, 2> {
fn conditional_zero(&mut self, if_zero: Target, then_zero: Target) {
let quot = self.add_virtual_target();
self.add_simple_generator(ConditionalZeroGenerator {
if_zero,
then_zero,
quot,
_phantom: PhantomData,
});
let prod = self.mul(if_zero, quot);
self.connect(prod, then_zero);
}

fn biguint_bits(&mut self, x: &BigUInt320Target) -> [BoolTarget; 320] {
let bits = x.0.map(|t| self.low_bits(t, 32, 32));
array::from_fn(|i| bits[i / 32][i % 32])
}

fn field_elements_to_biguint(&mut self, arr: &[Target]) -> BigUInt320Target {
assert!(arr.len() <= 5);
let mut ans = BigUInt320Target(array::from_fn(|_| self.zero()));
let neg_one = self.neg_one();
let two_32 = self.constant(GoldilocksField::from_canonical_u64(1 << 32));
for (n, &x) in arr.iter().rev().enumerate() {
// multiply by the order of the Goldilocks field
for i in (0..(2 * n)).rev() {
let tmp = self.add(ans.0[i + 1], two_32);
ans.0[i + 1] = self.sub(tmp, ans.0[i]);
let tmp = self.add(ans.0[i + 2], ans.0[i]);
ans.0[i + 2] = self.add(tmp, neg_one);
}
// add x
let [low, high] = self.split_32_bit(x);
ans.0[0] = self.add(ans.0[0], low);
ans.0[1] = self.add(ans.0[1], high);
self.normalize_bigint(&mut ans, 34, 2 * n + 1);
}
ans
}

fn normalize_bigint(
&mut self,
x: &mut BigUInt320Target,
max_digit_bits: usize,
max_num_carries: usize,
) {
for i in 0..max_num_carries {
let (low, high) = self.split_low_high(x.0[i], 32, max_digit_bits);
x.0[i] = low;
x.0[i + 1] = self.add(x.0[i + 1], high);
}
}

fn split_32_bit(&mut self, x: Target) -> [Target; 2] {
let (low, high) = self.split_low_high(x, 32, 64);
let max = self.constant(GoldilocksField::from_canonical_i64(0xFFFFFFFF));
let high_minus_max = self.sub(high, max);
self.conditional_zero(high_minus_max, low);
[low, high]
}

fn constant_biguint320(&mut self, n: &BigUint) -> BigUInt320Target {
assert!(n.bits() <= 320);
let digits = n.to_u32_digits();
let targets = array::from_fn(|i| {
let d = digits.get(i).copied().unwrap_or(0);
self.constant(GoldilocksField::from_canonical_u32(d))
});
BigUInt320Target(targets)
}

fn add_virtual_biguint320_target(&mut self) -> BigUInt320Target {
let targets = self.add_virtual_target_arr();
for t in targets {
self.range_check(t, 32);
}
BigUInt320Target(targets)
}

fn connect_biguint320(&mut self, x: &BigUInt320Target, y: &BigUInt320Target) {
for i in 0..10 {
self.connect(x.0[i], y.0[i]);
}
}
}
Loading
Loading