Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
14 changes: 10 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@ ark-serialize = { version = "0.5", default-features = false, features = ["derive
ark-scale = { version = "0.0.13", default-features = false }
ark-vrf = { version = "0.1.0", default-features = false, features = ["bandersnatch", "ring"] }
spin = { version = "0.9", default-features = false, features = ["once"], optional = true }
rand = { version = "0.8", features = ["getrandom"] }
rand = { version = "0.8", features = ["getrandom"], optional = true }

[dev-dependencies]
rand_core = "0.6"
paste = "1.0"

[[bin]]
name = "generate_test_keys"
path = "src/bin/generate_test_keys.rs"
required-features = ["generate-keys"]

[features]
default = ["std"]
# Enable the generate_test_keys binary. Requires std.
generate-keys = ["std", "rand"]
std = [
"bounded-collections/std",
"parity-scale-codec/std",
Expand All @@ -40,10 +44,12 @@ std = [
"ark-serialize/std",
"ark-scale/std",
"ark-vrf/std",
"ark-vrf/parallel"
"ark-vrf/parallel",
]
# Small ring 255, default to 16127
small-ring = []
# Include ring builder params binary data for building ring commitments.
# Disable this feature to reduce library size in production environments
# that only need to validate proofs (not build new ring commitments).
builder-params = []
# Prover for no-std environments with deterministic ring-proof.
# Not for production, may be useful for testing.
no-std-prover = [
Expand Down
39 changes: 32 additions & 7 deletions src/demo_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ use super::*;
use bounded_collections::{BoundedVec, ConstU32};
use schnorrkel::{signing_context, ExpansionMode, MiniSecretKey, PublicKey};

/// Unit type implements DomainSize for demo implementations that don't use ring VRF.
impl DomainSize for () {
fn max_ring_size(&self) -> usize {
1024
}

fn exponent(&self) -> u8 {
10 // 2^10 = 1024
}
}

// Example impls:

/// Totally insecure Anonymizer: Member and Secret are both the same `[u8; 32]` and the proof is
Expand All @@ -18,12 +29,13 @@ impl GenerateVerifiable for Trivial {
type Proof = [u8; 32];
type Signature = [u8; 32];
type StaticChunk = ();
type DomainSize = ();

fn is_member_valid(_member: &Self::Member) -> bool {
true
}

fn start_members() -> Self::Intermediate {
fn start_members(_domain_size: ()) -> Self::Intermediate {
BoundedVec::new()
}

Expand All @@ -50,6 +62,7 @@ impl GenerateVerifiable for Trivial {
}

fn open(
_domain_size: (),
member: &Self::Member,
members: impl Iterator<Item = Self::Member>,
) -> Result<Self::Commitment, ()> {
Expand All @@ -73,6 +86,7 @@ impl GenerateVerifiable for Trivial {
}

fn validate(
_domain_size: (),
proof: &Self::Proof,
members: &Self::Members,
_context: &[u8],
Expand Down Expand Up @@ -118,12 +132,13 @@ impl GenerateVerifiable for Simple {
type Proof = ([u8; 64], Alias);
type Signature = [u8; 64];
type StaticChunk = ();
type DomainSize = ();

fn is_member_valid(_member: &Self::Member) -> bool {
true
}

fn start_members() -> Self::Intermediate {
fn start_members(_domain_size: ()) -> Self::Intermediate {
BoundedVec::new()
}

Expand Down Expand Up @@ -152,6 +167,7 @@ impl GenerateVerifiable for Simple {
}

fn open(
_domain_size: (),
member: &Self::Member,
members: impl Iterator<Item = Self::Member>,
) -> Result<Self::Commitment, ()> {
Expand Down Expand Up @@ -182,6 +198,7 @@ impl GenerateVerifiable for Simple {
}

fn validate(
_domain_size: (),
proof: &Self::Proof,
members: &Self::Members,
context: &[u8],
Expand Down Expand Up @@ -235,7 +252,7 @@ mod tests {
let alice = <Simple as GenerateVerifiable>::member_from_secret(&alice_sec);
let bob = <Simple as GenerateVerifiable>::member_from_secret(&bob_sec);

let mut inter = <Simple as GenerateVerifiable>::start_members();
let mut inter = <Simple as GenerateVerifiable>::start_members(());
<Simple as GenerateVerifiable>::push_members(
&mut inter,
[alice.clone()].into_iter(),
Expand All @@ -253,23 +270,31 @@ mod tests {
let message = b"Hello world";

let r = SimpleReceipt::create(
(),
&alice_sec,
members.iter().cloned(),
context,
message.to_vec(),
)
.unwrap();
let (alias, msg) = r.verify(&members, &context).unwrap();
let (alias, msg) = r.verify((), &members, context).unwrap();
assert_eq!(&message[..], &msg[..]);
assert_eq!(alias, alice);

let r = SimpleReceipt::create(&bob_sec, members.iter().cloned(), context, message.to_vec())
.unwrap();
let (alias, msg) = r.verify(&members, &context).unwrap();
let r = SimpleReceipt::create(
(),
&bob_sec,
members.iter().cloned(),
context,
message.to_vec(),
)
.unwrap();
let (alias, msg) = r.verify((), &members, context).unwrap();
assert_eq!(&message[..], &msg[..]);
assert_eq!(alias, bob);

assert!(SimpleReceipt::create(
(),
&charlie_sec,
members.iter().cloned(),
context,
Expand Down
52 changes: 44 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ use scale_info::*;
pub mod demo_impls;
pub mod ring_vrf_impl;

/// Trait for domain size types used in ring operations.
///
/// The domain size determines the maximum ring size that can be supported.
pub trait DomainSize: Clone + Copy {
Copy link
Member

@davxy davxy Jan 29, 2026

Choose a reason for hiding this comment

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

@georgepisaltu @ggwpez

The main concern here is that DomainSize outside the ring implementation doesn't make much sense
(as you correctly wrote: domain size types used in ring operations).
This can instead be confusing, as downstream users may have no knowledge of polynomial commitment schemes or related concepts. These are strictly implementation details of RingProof when it is based on such schemes. From the user's perspective, the only relevant factor is the number of members they are allowed to include.

I suggest removing this trait from this generic module and replacing it with a more appropriate abstraction.

In the GenerateVerifiable trait, you could introduce an associated type such as Capacity, for example:

trait Capacity {
    const fn size(&self) -> usize;
}

(feel free to choose a better name :-) )

With this approach, the ring implementation can use the ark-vrf helper to derive the required PCS size
(ark_vrf::ring::pcs_domain_size(ring_size)), and then select the appropriate static data based on that value.

In the ring implementation you have:

enum RingSize {
    /// Domain size 2^11, max ring size 255
	Small,
	/// Domain size 2^12, max ring size 767
	Mid,
	/// Domain size 2^16, max ring size 16127
	Big,
}

impl Capacity for RingSize {
    const fn size(&self) -> usize {
        match self {
             Small => max_ring_size_from_pcs_domain_size(1 << 11),
             Mid => max_ring_size_from_pcs_domain_size(1 << 12),
             Big => max_ring_size_from_pcs_domain_size(1 << 16),
        }
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the exponent function and redefined the trait and type to be Capacity.

/// Returns the maximum ring size for this domain.
fn max_ring_size(&self) -> usize;

/// Returns the exponent of the domain size (i.e., `n` where domain size is `2^n`).
fn exponent(&self) -> u8;
}

// Fixed types:

/// Cryptographic identifier for a person within a specific application which deals with people.
Expand Down Expand Up @@ -70,8 +81,12 @@ pub trait GenerateVerifiable {

type StaticChunk: Clone + Eq + PartialEq + FullCodec + Debug + TypeInfo + MaxEncodedLen;

/// The domain size type used to parametrize ring operations.
/// Must implement the `DomainSize` trait which provides `max_ring_size()`.
type DomainSize: Clone + Copy + DomainSize;

/// Begin building a `Members` value.
fn start_members() -> Self::Intermediate;
fn start_members(domain_size: Self::DomainSize) -> Self::Intermediate;

/// Introduce a set of new `Member`s into the intermediate value used to build a new `Members`
/// value.
Expand Down Expand Up @@ -111,6 +126,7 @@ pub trait GenerateVerifiable {
/// **WARNING**: This function may panic if called from on-chain or an environment not
/// implementing the functionality.
fn open(
domain_size: Self::DomainSize,
member: &Self::Member,
members_iter: impl Iterator<Item = Self::Member>,
) -> Result<Self::Commitment, ()>;
Expand Down Expand Up @@ -146,13 +162,14 @@ pub trait GenerateVerifiable {
/// if so, ensure that the member is necessarily associated with `alias` in this `context` and
/// that they elected to opine `message`.
fn is_valid(
domain_size: Self::DomainSize,
proof: &Self::Proof,
members: &Self::Members,
context: &[u8],
alias: &Alias,
message: &[u8],
) -> bool {
match Self::validate(proof, members, context, message) {
match Self::validate(domain_size, proof, members, context, message) {
Ok(a) => &a == alias,
Err(()) => false,
}
Expand All @@ -163,6 +180,7 @@ pub trait GenerateVerifiable {

/// Like `is_valid`, but `alias` is returned, not provided.
fn validate(
_domain_size: Self::DomainSize,
_proof: &Self::Proof,
_members: &Self::Members,
_context: &[u8],
Expand Down Expand Up @@ -192,6 +210,7 @@ pub struct Receipt<Gen: GenerateVerifiable> {

impl<Gen: GenerateVerifiable> Receipt<Gen> {
pub fn create<'a>(
domain_size: Gen::DomainSize,
secret: &Gen::Secret,
members: impl Iterator<Item = Gen::Member>,
context: &[u8],
Expand All @@ -200,7 +219,7 @@ impl<Gen: GenerateVerifiable> Receipt<Gen> {
where
Gen::Member: 'a,
{
let commitment = Gen::open(&Gen::member_from_secret(secret), members)?;
let commitment = Gen::open(domain_size, &Gen::member_from_secret(secret), members)?;
let (proof, alias) = Gen::create(commitment, secret, context, &message)?;
Ok(Self {
proof,
Expand All @@ -217,19 +236,36 @@ impl<Gen: GenerateVerifiable> Receipt<Gen> {
pub fn into_parts(self) -> (Alias, Vec<u8>) {
(self.alias, self.message)
}
pub fn verify(self, members: &Gen::Members, context: &[u8]) -> Result<(Alias, Vec<u8>), Self> {
match Gen::validate(&self.proof, members, context, &self.message) {
pub fn verify(
self,
domain_size: Gen::DomainSize,
members: &Gen::Members,
context: &[u8],
) -> Result<(Alias, Vec<u8>), Self> {
match Gen::validate(domain_size, &self.proof, members, context, &self.message) {
Ok(alias) => Ok((alias, self.message)),
Err(()) => {
if self.is_valid(members, context) {
if self.is_valid(domain_size, members, context) {
Ok(self.into_parts())
} else {
Err(self)
}
}
}
}
pub fn is_valid(&self, members: &Gen::Members, context: &[u8]) -> bool {
Gen::is_valid(&self.proof, members, context, &self.alias, &self.message)
pub fn is_valid(
&self,
domain_size: Gen::DomainSize,
members: &Gen::Members,
context: &[u8],
) -> bool {
Gen::is_valid(
domain_size,
&self.proof,
members,
context,
&self.alias,
&self.message,
)
}
}
Binary file added src/ring-data/ring-builder-domain12.bin
Binary file not shown.
Binary file added src/ring-data/ring-builder-params-domain12.bin
Binary file not shown.
Loading