1use ark_ff::PrimeField;
2use mina_curves::pasta::Fp;
3use poseidon::hash::Inputs;
4use sha2::{Digest, Sha256};
5
6use crate::{
7 proofs::field::FieldWitness, scan_state::pending_coinbase::PendingCoinbase,
8 AppendToInputs as _, ToInputs,
9};
10
11pub trait OCamlString {
14 fn to_ocaml_str(&self) -> String;
15 fn from_ocaml_str(s: &str) -> Self;
16}
17
18impl<const N: usize> OCamlString for [u8; N] {
19 fn to_ocaml_str(&self) -> String {
20 to_ocaml_str(self)
21 }
22
23 fn from_ocaml_str(s: &str) -> Self {
24 from_ocaml_str(s)
25 }
26}
27
28pub fn to_ocaml_str(bytes: &[u8]) -> String {
29 let mut s = String::with_capacity(256);
30
31 for b in bytes {
32 let c = char::from(*b);
33 if c == '\\' {
34 s.push_str("\\\\");
35 } else if c.is_ascii() && !c.is_ascii_control() {
36 s.push(c);
37 } else {
38 match b {
39 7 => s.push_str(r"\a"),
40 8 => s.push_str(r"\b"),
41 9 => s.push_str(r"\t"),
42 10 => s.push_str(r"\n"),
43 12 => s.push_str(r"\f"),
45 13 => s.push_str(r"\r"),
46 _ => s.push_str(&format!("\\{:<03}", b)),
47 }
48 }
49 }
50
51 s
52}
53
54pub fn from_ocaml_str<const N: usize>(s: &str) -> [u8; N] {
55 let mut bytes = [0; N];
56 let mut b_index = 0;
57
58 let mut index = 0;
59 let s = s.as_bytes();
60 while index < s.len() {
61 if s[index] == b'\\' {
62 if s.get(index + 1).map(|next| *next == b'\\').unwrap_or(false) {
63 bytes[b_index] = b'\\';
64 index += 2;
65 } else if s
66 .get(index + 1)
67 .map(|next| "abtnfr".contains(char::from(*next)))
68 .unwrap_or(false)
70 {
71 bytes[b_index] = match s[index + 1] {
72 b'a' => 7,
73 b'b' => 8,
74 b't' => 9,
75 b'n' => 10,
76 b'f' => 12,
78 b'r' => 13,
79 _ => unreachable!(),
80 };
81 index += 2;
82 } else {
83 let n1 = s[index + 1] - b'0';
84 let n2 = s[index + 2] - b'0';
85 let n3 = s[index + 3] - b'0';
86 bytes[b_index] = (n1 * 100) + (n2 * 10) + n3;
87 index += 4;
88 }
89 } else {
90 bytes[b_index] = s[index];
91 index += 1;
92 }
93
94 b_index += 1;
95 }
96
97 bytes
98}
99
100#[derive(Clone, PartialEq, Eq)]
102pub struct AuxHash(pub [u8; 32]);
103
104impl std::fmt::Debug for AuxHash {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.write_fmt(format_args!("AuxHash({})", self.to_ocaml_str()))
107 }
108}
109
110impl AuxHash {
111 fn to_ocaml_str(&self) -> String {
112 to_ocaml_str(&self.0)
113 }
114
115 fn from_ocaml_str(s: &str) -> Self {
116 Self(from_ocaml_str(s))
117 }
118}
119
120#[derive(Clone, PartialEq, Eq)]
122pub struct PendingCoinbaseAux(pub [u8; 32]);
123
124impl std::fmt::Debug for PendingCoinbaseAux {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.write_fmt(format_args!("PendingCoinbaseAux({})", self.to_ocaml_str()))
127 }
128}
129
130impl PendingCoinbaseAux {
131 fn to_ocaml_str(&self) -> String {
132 to_ocaml_str(&self.0)
133 }
134
135 fn from_ocaml_str(s: &str) -> Self {
136 Self(from_ocaml_str(s))
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct NonStark {
143 pub ledger_hash: Fp,
144 pub aux_hash: AuxHash,
145 pub pending_coinbase_aux: PendingCoinbaseAux,
146}
147
148impl NonStark {
149 pub fn digest(&self) -> [u8; 32] {
151 let Self {
152 ledger_hash,
153 aux_hash,
154 pending_coinbase_aux,
155 } = self;
156
157 let mut sha: Sha256 = Sha256::new();
158
159 let mut ledger_hash_bytes: [u8; 32] = <[u8; 32]>::default();
160
161 let ledger_hash = ledger_hash.into_bigint();
162 for (i, &word) in ledger_hash.0.iter().enumerate() {
164 let bytes = word.to_le_bytes();
165 ledger_hash_bytes[i * 8..(i + 1) * 8].copy_from_slice(&bytes);
166 }
167 ledger_hash_bytes.reverse();
168
169 sha.update(ledger_hash_bytes.as_slice());
170 sha.update(aux_hash.0.as_slice());
171 sha.update(pending_coinbase_aux.0.as_slice());
172
173 sha.finalize().into()
174 }
175}
176
177impl ToInputs for NonStark {
178 fn to_inputs(&self, inputs: &mut Inputs) {
180 let digest = self.digest();
181 inputs.append_bytes(digest.as_slice());
182 }
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
195pub struct StagedLedgerHash<F: FieldWitness> {
196 pub non_snark: NonStark,
197 pub pending_coinbase_hash: F,
198}
199
200impl StagedLedgerHash<Fp> {
201 pub fn of_aux_ledger_and_coinbase_hash(
203 aux_hash: AuxHash,
204 ledger_hash: Fp,
205 pending_coinbase: &mut PendingCoinbase,
206 ) -> Self {
207 Self {
208 non_snark: NonStark {
209 ledger_hash,
210 aux_hash,
211 pending_coinbase_aux: pending_coinbase.hash_extra(),
212 },
213 pending_coinbase_hash: pending_coinbase.merkle_root(),
214 }
215 }
216
217 #[cfg(test)]
219 pub fn from_ocaml_strings(
220 ledger_hash: &str,
221 aux_hash: &str,
222 pending_coinbase_aux: &str,
223 pending_coinbase_hash: &str,
224 ) -> Self {
225 use std::str::FromStr;
226
227 Self {
228 non_snark: NonStark {
229 ledger_hash: Fp::from_str(ledger_hash).unwrap(),
230 aux_hash: AuxHash::from_ocaml_str(aux_hash),
231 pending_coinbase_aux: PendingCoinbaseAux::from_ocaml_str(pending_coinbase_aux),
232 },
233 pending_coinbase_hash: Fp::from_str(pending_coinbase_hash).unwrap(),
234 }
235 }
236}
237
238impl ToInputs for StagedLedgerHash<Fp> {
239 fn to_inputs(&self, inputs: &mut Inputs) {
240 let Self {
241 non_snark,
242 pending_coinbase_hash,
243 } = self;
244
245 inputs.append(non_snark);
246 inputs.append(pending_coinbase_hash);
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use std::str::FromStr;
253
254 #[cfg(target_family = "wasm")]
255 use wasm_bindgen_test::wasm_bindgen_test as test;
256
257 use super::*;
258
259 #[test]
260 fn test_convert() {
261 let s = r"\\\236\235f\255\200o8\217Rxlmily\194\219\1949\221N\145\180g)\215:'\251W\233";
271 let pending_coinbase_aux = PendingCoinbaseAux::from_ocaml_str(s);
272 assert_eq!(s, pending_coinbase_aux.to_ocaml_str());
273
274 let s = r"\\\249\245k\176]TJ\216\183\001\204\177\131\030\244o\178\188\191US\156\192Hi\194P\223\004\000\003";
275 let aux_hash = AuxHash::from_ocaml_str(s);
276 assert_eq!(s, aux_hash.to_ocaml_str());
277
278 let a = AuxHash([
282 9, 204, 83, 160, 70, 227, 22, 142, 146, 172, 220, 46, 82, 39, 222, 76, 38, 98, 191,
283 138, 59, 22, 235, 137, 190, 62, 205, 46, 31, 195, 45, 231,
284 ]);
285
286 println!("a={}", a.to_ocaml_str());
287
288 let s = r"\t\204S\160F\227\022\142\146\172\220.R'\222L&b\191\138;\022\235\137\190>\205.\031\195-\231";
289 assert_eq!(s, a.to_ocaml_str());
290 let aux_hash = AuxHash::from_ocaml_str(s);
291 assert_eq!(s, aux_hash.to_ocaml_str());
292
293 let s = r"\000 \014WQ\192&\229C\178\232\171.\176`\153\218\161\209\229\223Gw\143w\135\250\171E\205\241/\227\168";
294 let memo = <[u8; 34]>::from_ocaml_str(s);
295 assert_eq!(s, memo.to_ocaml_str());
296
297 let s = r"\n\220\211\153\014A\191\006\019\231/\244\155\005\212\1310|\227\133\176O\196\131\023t\152\178\130?\206U";
303 let pending_coinbase_aux = PendingCoinbaseAux::from_ocaml_str(s);
304 assert_eq!(s, pending_coinbase_aux.to_ocaml_str());
305 }
306
307 #[test]
308 fn test_non_snark_digest() {
309 let non_snark = NonStark {
318 ledger_hash: Fp::from_str(
319 "7213023165825031994332898585791275635753820608093286100176380057570051468967",
320 )
321 .unwrap(),
322 aux_hash: AuxHash::from_ocaml_str(
323 r"T\249\245k\176]TJ\216\183\001\204\177\131\030\244o\178\188\191US\156\192Hi\194P\223\004\000\003",
324 ),
325 pending_coinbase_aux: PendingCoinbaseAux::from_ocaml_str(
326 r"_\236\235f\255\200o8\217Rxlmily\194\219\1949\221N\145\180g)\215:'\251W\233",
327 ),
328 };
329
330 assert_eq!(
331 non_snark.digest().to_ocaml_str(),
332 r"\t\204S\160F\227\022\142\146\172\220.R'\222L&b\191\138;\022\235\137\190>\205.\031\195-\231"
333 );
334
335 let non_snark = NonStark {
344 ledger_hash: Fp::from_str(
345 "18582860218764414485081234471609377222894570081548691702645303871998665679024",
346 )
347 .unwrap(),
348 aux_hash: AuxHash::from_ocaml_str(
349 r"0\136Wg\182DbX\203kLi\212%\199\206\142#\213`L\160bpCB\1413\240\193\171K",
350 ),
351 pending_coinbase_aux: PendingCoinbaseAux::from_ocaml_str(
352 r"\n\220\211\153\014A\191\006\019\231/\244\155\005\212\1310|\227\133\176O\196\131\023t\152\178\130?\206U",
353 ),
354 };
355
356 assert_eq!(
357 non_snark.digest().to_ocaml_str(),
358 r"u~\218kzX\228$\027qG\239\135\255:\143\171\186\011\200P\243\163\135\223T>\017\172\254\1906",
359 );
360 }
361}