Skip to main content

miden_protocol/note/
nullifier.rs

1use alloc::string::String;
2use core::fmt::{Debug, Display, Formatter};
3
4use miden_core::WORD_SIZE;
5use miden_crypto::WordError;
6use miden_crypto_derive::WordWrapper;
7
8use super::{
9    ByteReader,
10    ByteWriter,
11    Deserializable,
12    DeserializationError,
13    Felt,
14    Hasher,
15    Serializable,
16    Word,
17    ZERO,
18};
19use crate::note::{NoteDetails, NoteMetadata, NoteScriptRoot};
20
21// CONSTANTS
22// ================================================================================================
23
24const NULLIFIER_PREFIX_SHIFT: u8 = 48;
25
26// NULLIFIER
27// ================================================================================================
28
29/// A note's nullifier.
30///
31/// A note's nullifier is computed as:
32///
33/// > `hash(SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT, ASSET_COMMITMENT, METADATA,
34/// > ATTACHMENTS_COMMITMENT)`.
35///
36/// This achieves the following properties:
37/// - Every note can be reduced to a single unique nullifier.
38/// - We cannot derive a note's ID from its nullifier, or a note's nullifier from its ID.
39/// - To compute the nullifier we must know all components of the note: serial_num, script_root,
40///   storage_commitment, asset_commitment, metadata and attachments_commitment.
41#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, WordWrapper)]
42pub struct Nullifier(Word);
43
44impl Nullifier {
45    /// Returns a new note [Nullifier] instantiated from the provided note components.
46    pub fn new(
47        script_root: NoteScriptRoot,
48        storage_commitment: Word,
49        asset_commitment: Word,
50        serial_num: Word,
51        metadata_word: Word,
52        attachments_commitment: Word,
53    ) -> Self {
54        let mut elements = [ZERO; 6 * WORD_SIZE];
55        elements[..4].copy_from_slice(serial_num.as_elements());
56        elements[4..8].copy_from_slice(script_root.as_elements());
57        elements[8..12].copy_from_slice(storage_commitment.as_elements());
58        elements[12..16].copy_from_slice(asset_commitment.as_elements());
59        elements[16..20].copy_from_slice(metadata_word.as_elements());
60        elements[20..24].copy_from_slice(attachments_commitment.as_elements());
61        Self(Hasher::hash_elements(&elements))
62    }
63
64    /// Returns a new note [Nullifier] instantiated from the provided note details and metadata.
65    pub fn from_details_and_metadata(details: &NoteDetails, metadata: &NoteMetadata) -> Self {
66        Self::new(
67            details.script().root(),
68            details.storage().commitment(),
69            details.assets().commitment(),
70            details.serial_num(),
71            metadata.to_metadata_word(),
72            metadata.attachments_commitment(),
73        )
74    }
75
76    /// Returns the most significant felt (the last element in array)
77    pub fn most_significant_felt(&self) -> Felt {
78        self.as_elements()[3]
79    }
80
81    /// Returns the prefix of this nullifier.
82    ///
83    /// Nullifier prefix is defined as the 16 most significant bits of the nullifier value.
84    pub fn prefix(&self) -> u16 {
85        (self.as_word()[3].as_canonical_u64() >> NULLIFIER_PREFIX_SHIFT) as u16
86    }
87
88    /// Creates a Nullifier from a hex string. Assumes that the string starts with "0x" and
89    /// that the hexadecimal characters are big-endian encoded.
90    ///
91    /// Callers must ensure the provided value is an actual [`Nullifier`].
92    pub fn from_hex(hex_value: &str) -> Result<Self, WordError> {
93        Word::try_from(hex_value).map(Self::from_raw)
94    }
95
96    #[cfg(any(feature = "testing", test))]
97    pub fn dummy(n: u64) -> Self {
98        Self(Word::new([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new_unchecked(n)]))
99    }
100}
101
102impl Display for Nullifier {
103    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
104        f.write_str(&self.to_hex())
105    }
106}
107
108impl Debug for Nullifier {
109    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
110        Display::fmt(self, f)
111    }
112}
113
114// SERIALIZATION
115// ================================================================================================
116
117impl Serializable for Nullifier {
118    fn write_into<W: ByteWriter>(&self, target: &mut W) {
119        target.write_bytes(&self.0.to_bytes());
120    }
121
122    fn get_size_hint(&self) -> usize {
123        Word::SERIALIZED_SIZE
124    }
125}
126
127impl Deserializable for Nullifier {
128    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
129        let nullifier = Word::read_from(source)?;
130        Ok(Self(nullifier))
131    }
132}
133
134// TESTS
135// ================================================================================================
136
137#[cfg(test)]
138mod tests {
139    use crate::note::Nullifier;
140
141    #[test]
142    fn test_from_hex_and_back() {
143        let nullifier_hex = "0x41e7dbbc8ce63ec25cf2d76d76162f16ef8fd1195288171f5e5a3e178222f6d2";
144        let nullifier = Nullifier::from_hex(nullifier_hex).unwrap();
145
146        assert_eq!(nullifier_hex, nullifier.to_hex());
147    }
148}