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}