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 progrma 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 discriminator + 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
23//! data to 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 as part of 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 ElGamal proof`]: https://docs.solanalabs.com/runtime/zk-token-proof
33//! [`context-state`]: https://docs.solanalabs.com/runtime/zk-token-proof#context-data
34
35use {
36    crate::zk_elgamal_proof_program::proof_data::ZkProofData,
37    bytemuck::{bytes_of, Pod},
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-ciphertext proof.
60    ///
61    /// A zero-ciphertext 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. `ZeroCiphertextProofData` if proof is provided as instruction data
71    ///   ii. `u32` byte offset if proof is provided as an account
72    ///
73    VerifyZeroCiphertext,
74
75    /// Verify a ciphertext-ciphertext equality proof.
76    ///
77    /// A ciphertext-ciphertext equality proof certifies that two ElGamal ciphertexts encrypt the
78    /// same message.
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. `CiphertextCiphertextEqualityProofData` if proof is provided as instruction data
88    ///   ii. `u32` byte offset if proof is provided as an account
89    ///
90    VerifyCiphertextCiphertextEquality,
91
92    /// Verify a ciphertext-commitment equality proof.
93    ///
94    /// A ciphertext-commitment equality proof certifies that an ElGamal ciphertext and a Pedersen
95    /// commitment encrypt/encode the 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. `CiphertextCommitmentEqualityProofData` if proof is provided as instruction data
105    ///   ii. `u32` byte offset if proof is provided as an account
106    ///
107    VerifyCiphertextCommitmentEquality,
108
109    /// Verify a public key validity zero-knowledge proof.
110    ///
111    /// A public key validity proof certifies that an ElGamal public key is well-formed and the
112    /// prover knows the corresponding secret key.
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. `PubkeyValidityData` if proof is provided as instruction data
122    ///   ii. `u32` byte offset if proof is provided as an account
123    ///
124    VerifyPubkeyValidity,
125
126    /// Verify a percentage-with-cap proof.
127    ///
128    /// A percentage-with-cap proof certifies that a tuple of Pedersen commitments satisfy a
129    /// percentage relation.
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. `PercentageWithCapProofData` if proof is provided as instruction data
139    ///   ii. `u32` byte offset if proof is provided as an account
140    ///
141    VerifyPercentageWithCap,
142
143    /// Verify a 64-bit batched range proof.
144    ///
145    /// A batched range proof is defined with respect to a sequence of Pedersen commitments `[C_1,
146    /// ..., C_N]` and bit-lengths `[n_1, ..., n_N]`. It certifies that each commitment `C_i` is a
147    /// commitment to a positive number of bit-length `n_i`. Batch verifying range proofs is more
148    /// efficient than verifying independent range proofs on commitments `C_1, ..., C_N`
149    /// separately.
150    ///
151    /// The bit-length of a batched range proof specifies the sum of the individual bit-lengths
152    /// `n_1, ..., n_N`. For example, this instruction can be used to certify that two commitments
153    /// `C_1` and `C_2` each hold positive 32-bit numbers.
154    ///
155    /// Accounts expected by this instruction:
156    ///
157    ///   0. `[]` (Optional) Account to read the proof from
158    ///   1. `[writable]` (Optional) The proof context account
159    ///   2. `[]` (Optional) The proof context account owner
160    ///
161    /// The instruction expects either:
162    ///   i. `BatchedRangeProofU64Data` if proof is provided as instruction data
163    ///   ii. `u32` byte offset if proof is provided as an account
164    ///
165    VerifyBatchedRangeProofU64,
166
167    /// Verify 128-bit batched range proof.
168    ///
169    /// The bit-length of a batched range proof specifies the sum of the individual bit-lengths
170    /// `n_1, ..., n_N`. For example, this instruction can be used to certify that two commitments
171    /// `C_1` and `C_2` each hold positive 64-bit numbers.
172    ///
173    /// Accounts expected by this instruction:
174    ///
175    ///   0. `[]` (Optional) Account to read the proof from
176    ///   1. `[writable]` (Optional) The proof context account
177    ///   2. `[]` (Optional) The proof context account owner
178    ///
179    /// The instruction expects either:
180    ///   i. `BatchedRangeProofU128Data` if proof is provided as instruction data
181    ///   ii. `u32` byte offset if proof is provided as an account
182    ///
183    VerifyBatchedRangeProofU128,
184
185    /// Verify 256-bit batched range proof.
186    ///
187    /// The bit-length of a batched range proof specifies the sum of the individual bit-lengths
188    /// `n_1, ..., n_N`. For example, this instruction can be used to certify that four commitments
189    /// `[C_1, C_2, C_3, C_4]` each hold positive 64-bit numbers.
190    ///
191    /// Accounts expected by this instruction:
192    ///
193    ///   0. `[]` (Optional) Account to read the proof from
194    ///   1. `[writable]` (Optional) The proof context account
195    ///   2. `[]` (Optional) The proof context account owner
196    ///
197    /// The instruction expects either:
198    ///   i. `BatchedRangeProofU256Data` if proof is provided as instruction data
199    ///   ii. `u32` byte offset if proof is provided as an account
200    ///
201    VerifyBatchedRangeProofU256,
202
203    /// Verify a grouped-ciphertext with 2 handles validity proof.
204    ///
205    /// A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
206    /// well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
207    /// decryption handles.
208    ///
209    /// Accounts expected by this instruction:
210    ///
211    ///   0. `[]` (Optional) Account to read the proof from
212    ///   1. `[writable]` (Optional) The proof context account
213    ///   2. `[]` (Optional) The proof context account owner
214    ///
215    /// The instruction expects either:
216    ///   i. `GroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data
217    ///   ii. `u32` byte offset if proof is provided as an account
218    ///
219    VerifyGroupedCiphertext2HandlesValidity,
220
221    /// Verify a batched grouped-ciphertext with 2 handles validity proof.
222    ///
223    /// A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
224    /// ciphertext that are encrypted using the same set of ElGamal public keys. A batched
225    /// grouped-ciphertext validity proof is shorter and more efficient than two individual
226    /// grouped-ciphertext validity proofs.
227    ///
228    /// Accounts expected by this instruction:
229    ///
230    ///   0. `[]` (Optional) Account to read the proof from
231    ///   1. `[writable]` (Optional) The proof context account
232    ///   2. `[]` (Optional) The proof context account owner
233    ///
234    /// The instruction expects either:
235    ///   i. `BatchedGroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data
236    ///   ii. `u32` byte offset if proof is provided as an account
237    ///
238    VerifyBatchedGroupedCiphertext2HandlesValidity,
239
240    /// Verify a grouped-ciphertext with 3 handles validity proof.
241    ///
242    /// A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
243    /// well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
244    /// decryption handles.
245    ///
246    /// Accounts expected by this instruction:
247    ///
248    ///   * Creating a proof context account
249    ///   0. `[]` (Optional) Account to read the proof from
250    ///   1. `[writable]` The proof context account
251    ///   2. `[]` The proof context account owner
252    ///
253    ///   * Otherwise
254    ///     None
255    ///
256    /// The instruction expects either:
257    ///   i. `GroupedCiphertext3HandlesValidityProofData` if proof is provided as instruction data
258    ///   ii. `u32` byte offset if proof is provided as an account
259    ///
260    VerifyGroupedCiphertext3HandlesValidity,
261
262    /// Verify a batched grouped-ciphertext with 3 handles validity proof.
263    ///
264    /// A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
265    /// ciphertext that are encrypted using the same set of ElGamal public keys. A batched
266    /// grouped-ciphertext validity proof is shorter and more efficient than two individual
267    /// grouped-ciphertext validity proofs.
268    ///
269    /// Accounts expected by this instruction:
270    ///
271    ///   * Creating a proof context account
272    ///   0. `[]` (Optional) Account to read the proof from
273    ///   1. `[writable]` The proof context account
274    ///   2. `[]` The proof context account owner
275    ///
276    ///   * Otherwise
277    ///     None
278    ///
279    /// The instruction expects either:
280    ///   i. `BatchedGroupedCiphertext3HandlesValidityProofData` if proof is provided as instruction data
281    ///   ii. `u32` byte offset if proof is provided as an account
282    ///
283    VerifyBatchedGroupedCiphertext3HandlesValidity,
284}
285
286/// Pubkeys associated with a context state account to be used as parameters to functions.
287#[derive(Clone, Copy, Debug, PartialEq)]
288pub struct ContextStateInfo<'a> {
289    pub context_state_account: &'a Pubkey,
290    pub context_state_authority: &'a Pubkey,
291}
292
293/// Create a `CloseContextState` instruction.
294pub fn close_context_state(
295    context_state_info: ContextStateInfo,
296    destination_account: &Pubkey,
297) -> Instruction {
298    let accounts = vec![
299        AccountMeta::new(*context_state_info.context_state_account, false),
300        AccountMeta::new(*destination_account, false),
301        AccountMeta::new_readonly(*context_state_info.context_state_authority, true),
302    ];
303
304    let data = vec![ToPrimitive::to_u8(&ProofInstruction::CloseContextState).unwrap()];
305
306    Instruction {
307        program_id: crate::zk_elgamal_proof_program::id(),
308        accounts,
309        data,
310    }
311}
312
313impl ProofInstruction {
314    pub fn encode_verify_proof<T, U>(
315        &self,
316        context_state_info: Option<ContextStateInfo>,
317        proof_data: &T,
318    ) -> Instruction
319    where
320        T: Pod + ZkProofData<U>,
321        U: Pod,
322    {
323        let accounts = if let Some(context_state_info) = context_state_info {
324            vec![
325                AccountMeta::new(*context_state_info.context_state_account, false),
326                AccountMeta::new_readonly(*context_state_info.context_state_authority, false),
327            ]
328        } else {
329            vec![]
330        };
331
332        let mut data = vec![ToPrimitive::to_u8(self).unwrap()];
333        data.extend_from_slice(bytes_of(proof_data));
334
335        Instruction {
336            program_id: crate::zk_elgamal_proof_program::id(),
337            accounts,
338            data,
339        }
340    }
341
342    pub fn encode_verify_proof_from_account(
343        &self,
344        context_state_info: Option<ContextStateInfo>,
345        proof_account: &Pubkey,
346        offset: u32,
347    ) -> Instruction {
348        let accounts = if let Some(context_state_info) = context_state_info {
349            vec![
350                AccountMeta::new(*proof_account, false),
351                AccountMeta::new(*context_state_info.context_state_account, false),
352                AccountMeta::new_readonly(*context_state_info.context_state_authority, false),
353            ]
354        } else {
355            vec![AccountMeta::new(*proof_account, false)]
356        };
357
358        let mut data = vec![ToPrimitive::to_u8(self).unwrap()];
359        data.extend_from_slice(&offset.to_le_bytes());
360
361        Instruction {
362            program_id: crate::zk_elgamal_proof_program::id(),
363            accounts,
364            data,
365        }
366    }
367
368    pub fn instruction_type(input: &[u8]) -> Option<Self> {
369        input
370            .first()
371            .and_then(|instruction| FromPrimitive::from_u8(*instruction))
372    }
373
374    pub fn proof_data<T, U>(input: &[u8]) -> Option<&T>
375    where
376        T: Pod + ZkProofData<U>,
377        U: Pod,
378    {
379        input
380            .get(1..)
381            .and_then(|data| bytemuck::try_from_bytes(data).ok())
382    }
383}