albus_solana_verifier/
lib.rs

1/*
2 * This file is part of Albus code.
3 *
4 * Copyright (c) 2023, mFactory GmbH
5 *
6 * Albus is free software: you can redistribute it
7 * and/or modify it under the terms of the GNU Affero General Public License
8 * as published by the Free Software Foundation, either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * Albus is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
13 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program.
18 * If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.
19 *
20 * You can be released from the requirements of the Affero GNU General Public License
21 * by purchasing a commercial license. The purchase of such a license is
22 * mandatory as soon as you develop commercial activities using the
23 * Albus code without disclosing the source code of
24 * your own applications.
25 *
26 * The developer of this program can be contacted at <info@albus.finance>.
27 */
28
29mod constants;
30#[cfg(feature = "cpi")]
31mod cpi;
32mod utils;
33
34use crate::constants::{ALBUS_DEV_PROGRAM_ID, ALBUS_PROGRAM_ID, PROOF_REQUEST_DISCRIMINATOR};
35use crate::utils::cmp_pubkeys;
36use arrayref::array_ref;
37use arrayref::array_refs;
38use solana_program::{
39    account_info::AccountInfo, clock::Clock, msg, program_error::ProgramError, pubkey::Pubkey,
40    sysvar::Sysvar,
41};
42
43pub struct AlbusVerifier<'a, 'info> {
44    /// Proof request address
45    proof_request: &'a AccountInfo<'info>,
46    /// (optional) Proof request owner's address
47    proof_request_owner: Option<Pubkey>,
48    /// (optional) Policy address
49    policy: Option<Pubkey>,
50}
51
52impl<'a, 'info> AlbusVerifier<'a, 'info> {
53    pub fn new(proof_request: &'a AccountInfo<'info>) -> AlbusVerifier<'a, 'info> {
54        Self {
55            proof_request,
56            policy: None,
57            proof_request_owner: None,
58        }
59    }
60
61    pub fn check_policy(mut self, addr: Pubkey) -> Self {
62        self.policy = Some(addr);
63        self
64    }
65
66    pub fn check_owner(mut self, addr: Pubkey) -> Self {
67        self.proof_request_owner = Some(addr);
68        self
69    }
70
71    #[cfg(feature = "cpi")]
72    fn cpi_call(&self, _ctx: cpi::VerifyProofRequest) -> Result<(), ProgramError> {
73        unimplemented!()
74    }
75
76    pub fn run(&self) -> Result<(), ProgramError> {
77        self.check_program_account(self.proof_request)?;
78        self.check_proof_request()?;
79        Ok(())
80    }
81
82    /// Checks if the provided account is a valid program account
83    fn check_program_account(&self, acc: &AccountInfo) -> Result<(), ProgramError> {
84        if !cmp_pubkeys(acc.owner, ALBUS_PROGRAM_ID)
85            && !cmp_pubkeys(acc.owner, ALBUS_DEV_PROGRAM_ID)
86        {
87            return Err(ProgramError::IllegalOwner);
88        }
89        if acc.data_is_empty() {
90            msg!("Error: Program account {} is empty", acc.key);
91            return Err(ProgramError::UninitializedAccount);
92        }
93        Ok(())
94    }
95
96    fn check_proof_request(&self) -> Result<(), ProgramError> {
97        let data = self.proof_request.data.borrow();
98        let data = array_ref![data, 0, 218];
99
100        let (
101            discriminator,
102            _service,
103            policy,
104            _circuit,
105            _issuer,
106            owner,
107            _identifier,
108            _created_at,
109            expired_at,
110            _verified_at,
111            _proved_at,
112            _retention_end_date,
113            [status],
114            _bump,
115            // _proof,
116            // _public_inputs,
117        ) = array_refs![data, 8, 32, 32, 32, 32, 32, 8, 8, 8, 8, 8, 8, 1, 1];
118
119        if discriminator != PROOF_REQUEST_DISCRIMINATOR {
120            msg!("AlbusVerifierError: Invalid proof request discriminator");
121            return Err(ProgramError::InvalidAccountData);
122        }
123
124        if let Some(key) = self.policy {
125            if !cmp_pubkeys(key, policy) {
126                msg!("AlbusVerifierError: Invalid proof request policy");
127                return Err(ProgramError::InvalidAccountData);
128            }
129        }
130
131        if let Some(key) = self.proof_request_owner {
132            if !cmp_pubkeys(key, owner) {
133                msg!("AlbusVerifierError: Invalid proof request owner");
134                return Err(ProgramError::InvalidAccountData);
135            }
136        }
137
138        let expired_at = i64::from_le_bytes(*expired_at);
139        let status = ProofRequestStatus::try_from(*status)?;
140        let timestamp = Clock::get()?.unix_timestamp;
141
142        if expired_at > 0 && expired_at < timestamp {
143            msg!("AlbusVerifierError: Expired!");
144            return Err(ProgramError::Custom(VerificationError::Expired as u32));
145        }
146
147        match status {
148            ProofRequestStatus::Pending => {
149                msg!("AlbusVerifierError: Proof request is pending");
150                Err(ProgramError::Custom(VerificationError::Pending as u32))
151            }
152            ProofRequestStatus::Proved => {
153                msg!("AlbusVerifierError: Proof request is not verified");
154                Err(ProgramError::Custom(VerificationError::NotVerified as u32))
155            }
156            ProofRequestStatus::Rejected => {
157                msg!("AlbusVerifierError: Proof request is rejected");
158                Err(ProgramError::Custom(VerificationError::Rejected as u32))
159            }
160            ProofRequestStatus::Verified => Ok(()),
161        }
162    }
163}
164
165#[repr(u8)]
166pub enum VerificationError {
167    NotVerified,
168    Expired,
169    Pending,
170    Rejected,
171}
172
173#[repr(u8)]
174#[derive(Default, Debug, Eq, PartialEq, Clone)]
175pub enum ProofRequestStatus {
176    #[default]
177    Pending,
178    Proved,
179    Verified,
180    Rejected,
181}
182
183impl TryFrom<u8> for ProofRequestStatus {
184    type Error = ProgramError;
185
186    fn try_from(value: u8) -> Result<Self, Self::Error> {
187        match value {
188            0 => Ok(Self::Pending),
189            1 => Ok(Self::Proved),
190            2 => Ok(Self::Verified),
191            3 => Ok(Self::Rejected),
192            _ => Err(ProgramError::InvalidAccountData),
193        }
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use solana_program::{account_info::AccountInfo, clock::Clock, pubkey::Pubkey};
200
201    use super::*;
202
203    #[test]
204    fn test_verified() {
205        solana_program::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
206
207        let policy = Pubkey::new_unique();
208
209        assert_eq!(
210            AlbusVerifier::new(
211                &ProofRequestBuilder::new()
212                    .with_status(ProofRequestStatus::Verified)
213                    .with_policy(policy)
214                    .build()
215            )
216            .check_policy(policy)
217            .run(),
218            Ok(())
219        );
220    }
221
222    #[test]
223    fn test_expired() {
224        solana_program::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
225
226        let policy = Pubkey::new_unique();
227
228        assert_eq!(
229            AlbusVerifier::new(
230                &ProofRequestBuilder::new()
231                    .with_status(ProofRequestStatus::Proved)
232                    .with_policy(policy)
233                    .with_expired_at(1)
234                    .build()
235            )
236            .check_policy(policy)
237            .run(),
238            Err(ProgramError::Custom(VerificationError::Expired as u32))
239        );
240    }
241
242    #[test]
243    fn test_ownership() {
244        solana_program::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
245
246        let owner = Pubkey::new_unique();
247        let policy = Pubkey::new_unique();
248
249        assert_eq!(
250            AlbusVerifier::new(
251                &ProofRequestBuilder::new()
252                    .with_status(ProofRequestStatus::Verified)
253                    .with_policy(policy)
254                    .with_owner(owner)
255                    .build()
256            )
257            .check_policy(policy)
258            .check_owner(owner)
259            .run(),
260            Ok(())
261        );
262
263        // invalid owner
264        assert_eq!(
265            AlbusVerifier::new(
266                &ProofRequestBuilder::new()
267                    .with_status(ProofRequestStatus::Verified)
268                    .with_policy(policy)
269                    .with_owner(owner)
270                    .build()
271            )
272            .check_policy(policy)
273            .check_owner(Pubkey::new_unique())
274            .run(),
275            Err(ProgramError::InvalidAccountData)
276        );
277    }
278
279    struct ProofRequestBuilder {
280        expired_at: i64,
281        owner: Pubkey,
282        issuer: Pubkey,
283        policy: Pubkey,
284        status: u8,
285        // --
286        _pk: Pubkey,
287        _owner: Pubkey,
288        _lamports: u64,
289        _data: Vec<u8>,
290    }
291
292    impl ProofRequestBuilder {
293        pub fn new() -> Self {
294            Self {
295                expired_at: 0,
296                issuer: Default::default(),
297                owner: Default::default(),
298                policy: Default::default(),
299                status: 0,
300                _pk: Default::default(),
301                _owner: ALBUS_PROGRAM_ID,
302                _lamports: 0,
303                _data: vec![],
304            }
305        }
306
307        pub fn with_status(&mut self, status: ProofRequestStatus) -> &mut Self {
308            self.status = status as u8;
309            self
310        }
311
312        pub fn with_issuer(&mut self, addr: Pubkey) -> &mut Self {
313            self.issuer = addr;
314            self
315        }
316
317        pub fn with_owner(&mut self, addr: Pubkey) -> &mut Self {
318            self.owner = addr;
319            self
320        }
321
322        pub fn with_policy(&mut self, addr: Pubkey) -> &mut Self {
323            self.policy = addr;
324            self
325        }
326
327        pub fn with_expired_at(&mut self, timestamp: i64) -> &mut Self {
328            self.expired_at = timestamp;
329            self
330        }
331
332        fn build(&mut self) -> AccountInfo {
333            self._data = [
334                PROOF_REQUEST_DISCRIMINATOR,
335                &Pubkey::new_unique().to_bytes(), //service
336                &self.policy.to_bytes(),
337                &Pubkey::new_unique().to_bytes(), // circuit
338                &self.issuer.to_bytes(),
339                &self.owner.to_bytes(),
340                &0u64.to_le_bytes(),
341                &0i64.to_le_bytes(),
342                &self.expired_at.to_le_bytes(),
343                &0i64.to_le_bytes(),
344                &0i64.to_le_bytes(),
345                &0i64.to_le_bytes(),
346                &[self.status],
347                &0u8.to_le_bytes(), // bump
348                &[0u8; 256],        // proof
349                &[0u8; 28],         // public_inputs
350            ]
351            .into_iter()
352            .flatten()
353            .copied()
354            .collect::<Vec<_>>();
355
356            AccountInfo::new(
357                &self._pk,
358                false,
359                true,
360                &mut self._lamports,
361                &mut self._data,
362                &self._owner,
363                false,
364                0,
365            )
366        }
367    }
368
369    struct SyscallStubs {}
370    impl solana_program::program_stubs::SyscallStubs for SyscallStubs {
371        fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 {
372            unsafe {
373                *(var_addr as *mut _ as *mut Clock) = Clock {
374                    slot: 1,
375                    epoch_start_timestamp: 2,
376                    epoch: 3,
377                    leader_schedule_epoch: 4,
378                    unix_timestamp: 5,
379                };
380            }
381            solana_program::entrypoint::SUCCESS
382        }
383    }
384}