Skip to main content

nox_core/models/
note.rs

1use crate::protocol::serialization::{deserialize_fr, serialize_fr};
2
3use ark_bn254::Fr;
4use ark_ff::{BigInteger, PrimeField};
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Serialize, Deserialize, PartialEq)]
8pub struct Note {
9    #[serde(serialize_with = "serialize_fr", deserialize_with = "deserialize_fr")]
10    pub asset_id: Fr,
11    #[serde(serialize_with = "serialize_fr", deserialize_with = "deserialize_fr")]
12    pub value: Fr,
13    #[serde(serialize_with = "serialize_fr", deserialize_with = "deserialize_fr")]
14    pub secret: Fr,
15    #[serde(serialize_with = "serialize_fr", deserialize_with = "deserialize_fr")]
16    pub nullifier: Fr,
17    #[serde(serialize_with = "serialize_fr", deserialize_with = "deserialize_fr")]
18    pub timelock: Fr,
19    #[serde(serialize_with = "serialize_fr", deserialize_with = "deserialize_fr")]
20    pub hashlock: Fr,
21}
22
23impl std::fmt::Debug for Note {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        f.debug_struct("Note")
26            .field("asset_id", &self.asset_id)
27            .field("value", &self.value)
28            .field("secret", &"[REDACTED]")
29            .field("nullifier", &"[REDACTED]")
30            .field("timelock", &self.timelock)
31            .field("hashlock", &self.hashlock)
32            .finish()
33    }
34}
35
36impl Note {
37    #[must_use]
38    pub fn serialize_to_bytes(&self) -> Vec<u8> {
39        let mut bytes = Vec::with_capacity(192);
40        bytes.extend_from_slice(&self.asset_id.into_bigint().to_bytes_be());
41        bytes.extend_from_slice(&self.value.into_bigint().to_bytes_be());
42        bytes.extend_from_slice(&self.secret.into_bigint().to_bytes_be());
43        bytes.extend_from_slice(&self.nullifier.into_bigint().to_bytes_be());
44        bytes.extend_from_slice(&self.timelock.into_bigint().to_bytes_be());
45        bytes.extend_from_slice(&self.hashlock.into_bigint().to_bytes_be());
46        bytes
47    }
48
49    /// Deserialize from a 192-byte big-endian representation. Returns `None` if length != 192.
50    #[must_use]
51    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
52        if bytes.len() != 192 {
53            return None;
54        }
55        Some(Self {
56            asset_id: Fr::from_be_bytes_mod_order(&bytes[0..32]),
57            value: Fr::from_be_bytes_mod_order(&bytes[32..64]),
58            secret: Fr::from_be_bytes_mod_order(&bytes[64..96]),
59            nullifier: Fr::from_be_bytes_mod_order(&bytes[96..128]),
60            timelock: Fr::from_be_bytes_mod_order(&bytes[128..160]),
61            hashlock: Fr::from_be_bytes_mod_order(&bytes[160..192]),
62        })
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use ark_ff::One;
70    use ark_std::Zero;
71
72    fn sample_note() -> Note {
73        Note {
74            asset_id: Fr::from(42u64),
75            value: Fr::from(1000u64),
76            secret: Fr::from(999u64),
77            nullifier: Fr::from(777u64),
78            timelock: Fr::zero(),
79            hashlock: Fr::zero(),
80        }
81    }
82
83    #[test]
84    fn roundtrip_serialize_deserialize() {
85        let note = sample_note();
86        let bytes = note.serialize_to_bytes();
87        assert_eq!(bytes.len(), 192);
88        let decoded = Note::from_bytes(&bytes).expect("should decode");
89        assert_eq!(note, decoded);
90    }
91
92    #[test]
93    fn from_bytes_wrong_length() {
94        assert!(Note::from_bytes(&[0u8; 191]).is_none());
95        assert!(Note::from_bytes(&[0u8; 193]).is_none());
96        assert!(Note::from_bytes(&[]).is_none());
97    }
98
99    #[test]
100    fn roundtrip_with_one_values() {
101        let note = Note {
102            asset_id: Fr::one(),
103            value: Fr::one(),
104            secret: Fr::one(),
105            nullifier: Fr::one(),
106            timelock: Fr::one(),
107            hashlock: Fr::one(),
108        };
109        let bytes = note.serialize_to_bytes();
110        let decoded = Note::from_bytes(&bytes).expect("should decode");
111        assert_eq!(note, decoded);
112    }
113
114    #[test]
115    fn roundtrip_with_zero_values() {
116        let note = Note {
117            asset_id: Fr::zero(),
118            value: Fr::zero(),
119            secret: Fr::zero(),
120            nullifier: Fr::zero(),
121            timelock: Fr::zero(),
122            hashlock: Fr::zero(),
123        };
124        let bytes = note.serialize_to_bytes();
125        let decoded = Note::from_bytes(&bytes).expect("should decode");
126        assert_eq!(note, decoded);
127    }
128}