Skip to content

Commit 2602d3f

Browse files
ricardo-perellommaker
authored andcommitted
Fix domain separator derivation
1 parent dbc4c94 commit 2602d3f

4 files changed

Lines changed: 254 additions & 243 deletions

File tree

spongefish/examples/schnorr.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ use ark_ec::{CurveGroup, PrimeGroup};
33
use ark_std::UniformRand;
44
use rand::rngs::OsRng;
55
use spongefish::{
6-
Codec, Encoding, NargDeserialize, NargSerialize, ProverState, VerificationError,
7-
VerificationResult, VerifierState,
6+
protocol_label, Codec, DomainSeparator, Encoding, NargDeserialize, NargSerialize, ProverState,
7+
VerificationError, VerificationResult, VerifierState, DOMAIN_SEPARATOR_MACRO_SPONGE_INFO,
88
};
99

1010
struct Schnorr;
1111

1212
impl Schnorr {
13+
pub fn protocol_id() -> Vec<u8> {
14+
protocol_label(core::format_args!("schnorr proof"))
15+
}
16+
1317
/// Here the proving algorithm takes as input a [`ProverState`], and an instance-witness pair.
1418
///
1519
/// The [`ProverState`] actually depends on a duplex sponge interface (over any field) and a random number generator.
@@ -74,8 +78,12 @@ fn main() {
7478
let pk = generator * sk;
7579
let instance = [generator, pk];
7680

77-
let domain_sep =
78-
spongefish::domain_separator!("schnorr proof"; "spongefish examples").instance(&instance);
81+
let domain_sep = DomainSeparator::derive(
82+
Schnorr::protocol_id().as_ref(),
83+
DOMAIN_SEPARATOR_MACRO_SPONGE_INFO,
84+
spongefish::session!("spongefish examples").as_slice(),
85+
)
86+
.instance(&instance);
7987

8088
// Prove the relation sk * G::generator() = pk
8189
let mut prover_state = domain_sep.std_prover();

spongefish/src/domain_separator.rs

Lines changed: 96 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,186 @@
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-
54-
use core::{fmt, fmt::Arguments};
1+
use core::{fmt::Arguments, marker::PhantomData};
552

563
use rand::rngs::StdRng;
574

585
#[cfg(feature = "sha3")]
596
use crate::VerifierState;
60-
use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash, Unit};
7+
use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash};
8+
9+
/// Sponge / compilation info for [`domain_separator!`] when no explicit `sponge_info` is supplied.
10+
pub const DOMAIN_SEPARATOR_MACRO_SPONGE_INFO: &[u8] = b"spongefish/domain_separator/macro/v1";
6111

6212
/// Marker structure for domain separators without an associated instance.
6313
///
6414
/// The Fiat--Shamir transformation requires an instance to provide a sound non-interactive proof.
6515
/// This type is used to make sure that the developer does not forget to add it.
6616
///
6717
/// ```compile_fail
18+
/// # // a BAD EXAMPLE of instantiating a domain separator.
19+
/// # // It will fail at compilation time.
6820
/// use spongefish::domain_separator;
6921
///
7022
/// domain_separator!("this will not compile").std_prover();
7123
/// ```
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)]
79-
pub struct WithoutInstance;
24+
#[derive(Debug, Clone, Copy)]
25+
pub struct WithoutInstance<I: ?Sized>(PhantomData<I>);
26+
27+
impl<I: ?Sized> WithoutInstance<I> {
28+
const fn new() -> Self {
29+
Self(PhantomData)
30+
}
31+
}
8032

8133
/// Marker structure storing the instance once it has been provided.
8234
///
8335
/// ```no_run
8436
/// use spongefish::domain_separator;
8537
///
8638
/// let _prover = domain_separator!("this will compile")
87-
/// .session(spongefish::session!("example"))
8839
/// .instance(b"yellowsubmarine")
8940
/// .std_prover();
9041
/// ```
91-
pub struct WithInstance<I>(I);
92-
93-
/// Session state marker: no session context has been resolved yet.
94-
#[derive(Debug, Clone, Copy, Default)]
95-
pub struct WithoutSession;
42+
#[derive(Debug, Clone, Copy)]
43+
pub struct WithInstance<'i, I: ?Sized>(&'i I);
9644

