light_system_program/invoke_cpi/
verify_signer.rs

1use account_compression::{
2    utils::{check_discrimininator::check_discriminator, constants::CPI_AUTHORITY_PDA_SEED},
3    AddressMerkleTreeAccount, StateMerkleTreeAccount,
4};
5use anchor_lang::prelude::*;
6use light_concurrent_merkle_tree::zero_copy::ConcurrentMerkleTreeZeroCopy;
7use light_hasher::Poseidon;
8use light_heap::{bench_sbf_end, bench_sbf_start};
9use light_macros::heap_neutral;
10use std::mem;
11
12use crate::{
13    errors::SystemProgramError, sdk::compressed_account::PackedCompressedAccountWithMerkleContext,
14    OutputCompressedAccountWithPackedContext,
15};
16
17/// Checks:
18/// 1. Invoking program is signer (cpi_signer_check)
19/// 2. Input compressed accounts with data are owned by the invoking program
20///    (input_compressed_accounts_signer_check)
21/// 3. Output compressed accounts with data are owned by the invoking program
22///    (output_compressed_accounts_write_access_check)
23pub fn cpi_signer_checks(
24    invoking_programid: &Pubkey,
25    authority: &Pubkey,
26    input_compressed_accounts_with_merkle_context: &[PackedCompressedAccountWithMerkleContext],
27    output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
28) -> Result<()> {
29    bench_sbf_start!("cpda_cpi_signer_checks");
30    cpi_signer_check(invoking_programid, authority)?;
31    bench_sbf_end!("cpda_cpi_signer_checks");
32    bench_sbf_start!("cpd_input_checks");
33    input_compressed_accounts_signer_check(
34        input_compressed_accounts_with_merkle_context,
35        invoking_programid,
36    )?;
37    bench_sbf_end!("cpd_input_checks");
38    bench_sbf_start!("cpda_cpi_write_checks");
39    output_compressed_accounts_write_access_check(output_compressed_accounts, invoking_programid)?;
40    bench_sbf_end!("cpda_cpi_write_checks");
41    Ok(())
42}
43
44/// Cpi signer check, validates that the provided invoking program
45/// is the actual invoking program.
46#[heap_neutral]
47pub fn cpi_signer_check(invoking_program: &Pubkey, authority: &Pubkey) -> Result<()> {
48    let seeds = [CPI_AUTHORITY_PDA_SEED];
49    let derived_signer = Pubkey::try_find_program_address(&seeds, invoking_program)
50        .ok_or(ProgramError::InvalidSeeds)?
51        .0;
52    if derived_signer != *authority {
53        msg!(
54            "Cpi signer check failed. Derived cpi signer {} !=  authority {}",
55            derived_signer,
56            authority
57        );
58        return err!(SystemProgramError::CpiSignerCheckFailed);
59    }
60    Ok(())
61}
62
63/// Checks that the invoking program owns all input compressed accounts.
64pub fn input_compressed_accounts_signer_check(
65    input_compressed_accounts_with_merkle_context: &[PackedCompressedAccountWithMerkleContext],
66    invoking_program_id: &Pubkey,
67) -> Result<()> {
68    input_compressed_accounts_with_merkle_context
69        .iter()
70        .try_for_each(
71            |compressed_account_with_context: &PackedCompressedAccountWithMerkleContext| {
72                let invoking_program_id = invoking_program_id.key();
73                if invoking_program_id == compressed_account_with_context.compressed_account.owner {
74                    Ok(())
75                } else {
76                    msg!(
77                        "Input signer check failed. Program cannot invalidate an account it doesn't own. Owner {} !=  invoking_program_id {}",
78                        compressed_account_with_context.compressed_account.owner,
79                        invoking_program_id
80                    );
81                    err!(SystemProgramError::SignerCheckFailed)
82                }
83            },
84        )
85}
86
87/// Write access check for output compressed accounts.
88/// - Only program-owned output accounts can hold data.
89/// - Every output account that holds data has to be owned by the
90///     invoking_program.
91/// - outputs without data can be owned by any pubkey.
92#[inline(never)]
93pub fn output_compressed_accounts_write_access_check(
94    output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
95    invoking_program_id: &Pubkey,
96) -> Result<()> {
97    for compressed_account in output_compressed_accounts.iter() {
98        if compressed_account.compressed_account.data.is_some()
99            && compressed_account.compressed_account.owner != invoking_program_id.key()
100        {
101            msg!(
102                    "Signer/Program cannot write into an account it doesn't own. Write access check failed compressed account owner {} !=  invoking_program_id {}",
103                    compressed_account.compressed_account.owner,
104                    invoking_program_id.key()
105                );
106            msg!("compressed_account: {:?}", compressed_account);
107            return err!(SystemProgramError::WriteAccessCheckFailed);
108        }
109        if compressed_account.compressed_account.data.is_none()
110            && compressed_account.compressed_account.owner == invoking_program_id.key()
111        {
112            msg!("For program owned compressed accounts the data field needs to be defined.");
113            msg!("compressed_account: {:?}", compressed_account);
114            return err!(SystemProgramError::DataFieldUndefined);
115        }
116    }
117    Ok(())
118}
119
120pub fn check_program_owner_state_merkle_tree<'a, 'b: 'a>(
121    merkle_tree_acc_info: &'b AccountInfo<'a>,
122    invoking_program: &Option<Pubkey>,
123) -> Result<(u32, Option<u64>, u64)> {
124    let (seq, next_index) = {
125        let merkle_tree = merkle_tree_acc_info.try_borrow_data()?;
126        check_discriminator::<StateMerkleTreeAccount>(&merkle_tree).map_err(ProgramError::from)?;
127        let merkle_tree = ConcurrentMerkleTreeZeroCopy::<Poseidon, 26>::from_bytes_zero_copy(
128            &merkle_tree[8 + mem::size_of::<StateMerkleTreeAccount>()..],
129        )
130        .map_err(ProgramError::from)?;
131
132        let seq = merkle_tree.sequence_number() as u64 + 1;
133        let next_index: u32 = merkle_tree.next_index().try_into().unwrap();
134
135        (seq, next_index)
136    };
137
138    let merkle_tree =
139        AccountLoader::<StateMerkleTreeAccount>::try_from(merkle_tree_acc_info).unwrap();
140    let merkle_tree_unpacked = merkle_tree.load()?;
141
142    let network_fee = if merkle_tree_unpacked.metadata.rollover_metadata.network_fee != 0 {
143        Some(merkle_tree_unpacked.metadata.rollover_metadata.network_fee)
144    } else {
145        None
146    };
147    if merkle_tree_unpacked.metadata.access_metadata.program_owner != Pubkey::default() {
148        if let Some(invoking_program) = invoking_program {
149            if *invoking_program == merkle_tree_unpacked.metadata.access_metadata.program_owner {
150                return Ok((next_index, network_fee, seq));
151            }
152        }
153        msg!(
154            "invoking_program.key() {:?} == merkle_tree_unpacked.program_owner {:?}",
155            invoking_program,
156            merkle_tree_unpacked.metadata.access_metadata.program_owner
157        );
158        return Err(SystemProgramError::InvalidMerkleTreeOwner.into());
159    }
160    Ok((next_index, network_fee, seq))
161}
162
163pub fn check_program_owner_address_merkle_tree<'a, 'b: 'a>(
164    merkle_tree_acc_info: &'b AccountInfo<'a>,
165    invoking_program: &Option<Pubkey>,
166) -> Result<Option<u64>> {
167    let merkle_tree =
168        AccountLoader::<AddressMerkleTreeAccount>::try_from(merkle_tree_acc_info).unwrap();
169    let merkle_tree_unpacked = merkle_tree.load()?;
170    let network_fee = if merkle_tree_unpacked.metadata.rollover_metadata.network_fee != 0 {
171        Some(merkle_tree_unpacked.metadata.rollover_metadata.network_fee)
172    } else {
173        None
174    };
175    if merkle_tree_unpacked.metadata.access_metadata.program_owner != Pubkey::default() {
176        if let Some(invoking_program) = invoking_program {
177            if *invoking_program == merkle_tree_unpacked.metadata.access_metadata.program_owner {
178                msg!(
179                    "invoking_program.key() {:?} == merkle_tree_unpacked.program_owner {:?}",
180                    invoking_program,
181                    merkle_tree_unpacked.metadata.access_metadata.program_owner
182                );
183                return Ok(network_fee);
184            }
185        }
186        msg!(
187            "invoking_program.key() {:?} == merkle_tree_unpacked.program_owner {:?}",
188            invoking_program,
189            merkle_tree_unpacked.metadata.access_metadata.program_owner
190        );
191        err!(SystemProgramError::InvalidMerkleTreeOwner)
192    } else {
193        Ok(network_fee)
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use super::*;
200    use crate::sdk::compressed_account::{CompressedAccount, CompressedAccountData};
201
202    #[test]
203    fn test_cpi_signer_check() {
204        for _ in 0..1000 {
205            let seeds = [CPI_AUTHORITY_PDA_SEED];
206            let invoking_program = Pubkey::new_unique();
207            let (derived_signer, _) = Pubkey::find_program_address(&seeds[..], &invoking_program);
208            assert_eq!(cpi_signer_check(&invoking_program, &derived_signer), Ok(()));
209
210            let authority = Pubkey::new_unique();
211            let invoking_program = Pubkey::new_unique();
212            assert!(
213                cpi_signer_check(&invoking_program, &authority)
214                    == Err(ProgramError::InvalidSeeds.into())
215                    || cpi_signer_check(&invoking_program, &authority)
216                        == Err(SystemProgramError::CpiSignerCheckFailed.into())
217            );
218        }
219    }
220
221    #[test]
222    fn test_input_compressed_accounts_signer_check() {
223        let authority = Pubkey::new_unique();
224        let mut compressed_account_with_context = PackedCompressedAccountWithMerkleContext {
225            compressed_account: CompressedAccount {
226                owner: authority,
227                ..CompressedAccount::default()
228            },
229            ..PackedCompressedAccountWithMerkleContext::default()
230        };
231
232        assert_eq!(
233            input_compressed_accounts_signer_check(
234                &[compressed_account_with_context.clone()],
235                &authority
236            ),
237            Ok(())
238        );
239
240        compressed_account_with_context.compressed_account.owner = Pubkey::new_unique();
241        assert_eq!(
242            input_compressed_accounts_signer_check(&[compressed_account_with_context], &authority),
243            Err(SystemProgramError::SignerCheckFailed.into())
244        );
245    }
246
247    #[test]
248    fn test_output_compressed_accounts_write_access_check() {
249        let authority = Pubkey::new_unique();
250        let compressed_account = CompressedAccount {
251            owner: authority,
252            data: Some(CompressedAccountData::default()),
253            ..CompressedAccount::default()
254        };
255        let output_compressed_account = OutputCompressedAccountWithPackedContext {
256            compressed_account,
257            ..OutputCompressedAccountWithPackedContext::default()
258        };
259
260        assert_eq!(
261            output_compressed_accounts_write_access_check(&[output_compressed_account], &authority),
262            Ok(())
263        );
264
265        // Invalid program owner but no data should succeed
266        let compressed_account = CompressedAccount {
267            owner: Pubkey::new_unique(),
268            ..CompressedAccount::default()
269        };
270        let mut output_compressed_account = OutputCompressedAccountWithPackedContext {
271            compressed_account,
272            ..OutputCompressedAccountWithPackedContext::default()
273        };
274
275        assert_eq!(
276            output_compressed_accounts_write_access_check(
277                &[output_compressed_account.clone()],
278                &authority
279            ),
280            Ok(())
281        );
282
283        // Invalid program owner and data should fail
284        output_compressed_account.compressed_account.data = Some(CompressedAccountData::default());
285
286        assert_eq!(
287            output_compressed_accounts_write_access_check(&[output_compressed_account], &authority),
288            Err(SystemProgramError::WriteAccessCheckFailed.into())
289        );
290    }
291}