diff --git a/src/inc_encoding.rs b/src/inc_encoding.rs index 001e641..a9185ef 100644 --- a/src/inc_encoding.rs +++ b/src/inc_encoding.rs @@ -45,11 +45,6 @@ pub trait IncomparableEncoding { randomness: &Self::Randomness, epoch: u32, ) -> Result, Self::Error>; - - /// Function to check internal consistency of any given parameters - /// For testing only, and expected to panic if something is wrong. - #[cfg(test)] - fn internal_consistency_check(); } pub mod target_sum; diff --git a/src/inc_encoding/target_sum.rs b/src/inc_encoding/target_sum.rs index 37a20f2..dea44c2 100644 --- a/src/inc_encoding/target_sum.rs +++ b/src/inc_encoding/target_sum.rs @@ -61,6 +61,18 @@ impl IncomparableEncoding randomness: &Self::Randomness, epoch: u32, ) -> Result, Self::Error> { + const { + // base and dimension must not be too large + assert!( + MH::BASE <= 1 << 8, + "Target Sum Encoding: Base must be at most 2^8" + ); + assert!( + MH::DIMENSION <= 1 << 8, + "Target Sum Encoding: Dimension must be at most 2^8" + ); + } + // apply the message hash first to get chunks let chunks = MH::apply(parameter, epoch, randomness, message).map_err(TargetSumError::HashError)?; @@ -75,22 +87,6 @@ impl IncomparableEncoding }) } } - - #[cfg(test)] - fn internal_consistency_check() { - // base and dimension must not be too large - assert!( - Self::BASE <= 1 << 8, - "Target Sum Encoding: Base must be at most 2^8" - ); - assert!( - Self::DIMENSION <= 1 << 8, - "Target Sum Encoding: Dimension must be at most 2^8" - ); - - // also check internal consistency of message hash - MH::internal_consistency_check(); - } } #[cfg(test)] @@ -105,11 +101,6 @@ mod tests { const TEST_TARGET_SUM: usize = 115; type TestTargetSumEncoding = TargetSumEncoding; - #[test] - fn test_internal_consistency() { - TestTargetSumEncoding::internal_consistency_check(); - } - #[test] fn test_successful_encoding_fixed_message() { // keep message fixed and only resample randomness diff --git a/src/signature.rs b/src/signature.rs index 17c0b23..0bcfcb0 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -203,14 +203,6 @@ pub trait SignatureScheme { message: &[u8; MESSAGE_LENGTH], sig: &Self::Signature, ) -> bool; - - /// A test-only function to assert that all internal parameters chosen for the - /// signature scheme are valid and compatible. - /// - /// ### Panics - /// This function will panic if any of the internal consistency checks fail. - #[cfg(test)] - fn internal_consistency_check(); } pub mod generalized_xmss; diff --git a/src/signature/generalized_xmss.rs b/src/signature/generalized_xmss.rs index ed6ca7e..3aa19ee 100644 --- a/src/signature/generalized_xmss.rs +++ b/src/signature/generalized_xmss.rs @@ -658,6 +658,25 @@ where activation_epoch: usize, num_active_epochs: usize, ) -> (Self::PublicKey, Self::SecretKey) { + const { + // assert BASE and DIMENSION are small enough to make sure that we can fit + // pos_in_chain and chain_index in u8. + assert!( + IE::BASE <= 1 << 8, + "Generalized XMSS: Encoding base too large, must be at most 2^8" + ); + assert!( + IE::DIMENSION <= 1 << 8, + "Generalized XMSS: Encoding dimension too large, must be at most 2^8" + ); + + // LOG_LIFETIME needs to be even, so that we can use the top-bottom tree approach + assert!( + LOG_LIFETIME.is_multiple_of(2), + "Generalized XMSS: LOG_LIFETIME must be multiple of two" + ); + } + // checks for `activation_epoch` and `num_active_epochs` assert!( activation_epoch + num_active_epochs <= Self::LIFETIME as usize, @@ -921,33 +940,6 @@ where &sig.path, ) } - - #[cfg(test)] - fn internal_consistency_check() { - // we check consistency of all internally used components - // namely, PRF, incomparable encoding, and tweak hash - PRF::internal_consistency_check(); - IE::internal_consistency_check(); - TH::internal_consistency_check(); - - // assert BASE and DIMENSION are small enough to make sure that we can fit - // pos_in_chain and chain_index in u8. - - assert!( - IE::BASE <= 1 << 8, - "Generalized XMSS: Encoding base too large, must be at most 2^8" - ); - assert!( - IE::DIMENSION <= 1 << 8, - "Generalized XMSS: Encoding dimension too large, must be at most 2^8" - ); - - // LOG_LIFETIME needs to be even, so that we can use the top-bottom tree approach - assert!( - LOG_LIFETIME.is_multiple_of(2), - "Generalized XMSS: LOG_LIFETIME must be multiple of two" - ); - } } impl Encode for GeneralizedXMSSPublicKey { @@ -1061,8 +1053,6 @@ mod tests { const LOG_LIFETIME: usize = 6; type Sig = GeneralizedXMSSSignatureScheme; - Sig::internal_consistency_check(); - test_signature_scheme_correctness::(2, 0, Sig::LIFETIME as usize); test_signature_scheme_correctness::(19, 0, Sig::LIFETIME as usize); test_signature_scheme_correctness::(0, 0, Sig::LIFETIME as usize); @@ -1083,8 +1073,6 @@ mod tests { const LOG_LIFETIME: usize = 6; type Sig = GeneralizedXMSSSignatureScheme; - Sig::internal_consistency_check(); - // we sign the same (epoch, message) pair twice (which users of this code should not do) // and ensure that it produces the same randomness for the signature. let mut rng = rand::rng(); @@ -1122,8 +1110,6 @@ mod tests { const LOG_LIFETIME: usize = 10; type Sig = GeneralizedXMSSSignatureScheme; - Sig::internal_consistency_check(); - test_signature_scheme_correctness::(0, 0, Sig::LIFETIME as usize); test_signature_scheme_correctness::(11, 0, Sig::LIFETIME as usize); } @@ -1139,8 +1125,6 @@ mod tests { const LOG_LIFETIME: usize = 10; type Sig = GeneralizedXMSSSignatureScheme; - Sig::internal_consistency_check(); - test_signature_scheme_correctness::(2, 0, Sig::LIFETIME as usize); test_signature_scheme_correctness::(19, 0, Sig::LIFETIME as usize); } @@ -1156,8 +1140,6 @@ mod tests { const LOG_LIFETIME: usize = 6; type Sig = GeneralizedXMSSSignatureScheme; - Sig::internal_consistency_check(); - test_signature_scheme_correctness::(2, 0, Sig::LIFETIME as usize); test_signature_scheme_correctness::(19, 0, Sig::LIFETIME as usize); test_signature_scheme_correctness::(0, 0, Sig::LIFETIME as usize); diff --git a/src/signature/generalized_xmss/instantiations_aborting.rs b/src/signature/generalized_xmss/instantiations_aborting.rs index 37aca29..2ca521b 100644 --- a/src/signature/generalized_xmss/instantiations_aborting.rs +++ b/src/signature/generalized_xmss/instantiations_aborting.rs @@ -61,11 +61,6 @@ pub mod lifetime_2_to_the_6 { use super::SIGAbortingLifetime6Dim64Base8; - #[test] - pub fn test_internal_consistency() { - SIGAbortingLifetime6Dim64Base8::internal_consistency_check(); - } - #[test] pub fn test_correctness() { test_signature_scheme_correctness::( diff --git a/src/signature/generalized_xmss/instantiations_poseidon.rs b/src/signature/generalized_xmss/instantiations_poseidon.rs index 5858864..2f6d860 100644 --- a/src/signature/generalized_xmss/instantiations_poseidon.rs +++ b/src/signature/generalized_xmss/instantiations_poseidon.rs @@ -130,11 +130,9 @@ pub mod lifetime_2_to_the_18 { pub type SIGTargetSumLifetime18W8Off10 = GeneralizedXMSSSignatureScheme, THw8, LOG_LIFETIME>; - #[cfg(test)] + #[cfg(all(test, feature = "slow-tests"))] mod test { use crate::signature::SignatureScheme; - - #[cfg(feature = "slow-tests")] use crate::signature::test_templates::test_signature_scheme_correctness; use super::{ @@ -145,28 +143,6 @@ pub mod lifetime_2_to_the_18 { }; #[test] - pub fn test_w1_internal_consistency() { - SIGTargetSumLifetime18W1NoOff::internal_consistency_check(); - SIGTargetSumLifetime18W1Off10::internal_consistency_check(); - } - #[test] - pub fn test_w2_internal_consistency() { - SIGTargetSumLifetime18W2NoOff::internal_consistency_check(); - SIGTargetSumLifetime18W2Off10::internal_consistency_check(); - } - #[test] - pub fn test_w4_internal_consistency() { - SIGTargetSumLifetime18W4NoOff::internal_consistency_check(); - SIGTargetSumLifetime18W4Off10::internal_consistency_check(); - } - #[test] - pub fn test_w8_internal_consistency() { - SIGTargetSumLifetime18W8NoOff::internal_consistency_check(); - SIGTargetSumLifetime18W8Off10::internal_consistency_check(); - } - - #[test] - #[cfg(feature = "slow-tests")] pub fn test_w1_correctness() { test_signature_scheme_correctness::( 1032, @@ -180,7 +156,6 @@ pub mod lifetime_2_to_the_18 { ); } #[test] - #[cfg(feature = "slow-tests")] pub fn test_w2_correctness() { test_signature_scheme_correctness::( 436, @@ -194,7 +169,6 @@ pub mod lifetime_2_to_the_18 { ); } #[test] - #[cfg(feature = "slow-tests")] pub fn test_w4_correctness() { test_signature_scheme_correctness::( 21, @@ -208,7 +182,6 @@ pub mod lifetime_2_to_the_18 { ); } #[test] - #[cfg(feature = "slow-tests")] pub fn test_w8_correctness() { test_signature_scheme_correctness::( 32, @@ -358,11 +331,9 @@ pub mod lifetime_2_to_the_20 { pub type SIGTargetSumLifetime20W8Off10 = GeneralizedXMSSSignatureScheme, THw8, LOG_LIFETIME>; - #[cfg(test)] + #[cfg(all(test, feature = "slow-tests"))] mod test { use crate::signature::SignatureScheme; - - #[cfg(feature = "slow-tests")] use crate::signature::test_templates::test_signature_scheme_correctness; use super::{ @@ -373,28 +344,6 @@ pub mod lifetime_2_to_the_20 { }; #[test] - pub fn test_w1_internal_consistency() { - SIGTargetSumLifetime20W1NoOff::internal_consistency_check(); - SIGTargetSumLifetime20W1Off10::internal_consistency_check(); - } - #[test] - pub fn test_w2_internal_consistency() { - SIGTargetSumLifetime20W2NoOff::internal_consistency_check(); - SIGTargetSumLifetime20W2Off10::internal_consistency_check(); - } - #[test] - pub fn test_w4_internal_consistency() { - SIGTargetSumLifetime20W4NoOff::internal_consistency_check(); - SIGTargetSumLifetime20W4Off10::internal_consistency_check(); - } - #[test] - pub fn test_w8_internal_consistency() { - SIGTargetSumLifetime20W8NoOff::internal_consistency_check(); - SIGTargetSumLifetime20W8Off10::internal_consistency_check(); - } - - #[test] - #[cfg(feature = "slow-tests")] pub fn test_w1_correctness() { test_signature_scheme_correctness::( 1032, @@ -408,7 +357,6 @@ pub mod lifetime_2_to_the_20 { ); } #[test] - #[cfg(feature = "slow-tests")] pub fn test_w2_correctness() { test_signature_scheme_correctness::( 436, @@ -422,7 +370,6 @@ pub mod lifetime_2_to_the_20 { ); } #[test] - #[cfg(feature = "slow-tests")] pub fn test_w4_correctness() { test_signature_scheme_correctness::( 21, @@ -436,7 +383,6 @@ pub mod lifetime_2_to_the_20 { ); } #[test] - #[cfg(feature = "slow-tests")] pub fn test_w8_correctness() { test_signature_scheme_correctness::( 32, diff --git a/src/signature/generalized_xmss/instantiations_poseidon_top_level.rs b/src/signature/generalized_xmss/instantiations_poseidon_top_level.rs index 23d7eff..427eb19 100644 --- a/src/signature/generalized_xmss/instantiations_poseidon_top_level.rs +++ b/src/signature/generalized_xmss/instantiations_poseidon_top_level.rs @@ -57,11 +57,6 @@ pub mod lifetime_2_to_the_18 { #[cfg(feature = "slow-tests")] use crate::signature::test_templates::test_signature_scheme_correctness; - #[test] - pub fn test_internal_consistency() { - SIGTopLevelTargetSumLifetime18Dim64Base8::internal_consistency_check(); - } - #[test] #[cfg(feature = "slow-tests")] pub fn test_correctness() { @@ -144,11 +139,6 @@ pub mod lifetime_2_to_the_32 { #[cfg(feature = "slow-tests")] use crate::signature::test_templates::test_signature_scheme_correctness; - #[test] - pub fn test_internal_consistency() { - SIGTopLevelTargetSumLifetime32Dim64Base8::internal_consistency_check(); - } - #[test] #[cfg(feature = "slow-tests")] pub fn test_correctness() { @@ -225,11 +215,6 @@ pub mod lifetime_2_to_the_32 { #[cfg(feature = "slow-tests")] use crate::signature::test_templates::test_signature_scheme_correctness; - #[test] - pub fn test_internal_consistency() { - SIGTopLevelTargetSumLifetime32Dim48Base10::internal_consistency_check(); - } - #[test] #[cfg(feature = "slow-tests")] pub fn test_correctness() { @@ -305,11 +290,6 @@ pub mod lifetime_2_to_the_32 { #[cfg(feature = "slow-tests")] use crate::signature::test_templates::test_signature_scheme_correctness; - #[test] - pub fn test_internal_consistency() { - SIGTopLevelTargetSumLifetime32Dim32Base26::internal_consistency_check(); - } - #[test] #[cfg(feature = "slow-tests")] pub fn test_correctness() { @@ -391,11 +371,6 @@ pub mod lifetime_2_to_the_8 { use super::SIGTopLevelTargetSumLifetime8Dim64Base8; - #[test] - pub fn test_internal_consistency() { - SIGTopLevelTargetSumLifetime8Dim64Base8::internal_consistency_check(); - } - #[cfg(feature = "slow-tests")] #[test] pub fn test_correctness() { diff --git a/src/symmetric/message_hash.rs b/src/symmetric/message_hash.rs index 2875d05..3febb09 100644 --- a/src/symmetric/message_hash.rs +++ b/src/symmetric/message_hash.rs @@ -37,11 +37,6 @@ pub trait MessageHash { randomness: &Self::Randomness, message: &[u8; MESSAGE_LENGTH], ) -> Result, Self::Error>; - - /// Function to check internal consistency of any given parameters - /// For testing only, and expected to panic if something is wrong. - #[cfg(test)] - fn internal_consistency_check(); } pub mod aborting; diff --git a/src/symmetric/message_hash/aborting.rs b/src/symmetric/message_hash/aborting.rs index a48d901..db57f97 100644 --- a/src/symmetric/message_hash/aborting.rs +++ b/src/symmetric/message_hash/aborting.rs @@ -77,6 +77,53 @@ where randomness: &Self::Randomness, message: &[u8; MESSAGE_LENGTH], ) -> Result, HypercubeHashError> { + const { + // Check that Poseidon of width 24 is enough + assert!( + PARAMETER_LEN + RAND_LEN_FE + TWEAK_LEN_FE + MSG_LEN_FE <= 24, + "Poseidon of width 24 is not enough" + ); + assert!(HASH_LEN_FE <= 24, "Poseidon of width 24 is not enough"); + + // Check that we have enough hash output field elements + assert!( + HASH_LEN_FE >= DIMENSION.div_ceil(Z), + "Not enough hash output field elements for the requested dimension" + ); + assert!( + PARAMETER_LEN + RAND_LEN_FE + TWEAK_LEN_FE + MSG_LEN_FE >= HASH_LEN_FE, + "Input shorter than requested output" + ); + + // Base check + assert!( + BASE <= 1 << 8, + "Aborting Hypercube Message Hash: Base must be at most 2^8" + ); + + // Check that Q * w^z fits within the field + assert!( + Q as u64 * (BASE as u64).pow(Z as u32) <= F::ORDER_U64, + "Q * w^z exceeds field order" + ); + + // floor(log2(ORDER)) + let bits_per_fe = F::ORDER_U64.ilog2() as usize; + + // Check that we have enough bits to encode message + assert!( + bits_per_fe * MSG_LEN_FE >= 8 * MESSAGE_LENGTH, + "Aborting Hypercube Message Hash: not enough field elements to encode the message" + ); + + // Check that we have enough bits to encode tweak + // Epoch is a u32, and we have one domain separator byte + assert!( + bits_per_fe * TWEAK_LEN_FE >= 40, + "Aborting Hypercube Message Hash: not enough field elements to encode the epoch tweak" + ); + } + let hash_fe = poseidon_message_hash_fe::< PARAMETER_LEN, RAND_LEN_FE, @@ -85,8 +132,8 @@ where MSG_LEN_FE, >(parameter, epoch, randomness, message); - let q_wz = Q as u64 * (BASE as u64).pow(Z as u32); - let num_useful_fe = DIMENSION.div_ceil(Z); + let q_wz = const { Q as u64 * (BASE as u64).pow(Z as u32) }; + let num_useful_fe = const { DIMENSION.div_ceil(Z) }; let mut chunks = Vec::with_capacity(DIMENSION); for fe in &hash_fe[..num_useful_fe] { @@ -107,58 +154,6 @@ where Ok(chunks) } - - #[cfg(test)] - fn internal_consistency_check() { - const { - // Check that Poseidon of width 24 is enough - assert!( - PARAMETER_LEN + RAND_LEN_FE + TWEAK_LEN_FE + MSG_LEN_FE <= 24, - "Poseidon of width 24 is not enough" - ); - assert!(HASH_LEN_FE <= 24, "Poseidon of width 24 is not enough"); - - // Check that we have enough hash output field elements - assert!( - HASH_LEN_FE >= DIMENSION.div_ceil(Z), - "Not enough hash output field elements for the requested dimension" - ); - assert!( - PARAMETER_LEN + RAND_LEN_FE + TWEAK_LEN_FE + MSG_LEN_FE >= HASH_LEN_FE, - "Input shorter than requested output" - ); - - // Base check - assert!( - Self::BASE <= 1 << 8, - "Aborting Hypercube Message Hash: Base must be at most 2^8" - ); - } - - // Check that Q * w^z fits within the field - assert!( - Q as u64 * (BASE as u64).pow(Z as u32) <= F::ORDER_U64, - "Q * w^z exceeds field order" - ); - - // how many bits can be represented by one field element - let bits_per_fe = f64::floor(f64::log2(F::ORDER_U64 as f64)); - - // Check that we have enough bits to encode message - let message_fe_bits = bits_per_fe * f64::from(MSG_LEN_FE as u32); - assert!( - message_fe_bits >= f64::from((8_u32) * (MESSAGE_LENGTH as u32)), - "Aborting Hypercube Message Hash: not enough field elements to encode the message" - ); - - // Check that we have enough bits to encode tweak - // Epoch is a u32, and we have one domain separator byte - let tweak_fe_bits = bits_per_fe * f64::from(TWEAK_LEN_FE as u32); - assert!( - tweak_fe_bits >= f64::from(32 + 8_u32), - "Aborting Hypercube Message Hash: not enough field elements to encode the epoch tweak" - ); - } } // KoalaBear: p = 2^31 - 2^24 + 1 = 127 * 8^8 + 1 @@ -173,11 +168,6 @@ mod tests { use proptest::prelude::*; use rand::{SeedableRng, rngs::StdRng}; - #[test] - fn test_internal_consistency() { - HypercubePoseidonMHKoalaBear::internal_consistency_check(); - } - #[test] fn test_apply() { let mut rng = StdRng::seed_from_u64(1); @@ -234,8 +224,6 @@ mod tests { type HighAbortMH = AbortingHypercubeMessageHash<5, 5, 3, 9, 8, 3, 2_080_768, 2, 9>; const NUM_TRIALS: usize = 1000; - HighAbortMH::internal_consistency_check(); - let mut rng = StdRng::seed_from_u64(0); let parameter: FieldArray<5> = FieldArray(rng.random()); let message: [u8; 32] = rng.random(); @@ -272,8 +260,6 @@ mod tests { const NUM_SAMPLES: usize = 1_000_000; const NUM_OUTPUTS: usize = 16; // 4^2 - SmallMH::internal_consistency_check(); - let mut rng = StdRng::seed_from_u64(42); let parameter: FieldArray<5> = FieldArray(rng.random()); let message: [u8; 32] = rng.random(); diff --git a/src/symmetric/message_hash/poseidon.rs b/src/symmetric/message_hash/poseidon.rs index 8900d46..7d492e3 100644 --- a/src/symmetric/message_hash/poseidon.rs +++ b/src/symmetric/message_hash/poseidon.rs @@ -186,6 +186,51 @@ where randomness: &Self::Randomness, message: &[u8; MESSAGE_LENGTH], ) -> Result, Infallible> { + const { + // Check that Poseidon of width 24 is enough + assert!( + PARAMETER_LEN + TWEAK_LEN_FE + RAND_LEN_FE + MSG_LEN_FE <= 24, + "Poseidon of width 24 is not enough" + ); + assert!(HASH_LEN_FE <= 24, "Poseidon of width 24 is not enough"); + + // Base and dimension check + assert!( + BASE <= 1 << 8, + "Poseidon Message Hash: Base must be at most 2^8" + ); + assert!( + DIMENSION <= 1 << 8, + "Poseidon Message Hash: Dimension must be at most 2^8" + ); + + // how many bits can be represented by one field element: floor(log2(ORDER)) + let bits_per_fe = F::ORDER_U64.ilog2() as usize; + + // Check that we have enough bits to encode message + assert!( + bits_per_fe * MSG_LEN_FE >= 8 * MESSAGE_LENGTH, + "Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the message" + ); + + // Check that we have enough bits to encode tweak + // Epoch is a u32, and we have one domain separator byte + assert!( + bits_per_fe * TWEAK_LEN_FE >= 40, + "Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the epoch tweak" + ); + + // Check that decoding from field elements to chunks can be done + // injectively, i.e., we have enough chunks + // chunk_size = ceil(log2(BASE)) + assert!(BASE > 1, "Poseidon Message Hash: BASE must be > 1"); + let chunk_size = (usize::BITS - (BASE - 1).leading_zeros()) as usize; + assert!( + bits_per_fe * HASH_LEN_FE <= DIMENSION * chunk_size, + "Poseidon Message Hash: Parameter mismatch: not enough bits to decode the hash" + ); + } + let hash_fe = poseidon_message_hash_fe::< PARAMETER_LEN, RAND_LEN_FE, @@ -196,56 +241,6 @@ where Ok(decode_to_chunks::(&hash_fe).to_vec()) } - - #[cfg(test)] - fn internal_consistency_check() { - // Check that Poseidon of width 24 is enough - // Note: This block should be changed if we decide to support other Poseidon - // instances. Currently we use state of width 24 and pad with 0s. - - assert!( - PARAMETER_LEN + TWEAK_LEN_FE + RAND_LEN_FE + MSG_LEN_FE <= 24, - "Poseidon of width 24 is not enough" - ); - assert!(HASH_LEN_FE <= 24, "Poseidon of width 24 is not enough"); - - // Base and dimension check - assert!( - Self::BASE <= 1 << 8, - "Poseidon Message Hash: Base must be at most 2^8" - ); - assert!( - Self::DIMENSION <= 1 << 8, - "Poseidon Message Hash: Dimension must be at most 2^8" - ); - - // how many bits can be represented by one field element - let bits_per_fe = f64::floor(f64::log2(F::ORDER_U64 as f64)); - - // Check that we have enough bits to encode message - let message_fe_bits = bits_per_fe * f64::from(MSG_LEN_FE as u32); - assert!( - message_fe_bits >= f64::from((8_u32) * (MESSAGE_LENGTH as u32)), - "Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the message" - ); - - // Check that we have enough bits to encode tweak - // Epoch is a u32, and we have one domain separator byte - let tweak_fe_bits = bits_per_fe * f64::from(TWEAK_LEN_FE as u32); - assert!( - tweak_fe_bits >= f64::from(32 + 8_u32), - "Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the epoch tweak" - ); - - // Check that decoding from field elements to chunks can be done - // injectively, i.e., we have enough chunks - let hash_bits = bits_per_fe * f64::from(HASH_LEN_FE as u32); - let chunk_size = f64::ceil(f64::log2(Self::BASE as f64)) as usize; - assert!( - hash_bits <= f64::from((DIMENSION * chunk_size) as u32), - "Poseidon Message Hash: Parameter mismatch: not enough bits to decode the hash" - ); - } } // Example instantiations @@ -274,7 +269,6 @@ mod tests { let epoch = 13; let randomness = PoseidonMessageHash445::rand(&mut rng); - PoseidonMessageHash445::internal_consistency_check(); PoseidonMessageHash445::apply(¶meter, epoch, &randomness, &message).unwrap(); } @@ -289,7 +283,6 @@ mod tests { let epoch = 13; let randomness = PoseidonMessageHashW1::rand(&mut rng); - PoseidonMessageHashW1::internal_consistency_check(); PoseidonMessageHashW1::apply(¶meter, epoch, &randomness, &message).unwrap(); } @@ -575,7 +568,7 @@ mod tests { // input_uint = (p - 1) + (p - 1) * p + (p - 1) * p^2 // = (p^2 + p + 1) * (p - 1) // - // We’ll expand it: + // We'll expand it: // = (p - 1) * (p^2 + p + 1) // = p^3 - 1 diff --git a/src/symmetric/message_hash/top_level_poseidon.rs b/src/symmetric/message_hash/top_level_poseidon.rs index 8a8f5ef..b4803a1 100644 --- a/src/symmetric/message_hash/top_level_poseidon.rs +++ b/src/symmetric/message_hash/top_level_poseidon.rs @@ -142,6 +142,68 @@ where randomness: &Self::Randomness, message: &[u8; MESSAGE_LENGTH], ) -> Result, Infallible> { + const { + /// The width of the Poseidon2 permutation used. + const POSEIDON_WIDTH: usize = 24; + + // Check that the combined input fits within the Poseidon width. + assert!( + RAND_LEN + PARAMETER_LEN + TWEAK_LEN_FE + MSG_LEN_FE < POSEIDON_WIDTH, + "Top Level Poseidon Message Hash: Combined input length exceeds Poseidon width" + ); + + // POS_OUTPUT_LEN_FE must be equal to POS_INVOCATIONS * POS_OUTPUT_LEN_PER_INV_FE + assert!( + POS_OUTPUT_LEN_FE == POS_INVOCATIONS * POS_OUTPUT_LEN_PER_INV_FE, + "Top Level Poseidon Message Hash: POS_OUTPUT_LEN_FE must be equal to POS_INVOCATIONS * POS_OUTPUT_LEN_PER_INV_FE" + ); + + // POS_OUTPUT_LEN_FE must be at most 15 (because capacity is 9) + assert!( + POS_OUTPUT_LEN_PER_INV_FE <= 15, + "Top Level Poseidon Message Hash: POS_OUTPUT_LEN_PER_INV_FE must be at most 15" + ); + + // Number of invocations we require should fit in a field element + // For simplicity we require at most 2^8 invocations, which is more than enough + assert!( + POS_INVOCATIONS <= 1 << 8, + "Top Level Poseidon Message Hash: POS_INVOCATIONS must be at most 2^8" + ); + + // FINAL_LAYER must be a valid layer + assert!( + FINAL_LAYER <= (BASE - 1) * DIMENSION, + "Top Level Poseidon Message Hash: FINAL-LAYER must be a valid layer" + ); + + // Base and dimension check + assert!( + BASE <= 1 << 8, + "Poseidon Message Hash: Base must be at most 2^8" + ); + assert!( + DIMENSION <= 1 << 8, + "Poseidon Message Hash: Dimension must be at most 2^8" + ); + + // How many bits can be represented by one field element: floor(log2(ORDER)) + let bits_per_fe = F::ORDER_U64.ilog2() as usize; + + // Check that we have enough bits to encode message + assert!( + bits_per_fe * MSG_LEN_FE >= 8 * MESSAGE_LENGTH, + "Top Level Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the message" + ); + + // Check that we have enough bits to encode tweak + // Epoch is a u32, and we have one domain separator byte + assert!( + bits_per_fe * TWEAK_LEN_FE >= 40, + "Top Level Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the epoch tweak" + ); + } + let perm = poseidon2_24(); // first, encode the message and the epoch as field elements @@ -180,71 +242,6 @@ where POS_OUTPUT_LEN_FE, >(&pos_outputs)) } - - #[cfg(test)] - fn internal_consistency_check() { - /// The width of the Poseidon2 permutation used. - const POSEIDON_WIDTH: usize = 24; - - // Check that the combined input fits within the Poseidon width. - assert!( - RAND_LEN + PARAMETER_LEN + TWEAK_LEN_FE + MSG_LEN_FE < POSEIDON_WIDTH, - "Top Level Poseidon Message Hash: Combined input length exceeds Poseidon width" - ); - - // POS_OUTPUT_LEN_FE must be equal to POS_INVOCATIONS * POS_OUTPUT_LEN_PER_INV_FE - assert!( - POS_OUTPUT_LEN_FE == POS_INVOCATIONS * POS_OUTPUT_LEN_PER_INV_FE, - "Top Level Poseidon Message Hash: POS_OUTPUT_LEN_FE must be equal to POS_INVOCATIONS * POS_OUTPUT_LEN_PER_INV_FE" - ); - - // POS_OUTPUT_LEN_FE must be at most 15 (because capacity is 9) - assert!( - POS_OUTPUT_LEN_PER_INV_FE <= 15, - "Top Level Poseidon Message Hash: POS_OUTPUT_LEN_PER_INV_FE must be at most 15" - ); - - // Number of invocations we require should fit in a field element - // For simplicity we require at most 2^8 invocations, which is more than enough - assert!( - POS_INVOCATIONS <= 1 << 8, - "Top Level Poseidon Message Hash: POS_INVOCATIONS must be at most 2^8" - ); - - // FINAL_LAYER must be a valid layer - assert!( - FINAL_LAYER <= (BASE - 1) * DIMENSION, - "Top Level Poseidon Message Hash: FINAL-LAYER must be a valid layer" - ); - - // Base and dimension check - assert!( - Self::BASE <= 1 << 8, - "Poseidon Message Hash: Base must be at most 2^8" - ); - assert!( - Self::DIMENSION <= 1 << 8, - "Poseidon Message Hash: Dimension must be at most 2^8" - ); - - // How many bits can be represented by one field element - let bits_per_fe = f64::floor(f64::log2(F::ORDER_U64 as f64)); - - // Check that we have enough bits to encode message - let message_fe_bits = bits_per_fe * f64::from(MSG_LEN_FE as u32); - assert!( - message_fe_bits >= f64::from((8_u32) * (MESSAGE_LENGTH as u32)), - "Top Level Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the message" - ); - - // Check that we have enough bits to encode tweak - // Epoch is a u32, and we have one domain separator byte - let tweak_fe_bits = bits_per_fe * f64::from(TWEAK_LEN_FE as u32); - assert!( - tweak_fe_bits >= f64::from(32 + 8_u32), - "Top Level Poseidon Message Hash: Parameter mismatch: not enough field elements to encode the epoch tweak" - ); - } } #[cfg(test)] @@ -274,7 +271,6 @@ mod tests { let epoch = 313; let randomness = MH::rand(&mut rng); - MH::internal_consistency_check(); let hash: Vec = MH::apply(¶meter, epoch, &randomness, &message).unwrap(); // we also want that the output is in the relevant part of the hypercube, diff --git a/src/symmetric/prf.rs b/src/symmetric/prf.rs index 50504a6..ac2e810 100644 --- a/src/symmetric/prf.rs +++ b/src/symmetric/prf.rs @@ -25,11 +25,6 @@ pub trait Pseudorandom { message: &[u8; MESSAGE_LENGTH], counter: u64, ) -> Self::Randomness; - - /// Function to check internal consistency of any given parameters - /// For testing only, and expected to panic if something is wrong. - #[cfg(test)] - fn internal_consistency_check(); } pub mod shake_to_field; diff --git a/src/symmetric/prf/shake_to_field.rs b/src/symmetric/prf/shake_to_field.rs index d77563b..bf11b32 100644 --- a/src/symmetric/prf/shake_to_field.rs +++ b/src/symmetric/prf/shake_to_field.rs @@ -115,11 +115,6 @@ where F::from_u128(u128::from_be_bytes(buf)) }) } - - #[cfg(test)] - fn internal_consistency_check() { - // No check is needed - } } #[cfg(test)] diff --git a/src/symmetric/tweak_hash.rs b/src/symmetric/tweak_hash.rs index 6adf80c..caea13e 100644 --- a/src/symmetric/tweak_hash.rs +++ b/src/symmetric/tweak_hash.rs @@ -101,12 +101,6 @@ pub trait TweakableHash { PRF: Pseudorandom, PRF::Domain: Into, Self: Sized; - - /// Function to check internal consistency of any given parameters. - /// - /// This is for testing only and is expected to panic if something is wrong. - #[cfg(test)] - fn internal_consistency_check(); } /// Function implementing hash chains, implemented over a tweakable hash function diff --git a/src/symmetric/tweak_hash/poseidon.rs b/src/symmetric/tweak_hash/poseidon.rs index f17b8ec..4b24935 100644 --- a/src/symmetric/tweak_hash/poseidon.rs +++ b/src/symmetric/tweak_hash/poseidon.rs @@ -300,6 +300,40 @@ impl< tweak: &Self::Tweak, message: &[Self::Domain], ) -> Self::Domain { + const { + assert!( + CAPACITY < 24, + "Poseidon Tweak Chain Hash: Capacity must be less than 24" + ); + assert!( + PARAMETER_LEN + TWEAK_LEN + HASH_LEN <= 16, + "Poseidon Tweak Chain Hash: Input lengths too large for Poseidon instance" + ); + assert!( + PARAMETER_LEN + TWEAK_LEN + 2 * HASH_LEN <= 24, + "Poseidon Tweak Tree Hash: Input lengths too large for Poseidon instance" + ); + + // floor(log2(ORDER)) + let bits_per_fe = F::ORDER_U64.ilog2() as usize; + assert!( + bits_per_fe * 24 >= DOMAIN_PARAMETERS_LENGTH * 32, + "Poseidon Tweak Leaf Hash: not enough field elements to hash the domain separator" + ); + + // tree tweak: 32 (pos_in_level) + 8 (level) = 40 bits + // chain tweak: 32 (epoch) + 8 (chain_index) + 8 (pos_in_chain) + 8 (separator) = 56 bits + let tweak_fe_bits = bits_per_fe * TWEAK_LEN; + assert!( + tweak_fe_bits >= 40, + "Poseidon Tweak Hash: not enough field elements to encode the tree tweak" + ); + assert!( + tweak_fe_bits >= 56, + "Poseidon Tweak Hash: not enough field elements to encode the chain tweak" + ); + } + // we are in one of three cases: // (1) hashing within chains. We use compression mode. // (2) hashing two siblings in the tree. We use compression mode. @@ -691,41 +725,6 @@ impl< leaves } - - #[cfg(test)] - fn internal_consistency_check() { - assert!( - CAPACITY < 24, - "Poseidon Tweak Chain Hash: Capacity must be less than 24" - ); - assert!( - PARAMETER_LEN + TWEAK_LEN + HASH_LEN <= 16, - "Poseidon Tweak Chain Hash: Input lengths too large for Poseidon instance" - ); - assert!( - PARAMETER_LEN + TWEAK_LEN + 2 * HASH_LEN <= 24, - "Poseidon Tweak Tree Hash: Input lengths too large for Poseidon instance" - ); - - let bits_per_fe = f64::floor(f64::log2(F::ORDER_U64 as f64)); - let state_bits = bits_per_fe * f64::from(24_u32); - assert!( - state_bits >= f64::from((DOMAIN_PARAMETERS_LENGTH * 32) as u32), - "Poseidon Tweak Leaf Hash: not enough field elements to hash the domain separator" - ); - - let bits_for_tree_tweak = f64::from(32 + 8_u32); - let bits_for_chain_tweak = f64::from(32 + 8 + 8 + 8_u32); - let tweak_fe_bits = bits_per_fe * f64::from(TWEAK_LEN as u32); - assert!( - tweak_fe_bits >= bits_for_tree_tweak, - "Poseidon Tweak Hash: not enough field elements to encode the tree tweak" - ); - assert!( - tweak_fe_bits >= bits_for_chain_tweak, - "Poseidon Tweak Hash: not enough field elements to encode the chain tweak" - ); - } } // Example instantiations @@ -752,9 +751,6 @@ mod tests { fn test_apply_44() { let mut rng = rand::rng(); - // make sure parameters make sense - PoseidonTweak44::internal_consistency_check(); - // test that nothing is panicking let parameter = PoseidonTweak44::rand_parameter(&mut rng); let message_one = PoseidonTweak44::rand_domain(&mut rng); @@ -779,9 +775,6 @@ mod tests { fn test_apply_37() { let mut rng = rand::rng(); - // make sure parameters make sense - PoseidonTweak37::internal_consistency_check(); - // test that nothing is panicking let parameter = PoseidonTweak37::rand_parameter(&mut rng); let message_one = PoseidonTweak37::rand_domain(&mut rng);