Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0b685c5
enforce setchk point binding; remove verifier unwraps
Jan 20, 2026
7b99455
Update we_gate_arith.rs
Jan 20, 2026
91b86f1
commit-before-challenge
Jan 20, 2026
514baf5
Update we_gate_arith.rs
Jan 20, 2026
4816f79
Update we_gate_arith.rs
Jan 20, 2026
e72e002
ajtai public input prefix exposure
Jan 20, 2026
e23af1d
Update we_gate_arith.rs
Jan 20, 2026
f0d8b65
make binding canonical
Jan 20, 2026
877a9eb
Update lf_plus_sp1_oneproof.rs
Jan 20, 2026
7e82707
Update we_gate_arith.rs
Jan 20, 2026
9500c7a
optimize for larger kappa
Jan 20, 2026
ea2403d
Update lf_plus_sp1_oneproof.rs
Jan 20, 2026
1911358
Revert "optimize for larger kappa"
Jan 20, 2026
b8ae322
missing glue for dcom pub inputs
Jan 20, 2026
8167cdb
c0/c1 commitment opt
Jan 20, 2026
35a8040
Revert "c0/c1 commitment opt"
Jan 20, 2026
bca2f2f
Update commitment_scheme.rs
Jan 20, 2026
6631158
Revert "Update commitment_scheme.rs"
Jan 20, 2026
c4aa6da
hardening
Jan 20, 2026
e1c9b23
Update we_gate_arith.rs
Jan 20, 2026
782098e
cleanup proving calls
Jan 20, 2026
ef27e74
cleanup
Jan 20, 2026
7059148
Update we_gate_arith.rs
Jan 20, 2026
222c1fc
Update we_gate_arith.rs
Jan 20, 2026
9a73d43
Update we_gate_arith.rs
Jan 20, 2026
b33fe6d
api wip
Jan 21, 2026
b891976
Update sp1_oneproof_api.rs
Jan 21, 2026
693ba6c
fix LF+ benches
Jan 21, 2026
ee704cf
fix benches
Jan 21, 2026
2323069
Update we_dpp.rs
Jan 21, 2026
84bbf1e
Fix: Absorb statement public inputs before verifying
Jan 21, 2026
57ae057
Create test_tiny_field_accepting_set.rs
Jan 21, 2026
8be7fff
wip
Jan 21, 2026
49fa727
Update test_tiny_field_accepting_set.rs
Jan 21, 2026
fa1540e
Update accepting_set.rs
Jan 21, 2026
2008920
Create test_realistic_domain_too_large.rs
Jan 21, 2026
498a057
Update accepting_set.rs
Jan 21, 2026
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ quote = "1.0.37"
rand = { version = "0.8.5", default-features = false }
rand_chacha = { version = "0.3.1", default-features = false }
sha2 = { version = "0.10.9", default-features = false }
rayon = "1.10.0"
rayon = "1.11.0"
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.137"
stark-rings = { git = "https://github.com/NethermindEth/stark-rings.git", branch = "main", default-features = false }
Expand Down
238 changes: 238 additions & 0 deletions crates/dpp/src/accepting_set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
//! Explicit accepting-set enumeration for packed DPP queries (only for small domains).
//!
//! This module exists to support **arm-before-proof** locks in the GPT‑PRO interface:
//! the lock layer wants `(q, A)` where `A` is an explicit (small) accepting set such that:
//! accept ⇔ <q, (x||π)> ∈ A.
//!
//! Our current packed verifier is predicate-style:
//! accept ⇔ pred(Decode(<q,(x||π)>; w,b)) = 1.
//!
//! In general the induced accepting set is huge. Here we provide a *guarded* enumerator that
//! only works when the decoded-answer domain is small enough to brute-force.

use ark_ff::{BigInteger, PrimeField};
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use thiserror::Error;

use crate::packing::{centered_bigint_to_field, FlpcpPredicate, PackedDppQuery, PackedDppQuerySparse};

