solana_zk_token_sdk/instruction/
ciphertext_ciphertext_equality.rs

1//! The ciphertext-ciphertext equality proof instruction.
2//!
3//! A ciphertext-ciphertext equality proof is defined with respect to two twisted ElGamal
4//! ciphertexts. The proof certifies that the two ciphertexts encrypt the same message. To generate
5//! the proof, a prover must provide the decryption key for the first ciphertext and the randomness
6//! used to generate the second ciphertext.
7//!
8//! The first ciphertext associated with the proof is referred to as the "source" ciphertext. The
9//! second ciphertext associated with the proof is referred to as the "destination" ciphertext.
10
11#[cfg(not(target_os = "solana"))]
12use {
13    crate::{
14        encryption::{
15            elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
16            pedersen::PedersenOpening,
17        },
18        errors::{ProofGenerationError, ProofVerificationError},
19        sigma_proofs::ciphertext_ciphertext_equality_proof::CiphertextCiphertextEqualityProof,
20        transcript::TranscriptProtocol,
21    },
22    merlin::Transcript,
23    std::convert::TryInto,
24};
25use {
26    crate::{
27        instruction::{ProofType, ZkProofData},
28        zk_token_elgamal::pod,
29    },
30    bytemuck_derive::{Pod, Zeroable},
31};
32
33/// The instruction data that is needed for the
34/// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction.
35///
36/// It includes the cryptographic proof as well as the context data information needed to verify
37/// the proof.
38#[derive(Clone, Copy, Pod, Zeroable)]
39#[repr(C)]
40pub struct CiphertextCiphertextEqualityProofData {
41    pub context: CiphertextCiphertextEqualityProofContext,
42
43    pub proof: pod::CiphertextCiphertextEqualityProof,
44}
45
46/// The context data needed to verify a ciphertext-ciphertext equality proof.
47#[derive(Clone, Copy, Pod, Zeroable)]
48#[repr(C)]
49pub struct CiphertextCiphertextEqualityProofContext {
50    pub source_pubkey: pod::ElGamalPubkey, // 32 bytes
51
52    pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
53
54    pub source_ciphertext: pod::ElGamalCiphertext, // 64 bytes
55
56    pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
57}
58
59#[cfg(not(target_os = "solana"))]
60impl CiphertextCiphertextEqualityProofData {
61    pub fn new(
62        source_keypair: &ElGamalKeypair,
63        destination_pubkey: &ElGamalPubkey,
64        source_ciphertext: &ElGamalCiphertext,
65        destination_ciphertext: &ElGamalCiphertext,
66        destination_opening: &PedersenOpening,
67        amount: u64,
68    ) -> Result<Self, ProofGenerationError> {
69        let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.pubkey().into());
70        let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
71        let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes());
72        let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
73
74        let context = CiphertextCiphertextEqualityProofContext {
75            source_pubkey: pod_source_pubkey,
76            destination_pubkey: pod_destination_pubkey,
77            source_ciphertext: pod_source_ciphertext,
78            destination_ciphertext: pod_destination_ciphertext,
79        };
80
81        let mut transcript = context.new_transcript();
82
83        let proof = CiphertextCiphertextEqualityProof::new(
84            source_keypair,
85            destination_pubkey,
86            source_ciphertext,
87            destination_opening,
88            amount,
89            &mut transcript,
90        )
91        .into();
92
93        Ok(Self { context, proof })
94    }
95}
96
97impl ZkProofData<CiphertextCiphertextEqualityProofContext>
98    for CiphertextCiphertextEqualityProofData
99{
100    const PROOF_TYPE: ProofType = ProofType::CiphertextCiphertextEquality;
101
102    fn context_data(&self) -> &CiphertextCiphertextEqualityProofContext {
103        &self.context
104    }
105
106    #[cfg(not(target_os = "solana"))]
107    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
108        let mut transcript = self.context.new_transcript();
109
110        let source_pubkey = self.context.source_pubkey.try_into()?;
111        let destination_pubkey = self.context.destination_pubkey.try_into()?;
112        let source_ciphertext = self.context.source_ciphertext.try_into()?;
113        let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
114        let proof: CiphertextCiphertextEqualityProof = self.proof.try_into()?;
115
116        proof
117            .verify(
118                &source_pubkey,
119                &destination_pubkey,
120                &source_ciphertext,
121                &destination_ciphertext,
122                &mut transcript,
123            )
124            .map_err(|e| e.into())
125    }
126}
127
128#[allow(non_snake_case)]
129#[cfg(not(target_os = "solana"))]
130impl CiphertextCiphertextEqualityProofContext {
131    fn new_transcript(&self) -> Transcript {
132        let mut transcript = Transcript::new(b"CiphertextCiphertextEqualityProof");
133
134        transcript.append_pubkey(b"source-pubkey", &self.source_pubkey);
135        transcript.append_pubkey(b"destination-pubkey", &self.destination_pubkey);
136
137        transcript.append_ciphertext(b"source-ciphertext", &self.source_ciphertext);
138        transcript.append_ciphertext(b"destination-ciphertext", &self.destination_ciphertext);
139
140        transcript
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use super::*;
147
148    #[test]
149    fn test_ciphertext_ciphertext_instruction_correctness() {
150        let source_keypair = ElGamalKeypair::new_rand();
151        let destination_keypair = ElGamalKeypair::new_rand();
152
153        let amount: u64 = 0;
154        let source_ciphertext = source_keypair.pubkey().encrypt(amount);
155
156        let destination_opening = PedersenOpening::new_rand();
157        let destination_ciphertext = destination_keypair
158            .pubkey()
159            .encrypt_with(amount, &destination_opening);
160
161        let proof_data = CiphertextCiphertextEqualityProofData::new(
162            &source_keypair,
163            destination_keypair.pubkey(),
164            &source_ciphertext,
165            &destination_ciphertext,
166            &destination_opening,
167            amount,
168        )
169        .unwrap();
170
171        assert!(proof_data.verify_proof().is_ok());
172
173        let amount: u64 = 55;
174        let source_ciphertext = source_keypair.pubkey().encrypt(amount);
175
176        let destination_opening = PedersenOpening::new_rand();
177        let destination_ciphertext = destination_keypair
178            .pubkey()
179            .encrypt_with(amount, &destination_opening);
180
181        let proof_data = CiphertextCiphertextEqualityProofData::new(
182            &source_keypair,
183            destination_keypair.pubkey(),
184            &source_ciphertext,
185            &destination_ciphertext,
186            &destination_opening,
187            amount,
188        )
189        .unwrap();
190
191        assert!(proof_data.verify_proof().is_ok());
192
193        let amount = u64::MAX;
194        let source_ciphertext = source_keypair.pubkey().encrypt(amount);
195
196        let destination_opening = PedersenOpening::new_rand();
197        let destination_ciphertext = destination_keypair
198            .pubkey()
199            .encrypt_with(amount, &destination_opening);
200
201        let proof_data = CiphertextCiphertextEqualityProofData::new(
202            &source_keypair,
203            destination_keypair.pubkey(),
204            &source_ciphertext,
205            &destination_ciphertext,
206            &destination_opening,
207            amount,
208        )
209        .unwrap();
210
211        assert!(proof_data.verify_proof().is_ok());
212    }
213}