-
Notifications
You must be signed in to change notification settings - Fork 598
feat!: introduce WithHash<T> + use it in PublicImmutable
#8022
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
benesjan
merged 19 commits into
master
from
08-15-_do_not_merge_feat_optimizing_sharedimmutable
Feb 5, 2025
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
e9dd330
WIP
benesjan 71d6005
Update noir-projects/aztec-nr/aztec/src/utils/with_hash.nr
benesjan 15dde6c
Update noir-projects/aztec-nr/aztec/src/utils/with_hash.nr
benesjan f624897
nuking stale comment
benesjan d883af4
migration notes + code docs
benesjan fc3632a
better comments
benesjan b00b29f
docs
benesjan 6a23f11
testing storage slot allocation
benesjan 5962d97
tests to make Nico happy
benesjan 27dbb5e
WIP
benesjan f1ca087
fixes
benesjan 9c594df
comment fix
benesjan a1f2e70
fix
benesjan 720797c
Revert "fix"
benesjan a405a70
fix of fix
benesjan a4be3a6
Apply suggestions from code review
benesjan 878dc85
Update noir-projects/aztec-nr/aztec/src/utils/with_hash.nr
benesjan e6b47b8
fix
benesjan 8c91da7
type hint
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
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 |
|---|---|---|
|
|
@@ -5,3 +5,4 @@ pub mod field; | |
| pub mod point; | ||
| pub mod to_bytes; | ||
| pub mod secrets; | ||
| pub mod with_hash; | ||
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,238 @@ | ||
| use crate::{ | ||
| context::{PublicContext, UnconstrainedContext}, | ||
| history::public_storage::PublicStorageHistoricalRead, | ||
| oracle, | ||
| }; | ||
| use dep::protocol_types::{ | ||
| address::AztecAddress, block_header::BlockHeader, hash::poseidon2_hash, traits::Packable, | ||
| }; | ||
|
|
||
| /// A struct that allows for efficient reading of value `T` from public storage in private. | ||
| /// | ||
| /// The efficient reads are achieved by verifying large values through a single hash check | ||
| /// and then proving inclusion only of the hash in public storage. This reduces the number | ||
| /// of required tree inclusion proofs from `N` to 1. | ||
| /// | ||
| /// # Type Parameters | ||
| /// - `T`: The underlying type being wrapped, must implement `Packable<N>` | ||
| /// - `N`: The number of field elements required to pack values of type `T` | ||
| pub struct WithHash<T, let N: u32> { | ||
benesjan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| value: T, | ||
| packed: [Field; N], | ||
| hash: Field, | ||
| } | ||
|
|
||
| impl<T, let N: u32> WithHash<T, N> | ||
| where | ||
| T: Packable<N> + Eq, | ||
| { | ||
| pub fn new(value: T) -> Self { | ||
| let packed = value.pack(); | ||
| Self { value, packed, hash: poseidon2_hash(packed) } | ||
| } | ||
|
|
||
| pub fn get_value(self) -> T { | ||
| self.value | ||
| } | ||
|
|
||
| pub fn get_hash(self) -> Field { | ||
| self.hash | ||
| } | ||
|
|
||
| pub fn public_storage_read(context: PublicContext, storage_slot: Field) -> T { | ||
| context.storage_read(storage_slot) | ||
| } | ||
|
|
||
| pub unconstrained fn unconstrained_public_storage_read( | ||
| context: UnconstrainedContext, | ||
| storage_slot: Field, | ||
| ) -> T { | ||
| context.storage_read(storage_slot) | ||
| } | ||
|
|
||
| pub fn historical_public_storage_read( | ||
| header: BlockHeader, | ||
| address: AztecAddress, | ||
| storage_slot: Field, | ||
| ) -> T { | ||
| let historical_block_number = header.global_variables.block_number as u32; | ||
|
|
||
| // We could simply produce historical inclusion proofs for each field in `packed`, but that would require one | ||
| // full sibling path per storage slot (since due to kernel siloing the storage is not contiguous). Instead, we | ||
| // get an oracle to provide us the values, and instead we prove inclusion of their hash, which is both a much | ||
| // smaller proof (a single slot), and also independent of the size of T (except in that we need to pack and hash T). | ||
| let hint = WithHash::new( | ||
| /// Safety: We verify that a hash of the hint/packed data matches the stored hash. | ||
| unsafe { | ||
| oracle::storage::storage_read(address, storage_slot, historical_block_number) | ||
| }, | ||
| ); | ||
|
|
||
| let hash = header.public_storage_historical_read(storage_slot + N as Field, address); | ||
|
|
||
| if hash != 0 { | ||
| assert_eq(hash, hint.get_hash(), "Hint values do not match hash"); | ||
| } else { | ||
| // The hash slot can only hold a zero if it is uninitialized. Therefore, the hints must then be zero | ||
| // (i.e. the default value for public storage) as well. | ||
| assert_eq( | ||
| hint.get_value(), | ||
| T::unpack(std::mem::zeroed()), | ||
| "Non-zero hint for zero hash", | ||
| ); | ||
| }; | ||
|
|
||
| hint.get_value() | ||
| } | ||
| } | ||
|
|
||
| impl<T, let N: u32> Packable<N + 1> for WithHash<T, N> | ||
| where | ||
| T: Packable<N>, | ||
| { | ||
| fn pack(self) -> [Field; N + 1] { | ||
| let mut result: [Field; N + 1] = std::mem::zeroed(); | ||
| for i in 0..N { | ||
| result[i] = self.packed[i]; | ||
| } | ||
| result[N] = self.hash; | ||
|
|
||
| result | ||
| } | ||
|
|
||
| fn unpack(packed: [Field; N + 1]) -> Self { | ||
| let mut value_packed: [Field; N] = std::mem::zeroed(); | ||
| for i in 0..N { | ||
| value_packed[i] = packed[i]; | ||
| } | ||
| let hash = packed[N]; | ||
|
|
||
| Self { value: T::unpack(value_packed), packed: value_packed, hash } | ||
| } | ||
| } | ||
|
|
||
| mod test { | ||
| use crate::{ | ||
| oracle::random::random, | ||
| test::{ | ||
| helpers::{cheatcodes, test_environment::TestEnvironment}, | ||
| mocks::mock_struct::MockStruct, | ||
| }, | ||
| utils::with_hash::WithHash, | ||
| }; | ||
| use dep::protocol_types::hash::poseidon2_hash; | ||
| use dep::std::{mem, test::OracleMock}; | ||
|
|
||
| global storage_slot: Field = 47; | ||
|
|
||
| #[test] | ||
| unconstrained fn create_and_recover() { | ||
benesjan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let value = MockStruct { a: 5, b: 3 }; | ||
| let value_with_hash = WithHash::new(value); | ||
| let recovered = WithHash::unpack(value_with_hash.pack()); | ||
|
|
||
| assert_eq(recovered.value, value); | ||
| assert_eq(recovered.packed, value.pack()); | ||
| assert_eq(recovered.hash, poseidon2_hash(value.pack())); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn read_uninitialized_value() { | ||
| let mut env = TestEnvironment::new(); | ||
|
|
||
| let block_header = env.private().historical_header; | ||
| let address = env.contract_address(); | ||
|
|
||
| let result = WithHash::<MockStruct, _>::historical_public_storage_read( | ||
| block_header, | ||
| address, | ||
| storage_slot, | ||
| ); | ||
|
|
||
| // We should get zeroed value | ||
| let expected: MockStruct = mem::zeroed(); | ||
| assert_eq(result, expected); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn read_initialized_value() { | ||
| let mut env = TestEnvironment::new(); | ||
|
|
||
| let value = MockStruct { a: 5, b: 3 }; | ||
| let value_with_hash = WithHash::new(value); | ||
|
|
||
| // We write the value with hash to storage | ||
| cheatcodes::direct_storage_write( | ||
| env.contract_address(), | ||
| storage_slot, | ||
| value_with_hash.pack(), | ||
| ); | ||
|
|
||
| // We advance block by 1 because env.private() currently returns context at latest_block - 1 | ||
| env.advance_block_by(1); | ||
|
|
||
| let result = WithHash::<MockStruct, _>::historical_public_storage_read( | ||
| env.private().historical_header, | ||
| env.contract_address(), | ||
| storage_slot, | ||
| ); | ||
|
|
||
| assert_eq(result, value); | ||
| } | ||
|
|
||
| #[test(should_fail_with = "Non-zero hint for zero hash")] | ||
| unconstrained fn test_bad_hint_uninitialized_value() { | ||
| let mut env = TestEnvironment::new(); | ||
|
|
||
| env.advance_block_to(6); | ||
|
|
||
| let value_packed = MockStruct { a: 1, b: 1 }.pack(); | ||
|
|
||
| let block_header = env.private().historical_header; | ||
| let address = env.contract_address(); | ||
|
|
||
| // Mock the oracle to return a non-zero hint/packed value | ||
| let _ = OracleMock::mock("storageRead") | ||
| .with_params(( | ||
| address.to_field(), storage_slot, block_header.global_variables.block_number as u32, | ||
| value_packed.len(), | ||
| )) | ||
| .returns(value_packed) | ||
| .times(1); | ||
|
|
||
| // This should revert because the hint value is non-zero and the hash is zero (default value of storage) | ||
| let _ = WithHash::<MockStruct, _>::historical_public_storage_read( | ||
| block_header, | ||
| address, | ||
| storage_slot, | ||
| ); | ||
| } | ||
|
|
||
| #[test(should_fail_with = "Hint values do not match hash")] | ||
| unconstrained fn test_bad_hint_initialized_value() { | ||
| let mut env = TestEnvironment::new(); | ||
|
|
||
| let value_packed = MockStruct { a: 5, b: 3 }.pack(); | ||
|
|
||
| // We write the value to storage | ||
| cheatcodes::direct_storage_write(env.contract_address(), storage_slot, value_packed); | ||
|
|
||
| // Now we write incorrect hash to the hash storage slot | ||
| let incorrect_hash = random(); | ||
| let hash_storage_slot = storage_slot + (value_packed.len() as Field); | ||
| cheatcodes::direct_storage_write( | ||
| env.contract_address(), | ||
| hash_storage_slot, | ||
| [incorrect_hash], | ||
| ); | ||
|
|
||
| // We advance block by 1 because env.private() currently returns context at latest_block - 1 | ||
| env.advance_block_by(1); | ||
|
|
||
| let _ = WithHash::<MockStruct, _>::historical_public_storage_read( | ||
| env.private().historical_header, | ||
| env.contract_address(), | ||
| storage_slot, | ||
| ); | ||
| } | ||
| } | ||
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
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.