miden_objects/note/
nullifier.rs

1use alloc::string::String;
2use core::fmt::{Debug, Display, Formatter};
3
4use super::{
5    ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, Felt, Hasher,
6    NoteDetails, Serializable, WORD_SIZE, Word, ZERO,
7};
8use crate::utils::{HexParseError, hex_to_bytes};
9
10// CONSTANTS
11// ================================================================================================
12
13const NULLIFIER_PREFIX_SHIFT: u8 = 48;
14
15// NULLIFIER
16// ================================================================================================
17
18/// A note's nullifier.
19///
20/// A note's nullifier is computed as:
21///
22/// > hash(serial_num, script_root, input_commitment, asset_commitment).
23///
24/// This achieves the following properties:
25/// - Every note can be reduced to a single unique nullifier.
26/// - We cannot derive a note's commitment from its nullifier, or a note's nullifier from its hash.
27/// - To compute the nullifier we must know all components of the note: serial_num, script_root,
28///   input_commitment and asset_commitment.
29#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
30pub struct Nullifier(Digest);
31
32impl Nullifier {
33    /// Returns a new note [Nullifier] instantiated from the provided digest.
34    pub fn new(
35        script_root: Digest,
36        inputs_commitment: Digest,
37        asset_commitment: Digest,
38        serial_num: Word,
39    ) -> Self {
40        let mut elements = [ZERO; 4 * WORD_SIZE];
41        elements[..4].copy_from_slice(&serial_num);
42        elements[4..8].copy_from_slice(script_root.as_elements());
43        elements[8..12].copy_from_slice(inputs_commitment.as_elements());
44        elements[12..].copy_from_slice(asset_commitment.as_elements());
45        Self(Hasher::hash_elements(&elements))
46    }
47
48    /// Returns the elements of this nullifier.
49    pub fn as_elements(&self) -> &[Felt] {
50        self.0.as_elements()
51    }
52
53    /// Returns the most significant felt (the last element in array)
54    pub fn most_significant_felt(&self) -> Felt {
55        self.as_elements()[3]
56    }
57
58    /// Returns the digest defining this nullifier.
59    pub fn inner(&self) -> Digest {
60        self.0
61    }
62
63    /// Returns the prefix of this nullifier.
64    ///
65    /// Nullifier prefix is defined as the 16 most significant bits of the nullifier value.
66    pub fn prefix(&self) -> u16 {
67        (self.inner()[3].as_int() >> NULLIFIER_PREFIX_SHIFT) as u16
68    }
69
70    /// Creates a Nullifier from a hex string. Assumes that the string starts with "0x" and
71    /// that the hexadecimal characters are big-endian encoded.
72    pub fn from_hex(hex_value: &str) -> Result<Self, HexParseError> {
73        hex_to_bytes(hex_value).and_then(|bytes: [u8; 32]| {
74            let digest = Digest::try_from(bytes)?;
75            Ok(digest.into())
76        })
77    }
78
79    /// Returns a big-endian, hex-encoded string.
80    pub fn to_hex(&self) -> String {
81        self.0.to_hex()
82    }
83
84    #[cfg(any(feature = "testing", test))]
85    pub fn dummy(n: u64) -> Self {
86        use vm_core::FieldElement;
87
88        Self(Digest::new([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(n)]))
89    }
90}
91
92impl Display for Nullifier {
93    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
94        f.write_str(&self.to_hex())
95    }
96}
97
98impl Debug for Nullifier {
99    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
100        Display::fmt(self, f)
101    }
102}
103
104// CONVERSIONS INTO NULLIFIER
105// ================================================================================================
106
107impl From<&NoteDetails> for Nullifier {
108    fn from(note: &NoteDetails) -> Self {
109        Self::new(
110            note.script().root(),
111            note.inputs().commitment(),
112            note.assets().commitment(),
113            note.serial_num(),
114        )
115    }
116}
117
118impl From<Word> for Nullifier {
119    fn from(value: Word) -> Self {
120        Self(value.into())
121    }
122}
123
124impl From<Digest> for Nullifier {
125    fn from(value: Digest) -> Self {
126        Self(value)
127    }
128}
129
130// CONVERSIONS FROM NULLIFIER
131// ================================================================================================
132
133impl From<Nullifier> for Word {
134    fn from(nullifier: Nullifier) -> Self {
135        nullifier.0.into()
136    }
137}
138
139impl From<Nullifier> for [u8; 32] {
140    fn from(nullifier: Nullifier) -> Self {
141        nullifier.0.into()
142    }
143}
144
145impl From<&Nullifier> for Word {
146    fn from(nullifier: &Nullifier) -> Self {
147        nullifier.0.into()
148    }
149}
150
151impl From<&Nullifier> for [u8; 32] {
152    fn from(nullifier: &Nullifier) -> Self {
153        nullifier.0.into()
154    }
155}
156
157// SERIALIZATION
158// ================================================================================================
159
160impl Serializable for Nullifier {
161    fn write_into<W: ByteWriter>(&self, target: &mut W) {
162        target.write_bytes(&self.0.to_bytes());
163    }
164}
165
166impl Deserializable for Nullifier {
167    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
168        let nullifier = Digest::read_from(source)?;
169        Ok(Self(nullifier))
170    }
171}
172
173// TESTS
174// ================================================================================================
175
176#[cfg(test)]
177mod tests {
178    use crate::note::Nullifier;
179
180    #[test]
181    fn test_from_hex_and_back() {
182        let nullifier_hex = "0x41e7dbbc8ce63ec25cf2d76d76162f16ef8fd1195288171f5e5a3e178222f6d2";
183        let nullifier = Nullifier::from_hex(nullifier_hex).unwrap();
184
185        assert_eq!(nullifier_hex, nullifier.to_hex());
186    }
187}