Skip to content

Commit f702f3d

Browse files
committed
unify mint hashing
1 parent 2e21277 commit f702f3d

File tree

2 files changed

+85
-132
lines changed

2 files changed

+85
-132
lines changed

program-libs/ctoken-types/src/state/extensions/token_metadata.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,13 @@ pub fn token_metadata_hash<H: light_hasher::Hasher>(
130130
additional_metadata: &[(&[u8], &[u8])],
131131
version: u8,
132132
) -> Result<[u8; 32], HasherError> {
133-
token_metadata_hash_inner::<H, false>(update_authority, mint, metadata_hash, additional_metadata, version)
133+
token_metadata_hash_inner::<H, false>(
134+
update_authority,
135+
mint,
136+
metadata_hash,
137+
additional_metadata,
138+
version,
139+
)
134140
}
135141

136142
pub fn token_metadata_hash_with_hashed_values<H: light_hasher::Hasher>(
@@ -140,7 +146,13 @@ pub fn token_metadata_hash_with_hashed_values<H: light_hasher::Hasher>(
140146
additional_metadata: &[(&[u8], &[u8])],
141147
version: u8,
142148
) -> Result<[u8; 32], HasherError> {
143-
token_metadata_hash_inner::<H, true>(hashed_update_authority, hashed_mint, metadata_hash, additional_metadata, version)
149+
token_metadata_hash_inner::<H, true>(
150+
hashed_update_authority,
151+
hashed_mint,
152+
metadata_hash,
153+
additional_metadata,
154+
version,
155+
)
144156
}
145157

146158
macro_rules! impl_token_metadata_hasher {

program-libs/ctoken-types/src/state/mint.rs

Lines changed: 71 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
use light_compressed_account::Pubkey;
22
use light_hasher::{errors::HasherError, sha256::Sha256BE, Hasher, Poseidon};
33
use light_zero_copy::{traits::ZeroCopyAt, ZeroCopy, ZeroCopyMut};
4-
use solana_msg::msg;
54
use zerocopy::IntoBytes;
65

76
use crate::{
8-
hash_cache::HashCache,
9-
instructions::mint_action::CompressedMintInstructionData,
10-
state::{ExtensionStruct, ZExtensionStructMut},
11-
AnchorDeserialize, AnchorSerialize, CTokenError,
7+
hash_cache::HashCache, instructions::mint_action::CompressedMintInstructionData,
8+
state::ExtensionStruct, AnchorDeserialize, AnchorSerialize, CTokenError,
129
};
1310

1411
// Order is optimized for hashing.
@@ -38,19 +35,16 @@ pub struct CompressedMint {
3835
pub extensions: Option<Vec<ExtensionStruct>>,
3936
}
4037

41-
// TODO: unify code if possible
42-
// use nested token metadata layout for data extension
43-
impl CompressedMint {
44-
#[allow(dead_code)]
45-
pub fn hash(&self) -> Result<[u8; 32], CTokenError> {
46-
let mut hash_cache = HashCache::new();
47-
self.hash_with_cache(&mut hash_cache)
48-
}
49-
50-
pub fn hash_with_cache(&self, hash_cache: &mut HashCache) -> Result<[u8; 32], CTokenError> {
51-
let hashed_spl_mint = hash_cache.get_or_hash_mint(&self.spl_mint.into())?;
38+
macro_rules! impl_compressed_mint_hash {
39+
(
40+
$self:ident,
41+
$hash_cache:ident,
42+
$is_decompressed:expr
43+
$(,$deref_op:tt)?
44+
) => {{
45+
let hashed_spl_mint = $hash_cache.get_or_hash_mint(&$self.spl_mint.into())?;
5246
let mut supply_bytes = [0u8; 32];
53-
self.supply
47+
$self.supply
5448
.as_bytes()
5549
.iter()
5650
.rev()
@@ -59,18 +53,18 @@ impl CompressedMint {
5953

6054
let hashed_mint_authority;
6155
let hashed_mint_authority_option =
62-
if let Some(mint_authority) = self.mint_authority.as_ref() {
63-
hashed_mint_authority = hash_cache.get_or_hash_pubkey(&mint_authority.to_bytes());
56+
if let Some(mint_authority) = $self.mint_authority.as_ref() {
57+
hashed_mint_authority = $hash_cache.get_or_hash_pubkey(&($($deref_op)?mint_authority).to_bytes());
6458
Some(&hashed_mint_authority)
6559
} else {
6660
None
6761
};
6862

6963
let hashed_freeze_authority;
7064
let hashed_freeze_authority_option = if let Some(freeze_authority) =
71-
self.freeze_authority.as_ref()
65+
$self.freeze_authority.as_ref()
7266
{
73-
hashed_freeze_authority = hash_cache.get_or_hash_pubkey(&freeze_authority.to_bytes());
67+
hashed_freeze_authority = $hash_cache.get_or_hash_pubkey(&($($deref_op)?freeze_authority).to_bytes());
7468
Some(&hashed_freeze_authority)
7569
} else {
7670
None
@@ -79,36 +73,44 @@ impl CompressedMint {
7973
let mint_hash = CompressedMint::hash_with_hashed_values(
8074
&hashed_spl_mint,
8175
&supply_bytes,
82-
self.decimals,
83-
self.is_decompressed,
76+
$self.decimals,
77+
$is_decompressed,
8478
&hashed_mint_authority_option,
8579
&hashed_freeze_authority_option,
86-
self.version,
80+
$self.version,
8781
)?;
8882

89-
if let Some(extensions) = self.extensions.as_ref() {
83+
if let Some(extensions) = $self.extensions.as_ref() {
9084
let mut extension_hashchain = [0u8; 32];
9185
for extension in extensions.as_slice() {
92-
if self.version == 0 {
86+
let extension_hash = if $self.version == 0 {
87+
extension.hash::<Poseidon>()?
88+
} else if $self.version == 1 {
89+
extension.hash::<Sha256BE>()?
90+
} else {
91+
return Err(CTokenError::InvalidTokenDataVersion);
92+
};
93+
94+
if $self.version == 0 {
9395
extension_hashchain = Poseidon::hashv(&[
9496
extension_hashchain.as_slice(),
95-
extension.hash::<Poseidon>()?.as_slice(),
97+
extension_hash.as_slice(),
9698
])?;
97-
} else if self.version == 1 {
99+
} else if $self.version == 1 {
98100
extension_hashchain = Sha256BE::hashv(&[
99101
extension_hashchain.as_slice(),
100-
extension.hash::<Sha256BE>()?.as_slice(),
102+
extension_hash.as_slice(),
101103
])?;
102104
} else {
103105
return Err(CTokenError::InvalidTokenDataVersion);
104106
}
105107
}
106-
if self.version == 0 {
108+
if $self.version == 0 {
107109
Ok(Poseidon::hashv(&[
108110
mint_hash.as_slice(),
109111
extension_hashchain.as_slice(),
110112
])?)
111-
} else if self.version == 1 {
113+
} else if $self.version == 1 {
112114
Ok(Sha256BE::hashv(&[
113115
mint_hash.as_slice(),
114116
extension_hashchain.as_slice(),
@@ -119,6 +121,20 @@ impl CompressedMint {
119121
} else {
120122
Ok(mint_hash)
121123
}
124+
}};
125+
}
126+
127+
// TODO: unify code if possible
128+
// use nested token metadata layout for data extension
129+
impl CompressedMint {
130+
#[allow(dead_code)]
131+
pub fn hash(&self) -> Result<[u8; 32], CTokenError> {
132+
let mut hash_cache = HashCache::new();
133+
self.hash_with_cache(&mut hash_cache)
134+
}
135+
136+
pub fn hash_with_cache(&self, hash_cache: &mut HashCache) -> Result<[u8; 32], CTokenError> {
137+
impl_compressed_mint_hash!(self, hash_cache, self.is_decompressed)
122138
}
123139

124140
pub fn hash_with_hashed_values(
@@ -163,147 +179,72 @@ impl CompressedMint {
163179
hashed_freeze_authority: &Option<&[u8; 32]>,
164180
version: u8,
165181
) -> Result<[u8; 32], HasherError> {
166-
let mut hash_inputs = vec![hashed_spl_mint.as_slice(), supply_bytes.as_slice()];
182+
// Note: ArrayVec causes lifetime issues.
183+
let mut hash_inputs: [&[u8]; 8] = [&[]; 8];
184+
185+
hash_inputs[0] = hashed_spl_mint.as_slice();
186+
hash_inputs[1] = supply_bytes.as_slice();
187+
let mut input_count = 2;
167188

168189
// Add decimals with prefix if not 0
169190
let mut decimals_bytes = [0u8; 32];
170191
if decimals != 0 {
171192
decimals_bytes[30] = 1; // decimals prefix
172193
decimals_bytes[31] = decimals;
173-
hash_inputs.push(&decimals_bytes[..]);
194+
hash_inputs[input_count] = &decimals_bytes[..];
195+
input_count += 1;
174196
}
175197

176198
// Add is_decompressed with prefix if true
177199
let mut is_decompressed_bytes = [0u8; 32];
178200
if is_decompressed {
179201
is_decompressed_bytes[30] = 2; // is_decompressed prefix
180202
is_decompressed_bytes[31] = 1; // true as 1
181-
hash_inputs.push(&is_decompressed_bytes[..]);
203+
hash_inputs[input_count] = &is_decompressed_bytes[..];
204+
input_count += 1;
182205
}
183206

184207
// Add mint authority if present
185208
if let Some(hashed_mint_authority) = hashed_mint_authority {
186-
hash_inputs.push(hashed_mint_authority.as_slice());
209+
hash_inputs[input_count] = hashed_mint_authority.as_slice();
210+
input_count += 1;
187211
}
188212

189213
// Add freeze authority if present
190214
let empty_authority = [0u8; 32];
191215
if let Some(hashed_freeze_authority) = hashed_freeze_authority {
192216
// If there is freeze authority but no mint authority, add empty mint authority
193217
if hashed_mint_authority.is_none() {
194-
hash_inputs.push(&empty_authority[..]);
218+
hash_inputs[input_count] = &empty_authority[..];
219+
input_count += 1;
195220
}
196-
hash_inputs.push(hashed_freeze_authority.as_slice());
221+
hash_inputs[input_count] = hashed_freeze_authority.as_slice();
222+
input_count += 1;
197223
}
198224

199225
// Add version with prefix if not 0
200226
let mut num_extensions_bytes = [0u8; 32];
201227
if version != 0 {
202228
num_extensions_bytes[30] = 3; // version prefix
203229
num_extensions_bytes[31] = version;
204-
hash_inputs.push(&num_extensions_bytes[..]);
230+
hash_inputs[input_count] = &num_extensions_bytes[..];
231+
input_count += 1;
205232
}
206233

207-
let hash = H::hashv(hash_inputs.as_slice())?;
234+
let hash = H::hashv(&hash_inputs[..input_count])?;
208235

209236
Ok(hash)
210237
}
211238
}
212239

213240
impl ZCompressedMintMut<'_> {
214241
pub fn hash(&self, hash_cache: &mut HashCache) -> Result<[u8; 32], CTokenError> {
215-
let hashed_spl_mint = hash_cache.get_or_hash_mint(&self.spl_mint.into())?;
216-
let mut supply_bytes = [0u8; 32];
217-
self.supply
218-
.as_bytes()
219-
.iter()
220-
.rev()
221-
.zip(supply_bytes[24..].iter_mut())
222-
.for_each(|(x, y)| *y = *x);
223-
224-
let hashed_mint_authority;
225-
let hashed_mint_authority_option = if let Some(mint_authority) =
226-
self.mint_authority.as_ref()
227-
{
228-
hashed_mint_authority = hash_cache.get_or_hash_pubkey(&(*mint_authority).to_bytes());
229-
Some(&hashed_mint_authority)
230-
} else {
231-
None
232-
};
233-
234-
let hashed_freeze_authority;
235-
let hashed_freeze_authority_option =
236-
if let Some(freeze_authority) = self.freeze_authority.as_ref() {
237-
hashed_freeze_authority =
238-
hash_cache.get_or_hash_pubkey(&(*freeze_authority).to_bytes());
239-
Some(&hashed_freeze_authority)
240-
} else {
241-
None
242-
};
243-
244-
let mint_hash = CompressedMint::hash_with_hashed_values(
245-
&hashed_spl_mint,
246-
&supply_bytes,
247-
self.decimals,
242+
impl_compressed_mint_hash!(
243+
self,
244+
hash_cache,
248245
self.is_decompressed(),
249-
&hashed_mint_authority_option,
250-
&hashed_freeze_authority_option,
251-
self.version,
252-
)?;
253-
254-
if let Some(extensions) = self.extensions.as_ref() {
255-
let mut extension_hashchain = [0u8; 32];
256-
for extension in extensions.as_slice() {
257-
let extension_hash = match extension {
258-
ZExtensionStructMut::TokenMetadata(token_metadata) => {
259-
if *token_metadata.version == 0 {
260-
extension.hash::<Poseidon>()?
261-
} else if *token_metadata.version == 1 {
262-
extension.hash::<Sha256BE>()?
263-
} else {
264-
return Err(CTokenError::InvalidTokenDataVersion);
265-
}
266-
}
267-
_ => return Err(CTokenError::UnsupportedExtension),
268-
};
269-
270-
if self.version == 0 {
271-
extension_hashchain = Poseidon::hashv(&[
272-
extension_hashchain.as_slice(),
273-
extension_hash.as_slice(),
274-
])?;
275-
} else if self.version == 1 {
276-
extension_hashchain = Sha256BE::hashv(&[
277-
extension_hashchain.as_slice(),
278-
extension_hash.as_slice(),
279-
])?;
280-
} else {
281-
msg!("invalid version ");
282-
return Err(CTokenError::InvalidTokenDataVersion);
283-
}
284-
}
285-
286-
msg!(
287-
"ZCompressedMintMut extension_hashchain: {:?} ",
288-
extension_hashchain
289-
);
290-
291-
if self.version == 0 {
292-
Ok(Poseidon::hashv(&[
293-
mint_hash.as_slice(),
294-
extension_hashchain.as_slice(),
295-
])?)
296-
} else if self.version == 1 {
297-
Ok(Sha256BE::hashv(&[
298-
mint_hash.as_slice(),
299-
extension_hashchain.as_slice(),
300-
])?)
301-
} else {
302-
Err(CTokenError::InvalidTokenDataVersion)
303-
}
304-
} else {
305-
Ok(mint_hash)
306-
}
246+
*
247+
)
307248
}
308249
}
309250

0 commit comments

Comments
 (0)