1mod 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: &'a AccountInfo<'info>,
46 proof_request_owner: Option<Pubkey>,
48 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 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 ) = 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 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 _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(), &self.policy.to_bytes(),
337 &Pubkey::new_unique().to_bytes(), &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(), &[0u8; 256], &[0u8; 28], ]
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}