mina_p2p_messages/
b58.rs

1//! Base58check encoding/decoding.
2
3use binprot::{BinProtRead, BinProtWrite};
4use binprot_derive::{BinProtRead, BinProtWrite};
5use derive_more::From;
6use malloc_size_of_derive::MallocSizeOf;
7use serde::{Deserialize, Serialize};
8use std::{fmt, marker::PhantomData, str::FromStr};
9
10/// Before encoding, data is prepended with the version byte.
11pub fn encode(b: &[u8], v: u8) -> String {
12    bs58::encode(&b).with_check_version(v).into_string()
13}
14
15/// When decoded, resulting data is checked against the version byte as its
16/// prefix.
17///
18/// Note that the prefix is still the part of the result.
19pub fn decode(s: &str, v: u8) -> Result<Vec<u8>, bs58::decode::Error> {
20    bs58::decode(s).with_check(Some(v)).into_vec()
21}
22
23/// The type that can be converted to base58check representation.
24pub trait ToBase58Check: Sized {
25    type Error;
26
27    /// Produces base58check representation of this value.
28    fn to_base58check(&self) -> Result<String, Self::Error>;
29}
30
31/// The type that can be constructed from base58check representation.
32pub trait FromBase58Check: Sized {
33    type Error;
34
35    /// Constructs this instance from base58check representation.
36    fn from_base58check<T: AsRef<str>>(b58: T) -> Result<Self, Self::Error>;
37}
38
39pub trait Base58CheckVersion {
40    const VERSION_BYTE: u8;
41}
42
43#[derive(Debug, thiserror::Error)]
44pub enum ToBase58CheckError {
45    #[error("Error writing the value to binprot: {0}")]
46    Binprot(#[from] std::io::Error),
47}
48
49impl<T> ToBase58Check for T
50where
51    T: BinProtWrite + Base58CheckVersion,
52{
53    type Error = ToBase58CheckError;
54
55    fn to_base58check(&self) -> Result<String, Self::Error> {
56        let mut binprot = Vec::new();
57        self.binprot_write(&mut binprot)?;
58        Ok(encode(&binprot, Self::VERSION_BYTE))
59    }
60}
61
62#[derive(Debug, thiserror::Error)]
63pub enum FromBase58CheckError {
64    #[error("Error reading the value from binprot: {0}")]
65    Binprot(#[from] binprot::Error),
66    #[error("Error converting base58check to the value: {0}")]
67    Base58(#[from] bs58::decode::Error),
68}
69
70impl<T> FromBase58Check for T
71where
72    T: BinProtRead + Base58CheckVersion,
73{
74    type Error = FromBase58CheckError;
75
76    fn from_base58check<S: AsRef<str>>(b58: S) -> Result<Self, Self::Error> {
77        let binprot = decode(b58.as_ref(), Self::VERSION_BYTE)?;
78        let binable = T::binprot_read(&mut &binprot[..])?;
79        Ok(binable)
80    }
81}
82
83/// Wrapper that uses base58check of binprot serialization for the wrapped type
84/// for human readable serializer.
85#[derive(PartialEq, Eq, PartialOrd, Ord, MallocSizeOf)]
86pub struct Base58CheckOfBinProt<T, U, const V: u8>(T, PhantomData<U>);
87
88impl<T, U, const V: u8> Default for Base58CheckOfBinProt<T, U, V>
89where
90    T: Default,
91{
92    fn default() -> Self {
93        Self(T::default(), Default::default())
94    }
95}
96
97impl<T, U, const V: u8> Clone for Base58CheckOfBinProt<T, U, V>
98where
99    T: Clone,
100{
101    fn clone(&self) -> Self {
102        Self(self.0.clone(), Default::default())
103    }
104}
105
106impl<T, U, const V: u8> From<T> for Base58CheckOfBinProt<T, U, V> {
107    fn from(source: T) -> Self {
108        Base58CheckOfBinProt(source, Default::default())
109    }
110}
111
112impl<T, U, const V: u8> std::ops::Deref for Base58CheckOfBinProt<T, U, V> {
113    type Target = T;
114
115    fn deref(&self) -> &Self::Target {
116        &self.0
117    }
118}
119
120impl<T, U, const V: u8> Base58CheckOfBinProt<T, U, V> {
121    pub fn into_inner(self) -> T {
122        self.0
123    }
124
125    pub fn inner(&self) -> &T {
126        &self.0
127    }
128}
129
130impl<T, U, const V: u8> BinProtRead for Base58CheckOfBinProt<T, U, V>
131where
132    T: BinProtRead,
133{
134    fn binprot_read<R: std::io::Read + ?Sized>(r: &mut R) -> Result<Self, binprot::Error>
135    where
136        Self: Sized,
137    {
138        T::binprot_read(r).map(|v| Self(v, Default::default()))
139    }
140}
141
142impl<T, U, const V: u8> BinProtWrite for Base58CheckOfBinProt<T, U, V>
143where
144    T: BinProtWrite,
145{
146    fn binprot_write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
147        self.0.binprot_write(w)
148    }
149}
150
151impl<T, U, const V: u8> fmt::Display for Base58CheckOfBinProt<T, U, V>
152where
153    T: Clone,
154    U: From<T> + BinProtWrite,
155{
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        let mut binprot = Vec::new();
158        let from = U::from(self.0.clone());
159        from.binprot_write(&mut binprot).map_err(|e| {
160            serde::ser::Error::custom(format!("Failed to convert to base58check: {e}"))
161        })?;
162        let encoded = encode(&binprot, V);
163
164        write!(f, "{}", encoded)
165    }
166}
167
168impl<T, U, const V: u8> fmt::Debug for Base58CheckOfBinProt<T, U, V>
169where
170    T: Serialize + Clone,
171    U: From<T> + BinProtWrite,
172{
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(f, "{}", self)
175    }
176}
177
178impl<T, U, const V: u8> FromStr for Base58CheckOfBinProt<T, U, V>
179where
180    U: BinProtRead,
181    T: From<U>,
182{
183    type Err = FromBase58CheckError;
184
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        let binprot = decode(s, V)?;
187        let binable = U::binprot_read(&mut &binprot[1..])?;
188        Ok(T::from(binable).into())
189    }
190}
191
192impl<T, U, const V: u8> Serialize for Base58CheckOfBinProt<T, U, V>
193where
194    T: Clone + Serialize,
195    U: From<T> + BinProtWrite + std::fmt::Debug,
196{
197    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198    where
199        S: serde::Serializer,
200    {
201        if serializer.is_human_readable() {
202            serializer.serialize_str(&self.to_string())
203        } else {
204            self.0.serialize(serializer)
205        }
206    }
207}
208
209impl<'de, T, U, const V: u8> serde::Deserialize<'de> for Base58CheckOfBinProt<T, U, V>
210where
211    T: From<U> + Deserialize<'de>,
212    U: BinProtRead,
213{
214    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215    where
216        D: serde::Deserializer<'de>,
217    {
218        if deserializer.is_human_readable() {
219            let b58: String = Deserialize::deserialize(deserializer)?;
220            Ok(b58.parse().map_err(serde::de::Error::custom)?)
221        } else {
222            T::deserialize(deserializer).map(|v| Self(v, Default::default()))
223        }
224    }
225}
226
227/// Wrapper that uses base58check of byte representation for the wrapped type
228/// for human readable serializer.
229#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, From, BinProtRead, BinProtWrite)]
230pub struct Base58CheckOfBytes<T, const V: u8>(T);
231
232impl<T, const V: u8> Base58CheckOfBytes<T, V> {
233    pub fn into_inner(self) -> T {
234        self.0
235    }
236}
237
238impl<T, const V: u8> std::ops::Deref for Base58CheckOfBytes<T, V> {
239    type Target = T;
240
241    fn deref(&self) -> &Self::Target {
242        &self.0
243    }
244}
245
246impl<T, const V: u8> Serialize for Base58CheckOfBytes<T, V>
247where
248    T: Serialize + AsRef<[u8]> + std::fmt::Debug,
249{
250    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251    where
252        S: serde::Serializer,
253    {
254        if serializer.is_human_readable() {
255            let encoded = encode(self.0.as_ref(), V);
256            serializer.serialize_str(&encoded)
257        } else {
258            self.0.serialize(serializer)
259        }
260    }
261}
262
263impl<'de, T, const V: u8> serde::Deserialize<'de> for Base58CheckOfBytes<T, V>
264where
265    T: for<'a> From<&'a [u8]> + Deserialize<'de>,
266{
267    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
268    where
269        D: serde::Deserializer<'de>,
270    {
271        if deserializer.is_human_readable() {
272            let b58: String = Deserialize::deserialize(deserializer)?;
273            let bytes = decode(&b58, V).map_err(|e| {
274                serde::de::Error::custom(format!("Failed to construct from base58check: {e}"))
275            })?;
276            Ok(T::from(&bytes[1..]))
277        } else {
278            T::deserialize(deserializer)
279        }
280        .map(Self)
281    }
282}
283
284impl<T: mina_hasher::Hashable, U, const V: u8> mina_hasher::Hashable
285    for Base58CheckOfBinProt<T, U, V>
286{
287    type D = T::D;
288
289    fn to_roinput(&self) -> mina_hasher::ROInput {
290        self.0.to_roinput()
291    }
292
293    fn domain_string(domain_param: Self::D) -> Option<String> {
294        T::domain_string(domain_param)
295    }
296}
297
298impl<T: mina_hasher::Hashable, const V: u8> mina_hasher::Hashable for Base58CheckOfBytes<T, V> {
299    type D = T::D;
300
301    fn to_roinput(&self) -> mina_hasher::ROInput {
302        self.0.to_roinput()
303    }
304
305    fn domain_string(domain_param: Self::D) -> Option<String> {
306        T::domain_string(domain_param)
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::ToBase58Check;
313    use binprot::BinProtRead;
314    use binprot_derive::{BinProtRead, BinProtWrite};
315    use serde::{Deserialize, Serialize};
316
317    use crate::{
318        b58::{Base58CheckOfBinProt, Base58CheckVersion},
319        bigint::BigInt,
320        versioned::Versioned,
321    };
322
323    macro_rules! base58tests {
324        ($($name:ident($bytes:expr, $version:expr, $b58:expr $(,)? )),* $(,)?) => {
325            $(
326                #[test]
327                fn $name() {
328                    let bytes = hex::decode($bytes).unwrap();
329
330                    let encoded = super::encode(&bytes, $version);
331                    assert_eq!(&encoded, $b58);
332
333                    let decoded = super::decode($b58, $version).unwrap();
334                    assert_eq!(decoded[0], $version);
335                    assert_eq!(&decoded[1..], &bytes);
336                }
337            )*
338        };
339    }
340
341    base58tests!(
342        genesis_state_hash(
343            "01fc630629c6a1a237a3dc1d95fd54fbf9cca062486e9f57852ebc64e4042ceb3d",
344            0x10,
345            "3NLx3eBDTvYmP27bUmYANzmhjL5rGe36nGW6N5XhGcuStF6Zv7ZD"
346        ),
347        prev_state_hash(
348            "019b4f7c30bcf6c883c388097db490bfeeae5a1d36eb4b593af65e3511db4fc432",
349            0x10,
350            "3NLDHxUsL6Ehujf6x2AT6CXrpRjeY1rw9e93QJfJUAD3P6HVVtcA"
351        ),
352        state_hash(
353            "018d67aadd018581a812623915b13d5c3a6da7dfe8a195172d9bbd206810bc2329",
354            0x10,
355            "3NL7AkynW6hbDrhHTAht1GLG563Fo9fdcEQk1zEyy5XedC6aZTeB",
356        ),
357        ledger_hash(
358            "01636f5b2d67278e17bc4343c7c23fb4991f8cf0bbbfd8558615b124d5d6254801",
359            0x05,
360            "jwrPvAMUNo3EKT2puUk5Fxz6B7apRAoKNTGpAA49t3TRSfzvdrL"
361        ),
362        address(
363            "01013c2b5b48c22dc8b8c9d2c9d76a2ceaaf02beabb364301726c3f8e989653af51300",
364            0xcb,
365            "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk"
366        )
367    );
368
369    fn bigint(s: &str) -> BigInt {
370        let bytes = hex::decode(s).unwrap();
371        BigInt::binprot_read(&mut &bytes[..]).unwrap()
372    }
373
374    #[test]
375    fn binable_base58check() {
376        #[derive(Clone, BinProtRead, BinProtWrite)]
377        struct Binable(BigInt);
378
379        impl Base58CheckVersion for Binable {
380            const VERSION_BYTE: u8 = 0x10;
381        }
382
383        let b = Binable(bigint(
384            "fc630629c6a1a237a3dc1d95fd54fbf9cca062486e9f57852ebc64e4042ceb3d",
385        ));
386        let b58c = b.to_base58check().unwrap();
387        assert_eq!(&b58c, "a24htZ9FGiBmD2D9vBdX4uN7aeghG7K1852UEVrsgACMBHnL58")
388    }
389
390    #[test]
391    fn serde_as_base58check() {
392        #[derive(Clone, Debug, BinProtRead, BinProtWrite, Serialize, Deserialize, PartialEq)]
393        struct BinableV1(BigInt);
394
395        impl Base58CheckVersion for BinableV1 {
396            const VERSION_BYTE: u8 = 0x10;
397        }
398
399        #[derive(Clone, Debug, BinProtRead, BinProtWrite, Serialize, Deserialize, PartialEq)]
400        struct BinableV2(BigInt);
401
402        impl From<BinableV2> for Versioned<BinableV1, 1> {
403            fn from(v2: BinableV2) -> Self {
404                BinableV1(v2.0).into()
405            }
406        }
407
408        impl From<Versioned<BinableV1, 1>> for BinableV2 {
409            fn from(v1: Versioned<BinableV1, 1>) -> Self {
410                BinableV2(v1.into_inner().0)
411            }
412        }
413
414        #[derive(Debug, Serialize, Deserialize, PartialEq)]
415        struct Foo {
416            b: Base58CheckOfBinProt<BinableV2, Versioned<BinableV1, 1>, 0x10>,
417        }
418
419        let b = Foo {
420            b: BinableV2(bigint(
421                "fc630629c6a1a237a3dc1d95fd54fbf9cca062486e9f57852ebc64e4042ceb3d",
422            ))
423            .into(),
424        };
425        assert_eq!(
426            &serde_json::to_string(&b).unwrap(),
427            r#"{"b":"3NLx3eBDTvYmP27bUmYANzmhjL5rGe36nGW6N5XhGcuStF6Zv7ZD"}"#
428        );
429        assert_eq!(
430            serde_json::from_str::<Foo>(
431                r#"{"b":"3NLx3eBDTvYmP27bUmYANzmhjL5rGe36nGW6N5XhGcuStF6Zv7ZD"}"#
432            )
433            .unwrap(),
434            b
435        );
436    }
437}