solana_zk_token_sdk/instruction/grouped_ciphertext_validity/
handles_2.rs

1//! The grouped-ciphertext 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//! Currently, the grouped-ciphertext validity proof is restricted to ciphertexts with two handles.
9//! In accordance with the SPL Token program application, the first decryption handle associated
10//! with the proof is referred to as the "destination" handle and the second decryption handle is
11//! referred to as the "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::GroupedCiphertext2HandlesValidityProof,
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 `ProofInstruction::VerifyGroupedCiphertextValidity`
35/// 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 GroupedCiphertext2HandlesValidityProofData {
42    pub context: GroupedCiphertext2HandlesValidityProofContext,
43
44    pub proof: pod::GroupedCiphertext2HandlesValidityProof,
45}
46
47#[derive(Clone, Copy, Pod, Zeroable)]
48#[repr(C)]
49pub struct GroupedCiphertext2HandlesValidityProofContext {
50    pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
51
52    pub auditor_pubkey: pod::ElGamalPubkey, // 32 bytes
53
54    pub grouped_ciphertext: pod::GroupedElGamalCiphertext2Handles, // 96 bytes
55}
56
57#[cfg(not(target_os = "solana"))]
58impl GroupedCiphertext2HandlesValidityProofData {
59    pub fn new(
60        destination_pubkey: &ElGamalPubkey,
61        auditor_pubkey: &ElGamalPubkey,
62        grouped_ciphertext: &GroupedElGamalCiphertext<2>,
63        amount: u64,
64        opening: &PedersenOpening,
65    ) -> Result<Self, ProofGenerationError> {
66        let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
67        let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into());
68        let pod_grouped_ciphertext = (*grouped_ciphertext).into();
69
70        let context = GroupedCiphertext2HandlesValidityProofContext {
71            destination_pubkey: pod_destination_pubkey,
72            auditor_pubkey: pod_auditor_pubkey,
73            grouped_ciphertext: pod_grouped_ciphertext,
74        };
75
76        let mut transcript = context.new_transcript();
77
78        let proof = GroupedCiphertext2HandlesValidityProof::new(
79            (destination_pubkey, auditor_pubkey),
80            amount,
81            opening,
82            &mut transcript,
83        )
84        .into();
85
86        Ok(Self { context, proof })
87    }
88}
89
90impl ZkProofData<GroupedCiphertext2HandlesValidityProofContext>
91    for GroupedCiphertext2HandlesValidityProofData
92{
93    const PROOF_TYPE: ProofType = ProofType::GroupedCiphertext2HandlesValidity;
94
95    fn context_data(&self) -> &GroupedCiphertext2HandlesValidityProofContext {
96        &self.context
97    }
98
99    #[cfg(not(target_os = "solana"))]
100    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
101        let mut transcript = self.context.new_transcript();
102
103        let destination_pubkey = self.context.destination_pubkey.try_into()?;
104        let auditor_pubkey = self.context.auditor_pubkey.try_into()?;
105        let grouped_ciphertext: GroupedElGamalCiphertext<2> =
106            self.context.grouped_ciphertext.try_into()?;
107
108        let destination_handle = grouped_ciphertext.handles.first().unwrap();
109        let auditor_handle = grouped_ciphertext.handles.get(1).unwrap();
110
111        let proof: GroupedCiphertext2HandlesValidityProof = self.proof.try_into()?;
112
113        proof
114            .verify(
115                &grouped_ciphertext.commitment,
116                (&destination_pubkey, &auditor_pubkey),
117                (destination_handle, auditor_handle),
118                &mut transcript,
119            )
120            .map_err(|e| e.into())
121    }
122}
123
124#[cfg(not(target_os = "solana"))]
125impl GroupedCiphertext2HandlesValidityProofContext {
126    fn new_transcript(&self) -> Transcript {
127        let mut transcript = Transcript::new(b"CiphertextValidityProof");
128
129        transcript.append_pubkey(b"destination-pubkey", &self.destination_pubkey);
130        transcript.append_pubkey(b"auditor-pubkey", &self.auditor_pubkey);
131        transcript
132            .append_grouped_ciphertext_2_handles(b"grouped-ciphertext", &self.grouped_ciphertext);
133
134        transcript
135    }
136}
137
138#[cfg(test)]
139mod test {
140    use {
141        super::*,
142        crate::encryption::{elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal},
143    };
144
145    #[test]
146    fn test_ciphertext_validity_proof_instruction_correctness() {
147        let destination_keypair = ElGamalKeypair::new_rand();
148        let destination_pubkey = destination_keypair.pubkey();
149
150        let auditor_keypair = ElGamalKeypair::new_rand();
151        let auditor_pubkey = auditor_keypair.pubkey();
152
153        let amount: u64 = 55;
154        let opening = PedersenOpening::new_rand();
155        let grouped_ciphertext =
156            GroupedElGamal::encrypt_with([destination_pubkey, auditor_pubkey], amount, &opening);
157
158        let proof_data = GroupedCiphertext2HandlesValidityProofData::new(
159            destination_pubkey,
160            auditor_pubkey,
161            &grouped_ciphertext,
162            amount,
163            &opening,
164        )
165        .unwrap();
166
167        assert!(proof_data.verify_proof().is_ok());
168    }
169}