solana_zk_token_sdk/zk_token_proof_instruction.rs
1//! Instructions provided by the [`ZK Token proof`] program.
2//!
3//! There are two types of instructions in the proof program: proof verification instructions
4//! and the `CloseContextState` instruction.
5//!
6//! Each proof verification instruction verifies a certain type of zero-knowledge proof. These
7//! instructions are processed by the program in two steps:
8//! 1. The program verifies the zero-knowledge proof.
9//! 2. The program optionally stores the context component of the zero-knowledge proof to a
10//! dedicated [`context-state`] account.
11//!
12//! In step 1, the zero-knowledge proof can be included directly as the instruction data or
13//! pre-written to an account. The program determines whether the proof is provided as instruction
14//! data or pre-written to an account by inspecting the length of the data. If the instruction data
15//! is exactly 5 bytes (instruction disciminator + unsigned 32-bit integer), then the program
16//! assumes that the first account provided with the instruction contains the zero-knowledge proof
17//! and verifies the account data at the offset specified in the instruction data. Otherwise, the
18//! program assumes that the zero-knowledge proof is provided as part of the instruction data.
19//!
20//! In step 2, the program determines whether to create a context-state account by inspecting the
21//! number of accounts provided with the instruction. If two additional accounts are provided with
22//! the instruction after verifying the zero-knowledge proof, then the program writes the context data to
23//! the specified context-state account.
24//!
25//! NOTE: A context-state account must be pre-allocated to the exact size of the context data that
26//! is expected for a proof type before it is included in a proof verification instruction.
27//!
28//! The `CloseContextState` instruction closes a context state account. A transaction containing
29//! this instruction must be signed by the context account's owner. This instruction can be used by
30//! the account owner to reclaim lamports for storage.
31//!
32//! [`ZK Token proof`]: https://docs.solanalabs.com/runtime/zk-token-proof
33//! [`context-state`]: https://docs.solanalabs.com/runtime/zk-token-proof#context-data
34
35pub use crate::instruction::*;
36use {
37 bytemuck::bytes_of,
38 num_derive::{FromPrimitive, ToPrimitive},
39 num_traits::{FromPrimitive, ToPrimitive},
40 solana_instruction::{AccountMeta, Instruction},
41 solana_pubkey::Pubkey,
42};
43
44#[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
45#[repr(u8)]
46pub enum ProofInstruction {
47 /// Close a zero-knowledge proof context state.
48 ///
49 /// Accounts expected by this instruction:
50 /// 0. `[writable]` The proof context account to close
51 /// 1. `[writable]` The destination account for lamports
52 /// 2. `[signer]` The context account's owner
53 ///
54 /// Data expected by this instruction:
55 /// None
56 ///
57 CloseContextState,
58
59 /// Verify a zero-balance proof.
60 ///
61 /// A zero-balance proof certifies that an ElGamal ciphertext encrypts the value zero.
62 ///
63 /// Accounts expected by this instruction:
64 ///
65 /// 0. `[]` (Optional) Account to read the proof from
66 /// 1. `[writable]` (Optional) The proof context account
67 /// 2. `[]` (Optional) The proof context account owner
68 ///
69 /// The instruction expects either:
70 /// i. `ZeroBalanceProofData` if proof is provided as instruction data
71 /// ii. `u32` byte offset if proof is provided as an account
72 ///
73 VerifyZeroBalance,
74
75 /// Verify a withdraw zero-knowledge proof.
76 ///
77 /// This proof is a collection of smaller proofs that are required by the SPL Token 2022
78 /// confidential extension `Withdraw` instruction.
79 ///
80 /// Accounts expected by this instruction:
81 ///
82 /// 0. `[]` (Optional) Account to read the proof from
83 /// 1. `[writable]` (Optional) The proof context account
84 /// 2. `[]` (Optional) The proof context account owner
85 ///
86 /// The instruction expects either:
87 /// i. `WithdrawData` if proof is provided as instruction data
88 /// ii. `u32` byte offset if proof is provided as an account
89 ///
90 VerifyWithdraw,
91
92 /// Verify a ciphertext-ciphertext equality proof.
93 ///
94 /// A ciphertext-ciphertext equality proof certifies that two ElGamal ciphertexts encrypt the
95 /// same message.
96 ///
97 /// Accounts expected by this instruction:
98 ///
99 /// 0. `[]` (Optional) Account to read the proof from
100 /// 1. `[writable]` (Optional) The proof context account
101 /// 2. `[]` (Optional) The proof context account owner
102 ///
103 /// The instruction expects either:
104 /// i. `CiphertextCiphertextEqualityProofData` if proof is provided as instruction data
105 /// ii. `u32` byte offset if proof is provided as an account
106 ///
107 VerifyCiphertextCiphertextEquality,
108
109 /// Verify a transfer zero-knowledge proof.
110 ///
111 /// This proof is a collection of smaller proofs that are required by the SPL Token 2022
112 /// confidential extension `Transfer` instruction with transfer fees.
113 ///
114 /// Accounts expected by this instruction:
115 ///
116 /// 0. `[]` (Optional) Account to read the proof from
117 /// 1. `[writable]` (Optional) The proof context account
118 /// 2. `[]` (Optional) The proof context account owner
119 ///
120 /// The instruction expects either:
121 /// i. `TransferData` if proof is provided as instruction data
122 /// ii. `u32` byte offset if proof is provided as an account
123 ///
124 VerifyTransfer,
125
126 /// Verify a transfer with fee zero-knowledge proof.
127 ///
128 /// This proof is a collection of smaller proofs that are required by the SPL Token 2022
129 /// confidential extension `Transfer` instruction without transfer fees.
130 ///
131 /// Accounts expected by this instruction:
132 ///
133 /// 0. `[]` (Optional) Account to read the proof from
134 /// 1. `[writable]` (Optional) The proof context account
135 /// 2. `[]` (Optional) The proof context account owner
136 ///
137 /// The instruction expects either:
138 /// i. `TransferWithFeeData` if proof is provided as instruction data
139 /// ii. `u32` byte offset if proof is provided as an account
140 ///
141 VerifyTransferWithFee,
142
143 /// Verify a public key validity zero-knowledge proof.
144 ///
145 /// A public key validity proof certifies that an ElGamal public key is well-formed and the
146 /// prover knows the corresponding secret key.
147 ///
148 /// Accounts expected by this instruction:
149 ///
150 /// 0. `[]` (Optional) Account to read the proof from
151 /// 1. `[writable]` (Optional) The proof context account
152 /// 2. `[]` (Optional) The proof context account owner
153 ///
154 /// The instruction expects either:
155 /// i. `PubkeyValidityData` if proof is provided as instruction data
156 /// ii. `u32` byte offset if proof is provided as an account
157 ///
158 VerifyPubkeyValidity,
159
160 /// Verify a 64-bit range proof.
161 ///
162 /// A range proof is defined with respect to a Pedersen commitment. The 64-bit range proof
163 /// certifies that a Pedersen commitment holds an unsigned 64-bit number.
164 ///
165 /// Accounts expected by this instruction:
166 ///
167 /// 0. `[]` (Optional) Account to read the proof from
168 /// 1. `[writable]` (Optional) The proof context account
169 /// 2. `[]` (Optional) The proof context account owner
170 ///
171 /// The instruction expects either:
172 /// i. `RangeProofU64Data` if proof is provided as instruction data
173 /// ii. `u32` byte offset if proof is provided as an account
174 ///
175 VerifyRangeProofU64,
176
177 /// Verify a 64-bit batched range proof.
178 ///
179 /// A batched range proof is defined with respect to a sequence of Pedersen commitments `[C_1,
180 /// ..., C_N]` and bit-lengths `[n_1, ..., n_N]`. It certifies that each commitment `C_i` is a
181 /// commitment to a positive number of bit-length `n_i`. Batch verifying range proofs is more
182 /// efficient than verifying independent range proofs on commitments `C_1, ..., C_N`
183 /// separately.
184 ///
185 /// The bit-length of a batched range proof specifies the sum of the individual bit-lengths
186 /// `n_1, ..., n_N`. For example, this instruction can be used to certify that two commitments
187 /// `C_1` and `C_2` each hold positive 32-bit numbers.
188 ///
189 /// Accounts expected by this instruction:
190 ///
191 /// 0. `[]` (Optional) Account to read the proof from
192 /// 1. `[writable]` (Optional) The proof context account
193 /// 2. `[]` (Optional) The proof context account owner
194 ///
195 /// The instruction expects either:
196 /// i. `BatchedRangeProofU64Data` if proof is provided as instruction data
197 /// ii. `u32` byte offset if proof is provided as an account
198 ///
199 VerifyBatchedRangeProofU64,
200
201 /// Verify 128-bit batched range proof.
202 ///
203 /// The bit-length of a batched range proof specifies the sum of the individual bit-lengths
204 /// `n_1, ..., n_N`. For example, this instruction can be used to certify that two commitments
205 /// `C_1` and `C_2` each hold positive 64-bit numbers.
206 ///
207 /// Accounts expected by this instruction:
208 ///
209 /// 0. `[]` (Optional) Account to read the proof from
210 /// 1. `[writable]` (Optional) The proof context account
211 /// 2. `[]` (Optional) The proof context account owner
212 ///
213 /// The instruction expects either:
214 /// i. `BatchedRangeProofU128Data` if proof is provided as instruction data
215 /// ii. `u32` byte offset if proof is provided as an account
216 ///
217 VerifyBatchedRangeProofU128,
218
219 /// Verify 256-bit batched range proof.
220 ///
221 /// The bit-length of a batched range proof specifies the sum of the individual bit-lengths
222 /// `n_1, ..., n_N`. For example, this instruction can be used to certify that four commitments
223 /// `[C_1, C_2, C_3, C_4]` each hold positive 64-bit numbers.
224 ///
225 /// Accounts expected by this instruction:
226 ///
227 /// 0. `[]` (Optional) Account to read the proof from
228 /// 1. `[writable]` (Optional) The proof context account
229 /// 2. `[]` (Optional) The proof context account owner
230 ///
231 /// The instruction expects either:
232 /// i. `BatchedRangeProofU256Data` if proof is provided as instruction data
233 /// ii. `u32` byte offset if proof is provided as an account
234 ///
235 VerifyBatchedRangeProofU256,
236
237 /// Verify a ciphertext-commitment equality proof.
238 ///
239 /// A ciphertext-commitment equality proof certifies that an ElGamal ciphertext and a Pedersen
240 /// commitment encrypt/encode the same message.
241 ///
242 /// Accounts expected by this instruction:
243 ///
244 /// 0. `[]` (Optional) Account to read the proof from
245 /// 1. `[writable]` (Optional) The proof context account
246 /// 2. `[]` (Optional) The proof context account owner
247 ///
248 /// The instruction expects either:
249 /// i. `CiphertextCommitmentEqualityProofData` if proof is provided as instruction data
250 /// ii. `u32` byte offset if proof is provided as an account
251 ///
252 VerifyCiphertextCommitmentEquality,
253
254 /// Verify a grouped-ciphertext with 2 handles validity proof.
255 ///
256 /// A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
257 /// well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
258 /// decryption handles.
259 ///
260 /// Accounts expected by this instruction:
261 ///
262 /// 0. `[]` (Optional) Account to read the proof from
263 /// 1. `[writable]` (Optional) The proof context account
264 /// 2. `[]` (Optional) The proof context account owner
265 ///
266 /// The instruction expects either:
267 /// i. `GroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data
268 /// ii. `u32` byte offset if proof is provided as an account
269 ///
270 VerifyGroupedCiphertext2HandlesValidity,
271
272 /// Verify a batched grouped-ciphertext with 2 handles validity proof.
273 ///
274 /// A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
275 /// ciphertext that are encrypted using the same set of ElGamal public keys. A batched
276 /// grouped-ciphertext validity proof is shorter and more efficient than two individual
277 /// grouped-ciphertext validity proofs.
278 ///
279 /// Accounts expected by this instruction:
280 ///
281 /// 0. `[]` (Optional) Account to read the proof from
282 /// 1. `[writable]` (Optional) The proof context account
283 /// 2. `[]` (Optional) The proof context account owner
284 ///
285 /// The instruction expects either:
286 /// i. `BatchedGroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data
287 /// ii. `u32` byte offset if proof is provided as an account
288 ///
289 VerifyBatchedGroupedCiphertext2HandlesValidity,
290
291 /// Verify a fee sigma proof.
292 ///
293 /// A fee sigma proof certifies that a Pedersen commitment that encodes a transfer fee for SPL
294 /// Token 2022 is well-formed.
295 ///
296 /// Accounts expected by this instruction:
297 ///
298 /// 0. `[]` (Optional) Account to read the proof from
299 /// 1. `[writable]` (Optional) The proof context account
300 /// 2. `[]` (Optional) The proof context account owner
301 ///
302 /// The instruction expects either:
303 /// i. `FeeSigmaProofData` if proof is provided as instruction data
304 /// ii. `u32` byte offset if proof is provided as an account
305 ///
306 VerifyFeeSigma,
307
308 /// Verify a grouped-ciphertext with 3 handles validity proof.
309 ///
310 /// A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
311 /// well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
312 /// decryption handles.
313 ///
314 /// Accounts expected by this instruction:
315 ///
316 /// * Creating a proof context account
317 /// 0. `[]` (Optional) Account to read the proof from
318 /// 1. `[writable]` The proof context account
319 /// 2. `[]` The proof context account owner
320 ///
321 /// * Otherwise
322 /// None
323 ///
324 /// The instruction expects either:
325 /// i. `GroupedCiphertext3HandlesValidityProofData` if proof is provided as instruction data
326 /// ii. `u32` byte offset if proof is provided as an account
327 ///
328 VerifyGroupedCiphertext3HandlesValidity,
329
330 /// Verify a batched grouped-ciphertext with 3 handles validity proof.
331 ///
332 /// A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
333 /// ciphertext that are encrypted using the same set of ElGamal public keys. A batched
334 /// grouped-ciphertext validity proof is shorter and more efficient than two individual
335 /// grouped-ciphertext validity proofs.
336 ///
337 /// Accounts expected by this instruction:
338 ///
339 /// * Creating a proof context account
340 /// 0. `[]` (Optional) Account to read the proof from
341 /// 1. `[writable]` The proof context account
342 /// 2. `[]` The proof context account owner
343 ///
344 /// * Otherwise
345 /// None
346 ///
347 /// The instruction expects either:
348 /// i. `BatchedGroupedCiphertext3HandlesValidityProofData` if proof is provided as instruction data
349 /// ii. `u32` byte offset if proof is provided as an account
350 ///
351 VerifyBatchedGroupedCiphertext3HandlesValidity,
352}
353
354/// Pubkeys associated with a context state account to be used as parameters to functions.
355#[derive(Clone, Copy, Debug, PartialEq)]
356pub struct ContextStateInfo<'a> {
357 pub context_state_account: &'a Pubkey,
358 pub context_state_authority: &'a Pubkey,
359}
360
361/// Create a `CloseContextState` instruction.
362pub fn close_context_state(
363 context_state_info: ContextStateInfo,
364 destination_account: &Pubkey,
365) -> Instruction {
366 let accounts = vec![
367 AccountMeta::new(*context_state_info.context_state_account, false),
368 AccountMeta::new(*destination_account, false),
369 AccountMeta::new_readonly(*context_state_info.context_state_authority, true),
370 ];
371
372 let data = vec![ToPrimitive::to_u8(&ProofInstruction::CloseContextState).unwrap()];
373
374 Instruction {
375 program_id: crate::zk_token_proof_program::id(),
376 accounts,
377 data,
378 }
379}
380
381/// Create a `VerifyZeroBalance` instruction.
382pub fn verify_zero_balance(
383 context_state_info: Option<ContextStateInfo>,
384 proof_data: &ZeroBalanceProofData,
385) -> Instruction {
386 ProofInstruction::VerifyZeroBalance.encode_verify_proof(context_state_info, proof_data)
387}
388
389/// Create a `VerifyWithdraw` instruction.
390pub fn verify_withdraw(
391 context_state_info: Option<ContextStateInfo>,
392 proof_data: &WithdrawData,
393) -> Instruction {
394 ProofInstruction::VerifyWithdraw.encode_verify_proof(context_state_info, proof_data)
395}
396
397/// Create a `VerifyCiphertextCiphertextEquality` instruction.
398pub fn verify_ciphertext_ciphertext_equality(
399 context_state_info: Option<ContextStateInfo>,
400 proof_data: &CiphertextCiphertextEqualityProofData,
401) -> Instruction {
402 ProofInstruction::VerifyCiphertextCiphertextEquality
403 .encode_verify_proof(context_state_info, proof_data)
404}
405
406/// Create a `VerifyTransfer` instruction.
407pub fn verify_transfer(
408 context_state_info: Option<ContextStateInfo>,
409 proof_data: &TransferData,
410) -> Instruction {
411 ProofInstruction::VerifyTransfer.encode_verify_proof(context_state_info, proof_data)
412}
413
414/// Create a `VerifyTransferWithFee` instruction.
415pub fn verify_transfer_with_fee(
416 context_state_info: Option<ContextStateInfo>,
417 proof_data: &TransferWithFeeData,
418) -> Instruction {
419 ProofInstruction::VerifyTransferWithFee.encode_verify_proof(context_state_info, proof_data)
420}
421
422/// Create a `VerifyPubkeyValidity` instruction.
423pub fn verify_pubkey_validity(
424 context_state_info: Option<ContextStateInfo>,
425 proof_data: &PubkeyValidityData,
426) -> Instruction {
427 ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(context_state_info, proof_data)
428}
429
430/// Create a `VerifyRangeProofU64` instruction.
431pub fn verify_range_proof_u64(
432 context_state_info: Option<ContextStateInfo>,
433 proof_data: &RangeProofU64Data,
434) -> Instruction {
435 ProofInstruction::VerifyRangeProofU64.encode_verify_proof(context_state_info, proof_data)
436}
437
438/// Create a `VerifyBatchedRangeProofU64` instruction.
439pub fn verify_batched_verify_range_proof_u64(
440 context_state_info: Option<ContextStateInfo>,
441 proof_data: &BatchedRangeProofU64Data,
442) -> Instruction {
443 ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(context_state_info, proof_data)
444}
445
446/// Create a `VerifyBatchedRangeProofU128` instruction.
447pub fn verify_batched_verify_range_proof_u128(
448 context_state_info: Option<ContextStateInfo>,
449 proof_data: &BatchedRangeProofU128Data,
450) -> Instruction {
451 ProofInstruction::VerifyBatchedRangeProofU128
452 .encode_verify_proof(context_state_info, proof_data)
453}
454
455/// Create a `VerifyBatchedRangeProofU256` instruction.
456pub fn verify_batched_verify_range_proof_u256(
457 context_state_info: Option<ContextStateInfo>,
458 proof_data: &BatchedRangeProofU256Data,
459) -> Instruction {
460 ProofInstruction::VerifyBatchedRangeProofU256
461 .encode_verify_proof(context_state_info, proof_data)
462}
463
464/// Create a `VerifyCiphertextCommitmentEquality` instruction.
465pub fn verify_ciphertext_commitment_equality(
466 context_state_info: Option<ContextStateInfo>,
467 proof_data: &PubkeyValidityData,
468) -> Instruction {
469 ProofInstruction::VerifyCiphertextCommitmentEquality
470 .encode_verify_proof(context_state_info, proof_data)
471}
472
473/// Create a `VerifyGroupedCipehrtext3HandlesValidity` instruction.
474pub fn verify_grouped_ciphertext_3_handles_validity(
475 context_state_info: Option<ContextStateInfo>,
476 proof_data: &GroupedCiphertext3HandlesValidityProofData,
477) -> Instruction {
478 ProofInstruction::VerifyGroupedCiphertext3HandlesValidity
479 .encode_verify_proof(context_state_info, proof_data)
480}
481
482/// Create a `VerifyBatchedGroupedCiphertext3HandlesValidity` instruction.
483pub fn verify_batched_grouped_ciphertext_3_handles_validity(
484 context_state_info: Option<ContextStateInfo>,
485 proof_data: &BatchedGroupedCiphertext3HandlesValidityProofData,
486) -> Instruction {
487 ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
488 .encode_verify_proof(context_state_info, proof_data)
489}
490
491impl ProofInstruction {
492 pub fn encode_verify_proof<T, U>(
493 &self,
494 context_state_info: Option<ContextStateInfo>,
495 proof_data: &T,
496 ) -> Instruction
497 where
498 T: Pod + ZkProofData<U>,
499 U: Pod,
500 {
501 let accounts = if let Some(context_state_info) = context_state_info {
502 vec![
503 AccountMeta::new(*context_state_info.context_state_account, false),
504 AccountMeta::new_readonly(*context_state_info.context_state_authority, false),
505 ]
506 } else {
507 vec![]
508 };
509
510 let mut data = vec![ToPrimitive::to_u8(self).unwrap()];
511 data.extend_from_slice(bytes_of(proof_data));
512
513 Instruction {
514 program_id: crate::zk_token_proof_program::id(),
515 accounts,
516 data,
517 }
518 }
519
520 pub fn encode_verify_proof_from_account(
521 &self,
522 context_state_info: Option<ContextStateInfo>,
523 proof_account: &Pubkey,
524 offset: u32,
525 ) -> Instruction {
526 let accounts = if let Some(context_state_info) = context_state_info {
527 vec![
528 AccountMeta::new(*proof_account, false),
529 AccountMeta::new(*context_state_info.context_state_account, false),
530 AccountMeta::new_readonly(*context_state_info.context_state_authority, false),
531 ]
532 } else {
533 vec![AccountMeta::new(*proof_account, false)]
534 };
535
536 let mut data = vec![ToPrimitive::to_u8(self).unwrap()];
537 data.extend_from_slice(&offset.to_le_bytes());
538
539 Instruction {
540 program_id: crate::zk_token_proof_program::id(),
541 accounts,
542 data,
543 }
544 }
545
546 pub fn instruction_type(input: &[u8]) -> Option<Self> {
547 input
548 .first()
549 .and_then(|instruction| FromPrimitive::from_u8(*instruction))
550 }
551
552 pub fn proof_data<T, U>(input: &[u8]) -> Option<&T>
553 where
554 T: Pod + ZkProofData<U>,
555 U: Pod,
556 {
557 input
558 .get(1..)
559 .and_then(|data| bytemuck::try_from_bytes(data).ok())
560 }
561}