From 996cc84eca6f46673a3fa76a3fddac1c0fe4240c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 3 Dec 2024 13:01:51 +0000 Subject: [PATCH] Wip --- .../aztec/src/state_vars/public_immutable.nr | 30 ++--- .../aztec-nr/aztec/src/state_vars/storage.nr | 2 - noir-projects/aztec-nr/aztec/src/utils/mod.nr | 2 + .../aztec-nr/aztec/src/utils/with_hash.nr | 123 ++++++++++++++++++ .../src/field_compressed_string.nr | 6 +- 5 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/utils/with_hash.nr diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index a3e5df8b9c87..4d779950a9fd 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -1,6 +1,7 @@ use crate::{ context::{PrivateContext, PublicContext, UnconstrainedContext}, state_vars::storage::Storage, + utils::with_hash::WithHash, }; use dep::protocol_types::{ constants::INITIALIZATION_SLOT_SEPARATOR, @@ -18,7 +19,7 @@ pub struct PublicImmutable { impl Storage for PublicImmutable where - T: Serialize + Deserialize, + WithHash: Serialize + Deserialize, {} impl PublicImmutable { @@ -36,7 +37,7 @@ impl PublicImmutable { impl PublicImmutable where - T: Serialize + Deserialize, + T: Serialize + Deserialize + Eq, { // docs:start:public_immutable_struct_write pub fn initialize(self, value: T) { @@ -47,41 +48,36 @@ where // We populate the initialization slot with a non-zero value to indicate that the struct is initialized self.context.storage_write(initialization_slot, 0xdead); - self.context.storage_write(self.storage_slot, value); + self.context.storage_write(self.storage_slot, WithHash::new(value)); } // docs:end:public_immutable_struct_write // Note that we don't access the context, but we do call oracles that are only available in public // docs:start:public_immutable_struct_read pub fn read(self) -> T { - self.context.storage_read(self.storage_slot) + WithHash::public_storage_read(*self.context, self.storage_slot) } // docs:end:public_immutable_struct_read } impl PublicImmutable where - T: Serialize + Deserialize, + T: Serialize + Deserialize + Eq, { pub unconstrained fn read(self) -> T { - self.context.storage_read(self.storage_slot) + WithHash::unconstrained_public_storage_read(self.context, self.storage_slot) } } impl PublicImmutable where - T: Serialize + Deserialize, + T: Serialize + Deserialize + Eq, { pub fn read(self) -> T { - let header = self.context.get_header(); - let mut fields = [0; T_SERIALIZED_LEN]; - - for i in 0..fields.len() { - fields[i] = header.public_storage_historical_read( - self.storage_slot + i as Field, - (*self.context).this_address(), - ); - } - T::deserialize(fields) + WithHash::historical_public_storage_read( + self.context.get_header(), + self.context.this_address(), + self.storage_slot, + ) } } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr index a48c7de3749b..9b1e2a1d42d6 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr @@ -1,8 +1,6 @@ use dep::protocol_types::traits::{Deserialize, Serialize}; pub trait Storage -where - T: Serialize + Deserialize, { fn get_storage_slot(self) -> Field { self.storage_slot diff --git a/noir-projects/aztec-nr/aztec/src/utils/mod.nr b/noir-projects/aztec-nr/aztec/src/utils/mod.nr index d7abdd8234f5..82b22da4afe7 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/mod.nr @@ -4,6 +4,8 @@ pub mod comparison; pub mod point; pub mod test; pub mod to_bytes; +pub mod with_hash; pub use crate::utils::bytes::{bytes_to_fields, fields_to_bytes}; pub use crate::utils::collapse_array::collapse_array; +pub use crate::utils::with_hash::WithHash; diff --git a/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr b/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr new file mode 100644 index 000000000000..a7da4d89ee4f --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr @@ -0,0 +1,123 @@ +use crate::{context::{PublicContext, UnconstrainedContext}, oracle}; +use dep::protocol_types::{ + address::AztecAddress, + hash::poseidon2_hash, + header::Header, + traits::{Deserialize, Serialize}, +}; + +pub struct WithHash { + value: T, + serialized: [Field; N], + hash: Field, +} + +impl WithHash +where + T: Serialize + Deserialize + Eq, +{ + pub fn new(value: T) -> Self { + let serialized = value.serialize(); + Self { value, serialized, hash: poseidon2_hash(serialized) } + } + + 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) + } + + unconstrained pub fn unconstrained_public_storage_read(context: UnconstrainedContext, storage_slot: Field) -> T { + context.storage_read(storage_slot) + } + + pub fn historical_public_storage_read( + header: Header, + 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 both the ScheduledValueChange and + // ScheduledDelayChange, but that'd require one full sibling path per storage slot (since due to kernel siloing + // the storage is not contiguous), and in the best case in which T is a single field that'd be 4 slots. + // Instead, we get an oracle to provide us the correct values for both the value and delay changes, and instead + // prove inclusion of their hash, which is both a much smaller proof (a single slot), and also independent of + // the size of T. + let with_hash = WithHash::new( + unsafe { + oracle::storage::storage_read(address, storage_slot, historical_block_number) + }, + ); + + // Ideally the following would be simply public_storage::read_historical, but we can't implement that yet. + let hash = header.public_storage_historical_read(storage_slot + N as Field, address); + + if hash != 0 { + assert_eq(hash, with_hash.get_hash(), "Hint values do not match hash"); + } else { + // The hash slot can only hold a zero if it is uninitialized, meaning no value or delay change was ever + // scheduled. Therefore, the hints must then correspond to uninitialized scheduled changes. + assert_eq( + with_hash.get_value(), + T::deserialize(std::mem::zeroed()), + "Non-zero value change for zero hash", + ); + }; + + with_hash.get_value() + } +} + +impl Serialize for WithHash +where + T: Serialize, +{ + fn serialize(self) -> [Field; N + 1] { + let mut result: [Field; N + 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = self.serialized[i]; + } + result[N] = self.hash; + + result + } +} + +impl Deserialize for WithHash +where + T: Deserialize, +{ + fn deserialize(serialized: [Field; N + 1]) -> Self { + let mut value_serialized: [Field; N] = std::mem::zeroed(); + for i in 0..N { + value_serialized[i] = serialized[i]; + } + let hash = serialized[N]; + + Self { value: T::deserialize(value_serialized), serialized: value_serialized, hash } + } +} + +mod test { + use crate::test::mocks::mock_struct::MockStruct; + use crate::utils::with_hash::WithHash; + use dep::protocol_types::hash::poseidon2_hash; + + #[test] + unconstrained fn create_and_recover() { + let value = MockStruct { a: 5, b: 3 }; + let with_hash = WithHash::new(value); + let recovered = WithHash::deserialize(with_hash.serialize()); + + assert_eq(recovered.value, value); + assert_eq(recovered.serialized, value.serialize()); + assert_eq(recovered.hash, poseidon2_hash(value.serialize())); + } +} diff --git a/noir-projects/aztec-nr/compressed-string/src/field_compressed_string.nr b/noir-projects/aztec-nr/compressed-string/src/field_compressed_string.nr index 41b391c260c5..69f5f5881c4f 100644 --- a/noir-projects/aztec-nr/compressed-string/src/field_compressed_string.nr +++ b/noir-projects/aztec-nr/compressed-string/src/field_compressed_string.nr @@ -18,11 +18,13 @@ impl Deserialize<1> for FieldCompressedString { } } -impl FieldCompressedString { - pub fn is_eq(self, other: FieldCompressedString) -> bool { +impl Eq for FieldCompressedString { + fn eq(self, other: FieldCompressedString) -> bool { self.value == other.value } +} +impl FieldCompressedString { pub fn from_field(input_field: Field) -> Self { Self { value: input_field } }