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