2021-02-24 15:04:43 -08:00
|
|
|
//! The Sinsemilla hash function and commitment scheme.
|
|
|
|
|
2021-03-17 19:06:16 -07:00
|
|
|
use halo2::arithmetic::CurveExt;
|
|
|
|
use pasta_curves::pallas;
|
2021-04-19 15:05:56 -07:00
|
|
|
use subtle::CtOption;
|
2021-03-05 15:25:45 -08:00
|
|
|
|
2021-06-04 09:51:01 -07:00
|
|
|
use crate::spec::{extract_p_bottom, i2lebsp};
|
2021-02-24 15:04:43 -08:00
|
|
|
|
2021-04-19 15:02:59 -07:00
|
|
|
mod addition;
|
2021-04-19 15:05:56 -07:00
|
|
|
use self::addition::IncompletePoint;
|
2021-04-19 15:02:59 -07:00
|
|
|
|
2021-03-17 02:17:01 -07:00
|
|
|
mod constants;
|
2021-05-03 11:54:57 -07:00
|
|
|
mod sinsemilla_s;
|
2021-03-17 02:17:01 -07:00
|
|
|
pub use constants::*;
|
2021-02-24 15:04:43 -08:00
|
|
|
|
2021-06-18 23:57:42 -07:00
|
|
|
pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 {
|
2021-03-01 17:21:07 -08:00
|
|
|
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-06-07 22:18:16 -07:00
|
|
|
/// The sequence of K bits in little-endian order representing an integer
|
|
|
|
/// up to `2^K` - 1.
|
|
|
|
pub fn i2lebsp_k(int: usize) -> [bool; K] {
|
|
|
|
assert!(int < (1 << K));
|
2021-06-04 09:51:01 -07:00
|
|
|
i2lebsp(int as u64)
|
2021-06-07 22:18:16 -07:00
|
|
|
}
|
|
|
|
|
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.
|
2021-03-01 14:51:34 -08:00
|
|
|
struct Pad<I: Iterator<Item = bool>> {
|
2021-03-17 12:29:54 -07:00
|
|
|
/// The iterator we are padding.
|
2021-03-01 14:51:34 -08:00
|
|
|
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()`.
|
2021-03-01 14:51:34 -08:00
|
|
|
len: usize,
|
2021-03-17 12:29:54 -07:00
|
|
|
/// The amount of padding that remains to be emitted.
|
2021-03-01 14:51:34 -08:00
|
|
|
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.
|
2021-03-01 14:51:34 -08:00
|
|
|
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.
|
2021-03-01 14:51:34 -08:00
|
|
|
break None;
|
|
|
|
} else {
|
2021-03-17 12:29:54 -07:00
|
|
|
// Emit the next padding bit.
|
2021-03-01 14:51:34 -08:00
|
|
|
*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.
|
2021-03-01 14:51:34 -08:00
|
|
|
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.
|
2021-03-01 14:51:34 -08:00
|
|
|
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).
|
2021-03-01 14:51:34 -08:00
|
|
|
self.padding_left = Some(K - rem);
|
|
|
|
} else {
|
|
|
|
// No padding required.
|
|
|
|
self.padding_left = Some(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can
|
|
|
|
/// be used.
|
2021-06-15 21:09:25 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-03-18 17:43:18 -07:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub struct HashDomain {
|
|
|
|
Q: pallas::Point,
|
2021-03-17 02:20:10 -07:00
|
|
|
}
|
|
|
|
|
2021-03-18 17:43:18 -07:00
|
|
|
impl HashDomain {
|
2021-03-23 21:16:53 -07:00
|
|
|
/// Constructs a new `HashDomain` with a specific prefix string.
|
2021-03-18 17:43:18 -07:00
|
|
|
pub(crate) fn new(domain: &str) -> Self {
|
|
|
|
HashDomain {
|
|
|
|
Q: pallas::Point::hash_to_curve(Q_PERSONALIZATION)(domain.as_bytes()),
|
|
|
|
}
|
2021-03-17 02:20:10 -07:00
|
|
|
}
|
2021-02-24 15:04:43 -08:00
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash].
|
2021-03-18 17:43:18 -07:00
|
|
|
///
|
|
|
|
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
|
2021-04-19 15:05:56 -07:00
|
|
|
pub(crate) fn hash_to_point(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Point> {
|
|
|
|
self.hash_to_point_inner(msg).into()
|
|
|
|
}
|
|
|
|
|
2021-03-18 17:43:18 -07:00
|
|
|
#[allow(non_snake_case)]
|
2021-04-19 15:05:56 -07:00
|
|
|
fn hash_to_point_inner(&self, msg: impl Iterator<Item = bool>) -> IncompletePoint {
|
2021-03-18 17:43:18 -07:00
|
|
|
let padded: Vec<_> = Pad::new(msg).collect();
|
2021-02-24 15:04:43 -08:00
|
|
|
|
2021-03-18 17:43:18 -07:00
|
|
|
let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
|
|
|
|
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());
|
2021-03-17 02:20:10 -07:00
|
|
|
|
2021-03-18 17:43:18 -07:00
|
|
|
padded
|
|
|
|
.chunks(K)
|
2021-04-19 15:05:56 -07:00
|
|
|
.fold(IncompletePoint::from(self.Q), |acc, chunk| {
|
|
|
|
(acc + S(chunk)) + acc
|
|
|
|
})
|
2021-03-18 17:43:18 -07:00
|
|
|
}
|
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
|
2021-03-18 17:43:18 -07:00
|
|
|
///
|
|
|
|
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
|
2021-06-30 05:01:05 -07:00
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// This panics if the message length is greater than [`K`] * [`C`]
|
2021-04-19 15:05:56 -07:00
|
|
|
pub(crate) fn hash(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Base> {
|
2021-04-20 04:57:59 -07:00
|
|
|
extract_p_bottom(self.hash_to_point(msg))
|
2021-03-18 17:43:18 -07:00
|
|
|
}
|
2021-03-18 18:15:53 -07:00
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// Returns the Sinsemilla $Q$ constant for this domain.
|
|
|
|
#[cfg(test)]
|
2021-03-18 18:15:53 -07:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub(crate) fn Q(&self) -> pallas::Point {
|
|
|
|
self.Q
|
|
|
|
}
|
2021-03-17 02:20:10 -07:00
|
|
|
}
|
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// A domain in which $\mathsf{SinsemillaCommit}$ and $\mathsf{SinsemillaShortCommit}$ can
|
|
|
|
/// be used.
|
2021-03-19 21:10:09 -07:00
|
|
|
#[derive(Debug)]
|
2021-03-18 17:43:18 -07:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub struct CommitDomain {
|
2021-03-19 21:10:09 -07:00
|
|
|
M: HashDomain,
|
2021-03-18 17:43:18 -07:00
|
|
|
R: pallas::Point,
|
|
|
|
}
|
2021-03-17 02:20:10 -07:00
|
|
|
|
2021-03-18 17:43:18 -07:00
|
|
|
impl CommitDomain {
|
2021-03-23 21:16:53 -07:00
|
|
|
/// Constructs a new `CommitDomain` with a specific prefix string.
|
2021-03-18 17:43:18 -07:00
|
|
|
pub(crate) fn new(domain: &str) -> Self {
|
2021-03-23 21:30:03 -07:00
|
|
|
let m_prefix = format!("{}-M", domain);
|
|
|
|
let r_prefix = format!("{}-r", domain);
|
|
|
|
let hasher_r = pallas::Point::hash_to_curve(&r_prefix);
|
2021-03-18 17:43:18 -07:00
|
|
|
CommitDomain {
|
2021-03-19 21:10:09 -07:00
|
|
|
M: HashDomain::new(&m_prefix),
|
2021-03-23 21:30:03 -07:00
|
|
|
R: hasher_r(&[]),
|
2021-03-18 17:43:18 -07:00
|
|
|
}
|
2021-03-17 02:20:10 -07:00
|
|
|
}
|
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
2021-03-18 17:43:18 -07:00
|
|
|
///
|
|
|
|
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub(crate) fn commit(
|
|
|
|
&self,
|
|
|
|
msg: impl Iterator<Item = bool>,
|
|
|
|
r: &pallas::Scalar,
|
2021-04-19 15:05:56 -07:00
|
|
|
) -> CtOption<pallas::Point> {
|
|
|
|
(self.M.hash_to_point_inner(msg) + self.R * r).into()
|
2021-03-18 17:43:18 -07:00
|
|
|
}
|
2021-02-24 15:04:43 -08:00
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
2021-03-18 17:43:18 -07:00
|
|
|
///
|
|
|
|
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
|
|
|
|
pub(crate) fn short_commit(
|
|
|
|
&self,
|
|
|
|
msg: impl Iterator<Item = bool>,
|
|
|
|
r: &pallas::Scalar,
|
2021-04-19 15:05:56 -07:00
|
|
|
) -> CtOption<pallas::Base> {
|
|
|
|
extract_p_bottom(self.commit(msg, r))
|
2021-03-18 17:43:18 -07:00
|
|
|
}
|
2021-03-18 18:15:53 -07:00
|
|
|
|
2021-03-23 21:16:53 -07:00
|
|
|
/// Returns the Sinsemilla $R$ constant for this domain.
|
|
|
|
#[cfg(test)]
|
2021-03-18 18:15:53 -07:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub(crate) fn R(&self) -> pallas::Point {
|
|
|
|
self.R
|
|
|
|
}
|
2021-02-24 15:04:43 -08:00
|
|
|
}
|
2021-03-01 14:51:34 -08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-06-09 08:50:59 -07:00
|
|
|
use super::{i2lebsp_k, lebs2ip_k, Pad, K};
|
|
|
|
use rand::{self, rngs::OsRng, Rng};
|
2021-03-01 14:51:34 -08:00
|
|
|
|
|
|
|
#[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
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2021-06-09 08:50:59 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn lebs2ip_k_round_trip() {
|
|
|
|
let mut rng = OsRng;
|
|
|
|
{
|
|
|
|
let int = rng.gen_range(0..(1 << K));
|
|
|
|
assert_eq!(lebs2ip_k(&i2lebsp_k(int)) as usize, int);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(lebs2ip_k(&i2lebsp_k(0)) as usize, 0);
|
|
|
|
assert_eq!(lebs2ip_k(&i2lebsp_k((1 << K) - 1)) as usize, (1 << K) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn i2lebsp_k_round_trip() {
|
|
|
|
{
|
|
|
|
let bitstring = (0..K).map(|_| rand::random()).collect::<Vec<_>>();
|
|
|
|
assert_eq!(
|
|
|
|
i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(),
|
|
|
|
bitstring
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let bitstring = [false; K];
|
|
|
|
assert_eq!(
|
|
|
|
i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(),
|
|
|
|
bitstring
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let bitstring = [true; K];
|
|
|
|
assert_eq!(
|
|
|
|
i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(),
|
|
|
|
bitstring
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-03-01 14:51:34 -08:00
|
|
|
}
|