Skip to main content

nym_sphinx_addressing/
clients.rs

1// This is still not an ideal home for this struct, because it's not an
2// universal nymsphinx addressing method, however, it needs to be
3// accessible by both desktop and webassembly client (it's more
4// of a helper/utils structure, because before it reaches the gateway
5// it's already destructed).
6
7use crate::nodes::{NODE_IDENTITY_SIZE, NodeIdentity};
8use nym_crypto::asymmetric::{ed25519, x25519};
9use nym_sphinx_types::Destination;
10use serde::de::{Error as SerdeError, SeqAccess, Unexpected, Visitor};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use std::fmt::{self, Formatter};
13use std::str::FromStr;
14use thiserror::Error;
15
16// Not entirely sure whether this is the correct place for those, but let's see how it's going
17// to work out
18pub type ClientEncryptionKey = x25519::PublicKey;
19const CLIENT_ENCRYPTION_KEY_SIZE: usize = x25519::PUBLIC_KEY_SIZE;
20
21pub type ClientIdentity = ed25519::PublicKey;
22const CLIENT_IDENTITY_SIZE: usize = ed25519::PUBLIC_KEY_LENGTH;
23
24pub type RecipientBytes = [u8; Recipient::LEN];
25
26#[derive(Debug, Error)]
27pub enum RecipientFormattingError {
28    #[error("recipient is malformed - {reason} ")]
29    MalformedRecipientError { reason: String },
30
31    #[error("recipient's identity key is malformed: {0}")]
32    MalformedIdentityError(ed25519::Ed25519RecoveryError),
33
34    #[error("recipient's encryption key is malformed: {0}")]
35    MalformedEncryptionKeyError(#[from] x25519::KeyRecoveryError),
36
37    #[error("recipient gateway's identity key is malformed: {0}")]
38    MalformedGatewayError(ed25519::Ed25519RecoveryError),
39}
40
41// TODO: this should a different home... somewhere, but where?
42#[derive(Clone, Copy, PartialEq, Eq)]
43pub struct Recipient {
44    client_identity: ClientIdentity,
45    client_encryption_key: ClientEncryptionKey,
46    gateway: NodeIdentity,
47}
48
49// Serialize + Deserialize is not really used anymore (it was for a CBOR experiment)
50// however, if we decided we needed it again, it's already here
51impl Serialize for Recipient {
52    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
53    where
54        S: Serializer,
55    {
56        serializer.serialize_bytes(&self.to_bytes())
57    }
58}
59
60impl<'de> Deserialize<'de> for Recipient {
61    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
62    where
63        D: Deserializer<'de>,
64    {
65        struct RecipientVisitor;
66
67        impl<'de> Visitor<'de> for RecipientVisitor {
68            type Value = Recipient;
69
70            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
71                write!(
72                    formatter,
73                    "A recipient is 96-byte sequence containing two ed25519 public keys and one x25519 public key all in compressed forms."
74                )
75            }
76
77            fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
78            where
79                E: SerdeError,
80            {
81                if bytes.len() != Recipient::LEN {
82                    return Err(SerdeError::invalid_length(bytes.len(), &self));
83                }
84
85                let mut recipient_bytes = [0u8; Recipient::LEN];
86                // this shouldn't panic as we just checked for length
87                recipient_bytes.copy_from_slice(bytes);
88
89                Recipient::try_from_bytes(recipient_bytes).map_err(|_| {
90                    SerdeError::invalid_value(
91                        Unexpected::Other("At least one of the curve points was malformed"),
92                        &self,
93                    )
94                })
95            }
96
97            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
98            where
99                A: SeqAccess<'de>,
100            {
101                // if we know the size hint, check if it matches expectation,
102                // otherwise return an error
103                if let Some(size_hint) = seq.size_hint()
104                    && size_hint != Recipient::LEN
105                {
106                    return Err(SerdeError::invalid_length(size_hint, &self));
107                }
108
109                let mut recipient_bytes = [0u8; Recipient::LEN];
110
111                // clippy's suggestion is completely wrong and it iterates wrong sequence
112                #[allow(clippy::needless_range_loop)]
113                for i in 0..Recipient::LEN {
114                    let Some(elem) = seq.next_element::<u8>()? else {
115                        return Err(SerdeError::invalid_length(i + 1, &self));
116                    };
117                    recipient_bytes[i] = elem;
118                }
119
120                // make sure there are no trailing bytes
121                if seq.next_element::<u8>()?.is_some() {
122                    return Err(SerdeError::invalid_length(Recipient::LEN + 1, &self));
123                }
124
125                Recipient::try_from_bytes(recipient_bytes).map_err(|_| {
126                    SerdeError::invalid_value(
127                        Unexpected::Other("At least one of the curve points was malformed"),
128                        &self,
129                    )
130                })
131            }
132        }
133
134        deserializer.deserialize_bytes(RecipientVisitor)
135    }
136}
137
138impl Recipient {
139    pub const LEN: usize = CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE + NODE_IDENTITY_SIZE;
140
141    pub fn new(
142        client_identity: ClientIdentity,
143        client_encryption_key: ClientEncryptionKey,
144        gateway: NodeIdentity,
145    ) -> Self {
146        Recipient {
147            client_identity,
148            client_encryption_key,
149            gateway,
150        }
151    }
152
153    // TODO: Currently the `DestinationAddress` is equivalent to `ClientIdentity`, but perhaps
154    // it shouldn't be? Maybe it should be (for example) H(`ClientIdentity || ClientEncryptionKey`)
155    // instead? That is an open question.
156    pub fn as_sphinx_destination(&self) -> Destination {
157        // since the nym mix network differs slightly in design from loopix, we do not care
158        // about "surb_id" field at all and just use the default value.
159        Destination::new(
160            self.client_identity.derive_destination_address(),
161            Default::default(),
162        )
163    }
164
165    pub fn identity(&self) -> &ClientIdentity {
166        &self.client_identity
167    }
168
169    pub fn encryption_key(&self) -> &ClientEncryptionKey {
170        &self.client_encryption_key
171    }
172
173    pub fn gateway(&self) -> NodeIdentity {
174        self.gateway
175    }
176
177    pub fn to_bytes(self) -> RecipientBytes {
178        let mut out = [0u8; Self::LEN];
179        out[..CLIENT_IDENTITY_SIZE].copy_from_slice(&self.client_identity.to_bytes());
180        out[CLIENT_IDENTITY_SIZE..CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE]
181            .copy_from_slice(&self.client_encryption_key.to_bytes());
182        out[CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE..]
183            .copy_from_slice(&self.gateway.to_bytes());
184
185        out
186    }
187
188    pub fn try_from_bytes(bytes: RecipientBytes) -> Result<Self, RecipientFormattingError> {
189        let identity_bytes = &bytes[..CLIENT_IDENTITY_SIZE];
190        let enc_key_bytes =
191            &bytes[CLIENT_IDENTITY_SIZE..CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE];
192        let gateway_bytes = &bytes[CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE..];
193
194        let client_identity = match ClientIdentity::from_bytes(identity_bytes) {
195            Ok(client_id) => client_id,
196            Err(err) => return Err(RecipientFormattingError::MalformedIdentityError(err)),
197        };
198
199        let client_encryption_key = ClientEncryptionKey::from_bytes(enc_key_bytes)?;
200
201        let gateway = match NodeIdentity::from_bytes(gateway_bytes) {
202            Ok(gate_id) => gate_id,
203            Err(err) => return Err(RecipientFormattingError::MalformedGatewayError(err)),
204        };
205
206        Ok(Recipient {
207            client_identity,
208            client_encryption_key,
209            gateway,
210        })
211    }
212
213    pub fn try_from_base58_string<S: Into<String>>(
214        full_address: S,
215    ) -> Result<Self, RecipientFormattingError> {
216        let string_address = full_address.into();
217        let split: Vec<_> = string_address.split('@').collect();
218        if split.len() != 2 {
219            return Err(RecipientFormattingError::MalformedRecipientError {
220                reason: "the string address does not contain exactly a single '@' character"
221                    .to_string(),
222            });
223        }
224        let client_half = split[0];
225        let gateway_half = split[1];
226
227        let split_client: Vec<_> = client_half.split('.').collect();
228        if split_client.len() != 2 {
229            return Err(RecipientFormattingError::MalformedRecipientError {
230                reason: "the string address does not contain exactly a single '.' character"
231                    .to_string(),
232            });
233        }
234
235        let client_identity = match ClientIdentity::from_base58_string(split_client[0]) {
236            Ok(client_id) => client_id,
237            Err(err) => return Err(RecipientFormattingError::MalformedIdentityError(err)),
238        };
239
240        let client_encryption_key = ClientEncryptionKey::from_base58_string(split_client[1])?;
241
242        let gateway = match NodeIdentity::from_base58_string(gateway_half) {
243            Ok(gate_id) => gate_id,
244            Err(err) => return Err(RecipientFormattingError::MalformedGatewayError(err)),
245        };
246
247        Ok(Recipient {
248            client_identity,
249            client_encryption_key,
250            gateway,
251        })
252    }
253}
254
255// ADDRESS . ENCRYPTION @ GATEWAY_ID
256impl std::fmt::Display for Recipient {
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
258        write!(
259            f,
260            "{}.{}@{}",
261            self.client_identity.to_base58_string(),
262            self.client_encryption_key.to_base58_string(),
263            self.gateway.to_base58_string()
264        )
265    }
266}
267
268impl std::fmt::Debug for Recipient {
269    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
270        // use the Display implementation
271        <Self as std::fmt::Display>::fmt(self, f)
272    }
273}
274
275impl FromStr for Recipient {
276    type Err = RecipientFormattingError;
277
278    fn from_str(s: &str) -> Result<Self, Self::Err> {
279        Recipient::try_from_base58_string(s)
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    fn mock_recipient() -> Recipient {
288        Recipient::try_from_bytes([
289            67, 5, 132, 146, 3, 236, 116, 89, 254, 57, 131, 159, 69, 181, 55, 208, 12, 108, 136,
290            83, 58, 76, 171, 195, 31, 98, 92, 64, 68, 53, 156, 184, 100, 189, 73, 3, 238, 103, 156,
291            108, 124, 199, 42, 79, 172, 98, 81, 177, 182, 100, 167, 164, 74, 183, 199, 213, 162,
292            173, 102, 112, 30, 159, 148, 66, 44, 75, 230, 182, 138, 114, 170, 163, 209, 82, 204,
293            100, 118, 91, 57, 150, 212, 147, 151, 135, 148, 16, 213, 223, 182, 164, 242, 37, 40,
294            73, 137, 228,
295        ])
296        .unwrap()
297    }
298
299    #[test]
300    fn string_conversion_works() {
301        let mut rng = rand::rng();
302
303        let client_id_pair = ed25519::KeyPair::new(&mut rng);
304        let client_enc_pair = x25519::KeyPair::new(&mut rng);
305        let gateway_id_pair = ed25519::KeyPair::new(&mut rng);
306
307        let recipient = Recipient::new(
308            *client_id_pair.public_key(),
309            *client_enc_pair.public_key(),
310            *gateway_id_pair.public_key(),
311        );
312
313        let str_recipient = recipient.to_string();
314        let recovered_recipient = Recipient::try_from_base58_string(str_recipient).unwrap();
315
316        // as long as byte representation of internal keys are identical, it's all fine
317        assert_eq!(
318            recipient.client_identity.to_bytes(),
319            recovered_recipient.client_identity.to_bytes()
320        );
321        assert_eq!(
322            recipient.client_encryption_key.to_bytes(),
323            recovered_recipient.client_encryption_key.to_bytes()
324        );
325        assert_eq!(
326            recipient.gateway.to_bytes(),
327            recovered_recipient.gateway.to_bytes()
328        );
329    }
330
331    #[test]
332    fn bytes_conversion_works() {
333        let mut rng = rand::rng();
334
335        let client_id_pair = ed25519::KeyPair::new(&mut rng);
336        let client_enc_pair = x25519::KeyPair::new(&mut rng);
337        let gateway_id_pair = ed25519::KeyPair::new(&mut rng);
338
339        let recipient = Recipient::new(
340            *client_id_pair.public_key(),
341            *client_enc_pair.public_key(),
342            *gateway_id_pair.public_key(),
343        );
344
345        let bytes_recipient = recipient.to_bytes();
346        let recovered_recipient = Recipient::try_from_bytes(bytes_recipient).unwrap();
347
348        // as long as byte representation of internal keys are identical, it's all fine
349        assert_eq!(
350            recipient.client_identity.to_bytes(),
351            recovered_recipient.client_identity.to_bytes()
352        );
353        assert_eq!(
354            recipient.client_encryption_key.to_bytes(),
355            recovered_recipient.client_encryption_key.to_bytes()
356        );
357        assert_eq!(
358            recipient.gateway.to_bytes(),
359            recovered_recipient.gateway.to_bytes()
360        );
361    }
362
363    // calls `visit_bytes`
364    #[test]
365    fn bincode_serialisation_works() {
366        let recipient = mock_recipient();
367
368        #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
369        struct MyStruct {
370            recipient: Recipient,
371        }
372        let a = MyStruct { recipient };
373        let s = bincode::serialize(&a).unwrap();
374
375        let b = bincode::deserialize(&s).unwrap();
376
377        assert_eq!(a, b);
378    }
379
380    // calls `visit_seq`
381    #[test]
382    fn json_serialisation_works() {
383        use serde::{Deserialize, Serialize};
384
385        let recipient = mock_recipient();
386
387        #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
388        struct MyStruct {
389            recipient: Recipient,
390        }
391        let a = MyStruct { recipient };
392        let s = serde_json::to_string(&a).unwrap();
393
394        let b = serde_json::from_str(&s).unwrap();
395
396        assert_eq!(a, b);
397    }
398}