Skip to main content

spongefish/
domain_separator.rs

1//! Utilities for domain separation.
2//!
3//! A "domain separator" in spongefish has three components:
4//!
5//! - a *protocol identifier*, to identify the **non-interactive** protocol being used, and it's the responsibility of the proof system to provide this component.
6//! - 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.
7//! - an *instance*, which identifies the **statement** being proven. It's the responsibility of the witness generation procedure to provide this component.
8//!
9//! A domain separator can be instantiated in several equivalent ways:
10//! ```
11//! use spongefish::{domain_separator, session};
12//!
13//! let x = [1u8, 2, 3];
14//!
15//! // all at once, via the helper macro.
16//! let _ds1 = domain_separator!("proto"; "sess").instance(&x);
17//! // with the session provided at a later time:
18//! let _ds2 = domain_separator!("proto").session(session!("sess")).instance(&x);
19//! // if not specified, the session identifier is set to zero.
20//! let _ds3 = domain_separator!("proto").without_session().instance(&x);
21//! ```
22//! Domain separators can then be turned into prover and verifier state via
23//! [`DomainSeparator::to_prover`] and [`DomainSeparator::to_verifier`].
24//! Shorthands for [`StdHash`] are available via [`DomainSeparator::std_prover`] and [`DomainSeparator::std_verifier`].
25//! ```
26//! use spongefish::{domain_separator, session};
27//!
28//! let x = [1u8, 2, 3];
29//! let ds1 = domain_separator!("proto"; "sess").instance(&x);
30//! let ds2 = domain_separator!("proto").session(session!("sess")).instance(&x);
31//!
32//! // Same protocol, session, and instance yield the same transcript
33//! assert_eq!(
34//!     ds1.std_prover().verifier_message::<u64>(),
35//!     ds2.std_prover().verifier_message::<u64>()
36//! );
37//! ```
38//!
39//! For testing purposes, it's possible to instantiate a protocol without a session:
40//!
41//! ```
42//! use spongefish::{domain_separator, session};
43//!
44//! let x = [1u8, 2, 3];
45//! let ds1 = domain_separator!("proto"; "sess").instance(&x);
46//! let ds3 = domain_separator!("proto").without_session().instance(&x);
47//! assert_ne!(
48//!     ds1.std_prover().verifier_message::<u64>(),
49//!     ds3.std_prover().verifier_message::<u64>()
50//! );
51//! ```
52//!
53
54use core::{fmt, fmt::Arguments};
55
56use rand::rngs::StdRng;
57
58#[cfg(feature = "sha3")]
59use crate::VerifierState;
60use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash, Unit};
61
62/// Marker structure for domain separators without an associated instance.
63///
64/// The Fiat--Shamir transformation requires an instance to provide a sound non-interactive proof.
65/// This type is used to make sure that the developer does not forget to add it.
66///
67/// ```compile_fail
68/// use spongefish::domain_separator;
69///
70/// domain_separator!("this will not compile").std_prover();
71/// ```
72///
73/// ```compile_fail
74/// use spongefish::DomainSeparator;
75///
76/// DomainSeparator::new([0u8; 64]).instance(b"missing session");
77/// ```
78#[derive(Debug, Default, Copy, Clone)]
79pub struct WithoutInstance;
80
81/// Marker structure storing the instance once it has been provided.
82///
83/// ```no_run
84/// use spongefish::domain_separator;
85///
86/// let _prover = domain_separator!("this will compile")
87///     .session(spongefish::session!("example"))
88///     .instance(b"yellowsubmarine")
89///     .std_prover();
90/// ```
91pub struct WithInstance<I>(I);
92
93/// Session state marker: no session context has been resolved yet.
94#[derive(Debug, Clone, Copy, Default)]
95pub struct WithoutSession;
96
97/// Session state marker: a session context has been bound.
98pub struct WithSession<S>(pub(crate) S);
99
100impl<S: fmt::Debug> fmt::Debug for WithSession<S> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.debug_tuple("WithSession").field(&self.0).finish()
103    }
104}
105
106/// Explicit opt-out session marker.
107///
108/// Used by [`DomainSeparator::without_session`]. Encodes to an empty slice,
109/// matching the original behaviour when no session was provided.
110#[derive(Debug, Clone, Copy, Default)]
111pub struct NoSession;
112
113impl<T: Unit> Encoding<[T]> for NoSession {
114    fn encode(&self) -> impl AsRef<[T]> {
115        let empty: [T; 0] = [];
116        empty
117    }
118}
119
120/// Domain separator for a Fiat--Shamir transformation.
121///
122/// The API enforces: `new → session | without_session → instance → prover/verifier`.
123pub struct DomainSeparator<I, S = WithoutSession> {
124    /// **what** this interactive protocol is.
125    pub protocol: [u8; 64],
126    /// **where** this interactive protocol is being used.
127    pub session: S,
128    /// **how** this interactive protocol is used.
129    instance: I,
130}
131
132impl DomainSeparator<WithoutInstance, WithoutSession> {
133    #[must_use]
134    pub const fn new(protocol: [u8; 64]) -> Self {
135        Self {
136            protocol,
137            session: WithoutSession,
138            instance: WithoutInstance,
139        }
140    }
141}
142
143impl<I> DomainSeparator<I, WithoutSession> {
144    /// Binds a session context to the transcript.
145    ///
146    /// The session value may be provided either by value or by reference.
147    /// Passing `&session` avoids copying large session objects.
148    #[must_use]
149    pub fn session<S>(self, value: S) -> DomainSeparator<I, WithSession<S>> {
150        DomainSeparator {
151            protocol: self.protocol,
152            session: WithSession(value),
153            instance: self.instance,
154        }
155    }
156
157    /// Explicit opt-out: the protocol deliberately binds no application context.
158    #[must_use]
159    pub fn without_session(self) -> DomainSeparator<I, WithSession<NoSession>> {
160        self.session(NoSession)
161    }
162}
163
164impl<S> DomainSeparator<WithoutInstance, WithSession<S>> {
165    pub fn instance<I>(self, value: I) -> DomainSeparator<WithInstance<I>, WithSession<S>> {
166        DomainSeparator {
167            protocol: self.protocol,
168            session: self.session,
169            instance: WithInstance(value),
170        }
171    }
172}
173
174impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>>
175where
176    I: Encoding,
177    S: Encoding,
178{
179    #[cfg(feature = "sha3")]
180    #[must_use]
181    pub fn std_prover(&self) -> ProverState {
182        let mut prover_state = ProverState::from(StdHash::from_protocol_id(self.protocol));
183        prover_state.public_message(&self.session.0);
184        prover_state.public_message(&self.instance.0);
185        prover_state
186    }
187
188    #[cfg(feature = "sha3")]
189    #[must_use]
190    pub fn std_verifier<'ver>(&self, narg_string: &'ver [u8]) -> VerifierState<'ver, StdHash> {
191        let mut verifier_state =
192            VerifierState::from_parts(StdHash::from_protocol_id(self.protocol), narg_string);
193        verifier_state.public_message(&self.session.0);
194        verifier_state.public_message(&self.instance.0);
195        verifier_state
196    }
197}
198
199impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>> {
200    pub fn to_prover<H>(&self, h: H) -> ProverState<H, StdRng>
201    where
202        H: DuplexSpongeInterface,
203        [u8; 64]: Encoding<[H::U]>,
204        S: Encoding<[H::U]>,
205        I: Encoding<[H::U]>,
206    {
207        let mut prover_state = ProverState::from(h);
208        prover_state.public_message(&self.protocol);
209        prover_state.public_message(&self.session.0);
210        prover_state.public_message(&self.instance.0);
211        prover_state
212    }
213
214    pub fn to_verifier<'ver, H>(&self, h: H, narg_string: &'ver [u8]) -> VerifierState<'ver, H>
215    where
216        H: DuplexSpongeInterface,
217        [u8; 64]: Encoding<[H::U]>,
218        S: Encoding<[H::U]>,
219        I: Encoding<[H::U]>,
220    {
221        let mut verifier_state = VerifierState::from_parts(h, narg_string);
222        verifier_state.public_message(&self.protocol);
223        verifier_state.public_message(&self.session.0);
224        verifier_state.public_message(&self.instance.0);
225        verifier_state
226    }
227}
228
229#[inline]
230#[must_use]
231pub fn protocol_id(args: Arguments) -> [u8; 64] {
232    if let Some(message) = args.as_str() {
233        return pad_identifier(message.as_bytes());
234    }
235
236    let formatted = alloc::fmt::format(args);
237    pad_identifier(formatted.as_bytes())
238}
239
240#[inline]
241#[must_use]
242pub fn session_id(args: Arguments) -> [u8; 64] {
243    if let Some(message) = args.as_str() {
244        return derive_session_id(message.as_bytes());
245    }
246
247    let formatted = alloc::fmt::format(args);
248    derive_session_id(formatted.as_bytes())
249}
250
251#[inline]
252#[doc(hidden)]
253#[must_use]
254pub fn session_id_from_str<S>(value: &S) -> [u8; 64]
255where
256    S: AsRef<str> + ?Sized,
257{
258    derive_session_id(value.as_ref().as_bytes())
259}
260
261fn pad_identifier(identifier: &[u8]) -> [u8; 64] {
262    assert!(
263        identifier.len() <= 64,
264        "protocol identifier must fit in 64 bytes"
265    );
266
267    let mut protocol_id = [0u8; 64];
268    protocol_id[..identifier.len()].copy_from_slice(identifier);
269    protocol_id
270}
271
272fn derive_session_id(session: &[u8]) -> [u8; 64] {
273    let mut sponge = StdHash::from_protocol_id(pad_identifier(b"fiat-shamir/session-id"));
274    sponge.absorb(session);
275
276    let mut session_id = [0u8; 64];
277    sponge.squeeze(&mut session_id[32..]);
278    session_id
279}