solana_zk_token_proof_program/
lib.rs

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