solana_zk_sdk/zk_elgamal_proof_program/
instruction.rs

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