|
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}; |
55 | 2 |
|
56 | 3 | use rand::rngs::StdRng; |
57 | 4 |
|
58 | 5 | #[cfg(feature = "sha3")] |
59 | 6 | 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"; |
61 | 11 |
|
62 | 12 | /// Marker structure for domain separators without an associated instance. |
63 | 13 | /// |
64 | 14 | /// The Fiat--Shamir transformation requires an instance to provide a sound non-interactive proof. |
65 | 15 | /// This type is used to make sure that the developer does not forget to add it. |
66 | 16 | /// |
67 | 17 | /// ```compile_fail |
| 18 | +/// # // a BAD EXAMPLE of instantiating a domain separator. |
| 19 | +/// # // It will fail at compilation time. |
68 | 20 | /// use spongefish::domain_separator; |
69 | 21 | /// |
70 | 22 | /// domain_separator!("this will not compile").std_prover(); |
71 | 23 | /// ``` |
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 | +} |
80 | 32 |
|
81 | 33 | /// Marker structure storing the instance once it has been provided. |
82 | 34 | /// |
83 | 35 | /// ```no_run |
84 | 36 | /// use spongefish::domain_separator; |
85 | 37 | /// |
86 | 38 | /// let _prover = domain_separator!("this will compile") |
87 | | -/// .session(spongefish::session!("example")) |
88 | 39 | /// .instance(b"yellowsubmarine") |
89 | 40 | /// .std_prover(); |
90 | 41 | /// ``` |
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); |
96 | 44 |
|
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 | +} |
99 | 57 |
|
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); |
104 | 61 | } |
105 | 62 |
|
106 | | -/// Explicit opt-out session marker. |
| 63 | +/// Length-prefixed domain derivation: `LE32(|p|)||p||LE32(|i|)||i||LE32(|s|)||s`. |
107 | 64 | /// |
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); |
117 | 73 | } |
| 74 | + let mut domsep = [0u8; 64]; |
| 75 | + sponge.squeeze(&mut domsep[..32]); |
| 76 | + domsep |
118 | 77 | } |
119 | 78 |
|
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() |
130 | 86 | } |
131 | 87 |
|
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). |
133 | 92 | #[must_use] |
134 | | - pub const fn new(protocol: [u8; 64]) -> Self { |
| 93 | + pub fn derive(protocol_id: &[u8], sponge_info: &[u8], session: &[u8]) -> Self { |
135 | 94 | Self { |
136 | | - protocol, |
137 | | - session: WithoutSession, |
138 | | - instance: WithoutInstance, |
| 95 | + domsep: derive_domain_digest(protocol_id, sponge_info, session), |
| 96 | + instance: WithoutInstance::new(), |
139 | 97 | } |
140 | 98 | } |
141 | | -} |
142 | 99 |
|
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>> { |
150 | 101 | DomainSeparator { |
151 | | - protocol: self.protocol, |
152 | | - session: WithSession(value), |
153 | | - instance: self.instance, |
| 102 | + domsep: self.domsep, |
| 103 | + instance: WithInstance(value), |
154 | 104 | } |
155 | 105 | } |
| 106 | +} |
156 | 107 |
|
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 { |
158 | 117 | #[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 } |
161 | 124 | } |
162 | | -} |
163 | 125 |
|
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]); |
166 | 133 | DomainSeparator { |
167 | | - protocol: self.protocol, |
168 | | - session: self.session, |
169 | | - instance: WithInstance(value), |
| 134 | + domsep, |
| 135 | + instance: WithoutInstance::new(), |
170 | 136 | } |
171 | 137 | } |
172 | 138 | } |
173 | 139 |
|
174 | | -impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>> |
| 140 | +impl<I> DomainSeparator<WithInstance<'_, I>> |
175 | 141 | where |
176 | 142 | I: Encoding, |
177 | | - S: Encoding, |
178 | 143 | { |
179 | 144 | #[cfg(feature = "sha3")] |
180 | 145 | #[must_use] |
181 | 146 | 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); |
185 | 149 | prover_state |
186 | 150 | } |
187 | 151 |
|
188 | 152 | #[cfg(feature = "sha3")] |
189 | 153 | #[must_use] |
190 | 154 | pub fn std_verifier<'ver>(&self, narg_string: &'ver [u8]) -> VerifierState<'ver, StdHash> { |
191 | 155 | 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); |
195 | 158 | verifier_state |
196 | 159 | } |
197 | 160 | } |
198 | 161 |
|
199 | | -impl<I, S> DomainSeparator<WithInstance<I>, WithSession<S>> { |
| 162 | +impl<I> DomainSeparator<WithInstance<'_, I>> { |
200 | 163 | pub fn to_prover<H>(&self, h: H) -> ProverState<H, StdRng> |
201 | 164 | where |
202 | 165 | H: DuplexSpongeInterface, |
203 | 166 | [u8; 64]: Encoding<[H::U]>, |
204 | | - S: Encoding<[H::U]>, |
205 | 167 | I: Encoding<[H::U]>, |
206 | 168 | { |
207 | 169 | 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); |
211 | 172 | prover_state |
212 | 173 | } |
213 | 174 |
|
214 | 175 | pub fn to_verifier<'ver, H>(&self, h: H, narg_string: &'ver [u8]) -> VerifierState<'ver, H> |
215 | 176 | where |
216 | 177 | H: DuplexSpongeInterface, |
217 | 178 | [u8; 64]: Encoding<[H::U]>, |
218 | | - S: Encoding<[H::U]>, |
219 | 179 | I: Encoding<[H::U]>, |
220 | 180 | { |
221 | 181 | 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); |
225 | 184 | verifier_state |
226 | 185 | } |
227 | 186 | } |
|
0 commit comments