From cc1bcb8a4a73b97c4b7de9492d0f5a4d22ddb34a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 18 Apr 2024 21:01:36 +0000 Subject: [PATCH 1/2] equihash: Move `Params` into an internal submodule --- components/equihash/src/lib.rs | 1 + components/equihash/src/params.rs | 37 ++++++++++++++++++ .../equihash/src/test_vectors/invalid.rs | 2 +- components/equihash/src/test_vectors/valid.rs | 2 +- components/equihash/src/verify.rs | 39 +------------------ 5 files changed, 42 insertions(+), 39 deletions(-) create mode 100644 components/equihash/src/params.rs diff --git a/components/equihash/src/lib.rs b/components/equihash/src/lib.rs index fc23642063..e1f9490dc2 100644 --- a/components/equihash/src/lib.rs +++ b/components/equihash/src/lib.rs @@ -20,6 +20,7 @@ // Catch documentation errors caused by code changes. #![deny(rustdoc::broken_intra_doc_links)] +mod params; mod verify; #[cfg(test)] diff --git a/components/equihash/src/params.rs b/components/equihash/src/params.rs new file mode 100644 index 0000000000..2e17700065 --- /dev/null +++ b/components/equihash/src/params.rs @@ -0,0 +1,37 @@ +#[derive(Clone, Copy)] +pub(crate) struct Params { + pub(crate) n: u32, + pub(crate) k: u32, +} + +impl Params { + /// Returns `None` if the parameters are invalid. + pub(crate) fn new(n: u32, k: u32) -> Option { + // We place the following requirements on the parameters: + // - n is a multiple of 8, so the hash output has an exact byte length. + // - k >= 3 so the encoded solutions have an exact byte length. + // - k < n, so the collision bit length is at least 1. + // - n is a multiple of k + 1, so we have an integer collision bit length. + if (n % 8 == 0) && (k >= 3) && (k < n) && (n % (k + 1) == 0) { + Some(Params { n, k }) + } else { + None + } + } + pub(crate) fn indices_per_hash_output(&self) -> u32 { + 512 / self.n + } + pub(crate) fn hash_output(&self) -> u8 { + (self.indices_per_hash_output() * self.n / 8) as u8 + } + pub(crate) fn collision_bit_length(&self) -> usize { + (self.n / (self.k + 1)) as usize + } + pub(crate) fn collision_byte_length(&self) -> usize { + (self.collision_bit_length() + 7) / 8 + } + #[cfg(test)] + pub(crate) fn hash_length(&self) -> usize { + ((self.k as usize) + 1) * self.collision_byte_length() + } +} diff --git a/components/equihash/src/test_vectors/invalid.rs b/components/equihash/src/test_vectors/invalid.rs index 11da849e0d..5dbec5a33d 100644 --- a/components/equihash/src/test_vectors/invalid.rs +++ b/components/equihash/src/test_vectors/invalid.rs @@ -1,4 +1,4 @@ -use crate::verify::{Kind, Params}; +use crate::{params::Params, verify::Kind}; pub(crate) struct TestVector { pub(crate) params: Params, diff --git a/components/equihash/src/test_vectors/valid.rs b/components/equihash/src/test_vectors/valid.rs index a55de1b96a..4df20c642a 100644 --- a/components/equihash/src/test_vectors/valid.rs +++ b/components/equihash/src/test_vectors/valid.rs @@ -1,4 +1,4 @@ -use crate::verify::Params; +use crate::params::Params; pub(crate) struct TestVector { pub(crate) params: Params, diff --git a/components/equihash/src/verify.rs b/components/equihash/src/verify.rs index 2015008838..9d5d4ba534 100644 --- a/components/equihash/src/verify.rs +++ b/components/equihash/src/verify.rs @@ -8,11 +8,7 @@ use std::fmt; use std::io::Cursor; use std::mem::size_of; -#[derive(Clone, Copy)] -pub(crate) struct Params { - pub(crate) n: u32, - pub(crate) k: u32, -} +use crate::params::Params; #[derive(Clone)] struct Node { @@ -20,37 +16,6 @@ struct Node { indices: Vec, } -impl Params { - fn new(n: u32, k: u32) -> Result { - // We place the following requirements on the parameters: - // - n is a multiple of 8, so the hash output has an exact byte length. - // - k >= 3 so the encoded solutions have an exact byte length. - // - k < n, so the collision bit length is at least 1. - // - n is a multiple of k + 1, so we have an integer collision bit length. - if (n % 8 == 0) && (k >= 3) && (k < n) && (n % (k + 1) == 0) { - Ok(Params { n, k }) - } else { - Err(Error(Kind::InvalidParams)) - } - } - fn indices_per_hash_output(&self) -> u32 { - 512 / self.n - } - fn hash_output(&self) -> u8 { - (self.indices_per_hash_output() * self.n / 8) as u8 - } - fn collision_bit_length(&self) -> usize { - (self.n / (self.k + 1)) as usize - } - fn collision_byte_length(&self) -> usize { - (self.collision_bit_length() + 7) / 8 - } - #[cfg(test)] - fn hash_length(&self) -> usize { - ((self.k as usize) + 1) * self.collision_byte_length() - } -} - impl Node { fn new(p: &Params, state: &Blake2bState, i: u32) -> Self { let hash = generate_hash(state, i / p.indices_per_hash_output()); @@ -347,7 +312,7 @@ pub fn is_valid_solution( nonce: &[u8], soln: &[u8], ) -> Result<(), Error> { - let p = Params::new(n, k)?; + let p = Params::new(n, k).ok_or(Error(Kind::InvalidParams))?; let indices = indices_from_minimal(p, soln)?; // Recursive validation is faster From d4b405f03f2cb4ca8e89cbe9071ce2d25014453a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 18 Apr 2024 21:06:21 +0000 Subject: [PATCH 2/2] equihash: Move helper methods for minimal reprs into a submodule --- components/equihash/src/lib.rs | 1 + components/equihash/src/minimal.rs | 190 ++++++++++++++++++++++++++++ components/equihash/src/verify.rs | 194 ++--------------------------- 3 files changed, 198 insertions(+), 187 deletions(-) create mode 100644 components/equihash/src/minimal.rs diff --git a/components/equihash/src/lib.rs b/components/equihash/src/lib.rs index e1f9490dc2..cb6131ca3b 100644 --- a/components/equihash/src/lib.rs +++ b/components/equihash/src/lib.rs @@ -20,6 +20,7 @@ // Catch documentation errors caused by code changes. #![deny(rustdoc::broken_intra_doc_links)] +mod minimal; mod params; mod verify; diff --git a/components/equihash/src/minimal.rs b/components/equihash/src/minimal.rs new file mode 100644 index 0000000000..81da63e657 --- /dev/null +++ b/components/equihash/src/minimal.rs @@ -0,0 +1,190 @@ +use std::io::Cursor; +use std::mem::size_of; + +use byteorder::{BigEndian, ReadBytesExt}; + +use crate::params::Params; + +pub(crate) fn expand_array(vin: &[u8], bit_len: usize, byte_pad: usize) -> Vec { + assert!(bit_len >= 8); + assert!(u32::BITS as usize >= 7 + bit_len); + + let out_width = (bit_len + 7) / 8 + byte_pad; + let out_len = 8 * out_width * vin.len() / bit_len; + + // Shortcut for parameters where expansion is a no-op + if out_len == vin.len() { + return vin.to_vec(); + } + + let mut vout: Vec = vec![0; out_len]; + let bit_len_mask: u32 = (1 << bit_len) - 1; + + // The acc_bits least-significant bits of acc_value represent a bit sequence + // in big-endian order. + let mut acc_bits = 0; + let mut acc_value: u32 = 0; + + let mut j = 0; + for b in vin { + acc_value = (acc_value << 8) | u32::from(*b); + acc_bits += 8; + + // When we have bit_len or more bits in the accumulator, write the next + // output element. + if acc_bits >= bit_len { + acc_bits -= bit_len; + for x in byte_pad..out_width { + vout[j + x] = (( + // Big-endian + acc_value >> (acc_bits + (8 * (out_width - x - 1))) + ) & ( + // Apply bit_len_mask across byte boundaries + (bit_len_mask >> (8 * (out_width - x - 1))) & 0xFF + )) as u8; + } + j += out_width; + } + } + + vout +} + +/// Returns `None` if the parameters are invalid for this minimal encoding. +pub(crate) fn indices_from_minimal(p: Params, minimal: &[u8]) -> Option> { + let c_bit_len = p.collision_bit_length(); + // Division is exact because k >= 3. + if minimal.len() != ((1 << p.k) * (c_bit_len + 1)) / 8 { + return None; + } + + assert!(((c_bit_len + 1) + 7) / 8 <= size_of::()); + let len_indices = u32::BITS as usize * minimal.len() / (c_bit_len + 1); + let byte_pad = size_of::() - ((c_bit_len + 1) + 7) / 8; + + let mut csr = Cursor::new(expand_array(minimal, c_bit_len + 1, byte_pad)); + let mut ret = Vec::with_capacity(len_indices); + + // Big-endian so that lexicographic array comparison is equivalent to integer + // comparison + while let Ok(i) = csr.read_u32::() { + ret.push(i); + } + + Some(ret) +} + +#[cfg(test)] +mod tests { + use super::{expand_array, indices_from_minimal, Params}; + + #[test] + fn array_expansion() { + let check_array = |(bit_len, byte_pad), compact, expanded| { + assert_eq!(expand_array(compact, bit_len, byte_pad), expanded); + }; + + // 8 11-bit chunks, all-ones + check_array( + (11, 0), + &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + &[ + 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, + 0x07, 0xff, + ][..], + ); + // 8 21-bit chunks, alternating 1s and 0s + check_array( + (21, 0), + &[ + 0xaa, 0xaa, 0xad, 0x55, 0x55, 0x6a, 0xaa, 0xab, 0x55, 0x55, 0x5a, 0xaa, 0xaa, 0xd5, + 0x55, 0x56, 0xaa, 0xaa, 0xb5, 0x55, 0x55, + ], + &[ + 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, + 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, + ][..], + ); + // 8 21-bit chunks, based on example in the spec + check_array( + (21, 0), + &[ + 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x12, 0x30, 0x22, 0xb3, 0x82, + 0x26, 0xac, 0x19, 0xbd, 0xf2, 0x34, 0x56, + ], + &[ + 0x00, 0x00, 0x44, 0x00, 0x00, 0x29, 0x1f, 0xff, 0xff, 0x00, 0x01, 0x23, 0x00, 0x45, + 0x67, 0x00, 0x89, 0xab, 0x00, 0xcd, 0xef, 0x12, 0x34, 0x56, + ][..], + ); + // 16 14-bit chunks, alternating 11s and 00s + check_array( + (14, 0), + &[ + 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, + 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, + ], + &[ + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, + ][..], + ); + // 8 11-bit chunks, all-ones, 2-byte padding + check_array( + (11, 2), + &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + &[ + 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, + 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, + 0x00, 0x00, 0x07, 0xff, + ][..], + ); + } + + #[test] + fn minimal_solution_repr() { + let check_repr = |minimal, indices| { + assert_eq!( + indices_from_minimal(Params { n: 80, k: 3 }, minimal).unwrap(), + indices, + ); + }; + + // The solutions here are not intended to be valid. + check_repr( + &[ + 0x00, 0x00, 0x08, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x80, + 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x01, + ], + &[1, 1, 1, 1, 1, 1, 1, 1], + ); + check_repr( + &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + &[ + 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, + ], + ); + check_repr( + &[ + 0x0f, 0xff, 0xf8, 0x00, 0x20, 0x03, 0xff, 0xfe, 0x00, 0x08, 0x00, 0xff, 0xff, 0x80, + 0x02, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x80, + ], + &[131071, 128, 131071, 128, 131071, 128, 131071, 128], + ); + check_repr( + &[ + 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x4d, 0x10, 0x01, 0x4c, 0x80, + 0x0f, 0xfc, 0x00, 0x00, 0x2f, 0xff, 0xff, + ], + &[68, 41, 2097151, 1233, 665, 1023, 1, 1048575], + ); + } +} diff --git a/components/equihash/src/verify.rs b/components/equihash/src/verify.rs index 9d5d4ba534..53071ddc01 100644 --- a/components/equihash/src/verify.rs +++ b/components/equihash/src/verify.rs @@ -3,12 +3,13 @@ //! [Equihash]: https://zips.z.cash/protocol/protocol.pdf#equihash use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams, State as Blake2bState}; -use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{LittleEndian, WriteBytesExt}; use std::fmt; -use std::io::Cursor; -use std::mem::size_of; -use crate::params::Params; +use crate::{ + minimal::{expand_array, indices_from_minimal}, + params::Params, +}; #[derive(Clone)] struct Node { @@ -133,74 +134,6 @@ fn generate_hash(base_state: &Blake2bState, i: u32) -> Blake2bHash { state.finalize() } -fn expand_array(vin: &[u8], bit_len: usize, byte_pad: usize) -> Vec { - assert!(bit_len >= 8); - assert!(u32::BITS as usize >= 7 + bit_len); - - let out_width = (bit_len + 7) / 8 + byte_pad; - let out_len = 8 * out_width * vin.len() / bit_len; - - // Shortcut for parameters where expansion is a no-op - if out_len == vin.len() { - return vin.to_vec(); - } - - let mut vout: Vec = vec![0; out_len]; - let bit_len_mask: u32 = (1 << bit_len) - 1; - - // The acc_bits least-significant bits of acc_value represent a bit sequence - // in big-endian order. - let mut acc_bits = 0; - let mut acc_value: u32 = 0; - - let mut j = 0; - for b in vin { - acc_value = (acc_value << 8) | u32::from(*b); - acc_bits += 8; - - // When we have bit_len or more bits in the accumulator, write the next - // output element. - if acc_bits >= bit_len { - acc_bits -= bit_len; - for x in byte_pad..out_width { - vout[j + x] = (( - // Big-endian - acc_value >> (acc_bits + (8 * (out_width - x - 1))) - ) & ( - // Apply bit_len_mask across byte boundaries - (bit_len_mask >> (8 * (out_width - x - 1))) & 0xFF - )) as u8; - } - j += out_width; - } - } - - vout -} - -fn indices_from_minimal(p: Params, minimal: &[u8]) -> Result, Error> { - let c_bit_len = p.collision_bit_length(); - // Division is exact because k >= 3. - if minimal.len() != ((1 << p.k) * (c_bit_len + 1)) / 8 { - return Err(Error(Kind::InvalidParams)); - } - - assert!(((c_bit_len + 1) + 7) / 8 <= size_of::()); - let len_indices = u32::BITS as usize * minimal.len() / (c_bit_len + 1); - let byte_pad = size_of::() - ((c_bit_len + 1) + 7) / 8; - - let mut csr = Cursor::new(expand_array(minimal, c_bit_len + 1, byte_pad)); - let mut ret = Vec::with_capacity(len_indices); - - // Big-endian so that lexicographic array comparison is equivalent to integer - // comparison - while let Ok(i) = csr.read_u32::() { - ret.push(i); - } - - Ok(ret) -} - fn has_collision(a: &Node, b: &Node, len: usize) -> bool { a.hash .iter() @@ -313,7 +246,7 @@ pub fn is_valid_solution( soln: &[u8], ) -> Result<(), Error> { let p = Params::new(n, k).ok_or(Error(Kind::InvalidParams))?; - let indices = indices_from_minimal(p, soln)?; + let indices = indices_from_minimal(p, soln).ok_or(Error(Kind::InvalidParams))?; // Recursive validation is faster is_valid_solution_recursive(p, input, nonce, &indices) @@ -321,122 +254,9 @@ pub fn is_valid_solution( #[cfg(test)] mod tests { - use super::{ - expand_array, indices_from_minimal, is_valid_solution, is_valid_solution_iterative, - is_valid_solution_recursive, Params, - }; + use super::{is_valid_solution, is_valid_solution_iterative, is_valid_solution_recursive}; use crate::test_vectors::{INVALID_TEST_VECTORS, VALID_TEST_VECTORS}; - #[test] - fn array_expansion() { - let check_array = |(bit_len, byte_pad), compact, expanded| { - assert_eq!(expand_array(compact, bit_len, byte_pad), expanded); - }; - - // 8 11-bit chunks, all-ones - check_array( - (11, 0), - &[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ], - &[ - 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, - 0x07, 0xff, - ][..], - ); - // 8 21-bit chunks, alternating 1s and 0s - check_array( - (21, 0), - &[ - 0xaa, 0xaa, 0xad, 0x55, 0x55, 0x6a, 0xaa, 0xab, 0x55, 0x55, 0x5a, 0xaa, 0xaa, 0xd5, - 0x55, 0x56, 0xaa, 0xaa, 0xb5, 0x55, 0x55, - ], - &[ - 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, - 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, - ][..], - ); - // 8 21-bit chunks, based on example in the spec - check_array( - (21, 0), - &[ - 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x12, 0x30, 0x22, 0xb3, 0x82, - 0x26, 0xac, 0x19, 0xbd, 0xf2, 0x34, 0x56, - ], - &[ - 0x00, 0x00, 0x44, 0x00, 0x00, 0x29, 0x1f, 0xff, 0xff, 0x00, 0x01, 0x23, 0x00, 0x45, - 0x67, 0x00, 0x89, 0xab, 0x00, 0xcd, 0xef, 0x12, 0x34, 0x56, - ][..], - ); - // 16 14-bit chunks, alternating 11s and 00s - check_array( - (14, 0), - &[ - 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, - 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, - ], - &[ - 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0x33, 0x33, - ][..], - ); - // 8 11-bit chunks, all-ones, 2-byte padding - check_array( - (11, 2), - &[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ], - &[ - 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, - 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, - 0x00, 0x00, 0x07, 0xff, - ][..], - ); - } - - #[test] - fn minimal_solution_repr() { - let check_repr = |minimal, indices| { - assert_eq!( - indices_from_minimal(Params { n: 80, k: 3 }, minimal).unwrap(), - indices, - ); - }; - - // The solutions here are not intended to be valid. - check_repr( - &[ - 0x00, 0x00, 0x08, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x80, - 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x01, - ], - &[1, 1, 1, 1, 1, 1, 1, 1], - ); - check_repr( - &[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ], - &[ - 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, - ], - ); - check_repr( - &[ - 0x0f, 0xff, 0xf8, 0x00, 0x20, 0x03, 0xff, 0xfe, 0x00, 0x08, 0x00, 0xff, 0xff, 0x80, - 0x02, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x80, - ], - &[131071, 128, 131071, 128, 131071, 128, 131071, 128], - ); - check_repr( - &[ - 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x4d, 0x10, 0x01, 0x4c, 0x80, - 0x0f, 0xfc, 0x00, 0x00, 0x2f, 0xff, 0xff, - ], - &[68, 41, 2097151, 1233, 665, 1023, 1, 1048575], - ); - } - #[test] fn valid_test_vectors() { for tv in VALID_TEST_VECTORS {