-
Notifications
You must be signed in to change notification settings - Fork 610
feat: fix contract issues with key rotation, introduce get npk_m_hash_at #6541
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
Changes from all commits
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 |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| use dep::protocol_types::{ | ||
| abis::key_validation_request::KeyValidationRequest, address::AztecAddress, | ||
| header::Header, abis::key_validation_request::KeyValidationRequest, address::AztecAddress, | ||
| constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint, | ||
| storage::map::derive_storage_slot_in_map | ||
| }; | ||
|
|
@@ -17,8 +17,28 @@ pub fn get_npk_m(context: &mut PrivateContext, address: AztecAddress) -> Grumpki | |
| get_master_key(context, address, NULLIFIER_INDEX) | ||
| } | ||
|
|
||
| // For historical access, we move the fetching of the header to the top level to minimize redundant fetching / proving validity of the header. | ||
| pub fn get_npk_m_at( | ||
|
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. This function is only called in |
||
| context: &mut PrivateContext, | ||
| address: AztecAddress, | ||
| block_number: u32, | ||
| header: Header | ||
| ) -> GrumpkinPoint { | ||
| assert_eq(block_number, header.global_variables.block_number as u32); | ||
|
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. This feels strange, why do you want to pass the Just a small thing that popped up in my mind. As we seem to generally want to supply multiple ways to execute these functions on the header, should we take a look at having them as a trait on the header? In that way, you would get the header that you are interested in for data, and then you can use the "getters". let header = context.get_header_at(block_number);
let npk_m = header.get_npk_m(context, address);
// and for historical access to public state
let storage = header.public_storage_historical_read(address, storage_slot);@nventuro what are you thoughts? Should allow us to get rid of some code and think it is similar or better ux, but your call 🫡 If to change like this, might be better to handle in separate PR since we have the flow a few places that was otherwise untouched here. Think this could essentially get rid of almost all of the |
||
| get_master_key_at(context, address, NULLIFIER_INDEX, header) | ||
| } | ||
|
|
||
| pub fn get_npk_m_hash(context: &mut PrivateContext, address: AztecAddress) -> Field { | ||
| get_master_key(context, address, NULLIFIER_INDEX).hash() | ||
| get_npk_m(context, address).hash() | ||
| } | ||
|
|
||
| pub fn get_npk_m_hash_at( | ||
| context: &mut PrivateContext, | ||
| address: AztecAddress, | ||
| block_number: u32, | ||
| header: Header | ||
| ) -> Field { | ||
| get_npk_m_at(context, address, block_number, header).hash() | ||
| } | ||
|
|
||
| pub fn get_ivpk_m(context: &mut PrivateContext, address: AztecAddress) -> GrumpkinPoint { | ||
|
|
@@ -40,7 +60,23 @@ fn get_master_key( | |
| address: AztecAddress, | ||
| key_index: Field | ||
| ) -> GrumpkinPoint { | ||
| let key = fetch_key_from_registry(context, key_index, address); | ||
| let (x_coordinate_registry, y_coordinate_registry) = get_registry_with_key_index(context, key_index, address); | ||
|
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. Reorganized some things here
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. The split now made here such that you can use the See comment above, I think we can make some simplifications to get rid of some of all the |
||
| let key = fetch_key_from_registry(x_coordinate_registry, y_coordinate_registry); | ||
| fetch_key_from_oracle_if_zero(key, address, key_index) | ||
| } | ||
|
|
||
| fn get_master_key_at( | ||
| context: &mut PrivateContext, | ||
| address: AztecAddress, | ||
| key_index: Field, | ||
| header: Header | ||
| ) -> GrumpkinPoint { | ||
| let (x_coordinate_registry, y_coordinate_registry) = get_registry_with_key_index(context, key_index, address); | ||
| let key = fetch_key_from_registry_at(x_coordinate_registry, y_coordinate_registry, header); | ||
| fetch_key_from_oracle_if_zero(key, address, key_index) | ||
| } | ||
|
|
||
| fn fetch_key_from_oracle_if_zero(key: GrumpkinPoint, address: AztecAddress, key_index: Field) -> GrumpkinPoint { | ||
| if key.is_zero() { | ||
| // Keys were not registered in registry yet --> fetch key from PXE | ||
| let keys = fetch_and_constrain_keys(address); | ||
|
|
@@ -52,11 +88,11 @@ fn get_master_key( | |
| } | ||
| } | ||
|
|
||
| fn fetch_key_from_registry( | ||
| fn get_registry_with_key_index( | ||
|
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. I don't like much that we need to have this getter and then split the flows into But I think a better name would help here because when I saw the function I though it fetches some value from registry. I think something like
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. Update: Boss came up with how to refactor it here. |
||
| context: &mut PrivateContext, | ||
| key_index: Field, | ||
| address: AztecAddress | ||
| ) -> GrumpkinPoint { | ||
| ) -> (SharedMutablePrivateGetter<Field, DELAY>, SharedMutablePrivateGetter<Field, DELAY>) { | ||
| let x_coordinate_map_slot = key_index * 2 + 1; | ||
| let y_coordinate_map_slot = x_coordinate_map_slot + 1; | ||
| let x_coordinate_derived_slot = derive_storage_slot_in_map(x_coordinate_map_slot, address); | ||
|
|
@@ -72,10 +108,29 @@ fn fetch_key_from_registry( | |
| AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS), | ||
| y_coordinate_derived_slot | ||
| ); | ||
| let x_coordinate = x_coordinate_registry.get_current_value_in_private(); | ||
| let y_coordinate = y_coordinate_registry.get_current_value_in_private(); | ||
|
|
||
| GrumpkinPoint::new(x_coordinate, y_coordinate) | ||
| (x_coordinate_registry, y_coordinate_registry) | ||
|
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. The wording here makes it sound like it is separate registries, but it is just different coordinates so think I would still keep the naming similar to not make misunderstandings that it is a registry 🤷 |
||
| } | ||
|
|
||
| fn fetch_key_from_registry( | ||
| x_coordinate_registry: SharedMutablePrivateGetter<Field, DELAY>, | ||
| y_coordinate_registry: SharedMutablePrivateGetter<Field, DELAY> | ||
| ) -> GrumpkinPoint { | ||
| let key_x_coordinate = x_coordinate_registry.get_current_value_in_private(); | ||
| let key_y_coordinate = y_coordinate_registry.get_current_value_in_private(); | ||
|
|
||
| GrumpkinPoint::new(key_x_coordinate, key_y_coordinate) | ||
| } | ||
|
|
||
| fn fetch_key_from_registry_at( | ||
| x_coordinate_registry: SharedMutablePrivateGetter<Field, DELAY>, | ||
| y_coordinate_registry: SharedMutablePrivateGetter<Field, DELAY>, | ||
| header: Header | ||
| ) -> GrumpkinPoint { | ||
| let key_x_coordinate = x_coordinate_registry.get_current_value_in_private_at(header.global_variables.block_number as u32, header); | ||
|
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. Using |
||
| let key_y_coordinate = y_coordinate_registry.get_current_value_in_private_at(header.global_variables.block_number as u32, header); | ||
|
|
||
| GrumpkinPoint::new(key_x_coordinate, key_y_coordinate) | ||
| } | ||
|
|
||
| // Passes only when keys were not rotated - is expected to be called only when keys were not registered yet | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| use dep::protocol_types::{hash::pedersen_hash, traits::FromField, address::AztecAddress}; | ||
| use dep::protocol_types::{hash::pedersen_hash, traits::FromField, address::AztecAddress, header::Header}; | ||
|
|
||
| use crate::history::public_storage::{public_storage_historical_read, _public_storage_historical_read}; | ||
| use crate::context::PrivateContext; | ||
| use crate::history::public_storage::public_storage_historical_read; | ||
| use crate::public_storage; | ||
| use crate::state_vars::{ | ||
| storage::Storage, | ||
|
|
@@ -34,36 +34,50 @@ impl<T, INITIAL_DELAY> SharedMutablePrivateGetter<T, INITIAL_DELAY> { | |
|
|
||
| pub fn get_current_value_in_private(self) -> T where T: FromField { | ||
| let mut context = self.context; | ||
| let header = context.historical_header; | ||
|
|
||
| let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(context); | ||
| let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(header); | ||
| let effective_minimum_delay = delay_change.get_effective_minimum_delay_at(historical_block_number); | ||
| let block_horizon = value_change.get_block_horizon(historical_block_number, effective_minimum_delay); | ||
|
|
||
| context.set_tx_max_block_number(block_horizon); | ||
| value_change.get_current_at(historical_block_number) | ||
| } | ||
|
|
||
| pub fn get_current_value_in_private_at( | ||
| self, | ||
| block_number: u32, | ||
| header: Header | ||
| ) -> T where T: FromField { | ||
| assert_eq(block_number, header.global_variables.block_number as u32); | ||
| // We don't need the delay change because even if we execute this in the future, we are looking at the value | ||
| // from a specific time in the past. | ||
| let (value_change, _, historical_block_number) = self.historical_read_from_public_storage(header); | ||
|
|
||
| value_change.get_current_at(historical_block_number) | ||
| } | ||
|
|
||
| fn historical_read_from_public_storage( | ||
| self, | ||
| context: PrivateContext | ||
| header: Header | ||
| ) -> (ScheduledValueChange<T>, ScheduledDelayChange<INITIAL_DELAY>, u32) where T: FromField { | ||
| let value_change_slot = self.get_value_change_storage_slot(); | ||
| let mut raw_value_change_fields = [0; 3]; | ||
| for i in 0..3 { | ||
| raw_value_change_fields[i] = public_storage_historical_read( | ||
| context, | ||
| raw_value_change_fields[i] = _public_storage_historical_read( | ||
|
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. Made this change because otherwise every time we wanted to read a historical value, we'd have to refetch the header :/. See this comment.
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. I'm not head over heals about the |
||
| value_change_slot + i as Field, | ||
| self.other_contract_address | ||
| self.other_contract_address, | ||
| header, | ||
| ); | ||
| } | ||
|
|
||
| let delay_change_slot = self.get_delay_change_storage_slot(); | ||
| let raw_delay_change_fields = [public_storage_historical_read(context, delay_change_slot, context.this_address())]; | ||
|
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. Fixing
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. It's weird that this was not caught by a test. I see that it's used in the test contract to read from key registry so it's not the case that it was not caught because it was used to read from the same contract in a test. Given that it's reading delay I would say we never test the case when a delay is non-default. Could you create a separate PR based on master in which you:
Thanks |
||
| let raw_delay_change_fields = [_public_storage_historical_read(delay_change_slot, self.other_contract_address, header)]; | ||
|
|
||
| let value_change = ScheduledValueChange::deserialize(raw_value_change_fields); | ||
| let delay_change = ScheduledDelayChange::deserialize(raw_delay_change_fields); | ||
|
|
||
| let historical_block_number = context.historical_header.global_variables.block_number as u32; | ||
| let historical_block_number = header.global_variables.block_number as u32; | ||
|
|
||
| (value_change, delay_change, historical_block_number) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,11 +49,6 @@ impl<Context> EasyPrivateUint<&mut PrivateContext> { | |
| if maybe_notes[i].is_some() { | ||
| let note = maybe_notes[i].unwrap_unchecked(); | ||
|
|
||
| // Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while | ||
|
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. This is no longer needed because EasyPrivateUInts are only used currently in maps, and thus are pre-scoped to owner.
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. But we don't have a guarantee that EasyPrivateUInt will only ever be used in maps, no? I feel like the correct justification for removing this in here is that the notes inside the EasyPrivateUInt is protected by nullifier key and hence can't be nullified by "non-owner".
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. |
||
| // spending someone else's notes). | ||
| // TODO (#6312): This will break with key rotation. Fix this. Will not be able to pass this assert after rotating key | ||
| assert(note.npk_m_hash.eq(owner_npk_m_hash)); | ||
|
|
||
| // Removes the note from the owner's set of notes. | ||
| // docs:start:remove | ||
| self.set.remove(note); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,23 +55,12 @@ pub fn decrement_by_at_most( | |
| let options = create_note_getter_options_for_decreasing_balance(max_amount); | ||
| let opt_notes = balance.get_notes(options); | ||
|
|
||
| let owner_npk_m_hash = get_npk_m_hash(balance.context, owner); | ||
|
|
||
| let mut decremented = 0; | ||
| for i in 0..opt_notes.len() { | ||
| if opt_notes[i].is_some() { | ||
| let note = opt_notes[i].unwrap_unchecked(); | ||
|
|
||
| // This is similar to destroy_note, except we only compute the owner_npk_m_hash once instead of doing it in | ||
|
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. This is no longer needed because ValueNotes are only used currently in maps, and thus are pre-scoped to owner. A nice side effect of this cleanup is the reverting of needing to pass in any owner / npk_m_hash
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. cool! |
||
| // each loop iteration. | ||
|
|
||
| // Ensure the note is actually owned by the owner (to prevent user from generating a valid proof while | ||
| // spending someone else's notes). | ||
| // TODO (#6312): This will break with key rotation. Fix this. Will not be able to pass this after rotating keys. | ||
| assert(note.npk_m_hash.eq(owner_npk_m_hash)); | ||
| decremented += note.value; | ||
|
|
||
| balance.remove(note); | ||
| decremented += destroy_note(balance, note); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -88,18 +77,7 @@ pub fn decrement_by_at_most( | |
|
|
||
| // Removes the note from the owner's set of notes. | ||
| // Returns the value of the destroyed note. | ||
| pub fn destroy_note( | ||
| balance: PrivateSet<ValueNote, &mut PrivateContext>, | ||
| owner: AztecAddress, | ||
| note: ValueNote | ||
| ) -> Field { | ||
| // Ensure the note is actually owned by the owner (to prevent user from generating a valid proof while | ||
| // spending someone else's notes). | ||
| let owner_npk_m_hash = get_npk_m_hash(balance.context, owner); | ||
|
|
||
| // TODO (#6312): This will break with key rotation. Fix this. Will not be able to pass this after rotating keys. | ||
| assert(note.npk_m_hash.eq(owner_npk_m_hash)); | ||
|
|
||
| pub fn destroy_note(balance: PrivateSet<ValueNote, &mut PrivateContext>, note: ValueNote) -> Field { | ||
|
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. This is cute |
||
| balance.remove(note); | ||
|
|
||
| note.value | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,19 +118,13 @@ impl Deck<&mut PrivateContext> { | |
| inserted_cards | ||
| } | ||
|
|
||
| pub fn get_cards<N>(&mut self, cards: [Card; N], owner: AztecAddress) -> [CardNote; N] { | ||
| let owner_npk_m_hash = get_npk_m_hash(self.set.context, owner); | ||
|
|
||
| pub fn get_cards<N>(&mut self, cards: [Card; N]) -> [CardNote; N] { | ||
| let options = NoteGetterOptions::with_filter(filter_cards, cards); | ||
| let maybe_notes = self.set.get_notes(options); | ||
| let mut found_cards = [Option::none(); N]; | ||
| for i in 0..maybe_notes.len() { | ||
| if maybe_notes[i].is_some() { | ||
| let card_note = CardNote::from_note(maybe_notes[i].unwrap_unchecked()); | ||
| // Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while | ||
| // spending someone else's notes). | ||
| // TODO (#6312): This will break with key rotation. Fix this. Will not be able to pass this assert after rotating keys. | ||
|
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. This is no longer needed because self.set only is pre-scoped to only allow the owner to view their own notes.
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. Once again I feel like the main protection here is the nullifier key as relying on Maps for protection just seems scary to me. @LHerskind or WDYT about using Maps as a guardrail?
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. I think it is the way to do it. Talked with Esau, which is why he went this way. The storage should in my mind not know much about you application logic, but just be considered with the protocol. It should be aware of the protocol required keys, and "how" you nullify, but I see it as the job of the business logic to figure out "when" to nullify -> Jan are allowed to call nullify on the notes of his as defined by the application logic.
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. Ok ok, my alarm went off since it was the first time I saw it specifically used as a guardrail but if we explain it well to devs then it seems fine. |
||
| assert(card_note.note.npk_m_hash.eq(owner_npk_m_hash)); | ||
|
|
||
| for j in 0..cards.len() { | ||
| if found_cards[j].is_none() | ||
|
|
@@ -150,8 +144,8 @@ impl Deck<&mut PrivateContext> { | |
| ) | ||
| } | ||
|
|
||
| pub fn remove_cards<N>(&mut self, cards: [Card; N], owner: AztecAddress) { | ||
| let card_notes = self.get_cards(cards, owner); | ||
| pub fn remove_cards<N>(&mut self, cards: [Card; N]) { | ||
| let card_notes = self.get_cards(cards); | ||
| for card_note in card_notes { | ||
| self.set.remove(card_note.note); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| // A contract used along with `Parent` contract to test nested calls. | ||
| contract Child { | ||
| use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize}; | ||
| use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize, Map}; | ||
|
|
||
| use dep::aztec::{ | ||
| context::gas::GasOpts, | ||
|
|
@@ -13,7 +13,7 @@ contract Child { | |
| #[aztec(storage)] | ||
| struct Storage { | ||
| current_value: PublicMutable<Field>, | ||
| a_private_value: PrivateSet<ValueNote>, | ||
| a_map_with_private_values: Map<AztecAddress, PrivateSet<ValueNote>>, | ||
| } | ||
|
|
||
| // Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values. | ||
|
|
@@ -56,22 +56,15 @@ contract Child { | |
| let owner_ivpk_m = get_ivpk_m(&mut context, owner); | ||
|
|
||
| let mut note = ValueNote::new(new_value, owner_npk_m_hash); | ||
| storage.a_private_value.insert(&mut note, true, owner_ivpk_m); | ||
| storage.a_map_with_private_values.at(owner).insert(&mut note, true, owner_ivpk_m); | ||
| new_value | ||
| } | ||
|
|
||
| #[aztec(private)] | ||
| fn private_get_value(amount: Field, owner: AztecAddress) -> Field { | ||
| let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); | ||
|
|
||
| // TODO (#6312): This will break with key rotation. Fix this. Will not be able to find any notes after rotating key. | ||
|
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. No longer needed because the set is scoped to owner. |
||
| let mut options = NoteGetterOptions::new(); | ||
| options = options.select(ValueNote::properties().value, amount, Option::none()).select( | ||
| ValueNote::properties().npk_m_hash, | ||
| owner_npk_m_hash, | ||
| Option::none() | ||
| ).set_limit(1); | ||
| let notes = storage.a_private_value.get_notes(options); | ||
| options = options.select(ValueNote::properties().value, amount, Option::none()).set_limit(1); | ||
| let notes = storage.a_map_with_private_values.at(owner).get_notes(options); | ||
| notes[0].unwrap_unchecked().value | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,17 +30,13 @@ contract Claim { | |
| target_address == proof_note.header.contract_address, "Note does not correspond to the target contract" | ||
| ); | ||
|
|
||
| let msg_sender_npk_m_hash = get_npk_m_hash(&mut context, context.msg_sender()); | ||
|
|
||
| // TODO (#6312): This will break with key rotation. Fix this. Will not be able to pass this assert after key rotation. | ||
|
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. The claim contract no longer needs to check this, because someone without access to the nsk_m corresponding to the npk_m in the proof note will no longer be able to compute the nullifier |
||
| assert_eq(proof_note.npk_m_hash, msg_sender_npk_m_hash, "Note does not belong to the sender"); | ||
|
|
||
| // 2) Prove that the note hash exists in the note hash tree | ||
| prove_note_inclusion(proof_note, context); | ||
|
|
||
| // 3) 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: The nullifier is unique to the note and THIS contract because the protocol siloes all nullifiers with | ||
| // Note: Only the owner of the npk_m will be able to produce the nsk_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. | ||
| context.push_new_nullifier(proof_note.compute_nullifier(&mut context), 0); | ||
|
|
||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've exposed this to access this by passing in a header directly. One can make an argument that to keep it consistent with all the other changes I've made, that actually the
public_storage_historical_read_atfunction below should change and take a header as well, and not only a block number; but I wanted to keep changes minimal. Open to opinions though !There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think exposing this is totally fine but would rename it to something without the underscore prefix since that evokes private/internal function.