From 1dd923a19963936a33a25aabe07e2ba491fa0b8b Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 21 Oct 2022 09:24:54 +0200 Subject: [PATCH] add test vectors & fix nits --- crypto/src/hash/mod.rs | 2 +- crypto/src/hash/rpo/mds_freq.rs | 4 +- crypto/src/hash/rpo/mod.rs | 34 +++--- crypto/src/hash/rpo/tests.rs | 190 ++++++++++++++++++++++++++++---- crypto/src/merkle/mod.rs | 5 +- 5 files changed, 192 insertions(+), 43 deletions(-) diff --git a/crypto/src/hash/mod.rs b/crypto/src/hash/mod.rs index 6674e22..2337fe1 100644 --- a/crypto/src/hash/mod.rs +++ b/crypto/src/hash/mod.rs @@ -1,7 +1,7 @@ use crate::{ElementHasher, HashFn}; mod rpo; -pub use rpo::Rpo as Hasher; +pub use rpo::Rpo256 as Hasher; // TYPE ALIASES // ================================================================================================ diff --git a/crypto/src/hash/rpo/mds_freq.rs b/crypto/src/hash/rpo/mds_freq.rs index 8778c69..a510730 100644 --- a/crypto/src/hash/rpo/mds_freq.rs +++ b/crypto/src/hash/rpo/mds_freq.rs @@ -156,7 +156,7 @@ fn block3(x: [i64; 3], y: [i64; 3]) -> [i64; 3] { #[cfg(test)] mod tests { - use super::super::Rpo; + use super::super::Rpo256; use crate::hash::rpo::MDS; use proptest::prelude::*; use winterfell::math::{fields::f64::BaseElement, FieldElement}; @@ -187,7 +187,7 @@ mod tests { v2 = v1.clone(); apply_mds_naive(&mut v1); - Rpo::apply_mds(&mut v2); + Rpo256::apply_mds(&mut v2); prop_assert_eq!(v1, v2); } diff --git a/crypto/src/hash/rpo/mod.rs b/crypto/src/hash/rpo/mod.rs index 8c4b66a..c711416 100644 --- a/crypto/src/hash/rpo/mod.rs +++ b/crypto/src/hash/rpo/mod.rs @@ -50,10 +50,10 @@ const INV_ALPHA: u64 = 10540996611094048183; // HASHER IMPLEMENTATION // ================================================================================================ -/// Implementation of [Hasher] trait for Rescue Prime Optimized (RPO) hash function with 256-bit output. +/// Implementation of [Hasher] trait for Rescue Prime Optimized (Rpo256) hash function with 256-bit output. /// /// The hash function is implemented according to the Rescue Prime Optimized -/// [specifications](https://github.org/aszepieniec/rpo/) +/// [specifications](https://github.com/ASDiscreteMathematics/rpo) /// /// The parameters used to instantiate the function are: /// * Field: 64-bit prime field with modulus 2^64 - 2^32 + 1. @@ -66,29 +66,29 @@ const INV_ALPHA: u64 = 10540996611094048183; /// and it can be serialized into 32 bytes (256 bits). /// /// ## Hash output consistency -/// Functions [hash_elements()](Rpo::hash_elements), [merge()](Rpo::merge), and -/// [merge_with_int()](Rpo::merge_with_int) are internally consistent. That is, computing +/// Functions [hash_elements()](Rpo256::hash_elements), [merge()](Rpo256::merge), and +/// [merge_with_int()](Rpo256::merge_with_int) are internally consistent. That is, computing /// a hash for the same set of elements using these functions will always produce the same -/// result. For example, merging two digests using [merge()](Rpo::merge) will produce the +/// result. For example, merging two digests using [merge()](Rpo256::merge) will produce the /// same result as hashing 8 elements which make up these digests using -/// [hash_elements()](Rpo::hash_elements) function. +/// [hash_elements()](Rpo256::hash_elements) function. /// -/// However, [hash()](Rpo::hash) function is not consistent with functions mentioned above. +/// However, [hash()](Rpo256::hash) function is not consistent with functions mentioned above. /// For example, if we take two field elements, serialize them to bytes and hash them using -/// [hash()](Rpo::hash), the result will differ from the result obtained by hashing these -/// elements directly using [hash_elements()](Rpo::hash_elements) function. The reason for -/// this difference is that [hash()](Rpo::hash) function needs to be able to handle +/// [hash()](Rpo256::hash), the result will differ from the result obtained by hashing these +/// elements directly using [hash_elements()](Rpo256::hash_elements) function. The reason for +/// this difference is that [hash()](Rpo256::hash) function needs to be able to handle /// arbitrary binary strings, which may or may not encode valid field elements - and thus, /// deserialization procedure used by this function is different from the procedure used to /// deserialize valid field elements. /// /// Thus, if the underlying data consists of valid field elements, it might make more sense /// to deserialize them into field elements and then hash them using -/// [hash_elements()](Rpo::hash_elements) function rather then hashing the serialized bytes -/// using [hash()](Rpo::hash) function. -pub struct Rpo(); +/// [hash_elements()](Rpo256::hash_elements) function rather then hashing the serialized bytes +/// using [hash()](Rpo256::hash) function. +pub struct Rpo256(); -impl HashFn for Rpo { +impl HashFn for Rpo256 { type Digest = RpoDigest256; fn hash(bytes: &[u8]) -> Self::Digest { @@ -188,7 +188,7 @@ impl HashFn for Rpo { } } -impl ElementHasher for Rpo { +impl ElementHasher for Rpo256 { type BaseField = Felt; fn hash_elements>(elements: &[E]) -> Self::Digest { @@ -237,7 +237,7 @@ impl ElementHasher for Rpo { // HASH FUNCTION IMPLEMENTATION // ================================================================================================ -impl Rpo { +impl Rpo256 { // RESCUE PERMUTATION // -------------------------------------------------------------------------------------------- @@ -718,7 +718,7 @@ const INV_MDS: [[Felt; STATE_WIDTH]; STATE_WIDTH] = [ // ================================================================================================ /// Rescue round constants; -/// computed as in section ?? from [specifications](https://github.org/aszepieniec/rpo/) +/// computed as in [specifications](https://github.com/ASDiscreteMathematics/rpo) /// /// The constants are broken up into two arrays ARK1 and ARK2; ARK1 contains the constants for the /// first half of RPO round, and ARK2 contains constants for the second half of RPO round. diff --git a/crypto/src/hash/rpo/tests.rs b/crypto/src/hash/rpo/tests.rs index a839f3a..7f3bc37 100644 --- a/crypto/src/hash/rpo/tests.rs +++ b/crypto/src/hash/rpo/tests.rs @@ -1,5 +1,5 @@ use super::{ - ElementHasher, Felt, FieldElement, HashFn, Rpo, RpoDigest256, StarkField, ALPHA, INV_ALPHA, + ElementHasher, Felt, FieldElement, HashFn, Rpo256, RpoDigest256, StarkField, ALPHA, INV_ALPHA, INV_MDS, MDS, STATE_WIDTH, ZERO, }; use core::convert::TryInto; @@ -41,7 +41,7 @@ fn test_sbox() { expected.iter_mut().for_each(|v| *v = v.exp(ALPHA)); let mut actual = state; - Rpo::apply_sbox(&mut actual); + Rpo256::apply_sbox(&mut actual); assert_eq!(expected, actual); } @@ -54,7 +54,7 @@ fn test_inv_sbox() { expected.iter_mut().for_each(|v| *v = v.exp(INV_ALPHA)); let mut actual = state; - Rpo::apply_inv_sbox(&mut actual); + Rpo256::apply_inv_sbox(&mut actual); assert_eq!(expected, actual); } @@ -68,8 +68,8 @@ fn hash_elements_vs_merge() { RpoDigest256::new(elements[4..].try_into().unwrap()), ]; - let m_result = Rpo::merge(&digests); - let h_result = Rpo::hash_elements(&elements); + let m_result = Rpo256::merge(&digests); + let h_result = Rpo256::hash_elements(&elements); assert_eq!(m_result, h_result); } @@ -80,22 +80,22 @@ fn hash_elements_vs_merge_with_int() { // ----- value fits into a field element ------------------------------------------------------ let val: Felt = Felt::new(rand_value()); - let m_result = Rpo::merge_with_int(seed, val.as_int()); + let m_result = Rpo256::merge_with_int(seed, val.as_int()); let mut elements = seed.as_elements().to_vec(); elements.push(val); - let h_result = Rpo::hash_elements(&elements); + let h_result = Rpo256::hash_elements(&elements); assert_eq!(m_result, h_result); // ----- value does not fit into a field element ---------------------------------------------- let val = Felt::MODULUS + 2; - let m_result = Rpo::merge_with_int(seed, val); + let m_result = Rpo256::merge_with_int(seed, val); let mut elements = seed.as_elements().to_vec(); elements.push(Felt::new(val)); elements.push(Felt::new(1)); - let h_result = Rpo::hash_elements(&elements); + let h_result = Rpo256::hash_elements(&elements); assert_eq!(m_result, h_result); } @@ -103,23 +103,23 @@ fn hash_elements_vs_merge_with_int() { #[test] fn hash_padding() { // adding a zero bytes at the end of a byte string should result in a different hash - let r1 = Rpo::hash(&[1_u8, 2, 3]); - let r2 = Rpo::hash(&[1_u8, 2, 3, 0]); + let r1 = Rpo256::hash(&[1_u8, 2, 3]); + let r2 = Rpo256::hash(&[1_u8, 2, 3, 0]); assert_ne!(r1, r2); // same as above but with bigger inputs - let r1 = Rpo::hash(&[1_u8, 2, 3, 4, 5, 6]); - let r2 = Rpo::hash(&[1_u8, 2, 3, 4, 5, 6, 0]); + let r1 = Rpo256::hash(&[1_u8, 2, 3, 4, 5, 6]); + let r2 = Rpo256::hash(&[1_u8, 2, 3, 4, 5, 6, 0]); assert_ne!(r1, r2); // same as above but with input splitting over two elements - let r1 = Rpo::hash(&[1_u8, 2, 3, 4, 5, 6, 7]); - let r2 = Rpo::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0]); + let r1 = Rpo256::hash(&[1_u8, 2, 3, 4, 5, 6, 7]); + let r2 = Rpo256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0]); assert_ne!(r1, r2); // same as above but with multiple zeros - let r1 = Rpo::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0]); - let r2 = Rpo::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0]); + let r1 = Rpo256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0]); + let r2 = Rpo256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0]); assert_ne!(r1, r2); } @@ -128,8 +128,8 @@ fn hash_elements_padding() { let e1 = [Felt::new(rand_value()); 2]; let e2 = [e1[0], e1[1], ZERO]; - let r1 = Rpo::hash_elements(&e1); - let r2 = Rpo::hash_elements(&e2); + let r1 = Rpo256::hash_elements(&e1); + let r2 = Rpo256::hash_elements(&e2); assert_ne!(r1, r2); } @@ -151,7 +151,155 @@ fn hash_elements() { RpoDigest256::new(elements[4..8].try_into().unwrap()), ]; - let m_result = Rpo::merge(&digests); - let h_result = Rpo::hash_elements(&elements); + let m_result = Rpo256::merge(&digests); + let h_result = Rpo256::hash_elements(&elements); assert_eq!(m_result, h_result); } + +#[test] +fn hash_test_vectors() { + let elements = [ + Felt::new(0), + Felt::new(1), + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + Felt::new(8), + Felt::new(9), + Felt::new(10), + Felt::new(11), + Felt::new(12), + Felt::new(13), + Felt::new(14), + Felt::new(15), + Felt::new(16), + Felt::new(17), + Felt::new(18), + ]; + + for i in 0..elements.len() { + let expected = RpoDigest256::new(EXPECTED[i].try_into().unwrap()); + let result = Rpo256::hash_elements(&elements[..(i + 1)]); + assert_eq!(result, expected); + } +} + +const EXPECTED: [[Felt; 4]; 19] = [ + [ + Felt::new(1502364727743950833), + Felt::new(5880949717274681448), + Felt::new(162790463902224431), + Felt::new(6901340476773664264), + ], + [ + Felt::new(7478710183745780580), + Felt::new(3308077307559720969), + Felt::new(3383561985796182409), + Felt::new(17205078494700259815), + ], + [ + Felt::new(17439912364295172999), + Felt::new(17979156346142712171), + Felt::new(8280795511427637894), + Felt::new(9349844417834368814), + ], + [ + Felt::new(5105868198472766874), + Felt::new(13090564195691924742), + Felt::new(1058904296915798891), + Felt::new(18379501748825152268), + ], + [ + Felt::new(9133662113608941286), + Felt::new(12096627591905525991), + Felt::new(14963426595993304047), + Felt::new(13290205840019973377), + ], + [ + Felt::new(3134262397541159485), + Felt::new(10106105871979362399), + Felt::new(138768814855329459), + Felt::new(15044809212457404677), + ], + [ + Felt::new(162696376578462826), + Felt::new(4991300494838863586), + Felt::new(660346084748120605), + Felt::new(13179389528641752698), + ], + [ + Felt::new(2242391899857912644), + Felt::new(12689382052053305418), + Felt::new(235236990017815546), + Felt::new(5046143039268215739), + ], + [ + Felt::new(9585630502158073976), + Felt::new(1310051013427303477), + Felt::new(7491921222636097758), + Felt::new(9417501558995216762), + ], + [ + Felt::new(1994394001720334744), + Felt::new(10866209900885216467), + Felt::new(13836092831163031683), + Felt::new(10814636682252756697), + ], + [ + Felt::new(17486854790732826405), + Felt::new(17376549265955727562), + Felt::new(2371059831956435003), + Felt::new(17585704935858006533), + ], + [ + Felt::new(11368277489137713825), + Felt::new(3906270146963049287), + Felt::new(10236262408213059745), + Felt::new(78552867005814007), + ], + [ + Felt::new(17899847381280262181), + Felt::new(14717912805498651446), + Felt::new(10769146203951775298), + Felt::new(2774289833490417856), + ], + [ + Felt::new(3794717687462954368), + Felt::new(4386865643074822822), + Felt::new(8854162840275334305), + Felt::new(7129983987107225269), + ], + [ + Felt::new(7244773535611633983), + Felt::new(19359923075859320), + Felt::new(10898655967774994333), + Felt::new(9319339563065736480), + ], + [ + Felt::new(4935426252518736883), + Felt::new(12584230452580950419), + Felt::new(8762518969632303998), + Felt::new(18159875708229758073), + ], + [ + Felt::new(14871230873837295931), + Felt::new(11225255908868362971), + Felt::new(18100987641405432308), + Felt::new(1559244340089644233), + ], + [ + Felt::new(8348203744950016968), + Felt::new(4041411241960726733), + Felt::new(17584743399305468057), + Felt::new(16836952610803537051), + ], + [ + Felt::new(16139797453633030050), + Felt::new(1090233424040889412), + Felt::new(10770255347785669036), + Felt::new(16982398877290254028), + ], +]; diff --git a/crypto/src/merkle/mod.rs b/crypto/src/merkle/mod.rs index fe9e815..6c8819b 100644 --- a/crypto/src/merkle/mod.rs +++ b/crypto/src/merkle/mod.rs @@ -1,4 +1,4 @@ -use crate::{Felt, FieldElement, Word}; +use crate::{Felt, Word, ZERO}; pub mod merkle_path_set; pub mod merkle_tree; @@ -20,6 +20,7 @@ pub enum MerkleError { // HELPER FUNCTIONS // ================================================================================================ +#[cfg(test)] const fn int_to_node(value: u64) -> Word { - [Felt::new(value), Felt::ZERO, Felt::ZERO, Felt::ZERO] + [Felt::new(value), ZERO, ZERO, ZERO] }