Skip to content
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

feat: introduce WithHash<T> #11684

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,7 +19,7 @@ pub struct PublicImmutable<T, Context> {

impl<T, Context, let N: u32> Storage<T, N> for PublicImmutable<T, Context>
where
T: Serialize<N> + Deserialize<N>,
WithHash<T, _>: Serialize<N> + Deserialize<N>,
{}

impl<T, Context> PublicImmutable<T, Context> {
Expand All @@ -36,7 +37,7 @@ impl<T, Context> PublicImmutable<T, Context> {

impl<T, let T_SERIALIZED_LEN: u32> PublicImmutable<T, &mut PublicContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN> + Eq,
{
// docs:start:public_immutable_struct_write
pub fn initialize(self, value: T) {
Expand All @@ -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<T, let T_SERIALIZED_LEN: u32> PublicImmutable<T, UnconstrainedContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN> + 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<T, let T_SERIALIZED_LEN: u32> PublicImmutable<T, &mut PrivateContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN> + 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,
)
}
}
2 changes: 0 additions & 2 deletions noir-projects/aztec-nr/aztec/src/state_vars/storage.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use dep::protocol_types::traits::{Deserialize, Serialize};

pub trait Storage<T, let N: u32>
where
T: Serialize<N> + Deserialize<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand Down
2 changes: 2 additions & 0 deletions noir-projects/aztec-nr/aztec/src/utils/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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;
123 changes: 123 additions & 0 deletions noir-projects/aztec-nr/aztec/src/utils/with_hash.nr
Original file line number Diff line number Diff line change
@@ -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<T, let N: u32> {
value: T,
serialized: [Field; N],
hash: Field,
}

impl<T, let N: u32> WithHash<T, N>
where
T: Serialize<N> + Deserialize<N> + 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<T, let N: u32> Serialize<N + 1> for WithHash<T, N>
where
T: Serialize<N>,
{
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<T, let N: u32> Deserialize<N + 1> for WithHash<T, N>
where
T: Deserialize<N>,
{
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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand Down