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  }