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