/ circuit / algorithms / src / bhp / hash_uncompressed.rs
hash_uncompressed.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  use super::*;
 17  
 18  impl<E: Environment, const NUM_WINDOWS: u8, const WINDOW_SIZE: u8> HashUncompressed
 19      for BHP<E, NUM_WINDOWS, WINDOW_SIZE>
 20  {
 21      type Input = Boolean<E>;
 22      type Output = Group<E>;
 23  
 24      /// Returns the BHP hash of the given input as an affine group element.
 25      ///
 26      /// This uncompressed variant of the BHP hash function is provided to support
 27      /// the BHP commitment scheme, as it is typically not used by applications.
 28      fn hash_uncompressed(&self, input: &[Self::Input]) -> Self::Output {
 29          // The number of hasher bits to fit.
 30          let num_hasher_bits = NUM_WINDOWS as usize * WINDOW_SIZE as usize * BHP_CHUNK_SIZE;
 31          // The number of data bits in the output.
 32          let num_data_bits = E::BaseField::size_in_data_bits();
 33          // The maximum number of input bits per iteration.
 34          let max_input_bits_per_iteration = num_hasher_bits - num_data_bits;
 35  
 36          debug_assert!(num_data_bits < num_hasher_bits);
 37          debug_assert_eq!(num_data_bits - 64, self.domain.len());
 38  
 39          // Initialize a variable to store the hash from the current iteration.
 40          let mut digest = Group::zero();
 41  
 42          // Prepare a reusable vector for the preimage.
 43          let mut preimage = Vec::with_capacity(num_hasher_bits);
 44  
 45          // Compute the hash of the input.
 46          for (i, input_bits) in input.chunks(max_input_bits_per_iteration).enumerate() {
 47              // Determine if this is the first iteration.
 48              match i == 0 {
 49                  // Construct the first iteration as: [ 0...0 || DOMAIN || LENGTH(INPUT) || INPUT[0..BLOCK_SIZE] ].
 50                  true => {
 51                      // Initialize a vector for the hash preimage.
 52                      preimage.extend(self.domain.clone());
 53                      U64::constant(console::U64::new(input.len() as u64)).write_bits_le(&mut preimage);
 54                      preimage.extend_from_slice(input_bits);
 55                  }
 56                  // Construct the subsequent iterations as: [ PREVIOUS_HASH[0..DATA_BITS] || INPUT[I * BLOCK_SIZE..(I + 1) * BLOCK_SIZE] ].
 57                  false => {
 58                      // Initialize a vector for the hash preimage.
 59                      digest.to_x_coordinate().write_bits_le(&mut preimage);
 60                      preimage.truncate(num_data_bits);
 61                      preimage.extend_from_slice(input_bits);
 62                  }
 63              }
 64              // Hash the preimage for this iteration.
 65              digest = self.hasher.hash_uncompressed(&preimage);
 66              // Clear the preimage vector for the next iteration.
 67              preimage.clear();
 68          }
 69  
 70          digest
 71      }
 72  }
 73  
 74  #[cfg(test)]
 75  mod tests {
 76      use super::*;
 77      use alphavm_circuit_types::environment::Circuit;
 78      use alphavm_curves::{AffineCurve, ProjectiveCurve};
 79      use alphavm_utilities::{TestRng, Uniform};
 80  
 81      use anyhow::Result;
 82  
 83      const ITERATIONS: u64 = 100;
 84      const DOMAIN: &str = "BHPCircuit0";
 85  
 86      macro_rules! check_hash_uncompressed {
 87          ($bhp:ident, $mode:ident, $num_bits:expr, ($num_constants:expr, $num_public:expr, $num_private:expr, $num_constraints:expr), $rng:expr) => {{
 88              // Initialize BHP.
 89              let native = console::$bhp::<<Circuit as Environment>::Network>::setup(DOMAIN)?;
 90              let circuit = $bhp::<Circuit>::constant(native.clone());
 91  
 92              for i in 0..ITERATIONS {
 93                  // Sample a random input.
 94                  let input = (0..$num_bits).map(|_| Uniform::rand($rng)).collect::<Vec<_>>();
 95                  // Compute the expected hash.
 96                  let expected = console::HashUncompressed::hash_uncompressed(&native, &input)?;
 97                  // Prepare the circuit input.
 98                  let circuit_input: Vec<Boolean<_>> = Inject::new(Mode::$mode, input);
 99  
100                  Circuit::scope(format!("BHP {i}"), || {
101                      // Perform the hash operation.
102                      let candidate = circuit.hash_uncompressed(&circuit_input);
103                      assert_scope!($num_constants, $num_public, $num_private, $num_constraints);
104                      assert_eq!(expected, candidate.eject_value());
105                      assert!(candidate.eject_value().to_affine().is_on_curve());
106                      assert!(candidate.eject_value().to_affine().is_in_correct_subgroup_assuming_on_curve());
107                  });
108                  Circuit::reset();
109              }
110              Ok::<_, anyhow::Error>(())
111          }};
112      }
113  
114      fn check_hash_uncompressed<const NUM_WINDOWS: u8, const WINDOW_SIZE: u8>(
115          mode: Mode,
116          num_constants: u64,
117          num_public: u64,
118          num_private: u64,
119          num_constraints: u64,
120      ) -> Result<()> {
121          use console::HashUncompressed as H;
122  
123          // Initialize BHP.
124          let native = console::BHP::<<Circuit as Environment>::Network, NUM_WINDOWS, WINDOW_SIZE>::setup(DOMAIN)?;
125          let circuit = BHP::<Circuit, NUM_WINDOWS, WINDOW_SIZE>::new(Mode::Constant, native.clone());
126          // Determine the number of inputs.
127          let num_input_bits = NUM_WINDOWS as usize * WINDOW_SIZE as usize * BHP_CHUNK_SIZE;
128  
129          let mut rng = TestRng::default();
130  
131          for i in 0..ITERATIONS {
132              // Sample a random input.
133              let input = (0..num_input_bits).map(|_| bool::rand(&mut rng)).collect::<Vec<bool>>();
134              // Compute the expected hash.
135              let expected = native.hash_uncompressed(&input).expect("Failed to hash native input");
136              // Prepare the circuit input.
137              let circuit_input: Vec<Boolean<_>> = Inject::new(mode, input);
138  
139              Circuit::scope(format!("BHP {mode} {i}"), || {
140                  // Perform the hash operation.
141                  let candidate = circuit.hash_uncompressed(&circuit_input);
142                  assert_scope!(num_constants, num_public, num_private, num_constraints);
143                  assert_eq!(expected, candidate.eject_value());
144              });
145              Circuit::reset();
146          }
147          Ok(())
148      }
149  
150      #[test]
151      fn test_hash_uncompressed_constant() -> Result<()> {
152          check_hash_uncompressed::<32, 48>(Mode::Constant, 7239, 0, 0, 0)
153      }
154  
155      #[test]
156      fn test_hash_uncompressed_public() -> Result<()> {
157          check_hash_uncompressed::<32, 48>(Mode::Public, 470, 0, 8774, 8776)
158      }
159  
160      #[test]
161      fn test_hash_uncompressed_private() -> Result<()> {
162          check_hash_uncompressed::<32, 48>(Mode::Private, 470, 0, 8774, 8776)
163      }
164  
165      #[test]
166      fn test_hash_uncompressed_bhp256_constant() -> Result<()> {
167          let mut rng = TestRng::default();
168          check_hash_uncompressed!(BHP256, Constant, 261, (756, 0, 0, 0), &mut rng)
169      }
170  
171      #[test]
172      fn test_hash_uncompressed_bhp256_public() -> Result<()> {
173          let mut rng = TestRng::default();
174          check_hash_uncompressed!(BHP256, Public, 261, (403, 0, 445, 445), &mut rng)
175      }
176  
177      #[test]
178      fn test_hash_uncompressed_bhp256_private() -> Result<()> {
179          let mut rng = TestRng::default();
180          check_hash_uncompressed!(BHP256, Private, 261, (403, 0, 445, 445), &mut rng)
181      }
182  
183      #[test]
184      fn test_hash_uncompressed_bhp512_constant() -> Result<()> {
185          let mut rng = TestRng::default();
186          check_hash_uncompressed!(BHP512, Constant, 522, (1113, 0, 0, 0), &mut rng)
187      }
188  
189      #[test]
190      fn test_hash_uncompressed_bhp512_public() -> Result<()> {
191          let mut rng = TestRng::default();
192          check_hash_uncompressed!(BHP512, Public, 522, (409, 0, 895, 895), &mut rng)
193      }
194  
195      #[test]
196      fn test_hash_uncompressed_bhp512_private() -> Result<()> {
197          let mut rng = TestRng::default();
198          check_hash_uncompressed!(BHP512, Private, 522, (409, 0, 895, 895), &mut rng)
199      }
200  
201      #[test]
202      fn test_hash_uncompressed_bhp768_constant() -> Result<()> {
203          let mut rng = TestRng::default();
204          check_hash_uncompressed!(BHP768, Constant, 783, (1488, 0, 0, 0), &mut rng)
205      }
206  
207      #[test]
208      fn test_hash_uncompressed_bhp768_public() -> Result<()> {
209          let mut rng = TestRng::default();
210          check_hash_uncompressed!(BHP768, Public, 783, (429, 0, 1365, 1365), &mut rng)
211      }
212  
213      #[test]
214      fn test_hash_uncompressed_bhp768_private() -> Result<()> {
215          let mut rng = TestRng::default();
216          check_hash_uncompressed!(BHP768, Private, 783, (429, 0, 1365, 1365), &mut rng)
217      }
218  
219      #[test]
220      fn test_hash_uncompressed_bhp1024_constant() -> Result<()> {
221          let mut rng = TestRng::default();
222          check_hash_uncompressed!(BHP1024, Constant, 1043, (1815, 0, 0, 0), &mut rng)?;
223          check_hash_uncompressed!(BHP1024, Constant, 1044, (1815, 0, 0, 0), &mut rng)?;
224          check_hash_uncompressed!(BHP1024, Constant, 1046, (2413, 0, 0, 0), &mut rng)
225      }
226  
227      #[test]
228      fn test_hash_uncompressed_bhp1024_public() -> Result<()> {
229          let mut rng = TestRng::default();
230          check_hash_uncompressed!(BHP1024, Public, 1043, (413, 0, 1775, 1775), &mut rng)?;
231          check_hash_uncompressed!(BHP1024, Public, 1044, (413, 0, 1775, 1775), &mut rng)?;
232          check_hash_uncompressed!(BHP1024, Public, 1046, (418, 0, 2709, 2711), &mut rng)
233      }
234  
235      #[test]
236      fn test_hash_uncompressed_bhp1024_private() -> Result<()> {
237          let mut rng = TestRng::default();
238          check_hash_uncompressed!(BHP1024, Private, 1043, (413, 0, 1775, 1775), &mut rng)?;
239          check_hash_uncompressed!(BHP1024, Private, 1044, (413, 0, 1775, 1775), &mut rng)?;
240          check_hash_uncompressed!(BHP1024, Private, 1046, (418, 0, 2709, 2711), &mut rng)
241      }
242  
243      #[test]
244      fn test_hash_uncompressed_cost_comparison() -> Result<()> {
245          // The cost to hash 512 bits for each BHP variant is:
246          let mut rng = TestRng::default();
247          check_hash_uncompressed!(BHP256, Private, 512, (410, 0, 1799, 1801), &mut rng)?;
248          check_hash_uncompressed!(BHP512, Private, 512, (409, 0, 880, 880), &mut rng)?;
249          check_hash_uncompressed!(BHP768, Private, 512, (423, 0, 900, 900), &mut rng)?;
250          check_hash_uncompressed!(BHP1024, Private, 512, (407, 0, 875, 875), &mut rng)
251      }
252  }