Merge pull request #177 from zcash/bench-primitives

Add benchmarks for Poseidon and Sinsemilla primitives
This commit is contained in:
str4d 2021-08-12 13:35:10 +01:00 committed by GitHub
commit 5df0038bd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 16 deletions

View File

@ -64,6 +64,10 @@ test-dependencies = ["proptest"]
name = "note_decryption"
harness = false
[[bench]]
name = "primitives"
harness = false
[[bench]]
name = "small"
harness = false

68
benches/primitives.rs Normal file
View File

@ -0,0 +1,68 @@
use std::array;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use ff::Field;
use orchard::primitives::{
poseidon::{self, ConstantLength, OrchardNullifier},
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("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");
let hasher = sinsemilla::HashDomain::new("hasher");
let committer = sinsemilla::CommitDomain::new("committer");
let bits: Vec<bool> = (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);

View File

@ -4,6 +4,6 @@
// - EphemeralPublicKey
// - EphemeralSecretKey
pub(crate) mod poseidon;
pub mod poseidon;
pub mod redpallas;
pub(crate) mod sinsemilla;
pub mod sinsemilla;

View File

@ -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, const T: usize> = [F; T];
pub(crate) type State<F, const T: usize> = [F; T];
/// The type used to hold duplex sponge state.
pub type SpongeState<F, const RATE: usize> = [Option<F>; RATE];
pub(crate) type SpongeState<F, const RATE: usize> = [Option<F>; RATE];
/// The type used to hold the MDS matrix and its inverse.
pub type Mds<F, const T: usize> = [[F; T]; T];
pub(crate) type Mds<F, const T: usize> = [[F; T]; T];
/// A specification for a Poseidon permutation.
pub trait Spec<F: FieldExt, const T: usize, const RATE: usize> {
@ -151,7 +153,7 @@ impl<F: Copy, const RATE: usize> Sponge<F, RATE> {
}
/// A Poseidon duplex sponge.
pub struct Duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> {
pub(crate) struct Duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> {
sponge: Sponge<F, RATE>,
state: State<F, T>,
pad_and_add: Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>,
@ -162,7 +164,7 @@ pub struct Duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE:
impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> Duplex<F, S, T, RATE> {
/// 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<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>,
@ -184,7 +186,7 @@ impl<F: FieldExt, S: Spec<F, T, RATE>, 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<F: FieldExt, S: Spec<F, T, RATE>, 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<F: FieldExt, S: Spec<F, T, RATE>, 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<F, RATE>;
/// 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<F, T, RATE>,
D: Domain<F, S, T, RATE>,
const T: usize,
const RATE: usize,
> fmt::Debug for Hash<F, S, D, T, RATE>
{
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<F, T, RATE>,

View File

@ -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<Item = bool>) -> CtOption<pallas::Point> {
pub fn hash_to_point(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Point> {
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<Item = bool>) -> CtOption<pallas::Base> {
pub fn hash(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Base> {
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<Item = bool>,
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<Item = bool>,
r: &pallas::Scalar,