Skip to content

Commit 71ddedf

Browse files
committed
feat: hasher derivation for derived zero copy
1 parent a5ea5a2 commit 71ddedf

File tree

10 files changed

+376
-177
lines changed

10 files changed

+376
-177
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

program-libs/hasher/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ thiserror = { workspace = true }
1717
arrayvec = { workspace = true }
1818
num-bigint = { workspace = true }
1919
zerocopy = { workspace = true }
20+
light-zero-copy = { workspace = true }
2021

2122
[target.'cfg(not(target_os = "solana"))'.dependencies]
2223
ark-bn254 = { workspace = true }

program-libs/hasher/src/hash_to_field_size.rs

+73-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use arrayvec::ArrayVec;
2+
use light_zero_copy::ZeroCopyTraits;
23

34
use crate::{keccak::Keccak, to_byte_array::ToByteArray, Hasher, HasherError};
45

@@ -41,7 +42,78 @@ where
4142
arrays.iter().for_each(|x| slices.push(x.as_slice()));
4243
let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
4344
slices.push(bump_seed.as_slice());
44-
Keccak::hashv(slices.as_slice())
45+
let mut hash = Keccak::hashv(slices.as_slice())?;
46+
hash[0] = 0;
47+
Ok(hash)
48+
}
49+
}
50+
51+
impl HashToFieldSize for &mut [u8] {
52+
fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError> {
53+
let mut slices: [&[u8]; 2] = [&[]; 2];
54+
slices[0] = self;
55+
let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
56+
slices[1] = bump_seed.as_slice();
57+
let mut hash = Keccak::hashv(slices.as_slice())?;
58+
hash[0] = 0;
59+
Ok(hash)
60+
}
61+
}
62+
63+
impl HashToFieldSize for &[u8] {
64+
fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError> {
65+
let mut slices: [&[u8]; 2] = [&[]; 2];
66+
slices[0] = self;
67+
let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
68+
slices[1] = bump_seed.as_slice();
69+
let mut hash = Keccak::hashv(slices.as_slice())?;
70+
hash[0] = 0;
71+
Ok(hash)
72+
}
73+
}
74+
75+
impl<T, const PAD: bool, L> HashToFieldSize for light_zero_copy::slice::ZeroCopySlice<'_, L, T, PAD>
76+
where
77+
T: ToByteArray + ZeroCopyTraits,
78+
L: ZeroCopyTraits,
79+
u64: From<L>,
80+
{
81+
fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError> {
82+
let mut arrays = Vec::with_capacity(self.len());
83+
for item in self.iter() {
84+
let byte_array = item.to_byte_array()?;
85+
arrays.push(byte_array);
86+
}
87+
let mut slices = Vec::with_capacity(self.len() + 1);
88+
arrays.iter().for_each(|x| slices.push(x.as_slice()));
89+
let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
90+
slices.push(bump_seed.as_slice());
91+
let mut hash = Keccak::hashv(slices.as_slice())?;
92+
hash[0] = 0;
93+
Ok(hash)
94+
}
95+
}
96+
97+
impl<T, const PAD: bool, L> HashToFieldSize
98+
for light_zero_copy::slice_mut::ZeroCopySliceMut<'_, L, T, PAD>
99+
where
100+
T: ToByteArray + ZeroCopyTraits,
101+
L: ZeroCopyTraits,
102+
u64: From<L>,
103+
{
104+
fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError> {
105+
let mut arrays = Vec::with_capacity(self.len());
106+
for item in self.iter() {
107+
let byte_array = item.to_byte_array()?;
108+
arrays.push(byte_array);
109+
}
110+
let mut slices = Vec::with_capacity(self.len() + 1);
111+
arrays.iter().for_each(|x| slices.push(x.as_slice()));
112+
let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
113+
slices.push(bump_seed.as_slice());
114+
let mut hash = Keccak::hashv(slices.as_slice())?;
115+
hash[0] = 0;
116+
Ok(hash)
45117
}
46118
}
47119

program-libs/hasher/src/to_byte_array.rs

+25-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use zerocopy::IntoBytes;
2-
31
use crate::{Hasher, HasherError, Poseidon};
42

53
pub trait ToByteArray {
@@ -57,24 +55,35 @@ impl ToByteArray for solana_program::pubkey::Pubkey {
5755
}
5856
}
5957

