Skip to content

Commit cdcf564

Browse files
DataHasher macro: explicit validation for sha
1 parent a778539 commit cdcf564

File tree

5 files changed

+211
-16
lines changed

5 files changed

+211
-16
lines changed

sdk-libs/macros/src/hasher/data_hasher.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,36 @@ pub(crate) fn generate_data_hasher_impl(
8383

8484
Ok(hasher_impl)
8585
}
86+
87+
/// SHA256-specific DataHasher implementation that serializes the whole struct
88+
pub(crate) fn generate_data_hasher_impl_sha(
89+
struct_name: &syn::Ident,
90+
generics: &syn::Generics,
91+
) -> Result<TokenStream> {
92+
let (impl_gen, type_gen, where_clause) = generics.split_for_impl();
93+
94+
let hasher_impl = quote! {
95+
impl #impl_gen ::light_hasher::DataHasher for #struct_name #type_gen #where_clause {
96+
fn hash<H>(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError>
97+
where
98+
H: ::light_hasher::Hasher
99+
{
100+
use ::light_hasher::Hasher;
101+
use borsh::BorshSerialize;
102+
103+
// For SHA256, we serialize the whole struct and hash it in one go
104+
let serialized = self.try_to_vec().map_err(|_| ::light_hasher::HasherError::BorshError)?;
105+
let mut result = H::hash(&serialized)?;
106+
107+
// Truncate field size for non-Poseidon hashers
108+
if H::ID != 0 {
109+
result[0] = 0;
110+
}
111+
112+
Ok(result)
113+
}
114+
}
115+
};
116+
117+
Ok(hasher_impl)
118+
}

sdk-libs/macros/src/hasher/input_validator.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,36 @@ pub(crate) fn validate_input(input: &ItemStruct) -> Result<()> {
6060
Ok(())
6161
}
6262

