mod.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  /// Blake2Xs function
 20  ///
 21  /// This implementation is based on the BLAKE2Xs specification in Section 2 of
 22  /// <https://www.blake2.net/blake2x.pdf>
 23  mod hash_to_curve;
 24  
 25  pub struct Blake2Xs;
 26  
 27  impl Blake2Xs {
 28      /// Returns the BLAKE2Xs digest given:
 29      ///  - `input` is an input message as a slice of bytes,
 30      ///  - `XOF_DIGEST_LENGTH` is a `u16` set to the length of the final output digest in bytes,
 31      ///  - `PERSONALIZATION` is a `u64` representing a UTF-8 string of 8 characters.
 32      fn evaluate(input: &[u8], xof_digest_length: u16, persona: &[u8]) -> Vec<u8> {
 33          assert!(xof_digest_length > 0, "Output digest must be of non-zero length");
 34          assert!(persona.len() <= 8, "Personalization may be at most 8 characters");
 35  
 36          // Start by computing the digest of the input bytes.
 37          let xof_digest_length_node_offset = (xof_digest_length as u64) << 32;
 38          let input_digest = blake2s_simd::Params::new()
 39              .hash_length(32)
 40              .node_offset(xof_digest_length_node_offset)
 41              .personal(persona)
 42              .hash(input);
 43  
 44          let mut output = vec![];
 45  
 46          let num_rounds = xof_digest_length.saturating_add(31) / 32;
 47          for node_offset in 0..num_rounds {
 48              // Calculate the digest length for this round.
 49              let is_final_round = node_offset == num_rounds - 1;
 50              let has_remainder = !xof_digest_length.is_multiple_of(32);
 51              let digest_length = match is_final_round && has_remainder {
 52                  true => (xof_digest_length % 32) as usize,
 53                  false => 32,
 54              };
 55  
 56              // Compute the next part of the output digest.
 57              output.extend_from_slice(
 58                  blake2s_simd::Params::new()
 59                      .hash_length(digest_length)
 60                      .fanout(0)
 61                      .max_depth(0)
 62                      .max_leaf_length(32)
 63                      .node_offset(xof_digest_length_node_offset | (node_offset as u64))
 64                      .inner_hash_length(32)
 65                      .personal(persona)
 66                      .hash(input_digest.as_bytes())
 67                      .as_bytes(),
 68              );
 69          }
 70  
 71          output
 72      }
 73  }
 74  
 75  #[cfg(test)]
 76  mod tests {
 77      use crate::Blake2Xs;
 78      use serde::Deserialize;
 79  
 80      #[derive(Deserialize)]
 81      struct Case {
 82          hash: String,
 83          #[serde(rename = "in")]
 84          input: String,
 85          key: String,
 86          #[serde(rename = "out")]
 87          output: String,
 88      }
 89  
 90      #[test]
 91      fn test_blake2xs() {
 92          // Run test vector cases.
 93          let vectors: Vec<Case> = serde_json::from_str(include_str!("./resources/blake2-kat.json")).unwrap();
 94          for case in vectors.iter().filter(|v| &v.hash == "blake2xs" && v.key.is_empty()) {
 95              let input = hex::decode(case.input.as_bytes()).unwrap();
 96              let xof_digest_length = u16::try_from(case.output.len()).unwrap() / 2;
 97              let output = hex::encode(Blake2Xs::evaluate(&input, xof_digest_length, "".as_bytes()));
 98              assert_eq!(output, case.output);
 99          }
100      }
101  
102      #[test]
103      fn test_blake2s() {
104          // Run test vector cases for blake2s as a sanity check for the underlying impl.
105          let vectors: Vec<Case> = serde_json::from_str(include_str!("./resources/blake2-kat.json")).unwrap();
106          for case in vectors.iter().filter(|v| &v.hash == "blake2s" && v.key.is_empty()) {
107              let input = hex::decode(case.input.as_bytes()).unwrap();
108              let output = hex::encode(blake2s_simd::Params::new().personal(&0u64.to_le_bytes()).hash(&input).as_bytes());
109              assert_eq!(output, case.output);
110          }
111      }
112  }