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