-
Notifications
You must be signed in to change notification settings - Fork 613
feat: process note logs in aztec-nr #10651
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 7 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
a124c6a
Sketching out initial approach
nventuro ec92180
Success!
nventuro 2f52d31
IT LIVES
nventuro a443446
Misc doc improvements
nventuro dae83d7
Some more minor comments
nventuro 40d2dae
Remove old ts code
nventuro 893ad80
noir formatting
nventuro 4c88865
Merge branch 'master' into nv/process_note_logs
nventuro 206444f
It works!
nventuro 51a7f0b
Add some docs
nventuro 212219a
Merge branch 'master' into nv/process_note_logs
nventuro 646f5ff
Handle no note contracts
nventuro b771c98
Fix macro
nventuro ebf7412
Merge branch 'master' into nv/process_note_logs
nventuro eccd8b6
Fix import
nventuro da8408f
Remove extra file
nventuro d5fe202
Apply suggestions from code review
nventuro 96af47d
Rename foreach
nventuro 48fe292
Move files around
nventuro 7f46d5a
Merge branch 'master' into nv/process_note_logs
nventuro 30cbc8a
If I have to nargo fmt one more time
nventuro 5205cc4
Oh god
nventuro 76bbd1b
zzz
nventuro d44ec2e
kill me now
nventuro 8b7d508
Add node methods to txe node
nventuro ff0127c
Merge branch 'master' into nv/process_note_logs
nventuro 8f56981
Add sim prov
nventuro 72ea7c4
Fix build error
nventuro 36c29e8
fix: simulator oracle test
benesjan d8b24ab
Merge branch 'master' into nv/process_note_logs
benesjan 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| use std::static_assert; | ||
|
|
||
| use crate::{ | ||
| context::unconstrained_context::UnconstrainedContext, note::note_header::NoteHeader, | ||
| utils::array, | ||
| }; | ||
| use dep::protocol_types::{ | ||
| address::AztecAddress, | ||
| constants::{MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS}, | ||
| hash::compute_note_hash_nonce, | ||
| }; | ||
|
|
||
| // We reserve two fields in the note log that are not part of the note content: one for the storage slot, and one for | ||
| // the note type id. | ||
| global NOTE_LOG_RESERVED_FIELDS: u32 = 2; | ||
| global MAX_NOTE_SERIALIZED_LEN: u32 = PRIVATE_LOG_SIZE_IN_FIELDS - NOTE_LOG_RESERVED_FIELDS; | ||
|
|
||
| pub struct NoteHashesAndNullifier { | ||
| pub note_hash: Field, | ||
| pub siloed_note_hash: Field, | ||
| pub inner_nullifier: Field, | ||
| } | ||
|
|
||
| fn for_each_bounded_vec<T, let MaxLen: u32, Env>( | ||
| vec: BoundedVec<T, MaxLen>, | ||
| f: fn[Env](T, u32) -> (), | ||
| ) { | ||
| for i in 0..MaxLen { | ||
| if i < vec.len() { | ||
| f(vec.get_unchecked(i), i); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Processes a log given its plaintext by trying to find notes encoded in it. This process involves the discovery of | ||
| /// the nonce of any such notes, which requires knowledge of the transaction hash in which the notes would've been | ||
| /// created, along with the list of siloed note hashes in said transaction. | ||
| /// | ||
| /// Additionally, this requires a `compute_note_hash_and_nullifier` lambda that is able to compute these values for any | ||
| /// note in the contract given their contents. A typical implementation of such a function would look like this: | ||
| /// | ||
| /// ``` | ||
| /// |serialized_note_content, note_header, note_type_id| { | ||
| /// let hashes = if note_type_id == MyNoteType::get_note_type_id() { | ||
| /// assert(serialized_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); | ||
| /// dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( | ||
| /// MyNoteType::deserialize_content, | ||
| /// note_header, | ||
| /// true, | ||
| /// serialized_note_content.storage(), | ||
| /// ) | ||
| /// } else { | ||
| /// panic(f"Unknown note type id {note_type_id}") | ||
| /// }; | ||
| /// | ||
| /// Option::some(dep::aztec::oracle::management::NoteHashesAndNullifier { | ||
| /// note_hash: hashes[0], | ||
| /// siloed_note_hash: hashes[2], | ||
| /// inner_nullifier: hashes[3], | ||
| /// }) | ||
| /// } | ||
| /// ``` | ||
| pub unconstrained fn process_log<Env>( | ||
| context: UnconstrainedContext, | ||
| log_plaintext: BoundedVec<Field, PRIVATE_LOG_SIZE_IN_FIELDS>, | ||
| tx_hash: Field, | ||
| siloed_note_hashes_in_tx: BoundedVec<Field, MAX_NOTE_HASHES_PER_TX>, | ||
| recipient: AztecAddress, | ||
| compute_note_hash_and_nullifier: fn[Env](BoundedVec<Field, MAX_NOTE_SERIALIZED_LEN>, NoteHeader, Field) -> Option<NoteHashesAndNullifier>, | ||
| ) { | ||
| let (storage_slot, note_type_id, serialized_note_content) = | ||
| destructure_log_plaintext(log_plaintext); | ||
|
|
||
| // We need to find the note's nonce, which is the one that results in one of the siloed note hashes from tx_hash | ||
| for_each_bounded_vec( | ||
| siloed_note_hashes_in_tx, | ||
| |expected_siloed_note_hash, i| { | ||
| let candidate_nonce = compute_note_hash_nonce(tx_hash, i); | ||
|
|
||
| let header = NoteHeader::new(context.this_address(), candidate_nonce, storage_slot); | ||
|
|
||
| // TODO: handle failed note_hash_and_nullifier computation | ||
|
nventuro marked this conversation as resolved.
Outdated
|
||
| let hashes = compute_note_hash_and_nullifier( | ||
| serialized_note_content, | ||
| header, | ||
| note_type_id, | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| if hashes.siloed_note_hash == expected_siloed_note_hash { | ||
| // TODO(#10726): push these into a vec to deliver all at once instead of having one oracle call per note | ||
| deliver_note( | ||
| context.this_address(), // TODO(#10727): allow other contracts to deliver notes | ||
| storage_slot, | ||
| candidate_nonce, | ||
| serialized_note_content, | ||
| hashes.note_hash, | ||
| hashes.inner_nullifier, | ||
| tx_hash, | ||
| recipient, | ||
| ); | ||
|
|
||
| // We don't exit the loop - it is possible (though rare) for the same note content to be present | ||
| // multiple times in the same transaction with different nonces. | ||
| } | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| unconstrained fn destructure_log_plaintext( | ||
| log_plaintext: BoundedVec<Field, PRIVATE_LOG_SIZE_IN_FIELDS>, | ||
| ) -> (Field, Field, BoundedVec<Field, MAX_NOTE_SERIALIZED_LEN>) { | ||
| assert(log_plaintext.len() >= NOTE_LOG_RESERVED_FIELDS); | ||
|
|
||
| static_assert( | ||
| NOTE_LOG_RESERVED_FIELDS == 2, | ||
| "unepxected value for NOTE_LOG_RESERVED_FIELDS", | ||
| ); | ||
| let storage_slot = log_plaintext.get(0); | ||
| let note_type_id = log_plaintext.get(1); | ||
|
|
||
| let serialized_note_content = array::subbvec(log_plaintext, NOTE_LOG_RESERVED_FIELDS); | ||
|
|
||
| (storage_slot, note_type_id, serialized_note_content) | ||
| } | ||
|
|
||
| unconstrained fn deliver_note( | ||
| contract_address: AztecAddress, | ||
| storage_slot: Field, | ||
| nonce: Field, | ||
| content: BoundedVec<Field, MAX_NOTE_SERIALIZED_LEN>, | ||
| note_hash: Field, | ||
| nullifier: Field, | ||
| tx_hash: Field, | ||
| recipient: AztecAddress, | ||
| ) { | ||
| // TODO(#10728): do something instead of failing (e.g. not advance tagging indices) | ||
| assert( | ||
| deliver_note_oracle( | ||
| contract_address, | ||
| storage_slot, | ||
| nonce, | ||
| content, | ||
| note_hash, | ||
| nullifier, | ||
| tx_hash, | ||
| recipient, | ||
| ), | ||
| "Failed to deliver note", | ||
| ); | ||
| } | ||
|
|
||
| #[oracle(deliverNote)] | ||
| unconstrained fn deliver_note_oracle( | ||
| contract_address: AztecAddress, | ||
| storage_slot: Field, | ||
| nonce: Field, | ||
| content: BoundedVec<Field, MAX_NOTE_SERIALIZED_LEN>, | ||
| note_hash: Field, | ||
| nullifier: Field, | ||
| tx_hash: Field, | ||
| recipient: AztecAddress, | ||
| ) -> bool {} | ||
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,5 +1,7 @@ | ||
| mod collapse; | ||
| mod subarray; | ||
| mod subbvec; | ||
|
|
||
| pub use collapse::collapse; | ||
| pub use subarray::subarray; | ||
| pub use subbvec::subbvec; |
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 |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| use crate::utils::array; | ||
|
|
||
| /// Returns `DST_MAX_LEN` elements from a source BoundedVec, starting at `offset`. `offset` must not be larger than the | ||
| /// original length, and `DST_LEN` must not be larger than the total number of elements past `offset` (including the | ||
| /// zeroed elements past `len()`). | ||
| /// | ||
| /// Only elements at the beginning of the vector can be removed: it is not possible to also remove elements at the end | ||
| /// of the vector by passing a value for `DST_LEN` that is smaller than `len() - offset`. | ||
| /// | ||
| /// Examples: | ||
| /// ``` | ||
| /// let foo = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
| /// assert_eq(subbvec(foo, 2), BoundedVec::<_, 8>::from_array([3, 4, 5])); | ||
| /// | ||
| /// let bar: BoundedVec<_, 1> = subbvec(foo, 2); // fails - we can't return just 1 element since 3 remain | ||
| /// let baz: BoundedVec<_, 10> = subbvec(foo, 3); // fails - we can't return 10 elements since only 7 remain | ||
| /// ``` | ||
| pub fn subbvec<T, let SRC_MAX_LEN: u32, let DST_MAX_LEN: u32>( | ||
| vec: BoundedVec<T, SRC_MAX_LEN>, | ||
| offset: u32, | ||
| ) -> BoundedVec<T, DST_MAX_LEN> { | ||
| // from_parts_unchecked does not verify that the elements past len are zeroed, but that is not an issue in our case | ||
| // because we're constructing the new storage array as a subarray of the original one (which should have zeroed | ||
| // storage past len), guaranteeing correctness. This is because `subarray` does not allow extending arrays past | ||
| // their original length. | ||
| BoundedVec::from_parts_unchecked(array::subarray(vec.storage(), offset), vec.len() - offset) | ||
| } | ||
|
Comment on lines
+18
to
+27
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. I found this function so hard to read but at the same time so useful (and we'll likely use this and variants more and more as aztec-nr becomes more powerful) that I extracted it into a module with its own tests. |
||
|
|
||
| mod test { | ||
| use super::subbvec; | ||
|
|
||
| #[test] | ||
| unconstrained fn subbvec_empty() { | ||
| let bvec = BoundedVec::<Field, 0>::from_array([]); | ||
| assert_eq(subbvec(bvec, 0), bvec); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn subbvec_complete() { | ||
| let bvec = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
| assert_eq(subbvec(bvec, 0), bvec); | ||
|
|
||
| let smaller_capacity = BoundedVec::<_, 5>::from_array([1, 2, 3, 4, 5]); | ||
| assert_eq(subbvec(bvec, 0), smaller_capacity); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn subbvec_partial() { | ||
| let bvec = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
|
|
||
| assert_eq(subbvec(bvec, 2), BoundedVec::<_, 8>::from_array([3, 4, 5])); | ||
| assert_eq(subbvec(bvec, 2), BoundedVec::<_, 3>::from_array([3, 4, 5])); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn subbvec_into_empty() { | ||
| let bvec: BoundedVec<_, 10> = BoundedVec::from_array([1, 2, 3, 4, 5]); | ||
| assert_eq(subbvec(bvec, 5), BoundedVec::<_, 5>::from_array([])); | ||
| } | ||
|
|
||
| #[test(should_fail)] | ||
| unconstrained fn subbvec_offset_past_len() { | ||
| let bvec = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
| let _: BoundedVec<_, 1> = subbvec(bvec, 6); | ||
| } | ||
|
|
||
| #[test(should_fail)] | ||
| unconstrained fn subbvec_insufficient_dst_len() { | ||
| let bvec = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
|
|
||
| // We're not providing enough space to hold all of the items inside the original BoundedVec. subbvec can cause | ||
| // for the capacity to reduce, but not the length (other than by len - offset). | ||
| let _: BoundedVec<_, 1> = subbvec(bvec, 2); | ||
| } | ||
|
|
||
| #[test(should_fail_with = "DST_LEN too large for offset")] | ||
| unconstrained fn subbvec_dst_len_causes_enlarge() { | ||
| let bvec = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
|
|
||
| // subbvec does not supprt capacity increases | ||
| let _: BoundedVec<_, 11> = subbvec(bvec, 0); | ||
| } | ||
|
|
||
| #[test(should_fail_with = "DST_LEN too large for offset")] | ||
| unconstrained fn subbvec_dst_len_too_large_for_offset() { | ||
| let bvec = BoundedVec::<_, 10>::from_array([1, 2, 3, 4, 5]); | ||
|
|
||
| // This effectively requests a capacity increase, since there'd be just one element plus the 5 empty slots, | ||
| // which is less than 7. | ||
| let _: BoundedVec<_, 7> = subbvec(bvec, 4); | ||
| } | ||
| } | ||
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.