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}