Skip to main content

miden_protocol/address/
routing_parameters.rs

1use alloc::borrow::ToOwned;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4
5use bech32::primitives::decode::CheckedHrpstring;
6use bech32::{Bech32m, Hrp};
7
8use crate::address::AddressInterface;
9use crate::crypto::dsa::{ecdsa_k256_keccak, eddsa_25519_sha512};
10use crate::crypto::ies::SealingKey;
11use crate::errors::{AddressError, Bech32Error};
12use crate::note::NoteTag;
13use crate::utils::serde::{
14    ByteReader,
15    ByteWriter,
16    Deserializable,
17    DeserializationError,
18    Serializable,
19};
20use crate::utils::sync::LazyLock;
21
22/// The HRP used for encoding routing parameters.
23///
24/// This HRP is only used internally, but needs to be well-defined for other routing parameter
25/// encode/decode implementations.
26///
27/// `mrp` stands for Miden Routing Parameters.
28static ROUTING_PARAMETERS_HRP: LazyLock<Hrp> =
29    LazyLock::new(|| Hrp::parse("mrp").expect("hrp should be valid"));
30
31/// The separator character used in bech32.
32const BECH32_SEPARATOR: &str = "1";
33
34/// The value to encode the absence of a note tag routing parameter (i.e. `None`).
35///
36/// The note tag length occupies 6 bits (values 0..=63). Valid tag lengths are 0..=32,
37/// so we reserve the maximum 6-bit value (63) to represent `None`.
38///
39/// If the note tag length is absent from routing parameters, the note tag length for the address
40/// will be set to the default default tag length of the address' ID component.
41const ABSENT_NOTE_TAG_LEN: u8 = 63;
42
43/// The routing parameter key for the receiver profile.
44const RECEIVER_PROFILE_PARAM_KEY: u8 = 0;
45
46/// The routing parameter key for the encryption key.
47const ENCRYPTION_KEY_PARAM_KEY: u8 = 1;
48
49/// The expected length of Ed25519/X25519 public keys in bytes.
50const X25519_PUBLIC_KEY_LENGTH: usize = 32;
51
52/// The expected length of K256 (secp256k1) public keys in bytes (compressed format).
53const K256_PUBLIC_KEY_LENGTH: usize = 33;
54
55/// Discriminants for encryption key variants.
56const ENCRYPTION_KEY_X25519_XCHACHA20POLY1305: u8 = 0;
57const ENCRYPTION_KEY_K256_XCHACHA20POLY1305: u8 = 1;
58const ENCRYPTION_KEY_X25519_AEAD_POSEIDON2: u8 = 2;
59const ENCRYPTION_KEY_K256_AEAD_POSEIDON2: u8 = 3;
60
61/// Parameters that define how a sender should route a note to the [`AddressId`](super::AddressId)
62/// in an [`Address`](super::Address).
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct RoutingParameters {
65    interface: AddressInterface,
66    note_tag_len: Option<u8>,
67    encryption_key: Option<SealingKey>,
68}
69
70impl RoutingParameters {
71    // CONSTRUCTORS
72    // --------------------------------------------------------------------------------------------
73
74    /// Creates new [`RoutingParameters`] from an [`AddressInterface`] and all other parameters
75    /// initialized to `None`.
76    pub fn new(interface: AddressInterface) -> Self {
77        Self {
78            interface,
79            note_tag_len: None,
80            encryption_key: None,
81        }
82    }
83
84    /// Sets the note tag length routing parameter.
85    ///
86    /// The tag length determines how many bits of the address ID are encoded into [`NoteTag`]s of
87    /// notes targeted to this address. This lets the receiver choose their level of privacy. A
88    /// higher tag length makes the address ID more uniquely identifiable and reduces privacy,
89    /// while a shorter length increases privacy at the cost of matching more notes published
90    /// onchain.
91    ///
92    /// # Errors
93    ///
94    /// Returns an error if:
95    /// - The tag length exceeds the maximum of [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `].
96    pub fn with_note_tag_len(mut self, note_tag_len: u8) -> Result<Self, AddressError> {
97        if note_tag_len > NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH {
98            return Err(AddressError::TagLengthTooLarge(note_tag_len));
99        }
100
101        self.note_tag_len = Some(note_tag_len);
102        Ok(self)
103    }
104
105    // ACCESSORS
106    // --------------------------------------------------------------------------------------------
107
108    /// Returns the note tag length preference.
109    ///
110    /// This is guaranteed to be in range `0..=32` (i.e. at most
111    /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `]).
112    pub fn note_tag_len(&self) -> Option<u8> {
113        self.note_tag_len
114    }
115
116    /// Returns the [`AddressInterface`] of the account to which the address points.
117    pub fn interface(&self) -> AddressInterface {
118        self.interface
119    }
120
121    /// Returns the public encryption key.
122    pub fn encryption_key(&self) -> Option<&SealingKey> {
123        self.encryption_key.as_ref()
124    }
125
126    /// Sets the encryption key routing parameter.
127    ///
128    /// This allows senders to encrypt note payloads using sealed box encryption
129    /// for the recipient of this address.
130    pub fn with_encryption_key(mut self, key: SealingKey) -> Self {
131        self.encryption_key = Some(key);
132        self
133    }
134
135    // HELPERS
136    // --------------------------------------------------------------------------------------------
137
138    /// Encodes [`RoutingParameters`] to a byte vector.
139    pub(crate) fn encode_to_bytes(&self) -> Vec<u8> {
140        let mut encoded = Vec::new();
141
142        // Append the receiver profile key and the encoded value to the vector.
143        encoded.push(RECEIVER_PROFILE_PARAM_KEY);
144        encoded.extend(encode_receiver_profile(self.interface, self.note_tag_len));
145
146        // Append the encryption key if present.
147        if let Some(encryption_key) = &self.encryption_key {
148            encoded.push(ENCRYPTION_KEY_PARAM_KEY);
149            encode_encryption_key(encryption_key, &mut encoded);
150        }
151
152        encoded
153    }
154
155    /// Encodes [`RoutingParameters`] to a bech32 string _without_ the leading hrp and separator.
156    pub(crate) fn encode_to_string(&self) -> String {
157        let encoded = self.encode_to_bytes();
158
159        let bech32_str =
160            bech32::encode::<Bech32m>(*ROUTING_PARAMETERS_HRP, &encoded).expect("TODO");
161        let encoded_str = bech32_str
162            .strip_prefix(ROUTING_PARAMETERS_HRP.as_str())
163            .expect("bech32 str should start with the hrp");
164        let encoded_str = encoded_str
165            .strip_prefix(BECH32_SEPARATOR)
166            .expect("encoded str should start with bech32 separator `1`");
167        encoded_str.to_owned()
168    }
169
170    /// Decodes [`RoutingParameters`] from a bech32 string _without_ the leading hrp and separator.
171    pub(crate) fn decode(mut bech32_string: String) -> Result<Self, AddressError> {
172        // ------ Decode bech32 string into bytes ------
173
174        // Reinsert the expected HRP into the string that is stripped during encoding.
175        bech32_string.insert_str(0, BECH32_SEPARATOR);
176        bech32_string.insert_str(0, ROUTING_PARAMETERS_HRP.as_str());
177
178        // We use CheckedHrpString with an explicit checksum algorithm so we don't allow the
179        // `Bech32` or `NoChecksum` algorithms.
180        let checked_string =
181            CheckedHrpstring::new::<Bech32m>(&bech32_string).map_err(|source| {
182                // The CheckedHrpStringError does not implement core::error::Error, only
183                // std::error::Error, so for now we convert it to a String. Even if it will
184                // implement the trait in the future, we should include it as an opaque
185                // error since the crate does not have a stable release yet.
186                AddressError::decode_error_with_source(
187                    "failed to decode routing parameters bech32 string",
188                    Bech32Error::DecodeError(source.to_string().into()),
189                )
190            })?;
191
192        Self::decode_from_bytes(checked_string.byte_iter())
193    }
194
195    /// Decodes [`RoutingParameters`] from a byte iterator.
196    pub(crate) fn decode_from_bytes(
197        mut byte_iter: impl ExactSizeIterator<Item = u8>,
198    ) -> Result<Self, AddressError> {
199        let mut interface = None;
200        let mut note_tag_len = None;
201        let mut encryption_key = None;
202
203        while let Some(key) = byte_iter.next() {
204            match key {
205                RECEIVER_PROFILE_PARAM_KEY => {
206                    if interface.is_some() {
207                        return Err(AddressError::decode_error(
208                            "duplicate receiver profile routing parameter",
209                        ));
210                    }
211                    let receiver_profile = decode_receiver_profile(&mut byte_iter)?;
212                    interface = Some(receiver_profile.0);
213                    note_tag_len = receiver_profile.1;
214                },
215                ENCRYPTION_KEY_PARAM_KEY => {
216                    if encryption_key.is_some() {
217                        return Err(AddressError::decode_error(
218                            "duplicate encryption key routing parameter",
219                        ));
220                    }
221                    encryption_key = Some(decode_encryption_key(&mut byte_iter)?);
222                },
223                other => {
224                    return Err(AddressError::UnknownRoutingParameterKey(other));
225                },
226            }
227        }
228
229        let interface = interface.ok_or_else(|| {
230            AddressError::decode_error("interface must be present in routing parameters")
231        })?;
232
233        let mut routing_parameters = RoutingParameters::new(interface);
234        routing_parameters.note_tag_len = note_tag_len;
235        routing_parameters.encryption_key = encryption_key;
236
237        Ok(routing_parameters)
238    }
239}
240
241impl Serializable for RoutingParameters {
242    fn write_into<W: ByteWriter>(&self, target: &mut W) {
243        let bytes = self.encode_to_bytes();
244        // Due to the bech32 constraint of max 633 bytes, a u16 is sufficient.
245        let num_bytes = bytes.len() as u16;
246
247        target.write_u16(num_bytes);
248        target.write_many(bytes);
249    }
250}
251
252impl Deserializable for RoutingParameters {
253    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
254        let num_bytes = source.read_u16()?;
255        let bytes: Vec<u8> =
256            source.read_many_iter(num_bytes as usize)?.collect::<Result<_, _>>()?;
257
258        Self::decode_from_bytes(bytes.into_iter())
259            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
260    }
261}
262
263// ENCODING / DECODING HELPERS
264// ================================================================================================
265
266/// Returns receiver profile bytes constructed from the provided interface and note tag length.
267fn encode_receiver_profile(interface: AddressInterface, note_tag_len: Option<u8>) -> [u8; 2] {
268    let note_tag_len = note_tag_len.unwrap_or(ABSENT_NOTE_TAG_LEN);
269
270    let interface = interface as u16;
271    debug_assert_eq!(interface >> 10, 0, "address interface should fit into 10 bits");
272
273    // The interface takes up 10 bits and the tag length 6 bits, so we can merge them
274    // together.
275    let tag_len = (note_tag_len as u16) << 10;
276    let receiver_profile: u16 = tag_len | interface;
277    receiver_profile.to_be_bytes()
278}
279
280/// Reads the receiver profile from the provided bytes.
281fn decode_receiver_profile(
282    byte_iter: &mut impl ExactSizeIterator<Item = u8>,
283) -> Result<(AddressInterface, Option<u8>), AddressError> {
284    if byte_iter.len() < 2 {
285        return Err(AddressError::decode_error("expected two bytes to decode receiver profile"));
286    };
287
288    let byte0 = byte_iter.next().expect("byte0 should exist");
289    let byte1 = byte_iter.next().expect("byte1 should exist");
290    let receiver_profile = u16::from_be_bytes([byte0, byte1]);
291
292    let tag_len = (receiver_profile >> 10) as u8;
293    let note_tag_len = match tag_len {
294        ABSENT_NOTE_TAG_LEN => None,
295        0..=32 => Some(tag_len),
296        _ => {
297            return Err(AddressError::decode_error(format!("invalid note tag length {}", tag_len)));
298        },
299    };
300
301    let addr_interface = receiver_profile & 0b0000_0011_1111_1111;
302    let addr_interface = AddressInterface::try_from(addr_interface).map_err(|err| {
303        AddressError::decode_error_with_source("failed to decode address interface", err)
304    })?;
305
306    Ok((addr_interface, note_tag_len))
307}
308
309/// Append encryption key variant discriminant and key to the provided vector of bytes.
310fn encode_encryption_key(key: &SealingKey, encoded: &mut Vec<u8>) {
311    match key {
312        SealingKey::X25519XChaCha20Poly1305(pk) => {
313            encoded.push(ENCRYPTION_KEY_X25519_XCHACHA20POLY1305);
314            encoded.extend(&pk.to_bytes());
315        },
316        SealingKey::K256XChaCha20Poly1305(pk) => {
317            encoded.push(ENCRYPTION_KEY_K256_XCHACHA20POLY1305);
318            encoded.extend(&pk.to_bytes());
319        },
320        SealingKey::X25519AeadPoseidon2(pk) => {
321            encoded.push(ENCRYPTION_KEY_X25519_AEAD_POSEIDON2);
322            encoded.extend(&pk.to_bytes());
323        },
324        SealingKey::K256AeadPoseidon2(pk) => {
325            encoded.push(ENCRYPTION_KEY_K256_AEAD_POSEIDON2);
326            encoded.extend(&pk.to_bytes());
327        },
328    }
329}
330
331/// Reads the encryption key from the provided bytes.
332fn decode_encryption_key(
333    byte_iter: &mut impl ExactSizeIterator<Item = u8>,
334) -> Result<SealingKey, AddressError> {
335    // Read variant discriminant
336    let Some(variant) = byte_iter.next() else {
337        return Err(AddressError::decode_error(
338            "expected at least 1 byte for encryption key variant",
339        ));
340    };
341
342    // Reconstruct the appropriate PublicEncryptionKey variant
343    let public_encryption_key = match variant {
344        ENCRYPTION_KEY_X25519_XCHACHA20POLY1305 => {
345            SealingKey::X25519XChaCha20Poly1305(read_x25519_pub_key(byte_iter)?)
346        },
347        ENCRYPTION_KEY_K256_XCHACHA20POLY1305 => {
348            SealingKey::K256XChaCha20Poly1305(read_k256_pub_key(byte_iter)?)
349        },
350        ENCRYPTION_KEY_X25519_AEAD_POSEIDON2 => {
351            SealingKey::X25519AeadPoseidon2(read_x25519_pub_key(byte_iter)?)
352        },
353        ENCRYPTION_KEY_K256_AEAD_POSEIDON2 => {
354            SealingKey::K256AeadPoseidon2(read_k256_pub_key(byte_iter)?)
355        },
356        other => {
357            return Err(AddressError::decode_error(format!(
358                "unknown encryption key variant: {}",
359                other
360            )));
361        },
362    };
363
364    Ok(public_encryption_key)
365}
366
367fn read_x25519_pub_key(
368    byte_iter: &mut impl ExactSizeIterator<Item = u8>,
369) -> Result<eddsa_25519_sha512::PublicKey, AddressError> {
370    if byte_iter.len() < X25519_PUBLIC_KEY_LENGTH {
371        return Err(AddressError::decode_error(format!(
372            "expected {} bytes to decode X25519 public key",
373            X25519_PUBLIC_KEY_LENGTH
374        )));
375    }
376    let key_bytes: [u8; X25519_PUBLIC_KEY_LENGTH] = read_byte_array(byte_iter);
377    eddsa_25519_sha512::PublicKey::read_from_bytes(&key_bytes).map_err(|err| {
378        AddressError::decode_error_with_source("failed to decode X25519 public key", err)
379    })
380}
381
382fn read_k256_pub_key(
383    byte_iter: &mut impl ExactSizeIterator<Item = u8>,
384) -> Result<ecdsa_k256_keccak::PublicKey, AddressError> {
385    if byte_iter.len() < K256_PUBLIC_KEY_LENGTH {
386        return Err(AddressError::decode_error(format!(
387            "expected {} bytes to decode K256 public key",
388            K256_PUBLIC_KEY_LENGTH
389        )));
390    }
391    let key_bytes: [u8; K256_PUBLIC_KEY_LENGTH] = read_byte_array(byte_iter);
392    ecdsa_k256_keccak::PublicKey::read_from_bytes(&key_bytes).map_err(|err| {
393        AddressError::decode_error_with_source("failed to decode K256 public key", err)
394    })
395}
396
397/// Reads bytes from the provided iterator into an array of length N and returns this array.
398///
399/// Assumes that there are at least N bytes in the iterator.
400fn read_byte_array<const N: usize>(byte_iter: &mut impl ExactSizeIterator<Item = u8>) -> [u8; N] {
401    let mut array = [0u8; N];
402    for byte in array.iter_mut() {
403        *byte = byte_iter.next().expect("iterator should have enough bytes");
404    }
405    array
406}
407
408// TESTS
409// ================================================================================================
410
411#[cfg(test)]
412mod tests {
413    use bech32::{Bech32m, Checksum, Hrp};
414
415    use super::*;
416
417    /// Checks the assumptions about the total length allowed in bech32 encoding.
418    ///
419    /// The assumption is that encoding should error if the total length of the hrp + data (encoded
420    /// in GF(32)) + the separator + the checksum exceeds Bech32m::CODE_LENGTH.
421    #[test]
422    fn bech32_code_length_assertions() -> anyhow::Result<()> {
423        let hrp = Hrp::parse("mrp").unwrap();
424        let separator_len = BECH32_SEPARATOR.len();
425        // The fixed number of characters included in a bech32 string.
426        let fixed_num_bytes = hrp.as_str().len() + separator_len + Bech32m::CHECKSUM_LENGTH;
427        let num_allowed_chars = Bech32m::CODE_LENGTH - fixed_num_bytes;
428        // Multiply by the 5 bits per base32 character and divide by 8 bits per byte.
429        let num_allowed_bytes = num_allowed_chars * 5 / 8;
430
431        // The number of bytes that routing parameters effectively have available.
432        assert_eq!(num_allowed_bytes, 633);
433
434        // This amount of data is the max that should be okay to encode.
435        let data_ok = vec![5; num_allowed_bytes];
436        // One more byte than the max allowed amount should result in an error.
437        let data_too_long = vec![5; num_allowed_bytes + 1];
438
439        assert!(bech32::encode::<Bech32m>(hrp, &data_ok).is_ok());
440        assert!(bech32::encode::<Bech32m>(hrp, &data_too_long).is_err());
441
442        Ok(())
443    }
444
445    /// Tests bech32 encoding and decoding roundtrip with various tag lengths.
446    #[test]
447    fn routing_parameters_bech32_encode_decode_roundtrip() -> anyhow::Result<()> {
448        // Test case 1: No explicit tag length
449        let params_no_tag = RoutingParameters::new(AddressInterface::BasicWallet);
450        let encoded = params_no_tag.encode_to_string();
451        let decoded = RoutingParameters::decode(encoded)?;
452        assert_eq!(params_no_tag, decoded);
453        assert_eq!(decoded.note_tag_len(), None);
454
455        // Test case 2: Explicit tag length 0
456        let params_tag_0 =
457            RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(0)?;
458        let encoded = params_tag_0.encode_to_string();
459        let decoded = RoutingParameters::decode(encoded)?;
460        assert_eq!(params_tag_0, decoded);
461        assert_eq!(decoded.note_tag_len(), Some(0));
462
463        // Test case 3: Explicit tag length 6
464        let params_tag_6 =
465            RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(6)?;
466        let encoded = params_tag_6.encode_to_string();
467        let decoded = RoutingParameters::decode(encoded)?;
468        assert_eq!(params_tag_6, decoded);
469        assert_eq!(decoded.note_tag_len(), Some(6));
470
471        // Test case 4: Explicit tag length set to max
472        let params_tag_max = RoutingParameters::new(AddressInterface::BasicWallet)
473            .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?;
474        let encoded = params_tag_max.encode_to_string();
475        let decoded = RoutingParameters::decode(encoded)?;
476        assert_eq!(params_tag_max, decoded);
477        assert_eq!(decoded.note_tag_len(), Some(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH));
478
479        Ok(())
480    }
481
482    /// Tests serialization and deserialization roundtrip with various tag lengths.
483    #[test]
484    fn routing_parameters_serialization() -> anyhow::Result<()> {
485        // Test case 1: No explicit tag length
486        let params_no_tag = RoutingParameters::new(AddressInterface::BasicWallet);
487        let serialized = params_no_tag.to_bytes();
488        let deserialized = RoutingParameters::read_from_bytes(&serialized)?;
489        assert_eq!(params_no_tag, deserialized);
490        assert_eq!(deserialized.note_tag_len(), None);
491
492        // Test case 2: Explicit tag length 0
493        let params_tag_0 =
494            RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(0)?;
495        let serialized = params_tag_0.to_bytes();
496        let deserialized = RoutingParameters::read_from_bytes(&serialized)?;
497        assert_eq!(params_tag_0, deserialized);
498        assert_eq!(deserialized.note_tag_len(), Some(0));
499
500        // Test case 3: Explicit tag length 6
501        let params_tag_6 =
502            RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(6)?;
503        let serialized = params_tag_6.to_bytes();
504        let deserialized = RoutingParameters::read_from_bytes(&serialized)?;
505        assert_eq!(params_tag_6, deserialized);
506        assert_eq!(deserialized.note_tag_len(), Some(6));
507
508        // Test case 4: Explicit tag length set to max
509        let params_tag_max = RoutingParameters::new(AddressInterface::BasicWallet)
510            .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?;
511        let serialized = params_tag_max.to_bytes();
512        let deserialized = RoutingParameters::read_from_bytes(&serialized)?;
513        assert_eq!(params_tag_max, deserialized);
514        assert_eq!(deserialized.note_tag_len(), Some(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH));
515
516        Ok(())
517    }
518
519    /// Tests encoding/decoding and serialization for all encryption key variants.
520    #[test]
521    fn routing_parameters_all_encryption_key_variants() -> anyhow::Result<()> {
522        // Helper function to test both encoding/decoding and serialization
523        fn test_encryption_key_roundtrip(encryption_key: SealingKey) -> anyhow::Result<()> {
524            let routing_params = RoutingParameters::new(AddressInterface::BasicWallet)
525                .with_encryption_key(encryption_key.clone());
526
527            // Test bech32 encoding/decoding
528            let encoded = routing_params.encode_to_string();
529            let decoded = RoutingParameters::decode(encoded)?;
530            assert_eq!(routing_params, decoded);
531            assert_eq!(decoded.encryption_key(), Some(&encryption_key));
532
533            // Test serialization/deserialization
534            let serialized = routing_params.to_bytes();
535            let deserialized = RoutingParameters::read_from_bytes(&serialized)?;
536            assert_eq!(routing_params, deserialized);
537            assert_eq!(deserialized.encryption_key(), Some(&encryption_key));
538
539            Ok(())
540        }
541
542        // Test X25519XChaCha20Poly1305
543        {
544            use crate::crypto::dsa::eddsa_25519_sha512::SecretKey;
545            let secret_key = SecretKey::with_rng(&mut rand::rng());
546            let public_key = secret_key.public_key();
547            let encryption_key = SealingKey::X25519XChaCha20Poly1305(public_key);
548            test_encryption_key_roundtrip(encryption_key)?;
549        }
550
551        // Test K256XChaCha20Poly1305
552        {
553            use crate::crypto::dsa::ecdsa_k256_keccak::SecretKey;
554            let secret_key = SecretKey::with_rng(&mut rand::rng());
555            let public_key = secret_key.public_key();
556            let encryption_key = SealingKey::K256XChaCha20Poly1305(public_key);
557            test_encryption_key_roundtrip(encryption_key)?;
558        }
559
560        // Test X25519AeadPoseidon2
561        {
562            use crate::crypto::dsa::eddsa_25519_sha512::SecretKey;
563            let secret_key = SecretKey::with_rng(&mut rand::rng());
564            let public_key = secret_key.public_key();
565            let encryption_key = SealingKey::X25519AeadPoseidon2(public_key);
566            test_encryption_key_roundtrip(encryption_key)?;
567        }
568
569        // Test K256AeadPoseidon2
570        {
571            use crate::crypto::dsa::ecdsa_k256_keccak::SecretKey;
572            let secret_key = SecretKey::with_rng(&mut rand::rng());
573            let public_key = secret_key.public_key();
574            let encryption_key = SealingKey::K256AeadPoseidon2(public_key);
575            test_encryption_key_roundtrip(encryption_key)?;
576        }
577
578        Ok(())
579    }
580}