did_utils/crypto/
x25519.rs

1use multibase::Base::Base58Btc;
2use x25519_dalek::{PublicKey, StaticSecret};
3
4use super::{
5    alg::Algorithm,
6    errors::Error,
7    traits::{Generate, KeyMaterial, ToMultikey, BYTES_LENGTH_32, ECDH},
8    utils::{clone_slice_to_array, generate_seed},
9    AsymmetricKey,
10};
11
12pub type X25519KeyPair = AsymmetricKey<PublicKey, StaticSecret>;
13
14impl std::fmt::Debug for X25519KeyPair {
15    /// Returns a string representation of the public key.
16    ///
17    /// This function is used to implement the `fmt::Debug` trait.
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        f.write_fmt(format_args!("{:?}", self.public_key))
20    }
21}
22
23impl KeyMaterial for X25519KeyPair {
24    /// Returns the bytes of the public key.
25    ///
26    /// # Returns
27    ///
28    /// A `Result` containing the bytes of the public key or an `Error`.
29    fn public_key_bytes(&self) -> Result<[u8; BYTES_LENGTH_32], Error> {
30        Ok(clone_slice_to_array(self.public_key.as_bytes()))
31    }
32
33    /// Returns the bytes of the private key.
34    ///
35    /// # Returns
36    ///
37    /// A `Result` containing the bytes of the private key or an `Error`.
38    fn private_key_bytes(&self) -> Result<[u8; BYTES_LENGTH_32], Error> {
39        match &self.secret_key {
40            Some(sk) => Ok(clone_slice_to_array(sk.as_bytes())),
41            None => Err(Error::InvalidSecretKey),
42        }
43    }
44}
45
46impl Generate for X25519KeyPair {
47    /// Generates a new X25519 key pair.
48    ///
49    /// If the initial seed is empty or invalid, a random seed will be generated.
50    ///
51    /// # Arguments
52    ///
53    /// * `seed` - The initial seed to use, or empty if none.
54    ///
55    /// # Returns
56    ///
57    /// A new `X25519KeyPair` instance.
58    fn new() -> Result<X25519KeyPair, Error> {
59        Self::new_with_seed(vec![].as_slice())
60    }
61
62    /// Generates a new X25519 key pair with a seed.
63    ///
64    /// If the seed is empty or invalid, generates a new seed.
65    ///
66    /// # Arguments
67    ///
68    /// * `seed` - The initial seed to use.
69    ///
70    /// # Returns
71    ///
72    /// A new `X25519KeyPair` instance.
73    fn new_with_seed(seed: &[u8]) -> Result<X25519KeyPair, Error> {
74        match generate_seed(seed) {
75            Ok(secret_seed) => {
76                let sk = StaticSecret::from(secret_seed);
77                Ok(X25519KeyPair {
78                    public_key: PublicKey::from(&sk),
79                    secret_key: Some(sk),
80                })
81            }
82            Err(_) => Err(Error::InvalidSeed),
83        }
84    }
85
86    /// Creates a new `X25519KeyPair` from a public key.
87    ///
88    /// # Arguments
89    ///
90    /// * `public_key` - The bytes of the public key.
91    ///
92    /// # Returns
93    ///
94    /// A new `X25519KeyPair` instance.
95    fn from_public_key(public_key: &[u8; BYTES_LENGTH_32]) -> Result<X25519KeyPair, Error> {
96        match public_key.len() {
97            BYTES_LENGTH_32 => {
98                let pk = clone_slice_to_array(public_key);
99                Ok(X25519KeyPair {
100                    public_key: PublicKey::from(pk),
101                    secret_key: None,
102                })
103            }
104            _ => Err(Error::InvalidKeyLength),
105        }
106    }
107
108    /// Creates a new `X25519KeyPair` from a secret key.
109    ///
110    /// # Arguments
111    ///
112    /// * `secret_key` - The bytes of the secret key.
113    ///
114    /// # Returns
115    ///
116    /// A new `X25519KeyPair` instance.
117    fn from_secret_key(secret_key: &[u8; BYTES_LENGTH_32]) -> Result<X25519KeyPair, Error> {
118        match secret_key.len() {
119            BYTES_LENGTH_32 => {
120                let sk_bytes = clone_slice_to_array(secret_key);
121                let sk = StaticSecret::from(sk_bytes);
122                Ok(X25519KeyPair {
123                    public_key: PublicKey::from(&sk),
124                    secret_key: Some(sk),
125                })
126            }
127            _ => Err(Error::InvalidKeyLength),
128        }
129    }
130}
131
132impl ECDH for X25519KeyPair {
133    /// Performs a key exchange using the Diffie-Hellman algorithm.
134    ///
135    /// # Arguments
136    ///
137    /// * `key` - The public key of the other party.
138    ///
139    /// # Returns
140    ///
141    /// An optional vector of bytes representing the shared secret.
142    /// If the secret key is not available, returns `None`.
143    fn key_exchange(&self, key: &Self) -> Option<Vec<u8>> {
144        (self.secret_key).as_ref().map(|x| x.diffie_hellman(&key.public_key).as_bytes().to_vec())
145    }
146}
147
148impl ToMultikey for X25519KeyPair {
149    fn to_multikey(&self) -> String {
150        let prefix = &Algorithm::X25519.muticodec_prefix();
151        let bytes = &self.public_key.as_bytes()[..];
152        multibase::encode(Base58Btc, [prefix, bytes].concat())
153    }
154}
155
156#[cfg(test)]
157pub mod tests {
158    // use ed25519_dalek::{Signature, Verifier};
159
160    use crate::jwk::Jwk;
161    use x25519_dalek::{EphemeralSecret, PublicKey};
162
163    use super::*;
164    use crate::crypto::{
165        traits::{Generate, KeyMaterial, BYTES_LENGTH_32, ECDH},
166        utils::clone_slice_to_array,
167    };
168
169    // A test to create a new X25519KeyPair and check that bytes of both private and public key from
170    // key material is 32 bytes long.
171    #[test]
172    fn test_new() {
173        let keypair = X25519KeyPair::new().unwrap();
174        assert_eq!(keypair.public_key_bytes().unwrap().len(), BYTES_LENGTH_32);
175        assert_eq!(keypair.private_key_bytes().unwrap().len(), BYTES_LENGTH_32);
176    }
177
178    // Generate a new X25519KeyPair with a seed and check that bytes of both private and public key
179    // are equals to the given bytes pub_key_hex and pri_key_hex.
180    #[test]
181    fn test_new_with_seed() {
182        // generate seed bytes from the the string "Sample seed bytes of thirtytwo!b"
183        // Beware that you need a seed of 32 bytes to produce the deterministic key pair.
184        let my_string = String::from("Sample seed bytes of thirtytwo!b");
185        let seed: &[u8] = my_string.as_bytes();
186        let keypair = X25519KeyPair::new_with_seed(seed).unwrap();
187        let pub_key_hex = hex::encode(keypair.public_key_bytes().unwrap());
188        let pri_key_hex = hex::encode(keypair.private_key_bytes().unwrap());
189
190        assert_eq!(pub_key_hex, "2879534e09045c99580051db0cc7c0eac622a649b55893798fb62159f4134159");
191        assert_eq!(pri_key_hex, "53616d706c652073656564206279746573206f662074686972747974776f2162");
192    }
193
194    // Creat a test that:
195    // - Generate a key pair at the recipient side
196    // - Encrypt the content of the file wiht the public key
197    // - Use the secret key to decrypt the encrypted content.
198    #[test]
199    fn test_encrypt_decrypt() {
200        // === Recipient ===
201        // Generate a static secret keypair for the recipient
202        let decryption_keypair_at_recipient = X25519KeyPair::new().unwrap();
203        let encryption_public_key_bytes_at_recipient = decryption_keypair_at_recipient.public_key_bytes().unwrap();
204
205        // === Sender ===
206        let encryption_public_key_bytes_at_sender = clone_slice_to_array(&encryption_public_key_bytes_at_recipient);
207        // Generate an ephemeral secret keypair for the sender
208        let eph_secret_at_sender = EphemeralSecret::random();
209        // Store the ephemeral public key for transport in the header of message
210        let eph_public_key_in_transport = PublicKey::from(&eph_secret_at_sender);
211        // generate shared secret for use in symmetric encryption
212        let encryption_public_key_at_sender = X25519KeyPair::from_public_key(&encryption_public_key_bytes_at_sender).unwrap();
213        let shared_secret_at_sender = &eph_secret_at_sender.diffie_hellman(&encryption_public_key_at_sender.public_key);
214        // produce the key for use in symmetric encryption
215        let symmetric_key_at_sender = shared_secret_at_sender.as_bytes();
216
217        // === In Transport ===
218        // Cyper message encrypted with the symmetric key
219        // eph_public_key_in_transport
220
221        // === Recipient ===
222        // Construct EphPublicKey from message header
223        // let eph_secret_at_recipient = EphemeralSecret::from(&eph_public_key);
224        let shared_secret_at_recipient = decryption_keypair_at_recipient
225            .secret_key
226            .as_ref()
227            .unwrap()
228            .diffie_hellman(&eph_public_key_in_transport);
229
230        let eph_public_key_at_recipient = X25519KeyPair::from_public_key(eph_public_key_in_transport.as_bytes()).unwrap();
231        decryption_keypair_at_recipient.key_exchange(&eph_public_key_at_recipient);
232        let symmetric_key_at_recipient = shared_secret_at_recipient.as_bytes();
233
234        // Both equals assumes that encrypted payload will be successfuly decrypted.
235        assert_eq!(symmetric_key_at_sender, symmetric_key_at_recipient);
236    }
237
238    #[test]
239    fn test_x25519_keypair_to_multikey() {
240        let jwk: Jwk = serde_json::from_str(
241            r#"{
242                "kty": "OKP",
243                "crv": "X25519",
244                "x": "A2gufB762KKDkbTX0usDbekRJ-_PPBeVhc2gNgjpswU"
245            }"#,
246        )
247        .unwrap();
248
249        let keypair: X25519KeyPair = jwk.try_into().unwrap();
250        let multikey = keypair.to_multikey();
251
252        assert_eq!(&multikey, "z6LSbuUXWSgPfpiDBjUK6E7yiCKMN2eKJsXn5b55ZgqGz6Mr");
253    }
254}