mina_p2p_messages/
b58.rs

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