defuse_core/nonce/
versioned.rs

1use hex_literal::hex;
2use near_sdk::borsh::{BorshDeserialize, BorshSerialize};
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)]
16#[borsh(crate = "::near_sdk::borsh")]
17pub enum VersionedNonce {
18    V1(SaltedNonce<ExpirableNonce<[u8; 15]>>),
19}
20
21// NOTE: Legacy nonces can still be used at this time, but will be prohibited out in the near future.
22impl VersionedNonce {
23    /// Magic prefixes (first 4 bytes of `sha256(<versioned_nonce>)`) used to mark versioned nonces:
24    pub const VERSIONED_MAGIC_PREFIX: [u8; 4] = hex!("5628f6c6");
25
26    pub fn maybe_from(n: Nonce) -> Option<Self> {
27        let mut versioned = n.strip_prefix(&Self::VERSIONED_MAGIC_PREFIX)?;
28        Self::deserialize_reader(&mut versioned).ok()
29    }
30}
31
32impl From<VersionedNonce> for Nonce {
33    fn from(value: VersionedNonce) -> Self {
34        const SIZE: usize = size_of::<Nonce>();
35        let mut result = [0u8; SIZE];
36
37        (VersionedNonce::VERSIONED_MAGIC_PREFIX, value)
38            .serialize(&mut result.as_mut_slice())
39            .unwrap_or_else(|_| unreachable!());
40
41        result
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    use crate::{Deadline, nonce::salted::Salt};
50    use arbitrary::Unstructured;
51    use chrono::Utc;
52    use defuse_test_utils::random::random_bytes;
53    use rstest::rstest;
54
55    #[rstest]
56    fn maybe_from_test(random_bytes: Vec<u8>) {
57        let mut u = Unstructured::new(&random_bytes);
58        let legacy_nonce: Nonce = u.arbitrary().unwrap();
59
60        let expected = VersionedNonce::maybe_from(legacy_nonce);
61        assert!(expected.is_none());
62
63        let mut u = Unstructured::new(&random_bytes);
64        let nonce_bytes: [u8; 15] = u.arbitrary().unwrap();
65        let now = Deadline::new(Utc::now());
66        let salt: Salt = u.arbitrary().unwrap();
67
68        let salted = SaltedNonce::new(salt, ExpirableNonce::new(now, nonce_bytes));
69        let nonce: Nonce = VersionedNonce::V1(salted.clone()).into();
70
71        let exp = VersionedNonce::maybe_from(nonce);
72        assert_eq!(exp, Some(VersionedNonce::V1(salted)));
73    }
74}