Skip to main content

datex_crypto_facade/
crypto.rs

1use alloc::{boxed::Box, string::String, vec::Vec};
2use bs58;
3use core::{pin::Pin, result::Result};
4
5use crate::error::*;
6
7pub type AsyncCryptoResult<'a, T, E> =
8    Pin<Box<dyn Future<Output = Result<T, E>> + 'a>>;
9
10pub trait Crypto: Send + Sync {
11    /// Generate a new UUID (version 4). Returns the UUID as a string.
12    fn create_uuid() -> String;
13
14    /// Generate `length` random bytes.
15    fn random_bytes(length: usize) -> Vec<u8>;
16
17    // Hash
18    type Sha256Error: core::fmt::Debug + Send + Sync + 'static =
19        crate::error::Sha256Error;
20
21    /// Compute the SHA-256 hash of the input data. Returns the 32-byte hash.
22    fn hash_sha256<'a>(
23        to_digest: &'a [u8],
24    ) -> AsyncCryptoResult<'a, [u8; 32], Self::Sha256Error>;
25
26    /// Decode the given Base58 string into a 32-byte array.
27    /// Returns an error if the input is not valid Base58 or does not decode to exactly 32 bytes.
28    fn dec_b58_32(data: &str) -> Result<[u8; 32], B58DecodeError> {
29        let bytes = bs58::decode(data)
30            .into_vec()
31            .map_err(|_| B58DecodeError::InvalidBase58)?;
32
33        if bytes.len() != 32 {
34            return Err(B58DecodeError::WrongLength {
35                expected: 32,
36                got: bytes.len(),
37            });
38        }
39
40        let mut out = [0u8; 32];
41        out.copy_from_slice(&bytes);
42        Ok(out)
43    }
44
45    // HKDF
46    type HkdfError: core::fmt::Debug + Send + Sync + 'static =
47        crate::error::HkdfError;
48
49    /// Derive a 32-byte key from the input keying material and salt using HKDF-SHA256.
50    fn hkdf_sha256<'a>(
51        ikm: &'a [u8],
52        salt: &'a [u8],
53    ) -> AsyncCryptoResult<'a, [u8; 32], Self::HkdfError>;
54
55    // Ed25519
56    type Ed25519GenError: core::fmt::Debug + Send + Sync + 'static =
57        crate::error::Ed25519GenError;
58
59    /// Generate a new Ed25519 key pair. Returns the public and private keys.
60    fn gen_ed25519<'a>()
61    -> AsyncCryptoResult<'a, (Vec<u8>, Vec<u8>), Self::Ed25519GenError>;
62
63    type Ed25519SignError: core::fmt::Debug + Send + Sync + 'static =
64        crate::error::Ed25519SignError;
65
66    /// Sign the given data using the provided Ed25519 private key. Returns the signature.
67    fn sig_ed25519<'a>(
68        pri_key: &'a [u8],
69        data: &'a [u8],
70    ) -> AsyncCryptoResult<'a, [u8; 64], Self::Ed25519SignError>;
71
72    // Verify should not “error” for signature mismatch, only for malformed inputs
73    type Ed25519VerifyError: core::fmt::Debug + Send + Sync + 'static =
74        crate::error::Ed25519VerifyError;
75
76    /// Verify the given Ed25519 signature against the provided public key and data.
77    /// Returns true if the signature is valid, false if it is invalid.
78    fn ver_ed25519<'a>(
79        pub_key: &'a [u8],
80        sig: &'a [u8],
81        data: &'a [u8],
82    ) -> AsyncCryptoResult<'a, bool, Self::Ed25519VerifyError>;
83
84    // AES-CTR (generally only invalid input / backend unavailable)
85    type AesCtrError: core::fmt::Debug + Send + Sync + 'static =
86        crate::error::AesCtrError;
87
88    /// Encrypt the plaintext using AES-256 in CTR mode with the given key and IV. Returns the ciphertext.
89    fn aes_ctr_encrypt<'a>(
90        key: &'a [u8; 32],
91        iv: &'a [u8; 16],
92        plaintext: &'a [u8],
93    ) -> AsyncCryptoResult<'a, Vec<u8>, Self::AesCtrError>;
94
95    /// Decrypt the ciphertext using AES-256 in CTR mode with the given key and IV. Returns the plaintext.
96    fn aes_ctr_decrypt<'a>(
97        key: &'a [u8; 32],
98        iv: &'a [u8; 16],
99        cipher: &'a [u8],
100    ) -> AsyncCryptoResult<'a, Vec<u8>, Self::AesCtrError>;
101
102    // RFC3394 wrap/unwrap: unwrap has a unique failure mode (integrity fail)
103    type KeyWrapError: core::fmt::Debug + Send + Sync + 'static =
104        crate::error::KeyWrapError;
105
106    /// Wrap the given 32-byte key using the provided 32-byte KEK with the RFC3394 algorithm. Returns the wrapped key.
107    fn key_wrap_rfc3394<'a>(
108        kek: &'a [u8; 32],
109        key_to_wrap: &'a [u8; 32],
110    ) -> AsyncCryptoResult<'a, [u8; 40], Self::KeyWrapError>;
111
112    type KeyUnwrapError: core::fmt::Debug + Send + Sync + 'static =
113        crate::error::KeyUnwrapError;
114
115    /// Unwrap the given 40-byte wrapped key using the provided 32-byte KEK with the RFC3394 algorithm.
116    /// Returns the unwrapped key.
117    fn key_unwrap_rfc3394<'a>(
118        kek: &'a [u8; 32],
119        wrapped: &'a [u8; 40],
120    ) -> AsyncCryptoResult<'a, [u8; 32], Self::KeyUnwrapError>;
121
122    // X25519
123    type X25519GenError: core::fmt::Debug + Send + Sync + 'static =
124        crate::error::X25519GenError;
125
126    /// Generate a new X25519 key pair. Returns the public key as a base58 string and the private key as bytes.
127    fn gen_x25519<'a>()
128    -> AsyncCryptoResult<'a, ([u8; 44], [u8; 48]), Self::X25519GenError>;
129
130    type X25519DeriveError: core::fmt::Debug + Send + Sync + 'static =
131        crate::error::X25519DeriveError;
132
133    /// Derive a shared secret using the X25519 key agreement protocol with the given private key and peer's public key.
134    /// Returns the derived 32-byte shared secret.
135    fn derive_x25519<'a>(
136        pri_key: &'a [u8; 48],
137        peer_pub: &'a [u8; 44],
138    ) -> AsyncCryptoResult<'a, [u8; 32], Self::X25519DeriveError>;
139
140    // Base58
141    /// Encode the given bytes into a Base58 string.
142    fn enc_b58(data: &[u8]) -> String {
143        bs58::encode(data).into_string()
144    }
145
146    /// Decode the given Base58 string into bytes. Returns an error if the input is not valid Base58.
147    fn dec_b58(data: &str) -> Result<Vec<u8>, B58DecodeError> {
148        bs58::decode(data)
149            .into_vec()
150            .map_err(|_| B58DecodeError::InvalidBase58)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    // Dummy implementation of Crypto for testing the default impls.
159    struct T;
160
161    impl Crypto for T {
162        fn create_uuid() -> String {
163            // not used
164            String::new()
165        }
166
167        fn random_bytes(length: usize) -> Vec<u8> {
168            unimplemented!()
169        }
170
171        fn hash_sha256<'a>(
172            _to_digest: &'a [u8],
173        ) -> AsyncCryptoResult<'a, [u8; 32], Self::Sha256Error> {
174            unimplemented!()
175        }
176
177        fn hkdf_sha256<'a>(
178            _ikm: &'a [u8],
179            _salt: &'a [u8],
180        ) -> AsyncCryptoResult<'a, [u8; 32], Self::HkdfError> {
181            unimplemented!()
182        }
183
184        fn gen_ed25519<'a>()
185        -> AsyncCryptoResult<'a, (Vec<u8>, Vec<u8>), Self::Ed25519GenError>
186        {
187            unimplemented!()
188        }
189
190        fn sig_ed25519<'a>(
191            _pri_key: &'a [u8],
192            _data: &'a [u8],
193        ) -> AsyncCryptoResult<'a, [u8; 64], Self::Ed25519SignError> {
194            unimplemented!()
195        }
196
197        fn ver_ed25519<'a>(
198            _pub_key: &'a [u8],
199            _sig: &'a [u8],
200            _data: &'a [u8],
201        ) -> AsyncCryptoResult<'a, bool, Self::Ed25519VerifyError> {
202            unimplemented!()
203        }
204
205        fn aes_ctr_encrypt<'a>(
206            _key: &'a [u8; 32],
207            _iv: &'a [u8; 16],
208            _plaintext: &'a [u8],
209        ) -> AsyncCryptoResult<'a, Vec<u8>, Self::AesCtrError> {
210            unimplemented!()
211        }
212
213        fn aes_ctr_decrypt<'a>(
214            _key: &'a [u8; 32],
215            _iv: &'a [u8; 16],
216            _cipher: &'a [u8],
217        ) -> AsyncCryptoResult<'a, Vec<u8>, Self::AesCtrError> {
218            unimplemented!()
219        }
220
221        fn key_wrap_rfc3394<'a>(
222            _kek: &'a [u8; 32],
223            _key_to_wrap: &'a [u8; 32],
224        ) -> AsyncCryptoResult<'a, [u8; 40], Self::KeyWrapError> {
225            unimplemented!()
226        }
227
228        fn key_unwrap_rfc3394<'a>(
229            _kek: &'a [u8; 32],
230            _wrapped: &'a [u8; 40],
231        ) -> AsyncCryptoResult<'a, [u8; 32], Self::KeyUnwrapError> {
232            unimplemented!()
233        }
234
235        fn gen_x25519<'a>()
236        -> AsyncCryptoResult<'a, ([u8; 44], [u8; 48]), Self::X25519GenError>
237        {
238            unimplemented!()
239        }
240
241        fn derive_x25519<'a>(
242            _pri_key: &'a [u8; 48],
243            _peer_pub: &'a [u8; 44],
244        ) -> AsyncCryptoResult<'a, [u8; 32], Self::X25519DeriveError> {
245            unimplemented!()
246        }
247    }
248
249    #[test]
250    fn b58_roundtrip_arbitrary_bytes() {
251        let input = b"hello DATEX";
252        let encoded = T::enc_b58(input);
253        let decoded = T::dec_b58(&encoded).unwrap();
254        assert_eq!(input, &decoded[..]);
255    }
256
257    #[test]
258    fn b58_roundtrip_empty() {
259        let input: &[u8] = b"";
260        let encoded = T::enc_b58(input);
261        let decoded = T::dec_b58(&encoded).unwrap();
262        assert_eq!(input, &decoded[..]);
263    }
264
265    #[test]
266    fn b58_roundtrip_32_bytes() {
267        let mut input = [0u8; 32];
268        for (i, b) in input.iter_mut().enumerate() {
269            *b = (i as u8).wrapping_mul(7).wrapping_add(3);
270        }
271
272        let encoded = T::enc_b58(&input);
273        let decoded = T::dec_b58_32(&encoded).unwrap();
274        assert_eq!(input, decoded);
275    }
276
277    #[test]
278    fn b58_dec_invalid_base58_errors() {
279        // Contains characters not in base58 alphabet
280        let bad = "0OIl";
281        let err = T::dec_b58(bad).unwrap_err();
282        assert_eq!(err, B58DecodeError::InvalidBase58);
283    }
284
285    #[test]
286    fn b58_dec32_wrong_length_errors() {
287        // "2g" decodes to a single byte [0x61] ("a"), not 32 bytes.
288        let s = "2g";
289        let err = T::dec_b58_32(s).unwrap_err();
290        assert_eq!(
291            err,
292            B58DecodeError::WrongLength {
293                expected: 32,
294                got: 1
295            }
296        );
297    }
298}