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 }