mina_hasher/
lib.rs

1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3#![no_std]
4
5extern crate alloc;
6use alloc::{format, string::String, vec, vec::Vec};
7
8pub mod poseidon;
9pub mod roinput;
10pub use mina_curves::pasta::Fp;
11pub use poseidon::{PoseidonHasherKimchi, PoseidonHasherLegacy};
12pub use roinput::ROInput;
13
14use ark_ff::PrimeField;
15use o1_utils::FieldHelpers;
16
17/// The domain parameter trait is used during hashing to convey extra
18/// arguments to domain string generation. It is also used by generic signing
19/// code.
20pub trait DomainParameter: Clone {
21    /// Conversion into vector of bytes
22    fn into_bytes(self) -> Vec<u8>;
23}
24
25impl DomainParameter for () {
26    fn into_bytes(self) -> Vec<u8> {
27        vec![]
28    }
29}
30
31impl DomainParameter for u32 {
32    fn into_bytes(self) -> Vec<u8> {
33        self.to_le_bytes().to_vec()
34    }
35}
36
37impl DomainParameter for u64 {
38    fn into_bytes(self) -> Vec<u8> {
39        self.to_le_bytes().to_vec()
40    }
41}
42
43/// Interface for hashable objects
44///
45/// Mina uses fixed-length hashing with domain separation for each type of
46/// object hashed. The prior means that `Hashable` only supports types whose
47/// size is not variable.
48///
49/// **Important:** The developer MUST assure that all domain strings used
50/// throughout the system are unique and that all structures hashed are of fixed
51/// size.
52///
53/// Here is an example of how to implement the `Hashable` trait for am `Example`
54/// type.
55///
56/// ```rust
57/// use mina_hasher::{Hashable, ROInput};
58///
59/// #[derive(Clone)]
60/// struct Example;
61///
62/// impl Hashable for Example {
63///     type D = ();
64///
65///     fn to_roinput(&self) -> ROInput {
66///         let roi = ROInput::new();
67///         // Serialize example members
68///         // ...
69///         roi
70///     }
71///
72///     fn domain_string(_: Self::D) -> Option<String> {
73///        format!("Example").into()
74///    }
75/// }
76/// ```
77///
78/// See example in [`ROInput`] documentation
79pub trait Hashable: Clone {
80    /// Generic domain string argument type
81    type D: DomainParameter;
82
83    /// Serialization to random oracle input
84    fn to_roinput(&self) -> ROInput;
85
86    /// Generate unique domain string of length `<= 20`.
87    ///
88    /// The length bound is guarded by an assertion, but uniqueness must be
89    /// enforced by the developer implementing the traits (see [`Hashable`] for
90    /// more details). The domain string may be parameterized by the contents of
91    /// the generic `domain_param` argument.
92    ///
93    /// **Note:** You should always return `Some(String)`. A `None` return value
94    /// is only used for testing.
95    fn domain_string(domain_param: Self::D) -> Option<String>;
96}
97
98/// Interface for hashing [`Hashable`] inputs
99///
100/// Mina uses a unique hasher configured with domain separation for each type of
101/// object hashed.
102/// The underlying hash parameters are large and costly to initialize, so the
103/// [`Hasher`] interface provides a reusable context for efficient hashing with
104/// domain separation.
105///
106/// Example usage
107///
108/// ```rust
109/// use mina_hasher::{create_legacy, Fp, Hashable, Hasher, ROInput};
110///
111/// #[derive(Clone)]
112/// struct Something;
113///
114/// impl Hashable for Something {
115///     type D = u32;
116///
117///     fn to_roinput(&self) -> ROInput {
118///         let mut roi = ROInput::new();
119///         // ... serialize contents of self
120///         roi
121///     }
122///
123///     fn domain_string(id: Self::D) -> Option<String> {
124///         format!("Something {}", id).into()
125///     }
126/// }
127///
128/// let mut hasher = create_legacy::<Something>(123);
129/// let output: Fp = hasher.hash(&Something { });
130/// ```
131///
132pub trait Hasher<H: Hashable> {
133    /// Set the initial state based on domain separation string generated from
134    /// `H::domain_string(domain_param)`
135    fn init(&mut self, domain_param: H::D) -> &mut dyn Hasher<H>;
136
137    /// Restore the initial state that was set most recently
138    fn reset(&mut self) -> &mut dyn Hasher<H>;
139
140    /// Consume hash `input`
141    fn update(&mut self, input: &H) -> &mut dyn Hasher<H>;
142
143    /// Obtain has result output
144    fn digest(&mut self) -> Fp;
145
146    /// Hash input and obtain result output
147    fn hash(&mut self, input: &H) -> Fp {
148        self.reset();
149        self.update(input);
150        let output = self.digest();
151        self.reset();
152        output
153    }
154
155    /// Initialize state, hash input and obtain result output
156    fn init_and_hash(&mut self, domain_param: H::D, input: &H) -> Fp {
157        self.init(domain_param);
158        self.update(input);
159        let output = self.digest();
160        self.reset();
161        output
162    }
163}
164
165/// Transform domain prefix string to field element
166fn domain_prefix_to_field<F: PrimeField>(prefix: String) -> F {
167    const MAX_DOMAIN_STRING_LEN: usize = 20;
168    assert!(prefix.len() <= MAX_DOMAIN_STRING_LEN);
169    let prefix = &prefix[..core::cmp::min(prefix.len(), MAX_DOMAIN_STRING_LEN)];
170    let mut bytes = format!("{prefix:*<MAX_DOMAIN_STRING_LEN$}")
171        .as_bytes()
172        .to_vec();
173    bytes.resize(F::size_in_bytes(), 0);
174    F::from_bytes(&bytes).expect("invalid domain bytes")
175}
176
177/// Create a legacy hasher context
178pub fn create_legacy<H: Hashable>(domain_param: H::D) -> PoseidonHasherLegacy<H> {
179    poseidon::new_legacy::<H>(domain_param)
180}
181
182/// Create an experimental kimchi hasher context
183pub fn create_kimchi<H: Hashable>(domain_param: H::D) -> PoseidonHasherKimchi<H>
184where
185    H::D: DomainParameter,
186{
187    poseidon::new_kimchi::<H>(domain_param)
188}