solana_zk_token_proof_program/
lib.rs1#![forbid(unsafe_code)]
2
3use {
4 bytemuck::Pod,
5 solana_feature_set as feature_set,
6 solana_instruction::{error::InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT},
7 solana_log_collector::ic_msg,
8 solana_program_runtime::{declare_process_instruction, invoke_context::InvokeContext},
9 solana_sdk_ids::system_program,
10 solana_zk_token_sdk::{
11 zk_token_proof_instruction::*,
12 zk_token_proof_program::id,
13 zk_token_proof_state::{ProofContextState, ProofContextStateMeta},
14 },
15 std::result::Result,
16};
17
18pub const CLOSE_CONTEXT_STATE_COMPUTE_UNITS: u64 = 3_300;
19pub const VERIFY_ZERO_BALANCE_COMPUTE_UNITS: u64 = 6_000;
20pub const VERIFY_WITHDRAW_COMPUTE_UNITS: u64 = 110_000;
21pub const VERIFY_CIPHERTEXT_CIPHERTEXT_EQUALITY_COMPUTE_UNITS: u64 = 8_000;
22pub const VERIFY_TRANSFER_COMPUTE_UNITS: u64 = 219_000;
23pub const VERIFY_TRANSFER_WITH_FEE_COMPUTE_UNITS: u64 = 407_000;
24pub const VERIFY_PUBKEY_VALIDITY_COMPUTE_UNITS: u64 = 2_600;
25pub const VERIFY_RANGE_PROOF_U64_COMPUTE_UNITS: u64 = 105_000;
26pub const VERIFY_BATCHED_RANGE_PROOF_U64_COMPUTE_UNITS: u64 = 111_000;
27pub const VERIFY_BATCHED_RANGE_PROOF_U128_COMPUTE_UNITS: u64 = 200_000;
28pub const VERIFY_BATCHED_RANGE_PROOF_U256_COMPUTE_UNITS: u64 = 368_000;
29pub const VERIFY_CIPHERTEXT_COMMITMENT_EQUALITY_COMPUTE_UNITS: u64 = 6_400;
30pub const VERIFY_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 6_400;
31pub const VERIFY_BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 13_000;
32pub const VERIFY_FEE_SIGMA_COMPUTE_UNITS: u64 = 6_500;
33pub const VERIFY_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 8_100;
34pub const VERIFY_BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 16_400;
35
36const INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT: usize = 5;
37
38fn process_verify_proof<T, U>(invoke_context: &mut InvokeContext) -> Result<(), InstructionError>
39where
40 T: Pod + ZkProofData<U>,
41 U: Pod,
42{
43 let transaction_context = &invoke_context.transaction_context;
44 let instruction_context = transaction_context.get_current_instruction_context()?;
45 let instruction_data = instruction_context.get_instruction_data();
46
47 let mut accessed_accounts = 0_u16;
49
50 let context_data = if instruction_data.len() == INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT {
52 if !invoke_context
53 .get_feature_set()
54 .is_active(&feature_set::enable_zk_proof_from_account::id())
55 {
56 return Err(InstructionError::InvalidInstructionData);
57 }
58
59 let proof_data_account = instruction_context
60 .try_borrow_instruction_account(transaction_context, accessed_accounts)?;
61 accessed_accounts = accessed_accounts.checked_add(1).unwrap();
62
63 let proof_data_offset = u32::from_le_bytes(
64 instruction_data[1..INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT]
66 .try_into()
67 .map_err(|_| InstructionError::InvalidInstructionData)?,
68 );
69 let proof_data_start: usize = proof_data_offset
70 .try_into()
71 .map_err(|_| InstructionError::InvalidInstructionData)?;
72 let proof_data_end = proof_data_start
73 .checked_add(std::mem::size_of::<T>())
74 .ok_or(InstructionError::InvalidInstructionData)?;
75 let proof_data_raw = proof_data_account
76 .get_data()
77 .get(proof_data_start..proof_data_end)
78 .ok_or(InstructionError::InvalidAccountData)?;
79
80 let proof_data = bytemuck::try_from_bytes::<T>(proof_data_raw).map_err(|_| {
81 ic_msg!(invoke_context, "invalid proof data");
82 InstructionError::InvalidInstructionData
83 })?;
84 proof_data.verify_proof().map_err(|err| {
85 ic_msg!(invoke_context, "proof verification failed: {:?}", err);
86 InstructionError::InvalidInstructionData
87 })?;
88
89 *proof_data.context_data()
90 } else {
91 let proof_data =
92 ProofInstruction::proof_data::<T, U>(instruction_data).ok_or_else(|| {
93 ic_msg!(invoke_context, "invalid proof data");
94 InstructionError::InvalidInstructionData
95 })?;
96 proof_data.verify_proof().map_err(|err| {
97 ic_msg!(invoke_context, "proof_verification failed: {:?}", err);
98 InstructionError::InvalidInstructionData
99 })?;
100
101 *proof_data.context_data()
102 };
103
104 if instruction_context.get_number_of_instruction_accounts() > accessed_accounts {
106 let context_state_authority = *instruction_context
107 .try_borrow_instruction_account(
108 transaction_context,
109 accessed_accounts.checked_add(1).unwrap(),
110 )?
111 .get_key();
112
113 let mut proof_context_account = instruction_context
114 .try_borrow_instruction_account(transaction_context, accessed_accounts)?;
115
116 if *proof_context_account.get_owner() != id() {
117 return Err(InstructionError::InvalidAccountOwner);
118 }
119
120 let proof_context_state_meta =
121 ProofContextStateMeta::try_from_bytes(proof_context_account.get_data())?;
122
123 if proof_context_state_meta.proof_type != ProofType::Uninitialized.into() {
124 return Err(InstructionError::AccountAlreadyInitialized);
125 }
126
127 let context_state_data =
128 ProofContextState::encode(&context_state_authority, T::PROOF_TYPE, &context_data);
129
130 if proof_context_account.get_data().len() != context_state_data.len() {
131 return Err(InstructionError::InvalidAccountData);
132 }
133
134 proof_context_account.set_data_from_slice(&context_state_data)?;
135 }
136
137 Ok(())
138}
139
140fn process_close_proof_context(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
141 let transaction_context = &invoke_context.transaction_context;
142 let instruction_context = transaction_context.get_current_instruction_context()?;
143
144 let owner_pubkey = {
145 let owner_account =
146 instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
147
148 if !owner_account.is_signer() {
149 return Err(InstructionError::MissingRequiredSignature);
150 }
151 *owner_account.get_key()
152 }; let proof_context_account_pubkey = *instruction_context
155 .try_borrow_instruction_account(transaction_context, 0)?
156 .get_key();
157 let destination_account_pubkey = *instruction_context
158 .try_borrow_instruction_account(transaction_context, 1)?
159 .get_key();
160 if proof_context_account_pubkey == destination_account_pubkey {
161 return Err(InstructionError::InvalidInstructionData);
162 }
163
164 let mut proof_context_account =
165 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
166 let proof_context_state_meta =
167 ProofContextStateMeta::try_from_bytes(proof_context_account.get_data())?;
168 let expected_owner_pubkey = proof_context_state_meta.context_state_authority;
169
170 if owner_pubkey != expected_owner_pubkey {
171 return Err(InstructionError::InvalidAccountOwner);
172 }
173
174 let mut destination_account =
175 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
176 destination_account.checked_add_lamports(proof_context_account.get_lamports())?;
177 proof_context_account.set_lamports(0)?;
178 proof_context_account.set_data_length(0)?;
179 proof_context_account.set_owner(system_program::id().as_ref())?;
180
181 Ok(())
182}
183
184declare_process_instruction!(Entrypoint, 0, |invoke_context| {
185 let enable_zk_transfer_with_fee = invoke_context
186 .get_feature_set()
187 .is_active(&feature_set::enable_zk_transfer_with_fee::id());
188
189 let transaction_context = &invoke_context.transaction_context;
190 let instruction_context = transaction_context.get_current_instruction_context()?;
191 let instruction_data = instruction_context.get_instruction_data();
192 let instruction = ProofInstruction::instruction_type(instruction_data)
193 .ok_or(InstructionError::InvalidInstructionData)?;
194
195 if invoke_context.get_stack_height() != TRANSACTION_LEVEL_STACK_HEIGHT
196 && instruction != ProofInstruction::CloseContextState
197 {
198 return Err(InstructionError::UnsupportedProgramId);
200 }
201
202 match instruction {
203 ProofInstruction::CloseContextState => {
204 invoke_context
205 .consume_checked(CLOSE_CONTEXT_STATE_COMPUTE_UNITS)
206 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
207 ic_msg!(invoke_context, "CloseContextState");
208 process_close_proof_context(invoke_context)
209 }
210 ProofInstruction::VerifyZeroBalance => {
211 invoke_context
212 .consume_checked(VERIFY_ZERO_BALANCE_COMPUTE_UNITS)
213 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
214 ic_msg!(invoke_context, "VerifyZeroBalance");
215 process_verify_proof::<ZeroBalanceProofData, ZeroBalanceProofContext>(invoke_context)
216 }
217 ProofInstruction::VerifyWithdraw => {
218 invoke_context
219 .consume_checked(VERIFY_WITHDRAW_COMPUTE_UNITS)
220 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
221 ic_msg!(invoke_context, "VerifyWithdraw");
222 process_verify_proof::<WithdrawData, WithdrawProofContext>(invoke_context)
223 }
224 ProofInstruction::VerifyCiphertextCiphertextEquality => {
225 invoke_context
226 .consume_checked(VERIFY_CIPHERTEXT_CIPHERTEXT_EQUALITY_COMPUTE_UNITS)
227 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
228 ic_msg!(invoke_context, "VerifyCiphertextCiphertextEquality");
229 process_verify_proof::<
230 CiphertextCiphertextEqualityProofData,
231 CiphertextCiphertextEqualityProofContext,
232 >(invoke_context)
233 }
234 ProofInstruction::VerifyTransfer => {
235 invoke_context
236 .consume_checked(VERIFY_TRANSFER_COMPUTE_UNITS)
237 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
238 ic_msg!(invoke_context, "VerifyTransfer");
239 process_verify_proof::<TransferData, TransferProofContext>(invoke_context)
240 }
241 ProofInstruction::VerifyTransferWithFee => {
242 if !enable_zk_transfer_with_fee {
244 return Err(InstructionError::InvalidInstructionData);
245 }
246
247 invoke_context
248 .consume_checked(VERIFY_TRANSFER_WITH_FEE_COMPUTE_UNITS)
249 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
250 ic_msg!(invoke_context, "VerifyTransferWithFee");
251 process_verify_proof::<TransferWithFeeData, TransferWithFeeProofContext>(invoke_context)
252 }
253 ProofInstruction::VerifyPubkeyValidity => {
254 invoke_context
255 .consume_checked(VERIFY_PUBKEY_VALIDITY_COMPUTE_UNITS)
256 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
257 ic_msg!(invoke_context, "VerifyPubkeyValidity");
258 process_verify_proof::<PubkeyValidityData, PubkeyValidityProofContext>(invoke_context)
259 }
260 ProofInstruction::VerifyRangeProofU64 => {
261 invoke_context
262 .consume_checked(VERIFY_RANGE_PROOF_U64_COMPUTE_UNITS)
263 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
264 ic_msg!(invoke_context, "VerifyRangeProof");
265 process_verify_proof::<RangeProofU64Data, RangeProofContext>(invoke_context)
266 }
267 ProofInstruction::VerifyBatchedRangeProofU64 => {
268 invoke_context
269 .consume_checked(VERIFY_BATCHED_RANGE_PROOF_U64_COMPUTE_UNITS)
270 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
271 ic_msg!(invoke_context, "VerifyBatchedRangeProof64");
272 process_verify_proof::<BatchedRangeProofU64Data, BatchedRangeProofContext>(
273 invoke_context,
274 )
275 }
276 ProofInstruction::VerifyBatchedRangeProofU128 => {
277 invoke_context
278 .consume_checked(VERIFY_BATCHED_RANGE_PROOF_U128_COMPUTE_UNITS)
279 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
280 ic_msg!(invoke_context, "VerifyBatchedRangeProof128");
281 process_verify_proof::<BatchedRangeProofU128Data, BatchedRangeProofContext>(
282 invoke_context,
283 )
284 }
285 ProofInstruction::VerifyBatchedRangeProofU256 => {
286 if !enable_zk_transfer_with_fee {
288 return Err(InstructionError::InvalidInstructionData);
289 }
290
291 invoke_context
292 .consume_checked(VERIFY_BATCHED_RANGE_PROOF_U256_COMPUTE_UNITS)
293 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
294 ic_msg!(invoke_context, "VerifyBatchedRangeProof256");
295 process_verify_proof::<BatchedRangeProofU256Data, BatchedRangeProofContext>(
296 invoke_context,
297 )
298 }
299 ProofInstruction::VerifyCiphertextCommitmentEquality => {
300 invoke_context
301 .consume_checked(VERIFY_CIPHERTEXT_COMMITMENT_EQUALITY_COMPUTE_UNITS)
302 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
303 ic_msg!(invoke_context, "VerifyCiphertextCommitmentEquality");
304 process_verify_proof::<
305 CiphertextCommitmentEqualityProofData,
306 CiphertextCommitmentEqualityProofContext,
307 >(invoke_context)
308 }
309 ProofInstruction::VerifyGroupedCiphertext2HandlesValidity => {
310 invoke_context
311 .consume_checked(VERIFY_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS)
312 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
313 ic_msg!(invoke_context, "VerifyGroupedCiphertext2HandlesValidity");
314 process_verify_proof::<
315 GroupedCiphertext2HandlesValidityProofData,
316 GroupedCiphertext2HandlesValidityProofContext,
317 >(invoke_context)
318 }
319 ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity => {
320 invoke_context
321 .consume_checked(VERIFY_BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS)
322 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
323 ic_msg!(
324 invoke_context,
325 "VerifyBatchedGroupedCiphertext2HandlesValidity"
326 );
327 process_verify_proof::<
328 BatchedGroupedCiphertext2HandlesValidityProofData,
329 BatchedGroupedCiphertext2HandlesValidityProofContext,
330 >(invoke_context)
331 }
332 ProofInstruction::VerifyFeeSigma => {
333 if !enable_zk_transfer_with_fee {
335 return Err(InstructionError::InvalidInstructionData);
336 }
337
338 invoke_context
339 .consume_checked(VERIFY_FEE_SIGMA_COMPUTE_UNITS)
340 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
341 ic_msg!(invoke_context, "VerifyFeeSigma");
342 process_verify_proof::<FeeSigmaProofData, FeeSigmaProofContext>(invoke_context)
343 }
344 ProofInstruction::VerifyGroupedCiphertext3HandlesValidity => {
345 invoke_context
346 .consume_checked(VERIFY_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_COMPUTE_UNITS)
347 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
348 ic_msg!(invoke_context, "VerifyGroupedCiphertext3HandlesValidity");
349 process_verify_proof::<
350 GroupedCiphertext3HandlesValidityProofData,
351 GroupedCiphertext3HandlesValidityProofContext,
352 >(invoke_context)
353 }
354 ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity => {
355 invoke_context
356 .consume_checked(VERIFY_BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_COMPUTE_UNITS)
357 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
358 ic_msg!(
359 invoke_context,
360 "VerifyBatchedGroupedCiphertext3HandlesValidity"
361 );
362 process_verify_proof::<
363 BatchedGroupedCiphertext3HandlesValidityProofData,
364 BatchedGroupedCiphertext3HandlesValidityProofContext,
365 >(invoke_context)
366 }
367 }
368});