dusk_poseidon/hash.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use alloc::vec::Vec;
8
9use dusk_bls12_381::BlsScalar;
10use dusk_jubjub::JubJubScalar;
11use dusk_safe::{Call, Sponge};
12
13use crate::hades::ScalarPermutation;
14use crate::Error;
15
16#[cfg(feature = "zk")]
17pub(crate) mod gadget;
18
19/// The Domain Separation for Poseidon
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum Domain {
22 /// Domain to specify hashing of 4-arity merkle tree.
23 /// Note that selecting this domain-separator means that the total hash
24 /// input must be exactly 4 `BlsScalar` long, and any empty slots of the
25 /// merkle tree level need to be filled with the zero element.
26 Merkle4,
27 /// Domain to specify hashing of 2-arity merkle tree
28 /// Note that selecting this domain-separator means that the total hash
29 /// input must be exactly 2 `BlsScalar` long, and any empty slots of the
30 /// merkle tree level need to be filled with the zero element.
31 Merkle2,
32 /// Domain to specify hash used for encryption
33 Encryption,
34 /// Domain to specify hash for any other input
35 Other,
36}
37
38impl From<Domain> for u64 {
39 /// Encryption for the domain-separator are taken from section 4.2 of the
40 /// paper adapted to u64.
41 /// When `Other` is selected we set the domain-separator to zero. We can do
42 /// this since the io-pattern will be encoded in the tag in any case,
43 /// ensuring safety from collision attacks.
44 fn from(domain: Domain) -> Self {
45 match domain {
46 // 2^4 - 1
47 Domain::Merkle4 => 0x0000_0000_0000_000f,
48 // 2^2 - 1
49 Domain::Merkle2 => 0x0000_0000_0000_0003,
50 // 2^32
51 Domain::Encryption => 0x0000_0001_0000_0000,
52 // 0
53 Domain::Other => 0x0000_0000_0000_0000,
54 }
55 }
56}
57
58// This function, which is called during the finalization step of the hash, will
59// always produce a valid io-pattern based on the input.
60// The function will return an error if a merkle domain is selected but the
61// given input elements don't add up to the specified arity.
62fn io_pattern<T>(
63 domain: Domain,
64 input: &[&[T]],
65 output_len: usize,
66) -> Result<Vec<Call>, Error> {
67 let mut io_pattern = Vec::new();
68 // check total input length against domain
69 let input_len = input.iter().fold(0, |acc, input| acc + input.len());
70 match domain {
71 Domain::Merkle2 if input_len != 2 || output_len != 1 => {
72 return Err(Error::IOPatternViolation);
73 }
74 Domain::Merkle4 if input_len != 4 || output_len != 1 => {
75 return Err(Error::IOPatternViolation);
76 }
77 _ => {}
78 }
79 for input in input.iter() {
80 io_pattern.push(Call::Absorb(input.len()));
81 }
82 io_pattern.push(Call::Squeeze(output_len));
83
84 Ok(io_pattern)
85}
86
87/// Hash any given input into one or several scalar using the Hades
88/// permutation strategy. The Hash can absorb multiple chunks of input but will
89/// only call `squeeze` once at the finalization of the hash.
90/// The output length is set to 1 element per default, but this can be
91/// overridden with [`Hash::output_len`].
92pub struct Hash<'a> {
93 domain: Domain,
94 input: Vec<&'a [BlsScalar]>,
95 output_len: usize,
96}
97
98impl<'a> Hash<'a> {
99 /// Create a new hash.
100 pub fn new(domain: Domain) -> Self {
101 Self {
102 domain,
103 input: Vec::new(),
104 output_len: 1,
105 }
106 }
107
108 /// Override the length of the hash output (default value is 1) when using
109 /// the hash for anything other than hashing a merkle tree or
110 /// encryption.
111 pub fn output_len(&mut self, output_len: usize) {
112 if self.domain == Domain::Other && output_len > 0 {
113 self.output_len = output_len;
114 }
115 }
116
117 /// Update the hash input.
118 pub fn update(&mut self, input: &'a [BlsScalar]) {
119 self.input.push(input);
120 }
121
122 /// Finalize the hash.
123 ///
124 /// # Panics
125 /// This function panics when the io-pattern can not be created with the
126 /// given domain and input, e.g. using [`Domain::Merkle4`] with an input
127 /// anything other than 4 Scalar.
128 pub fn finalize(&self) -> Vec<BlsScalar> {
129 // Generate the hash using the sponge framework:
130 // initialize the sponge
131 let mut sponge = Sponge::start(
132 ScalarPermutation::new(),
133 io_pattern(self.domain, &self.input, self.output_len)
134 .expect("io-pattern should be valid"),
135 self.domain.into(),
136 )
137 .expect("at this point the io-pattern is valid");
138
139 // absorb the input
140 for input in self.input.iter() {
141 sponge
142 .absorb(input.len(), input)
143 .expect("at this point the io-pattern is valid");
144 }
145
146 // squeeze output_len elements
147 sponge
148 .squeeze(self.output_len)
149 .expect("at this point the io-pattern is valid");
150
151 // return the result
152 sponge
153 .finish()
154 .expect("at this point the io-pattern is valid")
155 }
156
157 /// Finalize the hash and output the result as a `JubJubScalar` by
158 /// truncating the `BlsScalar` output to 250 bits.
159 ///
160 /// # Panics
161 /// This function panics when the io-pattern can not be created with the
162 /// given domain and input, e.g. using [`Domain::Merkle4`] with an input
163 /// anything other than 4 Scalar.
164 pub fn finalize_truncated(&self) -> Vec<JubJubScalar> {
165 // bit-mask to 'cast' a bls-scalar result to a jubjub-scalar by
166 // truncating the 6 highest bits
167 const TRUNCATION_MASK: BlsScalar = BlsScalar::from_raw([
168 0xffff_ffff_ffff_ffff,
169 0xffff_ffff_ffff_ffff,
170 0xffff_ffff_ffff_ffff,
171 0x03ff_ffff_ffff_ffff,
172 ]);
173
174 // finalize the hash as bls-scalar
175 let bls_output = self.finalize();
176
177 bls_output
178 .iter()
179 .map(|bls| {
180 JubJubScalar::from_raw((bls & &TRUNCATION_MASK).reduce().0)
181 })
182 .collect()
183 }
184
185 /// Digest an input and calculate the hash immediately
186 ///
187 /// # Panics
188 /// This function panics when the io-pattern can not be created with the
189 /// given domain and input, e.g. using [`Domain::Merkle4`] with an input
190 /// anything other than 4 Scalar.
191 pub fn digest(domain: Domain, input: &'a [BlsScalar]) -> Vec<BlsScalar> {
192 let mut hash = Self::new(domain);
193 hash.update(input);
194 hash.finalize()
195 }
196
197 /// Digest an input and calculate the hash as jubjub-scalar immediately
198 ///
199 /// # Panics
200 /// This function panics when the io-pattern can not be created with the
201 /// given domain and input, e.g. using [`Domain::Merkle4`] with an input
202 /// anything other than 4 Scalar.
203 pub fn digest_truncated(
204 domain: Domain,
205 input: &'a [BlsScalar],
206 ) -> Vec<JubJubScalar> {
207 let mut hash = Self::new(domain);
208 hash.update(input);
209 hash.finalize_truncated()
210 }
211}