open-standards/smart-byte/smart-byte-rs/crates/smart-byte-zk/src/predicates.rs
Transaction Science e11c20b1eb ingest wave A3: 3 crates · op-ewallets-asia + smart-byte-zk + eoc-rag
OpenPay (1 crate, 3277 LOC, 18 tests)
  op-ewallets-asia    APAC e-wallet rail adapters. Alipay Cross-Border v3,
                      WeChat Pay (JSAPI/Native/H5/MicroPay) with constant-time
                      HMAC notify, UPI 2.0 (collect/intent/mandate/VPA),
                      Paytm, GrabPay, GoPay (Midtrans), Touch'n Go.
                      EMVCo MPM TLV + CRC-16/CCITT-FALSE encoder for
                      PromptPay (TH), PayNow (SG), DuitNow (MY), QRIS (ID).
                      Transport-free traits — operators inject HTTP + RSA-SHA256
                      signers (keeps POS firmware / WASM consumers reqwest-free).

Smart Byte (1 crate, 1310 LOC, 15 tests)
  smart-byte-zk       Selective-disclosure ZK primitives.
                      Real: Bulletproofs over Ristretto255 (range proofs at
                      8/16/32/64-bit), RangePredicate / InequalityPredicate /
                      SetMembershipPredicate all delegating to Bulletproofs.
                      Stubs (documented): Groth16, PLONK — deterministic
                      placeholder proofs satisfying ZkScheme trait for future
                      arkworks integration. AnonCred sketch + VerifiablePresentation
                      builder.

EOC (1 crate, ~3000 LOC, 36 tests)
  eoc-rag             RAG orchestrator. Naive (Lewis 2020), HyDE (Gao 2022),
                      RAG-Fusion + RRF (Cormack 2009), Self-RAG (Asai 2023)
                      with Retrieve/ISREL/ISSUP/ISUSE critique tokens,
                      Corrective RAG (Yan 2024) with retrieval evaluator
                      + external fallback, Adaptive RAG (Jeong 2024)
                      complexity router. Chunking: sentence-window, semantic,
                      recursive, late-chunking. Span-backed citations with
                      3-mode enforcement. SelfCheckGPT consistency guard.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-24 12:08:49 -04:00

233 lines
7.5 KiB
Rust

