solana_zk_token_sdk/instruction/batched_grouped_ciphertext_validity/
handles_3.rs

1//! The batched grouped-ciphertext with 3 handles validity proof instruction.
2//!
3//! A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
4//! ciphertext that are encrypted using the same set of ElGamal public keys. A batched
5//! grouped-ciphertext validity proof is shorter and more efficient than two individual
6//! grouped-ciphertext validity proofs.
7//!
8//! In accordance with the SPL Token program application, the first decryption handle associated
9//! with the proof is referred to as the "source" handle, the second decryption handle is referred
10//! to as the "destination" handle, and the third decryption handle is referred to as the "auditor"
11//! handle. Furthermore, the first grouped ciphertext is referred to as the "lo" ciphertext and the
12//! second grouped ciphertext is referred to as the "hi" ciphertext.
13
14#[cfg(not(target_os = "solana"))]
15use {
16    crate::{
17        encryption::{
18            elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext,
19            pedersen::PedersenOpening,
20        },
21        errors::{ProofGenerationError, ProofVerificationError},
22        sigma_proofs::batched_grouped_ciphertext_validity_proof::BatchedGroupedCiphertext3HandlesValidityProof,
23        transcript::TranscriptProtocol,
24    },
25    merlin::Transcript,
26};
27use {
28    crate::{
29        instruction::{ProofType, ZkProofData},
30        zk_token_elgamal::pod,
31    },
32    bytemuck_derive::{Pod, Zeroable},
33};
34
35/// The instruction data that is needed for the
36/// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity` instruction.
37///
38/// It includes the cryptographic proof as well as the context data information needed to verify
39/// the proof.
40#[derive(Clone, Copy, Pod, Zeroable)]
41#[repr(C)]
42pub struct BatchedGroupedCiphertext3HandlesValidityProofData {
43    pub context: BatchedGroupedCiphertext3HandlesValidityProofContext,
44
45    pub proof: pod::BatchedGroupedCiphertext3HandlesValidityProof,
46}
47
48#[derive(Clone, Copy, Pod, Zeroable)]
49#[repr(C)]
50pub struct BatchedGroupedCiphertext3HandlesValidityProofContext {
51    pub source_pubkey: pod::ElGamalPubkey, // 32 bytes
52
53    pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
54
55    pub auditor_pubkey: pod::ElGamalPubkey, // 32 bytes
56
57    pub grouped_ciphertext_lo: pod::GroupedElGamalCiphertext3Handles, // 128 bytes
58
59    pub grouped_ciphertext_hi: pod::GroupedElGamalCiphertext3Handles, // 128 bytes
60}
61
62#[cfg(not(target_os = "solana"))]
63impl BatchedGroupedCiphertext3HandlesValidityProofData {
64    pub fn new(
65        source_pubkey: &ElGamalPubkey,
66        destination_pubkey: &ElGamalPubkey,
67        auditor_pubkey: &ElGamalPubkey,
68        grouped_ciphertext_lo: &GroupedElGamalCiphertext<3>,
69        grouped_ciphertext_hi: &GroupedElGamalCiphertext<3>,
70        amount_lo: u64,
71        amount_hi: u64,
72        opening_lo: &PedersenOpening,
73        opening_hi: &PedersenOpening,
74    ) -> Result<Self, ProofGenerationError> {
75        let pod_source_pubkey = pod::ElGamalPubkey(source_pubkey.into());
76        let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
77        let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into());
78        let pod_grouped_ciphertext_lo = (*grouped_ciphertext_lo).into();
79        let pod_grouped_ciphertext_hi = (*grouped_ciphertext_hi).into();
80
81        let context = BatchedGroupedCiphertext3HandlesValidityProofContext {
82            source_pubkey: pod_source_pubkey,
83            destination_pubkey: pod_destination_pubkey,
84            auditor_pubkey: pod_auditor_pubkey,
85            grouped_ciphertext_lo: pod_grouped_ciphertext_lo,
86            grouped_ciphertext_hi: pod_grouped_ciphertext_hi,
87        };
88
89        let mut transcript = context.new_transcript();
90
91        let proof = BatchedGroupedCiphertext3HandlesValidityProof::new(
92            source_pubkey,
93            destination_pubkey,
94            auditor_pubkey,
95            amount_lo,
96            amount_hi,
97            opening_lo,
98            opening_hi,
99            &mut transcript,
100        )
101        .into();
102
103        Ok(Self { context, proof })
104    }
105}
106
107impl ZkProofData<BatchedGroupedCiphertext3HandlesValidityProofContext>
108    for BatchedGroupedCiphertext3HandlesValidityProofData
109{
110    const PROOF_TYPE: ProofType = ProofType::BatchedGroupedCiphertext3HandlesValidity;
111
112    fn context_data(&self) -> &BatchedGroupedCiphertext3HandlesValidityProofContext {
113        &self.context
114    }
115
116    #[cfg(not(target_os = "solana"))]
117    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
118        let mut transcript = self.context.new_transcript();
119
120        let source_pubkey = self.context.source_pubkey.try_into()?;
121        let destination_pubkey = self.context.destination_pubkey.try_into()?;
122        let auditor_pubkey = self.context.auditor_pubkey.try_into()?;
123        let grouped_ciphertext_lo: GroupedElGamalCiphertext<3> =
124            self.context.grouped_ciphertext_lo.try_into()?;
125        let grouped_ciphertext_hi: GroupedElGamalCiphertext<3> =
126            self.context.grouped_ciphertext_hi.try_into()?;
127
128        let source_handle_lo = grouped_ciphertext_lo.handles.first().unwrap();
129        let destination_handle_lo = grouped_ciphertext_lo.handles.get(1).unwrap();
130        let auditor_handle_lo = grouped_ciphertext_lo.handles.get(2).unwrap();
131
132        let source_handle_hi = grouped_ciphertext_hi.handles.first().unwrap();
133        let destination_handle_hi = grouped_ciphertext_hi.handles.get(1).unwrap();
134        let auditor_handle_hi = grouped_ciphertext_hi.handles.get(2).unwrap();
135
136        let proof: BatchedGroupedCiphertext3HandlesValidityProof = self.proof.try_into()?;
137
138        proof
139            .verify(
140                &source_pubkey,
141                &destination_pubkey,
142                &auditor_pubkey,
143                &grouped_ciphertext_lo.commitment,
144                &grouped_ciphertext_hi.commitment,
145                source_handle_lo,
146                source_handle_hi,
147                destination_handle_lo,
148                destination_handle_hi,
149                auditor_handle_lo,
150                auditor_handle_hi,
151                &mut transcript,
152            )
153            .map_err(|e| e.into())
154    }
155}
156
157#[cfg(not(target_os = "solana"))]
158impl BatchedGroupedCiphertext3HandlesValidityProofContext {
159    fn new_transcript(&self) -> Transcript {
160        let mut transcript = Transcript::new(b"BatchedGroupedCiphertext3HandlesValidityProof");
161
162        transcript.append_pubkey(b"source-pubkey", &self.source_pubkey);
163        transcript.append_pubkey(b"destination-pubkey", &self.destination_pubkey);
164        transcript.append_pubkey(b"auditor-pubkey", &self.auditor_pubkey);
165        transcript.append_grouped_ciphertext_3_handles(
166            b"grouped-ciphertext-lo",
167            &self.grouped_ciphertext_lo,
168        );
169        transcript.append_grouped_ciphertext_3_handles(
170            b"grouped-ciphertext-hi",
171            &self.grouped_ciphertext_hi,
172        );
173
174        transcript
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use {
181        super::*,
182        crate::encryption::{elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal},
183    };
184
185    #[test]
186    fn test_ciphertext_validity_proof_instruction_correctness() {
187        let source_keypair = ElGamalKeypair::new_rand();
188        let source_pubkey = source_keypair.pubkey();
189
190        let destination_keypair = ElGamalKeypair::new_rand();
191        let destination_pubkey = destination_keypair.pubkey();
192
193        let auditor_keypair = ElGamalKeypair::new_rand();
194        let auditor_pubkey = auditor_keypair.pubkey();
195
196        let amount_lo: u64 = 11;
197        let amount_hi: u64 = 22;
198
199        let opening_lo = PedersenOpening::new_rand();
200        let opening_hi = PedersenOpening::new_rand();
201
202        let grouped_ciphertext_lo = GroupedElGamal::encrypt_with(
203            [source_pubkey, destination_pubkey, auditor_pubkey],
204            amount_lo,
205            &opening_lo,
206        );
207
208        let grouped_ciphertext_hi = GroupedElGamal::encrypt_with(
209            [source_pubkey, destination_pubkey, auditor_pubkey],
210            amount_hi,
211            &opening_hi,
212        );
213
214        let proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
215            source_pubkey,
216            destination_pubkey,
217            auditor_pubkey,
218            &grouped_ciphertext_lo,
219            &grouped_ciphertext_hi,
220            amount_lo,
221            amount_hi,
222            &opening_lo,
223            &opening_hi,
224        )
225        .unwrap();
226
227        assert!(proof_data.verify_proof().is_ok());
228    }
229}