Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 4 additions & 9 deletions spongefish/examples/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@ use ark_ec::{CurveGroup, PrimeGroup};
use ark_std::UniformRand;
use rand::rngs::OsRng;
use spongefish::{
protocol_id, Codec, DomainSeparator, Encoding, NargDeserialize, NargSerialize, ProverState,
VerificationError, VerificationResult, VerifierState,
Codec, Encoding, NargDeserialize, NargSerialize, ProverState, VerificationError,
VerificationResult, VerifierState,
};

struct Schnorr;

impl Schnorr {
pub fn protocol_id() -> [u8; 64] {
protocol_id(core::format_args!("schnorr proof"))
}

/// Here the proving algorithm takes as input a [`ProverState`], and an instance-witness pair.
///
/// The [`ProverState`] actually depends on a duplex sponge interface (over any field) and a random number generator.
Expand Down Expand Up @@ -78,9 +74,8 @@ fn main() {
let pk = generator * sk;
let instance = [generator, pk];

let domain_sep = DomainSeparator::new(Schnorr::protocol_id())
.session(spongefish::session!("spongefish examples"))
.instance(&instance);
let domain_sep =
spongefish::domain_separator!("schnorr proof"; "spongefish examples").instance(&instance);

// Prove the relation sk * G::generator() = pk
let mut prover_state = domain_sep.std_prover();
Expand Down
227 changes: 109 additions & 118 deletions spongefish/src/domain_separator.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,41 @@
//! Utilities for domain separation.
//!
//! A "domain separator" in spongefish has three components:
//! A domain separator binds three pieces of public context before protocol messages are
//! exchanged:
//!
//! - a *protocol identifier*, to identify the **non-interactive** protocol being used, and it's the responsibility of the proof system to provide this component.
//! - a *session identifier*, to identify the **application** where this proof is being used, and it's the responsibility of the users of the application to provide this component.
//! - an *instance*, which identifies the **statement** being proven. It's the responsibility of the witness generation procedure to provide this component.
//! - a protocol identifier, chosen by the proof system;
//! - sponge / compilation information, chosen by the Fiat--Shamir compiler or helper macro;
//! - a session identifier, chosen by the application using the proof.
//!
//! A domain separator can be instantiated in several equivalent ways:
//! ```
//! use spongefish::{domain_separator, session};
//!
//! let x = [1u8, 2, 3];
//! These three byte strings are length-prefixed, hashed into a 64-byte transcript tag whose
//! first 32 bytes are derived and whose second 32 bytes are zero, and then the public instance is
//! absorbed separately by the prover/verifier state.
//!
//! // all at once, via the helper macro.
//! let _ds1 = domain_separator!("proto"; "sess").instance(&x);
//! // with the session provided at a later time:
//! let _ds2 = domain_separator!("proto").session(session!("sess")).instance(&x);
//! // if not specified, the session identifier is set to zero.
//! let _ds3 = domain_separator!("proto").without_session().instance(&x);
//! ```
//! Domain separators can then be turned into prover and verifier state via
//! [`DomainSeparator::to_prover`] and [`DomainSeparator::to_verifier`].
//! Shorthands for [`StdHash`] are available via [`DomainSeparator::std_prover`] and [`DomainSeparator::std_verifier`].
//! [`DomainSeparator::to_prover`] and [`DomainSeparator::to_verifier`]. Shorthands for
//! [`StdHash`] are available via [`DomainSeparator::std_prover`] and
//! [`DomainSeparator::std_verifier`].
//!
//! ```
//! use spongefish::{domain_separator, session};
//! use spongefish::domain_separator;
//!
//! let x = [1u8, 2, 3];
//! let ds1 = domain_separator!("proto"; "sess").instance(&x);
//! let ds2 = domain_separator!("proto").session(session!("sess")).instance(&x);
//! let ds2 = domain_separator!("proto"; "sess").instance(&x);
//!
//! // Same protocol, session, and instance yield the same transcript
//! assert_eq!(
//! ds1.std_prover().verifier_message::<u64>(),
//! ds2.std_prover().verifier_message::<u64>()
//! );
//! ```
//!
//! For testing purposes, it's possible to instantiate a protocol without a session:
//!
//! ```
//! use spongefish::{domain_separator, session};
//!
//! let x = [1u8, 2, 3];
//! let ds1 = domain_separator!("proto"; "sess").instance(&x);
//! let ds3 = domain_separator!("proto").without_session().instance(&x);
//! assert_ne!(
//! ds1.std_prover().verifier_message::<u64>(),
//! ds3.std_prover().verifier_message::<u64>()
//! );
//! ```
//!

use core::{fmt, fmt::Arguments};
use core::{fmt::Arguments, marker::PhantomData};

use rand::rngs::StdRng;

#[cfg(feature = "sha3")]
use crate::VerifierState;
use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash, Unit};
use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash};

