solana_zk_token_sdk/instruction/batched_grouped_ciphertext_validity/
handles_2.rs

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