orchard/src/primitives/sinsemilla.rs

224 lines
7.0 KiB
Rust
Raw Normal View History

2021-02-24 15:04:43 -08:00
//! The Sinsemilla hash function and commitment scheme.
2021-03-05 15:25:45 -08:00
use group::Group;
use halo2::arithmetic::CurveExt;
use pasta_curves::pallas;
2021-03-05 15:25:45 -08:00
use crate::spec::extract_p;
2021-02-24 15:04:43 -08:00
2021-03-17 02:17:01 -07:00
mod constants;
pub use constants::*;
2021-02-24 15:04:43 -08:00
2021-03-05 12:09:51 -08:00
fn lebs2ip_k(bits: &[bool]) -> u32 {
assert!(bits.len() == K);
2021-02-24 15:04:43 -08:00
bits.iter()
.enumerate()
.fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 })
}
2021-03-17 12:29:54 -07:00
/// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a
/// multiple of $K$ bits.
struct Pad<I: Iterator<Item = bool>> {
2021-03-17 12:29:54 -07:00
/// The iterator we are padding.
inner: I,
2021-03-17 12:29:54 -07:00
/// The measured length of the inner iterator.
///
/// This starts as a lower bound, and will be accurate once `padding_left.is_some()`.
len: usize,
2021-03-17 12:29:54 -07:00
/// The amount of padding that remains to be emitted.
padding_left: Option<usize>,
}
impl<I: Iterator<Item = bool>> Pad<I> {
fn new(inner: I) -> Self {
Pad {
inner,
len: 0,
padding_left: None,
}
}
}
impl<I: Iterator<Item = bool>> Iterator for Pad<I> {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
loop {
2021-03-17 12:29:54 -07:00
// If we have identified the required padding, the inner iterator has ended,
// and we will never poll it again.
if let Some(n) = self.padding_left.as_mut() {
if *n == 0 {
2021-03-17 12:29:54 -07:00
// Either we already emitted all necessary padding, or there was no
// padding required.
break None;
} else {
2021-03-17 12:29:54 -07:00
// Emit the next padding bit.
*n -= 1;
break Some(false);
}
} else if let Some(ret) = self.inner.next() {
2021-03-17 12:29:54 -07:00
// We haven't reached the end of the inner iterator yet.
self.len += 1;
assert!(self.len <= K * C);
break Some(ret);
} else {
2021-03-17 12:29:54 -07:00
// Inner iterator just ended, so we now know its length.
let rem = self.len % K;
if rem > 0 {
2021-03-17 12:29:54 -07:00
// The inner iterator requires padding in the range [1,K).
self.padding_left = Some(K - rem);
} else {
// No padding required.
self.padding_left = Some(0);
}
}
}
}
}
/// Trait representing a domain in which [`sinsemilla::hash_to_point`] and
/// [`sinsemilla::hash`] can be used.
///
/// [`sinsemilla::hash_to_point`]: self::hash_to_point
/// [`sinsemilla::hash`]: self::hash
pub trait HashDomain {
#[allow(non_snake_case)]
fn Q(&self) -> pallas::Point;
}
/// A domain with a specific prefix string, in which [`sinsemilla::hash_to_point`] and
/// [`sinsemilla::hash`] can be used.
///
/// [`sinsemilla::hash_to_point`]: self::hash_to_point
/// [`sinsemilla::hash`]: self::hash
pub struct Domain(pub &'static str);
impl HashDomain for Domain {
fn Q(&self) -> pallas::Point {
pallas::Point::hash_to_curve(Q_PERSONALIZATION)(self.0.as_bytes())
}
2021-02-24 15:04:43 -08:00
}
/// `SinsemillaHashToPoint` from [§ 5.4.1.9][concretesinsemillahash].
2021-02-24 15:04:43 -08:00
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
2021-02-24 15:04:43 -08:00
#[allow(non_snake_case)]
pub(crate) fn hash_to_point<D: HashDomain>(
domain: &D,
msg: impl Iterator<Item = bool>,
) -> pallas::Point {
let padded: Vec<_> = Pad::new(msg).collect();
2021-02-24 15:04:43 -08:00
2021-03-17 02:17:01 -07:00
let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
2021-03-05 12:09:51 -08:00
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());
2021-02-24 15:04:43 -08:00
padded
.chunks(K)
.fold(domain.Q(), |acc, chunk| acc.double() + S(chunk))
2021-02-24 15:04:43 -08:00
}
/// `SinsemillaHash` from [§ 5.4.1.9][concretesinsemillahash].
2021-02-24 15:04:43 -08:00
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
pub(crate) fn hash<D: HashDomain>(domain: &D, msg: impl Iterator<Item = bool>) -> pallas::Base {
extract_p(&hash_to_point(domain, msg))
}
/// Trait representing a domain in which [`sinsemilla::commit`] and
/// [`sinsemilla::short_commit`] can be used.
///
/// [`sinsemilla::commit`]: self::commit
/// [`sinsemilla::short_commit`]: self::short_commit
pub trait CommitDomain: HashDomain {
#[allow(non_snake_case)]
fn R(&self) -> pallas::Point;
}
/// A domain with a specific prefix string, in which [`sinsemilla::commit`] and
/// [`sinsemilla::short_commit`] can be used.
///
/// [`sinsemilla::commit`]: self::commit
/// [`sinsemilla::short_commit`]: self::short_commit
pub struct Comm(pub &'static str);
impl HashDomain for Comm {
fn Q(&self) -> pallas::Point {
let m_prefix = self.0.to_owned() + "-M";
pallas::Point::hash_to_curve(Q_PERSONALIZATION)(m_prefix.as_bytes())
}
}
impl CommitDomain for Comm {
fn R(&self) -> pallas::Point {
let r_prefix = self.0.to_owned() + "-r";
let hasher_r = pallas::Point::hash_to_curve(&r_prefix);
hasher_r(&[])
}
2021-02-24 15:04:43 -08:00
}
/// `SinsemillaCommit` from [§ 5.4.8.4][concretesinsemillacommit].
2021-02-24 15:04:43 -08:00
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
2021-02-24 15:04:43 -08:00
#[allow(non_snake_case)]
pub(crate) fn commit<D: CommitDomain>(
domain: &D,
msg: impl Iterator<Item = bool>,
2021-02-24 15:04:43 -08:00
r: &pallas::Scalar,
) -> pallas::Point {
hash_to_point(domain, msg) + domain.R() * r
2021-02-24 15:04:43 -08:00
}
/// `SinsemillaShortCommit` from [§ 5.4.8.4][concretesinsemillacommit].
2021-02-24 15:04:43 -08:00
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
pub(crate) fn short_commit<D: CommitDomain>(
domain: &D,
msg: impl Iterator<Item = bool>,
2021-02-24 15:04:43 -08:00
r: &pallas::Scalar,
) -> pallas::Base {
extract_p(&commit(domain, msg, r))
2021-02-24 15:04:43 -08:00
}
#[cfg(test)]
mod tests {
use super::Pad;
#[test]
fn pad() {
assert_eq!(Pad::new([].iter().cloned()).collect::<Vec<_>>(), vec![]);
assert_eq!(
Pad::new([true].iter().cloned()).collect::<Vec<_>>(),
vec![true, false, false, false, false, false, false, false, false, false]
);
assert_eq!(
Pad::new([true, true].iter().cloned()).collect::<Vec<_>>(),
vec![true, true, false, false, false, false, false, false, false, false]
);
assert_eq!(
Pad::new([true, true, true].iter().cloned()).collect::<Vec<_>>(),
vec![true, true, true, false, false, false, false, false, false, false]
);
assert_eq!(
Pad::new(
[true, true, false, true, false, true, false, true, false, true]
.iter()
.cloned()
)
.collect::<Vec<_>>(),
vec![true, true, false, true, false, true, false, true, false, true]
);
assert_eq!(
Pad::new(
[true, true, false, true, false, true, false, true, false, true, true]
.iter()
.cloned()
)
.collect::<Vec<_>>(),
vec![
true, true, false, true, false, true, false, true, false, true, true, false, false,
false, false, false, false, false, false, false
]
);
}
}