diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 9699f244..bf9e5783 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -6,11 +6,8 @@ use pasta_curves::pallas; use crate::spec::extract_p; -const GROUP_HASH_Q: &str = "z.cash:SinsemillaQ"; -const GROUP_HASH_S: &str = "z.cash:SinsemillaS"; - -const K: usize = 10; -const C: usize = 253; +mod constants; +pub use constants::*; fn lebs2ip_k(bits: &[bool]) -> u32 { assert!(bits.len() == K); @@ -79,59 +76,102 @@ impl> Iterator for Pad { } } +/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can +/// be used. +#[derive(Debug)] #[allow(non_snake_case)] -fn Q(domain_prefix: &str) -> pallas::Point { - pallas::Point::hash_to_curve(GROUP_HASH_Q)(domain_prefix.as_bytes()) +pub struct HashDomain { + Q: pallas::Point, } -/// `SinsemillaHashToPoint` from [§ 5.4.1.9][concretesinsemillahash]. -/// -/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash +impl HashDomain { + /// Constructs a new `HashDomain` with a specific prefix string. + pub(crate) fn new(domain: &str) -> Self { + HashDomain { + Q: pallas::Point::hash_to_curve(Q_PERSONALIZATION)(domain.as_bytes()), + } + } + + /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash + #[allow(non_snake_case)] + pub(crate) fn hash_to_point(&self, msg: impl Iterator) -> pallas::Point { + let padded: Vec<_> = Pad::new(msg).collect(); + + let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION); + let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); + + padded + .chunks(K) + .fold(self.Q, |acc, chunk| acc.double() + S(chunk)) + } + + /// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash + pub(crate) fn hash(&self, msg: impl Iterator) -> pallas::Base { + extract_p(&self.hash_to_point(msg)) + } + + /// Returns the Sinsemilla $Q$ constant for this domain. + #[cfg(test)] + #[allow(non_snake_case)] + pub(crate) fn Q(&self) -> pallas::Point { + self.Q + } +} + +/// A domain in which $\mathsf{SinsemillaCommit}$ and $\mathsf{SinsemillaShortCommit}$ can +/// be used. +#[derive(Debug)] #[allow(non_snake_case)] -pub(crate) fn hash_to_point(domain_prefix: &str, msg: impl Iterator) -> pallas::Point { - let padded: Vec<_> = Pad::new(msg).collect(); - - let hasher_S = pallas::Point::hash_to_curve(GROUP_HASH_S); - let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); - - padded - .chunks(K) - .fold(Q(domain_prefix), |acc, chunk| acc.double() + S(chunk)) +pub struct CommitDomain { + M: HashDomain, + R: pallas::Point, } -/// `SinsemillaHash` from [§ 5.4.1.9][concretesinsemillahash]. -/// -/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash -pub(crate) fn hash(domain_prefix: &str, msg: impl Iterator) -> pallas::Base { - extract_p(&hash_to_point(domain_prefix, msg)) -} +impl CommitDomain { + /// Constructs a new `CommitDomain` with a specific prefix string. + pub(crate) fn new(domain: &str) -> Self { + let m_prefix = format!("{}-M", domain); + let r_prefix = format!("{}-r", domain); + let hasher_r = pallas::Point::hash_to_curve(&r_prefix); + CommitDomain { + M: HashDomain::new(&m_prefix), + R: hasher_r(&[]), + } + } -/// `SinsemillaCommit` from [§ 5.4.8.4][concretesinsemillacommit]. -/// -/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit -#[allow(non_snake_case)] -pub(crate) fn commit( - domain_prefix: &str, - msg: impl Iterator, - r: &pallas::Scalar, -) -> pallas::Point { - let m_prefix = domain_prefix.to_owned() + "-M"; - let r_prefix = domain_prefix.to_owned() + "-r"; + /// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + #[allow(non_snake_case)] + pub(crate) fn commit( + &self, + msg: impl Iterator, + r: &pallas::Scalar, + ) -> pallas::Point { + self.M.hash_to_point(msg) + self.R * r + } - let hasher_r = pallas::Point::hash_to_curve(&r_prefix); + /// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + pub(crate) fn short_commit( + &self, + msg: impl Iterator, + r: &pallas::Scalar, + ) -> pallas::Base { + extract_p(&self.commit(msg, r)) + } - hash_to_point(&m_prefix, msg) + hasher_r(&[]) * r -} - -/// `SinsemillaShortCommit` from [§ 5.4.8.4][concretesinsemillacommit]. -/// -/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit -pub(crate) fn short_commit( - domain_prefix: &str, - msg: impl Iterator, - r: &pallas::Scalar, -) -> pallas::Base { - extract_p(&commit(domain_prefix, msg, r)) + /// Returns the Sinsemilla $R$ constant for this domain. + #[cfg(test)] + #[allow(non_snake_case)] + pub(crate) fn R(&self) -> pallas::Point { + self.R + } } #[cfg(test)] diff --git a/src/primitives/sinsemilla/constants.rs b/src/primitives/sinsemilla/constants.rs new file mode 100644 index 00000000..cb350bac --- /dev/null +++ b/src/primitives/sinsemilla/constants.rs @@ -0,0 +1,18 @@ +//! Sinsemilla generators + +/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ +pub const K: usize = 10; + +/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order +/// of Pallas. +pub const C: usize = 253; + +// Sinsemilla Q generators + +/// SWU hash-to-curve personalization for Sinsemilla $Q$ generators. +pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ"; + +// Sinsemilla S generators + +/// SWU hash-to-curve personalization for Sinsemilla $S$ generators. +pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS"; diff --git a/src/spec.rs b/src/spec.rs index b2445223..1c7e2203 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -40,8 +40,8 @@ pub(crate) fn commit_ivk( ) -> pallas::Scalar { // We rely on the API contract that to_le_bits() returns at least PrimeField::NUM_BITS // bits, which is equal to L_ORCHARD_BASE. - let ivk = sinsemilla::short_commit( - "z.cash:Orchard-CommitIvk", + let domain = sinsemilla::CommitDomain::new(&"z.cash:Orchard-CommitIvk"); + let ivk = domain.short_commit( iter::empty() .chain(ak.to_le_bits().iter().by_val().take(L_ORCHARD_BASE)) .chain(nk.to_le_bits().iter().by_val().take(L_ORCHARD_BASE)),