1use ark_ff::{PrimeField, ToBytes};
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_repr();
162 ledger_hash.write(ledger_hash_bytes.as_mut_slice()).unwrap();
163 ledger_hash_bytes.reverse();
164
165 sha.update(ledger_hash_bytes.as_slice());
166 sha.update(aux_hash.0.as_slice());
167 sha.update(pending_coinbase_aux.0.as_slice());
168
169 sha.finalize().into()
170 }
171}
172
173impl ToInputs for NonStark {
174 fn to_inputs(&self, inputs: &mut Inputs) {
176 let digest = self.digest();
177 inputs.append_bytes(digest.as_slice());
178 }
179}
180
181#[derive(Clone, Debug, PartialEq, Eq)]
191pub struct StagedLedgerHash<F: FieldWitness> {
192 pub non_snark: NonStark,
193 pub pending_coinbase_hash: F,
194}
195
196impl StagedLedgerHash<Fp> {
197 pub fn of_aux_ledger_and_coinbase_hash(
199 aux_hash: AuxHash,
200 ledger_hash: Fp,
201 pending_coinbase: &mut PendingCoinbase,
202 ) -> Self {
203 Self {
204 non_snark: NonStark {
205 ledger_hash,
206 aux_hash,
207 pending_coinbase_aux: pending_coinbase.hash_extra(),
208 },
209 pending_coinbase_hash: pending_coinbase.merkle_root(),
210 }
211 }
212
213 #[cfg(test)]
215 pub fn from_ocaml_strings(
216 ledger_hash: &str,
217 aux_hash: &str,
218 pending_coinbase_aux: &str,
219 pending_coinbase_hash: &str,
220 ) -> Self {
221 use std::str::FromStr;
222
223 Self {
224 non_snark: NonStark {
225 ledger_hash: Fp::from_str(ledger_hash).unwrap(),
226 aux_hash: AuxHash::from_ocaml_str(aux_hash),
227 pending_coinbase_aux: PendingCoinbaseAux::from_ocaml_str(pending_coinbase_aux),
228 },
229 pending_coinbase_hash: Fp::from_str(pending_coinbase_hash).unwrap(),
230 }
231 }
232}
233
234impl ToInputs for StagedLedgerHash<Fp> {
235 fn to_inputs(&self, inputs: &mut Inputs) {
236 let Self {
237 non_snark,
238 pending_coinbase_hash,
239 } = self;
240
241 inputs.append(non_snark);
242 inputs.append(pending_coinbase_hash);
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use std::str::FromStr;
249
250 #[cfg(target_family = "wasm")]
251 use wasm_bindgen_test::wasm_bindgen_test as test;
252
253 use super::*;
254
255 #[test]
256 fn test_convert() {
257 let s = r"\\\236\235f\255\200o8\217Rxlmily\194\219\1949\221N\145\180g)\215:'\251W\233";
267 let pending_coinbase_aux = PendingCoinbaseAux::from_ocaml_str(s);
268 assert_eq!(s, pending_coinbase_aux.to_ocaml_str());
269
270 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";
271 let aux_hash = AuxHash::from_ocaml_str(s);
272 assert_eq!(s, aux_hash.to_ocaml_str());
273
274 let a = AuxHash([
278 9, 204, 83, 160, 70, 227, 22, 142, 146, 172, 220, 46, 82, 39, 222, 76, 38, 98, 191,
279 138, 59, 22, 235, 137, 190, 62, 205, 46, 31, 195, 45, 231,
280 ]);
281
282 println!("a={}", a.to_ocaml_str());
283
284 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";
285 assert_eq!(s, a.to_ocaml_str());
286 let aux_hash = AuxHash::from_ocaml_str(s);
287 assert_eq!(s, aux_hash.to_ocaml_str());
288
289 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";
290 let memo = <[u8; 34]>::from_ocaml_str(s);
291 assert_eq!(s, memo.to_ocaml_str());
292
293 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";
299 let pending_coinbase_aux = PendingCoinbaseAux::from_ocaml_str(s);
300 assert_eq!(s, pending_coinbase_aux.to_ocaml_str());
301 }
302
303 #[test]
304 fn test_non_snark_digest() {
305 let non_snark = NonStark {
314 ledger_hash: Fp::from_str(
315 "7213023165825031994332898585791275635753820608093286100176380057570051468967",
316 )
317 .unwrap(),
318 aux_hash: AuxHash::from_ocaml_str(
319 r"T\249\245k\176]TJ\216\183\001\204\177\131\030\244o\178\188\191US\156\192Hi\194P\223\004\000\003",
320 ),
321 pending_coinbase_aux: PendingCoinbaseAux::from_ocaml_str(
322 r"_\236\235f\255\200o8\217Rxlmily\194\219\1949\221N\145\180g)\215:'\251W\233",
323 ),
324 };
325
326 assert_eq!(
327 non_snark.digest().to_ocaml_str(),
328 r"\t\204S\160F\227\022\142\146\172\220.R'\222L&b\191\138;\022\235\137\190>\205.\031\195-\231"
329 );
330
331 let non_snark = NonStark {
340 ledger_hash: Fp::from_str(
341 "18582860218764414485081234471609377222894570081548691702645303871998665679024",
342 )
343 .unwrap(),
344 aux_hash: AuxHash::from_ocaml_str(
345 r"0\136Wg\182DbX\203kLi\212%\199\206\142#\213`L\160bpCB\1413\240\193\171K",
346 ),
347 pending_coinbase_aux: PendingCoinbaseAux::from_ocaml_str(
348 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",
349 ),
350 };
351
352 assert_eq!(
353 non_snark.digest().to_ocaml_str(),
354 r"u~\218kzX\228$\027qG\239\135\255:\143\171\186\011\200P\243\163\135\223T>\017\172\254\1906",
355 );
356 }
357}