97-
/// Session state marker: a session context has been bound.
98-
pub struct WithSession<S>(pub(crate) S);
45+
/// Domain separator for a Fiat--Shamir transformation.
46+
///
47+
/// Built only via [`DomainSeparator::derive`]: `domsep` is a 64-byte protocol tag whose
48+
/// first 32 bytes are derived from length-prefixed `(protocol_id, sponge_info, session)`,
49+
/// then the instance is absorbed separately (duplex or `StdHash` bootstrap).
50+
#[derive(Debug, Clone, Copy)]
51+
pub struct DomainSeparator<I> {
52+
/// 64-byte domain tag; the first 32 bytes are derived and the second 32 bytes are zero.
53+
/// This feeds `StdHash::from_protocol_id` / duplex init.
54+
pub domsep: [u8; 64],
55+
instance: I,
56+
}
9957

100-
impl<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-
}
58+
fn absorb_domain_field(sponge: &mut StdHash, field: &[u8]) {
59+
sponge.absorb(&(field.len() as u32).to_le_bytes());
60+
sponge.absorb(field);
10461
}
10562

106-
/// Explicit opt-out session marker.
63+
/// Length-prefixed domain derivation: `LE32(|p|)||p||LE32(|i|)||i||LE32(|s|)||s`.
10764
///
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)]
111-
pub struct NoSession;
112-
113-
impl<T: Unit> Encoding<[T]> for NoSession {
114-
fn encode(&self) -> impl AsRef<[T]> {
115-
let empty: [T; 0] = [];
116-
empty
65+
/// The first 32 bytes are squeezed from [`StdHash`]. The second 32 bytes remain zero
66+
/// so the result can still be used with the existing 64-byte protocol tag hooks.
67+
#[cfg(feature = "sha3")]
68+
#[must_use]
69+
pub fn derive_domain_digest(protocol_id: &[u8], sponge_info: &[u8], session: &[u8]) -> [u8; 64] {
70+
let mut sponge = StdHash::from_protocol_id(pad_identifier(b"fiat-shamir/domain-separator"));
71+
for field in [protocol_id, sponge_info, session] {
72+
absorb_domain_field(&mut sponge, field);
11773
}
74+
let mut domsep = [0u8; 64];
75+
sponge.squeeze(&mut domsep[..32]);
76+
domsep
11877
}
11978

120-
/// Domain separator for a Fiat--Shamir transformation.
121-
///
122-
/// The API enforces: `new → session | without_session → instance → prover/verifier`.
123-
pub 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,
79+
/// Raw UTF-8 / formatted bytes for a protocol label (unpadded), for use with [`DomainSeparator::derive`].
80+
#[must_use]
81+
pub fn protocol_label(args: Arguments) -> alloc::vec::Vec<u8> {
82+
if let Some(message) = args.as_str() {
83+
return message.as_bytes().to_vec();
84+
}
85+
alloc::fmt::format(args).into_bytes()
13086
}
13187

132-
impl DomainSeparator<WithoutInstance, WithoutSession> {
88+
#[cfg(feature = "sha3")]
89+
impl<I: ?Sized> DomainSeparator<WithoutInstance<I>> {
90+
/// Domain separation from explicit protocol bytes, compilation/sponge info, and session bytes
91+
/// (the standard sponge over a length-prefixed injective encoding).
13392
#[must_use]
134-
pub const fn new(protocol: [u8; 64]) -> Self {
93+
pub fn derive(protocol_id: &[u8], sponge_info: &[u8], session: &[u8]) -> Self {
13594
Self {
136-
protocol,
137-
session: WithoutSession,
138-
instance: WithoutInstance,
95+
domsep: derive_domain_digest(protocol_id, sponge_info, session),
96+
instance: WithoutInstance::new(),
13997
}
14098
}
141-
}
14299

143-
impl<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>> {
100+
pub fn instance(self, value: &I) -> DomainSeparator<WithInstance<'_, I>> {
150101
DomainSeparator {
151-
protocol: self.protocol,
152-
session: WithSession(value),
153-
instance: self.instance,
102+
domsep: self.domsep,
103+
instance: WithInstance(value),
154104
}
155105
}
106+
}
156107

157-
/// Explicit opt-out: the protocol deliberately binds no application context.
108+
#[cfg(feature = "sha3")]
109+
/// Precomputes the `(protocol_id, sponge_info)` prefix of [`derive_domain_digest`] so only the
110+
/// session block is hashed per proof.
111+
pub struct DomainSeparatorPrefix {
112+
prefix: StdHash,
113+
}
114+
115+
#[cfg(feature = "sha3")]
116+
impl DomainSeparatorPrefix {
158117
#[must_use]
159-
pub fn without_session(self) -> DomainSeparator<I, WithSession<NoSession>> {
160-
self.session(NoSession)
118+
pub fn new(protocol_id: &[u8], sponge_info: &[u8]) -> Self {
119+
let mut prefix = StdHash::from_protocol_id(pad_identifier(b"fiat-shamir/domain-separator"));
120+
for field in [protocol_id, sponge_info] {
121+
absorb_domain_field(&mut prefix, field);
122+
}
123+
Self { prefix }
161124
}
162-
}
163125

164-
impl<S> DomainSeparator<WithoutInstance, WithSession<S>> {
165-
pub fn instance<I>(self, value: I) -> DomainSeparator<WithInstance<I>, WithSession<S>> {
126+
/// Finishes with the session field and returns a [`DomainSeparator`] ready for `.instance(...)`.
127+
#[must_use]
128+
pub fn with_session<I: ?Sized>(&self, session: &[u8]) -> DomainSeparator<WithoutInstance<I>> {
129+
let mut sponge = self.prefix.clone();
130+
absorb_domain_field(&mut sponge, session);
131+
let mut domsep = [0u8; 64];
132+
sponge.squeeze(&mut domsep[..32]);
166133
DomainSeparator {
167-
protocol: self.protocol,
168-
session: self.session,
169-
instance: WithInstance(value),
134+
domsep,
135+
instance: WithoutInstance::new(),
170136
}
171137
}
172138
}
173139

174-
impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>>
140+
impl<I> DomainSeparator<WithInstance<'_, I>>
175141
where
176142
I: Encoding,
177-
S: Encoding,
178143
{
179144
#[cfg(feature = "sha3")]
180145
#[must_use]
181146
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);
147+
let mut prover_state = ProverState::from(StdHash::from_protocol_id(self.domsep));
148+
prover_state.public_message(self.instance.0);
185149
prover_state
186150
}
187151

188152
#[cfg(feature = "sha3")]
189153
#[must_use]
190154
pub fn std_verifier<'ver>(&self, narg_string: &'ver [u8]) -> VerifierState<'ver, StdHash> {
191155
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);
156+
VerifierState::from_parts(StdHash::from_protocol_id(self.domsep), narg_string);
157+
verifier_state.public_message(self.instance.0);
195158
verifier_state
196159
}
197160
}
198161

199-
impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>> {
162+
impl<I> DomainSeparator<WithInstance<'_, I>> {
200163
pub fn to_prover<H>(&self, h: H) -> ProverState<H, StdRng>
201164
where
202165
H: DuplexSpongeInterface,
203166
[u8; 64]: Encoding<[H::U]>,
204-
S: Encoding<[H::U]>,
205167
I: Encoding<[H::U]>,
206168
{
207169
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);
170+
prover_state.public_message(&self.domsep);
171+
prover_state.public_message(self.instance.0);
211172
prover_state
212173
}
213174

214175
pub fn to_verifier<'ver, H>(&self, h: H, narg_string: &'ver [u8]) -> VerifierState<'ver, H>
215176
where
216177
H: DuplexSpongeInterface,
217178
[u8; 64]: Encoding<[H::U]>,
218-
S: Encoding<[H::U]>,
219179
I: Encoding<[H::U]>,
220180
{
221181
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);
182+
verifier_state.public_message(&self.domsep);
183+
verifier_state.public_message(self.instance.0);
225184
verifier_state
226185
}
227186
}

0 commit comments

Comments
 (0)