/// Marker structure for domain separators without an associated instance.
///
Expand All @@ -67,161 +45,174 @@ use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash, Unit};
/// ```compile_fail
/// use spongefish::domain_separator;
///
/// domain_separator!("this will not compile").std_prover();
/// domain_separator!("this will not compile"; "example session").std_prover();
/// ```
///
/// ```compile_fail
/// use spongefish::DomainSeparator;
///
/// DomainSeparator::new([0u8; 64]).instance(b"missing session");
/// DomainSeparator::derive(b"proto", b"sponge", b"session").std_prover();
/// ```
#[derive(Debug, Default, Copy, Clone)]
pub struct WithoutInstance;
#[derive(Debug, Clone, Copy)]
pub struct WithoutInstance<I: ?Sized>(PhantomData<I>);

impl<I: ?Sized> WithoutInstance<I> {
const fn new() -> Self {
Self(PhantomData)
}
}

/// Marker structure storing the instance once it has been provided.
///
/// ```no_run
/// use spongefish::domain_separator;
///
/// let _prover = domain_separator!("this will compile")
/// .session(spongefish::session!("example"))
/// let _prover = domain_separator!("this will compile"; "example session")
/// .instance(b"yellowsubmarine")
/// .std_prover();
/// ```
pub struct WithInstance<I>(I);
#[derive(Debug, Clone, Copy)]
pub struct WithInstance<'i, I: ?Sized>(&'i I);

/// Session state marker: no session context has been resolved yet.
#[derive(Debug, Clone, Copy, Default)]
pub struct WithoutSession;

/// Session state marker: a session context has been bound.
pub struct WithSession<S>(pub(crate) S);

impl<S: fmt::Debug> fmt::Debug for WithSession<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("WithSession").field(&self.0).finish()
}
/// Domain separator for a Fiat--Shamir transformation.
///
/// `domsep` is the derived 64-byte transcript tag. The instance is stored separately so it can be
/// absorbed by the selected prover/verifier backend after transcript initialization.
#[derive(Debug, Clone, Copy)]
pub struct DomainSeparator<I> {
/// 64-byte domain tag; the first 32 bytes are derived and the second 32 bytes are zero.
/// This feeds `StdHash::from_protocol_id` / duplex init.
pub domsep: [u8; 64],
instance: I,
}

