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}