Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 46 additions & 13 deletions src/nft_contract/src/types/nft_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ use aztec::{
delivery::{do_private_message_delivery, MessageDelivery},
logs::partial_note::encode_partial_note_private_message,
},
note::{note_interface::{NoteHash, NoteType}, utils::compute_note_nullifier},
note::{
note_interface::{NoteHash, NoteType},
utils::{compute_note_hash, compute_note_nullifier},
},
oracle::random::random,
protocol::{
address::AztecAddress,
constants::{
DOM_SEP__NOTE_COMPLETION_LOG_TAG, DOM_SEP__NOTE_HASH,
DOM_SEP__NOTE_COMPLETION_LOG_TAG, DOM_SEP__PARTIAL_NOTE_COMMITMENT,
DOM_SEP__PARTIAL_NOTE_VALIDITY_COMMITMENT,
},
hash::{compute_log_tag, poseidon2_hash_with_separator},
Expand Down Expand Up @@ -49,7 +52,7 @@ impl NoteHash for NFTNote {
// values, so that notes all behave the same way regardless of how they were created. To achieve this, we
// perform both steps of the partial note computation.

// First we create the partial note from a commitment to the private content (including storage slot).
// First we create the partial note from a commitment to the private content.
let partial_note =
PartialNFTNote { commitment: compute_partial_commitment(owner, randomness) };

Expand Down Expand Up @@ -151,7 +154,10 @@ impl NFTNote {
/// Computes a commitment to the private content of a partial NFTNote, i.e. the fields that will remain private. All
/// other note fields will be made public.
fn compute_partial_commitment(owner: AztecAddress, randomness: Field) -> Field {
poseidon2_hash_with_separator([owner.to_field(), randomness], DOM_SEP__NOTE_HASH)
poseidon2_hash_with_separator(
[owner.to_field(), randomness],
DOM_SEP__PARTIAL_NOTE_COMMITMENT,
)
}

#[derive(Packable)]
Expand All @@ -165,8 +171,9 @@ impl NoteType for NFTPartialNotePrivateLogContent {
}
}

/// A partial instance of a NFTNote. This value represents a private commitment to the owner, randomness and storage
/// slot, but the token id field has not yet been set. A partial note can be completed in public with the `complete`
/// A partial instance of a NFTNote. This value represents a private commitment to the owner and randomness (the storage
/// slot is folded into the completed note hash, not the commitment), but the token id field has not yet been set. A
/// partial note can be completed in public with the `complete`
/// function (revealing the token id to the public), resulting in a NFTNote that can be used like any other one (except
/// of course that its token id is known).
#[derive(Packable, Serialize, Deserialize)]
Expand Down Expand Up @@ -224,12 +231,38 @@ impl PartialNFTNote {
}

fn compute_complete_note_hash(self, storage_slot: Field, token_id: Field) -> Field {
// Here we finalize the note hash by including the (public) storage slot and token id into the partial note
// commitment. Note that we use the same separator as we used for the first round of poseidon - this is not
// an issue.
poseidon2_hash_with_separator(
[self.commitment, storage_slot, token_id],
DOM_SEP__NOTE_HASH,
)
// Finalize the note hash by folding the (public) storage slot and token id into the partial note commitment.
// `compute_note_hash` fixes the storage slot at the first position of the preimage (preventing cross-slot
// collisions) and appends the remaining fields in order.
compute_note_hash(storage_slot, [self.commitment, token_id])
}
}

mod test {
use super::{compute_partial_commitment, NFTNote, PartialNFTNote};
use aztec::{
note::note_interface::NoteHash,
protocol::{address::AztecAddress, traits::FromField},
};

global token_id: Field = 17;
global randomness: Field = 42;
global owner: AztecAddress = AztecAddress::from_field(50);
global storage_slot: Field = 13;

#[test]
fn note_hash_matches_completed_partial_note_hash() {
// A NFTNote must have the same note hash as a PartialNFTNote created and then completed with the same private
// values. This requires the same hash function in both flows, with the fields in the same order — the invariant
// that lets a directly-created note and a completed partial note be used interchangeably.
let note = NFTNote { token_id };
let note_hash = note.compute_note_hash(owner, storage_slot, randomness);

let partial_note =
PartialNFTNote { commitment: compute_partial_commitment(owner, randomness) };
let completed_partial_note_hash =
partial_note.compute_complete_note_hash(storage_slot, token_id);

assert_eq(note_hash, completed_partial_note_hash);
}
}
Loading