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}