solana_zk_token_sdk/instruction/grouped_ciphertext_validity/
handles_3.rs

1//! The grouped-ciphertext with 3 decryption handles validity proof instruction.
2//!
3//! A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
4//! well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
5//! decryption handles. To generate the proof, a prover must provide the Pedersen opening
6//! associated with the grouped ciphertext's commitment.
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
10//! referred to as the "destination" handle, and the third decryption handle is referred to as the
11//! "auditor" handle.
12
13#[cfg(not(target_os = "solana"))]
14use {
15    crate::{
16        encryption::{
17            elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext,
18            pedersen::PedersenOpening,
19        },
20        errors::{ProofGenerationError, ProofVerificationError},
21        sigma_proofs::grouped_ciphertext_validity_proof::GroupedCiphertext3HandlesValidityProof,
22        transcript::TranscriptProtocol,
23    },
24    merlin::Transcript,
25};
26use {
27    crate::{
28        instruction::{ProofType, ZkProofData},
29        zk_token_elgamal::pod,
30    },
31    bytemuck_derive::{Pod, Zeroable},
32};
33
34/// The instruction data that is needed for the
35/// `ProofInstruction::VerifyGroupedCiphertext3HandlesValidity` instruction.
36///
37/// It includes the cryptographic proof as well as the context data information needed to verify
38/// the proof.
39#[derive(Clone, Copy, Pod, Zeroable)]
40#[repr(C)]
41pub struct GroupedCiphertext3HandlesValidityProofData {
42    pub context: GroupedCiphertext3HandlesValidityProofContext,
43
44    pub proof: pod::GroupedCiphertext3HandlesValidityProof,
45}
46
47#[derive(Clone, Copy, Pod, Zeroable)]
48#[repr(C)]
49pub struct GroupedCiphertext3HandlesValidityProofContext {
50    pub source_pubkey: pod::ElGamalPubkey, // 32 bytes
51
52    pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
53
54    pub auditor_pubkey: pod::ElGamalPubkey, // 32 bytes
55
56    pub grouped_ciphertext: pod::GroupedElGamalCiphertext3Handles, // 128 bytes
57}
58
59#[cfg(not(target_os = "solana"))]
60impl GroupedCiphertext3HandlesValidityProofData {
61    pub fn new(
62        source_pubkey: &ElGamalPubkey,
63        destination_pubkey: &ElGamalPubkey,
64        auditor_pubkey: &ElGamalPubkey,
65        grouped_ciphertext: &GroupedElGamalCiphertext<3>,
66        amount: u64,
67        opening: &PedersenOpening,
68    ) -> Result<Self, ProofGenerationError> {
69        let pod_source_pubkey = pod::ElGamalPubkey(source_pubkey.into());
70        let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
71        let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into());
72        let pod_grouped_ciphertext = (*grouped_ciphertext).into();
73
74        let context = GroupedCiphertext3HandlesValidityProofContext {
75            source_pubkey: pod_source_pubkey,
76            destination_pubkey: pod_destination_pubkey,
77            auditor_pubkey: pod_auditor_pubkey,
78            grouped_ciphertext: pod_grouped_ciphertext,
79        };
80
81        let mut transcript = context.new_transcript();
82
83        let proof = GroupedCiphertext3HandlesValidityProof::new(
84            source_pubkey,
85            destination_pubkey,
86            auditor_pubkey,
87            amount,
88            opening,
89            &mut transcript,
90        )
91        .into();
92
93        Ok(Self { context, proof })
94    }
95}
96
97impl ZkProofData<GroupedCiphertext3HandlesValidityProofContext>
98    for GroupedCiphertext3HandlesValidityProofData
99{
100    const PROOF_TYPE: ProofType = ProofType::GroupedCiphertext3HandlesValidity;
101
102    fn context_data(&self) -> &GroupedCiphertext3HandlesValidityProofContext {
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 auditor_pubkey = self.context.auditor_pubkey.try_into()?;
113        let grouped_ciphertext: GroupedElGamalCiphertext<3> =
114            self.context.grouped_ciphertext.try_into()?;
115
116        let source_handle = grouped_ciphertext.handles.first().unwrap();
117        let destination_handle = grouped_ciphertext.handles.get(1).unwrap();
118        let auditor_handle = grouped_ciphertext.handles.get(2).unwrap();
119
120        let proof: GroupedCiphertext3HandlesValidityProof = self.proof.try_into()?;
121
122        proof
123            .verify(
124                &grouped_ciphertext.commitment,
125                &source_pubkey,
126                &destination_pubkey,
127                &auditor_pubkey,
128                source_handle,
129                destination_handle,
130                auditor_handle,
131                &mut transcript,
132            )
133            .map_err(|e| e.into())
134    }
135}
136
137#[cfg(not(target_os = "solana"))]
138impl GroupedCiphertext3HandlesValidityProofContext {
139    fn new_transcript(&self) -> Transcript {
140        let mut transcript = Transcript::new(b"GroupedCiphertext3HandlesValidityProof");
141
142        transcript.append_pubkey(b"source-pubkey", &self.source_pubkey);
143        transcript.append_pubkey(b"destination-pubkey", &self.destination_pubkey);
144        transcript.append_pubkey(b"auditor-pubkey", &self.auditor_pubkey);
145        transcript
146            .append_grouped_ciphertext_3_handles(b"grouped-ciphertext", &self.grouped_ciphertext);
147
148        transcript
149    }
150}
151
152#[cfg(test)]
153mod test {
154    use {
155        super::*,
156        crate::encryption::{elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal},
157    };
158
159    #[test]
160    fn test_ciphertext_validity_proof_instruction_correctness() {
161        let source_keypair = ElGamalKeypair::new_rand();
162        let source_pubkey = source_keypair.pubkey();
163
164        let destination_keypair = ElGamalKeypair::new_rand();
165        let destination_pubkey = destination_keypair.pubkey();
166
167        let auditor_keypair = ElGamalKeypair::new_rand();
168        let auditor_pubkey = auditor_keypair.pubkey();
169
170        let amount: u64 = 55;
171        let opening = PedersenOpening::new_rand();
172        let grouped_ciphertext = GroupedElGamal::encrypt_with(
173            [source_pubkey, destination_pubkey, auditor_pubkey],
174            amount,
175            &opening,
176        );
177
178        let proof_data = GroupedCiphertext3HandlesValidityProofData::new(
179            source_pubkey,
180            destination_pubkey,
181            auditor_pubkey,
182            &grouped_ciphertext,
183            amount,
184            &opening,
185        )
186        .unwrap();
187
188        assert!(proof_data.verify_proof().is_ok());
189    }
190}