solana_zk_token_sdk/instruction/
ciphertext_commitment_equality.rs

1//! The ciphertext-commitment equality proof instruction.
2//!
3//! A ciphertext-commitment equality proof is defined with respect to a twisted ElGamal ciphertext
4//! and a Pedersen commitment. The proof certifies that a given ciphertext and a commitment pair
5//! encrypts/encodes the same message. To generate the proof, a prover must provide the decryption
6//! key for the first ciphertext and the Pedersen opening for the commitment.
7
8#[cfg(not(target_os = "solana"))]
9use {
10    crate::{
11        encryption::{
12            elgamal::{ElGamalCiphertext, ElGamalKeypair},
13            pedersen::{PedersenCommitment, PedersenOpening},
14        },
15        errors::{ProofGenerationError, ProofVerificationError},
16        sigma_proofs::ciphertext_commitment_equality_proof::CiphertextCommitmentEqualityProof,
17        transcript::TranscriptProtocol,
18    },
19    merlin::Transcript,
20    std::convert::TryInto,
21};
22use {
23    crate::{
24        instruction::{ProofType, ZkProofData},
25        zk_token_elgamal::pod,
26    },
27    bytemuck_derive::{Pod, Zeroable},
28};
29/// The instruction data that is needed for the
30/// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction.
31///
32/// It includes the cryptographic proof as well as the context data information needed to verify
33/// the proof.
34#[derive(Clone, Copy, Pod, Zeroable)]
35#[repr(C)]
36pub struct CiphertextCommitmentEqualityProofData {
37    pub context: CiphertextCommitmentEqualityProofContext,
38    pub proof: pod::CiphertextCommitmentEqualityProof,
39}
40
41/// The context data needed to verify a ciphertext-commitment equality proof.
42#[derive(Clone, Copy, Pod, Zeroable)]
43#[repr(C)]
44pub struct CiphertextCommitmentEqualityProofContext {
45    /// The ElGamal pubkey
46    pub pubkey: pod::ElGamalPubkey, // 32 bytes
47
48    /// The ciphertext encrypted under the ElGamal pubkey
49    pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
50
51    /// The Pedersen commitment
52    pub commitment: pod::PedersenCommitment, // 32 bytes
53}
54
55#[cfg(not(target_os = "solana"))]
56impl CiphertextCommitmentEqualityProofData {
57    pub fn new(
58        keypair: &ElGamalKeypair,
59        ciphertext: &ElGamalCiphertext,
60        commitment: &PedersenCommitment,
61        opening: &PedersenOpening,
62        amount: u64,
63    ) -> Result<Self, ProofGenerationError> {
64        let context = CiphertextCommitmentEqualityProofContext {
65            pubkey: pod::ElGamalPubkey(keypair.pubkey().into()),
66            ciphertext: pod::ElGamalCiphertext(ciphertext.to_bytes()),
67            commitment: pod::PedersenCommitment(commitment.to_bytes()),
68        };
69        let mut transcript = context.new_transcript();
70        let proof = CiphertextCommitmentEqualityProof::new(
71            keypair,
72            ciphertext,
73            opening,
74            amount,
75            &mut transcript,
76        );
77        Ok(CiphertextCommitmentEqualityProofData {
78            context,
79            proof: proof.into(),
80        })
81    }
82}
83
84impl ZkProofData<CiphertextCommitmentEqualityProofContext>
85    for CiphertextCommitmentEqualityProofData
86{
87    const PROOF_TYPE: ProofType = ProofType::CiphertextCommitmentEquality;
88
89    fn context_data(&self) -> &CiphertextCommitmentEqualityProofContext {
90        &self.context
91    }
92
93    #[cfg(not(target_os = "solana"))]
94    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
95        let mut transcript = self.context.new_transcript();
96
97        let pubkey = self.context.pubkey.try_into()?;
98        let ciphertext = self.context.ciphertext.try_into()?;
99        let commitment = self.context.commitment.try_into()?;
100        let proof: CiphertextCommitmentEqualityProof = self.proof.try_into()?;
101
102        proof
103            .verify(&pubkey, &ciphertext, &commitment, &mut transcript)
104            .map_err(|e| e.into())
105    }
106}
107
108#[allow(non_snake_case)]
109#[cfg(not(target_os = "solana"))]
110impl CiphertextCommitmentEqualityProofContext {
111    fn new_transcript(&self) -> Transcript {
112        let mut transcript = Transcript::new(b"CtxtCommEqualityProof");
113        transcript.append_pubkey(b"pubkey", &self.pubkey);
114        transcript.append_ciphertext(b"ciphertext", &self.ciphertext);
115        transcript.append_commitment(b"commitment", &self.commitment);
116        transcript
117    }
118}
119
120#[cfg(test)]
121mod test {
122    use {
123        super::*,
124        crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen},
125    };
126
127    #[test]
128    fn test_ctxt_comm_equality_proof_correctness() {
129        let keypair = ElGamalKeypair::new_rand();
130        let amount: u64 = 55;
131        let ciphertext = keypair.pubkey().encrypt(amount);
132        let (commitment, opening) = Pedersen::new(amount);
133
134        let proof_data = CiphertextCommitmentEqualityProofData::new(
135            &keypair,
136            &ciphertext,
137            &commitment,
138            &opening,
139            amount,
140        )
141        .unwrap();
142
143        assert!(proof_data.verify_proof().is_ok());
144    }
145}