Skip to main content

vrf_wasm/
encoding.rs

1// Copyright (c) 2022, Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Encodings of binary data such as Base64 and Hex.
5//!
6//! # Example
7//! ```rust
8//! # use vrf_wasm::*;
9//! assert_eq!(Hex::encode("Hello world!"), "48656c6c6f20776f726c6421");
10//! assert_eq!(Hex::encode_with_format("Hello world!"), "0x48656c6c6f20776f726c6421");
11//! assert_eq!(Base64::encode("Hello world!"), "SGVsbG8gd29ybGQh");
12//! assert_eq!(Base58::encode("Hello world!"), "2NEpo7TZRhna7vSvL");
13//! ```
14
15use std::fmt::Debug;
16
17use base64ct::Encoding as _;
18use bech32::{FromBase32, Variant};
19use serde;
20use serde::de::{Deserializer, Error};
21use serde::ser::Serializer;
22use serde::Deserialize;
23use serde::Serialize;
24use serde_with::{DeserializeAs, SerializeAs};
25
26use crate::error::FastCryptoError::InvalidInput;
27use crate::error::{FastCryptoError, FastCryptoResult};
28
29/// Trait representing a general binary-to-string encoding.
30pub trait Encoding {
31    /// Decode this encoding into bytes.
32    fn decode(s: &str) -> FastCryptoResult<Vec<u8>>;
33
34    /// Encode bytes into a string.
35    fn encode<T: AsRef<[u8]>>(data: T) -> String;
36}
37
38/// Implement `DeserializeAs<Vec<u8>>`, `DeserializeAs<[u8; N]>` and `SerializeAs<T: AsRef<[u8]>`
39/// for a type that implements `Encoding`.
40macro_rules! impl_serde_as_for_encoding {
41    ($encoding:ty) => {
42        impl<'de> DeserializeAs<'de, Vec<u8>> for $encoding {
43            fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
44            where
45                D: Deserializer<'de>,
46            {
47                let s = String::deserialize(deserializer)?;
48                Self::decode(&s).map_err(|_| Error::custom("Deserialization failed"))
49            }
50        }
51
52        impl<T> SerializeAs<T> for $encoding
53        where
54            T: AsRef<[u8]>,
55        {
56            fn serialize_as<S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
57            where
58                S: Serializer,
59            {
60                let encoded_string = Self::encode(value);
61                Self(encoded_string).serialize(serializer)
62            }
63        }
64
65        impl<'de, const N: usize> DeserializeAs<'de, [u8; N]> for $encoding {
66            fn deserialize_as<D>(deserializer: D) -> Result<[u8; N], D::Error>
67            where
68                D: Deserializer<'de>,
69            {
70                let value: Vec<u8> = <$encoding>::deserialize_as(deserializer)?;
71                value
72                    .try_into()
73                    .map_err(|_| Error::custom(format!("Invalid array length, expecting {}", N)))
74            }
75        }
76    };
77}
78
79/// Implement `TryFrom<String>` for a type that implements `Encoding`.
80macro_rules! impl_try_from_string {
81    ($encoding:ty) => {
82        impl TryFrom<String> for $encoding {
83            type Error = FastCryptoError;
84            fn try_from(value: String) -> Result<Self, Self::Error> {
85                // Error on invalid encoding
86                <$encoding>::decode(&value)?;
87                Ok(Self(value))
88            }
89        }
90    };
91}
92
93/// Base64 encoding
94#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
95#[serde(try_from = "String")]
96pub struct Base64(String);
97
98impl_serde_as_for_encoding!(Base64);
99impl_try_from_string!(Base64);
100
101impl Base64 {
102    /// Decodes this Base64 encoding to bytes.
103    pub fn to_vec(&self) -> FastCryptoResult<Vec<u8>> {
104        Self::decode(&self.0)
105    }
106    /// Encodes bytes as a Base64.
107    pub fn from_bytes(bytes: &[u8]) -> Self {
108        Self(Self::encode(bytes))
109    }
110    /// Get a string representation of this Base64 encoding.
111    pub fn encoded(&self) -> String {
112        self.0.clone()
113    }
114}
115
116/// Hex string encoding.
117#[derive(Deserialize, Debug, Clone, PartialEq)]
118#[serde(try_from = "String")]
119pub struct Hex(String);
120
121impl TryFrom<String> for Hex {
122    type Error = FastCryptoError;
123    fn try_from(value: String) -> Result<Self, Self::Error> {
124        let s = value.strip_prefix("0x").unwrap_or(&value);
125        Ok(Self(s.to_string()))
126    }
127}
128
129impl Serialize for Hex {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: Serializer,
133    {
134        // Hex strings are serialized with a 0x prefix which differs from the output of `Hex::encode`.
135        String::serialize(&self.encoded_with_format(), serializer)
136    }
137}
138
139impl_serde_as_for_encoding!(Hex);
140
141impl Hex {
142    /// Create a hex encoding from a string.
143    #[cfg(test)]
144    pub fn from_string(s: &str) -> Self {
145        Hex(s.to_string())
146    }
147    /// Decodes this hex encoding to bytes.
148    pub fn to_vec(&self) -> FastCryptoResult<Vec<u8>> {
149        Self::decode(&self.0)
150    }
151    /// Encodes bytes as a hex string.
152    pub fn from_bytes(bytes: &[u8]) -> Self {
153        Self(Self::encode(bytes))
154    }
155    /// Encode bytes as a hex string with a "0x" prefix.
156    pub fn encode_with_format<T: AsRef<[u8]>>(bytes: T) -> String {
157        Self::format(&Self::encode(bytes))
158    }
159    /// Get a string representation of this Hex encoding with a "0x" prefix.
160    pub fn encoded_with_format(&self) -> String {
161        Self::format(&self.0)
162    }
163    /// Add "0x" prefix to a hex string.
164    fn format(hex_string: &str) -> String {
165        format!("0x{}", hex_string)
166    }
167}
168
169/// Decodes a hex string to bytes. Both upper and lower case characters are allowed in the hex string.
170pub fn decode_bytes_hex<T: for<'a> TryFrom<&'a [u8]>>(s: &str) -> FastCryptoResult<T> {
171    let value = Hex::decode(s)?;
172    T::try_from(&value[..]).map_err(|_| InvalidInput)
173}
174
175impl Encoding for Hex {
176    /// Decodes a hex string to bytes. Both upper and lower case characters are accepted, and the
177    /// string may have a "0x" prefix or not.
178    fn decode(s: &str) -> FastCryptoResult<Vec<u8>> {
179        let s = s.strip_prefix("0x").unwrap_or(s);
180        hex::decode(s).map_err(|_| InvalidInput)
181    }
182
183    /// Hex encoding is without "0x" prefix. See `Hex::encode_with_format` for encoding with "0x".
184    fn encode<T: AsRef<[u8]>>(data: T) -> String {
185        hex::encode(data.as_ref())
186    }
187}
188
189impl Encoding for Base64 {
190    fn decode(s: &str) -> FastCryptoResult<Vec<u8>> {
191        base64ct::Base64::decode_vec(s).map_err(|_| InvalidInput)
192    }
193
194    fn encode<T: AsRef<[u8]>>(data: T) -> String {
195        base64ct::Base64::encode_string(data.as_ref())
196    }
197}
198
199#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
200#[serde(try_from = "String")]
201pub struct Base58(String);
202
203impl_serde_as_for_encoding!(Base58);
204impl_try_from_string!(Base58);
205
206impl Encoding for Base58 {
207    fn decode(s: &str) -> FastCryptoResult<Vec<u8>> {
208        bs58::decode(s).into_vec().map_err(|_| InvalidInput)
209    }
210
211    fn encode<T: AsRef<[u8]>>(data: T) -> String {
212        bs58::encode(data).into_string()
213    }
214}
215
216/// Bech32 encoding
217pub struct Bech32;
218
219impl Bech32 {
220    /// Decodes the Bech32 string to bytes, validating the given human readable part (hrp). See spec: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
221    /// # Example:
222    /// ```
223    /// use vrf_wasm::Bech32;
224    /// let bytes = Bech32::decode("split1qqqqsk5gh5","split").unwrap();
225    /// assert_eq!(bytes, vec![0, 0]);
226    /// ```
227    pub fn decode(s: &str, hrp: &str) -> FastCryptoResult<Vec<u8>> {
228        let (parsed, data, variant) = bech32::decode(s).map_err(|_| InvalidInput)?;
229        if parsed != hrp || variant != Variant::Bech32 {
230            Err(InvalidInput)
231        } else {
232            Vec::<u8>::from_base32(&data).map_err(|_| InvalidInput)
233        }
234    }
235
236    /// Encodes bytes into a Bech32 encoded string, with the given human readable part (hrp). See spec: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
237    /// # Example:
238    /// ```
239    /// use vrf_wasm::Bech32;
240    /// let str = Bech32::encode(vec![0, 0],"split").unwrap();
241    /// assert_eq!(str, "split1qqqqsk5gh5".to_string());
242    /// ```
243    pub fn encode<T: AsRef<[u8]>>(data: T, hrp: &str) -> FastCryptoResult<String> {
244        use bech32::ToBase32;
245        bech32::encode(hrp, data.to_base32(), Variant::Bech32).map_err(|_| InvalidInput)
246    }
247}