#[derive(Debug, Error)]
pub enum AcceptingSetError {
#[error("invalid parameters")]
InvalidParams,
#[error("decoded-answer domain too large to enumerate (estimated {estimated} > limit {limit})")]
DomainTooLarge { estimated: u128, limit: u128 },
}

/// Estimate the number of centered integer tuples `ans` satisfying `|ans_i| <= b_i-1`.
fn estimate_domain_size(b: &[BigInt]) -> Option<u128> {
let mut acc: u128 = 1;
for bi in b {
// range size = 2*(b_i-1)+1
let bound = bi - BigInt::one();
if bound.is_negative() {
return None;
}
let r = (bound * BigInt::from(2u64)) + BigInt::one();
// If we can't represent the range size in u128, the domain is certainly enormous.
let r_u = r.to_u128().unwrap_or(u128::MAX);
// If multiplication overflows, treat as enormous.
acc = acc.checked_mul(r_u).unwrap_or(u128::MAX);
}
Some(acc)
}

/// Enumerate the induced accepting set
/// A = { <ans, w> : ans in [-b_i+1 .. b_i-1], pred(ans)=true } ⊂ F
/// for a dense packed query.
///
/// This is **only feasible** when the decoded-answer domain is tiny.
pub fn accepting_set_for_packed_query<F: PrimeField>(
query: &PackedDppQuery<F>,
limit: u128,
) -> Result<Vec<F>, AcceptingSetError> {
let k = query.w.len();
if k == 0 || query.b.len() != k {
return Err(AcceptingSetError::InvalidParams);
}
let est = estimate_domain_size(&query.b).ok_or(AcceptingSetError::InvalidParams)?;
if est > limit {
return Err(AcceptingSetError::DomainTooLarge { estimated: est, limit });
}

// Fast path for k=3 and small numeric domains.
//
// The generic enumerator below is BigInt-heavy (nested loops doing BigInt increments and
// conversions) and becomes slow even around ~1e5–1e6 points. For tiny-field demo tests we
// want this to run quickly, so when:
// - k == 3,
// - bounds and weights fit in i64/i128,
// - and the predicate is MulEqModP over a small modulus,
// we enumerate in i64/i128 and only convert accepted tuples into field elements.
if query.w.len() == 3 {
if let FlpcpPredicate::MulEqModP { p_small } = &query.pred {
if let (Some(p_small_u128), Some(p_u128)) = (p_small.to_u128(), modulus_u128::<F>()) {
if p_small_u128 > 1 && p_u128 > 1 {
// Precompute p (mod p_small) for handling negative centered reps.
let p_mod_ps = (p_u128 % p_small_u128) as u128;

// Convert bounds b_i-1 into i64.
let mut bound: [i64; 3] = [0; 3];
for i in 0..3 {
let bi = &query.b[i];
let b1 = (bi - BigInt::one()).to_i64();
if b1.is_none() {
bound = [0; 3];
break;
}
bound[i] = b1.unwrap();
}
if bound[0] > 0 && bound[1] > 0 && bound[2] > 0 {
// Convert weights w_i into i128 (safe for toy params).
let mut w: [i128; 3] = [0; 3];
for i in 0..3 {
let wi = query.w[i].to_i128();
if wi.is_none() {
w = [0; 3];
break;
}
w[i] = wi.unwrap();
}
if w[0] != 0 && w[1] != 0 && w[2] != 0 {
#[inline]
fn mod_small(z: i64, p_mod_ps: u128, ps: u128) -> u128 {
if z >= 0 {
(z as u128) % ps
} else {
let t = ((-z) as u128) % ps;
if t == 0 {
0
} else {
(p_mod_ps + ps - t) % ps
}
}
}

#[inline]
fn mod_p_u128(z: i128, p: u128) -> u128 {
// z mod p in [0,p)
let p_i = p as i128;
let mut r = z % p_i;
if r < 0 {
r += p_i;
}
r as u128
}

// Store residues in u128 for fast sort/dedup, then map to F at end.
let mut out_u128: Vec<u128> = Vec::new();
let b0 = bound[0];
let b1 = bound[1];
let b2 = bound[2];
for t0 in -b0..=b0 {
let a0 = mod_small(t0, p_mod_ps, p_small_u128);
for t1 in -b1..=b1 {
let a1 = mod_small(t1, p_mod_ps, p_small_u128);
let a01 = (a0 * a1) % p_small_u128;
for t2 in -b2..=b2 {
let a2 = mod_small(t2, p_mod_ps, p_small_u128);
if (a01 + p_small_u128 - a2) % p_small_u128 == 0 {
// Accept: compute packed a = <t, w> as integer, then reduce mod p.
let a_int = (w[0] * (t0 as i128))
+ (w[1] * (t1 as i128))
+ (w[2] * (t2 as i128));
let a_mod = mod_p_u128(a_int, p_u128);
out_u128.push(a_mod);
}
}
}
}

out_u128.sort_unstable();
out_u128.dedup();
let out = out_u128
.into_iter()
.map(|a| F::from_le_bytes_mod_order(&a.to_le_bytes()))
.collect::<Vec<_>>();
return Ok(out);
}
}
}
}
}
}

// Enumerate centered integer answers within bounds.
let mut cur: Vec<BigInt> = vec![BigInt::zero(); k];
let mut out: Vec<F> = Vec::new();

fn rec<F: PrimeField>(
i: usize,
cur: &mut [BigInt],
w: &[BigInt],
b: &[BigInt],
pred: &FlpcpPredicate<F>,
out: &mut Vec<F>,
) {
let k = b.len();
if i == k {
// Convert to field for predicate check.
let ans_field: Vec<F> = cur.iter().map(|z| centered_bigint_to_field::<F>(z)).collect();
if pred.check(&ans_field) {
// Compute packed answer a = <cur, w> (integer), then map into field.
let mut a_int = BigInt::zero();
for j in 0..k {
a_int += &cur[j] * &w[j];
}
out.push(centered_bigint_to_field::<F>(&a_int));
}
return;
}

let bound = &b[i] - BigInt::one();
// iterate t ∈ [-bound .. bound]
let mut t = -bound.clone();
while t <= bound {
cur[i] = t.clone();
rec::<F>(i + 1, cur, w, b, pred, out);
t += BigInt::one();
}
}

rec::<F>(0, &mut cur, &query.w, &query.b, &query.pred, &mut out);

// Dedup and return in canonical order (by field integer rep).
//
// Note: this is only for small sets; O(|A| log |A|) is fine.
let p = BigInt::from_bytes_le(num_bigint::Sign::Plus, &F::MODULUS.to_bytes_le());
out.sort_by(|a, b| {
let aa = BigInt::from_bytes_le(num_bigint::Sign::Plus, &a.into_bigint().to_bytes_le()) % &p;
let bb = BigInt::from_bytes_le(num_bigint::Sign::Plus, &b.into_bigint().to_bytes_le()) % &p;
aa.cmp(&bb)
});
out.dedup();
Ok(out)
}

