From 9e88da91f1f20296907c705340791fb428661819 Mon Sep 17 00:00:00 2001 From: Andrew Westberg Date: Tue, 13 Aug 2024 19:30:44 +0000 Subject: [PATCH] Implement EpochNonce as a NonceGenerator --- pallas-crypto/Cargo.toml | 1 + pallas-crypto/src/nonce/epoch_nonce.rs | 86 ++++++++++++++++++++++++ pallas-crypto/src/nonce/mod.rs | 5 +- pallas-crypto/src/nonce/rolling_nonce.rs | 23 ++++--- 4 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 pallas-crypto/src/nonce/epoch_nonce.rs diff --git a/pallas-crypto/Cargo.toml b/pallas-crypto/Cargo.toml index 4e976ef1..1d1c8743 100644 --- a/pallas-crypto/Cargo.toml +++ b/pallas-crypto/Cargo.toml @@ -19,6 +19,7 @@ pallas-codec = { version = "=0.29.0", path = "../pallas-codec" } serde = "1.0.143" [dev-dependencies] +itertools = "0.13" quickcheck = "1.0" quickcheck_macros = "1.0" rand = "0.8" diff --git a/pallas-crypto/src/nonce/epoch_nonce.rs b/pallas-crypto/src/nonce/epoch_nonce.rs new file mode 100644 index 00000000..a3ea955d --- /dev/null +++ b/pallas-crypto/src/nonce/epoch_nonce.rs @@ -0,0 +1,86 @@ +use crate::hash::{Hash, Hasher}; +use crate::nonce::{Error, NonceGenerator}; + +/// A nonce generator that calculates an epoch nonce from the eta_v value (nc) of the block right before +/// the stability window and the block hash of the first block from the previous epoch (nh). +#[derive(Debug, Clone)] +pub struct EpochNonceGenerator { + pub nonce: Hash<32>, +} + +impl EpochNonceGenerator { + /// Create a new [`EpochNonceGenerator`] generator. + /// params: + /// - nc: the eta_v value of the block right before the stability window. + /// - nh: the block hash of the first block from the previous epoch. + /// - extra_entropy: optional extra entropy to be used in the nonce calculation. + pub fn new(nc: Hash<32>, nh: Hash<32>, extra_entropy: Option<&[u8]>) -> Self { + let mut hasher = Hasher::<256>::new(); + hasher.input(nc.as_ref()); + hasher.input(nh.as_ref()); + let epoch_nonce = hasher.finalize(); + if let Some(extra_entropy) = extra_entropy { + let mut hasher = Hasher::<256>::new(); + hasher.input(epoch_nonce.as_ref()); + hasher.input(extra_entropy); + let extra_nonce = hasher.finalize(); + Self { nonce: extra_nonce } + } else { + Self { nonce: epoch_nonce } + } + } +} + +impl NonceGenerator for EpochNonceGenerator { + fn finalize(&mut self) -> Result, Error> { + Ok(self.nonce) + } +} + +#[cfg(test)] +mod tests { + use itertools::izip; + + use crate::hash::Hash; + + use super::*; + + #[test] + fn test_epoch_nonce() { + let nc_values = vec![ + hex::decode("e86e133bd48ff5e79bec43af1ac3e348b539172f33e502d2c96735e8c51bd04d") + .unwrap(), + hex::decode("d1340a9c1491f0face38d41fd5c82953d0eb48320d65e952414a0c5ebaf87587") + .unwrap(), + ]; + let nh_values = vec![ + hex::decode("d7a1ff2a365abed59c9ae346cba842b6d3df06d055dba79a113e0704b44cc3e9") + .unwrap(), + hex::decode("ee91d679b0a6ce3015b894c575c799e971efac35c7a8cbdc2b3f579005e69abd") + .unwrap(), + ]; + let ee = hex::decode("d982e06fd33e7440b43cefad529b7ecafbaa255e38178ad4189a37e4ce9bf1fa") + .unwrap(); + let extra_entropy_values: Vec> = vec![None, Some(&ee)]; + let expected_epoch_nonces = vec![ + hex::decode("e536a0081ddd6d19786e9d708a85819a5c3492c0da7349f59c8ad3e17e4acd98") + .unwrap(), + hex::decode("0022cfa563a5328c4fb5c8017121329e964c26ade5d167b1bd9b2ec967772b60") + .unwrap(), + ]; + + for (nc_value, nh_value, extra_entropy_value, expected_epoch_nonce) in izip!( + nc_values.iter(), + nh_values.iter(), + extra_entropy_values.iter(), + expected_epoch_nonces.iter() + ) { + let nc: Hash<32> = Hash::from(nc_value.as_slice()); + let nh: Hash<32> = Hash::from(nh_value.as_slice()); + let extra_entropy = *extra_entropy_value; + let mut epoch_nonce = EpochNonceGenerator::new(nc, nh, extra_entropy); + let nonce = epoch_nonce.finalize().unwrap(); + assert_eq!(nonce.as_ref(), expected_epoch_nonce.as_slice()); + } + } +} diff --git a/pallas-crypto/src/nonce/mod.rs b/pallas-crypto/src/nonce/mod.rs index 12adbcf6..174b7498 100644 --- a/pallas-crypto/src/nonce/mod.rs +++ b/pallas-crypto/src/nonce/mod.rs @@ -1,5 +1,8 @@ use thiserror::Error; +use crate::hash::Hash; + +pub mod epoch_nonce; pub mod rolling_nonce; #[derive(Error, Debug)] @@ -10,5 +13,5 @@ pub enum Error { /// A trait for generating nonces. pub trait NonceGenerator: Sized { - fn finalize(&mut self) -> Result; + fn finalize(&mut self) -> Result, Error>; } diff --git a/pallas-crypto/src/nonce/rolling_nonce.rs b/pallas-crypto/src/nonce/rolling_nonce.rs index 94a37922..c660a45e 100644 --- a/pallas-crypto/src/nonce/rolling_nonce.rs +++ b/pallas-crypto/src/nonce/rolling_nonce.rs @@ -5,12 +5,12 @@ use crate::nonce::{Error, NonceGenerator}; /// the shelley era and beyond. These rolling nonce values are used to help calculate the epoch /// nonce values used in consensus for the Ouroboros protocols (tpraos, praos, cpraos). #[derive(Debug, Clone)] -pub struct RollingNonce { +pub struct RollingNonceGenerator { pub nonce: Hash<32>, block_eta_v: Option>, } -impl RollingNonce { +impl RollingNonceGenerator { pub fn new(nonce: Hash<32>) -> Self { Self { nonce, @@ -30,8 +30,8 @@ impl RollingNonce { } } -impl NonceGenerator for RollingNonce { - fn finalize(&mut self) -> Result { +impl NonceGenerator for RollingNonceGenerator { + fn finalize(&mut self) -> Result, Error> { if self.block_eta_v.is_none() { return Err(Error::Nonce( "Must call apply_block before finalize!".to_string(), @@ -40,7 +40,7 @@ impl NonceGenerator for RollingNonce { let mut hasher = Hasher::<256>::new(); hasher.input(self.nonce.as_ref()); hasher.input(self.block_eta_v.unwrap().as_ref()); - Ok(RollingNonce::new(hasher.finalize())) + Ok(hasher.finalize()) } } @@ -150,13 +150,18 @@ mod tests { .unwrap(), ]; - let mut nonce = RollingNonce::new(Hash::from(shelley_genesis_hash.as_slice())); + let mut rolling_nonce_generator = + RollingNonceGenerator::new(Hash::from(shelley_genesis_hash.as_slice())); for (eta_vrf_0, expected_eta_v) in eta_vrf_0_values.iter().zip(expected_eta_v_values.iter()) { - nonce.apply_block(eta_vrf_0).unwrap(); - nonce = nonce.finalize().unwrap(); - assert_eq!(nonce.nonce.as_ref(), expected_eta_v.as_slice()); + rolling_nonce_generator.apply_block(eta_vrf_0).unwrap(); + rolling_nonce_generator = + RollingNonceGenerator::new(rolling_nonce_generator.finalize().unwrap()); + assert_eq!( + rolling_nonce_generator.nonce.as_ref(), + expected_eta_v.as_slice() + ); } } }