/// Explicit opt-out session marker.
/// Length-prefixed domain derivation: `LE32(|p|)||p||LE32(|i|)||i||LE32(|s|)||s`.
///
/// Used by [`DomainSeparator::without_session`]. Encodes to an empty slice,
/// matching the original behaviour when no session was provided.
#[derive(Debug, Clone, Copy, Default)]
pub struct NoSession;
/// The first 32 bytes are squeezed from [`StdHash`]. The second 32 bytes remain zero
/// so the result can still be used with the existing 64-byte protocol tag hooks.
#[cfg(feature = "sha3")]
#[must_use]
pub fn derive_domain_digest(protocol_id: &[u8], sponge_info: &[u8], session: &[u8]) -> [u8; 64] {
let mut sponge = StdHash::from_protocol_id(pad_identifier(b"fiat-shamir/domain-separator"));
sponge.absorb(&(protocol_id.len() as u32).to_le_bytes());
sponge.absorb(protocol_id);
sponge.absorb(&(sponge_info.len() as u32).to_le_bytes());
sponge.absorb(sponge_info);
sponge.absorb(&(session.len() as u32).to_le_bytes());
sponge.absorb(session);

impl<T: Unit> Encoding<[T]> for NoSession {
fn encode(&self) -> impl AsRef<[T]> {
let empty: [T; 0] = [];
empty
}
let mut domsep = [0u8; 64];
sponge.squeeze(&mut domsep[..32]);
domsep
}

/// Domain separator for a Fiat--Shamir transformation.
///
/// The API enforces: `new → session | without_session → instance → prover/verifier`.
pub struct DomainSeparator<I, S = WithoutSession> {
/// **what** this interactive protocol is.
pub protocol: [u8; 64],
/// **where** this interactive protocol is being used.
pub session: S,
/// **how** this interactive protocol is used.
instance: I,
/// Raw UTF-8 / formatted bytes for a protocol label (unpadded), for use with [`DomainSeparator::derive`].
#[must_use]
pub fn protocol_label(args: Arguments) -> alloc::vec::Vec<u8> {
if let Some(message) = args.as_str() {
return message.as_bytes().to_vec();
}
alloc::fmt::format(args).into_bytes()
}

impl DomainSeparator<WithoutInstance, WithoutSession> {
#[cfg(feature = "sha3")]
impl<I: ?Sized> DomainSeparator<WithoutInstance<I>> {
/// Domain separation from explicit protocol bytes, compilation/sponge info, and session bytes
/// (the standard sponge over a length-prefixed injective encoding).
#[must_use]
pub const fn new(protocol: [u8; 64]) -> Self {
pub fn derive(protocol_id: &[u8], sponge_info: &[u8], session: &[u8]) -> Self {
Self {
protocol,
session: WithoutSession,
instance: WithoutInstance,
domsep: derive_domain_digest(protocol_id, sponge_info, session),
instance: WithoutInstance::new(),
}
}
}

impl<I> DomainSeparator<I, WithoutSession> {
/// Binds a session context to the transcript.
///
/// The session value may be provided either by value or by reference.
/// Passing `&session` avoids copying large session objects.
#[must_use]
pub fn session<S>(self, value: S) -> DomainSeparator<I, WithSession<S>> {
pub fn instance(self, value: &I) -> DomainSeparator<WithInstance<'_, I>> {
DomainSeparator {
protocol: self.protocol,
session: WithSession(value),
instance: self.instance,
domsep: self.domsep,
instance: WithInstance(value),
}
}
}

/// Explicit opt-out: the protocol deliberately binds no application context.
#[cfg(feature = "sha3")]
/// Precomputes the `(protocol_id, sponge_info)` prefix of [`derive_domain_digest`] so only the
/// session block is hashed per proof.
pub struct DomainSeparatorPrefix {
prefix: StdHash,
}

#[cfg(feature = "sha3")]
impl DomainSeparatorPrefix {
#[must_use]
pub fn without_session(self) -> DomainSeparator<I, WithSession<NoSession>> {
self.session(NoSession)
pub fn new(protocol_id: &[u8], sponge_info: &[u8]) -> Self {
let mut prefix = StdHash::from_protocol_id(pad_identifier(b"fiat-shamir/domain-separator"));
prefix.absorb(&(protocol_id.len() as u32).to_le_bytes());
prefix.absorb(protocol_id);
prefix.absorb(&(sponge_info.len() as u32).to_le_bytes());
prefix.absorb(sponge_info);
Self { prefix }
}
}

impl<S> DomainSeparator<WithoutInstance, WithSession<S>> {
pub fn instance<I>(self, value: I) -> DomainSeparator<WithInstance<I>, WithSession<S>> {
/// Finishes with the session field and returns a [`DomainSeparator`] ready for `.instance(...)`.
#[must_use]
pub fn with_session<I: ?Sized>(&self, session: &[u8]) -> DomainSeparator<WithoutInstance<I>> {
let mut sponge = self.prefix.clone();
sponge.absorb(&(session.len() as u32).to_le_bytes());
sponge.absorb(session);
let mut domsep = [0u8; 64];
sponge.squeeze(&mut domsep[..32]);
DomainSeparator {
protocol: self.protocol,
session: self.session,
instance: WithInstance(value),
domsep,
instance: WithoutInstance::new(),
}
}
}

impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>>
impl<I> DomainSeparator<WithInstance<'_, I>>
where
I: Encoding,
S: Encoding,
{
#[cfg(feature = "sha3")]
#[must_use]
pub fn std_prover(&self) -> ProverState {
let mut prover_state = ProverState::from(StdHash::from_protocol_id(self.protocol));
prover_state.public_message(&self.session.0);
prover_state.public_message(&self.instance.0);
let mut prover_state = ProverState::from(StdHash::from_protocol_id(self.domsep));
prover_state.public_message(self.instance.0);
prover_state
}

#[cfg(feature = "sha3")]
#[must_use]
pub fn std_verifier<'ver>(&self, narg_string: &'ver [u8]) -> VerifierState<'ver, StdHash> {
let mut verifier_state =
VerifierState::from_parts(StdHash::from_protocol_id(self.protocol), narg_string);
verifier_state.public_message(&self.session.0);
verifier_state.public_message(&self.instance.0);
VerifierState::from_parts(StdHash::from_protocol_id(self.domsep), narg_string);
verifier_state.public_message(self.instance.0);
verifier_state
}
}

impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>> {
impl<I> DomainSeparator<WithInstance<'_, I>> {
pub fn to_prover<H>(&self, h: H) -> ProverState<H, StdRng>
where
H: DuplexSpongeInterface,
[u8; 64]: Encoding<[H::U]>,
S: Encoding<[H::U]>,
I: Encoding<[H::U]>,
{
let mut prover_state = ProverState::from(h);
prover_state.public_message(&self.protocol);
prover_state.public_message(&self.session.0);
prover_state.public_message(&self.instance.0);
prover_state.public_message(&self.domsep);
prover_state.public_message(self.instance.0);
prover_state
}

pub fn to_verifier<'ver, H>(&self, h: H, narg_string: &'ver [u8]) -> VerifierState<'ver, H>
where
H: DuplexSpongeInterface,
[u8; 64]: Encoding<[H::U]>,
S: Encoding<[H::U]>,
I: Encoding<[H::U]>,
{
let mut verifier_state = VerifierState::from_parts(h, narg_string);
verifier_state.public_message(&self.protocol);
verifier_state.public_message(&self.session.0);
verifier_state.public_message(&self.instance.0);
verifier_state.public_message(&self.domsep);
verifier_state.public_message(self.instance.0);
verifier_state
}
}
Expand Down
1 change: 1 addition & 0 deletions spongefish/src/drivers/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ where
assert_eq!(encoded_bytes(value), encoded_bytes(&decoded));
}

#[allow(unused)]
fn assert_narg_advances_buffer<T>(value: &T)
where
T: NargSerialize + NargDeserialize,
Expand Down
Loading