light_token_interface/state/compressed_token/
hash.rs

1use borsh::BorshSerialize;
2use light_compressed_account::hash_to_bn254_field_size_be;
3use light_hasher::{errors::HasherError, sha256::Sha256BE, Hasher, Poseidon};
4use light_program_profiler::profile;
5
6use super::TokenData;
7use crate::{state::compressed_token::CompressedTokenAccountState, NATIVE_MINT};
8
9/// Hashing schema: H(mint, owner, amount, delegate, delegated_amount,
10/// is_native, state)
11///
12/// delegate, delegated_amount, is_native and state have dynamic positions.
13/// Always hash mint, owner and amount If delegate hash delegate and
14/// delegated_amount together. If is native hash is_native else is omitted.
15/// If frozen hash AccountState::Frozen else is omitted.
16///
17/// Security: to prevent the possibility that different fields with the same
18/// value to result in the same hash we add a prefix to the delegated amount, is
19/// native and state fields. This way we can have a dynamic hashing schema and
20/// hash only used values.
21impl TokenData {
22    /// Only the spl representation of native tokens (wrapped SOL) is
23    /// compressed.
24    /// The sol value is stored in the token pool account.
25    /// The sol value in the compressed account is independent from
26    /// the wrapped sol amount.
27    pub fn is_native(&self) -> bool {
28        self.mint == NATIVE_MINT
29    }
30
31    pub fn hash_with_hashed_values(
32        hashed_mint: &[u8; 32],
33        hashed_owner: &[u8; 32],
34        amount_bytes: &[u8; 32],
35        hashed_delegate: &Option<&[u8; 32]>,
36    ) -> Result<[u8; 32], HasherError> {
37        Self::hash_inputs_with_hashed_values::<false>(
38            hashed_mint,
39            hashed_owner,
40            amount_bytes,
41            hashed_delegate,
42        )
43    }
44
45    pub fn hash_frozen_with_hashed_values(
46        hashed_mint: &[u8; 32],
47        hashed_owner: &[u8; 32],
48        amount_bytes: &[u8; 32],
49        hashed_delegate: &Option<&[u8; 32]>,
50    ) -> Result<[u8; 32], HasherError> {
51        Self::hash_inputs_with_hashed_values::<true>(
52            hashed_mint,
53            hashed_owner,
54            amount_bytes,
55            hashed_delegate,
56        )
57    }
58
59    /// We should not hash pubkeys multiple times. For all we can assume mints
60    /// are equal. For all input compressed accounts we assume owners are
61    /// equal.
62    pub fn hash_inputs_with_hashed_values<const FROZEN_INPUTS: bool>(
63        mint: &[u8; 32],
64        owner: &[u8; 32],
65        amount_bytes: &[u8],
66        hashed_delegate: &Option<&[u8; 32]>,
67    ) -> Result<[u8; 32], HasherError> {
68        let mut hash_inputs = vec![mint.as_slice(), owner.as_slice(), amount_bytes];
69        if let Some(hashed_delegate) = hashed_delegate {
70            hash_inputs.push(hashed_delegate.as_slice());
71        }
72        let mut state_bytes = [0u8; 32];
73        if FROZEN_INPUTS {
74            state_bytes[31] = CompressedTokenAccountState::Frozen as u8;
75            hash_inputs.push(&state_bytes[..]);
76        }
77        Poseidon::hashv(hash_inputs.as_slice())
78    }
79}
80
81impl TokenData {
82    /// TokenDataVersion 3
83    /// CompressedAccount Discriminator [0,0,0,0,0,0,0,4]
84    #[profile]
85    #[inline(always)]
86    pub fn hash_sha_flat(&self) -> Result<[u8; 32], HasherError> {
87        let bytes = self.try_to_vec().map_err(|_| HasherError::BorshError)?;
88        Sha256BE::hash(bytes.as_slice())
89    }
90
91    /// Hashes token data of token accounts.
92    ///
93    /// Note, hashing changed for token account data in batched Merkle trees.
94    /// For hashing of token account data stored in concurrent Merkle trees use hash_v1().
95    /// TokenDataVersion 2
96    /// CompressedAccount Discriminator [0,0,0,0,0,0,0,3]
97    pub fn hash_v2(&self) -> Result<[u8; 32], HasherError> {
98        self._hash::<true>()
99    }
100
101    /// Hashes token data of token accounts stored in concurrent Merkle trees.
102    /// TokenDataVersion 1
103    /// CompressedAccount Discriminator [2,0,0,0,0,0,0,0]
104    ///
105    pub fn hash_v1(&self) -> Result<[u8; 32], HasherError> {
106        self._hash::<false>()
107    }
108
109    fn _hash<const BATCHED: bool>(&self) -> Result<[u8; 32], HasherError> {
110        let hashed_mint = hash_to_bn254_field_size_be(self.mint.to_bytes().as_slice());
111        let hashed_owner = hash_to_bn254_field_size_be(self.owner.to_bytes().as_slice());
112        let mut amount_bytes = [0u8; 32];
113        if BATCHED {
114            amount_bytes[24..].copy_from_slice(self.amount.to_be_bytes().as_slice());
115        } else {
116            amount_bytes[24..].copy_from_slice(self.amount.to_le_bytes().as_slice());
117        }
118        let hashed_delegate;
119        let hashed_delegate_option = if let Some(delegate) = self.delegate {
120            hashed_delegate = hash_to_bn254_field_size_be(delegate.to_bytes().as_slice());
121            Some(&hashed_delegate)
122        } else {
123            None
124        };
125        if self.state != CompressedTokenAccountState::Initialized as u8 {
126            Self::hash_inputs_with_hashed_values::<true>(
127                &hashed_mint,
128                &hashed_owner,
129                &amount_bytes,
130                &hashed_delegate_option,
131            )
132        } else {
133            Self::hash_inputs_with_hashed_values::<false>(
134                &hashed_mint,
135                &hashed_owner,
136                &amount_bytes,
137                &hashed_delegate_option,
138            )
139        }
140    }
141}