diff --git a/spongefish/examples/schnorr.rs b/spongefish/examples/schnorr.rs index 4539c66..46acc09 100644 --- a/spongefish/examples/schnorr.rs +++ b/spongefish/examples/schnorr.rs @@ -3,13 +3,17 @@ use ark_ec::{CurveGroup, PrimeGroup}; use ark_std::UniformRand; use rand::rngs::OsRng; use spongefish::{ - Codec, Encoding, NargDeserialize, NargSerialize, ProverState, VerificationError, - VerificationResult, VerifierState, + protocol_label, Codec, DomainSeparator, Encoding, NargDeserialize, NargSerialize, ProverState, + VerificationError, VerificationResult, VerifierState, DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, }; struct Schnorr; impl Schnorr { + pub fn protocol_id() -> Vec { + protocol_label(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. @@ -74,8 +78,12 @@ fn main() { let pk = generator * sk; let instance = [generator, pk]; - let domain_sep = - spongefish::domain_separator!("schnorr proof"; "spongefish examples").instance(&instance); + let domain_sep = DomainSeparator::derive( + Schnorr::protocol_id().as_ref(), + DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + spongefish::session!("spongefish examples").as_slice(), + ) + .instance(&instance); // Prove the relation sk * G::generator() = pk let mut prover_state = domain_sep.std_prover(); diff --git a/spongefish/src/domain_separator.rs b/spongefish/src/domain_separator.rs index 0c52d01..abca733 100644 --- a/spongefish/src/domain_separator.rs +++ b/spongefish/src/domain_separator.rs @@ -1,63 +1,13 @@ -//! Utilities for domain separation. -//! -//! A "domain separator" in spongefish has three components: -//! -//! - 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 domain separator can be instantiated in several equivalent ways: -//! ``` -//! use spongefish::{domain_separator, session}; -//! -//! let x = [1u8, 2, 3]; -//! -//! // 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`]. -//! ``` -//! use spongefish::{domain_separator, session}; -//! -//! let x = [1u8, 2, 3]; -//! let ds1 = domain_separator!("proto"; "sess").instance(&x); -//! let ds2 = domain_separator!("proto").session(session!("sess")).instance(&x); -//! -//! // Same protocol, session, and instance yield the same transcript -//! assert_eq!( -//! ds1.std_prover().verifier_message::(), -//! ds2.std_prover().verifier_message::() -//! ); -//! ``` -//! -//! 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::(), -//! ds3.std_prover().verifier_message::() -//! ); -//! ``` -//! - -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}; + +/// Sponge / compilation info for [`domain_separator!`] when no explicit `sponge_info` is supplied. +pub const DOMAIN_SEPARATOR_MACRO_SPONGE_INFO: &[u8] = b"spongefish/domain_separator/macro/v1"; /// Marker structure for domain separators without an associated instance. /// @@ -65,18 +15,20 @@ use crate::{DuplexSpongeInterface, Encoding, ProverState, StdHash, Unit}; /// This type is used to make sure that the developer does not forget to add it. /// /// ```compile_fail +/// # // a BAD EXAMPLE of instantiating a domain separator. +/// # // It will fail at compilation time. /// use spongefish::domain_separator; /// /// domain_separator!("this will not compile").std_prover(); /// ``` -/// -/// ```compile_fail -/// use spongefish::DomainSeparator; -/// -/// DomainSeparator::new([0u8; 64]).instance(b"missing session"); -/// ``` -#[derive(Debug, Default, Copy, Clone)] -pub struct WithoutInstance; +#[derive(Debug, Clone, Copy)] +pub struct WithoutInstance(PhantomData); + +impl WithoutInstance { + const fn new() -> Self { + Self(PhantomData) + } +} /// Marker structure storing the instance once it has been provided. /// @@ -84,104 +36,116 @@ pub struct WithoutInstance; /// use spongefish::domain_separator; /// /// let _prover = domain_separator!("this will compile") -/// .session(spongefish::session!("example")) /// .instance(b"yellowsubmarine") /// .std_prover(); /// ``` -pub struct WithInstance(I); - -/// Session state marker: no session context has been resolved yet. -#[derive(Debug, Clone, Copy, Default)] -pub struct WithoutSession; +#[derive(Debug, Clone, Copy)] +pub struct WithInstance<'i, I: ?Sized>(&'i I); -/// Session state marker: a session context has been bound. -pub struct WithSession(pub(crate) S); +/// Domain separator for a Fiat--Shamir transformation. +/// +/// Built only via [`DomainSeparator::derive`]: `domsep` is a 64-byte protocol tag whose +/// first 32 bytes are derived from length-prefixed `(protocol_id, sponge_info, session)`, +/// then the instance is absorbed separately (duplex or `StdHash` bootstrap). +#[derive(Debug, Clone, Copy)] +pub struct DomainSeparator { + /// 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, +} -impl fmt::Debug for WithSession { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("WithSession").field(&self.0).finish() - } +fn absorb_domain_field(sponge: &mut StdHash, field: &[u8]) { + sponge.absorb(&(field.len() as u32).to_le_bytes()); + sponge.absorb(field); } -/// 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; - -impl Encoding<[T]> for NoSession { - fn encode(&self) -> impl AsRef<[T]> { - let empty: [T; 0] = []; - empty +/// 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")); + for field in [protocol_id, sponge_info, session] { + absorb_domain_field(&mut sponge, field); } + 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 { - /// **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 { + if let Some(message) = args.as_str() { + return message.as_bytes().to_vec(); + } + alloc::fmt::format(args).into_bytes() } -impl DomainSeparator { +#[cfg(feature = "sha3")] +impl DomainSeparator> { + /// 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 DomainSeparator { - /// 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(self, value: S) -> DomainSeparator> { + pub fn instance(self, value: &I) -> DomainSeparator> { 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> { - 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")); + for field in [protocol_id, sponge_info] { + absorb_domain_field(&mut prefix, field); + } + Self { prefix } } -} -impl DomainSeparator> { - pub fn instance(self, value: I) -> DomainSeparator, WithSession> { + /// Finishes with the session field and returns a [`DomainSeparator`] ready for `.instance(...)`. + #[must_use] + pub fn with_session(&self, session: &[u8]) -> DomainSeparator> { + let mut sponge = self.prefix.clone(); + absorb_domain_field(&mut sponge, 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 DomainSeparator, WithSession> +impl DomainSeparator> 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 } @@ -189,25 +153,22 @@ where #[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 DomainSeparator, WithSession> { +impl DomainSeparator> { pub fn to_prover(&self, h: H) -> ProverState 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 } @@ -215,13 +176,11 @@ impl DomainSeparator, WithSession> { 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 } } diff --git a/spongefish/src/lib.rs b/spongefish/src/lib.rs index 930c410..a287699 100644 --- a/spongefish/src/lib.rs +++ b/spongefish/src/lib.rs @@ -5,7 +5,8 @@ //! # Examples //! //! A [`ProverState`] and a [`VerifierState`] can be built via a [`DomainSeparator`], which -//! is composed of a protocol identifier, a mandatory session identifier, and the public instance. +//! binds a derived 64-byte domain tag (from protocol id, sponge/compilation info, and session bytes), +//! then the public instance. //! The snippets below illustrate three typical situations. //! //! ``` @@ -33,6 +34,7 @@ //! // a proof is malleable if we don't check we read everything //! assert!(verifier_state.check_eof().is_ok()) //! ``` +//! //! The above code will fail to compile if no instance is given. //! The implementor has full responsibility in providing the correct instance of the proof system. //! @@ -153,13 +155,10 @@ //! //! Previous version of this library were audited by [Radically Open Security]. //! -//! The user has full responsibility in instantiating [`DomainSeparator`] in a secure way, -//! but the library requiring three elements on initialization: -//! - a mandatory 64-bytes protocol identifier, -//! uniquely identifying the non-interactive protocol being built. -//! - a 64-bytes session identifier, -//! corresponding to session and sub-session identifiers in universal composability lingo. -//! - a mandatory instance that will be used in the proof system. +//! The user has full responsibility in instantiating [`DomainSeparator`] in a secure way. +//! [`DomainSeparator::derive`] takes protocol bytes, sponge/compilation info, and session bytes, +//! hashes them with the standard hash into a 64-byte tag whose second half is zero, +//! then the instance is absorbed into the transcript. //! //! The developer is in charge of making sure they are chosen appropriately. //! In particular, the instance encoding function prefix-free. @@ -214,11 +213,13 @@ mod domain_separator; #[doc(hidden)] pub use codecs::ByteArray; pub use codecs::{Codec, Decoding, Encoding}; +pub use domain_separator::protocol_label; +pub use domain_separator::DomainSeparator; +pub use domain_separator::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO; +#[cfg(feature = "sha3")] +pub use domain_separator::{derive_domain_digest, DomainSeparatorPrefix}; #[doc(hidden)] pub use domain_separator::{protocol_id, session_id, session_id_from_str}; -pub use domain_separator::{ - DomainSeparator, NoSession, WithInstance, WithSession, WithoutInstance, WithoutSession, -}; pub use duplex_sponge::{DuplexSponge, DuplexSpongeInterface, Permutation, Unit}; pub use error::{VerificationError, VerificationResult}; pub use io::{NargDeserialize, NargSerialize}; @@ -231,36 +232,49 @@ pub use spongefish_derive::{Codec, Decoding, Encoding, NargDeserialize, Unit}; #[cfg(feature = "sha3")] pub type StdHash = instantiations::Shake128; -/// Build a [`DomainSeparator`] from a protocol identifier string. -/// -/// Chain `.session(..)` or `.without_session()` before `.instance(..)`. +/// Build a [`DomainSeparator`] from a formatted string. /// /// ``` -/// let domsep = spongefish::domain_separator!("spongefish") -/// .session(spongefish::session!("DomainSeparator")) +/// let domsep = spongefish::domain_separator!("spongefish"; "DomainSeparator") /// .instance(b"trivial"); /// let _prover = domsep.std_prover(); /// ``` #[macro_export] macro_rules! domain_separator { - ($protocol_fmt:literal $(, $protocol_arg:expr)* ; $session_fmt:literal $(, $session_arg:expr)* $(,)?) => {{ - $crate::DomainSeparator::new($crate::protocol_id(core::format_args!( - $protocol_fmt $(, $protocol_arg)* - ))) - .session($crate::session!($session_fmt $(, $session_arg)*)) + ($fmt:literal $(, $arg:expr)* $(,)? ; $sess_fmt:literal $(, $sess_arg:expr)* $(,)?) => {{ + let protocol = $crate::protocol_label(core::format_args!($fmt $(, $arg)*)); + let sess = $crate::session_id(core::format_args!($sess_fmt $(, $sess_arg)*)); + $crate::DomainSeparator::derive( + protocol.as_slice(), + $crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + sess.as_slice(), + ) + }}; + ($fmt:literal $(, $arg:expr)* $(,)? ; $session:expr $(,)?) => {{ + let protocol = $crate::protocol_label(core::format_args!($fmt $(, $arg)*)); + let sess = $crate::session_id_from_str(&$session); + $crate::DomainSeparator::derive( + protocol.as_slice(), + $crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + sess.as_slice(), + ) }}; ($fmt:literal $(, $arg:expr)* $(,)?) => {{ - $crate::DomainSeparator::new($crate::protocol_id(core::format_args!($fmt $(, $arg)*))) + let protocol = $crate::protocol_label(core::format_args!($fmt $(, $arg)*)); + $crate::DomainSeparator::derive( + protocol.as_slice(), + $crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + &[], + ) }}; } -/// Attaches a 64-byte session identifier to the domain separator. +/// Builds session bytes (64-byte field, high half from SHAKE128) for use with [`DomainSeparator::derive`]. /// /// ``` /// # use spongefish::{DomainSeparator, session}; /// -/// DomainSeparator::new([0u8; 64]) -/// .session(session!("example at L{{line!()}}")) +/// let _ = DomainSeparator::derive(b"proto", b"sponge-info", session!("example at L{{line!()}}").as_slice()) /// .instance(b"empty"); /// ``` #[macro_export] diff --git a/spongefish/src/tests.rs b/spongefish/src/tests.rs index 17e99ca..f24b33c 100644 --- a/spongefish/src/tests.rs +++ b/spongefish/src/tests.rs @@ -8,9 +8,7 @@ use crate::{DuplexSpongeInterface, Encoding, NargDeserialize, VerificationError} #[test] fn prover_rng_emits_entropy() { let instance = [42u32, 7u32]; - let domain = crate::domain_separator!("rng test") - .session(crate::session!("rng session")) - .instance(&instance); + let domain = crate::domain_separator!("rng test"; "rng session").instance(&instance); let mut prover = domain.std_prover(); let mut first = [0u8; 32]; @@ -25,9 +23,7 @@ fn prover_rng_emits_entropy() { #[test] fn prover_messages_round_trip() { let instance = [1u32, 2u32]; - let domain = crate::domain_separator!("round trip") - .without_session() - .instance(&instance); + let domain = crate::domain_separator!("round trip").instance(&instance); let mut prover = domain.std_prover(); prover.public_message(&instance[0]); @@ -43,9 +39,7 @@ fn prover_messages_round_trip() { #[test] fn check_eof_reports_remaining_bytes() { let instance = [5u32, 6u32]; - let domain = crate::domain_separator!("check eof") - .without_session() - .instance(&instance); + let domain = crate::domain_separator!("check eof").instance(&instance); let mut prover = domain.std_prover(); prover.prover_message(&instance[0]); @@ -60,9 +54,8 @@ fn check_eof_reports_remaining_bytes() { #[test] fn verifier_challenge_matches_prover() { let instance = [10u32, 11u32]; - let domain = crate::domain_separator!("challenge sync") - .session(crate::session!("challenge session")) - .instance(&instance); + let domain = + crate::domain_separator!("challenge sync"; "challenge session").instance(&instance); let mut prover = domain.std_prover(); let challenge: u32 = prover.verifier_message(); @@ -74,54 +67,43 @@ fn verifier_challenge_matches_prover() { } #[test] -fn domain_separator_accepts_variable_sessions() { +fn domain_separator_macro_session_arms_agree_on_domsep() { let instance = [0u8; 0]; - let literal_session = crate::domain_separator!("variable sessions") - .session(crate::session!("shared session")) - .instance(&instance) - .session - .0; - + let a = crate::domain_separator!("variable sessions"; "shared session").instance(&instance); let session_str = "shared session"; - let from_str = crate::domain_separator!("variable sessions") - .session(crate::session_id_from_str(session_str)) - .instance(&instance) - .session - .0; - assert_eq!(literal_session, from_str); - + let b = crate::domain_separator!("variable sessions"; session_str).instance(&instance); let session_owned = String::from("shared session"); - let from_owned = crate::domain_separator!("variable sessions") - .session(crate::session_id_from_str(&session_owned)) - .instance(&instance) - .session - .0; - assert_eq!(literal_session, from_owned); + let c = crate::domain_separator!("variable sessions"; session_owned).instance(&instance); + let d = crate::domain_separator!("variable sessions"; &session_owned).instance(&instance); + assert_eq!(a.domsep, b.domsep); + assert_eq!(a.domsep, c.domsep); + assert_eq!(a.domsep, d.domsep); } +/// Empty `session` in [`crate::DomainSeparator::derive`] must not coincide with a real session +/// (upstream tested this via `without_session()` vs `.session(...)`). #[test] -fn without_session_distinct_from_real_session() { +fn empty_session_distinct_from_nonempty_session() { let instance = [0u8; 0]; + let proto = crate::protocol_label(core::format_args!("app")); + let no_sess = crate::DomainSeparator::derive( + proto.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + &[], + ) + .instance(&instance); + let with_sess = crate::domain_separator!("app"; "production").instance(&instance); - let no_sess = crate::domain_separator!("app") - .without_session() - .instance(&instance); - let with_sess = crate::domain_separator!("app") - .session(crate::session!("production")) - .instance(&instance); + assert_ne!(no_sess.domsep, with_sess.domsep); let mut a = no_sess.std_prover(); let mut b = with_sess.std_prover(); - - let ca: u32 = a.verifier_message(); - let cb: u32 = b.verifier_message(); - - assert_ne!(ca, cb); + assert_ne!(a.verifier_message::(), b.verifier_message::()); } #[test] -fn different_session_values_diverge() { - use crate::{DomainSeparator, Encoding}; +fn different_derive_session_bytes_diverge() { + use crate::DomainSeparator; struct Ctx(u64); @@ -132,27 +114,27 @@ fn different_session_values_diverge() { } let instance = [0u8; 0]; - let session_1 = Ctx(1); - let session_2 = Ctx(2); - let a = DomainSeparator::new(crate::protocol_id(core::format_args!("p"))) - .session(session_1) - .instance(&instance); - let b = DomainSeparator::new(crate::protocol_id(core::format_args!("p"))) - .session(session_2) - .instance(&instance); + let proto = crate::protocol_id(core::format_args!("p")); + let a = DomainSeparator::derive( + proto.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + Ctx(1).encode().as_ref(), + ) + .instance(&instance); + let b = DomainSeparator::derive( + proto.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + Ctx(2).encode().as_ref(), + ) + .instance(&instance); let mut pa = a.std_prover(); let mut pb = b.std_prover(); - - let ca: u32 = pa.verifier_message(); - let cb: u32 = pb.verifier_message(); - assert_ne!(ca, cb); + assert_ne!(pa.verifier_message::(), pb.verifier_message::()); } #[test] -fn borrowed_session_matches_owned_session() { - use alloc::string::String; - +fn same_session_encoding_same_challenge() { struct Ctx(String); impl Encoding for Ctx { @@ -162,17 +144,26 @@ fn borrowed_session_matches_owned_session() { } let instance = [0u8; 0]; - let borrowed_ctx = Ctx(String::from("borrowed-session")); - let borrowed = crate::domain_separator!("borrowed session") - .session(&borrowed_ctx) - .instance(&instance); - let owned = crate::domain_separator!("borrowed session") - .session(Ctx(String::from("borrowed-session"))) - .instance(&instance); - - let borrowed_challenge: u64 = borrowed.std_prover().verifier_message(); - let owned_challenge: u64 = owned.std_prover().verifier_message(); - assert_eq!(borrowed_challenge, owned_challenge); + let proto = crate::protocol_label(core::format_args!("borrowed session")); + let b1 = Ctx(String::from("borrowed-session")); + let b2 = Ctx(String::from("borrowed-session")); + let dom1 = crate::DomainSeparator::derive( + proto.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + b1.encode().as_ref(), + ) + .instance(&instance); + let dom2 = crate::DomainSeparator::derive( + proto.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + b2.encode().as_ref(), + ) + .instance(&instance); + + assert_eq!(dom1.domsep, dom2.domsep); + let c1: u64 = dom1.std_prover().verifier_message(); + let c2: u64 = dom2.std_prover().verifier_message(); + assert_eq!(c1, c2); } #[test] @@ -207,15 +198,22 @@ fn std_transcript_initialization_matches_manual_shake128() { let session = crate::session_id(core::format_args!("discrete_logarithm")); let instance = [42u32, 7u32]; - let domain = crate::DomainSeparator::new(protocol) - .session(session) - .instance(&instance); + let domain = crate::DomainSeparator::derive( + protocol.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + session.as_slice(), + ) + .instance(&instance); let mut prover = domain.std_prover(); let challenge: [u8; 32] = prover.verifier_message(); - let mut manual = crate::StdHash::from_protocol_id(protocol); - manual.absorb(&session); + let domsep = crate::derive_domain_digest( + protocol.as_slice(), + crate::DOMAIN_SEPARATOR_MACRO_SPONGE_INFO, + session.as_slice(), + ); + let mut manual = crate::StdHash::from_protocol_id(domsep); let encoded_instance = instance.encode(); manual.absorb(encoded_instance.as_ref()); let expected = manual.squeeze_array::<32>(); @@ -223,6 +221,38 @@ fn std_transcript_initialization_matches_manual_shake128() { assert_eq!(challenge, expected); } +#[test] +fn derive_matches_prefix_builder_and_differs_on_inputs() { + let p = b"p"; + let i = b"i"; + let s = b"s"; + let d = crate::derive_domain_digest(p, i, s); + assert!(d[..32].iter().any(|&byte| byte != 0)); + assert!(d[32..].iter().all(|&byte| byte == 0)); + + let from_builder = crate::DomainSeparatorPrefix::new(p, i).with_session::<[u32; 0]>(s); + assert_eq!(from_builder.domsep, d); + + let d2 = crate::derive_domain_digest(p, i, b"t"); + assert_ne!(d, d2); +} + +#[cfg(feature = "keccak")] +#[test] +fn derived_duplex_keccak_challenge_matches() { + use crate::instantiations::Keccak; + + let instance = [3u32, 14u32]; + let dom = crate::DomainSeparator::derive(b"proto", b"sponge", b"sess").instance(&instance); + + let mut p = dom.to_prover(Keccak::default()); + let challenge: u32 = p.verifier_message(); + let proof = p.narg_string().to_vec(); + + let mut v = dom.to_verifier(Keccak::default(), &proof); + assert_eq!(v.verifier_message::(), challenge); +} + #[test] fn verifier_prover_message_rolls_back_on_deserialize_error() { struct BadMessage;