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}