63+
/// SHA256-specific validation - much more relaxed constraints
64+
pub(crate) fn validate_input_sha(input: &ItemStruct) -> Result<()> {
65+
// Check that we have a struct with named fields
66+
match &input.fields {
67+
Fields::Named(_) => (),
68+
_ => {
69+
return Err(Error::new_spanned(
70+
input,
71+
"Only structs with named fields are supported",
72+
))
73+
}
74+
};
75+
76+
// For SHA256, we don't limit field count or require specific attributes
77+
// Just ensure flatten is not used (not implemented for SHA256 path)
78+
let flatten_field_exists = input
79+
.fields
80+
.iter()
81+
.any(|field| get_field_attribute(field) == FieldAttribute::Flatten);
82+
83+
if flatten_field_exists {
84+
return Err(Error::new_spanned(
85+
input,
86+
"Flatten attribute is not supported in SHA256 hasher.",
87+
));
88+
}
89+
90+
Ok(())
91+
}
92+
6393
/// Gets the primary attribute for a field (only one attribute can be active)
6494
pub(crate) fn get_field_attribute(field: &Field) -> FieldAttribute {
6595
if field.attrs.iter().any(|attr| attr.path().is_ident("hash")) {

sdk-libs/macros/src/hasher/light_hasher.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use quote::quote;
33
use syn::{Fields, ItemStruct, Result};
44

55
use crate::hasher::{
6-
data_hasher::generate_data_hasher_impl,
6+
data_hasher::{generate_data_hasher_impl, generate_data_hasher_impl_sha},
77
field_processor::{process_field, FieldProcessingContext},
8-
input_validator::{get_field_attribute, validate_input, FieldAttribute},
9-
to_byte_array::generate_to_byte_array_impl_with_hasher,
8+
input_validator::{get_field_attribute, validate_input, validate_input_sha, FieldAttribute},
9+
to_byte_array::{generate_to_byte_array_impl_sha, generate_to_byte_array_impl_with_hasher},
1010
};
1111

1212
/// - ToByteArray:
@@ -53,7 +53,26 @@ pub(crate) fn derive_light_hasher(input: ItemStruct) -> Result<TokenStream> {
5353
}
5454

5555
pub(crate) fn derive_light_hasher_sha(input: ItemStruct) -> Result<TokenStream> {
56-
derive_light_hasher_with_hasher(input, &quote!(::light_hasher::Sha256))
56+
// Use SHA256-specific validation (no field count limits)
57+
validate_input_sha(&input)?;
58+
59+
let generics = input.generics.clone();
60+
61+
let fields = match &input.fields {
62+
Fields::Named(fields) => fields.clone(),
63+
_ => unreachable!("Validation should have caught this"),
64+
};
65+
66+
let field_count = fields.named.len();
67+
68+
let to_byte_array_impl = generate_to_byte_array_impl_sha(&input.ident, &generics, field_count)?;
69+
let data_hasher_impl = generate_data_hasher_impl_sha(&input.ident, &generics)?;
70+
71+
Ok(quote! {
72+
#to_byte_array_impl
73+
74+
#data_hasher_impl
75+
})
5776
}
5877

5978
fn derive_light_hasher_with_hasher(input: ItemStruct, hasher: &TokenStream) -> Result<TokenStream> {

sdk-libs/macros/src/hasher/to_byte_array.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,70 @@ pub(crate) fn generate_to_byte_array_impl_with_hasher(
2121
Some(s) => s,
2222
None => &alt_res,
2323
};
24-
let field_assignment: TokenStream = syn::parse_str(str)?;
25-
26-
// Create a token stream with the field_assignment and the import code
27-
let mut hash_imports = proc_macro2::TokenStream::new();
28-
for code in &context.hash_to_field_size_code {
29-
hash_imports.extend(code.clone());
30-
}
3124

25+
let content: TokenStream = str.parse().expect("Invalid generated code");
3226
Ok(quote! {
3327
impl #impl_gen ::light_hasher::to_byte_array::ToByteArray for #struct_name #type_gen #where_clause {
34-
const NUM_FIELDS: usize = #field_count;
28+
const NUM_FIELDS: usize = 1;
3529

3630
fn to_byte_array(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> {
37-
#hash_imports
38-
#field_assignment
31+
use ::light_hasher::to_byte_array::ToByteArray;
32+
use ::light_hasher::hash_to_field_size::HashToFieldSize;
33+
#content
3934
}
4035
}
4136
})
4237
} else {
38+
let data_hasher_assignments = &context.data_hasher_assignments;
4339
Ok(quote! {
4440
impl #impl_gen ::light_hasher::to_byte_array::ToByteArray for #struct_name #type_gen #where_clause {
4541
const NUM_FIELDS: usize = #field_count;
4642

4743
fn to_byte_array(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> {
48-
::light_hasher::DataHasher::hash::<#hasher>(self)
49-
}
44+
use ::light_hasher::to_byte_array::ToByteArray;
45+
use ::light_hasher::hash_to_field_size::HashToFieldSize;
46+
use ::light_hasher::Hasher;
47+
let mut result = #hasher::hashv(&[
48+
#(#data_hasher_assignments.as_slice(),)*
49+
])?;
50+
51+
// Truncate field size for non-Poseidon hashers
52+
if #hasher::ID != 0 {
53+
result[0] = 0;
54+
}
5055

56+
Ok(result)
57+
}
5158
}
5259
})
5360
}
5461
}
62+
63+
/// SHA256-specific ToByteArray implementation that serializes the whole struct
64+
pub(crate) fn generate_to_byte_array_impl_sha(
65+
struct_name: &syn::Ident,
66+
generics: &syn::Generics,
67+
field_count: usize,
68+
) -> Result<TokenStream> {
69+
let (impl_gen, type_gen, where_clause) = generics.split_for_impl();
70+
71+
Ok(quote! {
72+
impl #impl_gen ::light_hasher::to_byte_array::ToByteArray for #struct_name #type_gen #where_clause {
73+
const NUM_FIELDS: usize = #field_count;
74+
75+
fn to_byte_array(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> {
76+
use borsh::BorshSerialize;
77+
use ::light_hasher::Hasher;
78+
79+
// For SHA256, we can serialize the whole struct and hash it in one go
80+
let serialized = self.try_to_vec().map_err(|_| ::light_hasher::HasherError::BorshError)?;
81+
let mut result = ::light_hasher::Sha256::hash(&serialized)?;
82+
83+
// Truncate field size for non-Poseidon hashers
84+
result[0] = 0;
85+
86+
Ok(result)
87+
}
88+
}
89+
})
90+
}

