-
Notifications
You must be signed in to change notification settings - Fork 615
feat: add note metadata #12240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add note metadata #12240
Changes from 8 commits
603c5d8
70f483d
e999f39
622e89b
7045db6
8aea1a3
e8ee039
fe2298a
a8bbad4
ae7cfb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -29,9 +29,16 @@ pub trait NullifiableNote { | |||||
| /// circuits. | ||||||
| fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field; | ||||||
|
|
||||||
| /// Same as compute_nullifier, but unconstrained. This version does not take a note hash because it'll only be | ||||||
| /// invoked in unconstrained contexts, where there is no gate count. Note that it also takes a contract address: | ||||||
| /// this is because nullifier computation typically involves contract siloed nullifying keys. | ||||||
| /// Like `compute_nullifier`, this function also computes a note's nullifier, but there are some key differences. | ||||||
| /// | ||||||
| /// First and most importantly, this function is unconstrained: there are no guarantees on the returned value being | ||||||
| /// correct. Because of that it doesn't need to take a context (since it won't perform any kernel key validation | ||||||
| /// requests) nor note hash (since it would be meaningless to tie this to a note that has been read). | ||||||
| /// | ||||||
| /// Second, it always compute the nullifier for a **settled** note, i.e. a note that has been created in a previous | ||||||
|
nventuro marked this conversation as resolved.
Outdated
|
||||||
| /// transaction, which therefore has a nonce. This is typically fine, since this function will mostly be used in | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think readers would generally have no clue why this implies that it has a nonce.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After some reflection I think we just simplify this fn so that it also takes a note hash. I'll do that once both our PRs are in. |
||||||
| /// unconstrained execution contexts, which exist outside of any transaction and therefore have no concept of | ||||||
| /// pending notes, only settled. | ||||||
| unconstrained fn compute_nullifier_without_context( | ||||||
| self, | ||||||
| storage_slot: Field, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| use protocol_types::traits::Serialize; | ||
|
|
||
| // There's temporarily quite a bit of boilerplate here because Noir does not yet support enums. This file will | ||
| // eventually be simplified into something closer to: | ||
| // | ||
| // pub enum NoteMetadata { | ||
| // PendingSamePhase{ note_hash_counter: u32 }, | ||
| // PendingOtherPhase{ note_hash_counter: u32, nonce: Field }, | ||
| // Settled{ nonce: Field }, | ||
| // } | ||
| // | ||
| // For now, we have `NoteMetadata` acting as a sort of tagged union. | ||
|
|
||
| struct NoteStageEnum { | ||
| /// A note that was created in the transaction that is currently being executed, during the current execution phase, | ||
| /// i.e. non-revertible or revertible. | ||
| /// | ||
| /// These notes are not yet in the note hash tree, though they will be inserted unless nullified in this transaction | ||
| /// (becoming a transient note). | ||
| PENDING_SAME_PHASE: u8, | ||
| /// A note that was created in the transaction that is currently being executed, during the previous execution | ||
| /// phase. Because there are only two phases and their order is always the same (first non-revertible and then | ||
| /// revertible) this implies that the note was created in the non-revertible phase, and that the current phase is | ||
| /// the revertible phase. | ||
| /// | ||
| /// These notes are not yet in the note hash tree, though they will be inserted **even if nullified in this | ||
| /// transaction**. This means that they must be nullified as if they were settled (i.e. using the unique note hash) | ||
| /// in order to avoid double spends once they become settled. | ||
| PENDING_PREVIOUS_PHASE: u8, | ||
| /// A note that was created in a prior transaction and is therefore already in the note hash tree. | ||
| SETTLED: u8, | ||
| } | ||
|
|
||
| global NoteStage: NoteStageEnum = | ||
| NoteStageEnum { PENDING_SAME_PHASE: 1, PENDING_PREVIOUS_PHASE: 2, SETTLED: 3 }; | ||
|
|
||
| /// The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel | ||
| /// read requests, as well as the correct nullifier to avoid double-spends. | ||
| /// | ||
| /// This represents a note in any of the three valid stages (pending same phase, pending previous phase, or settled). In | ||
| /// order to access the underlying fields callers must first find the appropriate stage (e.g. via `is_settled()`) and | ||
| /// then convert this into the appropriate type (e.g. via `to_settled()`). | ||
| #[derive(Eq, Serialize)] | ||
| pub struct NoteMetadata { | ||
| stage: u8, | ||
| maybe_nonce: Field, | ||
| } | ||
|
|
||
| impl NoteMetadata { | ||
| /// Constructs a `NoteMetadata` object from optional note hash counter and nonce. Both a zero note hash counter and | ||
| /// a zero nonce are invalid, so those are used to signal non-existent values. | ||
| pub fn from_raw_data(nonzero_note_hash_counter: bool, maybe_nonce: Field) -> Self { | ||
| if nonzero_note_hash_counter { | ||
| if maybe_nonce == 0 { | ||
| Self { stage: NoteStage.PENDING_SAME_PHASE, maybe_nonce } | ||
| } else { | ||
| Self { stage: NoteStage.PENDING_PREVIOUS_PHASE, maybe_nonce } | ||
| } | ||
| } else if maybe_nonce != 0 { | ||
| Self { stage: NoteStage.SETTLED, maybe_nonce } | ||
| } else { | ||
| panic( | ||
| f"Note has a zero note hash counter and no nonce - existence cannot be proven", | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// Returns true if the note is pending **and** from the same phase, i.e. if it's been created in the current | ||
| /// transaction during the current execution phase (either non-revertible or revertible). | ||
| pub fn is_pending_same_phase(self) -> bool { | ||
| self.stage == NoteStage.PENDING_SAME_PHASE | ||
| } | ||
|
|
||
| /// Returns true if the note is pending **and** from the previous phase, i.e. if it's been created in the current | ||
| /// transaction during an execution phase prior to the current one. Because private execution only has two phases | ||
| /// with strict ordering, this implies that the note was created in the non-revertible phase, and that the current | ||
| /// phase is the revertible phase. | ||
| pub fn is_pending_previous_phase(self) -> bool { | ||
| self.stage == NoteStage.PENDING_PREVIOUS_PHASE | ||
| } | ||
|
|
||
| /// Returns true if the note is settled, i.e. if it's been created in a prior transaction and is therefore already | ||
| /// in the note hash tree. | ||
| pub fn is_settled(self) -> bool { | ||
| self.stage == NoteStage.SETTLED | ||
| } | ||
|
|
||
| /// Asserts that the metadata is that of a pending note from the same phase and converts it accordingly. | ||
| pub fn to_pending_same_phase(self) -> PendingSamePhaseNoteMetadata { | ||
| assert_eq(self.stage, NoteStage.PENDING_SAME_PHASE); | ||
| PendingSamePhaseNoteMetadata::new() | ||
| } | ||
|
|
||
| /// Asserts that the metadata is that of a pending note from a previous phase and converts it accordingly. | ||
| pub fn to_pending_previous_phase(self) -> PendingPreviousPhaseNoteMetadata { | ||
| assert_eq(self.stage, NoteStage.PENDING_PREVIOUS_PHASE); | ||
| PendingPreviousPhaseNoteMetadata::new(self.maybe_nonce) | ||
| } | ||
|
|
||
| /// Asserts that the metadata is that of a settled note and converts it accordingly. | ||
| pub fn to_settled(self) -> SettledNoteMetadata { | ||
| assert_eq(self.stage, NoteStage.SETTLED); | ||
| SettledNoteMetadata::new(self.maybe_nonce) | ||
| } | ||
| } | ||
|
|
||
| impl From<PendingSamePhaseNoteMetadata> for NoteMetadata { | ||
| fn from(_value: PendingSamePhaseNoteMetadata) -> Self { | ||
| NoteMetadata::from_raw_data(true, std::mem::zeroed()) | ||
| } | ||
| } | ||
|
|
||
| impl From<PendingPreviousPhaseNoteMetadata> for NoteMetadata { | ||
| fn from(value: PendingPreviousPhaseNoteMetadata) -> Self { | ||
| NoteMetadata::from_raw_data(true, value.nonce()) | ||
| } | ||
| } | ||
|
|
||
| impl From<SettledNoteMetadata> for NoteMetadata { | ||
| fn from(value: SettledNoteMetadata) -> Self { | ||
| NoteMetadata::from_raw_data(false, value.nonce()) | ||
| } | ||
| } | ||
|
|
||
| /// The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel | ||
| /// read requests, as well as the correct nullifier to avoid double-spends. | ||
| /// | ||
| /// This represents a pending same phase note, i.e. a note that was created in the transaction that is currently being | ||
| /// executed during the current execution phase (either non-revertible o revertible). | ||
|
nventuro marked this conversation as resolved.
Outdated
|
||
| pub struct PendingSamePhaseNoteMetadata { | ||
| // This struct contains no fields since there is no metadata associated with a pending same phase note: it has no | ||
| // nonce (since it may get squashed by a nullifier emitted in the same phase), and while it does have a note hash | ||
| // counter we cannot constrain its value (and don't need to - only that it is non-zero). | ||
| } | ||
|
|
||
| impl PendingSamePhaseNoteMetadata { | ||
| pub fn new() -> Self { | ||
| Self {} | ||
| } | ||
| } | ||
|
|
||
| /// The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel | ||
| /// read requests, as well as the correct nullifier to avoid double-spends. | ||
| /// | ||
| /// This represents a pending previous phase note, i.e. a note that was created in the transaction that is currently | ||
| /// being executed, during the previous execution phase. Because there are only two phases and their order is always the | ||
| /// same (first non-revertible and then revertible) this implies that the note was created in the non-revertible phase, | ||
| /// and that the current phase is the revertible phase. | ||
| pub struct PendingPreviousPhaseNoteMetadata { | ||
| nonce: Field, | ||
| // This struct does not contain a note hash counter, even though one exists for this note, because we cannot | ||
| // constrain its value (and don't need to - only that it is non-zero). | ||
| } | ||
|
|
||
| impl PendingPreviousPhaseNoteMetadata { | ||
| pub fn new(nonce: Field) -> Self { | ||
| Self { nonce } | ||
| } | ||
|
|
||
| pub fn nonce(self) -> Field { | ||
| self.nonce | ||
| } | ||
| } | ||
|
|
||
| /// The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel | ||
| /// read requests, as well as the correct nullifier to avoid double-spends. | ||
| /// | ||
| /// This represents a settled note, i.e. a note that was created in a prior transaction and is therefore already in the | ||
| /// note hash tree. | ||
| pub struct SettledNoteMetadata { | ||
| nonce: Field, | ||
| } | ||
|
|
||
| impl SettledNoteMetadata { | ||
| pub fn new(nonce: Field) -> Self { | ||
| Self { nonce } | ||
| } | ||
|
|
||
| pub fn nonce(self) -> Field { | ||
| self.nonce | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,27 @@ | ||
| use protocol_types::{address::AztecAddress, traits::Serialize, utils::arrays::array_concat}; | ||
| use crate::note::note_metadata::NoteMetadata; | ||
| use protocol_types::{ | ||
| address::AztecAddress, | ||
| traits::{Serialize, ToField}, | ||
| utils::arrays::array_concat, | ||
| }; | ||
|
|
||
| /// A container of a note and the metadata required to constrain its existence, regardless of whether the note is | ||
| /// historical (created in a previous transaction) or transient (created in the current transaction). | ||
| /// A container of a note and the metadata required to prove its existence, regardless of whether the note is | ||
| /// pending (created in the current transaction) or settled (created in a previous transaction). | ||
| #[derive(Eq)] | ||
| pub struct RetrievedNote<NOTE> { | ||
| pub note: NOTE, | ||
| pub contract_address: AztecAddress, | ||
| pub nonce: Field, | ||
| pub note_hash_counter: u32, // a note_hash_counter of 0 means non-transient | ||
| pub metadata: NoteMetadata, | ||
| } | ||
|
|
||
| impl<NOTE> Eq for RetrievedNote<NOTE> | ||
| where | ||
| NOTE: Eq, | ||
| { | ||
| fn eq(self, other: Self) -> bool { | ||
| (self.note == other.note) | ||
| & (self.contract_address == other.contract_address) | ||
| & (self.nonce == other.nonce) | ||
| & (self.note_hash_counter == other.note_hash_counter) | ||
| } | ||
| } | ||
|
|
||
| impl<NOTE, let N: u32> Serialize<N + 3> for RetrievedNote<NOTE> | ||
| impl<NOTE, let N: u32> Serialize<N + 1 + 2> for RetrievedNote<NOTE> | ||
| where | ||
| NOTE: Serialize<N>, | ||
| { | ||
| fn serialize(self) -> [Field; N + 3] { | ||
| fn serialize(self) -> [Field; N + 1 + 2] { | ||
| array_concat( | ||
| self.note.serialize(), | ||
| [self.contract_address.to_field(), self.nonce, self.note_hash_counter as Field], | ||
| array_concat(self.note.serialize(), [self.contract_address.to_field()]), | ||
| self.metadata.serialize(), | ||
| ) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.