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}