1use anchor_lang::{
2 prelude::*,
3 solana_program::{clock::Slot, hash::HASH_BYTES, pubkey::PUBKEY_BYTES},
4};
5
6use crate::{
7 error::ErrorCode, GROTH16_PROOF_SIZE, PROOF_PUBLIC_VALUES_MAX_SIZE, PROOF_VERIFICATION_KEY_SIZE,
8};
9
10#[account]
11#[derive(Debug, InitSpace, PartialEq, Eq, PartialOrd, Ord)]
12pub struct Checkpoint {
13 pub slot: u64,
14 pub proof: [u8; GROTH16_PROOF_SIZE],
15 #[max_len(PROOF_VERIFICATION_KEY_SIZE)]
16 pub verification_key: String,
17 #[max_len(PROOF_PUBLIC_VALUES_MAX_SIZE)]
18 pub public_values: Vec<u8>,
19}
20
21impl Checkpoint {
22 pub fn new(
23 proof: [u8; GROTH16_PROOF_SIZE],
24 public_values: Vec<u8>,
25 verification_key: String,
26 slot: Slot,
27 ) -> Result<Self> {
28 if public_values.len() > PROOF_PUBLIC_VALUES_MAX_SIZE {
29 return Err(error!(ErrorCode::PublicValuesExceedMaxSize));
30 }
31 if verification_key.len() != PROOF_VERIFICATION_KEY_SIZE {
32 return Err(error!(ErrorCode::InvalidVerificationKeySize));
33 }
34
35 Ok(Self {
36 proof,
37 public_values,
38 verification_key,
39 slot,
40 })
41 }
42
43 pub fn is_initial(&self) -> bool {
44 self.slot == 0
45 && self.public_values.is_empty()
46 && self.verification_key.is_empty()
47 && self.proof == [0u8; GROTH16_PROOF_SIZE]
48 }
49
50 pub fn store(&mut self, new: Self) -> Result<()> {
51 if new.public_values.len() > PROOF_PUBLIC_VALUES_MAX_SIZE {
52 return Err(error!(ErrorCode::PublicValuesExceedMaxSize));
53 }
54 if new.verification_key.len() != PROOF_VERIFICATION_KEY_SIZE {
55 return Err(error!(ErrorCode::InvalidVerificationKeySize));
56 }
57
58 self.proof = new.proof;
59 self.public_values = new.public_values;
60 self.verification_key = new.verification_key;
61 self.slot = new.slot;
62 Ok(())
63 }
64
65 #[cfg(feature = "sp1")]
66 pub fn verify_zk_proof(&self) -> Result<()> {
67 sp1_solana::verify_proof(
68 &self.proof,
69 &self.public_values,
70 &self.verification_key,
71 sp1_solana::GROTH16_VK_5_0_0_BYTES,
72 )
73 .map_err(|_| error!(ErrorCode::ProofVerificationFailed))
74 }
75
76 pub fn blober(&self) -> Result<Pubkey> {
77 bincode::deserialize::<Pubkey>(
78 self.public_values
79 .get(..PUBKEY_BYTES)
80 .ok_or_else(|| error!(ErrorCode::InvalidPublicValue))?,
81 )
82 .map_err(|_| error!(ErrorCode::InvalidPublicValue))
83 }
84
85 pub fn initial_hash(&self) -> Result<[u8; HASH_BYTES]> {
86 bincode::deserialize::<[u8; HASH_BYTES]>(
87 self.public_values
88 .get(PUBKEY_BYTES..PUBKEY_BYTES + HASH_BYTES)
89 .ok_or_else(|| error!(ErrorCode::InvalidPublicValue))?,
90 )
91 .map_err(|_| error!(ErrorCode::InvalidPublicValue))
92 }
93
94 pub fn final_hash(&self) -> Result<[u8; HASH_BYTES]> {
95 bincode::deserialize::<[u8; HASH_BYTES]>(
96 self.public_values
97 .get(PUBKEY_BYTES + HASH_BYTES..)
98 .ok_or_else(|| error!(ErrorCode::InvalidPublicValue))?,
99 )
100 .map_err(|_| error!(ErrorCode::InvalidPublicValue))
101 }
102
103 pub fn non_base_commitments(&self) -> Option<&[u8]> {
104 self.public_values.get(PUBKEY_BYTES + HASH_BYTES * 2..)
105 }
106
107 #[cfg(feature = "cpi")]
108 pub fn cpi_create_checkpoint<'info>(
109 &self,
110 blober: Pubkey,
111 data_anchor: AccountInfo<'info>,
112 account_infos: crate::cpi::accounts::CreateCheckpoint<'info>,
113 pda_signer_bump: &[u8],
114 ) -> Result<()> {
115 use crate::{CHECKPOINT_PDA_SIGNER_SEED, CHECKPOINT_SEED, SEED};
116
117 let seeds = &[&[
118 SEED,
119 CHECKPOINT_SEED,
120 CHECKPOINT_PDA_SIGNER_SEED,
121 blober.as_ref(),
122 pda_signer_bump,
123 ][..]];
124
125 let cpi_context = CpiContext::new(data_anchor, account_infos).with_signer(seeds);
126
127 crate::cpi::create_checkpoint(
128 cpi_context,
129 blober,
130 self.proof,
131 self.public_values.clone(),
132 self.verification_key.clone(),
133 self.slot,
134 )
135 }
136}
137
138#[account]
139#[derive(Debug, InitSpace, PartialEq, Eq, PartialOrd, Ord)]
140pub struct CheckpointConfig {
141 pub blober: Pubkey,
142 pub authority: Pubkey,
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_checkpoint_serde() {
151 let checkpoint = Checkpoint {
152 slot: 123456,
153 proof: [1; GROTH16_PROOF_SIZE],
154 verification_key: [
155 ["0x"].as_slice(),
156 ["0"; PROOF_VERIFICATION_KEY_SIZE - 2].as_slice(),
157 ]
158 .concat()
159 .join(""),
160 public_values: vec![2; PROOF_PUBLIC_VALUES_MAX_SIZE - 32],
161 };
162
163 let serialized = checkpoint.try_to_vec().unwrap();
164 let deserialized = Checkpoint::try_from_slice(&serialized).unwrap();
165 assert_eq!(checkpoint, deserialized);
166 let slot_bytes = [58, 0, 0, 0, 0, 0, 0, 0];
167 let groth16_bytes = [
168 164, 89, 76, 89, 44, 223, 17, 194, 251, 101, 97, 41, 176, 230, 219, 11, 162, 36, 92,
169 57, 173, 127, 79, 160, 29, 151, 174, 198, 52, 107, 226, 56, 172, 240, 32, 218, 15, 158,
170 106, 75, 63, 123, 94, 65, 249, 31, 115, 83, 252, 159, 13, 220, 48, 93, 244, 134, 12,
171 87, 61, 215, 180, 13, 103, 247, 235, 136, 132, 99, 24, 214, 43, 235, 25, 16, 59, 220,
172 201, 75, 88, 109, 240, 33, 14, 71, 60, 153, 181, 225, 16, 197, 255, 58, 185, 142, 168,
173 235, 138, 162, 253, 62, 11, 218, 213, 145, 139, 92, 213, 124, 214, 7, 218, 184, 146,
174 236, 207, 77, 134, 83, 203, 224, 141, 86, 123, 153, 32, 38, 88, 151, 41, 114, 244, 126,
175 46, 75, 209, 182, 185, 186, 89, 203, 201, 147, 126, 200, 232, 224, 187, 81, 229, 26,
176 211, 192, 143, 255, 37, 155, 243, 94, 93, 202, 187, 237, 216, 39, 3, 82, 175, 113, 181,
177 129, 184, 71, 170, 200, 41, 157, 94, 233, 138, 61, 241, 169, 253, 202, 224, 91, 145,
178 99, 5, 187, 189, 140, 205, 41, 112, 6, 12, 14, 86, 45, 63, 35, 84, 28, 99, 230, 188,
179 235, 149, 19, 16, 91, 241, 74, 136, 170, 215, 222, 108, 129, 108, 64, 83, 154, 71, 200,
180 145, 66, 20, 63, 124, 47, 7, 227, 127, 174, 250, 247, 124, 167, 144, 233, 140, 122,
181 233, 253, 244, 30, 139, 185, 240, 133, 144, 197, 144, 88, 74, 237, 166, 119,
182 ];
183 let verification_key_bytes = [
184 66, 0, 0, 0, 48, 120, 48, 48, 54, 102, 54, 101, 54, 98, 52, 101, 57, 54, 50, 52, 53, 102, 57, 56,
186 101, 48, 52, 55, 98, 49, 55, 99, 99, 50, 98, 100, 52, 98, 101, 55, 56, 102, 98, 48, 50,
187 101, 48, 56, 55, 54, 57, 51, 56, 49, 51, 53, 97, 97, 99, 100, 50, 100, 48, 51, 99, 97,
188 51, 99, 102, 49,
189 ];
190 let public_value_bytes = [
191 96, 0, 0, 0, 3, 145, 232, 95, 237, 197, 86, 36, 133, 7, 130, 192, 44, 20, 165, 56, 142, 241, 131,
193 217, 169, 251, 153, 244, 24, 200, 141, 237, 87, 185, 20, 36, 227, 176, 196, 66, 152,
194 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, 174, 65, 228, 100, 155, 147,
195 76, 164, 149, 153, 27, 120, 82, 184, 85, 235, 11, 55, 168, 251, 31, 209, 12, 182, 200,
196 154, 42, 5, 29, 196, 222, 85, 16, 54, 24, 5, 250, 103, 79, 41, 124, 74, 196, 185, 94,
197 22, 176, 0, 0, 0, 0, 0, 0, 0, 0,
198 ];
199 let example = [
200 &slot_bytes[..],
201 &groth16_bytes[..],
202 &verification_key_bytes[..],
203 &public_value_bytes[..],
204 ]
205 .concat();
206 let example = &mut example.as_slice();
207 let slot = example
208 .get(..8)
209 .and_then(|s| s.try_into().ok())
210 .map(u64::from_le_bytes)
211 .unwrap();
212
213 let groth16 = example.get(8..8 + GROTH16_PROOF_SIZE).unwrap();
214
215 let vk_key_size = example
216 .get(8 + GROTH16_PROOF_SIZE..8 + GROTH16_PROOF_SIZE + 4)
217 .and_then(|s| s.try_into().ok())
218 .map(u32::from_le_bytes)
219 .unwrap() as usize;
220
221 let vk_key_hex = example
222 .get(8 + GROTH16_PROOF_SIZE + 4..8 + GROTH16_PROOF_SIZE + 4 + vk_key_size)
223 .unwrap();
224
225 let public_values_size = example
226 .get(
227 8 + GROTH16_PROOF_SIZE + 4 + vk_key_size
228 ..8 + GROTH16_PROOF_SIZE + 4 + vk_key_size + 4,
229 )
230 .and_then(|s| s.try_into().ok())
231 .map(u32::from_le_bytes)
232 .unwrap() as usize;
233
234 let public_values = example
235 .get(
236 8 + GROTH16_PROOF_SIZE + 4 + vk_key_size + 4
237 ..8 + GROTH16_PROOF_SIZE + 4 + vk_key_size + 4 + public_values_size,
238 )
239 .unwrap()
240 .to_vec();
241
242 let _checkpoint = Checkpoint {
243 proof: groth16.try_into().unwrap(),
244 verification_key: String::from_utf8(vk_key_hex.to_vec()).unwrap(),
245 public_values,
246 slot,
247 };
248 let _deserialized = Checkpoint::deserialize(example).unwrap();
249 }
250}