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}