-
Notifications
You must be signed in to change notification settings - Fork 599
fix: claim contract & improve nullif docs #21234
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
ad081d0
fix claim contract em itting raw nullifier
nventuro 3154123
improve docs for nullifs
nventuro 68fa603
add links
nventuro 9a16761
fix typos and improve docs clarity
nventuro bd78b0b
remove unused NoteHash import, fix cspell and doc link paths
nventuro 41af90c
fix: update e2e test expectation after hinted_note -> confirmed_note …
AztecBot c7cc346
Merge branch 'merge-train/fairies' into nv/fix-claim
nventuro b62709c
fix: typo nullifer -> nullifier in migration_notes.md
AztecBot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,50 @@ | ||
| //! Nullifier-related utilities. | ||
|
|
||
| //! | ||
| //! Nullifiers are one of the key primitives of private state. A nullifier is a `Field` value that is stored in one of | ||
| //! the Aztec state trees: the nullifier tree. Only unique values can be inserted into this tree: attempting to create | ||
| //! an | ||
| //! already existing nullifier (a duplicate nullifier) will result in either the transaction being unprovable, invalid, | ||
| //! or reverting, depending on exactly when the duplicate is created. | ||
| //! | ||
| //! Generally, nullifiers are used to prevent an action from happening more than once, or to more generally 'consume' a | ||
| //! resource. This can include preventing re-initialization of contracts, replay attacks of signatures, repeated claims | ||
| //! of a deposit, double-spends of received funds, etc. To achieve this, nullifiers must be computed | ||
| //! **deterministically** from the resource they're consuming. For example a contract initialization nullifier might | ||
| //! use | ||
| //! its address, or a signature replay protection could use the signature hash. | ||
| //! | ||
| //! One of the key properties of nullifiers is that they can be created by private functions, resulting in transactions | ||
| //! that do not reveal which actions they've performed. Their computation often involves a **secret parameter**, often | ||
| //! derived from a nullifier hiding key (`nhk`) which prevents linking of the resource that was consumed from the | ||
| //! nullifier. For example, it is not possible to determine which nullifier corresponds to a given note hash without | ||
| //! knowledge of the `nhk`, and so the transactions that created the note and nullifier remain unlinked. | ||
|
Comment on lines
+16
to
+20
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. 🙌 |
||
| //! | ||
| //! In other words, a nullifier is (in most cases) a random-looking but deterministic record of a private, one-time | ||
| //! action, which does not leak what action has been taken, and which preserves the property of transaction | ||
| //! unlinkability. | ||
| //! | ||
| //! In some cases, nullifiers cannot be secret as knowledge of them **must** be public information. For example, | ||
| //! contracts used by multiple people (like tokens) cannot have secrets in their initialization nullifiers: for users | ||
| //! to | ||
| //! use the contract they must prove that it has been initialized, and this requires them being able to compute the | ||
| //! initialization nullifier. | ||
| //! | ||
| //! ## Nullifier Creation | ||
| //! | ||
| //! The low-level mechanisms to create new nullifiers are [`crate::context::PrivateContext::push_nullifier`] and | ||
| //! [`crate::context::PublicContext::push_nullifier`], but these require care and can be hard to use correctly. | ||
| //! Higher-level abstractions exist which safely create nullifiers, such as [`crate::note::lifecycle::destroy_note`] | ||
| //! and | ||
| //! [`crate::state_vars::SingleUseClaim`]. | ||
| //! | ||
| //! ## Reading Nullifiers | ||
| //! | ||
| //! Private functions can prove that nullifiers have been created via | ||
| //! [`crate::context::PrivateContext::assert_nullifier_exists`] and | ||
| //! [`crate::history::nullifier::assert_nullifier_existed_by`], but the only general mechanism to privately prove that | ||
| //! a | ||
| //! nullifier _does not_ exist is to create it - which can only be done once. | ||
| //! | ||
| //! Public functions on the other hand can prove both nullifier existence and non-existence via | ||
| //! [`crate::context::PublicContext::nullifier_exists_unsafe`]. | ||
| pub mod utils; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,73 +7,77 @@ pub contract Claim { | |
| // docs:end:history_import | ||
| use aztec::{ | ||
| macros::{functions::{external, initializer}, storage::storage}, | ||
| note::{ | ||
| HintedNote, note_interface::NoteHash, | ||
| utils::compute_confirmed_note_hash_for_nullification, | ||
| }, | ||
| protocol::address::AztecAddress, | ||
| state_vars::PublicImmutable, | ||
| note::{HintedNote, utils::compute_confirmed_note_hash_for_nullification}, | ||
| protocol::{address::AztecAddress, traits::Packable}, | ||
| state_vars::{Map, Owned, PublicImmutable, SingleUseClaim}, | ||
| }; | ||
| use token::Token; | ||
| use uint_note::UintNote; | ||
|
|
||
| // TODO: This can be optimized by storing the addresses in Config struct in 1 PublicImmutable (less merkle proofs). | ||
| #[storage] | ||
| struct Storage<Context> { | ||
| #[derive(Eq, Packable)] | ||
| struct ClaimConfig { | ||
| // Address of a contract based on whose notes we distribute the rewards | ||
| target_contract: PublicImmutable<AztecAddress, Context>, | ||
| target_contract: AztecAddress, | ||
| // Token to be distributed as a reward when claiming | ||
| reward_token: PublicImmutable<AztecAddress, Context>, | ||
| reward_token: AztecAddress, | ||
| } | ||
|
|
||
| #[storage] | ||
| struct Storage<Context> { | ||
| config: PublicImmutable<ClaimConfig, Context>, | ||
| // Maps a note hash to owner-specific single-use claims, preventing double-claiming of rewards. | ||
| note_hash_claims: Map<Field, Owned<SingleUseClaim<Context>, Context>, Context>, | ||
|
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. A comment describing what the Map's key Field represents would be nice here |
||
| } | ||
|
|
||
| #[external("public")] | ||
| #[initializer] | ||
| fn constructor(target_contract: AztecAddress, reward_token: AztecAddress) { | ||
| self.storage.target_contract.initialize(target_contract); | ||
| self.storage.reward_token.initialize(reward_token); | ||
| self.storage.config.initialize(ClaimConfig { target_contract, reward_token }); | ||
| } | ||
|
|
||
| #[external("private")] | ||
| fn claim(hinted_note: HintedNote<UintNote>, recipient: AztecAddress) { | ||
| // 1) Check that the note corresponds to the target contract | ||
| let target_address = self.storage.target_contract.read(); | ||
| // 1) Prove that the note exists | ||
| // docs:start:prove_note_inclusion | ||
| let header = self.context.get_anchor_block_header(); | ||
| let confirmed_note = assert_note_existed_by(header, hinted_note); | ||
| // docs:end:prove_note_inclusion | ||
|
|
||
| let config = self.storage.config.read(); | ||
|
|
||
| // 2) Check that the note corresponds to the target contract | ||
| assert( | ||
| target_address == hinted_note.contract_address, | ||
| config.target_contract == confirmed_note.contract_address, | ||
| "Note does not correspond to the target contract", | ||
| ); | ||
|
|
||
| // 2) Check that the note is owned by the msg_sender | ||
| assert(hinted_note.owner == self.msg_sender(), "Note is not owned by the msg_sender"); | ||
| // 3) Check that the note is owned by the msg_sender | ||
| assert(confirmed_note.owner == self.msg_sender(), "Note is not owned by the msg_sender"); | ||
|
|
||
| // Given that there is only 1 state variable in the Crowdfunding contract we don't need to constrain the storage | ||
| // slot of the note as there is no risk of claiming with a note that is not a donation note. | ||
|
|
||
| // 3) Prove that the note hash exists in the note hash tree | ||
| // docs:start:prove_note_inclusion | ||
| let header = self.context.get_anchor_block_header(); | ||
| let confirmed_note = assert_note_existed_by(header, hinted_note); | ||
| // docs:end:prove_note_inclusion | ||
|
|
||
| // 4) Compute and emit a nullifier which is unique to the note and this contract to ensure the reward can be | ||
| // claimed only once with the given note. | ||
| // Note: Only the owner of the npk_m will be able to produce the nhk_app and compute this nullifier. | ||
| // The nullifier is unique to the note and THIS contract because the protocol siloes all nullifiers with | ||
| // the address of a contract it was emitted from. | ||
| // TODO(#7775): manually computing the hash and passing it to compute_nullifier func is not great as note could | ||
| // handle it on its own or we could make assert_note_existed_by return note_hash_for_nullification. | ||
| // 4) Consume the claim of this note, ensuring the reward can be claimed only once with the given note. Each | ||
| // claim being tied to its owner results in unlinkability of the claim's nullifier and the underlying note | ||
| // hash via the owner's nullifier hiding key (`nhk`). | ||
| // | ||
| // Note: we're using the note's hash as the claim identifier, as it serves as a unique identifier (because we | ||
| // know the note is settled, and hence its note hash unique). This is not the same thing as nullifying the note | ||
| // however, which can only be done from the contract that emitted the note. From the point of view of the | ||
| // Crowdfunding contract, the note is still active. | ||
| let note_hash_for_nullification = | ||
| compute_confirmed_note_hash_for_nullification(confirmed_note); | ||
| let nullifier = hinted_note.note.compute_nullifier( | ||
| self.context, | ||
| hinted_note.owner, | ||
| note_hash_for_nullification, | ||
| ); | ||
| self.context.push_nullifier(nullifier); | ||
| self | ||
| .storage | ||
| .note_hash_claims | ||
| .at(note_hash_for_nullification) | ||
| .at(confirmed_note.owner) | ||
nventuro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .claim(); | ||
|
|
||
| // 5) Finally we mint the reward token to the sender of the transaction | ||
| self.enqueue(Token::at(self.storage.reward_token.read()).mint_to_public( | ||
| // 5) Finally we mint the reward token to the requested recipient | ||
| self.enqueue(Token::at(config.reward_token).mint_to_public( | ||
| recipient, | ||
| hinted_note.note.value, | ||
| confirmed_note.note.value, | ||
| )); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.