From 08b279b90025b505c1e0f1ed78a2ce4827588b38 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 10 Aug 2021 12:45:23 +0100 Subject: [PATCH 1/2] Expose and benchmark Sinsemilla primitive --- Cargo.toml | 4 +++ benches/primitives.rs | 55 ++++++++++++++++++++++++++++++++++++ src/primitives.rs | 2 +- src/primitives/sinsemilla.rs | 14 ++++----- 4 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 benches/primitives.rs diff --git a/Cargo.toml b/Cargo.toml index 959ec95f..587fee6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,10 @@ test-dependencies = ["proptest"] name = "note_decryption" harness = false +[[bench]] +name = "primitives" +harness = false + [[bench]] name = "small" harness = false diff --git a/benches/primitives.rs b/benches/primitives.rs new file mode 100644 index 00000000..74478643 --- /dev/null +++ b/benches/primitives.rs @@ -0,0 +1,55 @@ +use std::array; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use ff::Field; +use orchard::primitives::sinsemilla; + +use pasta_curves::pallas; +#[cfg(unix)] +use pprof::criterion::{Output, PProfProfiler}; +use rand::{rngs::OsRng, Rng}; + +fn bench_primitives(c: &mut Criterion) { + let mut rng = OsRng; + + { + let mut group = c.benchmark_group("Sinsemilla"); + + let hasher = sinsemilla::HashDomain::new("hasher"); + let committer = sinsemilla::CommitDomain::new("committer"); + let bits: Vec = (0..1086).map(|_| rng.gen()).collect(); + let r = pallas::Scalar::random(rng); + + // Benchmark the input sizes we use in Orchard: + // - 510 bits for Commit^ivk + // - 520 bits for MerkleCRH + // - 1086 bits for NoteCommit + for size in array::IntoIter::new([510, 520, 1086]) { + group.bench_function(BenchmarkId::new("hash-to-point", size), |b| { + b.iter(|| hasher.hash_to_point(bits[..size].iter().cloned())) + }); + + group.bench_function(BenchmarkId::new("hash", size), |b| { + b.iter(|| hasher.hash(bits[..size].iter().cloned())) + }); + + group.bench_function(BenchmarkId::new("commit", size), |b| { + b.iter(|| committer.commit(bits[..size].iter().cloned(), &r)) + }); + + group.bench_function(BenchmarkId::new("short-commit", size), |b| { + b.iter(|| committer.commit(bits[..size].iter().cloned(), &r)) + }); + } + } +} + +#[cfg(unix)] +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); + targets = bench_primitives +} +#[cfg(not(unix))] +criterion_group!(benches, bench_primitives); +criterion_main!(benches); diff --git a/src/primitives.rs b/src/primitives.rs index a71ff0c2..08331d44 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -6,4 +6,4 @@ pub(crate) mod poseidon; pub mod redpallas; -pub(crate) mod sinsemilla; +pub mod sinsemilla; diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index a90c4fee..ae4498ed 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -22,7 +22,7 @@ pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { /// 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] { +pub(crate) fn i2lebsp_k(int: usize) -> [bool; K] { assert!(int < (1 << K)); i2lebsp(int as u64) } @@ -97,7 +97,7 @@ pub struct HashDomain { impl HashDomain { /// Constructs a new `HashDomain` with a specific prefix string. - pub(crate) fn new(domain: &str) -> Self { + pub fn new(domain: &str) -> Self { HashDomain { Q: pallas::Point::hash_to_curve(Q_PERSONALIZATION)(domain.as_bytes()), } @@ -106,7 +106,7 @@ impl HashDomain { /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. /// /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash - pub(crate) fn hash_to_point(&self, msg: impl Iterator) -> CtOption { + pub fn hash_to_point(&self, msg: impl Iterator) -> CtOption { self.hash_to_point_inner(msg).into() } @@ -131,7 +131,7 @@ impl HashDomain { /// # Panics /// /// This panics if the message length is greater than [`K`] * [`C`] - pub(crate) fn hash(&self, msg: impl Iterator) -> CtOption { + pub fn hash(&self, msg: impl Iterator) -> CtOption { extract_p_bottom(self.hash_to_point(msg)) } @@ -154,7 +154,7 @@ pub struct CommitDomain { impl CommitDomain { /// Constructs a new `CommitDomain` with a specific prefix string. - pub(crate) fn new(domain: &str) -> Self { + pub 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); @@ -168,7 +168,7 @@ impl CommitDomain { /// /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit #[allow(non_snake_case)] - pub(crate) fn commit( + pub fn commit( &self, msg: impl Iterator, r: &pallas::Scalar, @@ -179,7 +179,7 @@ impl CommitDomain { /// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. /// /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit - pub(crate) fn short_commit( + pub fn short_commit( &self, msg: impl Iterator, r: &pallas::Scalar, From 0d306d18aaf68785cde46027017e9ae1b3d1d370 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 10 Aug 2021 13:44:04 +0100 Subject: [PATCH 2/2] Expose and benchmark Poseidon --- benches/primitives.rs | 15 ++++++++++++++- src/primitives.rs | 2 +- src/primitives/poseidon.rs | 36 +++++++++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/benches/primitives.rs b/benches/primitives.rs index 74478643..8a40023b 100644 --- a/benches/primitives.rs +++ b/benches/primitives.rs @@ -2,7 +2,10 @@ use std::array; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use ff::Field; -use orchard::primitives::sinsemilla; +use orchard::primitives::{ + poseidon::{self, ConstantLength, OrchardNullifier}, + sinsemilla, +}; use pasta_curves::pallas; #[cfg(unix)] @@ -12,6 +15,16 @@ use rand::{rngs::OsRng, Rng}; fn bench_primitives(c: &mut Criterion) { let mut rng = OsRng; + { + let mut group = c.benchmark_group("Poseidon"); + + let message = [pallas::Base::random(rng), pallas::Base::random(rng)]; + + group.bench_function("2-to-1", |b| { + b.iter(|| poseidon::Hash::init(OrchardNullifier, ConstantLength).hash(message)) + }); + } + { let mut group = c.benchmark_group("Sinsemilla"); diff --git a/src/primitives.rs b/src/primitives.rs index 08331d44..25e8142f 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -4,6 +4,6 @@ // - EphemeralPublicKey // - EphemeralSecretKey -pub(crate) mod poseidon; +pub mod poseidon; pub mod redpallas; pub mod sinsemilla; diff --git a/src/primitives/poseidon.rs b/src/primitives/poseidon.rs index 2b17d9ac..911df2e8 100644 --- a/src/primitives/poseidon.rs +++ b/src/primitives/poseidon.rs @@ -1,3 +1,5 @@ +//! The Poseidon algebraic hash function. + use std::array; use std::fmt; use std::iter; @@ -17,13 +19,13 @@ pub use nullifier::OrchardNullifier; use grain::SboxType; /// The type used to hold permutation state. -pub type State = [F; T]; +pub(crate) type State = [F; T]; /// The type used to hold duplex sponge state. -pub type SpongeState = [Option; RATE]; +pub(crate) type SpongeState = [Option; RATE]; /// The type used to hold the MDS matrix and its inverse. -pub type Mds = [[F; T]; T]; +pub(crate) type Mds = [[F; T]; T]; /// A specification for a Poseidon permutation. pub trait Spec { @@ -151,7 +153,7 @@ impl Sponge { } /// A Poseidon duplex sponge. -pub struct Duplex, const T: usize, const RATE: usize> { +pub(crate) struct Duplex, const T: usize, const RATE: usize> { sponge: Sponge, state: State, pad_and_add: Box, &SpongeState)>, @@ -162,7 +164,7 @@ pub struct Duplex, const T: usize, const RATE: impl, const T: usize, const RATE: usize> Duplex { /// Constructs a new duplex sponge for the given Poseidon specification. - pub fn new( + pub(crate) fn new( spec: S, initial_capacity_element: F, pad_and_add: Box, &SpongeState)>, @@ -184,7 +186,7 @@ impl, const T: usize, const RATE: usize> Duplex } /// Absorbs an element into the sponge. - pub fn absorb(&mut self, value: F) { + pub(crate) fn absorb(&mut self, value: F) { match self.sponge { Sponge::Absorbing(ref mut input) => { for entry in input.iter_mut() { @@ -212,7 +214,7 @@ impl, const T: usize, const RATE: usize> Duplex } /// Squeezes an element from the sponge. - pub fn squeeze(&mut self) -> F { + pub(crate) fn squeeze(&mut self) -> F { loop { match self.sponge { Sponge::Absorbing(ref input) => { @@ -246,6 +248,7 @@ pub trait Domain, const T: usize, const RATE: u /// The initial capacity element, encoding this domain. fn initial_capacity_element(&self) -> F; + /// The padding that will be added to each state word by [`Domain::pad_and_add`]. fn padding(&self) -> SpongeState; /// Returns a function that will update the given state with the given input to a @@ -305,6 +308,25 @@ pub struct Hash< domain: D, } +impl< + F: FieldExt, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, + > fmt::Debug for Hash +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Hash") + .field("width", &T) + .field("rate", &RATE) + .field("R_F", &S::full_rounds()) + .field("R_P", &S::partial_rounds()) + .field("domain", &self.domain) + .finish() + } +} + impl< F: FieldExt, S: Spec,