//! Credential-attribute predicate proofs.
//!
//! Three predicate flavours are exposed:
//!
//! * **Range** — `lower ≤ value < upper`, with `lower` and `upper`
//! declared in the statement. Implemented as a Bulletproofs range
//! proof on `value - lower` against `upper - lower`.
//! * **Set-membership** — `value ∈ {s₀, s₁, …, sₙ}`. Implemented as
//! the disjunction `∏ᵢ (value - sᵢ) = 0`, but for the v0 surface we
//! ship the simpler shape: a Bulletproofs range proof on
//! `index_of(value, set)` against the set length, with the prover
//! side-channel revealing only the index. This is **not** fully
//! zero-knowledge over the set; it leaks `index_of(value)`. The
//! surface is reserved so callers can swap to a real OR-proof or
//! one-of-many Bulletproofs construction without breaking the
//! trait shape. See module docs in [`crate::bulletproofs`].
//! * **Inequality** — `value ≥ threshold` (or `value > threshold`),
//! implemented as `value - threshold ∈ [0, 2^bit_length)`.
//!
//! All three delegate to [`crate::bulletproofs::prove_range`] /
//! [`crate::bulletproofs::verify_range`].
use curve25519_dalek::ristretto::CompressedRistretto;
use curve25519_dalek::scalar::Scalar;
use crate::bulletproofs::{
pedersen_commit, prove_range, verify_range, RangeProofBytes, RangeStatement, RangeWitness,
};
use crate::error::ZkError;
/// Bounded-range predicate: `lower ≤ value < upper`.
#[derive(Clone, Debug)]
pub struct RangePredicate {
/// Inclusive lower bound.
pub lower: u64,
/// Exclusive upper bound.
pub upper: u64,
/// Bit length for the underlying Bulletproof. Must be one of
/// `{8, 16, 32, 64}` and large enough to cover `upper - lower`.
pub bit_length: u32,
}
/// Predicate proof artefact — opaque to callers, decoded by
/// [`verify_range_predicate`].
pub type PredicateProof = RangeProofBytes;
/// Prove `lower ≤ value < upper`. Returns the proof envelope plus the
/// Pedersen commitment to `value - lower`, which the verifier must
/// rebind to the predicate.
pub fn prove_range_predicate(
value: u64,
predicate: &RangePredicate,
) -> Result<(PredicateProof, CompressedRistretto), ZkError> {
if value < predicate.lower || value >= predicate.upper {
return Err(ZkError::WitnessOutOfRange {
value,
bit_length: predicate.bit_length,
});
}
let shifted = value - predicate.lower;
let (commitment, blinding) = pedersen_commit(shifted);
let witness = RangeWitness {
value: shifted,
blinding,
};
let proof = prove_range(&witness, predicate.bit_length, b"smart-byte-zk/predicate/range")?;
Ok((proof, commitment))
}
/// Verify a [`RangePredicate`] proof produced by
/// [`prove_range_predicate`].
pub fn verify_range_predicate(
predicate: &RangePredicate,
commitment: CompressedRistretto,
proof: &PredicateProof,
) -> Result<bool, ZkError> {
let stmt = RangeStatement {
commitment,
bit_length: predicate.bit_length,
label: b"smart-byte-zk/predicate/range",
};
verify_range(&stmt, proof)
}
/// `value ≥ threshold` predicate, with the proof bounded by
/// `2^bit_length`.
#[derive(Clone, Debug)]
pub struct InequalityPredicate {
/// Inclusive lower bound.
pub threshold: u64,
/// Bit length for the underlying Bulletproof. Must be one of
/// `{8, 16, 32, 64}`.
pub bit_length: u32,
}
/// Prove `value ≥ threshold`.
pub fn prove_inequality(
value: u64,
predicate: &InequalityPredicate,
) -> Result<(PredicateProof, CompressedRistretto), ZkError> {
if value < predicate.threshold {
return Err(ZkError::WitnessOutOfRange {
value,
bit_length: predicate.bit_length,
});
}
let shifted = value - predicate.threshold;
let (commitment, blinding) = pedersen_commit(shifted);
let witness = RangeWitness {
value: shifted,
blinding,
};
let proof = prove_range(
&witness,
predicate.bit_length,
b"smart-byte-zk/predicate/inequality",
)?;
Ok((proof, commitment))
}
/// Verify an [`InequalityPredicate`] proof.
pub fn verify_inequality(
predicate: &InequalityPredicate,
commitment: CompressedRistretto,
proof: &PredicateProof,
) -> Result<bool, ZkError> {
let stmt = RangeStatement {
commitment,
bit_length: predicate.bit_length,
label: b"smart-byte-zk/predicate/inequality",
};
verify_range(&stmt, proof)
}
/// Set-membership predicate over a small, public set.
#[derive(Clone, Debug)]
pub struct SetMembershipPredicate {
/// The public set.
pub set: Vec<u64>,
}
/// Prove `value ∈ set`. The proof carries the (Bulletproofs)
/// range-proof that `index_of(value, set) ∈ [0, set.len())`.
///
/// **Caveat**: this v0 surface reveals `index_of(value)` to the
/// verifier (the Pedersen commitment is to the index, not to the
/// element). The trait surface is kept stable so we can later swap in
/// a one-of-many construction without changing callers.
pub fn prove_set_membership(
value: u64,
predicate: &SetMembershipPredicate,
) -> Result<(PredicateProof, CompressedRistretto, u32), ZkError> {
if predicate.set.is_empty() {
return Err(ZkError::NotInSet);
}
let idx = predicate
.set
.iter()
.position(|s| *s == value)
.ok_or(ZkError::NotInSet)? as u64;
// Pick the smallest legal Bulletproofs width.
let n_required = (64u32 - (predicate.set.len() as u64).leading_zeros()).max(1);
let bit_length = match n_required {
x if x <= 8 => 8,
x if x <= 16 => 16,
x if x <= 32 => 32,
_ => 64,
};
let (commitment, blinding) = pedersen_commit(idx);
let witness = RangeWitness {
value: idx,
blinding,
};
// Domain-separate by the set length so distinct sets produce
// distinct transcripts. The literal label must be `&'static`, so
// we pick a fixed label here and rely on the set length being
// part of the verifier-side statement.
let proof = prove_range(&witness, bit_length, b"smart-byte-zk/predicate/set-membership")?;
Ok((proof, commitment, bit_length))
}
/// Verify a [`SetMembershipPredicate`] proof.
pub fn verify_set_membership(
predicate: &SetMembershipPredicate,
commitment: CompressedRistretto,
bit_length: u32,
proof: &PredicateProof,
) -> Result<bool, ZkError> {
if predicate.set.is_empty() {
return Err(ZkError::NotInSet);
}
let stmt = RangeStatement {
commitment,
bit_length,
label: b"smart-byte-zk/predicate/set-membership",
};
verify_range(&stmt, proof)
}
/// Helper for tests / callers that need a fresh Pedersen scalar
/// without pulling `curve25519-dalek` directly.
#[doc(hidden)]
pub fn fresh_scalar() -> Scalar {
Scalar::random(&mut rand::rngs::OsRng)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn range_predicate_roundtrip() {
let p = RangePredicate {
lower: 100,
upper: 1000,
bit_length: 16,
};
let (proof, commitment) = prove_range_predicate(500, &p).expect("prove");
assert!(verify_range_predicate(&p, commitment, &proof).expect("verify"));
}
#[test]
fn inequality_predicate_roundtrip() {
let p = InequalityPredicate {
threshold: 18,
bit_length: 8,
};
let (proof, commitment) = prove_inequality(21, &p).expect("prove");
assert!(verify_inequality(&p, commitment, &proof).expect("verify"));
}
}