fn modulus_u128<F: PrimeField>() -> Option<u128> {
// Parse modulus as u128 if it fits (true for Goldilocks / Fp64).
let p = BigInt::from_bytes_le(num_bigint::Sign::Plus, &F::MODULUS.to_bytes_le());
p.to_u128()
}

/// Sparse packed query variant.
pub fn accepting_set_for_packed_query_sparse<F: PrimeField>(
query: &PackedDppQuerySparse<F>,
limit: u128,
) -> Result<Vec<F>, AcceptingSetError> {
// Same logic; only metadata differs.
let dense = PackedDppQuery::<F> {
q: vec![], // unused
w: query.w.clone(),
b: query.b.clone(),
pred: query.pred.clone(),
};
accepting_set_for_packed_query::<F>(&dense, limit)
}

1 change: 1 addition & 0 deletions crates/dpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod packing;
pub mod embedding;
pub mod boolean_proof;
pub mod pipeline;
pub mod accepting_set;

pub mod rs;
pub mod dr1cs_flpcp;
Expand Down
77 changes: 77 additions & 0 deletions crates/dpp/tests/test_realistic_domain_too_large.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use ark_ff::{Field, Fp64, MontBackend, MontConfig, PrimeField};
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;

use dpp::accepting_set::{accepting_set_for_packed_query_sparse, AcceptingSetError};
use dpp::dr1cs_flpcp::{Dr1csInstanceSparse, RsDr1csNpFlpcpSparse};
use dpp::pipeline::build_rev2_dpp_sparse_boolean_auto;
use dpp::{EmbeddingParams, SparseVec};

