Skip to content

Commit 7f6196f

Browse files
cherrypicked hasher update: support sha in hasher and lighthasher macro
lint remove unused _output_account_info update lightdiscriminator macro
1 parent 50778df commit 7f6196f

File tree

13 files changed

+630
-42
lines changed

13 files changed

+630
-42
lines changed

program-libs/hasher/src/keccak.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::{
99
pub struct Keccak;
1010

1111
impl Hasher for Keccak {
12+
const ID: u8 = 2;
13+
1214
fn hash(val: &[u8]) -> Result<Hash, HasherError> {
1315
Self::hashv(&[val])
1416
}

program-libs/hasher/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub const HASH_BYTES: usize = 32;
2424
pub type Hash = [u8; HASH_BYTES];
2525

2626
pub trait Hasher {
27+
const ID: u8;
2728
fn hash(val: &[u8]) -> Result<Hash, HasherError>;
2829
fn hashv(vals: &[&[u8]]) -> Result<Hash, HasherError>;
2930
fn zero_bytes() -> ZeroBytes;

program-libs/hasher/src/poseidon.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ impl From<PoseidonSyscallError> for u64 {
7878
pub struct Poseidon;
7979

8080
impl Hasher for Poseidon {
81+
const ID: u8 = 0;
82+
8183
fn hash(val: &[u8]) -> Result<Hash, HasherError> {
8284
Self::hashv(&[val])
8385
}

program-libs/hasher/src/sha256.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
pub struct Sha256;
1010

1111
impl Hasher for Sha256 {
12+
const ID: u8 = 1;
1213
fn hash(val: &[u8]) -> Result<Hash, HasherError> {
1314
Self::hashv(&[val])
1415
}

sdk-libs/macros/src/discriminator.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ use quote::quote;
44
use syn::{ItemStruct, Result};
55

66
pub(crate) fn discriminator(input: ItemStruct) -> Result<TokenStream> {
7+
discriminator_with_hasher(input, false)
8+
}
9+
10+
pub(crate) fn discriminator_sha(input: ItemStruct) -> Result<TokenStream> {
11+
discriminator_with_hasher(input, true)
12+
}
13+
14+
fn discriminator_with_hasher(input: ItemStruct, is_sha: bool) -> Result<TokenStream> {
715
let account_name = &input.ident;
816

917
let (impl_gen, type_gen, where_clause) = input.generics.split_for_impl();
@@ -12,6 +20,10 @@ pub(crate) fn discriminator(input: ItemStruct) -> Result<TokenStream> {
1220
discriminator.copy_from_slice(&Sha256::hash(account_name.to_string().as_bytes()).unwrap()[..8]);
1321
let discriminator: proc_macro2::TokenStream = format!("{discriminator:?}").parse().unwrap();
1422

23+
// For SHA256 variant, we could add specific logic here if needed
24+
// Currently both variants work the same way since discriminator is just based on struct name
25+
let _variant_marker = if is_sha { "sha256" } else { "poseidon" };
26+
1527
Ok(quote! {
1628
impl #impl_gen LightDiscriminator for #account_name #type_gen #where_clause {
1729
const LIGHT_DISCRIMINATOR: [u8; 8] = #discriminator;
@@ -47,4 +59,46 @@ mod tests {
4759
assert!(output.contains("impl LightDiscriminator for MyAccount"));
4860
assert!(output.contains("[181 , 255 , 112 , 42 , 17 , 188 , 66 , 199]"));
4961
}
62+
63+
#[test]
64+
fn test_discriminator_sha() {
65+
let input: ItemStruct = parse_quote! {
66+
struct MyAccount {
67+
a: u32,
68+
b: i32,
69+
c: u64,
70+
d: i64,
71+
}
72+
};
73+
74+
let output = discriminator_sha(input).unwrap();
75+
let output = output.to_string();
76+
77+
assert!(output.contains("impl LightDiscriminator for MyAccount"));
78+
assert!(output.contains("[181 , 255 , 112 , 42 , 17 , 188 , 66 , 199]"));
79+
}
80+
81+
#[test]
82+
fn test_discriminator_sha_large_struct() {
83+
// Test that SHA256 discriminator can handle large structs (that would fail with regular hasher)
84+
let input: ItemStruct = parse_quote! {
85+
struct LargeAccount {
86+
pub field1: u64, pub field2: u64, pub field3: u64, pub field4: u64,
87+
pub field5: u64, pub field6: u64, pub field7: u64, pub field8: u64,
88+
pub field9: u64, pub field10: u64, pub field11: u64, pub field12: u64,
89+
pub field13: u64, pub field14: u64, pub field15: u64,
90+
pub owner: solana_program::pubkey::Pubkey,
91+
pub authority: solana_program::pubkey::Pubkey,
92+
}
93+
};
94+
95+
let result = discriminator_sha(input);
96+
assert!(
97+
result.is_ok(),
98+
"SHA256 discriminator should handle large structs"
99+
);
100+
101+
let output = result.unwrap().to_string();
102+
assert!(output.contains("impl LightDiscriminator for LargeAccount"));
103+
}
50104
}

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

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ pub(crate) fn generate_data_hasher_impl(
3737
slices[num_flattned_fields] = element.as_slice();
3838
}
3939

40-
H::hashv(slices.as_slice())
40+
let mut result = H::hashv(slices.as_slice())?;
41+
42+
// Apply field size truncation for non-Poseidon hashers
43+
if H::ID != 0 {
44+
result[0] = 0;
45+
}
46+
47+
Ok(result)
4148
}
4249
}
4350
}
@@ -59,10 +66,50 @@ pub(crate) fn generate_data_hasher_impl(
5966
println!("DataHasher::hash inputs {:?}", debug_prints);
6067
}
6168
}
62-
H::hashv(&[
69+
let mut result = H::hashv(&[
6370
#(#data_hasher_assignments.as_slice(),)*
64-
])
71+
])?;
72+
73+
// Apply field size truncation for non-Poseidon hashers
74+
if H::ID != 0 {
75+
result[0] = 0;
76+
}
77+
78+
Ok(result)
79+
}
80+
}
81+
}
82+
};
83+
84+
Ok(hasher_impl)
85+
}
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;
65110
}
111+
112+
Ok(result)
66113
}
67114
}
68115
};

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")) {

0 commit comments

Comments
 (0)