sdk-tests/native-compressible/src/lib.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,81 @@ mod test_sha_hasher {
203203

204204
println!("Same account with Poseidon: {:?}", poseidon_hash);
205205
}
206+
207+
#[test]
208+
fn test_large_struct_with_sha_hasher() {
209+
// This demonstrates that SHA256 can handle arbitrary-sized data
210+
// while Poseidon is limited to 12 fields in the current implementation
211+
212+
use light_hasher::{Hasher, Sha256};
213+
214+
// Create a large struct that would exceed Poseidon's field limits
215+
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
216+
struct LargeStruct {
217+
pub field1: u64,
218+
pub field2: u64,
219+
pub field3: u64,
220+
pub field4: u64,
221+
pub field5: u64,
222+
pub field6: u64,
223+
pub field7: u64,
224+
pub field8: u64,
225+
pub field9: u64,
226+
pub field10: u64,
227+
pub field11: u64,
228+
pub field12: u64,
229+
pub field13: u64,
230+
// Pubkeys that would require #[hash] attribute with Poseidon
231+
pub owner: solana_program::pubkey::Pubkey,
232+
pub authority: solana_program::pubkey::Pubkey,
233+
}
234+
235+
let large_account = LargeStruct {
236+
field1: 1,
237+
field2: 2,
238+
field3: 3,
239+
field4: 4,
240+
field5: 5,
241+
field6: 6,
242+
field7: 7,
243+
field8: 8,
244+
field9: 9,
245+
field10: 10,
246+
field11: 11,
247+
field12: 12,
248+
field13: 13,
249+
owner: solana_program::pubkey::Pubkey::new_unique(),
250+
authority: solana_program::pubkey::Pubkey::new_unique(),
251+
};
252+
253+
// Test that SHA256 can hash large data by serializing the whole struct
254+
let serialized = large_account.try_to_vec().unwrap();
255+
println!("Serialized struct size: {} bytes", serialized.len());
256+
257+
// SHA256 can hash arbitrary amounts of data
258+
let sha_hash = Sha256::hash(&serialized).unwrap();
259+
println!("SHA256 hash: {:?}", sha_hash);
260+
261+
// Verify the hash is truncated properly (first byte should be 0 for field size)
262+
// Note: Since SHA256::ID = 1 (not 0), the system program expects truncation
263+
let mut expected_hash = sha_hash;
264+
expected_hash[0] = 0;
265+
266+
assert_eq!(sha_hash.len(), 32);
267+
// For demonstration - in real usage, the truncation would be applied by the system
268+
println!("SHA256 hash truncated: {:?}", expected_hash);
269+
270+
// Show that this would be different from a smaller struct
271+
let small_struct = MyPdaAccount {
272+
compression_info: None,
273+
data: [42u8; 31],
274+
};
275+
276+
let small_serialized = small_struct.try_to_vec().unwrap();
277+
let small_hash = Sha256::hash(&small_serialized).unwrap();
278+
279+
// Different data should produce different hashes
280+
assert_ne!(sha_hash, small_hash);
281+
println!("Different struct produces different hash: {:?}", small_hash);
282+
}
206283
}

0 commit comments

Comments
 (0)