Skip to main content

defuse_core/nonce/
versioned.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2use hex_literal::hex;
3
4use crate::{
5    Nonce,
6    nonce::{expirable::ExpirableNonce, salted::SaltedNonce},
7};
8
9/// To distinguish between legacy nonces and versioned nonces
10/// we use a specific prefix individual for each version.
11/// Serialized versioned nonce contains:
12///     `VERSIONED_MAGIC_PREFIX (4 bytes) || VERSION (1 byte) || NONCE_BYTES (27 bytes)`
13/// Currently supported versions:
14///     - V1: `SALT (4 bytes) || DEADLINE (8 bytes) || NONCE (15 random bytes)`
15#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
16pub enum VersionedNonce {
17    V1(SaltedNonce<ExpirableNonce<[u8; 15]>>),
18}
19
20// NOTE: Legacy nonces can still be used at this time, but will be prohibited out in the near future.
21impl VersionedNonce {
22    /// Magic prefixes (first 4 bytes of `sha256(<versioned_nonce>)`) used to mark versioned nonces:
23    pub const VERSIONED_MAGIC_PREFIX: [u8; 4] = hex!("5628f6c6");
24
25    pub fn maybe_from(n: Nonce) -> Option<Self> {
26        let mut versioned = n.strip_prefix(&Self::VERSIONED_MAGIC_PREFIX)?;
27        Self::deserialize_reader(&mut versioned).ok()
28    }
29}
30
31impl From<VersionedNonce> for Nonce {
32    fn from(value: VersionedNonce) -> Self {
33        const SIZE: usize = size_of::<Nonce>();
34        let mut result = [0u8; SIZE];
35
36        (VersionedNonce::VERSIONED_MAGIC_PREFIX, value)
37            .serialize(&mut result.as_mut_slice())
38            .unwrap_or_else(|_| unreachable!());
39
40        result
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    use crate::{Timestamp, nonce::salted::Salt};
49    use arbitrary::Unstructured;
50    use defuse_test_utils::random::random_bytes;
51    use rstest::rstest;
52
53    #[rstest]
54    fn maybe_from_test(random_bytes: Vec<u8>) {
55        let mut u = Unstructured::new(&random_bytes);
56        let legacy_nonce: Nonce = u.arbitrary().unwrap();
57
58        let expected = VersionedNonce::maybe_from(legacy_nonce);
59        assert!(expected.is_none());
60
61        let mut u = Unstructured::new(&random_bytes);
62        let nonce_bytes: [u8; 15] = u.arbitrary().unwrap();
63        let now = Timestamp::now();
64        let salt: Salt = u.arbitrary().unwrap();
65
66        let salted = SaltedNonce::new(salt, ExpirableNonce::new(now, nonce_bytes));
67        let nonce: Nonce = VersionedNonce::V1(salted.clone()).into();
68
69        let exp = VersionedNonce::maybe_from(nonce);
70        assert_eq!(exp, Some(VersionedNonce::V1(salted)));
71    }
72}