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