#[derive(MontConfig)]
// Goldilocks prime (2^64 - 2^32 + 1)
#[modulus = "18446744069414584321"]
#[generator = "7"]
pub struct GoldilocksConfig;
type F = Fp64<MontBackend<GoldilocksConfig, 1>>;

#[derive(MontConfig)]
// NIST P-384 prime (same as `we_dpp` bench uses as FBig)
#[modulus = "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319"]
#[generator = "2"]
pub struct Secp384r1Config;
type FBig = ark_ff::Fp384<MontBackend<Secp384r1Config, 6>>;

/// This is the “realistic next step” test: show that for non-tiny instances, the induced
/// accepting set of the packed-predicate DPP is astronomically large, so explicit enumeration
/// (Case A) is impossible.
///
/// We keep the instance size moderate so this test runs fast: it should fail early with
/// `DomainTooLarge` before doing any heavy work.
#[test]
fn test_packed_predicate_accepting_set_is_too_large_for_realistic_sizes() {
// Choose k_rows large enough that the bound b explodes, but still small enough that
// constructing the instance is cheap.
let k_rows = 64usize;
let n_total = 256usize;
let l_public = 8usize;
assert!(l_public < n_total);

// Use a sparse dr1cs instance with trivial structure:
// (z_i) * (z_{i+1}) = z_{i+2} on each row (wrapping indices).
let mut a = Vec::with_capacity(k_rows);
let mut b = Vec::with_capacity(k_rows);
let mut c = Vec::with_capacity(k_rows);
for i in 0..k_rows {
let i0 = i % n_total;
let i1 = (i + 1) % n_total;
let i2 = (i + 2) % n_total;
a.push(SparseVec::new(vec![(F::ONE, i0)]));
b.push(SparseVec::new(vec![(F::ONE, i1)]));
c.push(SparseVec::new(vec![(F::ONE, i2)]));
}
let inst = Dr1csInstanceSparse::<F> { n: n_total, a, b, c };
let ell = 2 * k_rows;

// Dummy statement x (public prefix of z). It does not need to be satisfying for this test.
let x = vec![FBig::ZERO; l_public];

let flpcp = RsDr1csNpFlpcpSparse::<F>::new(inst, l_public, ell);
let dppv = build_rev2_dpp_sparse_boolean_auto::<F, FBig, _>(
flpcp,
EmbeddingParams {
gamma: 2,
assume_boolean_proof: true,
k_prime: 0,
},
)
.expect("build_rev2_dpp_sparse_boolean_auto");

// Sample a packed query (cheap relative to any enumeration).
let mut rng = ChaCha20Rng::seed_from_u64(1);
let query = dppv.sample_query(&mut rng, &x).expect("sample_query");

// Attempt to enumerate with a very small limit: this must fail with DomainTooLarge.
let res = accepting_set_for_packed_query_sparse::<FBig>(&query, 1_000_000);
assert!(matches!(res, Err(AcceptingSetError::DomainTooLarge { .. })));
}

Loading