snarkvm_console_algorithms/blake2xs/
mod.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkVM 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>
20mod hash_to_curve;
21
22pub struct Blake2Xs;
23
24impl 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)]
73mod 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}