60-
impl ToByteArray for zerocopy::little_endian::U16 {
61-
const NUM_FIELDS: usize = 1;
58+
macro_rules! impl_to_byte_array_zero_copy_primitive_types {
59+
($target:ty, $inner:ty) => {
60+
impl ToByteArray for $target {
61+
const NUM_FIELDS: usize = 1;
62+
const IS_PRIMITIVE: bool = true;
6263

63-
fn to_byte_array(&self) -> Result<[u8; 32], HasherError> {
64-
let bytes: u16 = (*self).into();
65-
println!("U16 to byte array {}", bytes);
66-
bytes.to_byte_array()
67-
}
64+
fn to_byte_array(&self) -> Result<[u8; 32], HasherError> {
65+
let bytes: $inner = (*self).into();
66+
bytes.to_byte_array()
67+
}
6868

69-
fn to_byte_arrays<const NUM_FIELDS: usize>(
70-
&self,
71-
) -> Result<[[u8; 32]; NUM_FIELDS], HasherError> {
72-
if Self::NUM_FIELDS != NUM_FIELDS {
73-
return Err(HasherError::InvalidNumFields);
69+
fn to_byte_arrays<const NUM_FIELDS: usize>(
70+
&self,
71+
) -> Result<[[u8; 32]; NUM_FIELDS], HasherError> {
72+
if Self::NUM_FIELDS != NUM_FIELDS {
73+
return Err(HasherError::InvalidNumFields);
74+
}
75+
Ok([self.to_byte_array()?; NUM_FIELDS])
76+
}
7477
}
75-
Ok([self.to_byte_array()?; NUM_FIELDS])
76-
}
78+
};
7779
}
80+
use zerocopy::little_endian::{I16, I32, I64, U16, U32, U64};
81+
impl_to_byte_array_zero_copy_primitive_types!(U16, u16);
82+
impl_to_byte_array_zero_copy_primitive_types!(U32, u32);
83+
impl_to_byte_array_zero_copy_primitive_types!(U64, u64);
84+
impl_to_byte_array_zero_copy_primitive_types!(I16, i16);
85+
impl_to_byte_array_zero_copy_primitive_types!(I32, i32);
86+
impl_to_byte_array_zero_copy_primitive_types!(I64, i64);
7887

7988
impl<T: ToByteArray> ToByteArray for Option<T> {
8089
const NUM_FIELDS: usize = 1;

program-libs/zero-copy-derive/src/lib.rs

+3-15
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@ pub fn derive_zero_copy(input: TokenStream) -> TokenStream {
6767
}
6868
false
6969
});
70+
let saved_hasher = hasher;
7071
println!("hasher {}", hasher);
7172

7273
// Process the input to extract struct information
7374
let (name, z_struct_name, z_struct_meta_name, fields) = utils::process_input(&input);
7475

7576
// Process the fields to separate meta fields and struct fields
7677
let (meta_fields, struct_fields) = utils::process_fields(fields);
77-
78+
// let hasher = false;
7879
// Generate each implementation part using the respective modules
7980
let meta_struct_def_mut =
8081
meta_struct::generate_meta_struct::<true>(&z_struct_meta_name, &meta_fields, hasher);
@@ -88,6 +89,7 @@ pub fn derive_zero_copy(input: TokenStream) -> TokenStream {
8889
&meta_fields,
8990
hasher,
9091
);
92+
let hasher = saved_hasher;
9193
let z_struct_def = z_struct::generate_z_struct::<false>(
9294
&z_struct_name,
9395
&z_struct_meta_name,
@@ -159,20 +161,6 @@ pub fn derive_zero_copy_eq(input: TokenStream) -> TokenStream {
159161
// Parse the input DeriveInput
160162
let input = parse_macro_input!(input as DeriveInput);
161163

162-
// Check for LightHasher in derive attributes (same logic as in derive_zero_copy)
163-
let hasher = input.attrs.iter().any(|attr| {
164-
if attr.path().is_ident("poseidon_hasher") {
165-
return true;
166-
}
167-
168-
if attr.path().is_ident("derive") {
169-
let derive_string = attr.to_token_stream().to_string();
170-
return derive_string.contains("LightHasher");
171-
}
172-
173-
false
174-
});
175-
176164
// Process the input to extract struct information
177165
let (name, z_struct_name, z_struct_meta_name, fields) = utils::process_input(&input);
178166

program-libs/zero-copy-derive/src/meta_struct.rs

+13
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,21 @@ pub fn generate_meta_struct<const MUT: bool>(
1919
// Generate the meta struct fields with converted types
2020
let meta_fields_with_converted_types = meta_fields.iter().map(|field| {
2121
let field_name = &field.ident;
22+
let attributes = if hasher {
23+
field
24+
.attrs
25+
.iter()
26+
.map(|attr| {
27+
let path = attr;
28+
quote! { #path }
29+
})
30+
.collect::<Vec<_>>()
31+
} else {
32+
vec![quote! {}]
33+
};
2234
let field_type = convert_to_zerocopy_type(&field.ty);
2335
quote! {
36+
#(#attributes)*
2437
pub #field_name: #field_type
2538
}
2639
});

0 commit comments

Comments
 (0)