mod.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the alphavm library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 /// Blake2Xs function 17 /// 18 /// This implementation is based on the BLAKE2Xs specification in Section 2 of 19 /// <https://www.blake2.net/blake2x.pdf> 20 mod hash_to_curve; 21 22 pub struct Blake2Xs; 23 24 impl Blake2Xs { 25 /// Returns the BLAKE2Xs digest given: 26 /// - `input` is an input message as a slice of bytes, 27 /// - `XOF_DIGEST_LENGTH` is a `u16` set to the length of the final output digest in bytes, 28 /// - `PERSONALIZATION` is a `u64` representing a UTF-8 string of 8 characters. 29 fn evaluate(input: &[u8], xof_digest_length: u16, persona: &[u8]) -> Vec<u8> { 30 assert!(xof_digest_length > 0, "Output digest must be of non-zero length"); 31 assert!(persona.len() <= 8, "Personalization may be at most 8 characters"); 32 33 // Start by computing the digest of the input bytes. 34 let xof_digest_length_node_offset = (xof_digest_length as u64) << 32; 35 let input_digest = blake2s_simd::Params::new() 36 .hash_length(32) 37 .node_offset(xof_digest_length_node_offset) 38 .personal(persona) 39 .hash(input); 40 41 let mut output = vec![]; 42 43 let num_rounds = xof_digest_length.saturating_add(31) / 32; 44 for node_offset in 0..num_rounds { 45 // Calculate the digest length for this round. 46 let is_final_round = node_offset == num_rounds - 1; 47 let has_remainder = xof_digest_length % 32 != 0; 48 let digest_length = match is_final_round && has_remainder { 49 true => (xof_digest_length % 32) as usize, 50 false => 32, 51 }; 52 53 // Compute the next part of the output digest. 54 output.extend_from_slice( 55 blake2s_simd::Params::new() 56 .hash_length(digest_length) 57 .fanout(0) 58 .max_depth(0) 59 .max_leaf_length(32) 60 .node_offset(xof_digest_length_node_offset | (node_offset as u64)) 61 .inner_hash_length(32) 62 .personal(persona) 63 .hash(input_digest.as_bytes()) 64 .as_bytes(), 65 ); 66 } 67 68 output 69 } 70 } 71 72 #[cfg(test)] 73 mod tests { 74 use crate::Blake2Xs; 75 use serde::Deserialize; 76 77 #[derive(Deserialize)] 78 struct Case { 79 hash: String, 80 #[serde(rename = "in")] 81 input: String, 82 key: String, 83 #[serde(rename = "out")] 84 output: String, 85 } 86 87 #[test] 88 fn test_blake2xs() { 89 // Run test vector cases. 90 let vectors: Vec<Case> = serde_json::from_str(include_str!("./resources/blake2-kat.json")).unwrap(); 91 for case in vectors.iter().filter(|v| &v.hash == "blake2xs" && v.key.is_empty()) { 92 let input = hex::decode(case.input.as_bytes()).unwrap(); 93 let xof_digest_length = u16::try_from(case.output.len()).unwrap() / 2; 94 let output = hex::encode(Blake2Xs::evaluate(&input, xof_digest_length, "".as_bytes())); 95 assert_eq!(output, case.output); 96 } 97 } 98 99 #[test] 100 fn test_blake2s() { 101 // Run test vector cases for blake2s as a sanity check for the underlying impl. 102 let vectors: Vec<Case> = serde_json::from_str(include_str!("./resources/blake2-kat.json")).unwrap(); 103 for case in vectors.iter().filter(|v| &v.hash == "blake2s" && v.key.is_empty()) { 104 let input = hex::decode(case.input.as_bytes()).unwrap(); 105 let output = hex::encode(blake2s_simd::Params::new().personal(&0u64.to_le_bytes()).hash(&input).as_bytes()); 106 assert_eq!(output, case.output); 107 } 108 } 109 }