solana_zk_sdk/zk_elgamal_proof_program/proof_data/
zero_ciphertext.rs

1//! The zero-ciphertext proof instruction.
2//!
3//! A zero-ciphertext proof is defined with respect to a twisted ElGamal ciphertext. The proof
4//! certifies that a given ciphertext encrypts the message 0 in the field (`Scalar::zero()`). To
5//! generate the proof, a prover must provide the decryption key for the ciphertext.
6
7#[cfg(target_arch = "wasm32")]
8use wasm_bindgen::prelude::*;
9#[cfg(not(target_os = "solana"))]
10use {
11    crate::{
12        encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair},
13        sigma_proofs::zero_ciphertext::ZeroCiphertextProof,
14        zk_elgamal_proof_program::{
15            errors::{ProofGenerationError, ProofVerificationError},
16            proof_data::errors::ProofDataError,
17        },
18    },
19    bytemuck::bytes_of,
20    merlin::Transcript,
21    std::convert::TryInto,
22};
23use {
24    crate::{
25        encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
26        sigma_proofs::pod::PodZeroCiphertextProof,
27        zk_elgamal_proof_program::proof_data::{pod::impl_wasm_to_bytes, ProofType, ZkProofData},
28    },
29    bytemuck_derive::{Pod, Zeroable},
30};
31
32/// The instruction data that is needed for the `ProofInstruction::ZeroCiphertext` instruction.
33///
34/// It includes the cryptographic proof as well as the context data information needed to verify
35/// the proof.
36#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
37#[derive(Clone, Copy, Pod, Zeroable)]
38#[repr(C)]
39pub struct ZeroCiphertextProofData {
40    /// The context data for the zero-ciphertext proof
41    pub context: ZeroCiphertextProofContext, // 96 bytes
42
43    /// Proof that the ciphertext is zero
44    pub proof: PodZeroCiphertextProof, // 96 bytes
45}
46
47/// The context data needed to verify a zero-ciphertext proof.
48#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
49#[derive(Clone, Copy, Pod, Zeroable)]
50#[repr(C)]
51pub struct ZeroCiphertextProofContext {
52    /// The ElGamal pubkey associated with the ElGamal ciphertext
53    pub pubkey: PodElGamalPubkey, // 32 bytes
54
55    /// The ElGamal ciphertext that encrypts zero
56    pub ciphertext: PodElGamalCiphertext, // 64 bytes
57}
58
59#[cfg(not(target_os = "solana"))]
60#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
61impl ZeroCiphertextProofData {
62    pub fn new(
63        keypair: &ElGamalKeypair,
64        ciphertext: &ElGamalCiphertext,
65    ) -> Result<Self, ProofGenerationError> {
66        let pod_pubkey = PodElGamalPubkey(keypair.pubkey().into());
67        let pod_ciphertext = PodElGamalCiphertext(ciphertext.to_bytes());
68
69        let context = ZeroCiphertextProofContext {
70            pubkey: pod_pubkey,
71            ciphertext: pod_ciphertext,
72        };
73
74        let mut transcript = context.new_transcript();
75        let proof = ZeroCiphertextProof::new(keypair, ciphertext, &mut transcript).into();
76
77        Ok(ZeroCiphertextProofData { context, proof })
78    }
79}
80
81impl_wasm_to_bytes!(TYPE = ZeroCiphertextProofData);
82
83impl ZkProofData<ZeroCiphertextProofContext> for ZeroCiphertextProofData {
84    const PROOF_TYPE: ProofType = ProofType::ZeroCiphertext;
85
86    fn context_data(&self) -> &ZeroCiphertextProofContext {
87        &self.context
88    }
89
90    #[cfg(not(target_os = "solana"))]
91    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
92        let mut transcript = self.context.new_transcript();
93        let pubkey = self.context.pubkey.try_into()?;
94        let ciphertext = self.context.ciphertext.try_into()?;
95        let proof: ZeroCiphertextProof = self.proof.try_into()?;
96        proof
97            .verify(&pubkey, &ciphertext, &mut transcript)
98            .map_err(|e| e.into())
99    }
100}
101
102#[allow(non_snake_case)]
103#[cfg(not(target_os = "solana"))]
104impl ZeroCiphertextProofContext {
105    fn new_transcript(&self) -> Transcript {
106        let mut transcript = Transcript::new(b"zero-ciphertext-instruction");
107
108        transcript.append_message(b"pubkey", bytes_of(&self.pubkey));
109        transcript.append_message(b"ciphertext", bytes_of(&self.ciphertext));
110
111        transcript
112    }
113}
114
115impl_wasm_to_bytes!(TYPE = ZeroCiphertextProofContext);
116
117#[cfg(test)]
118mod test {
119    use super::*;
120
121    #[test]
122    fn test_zero_ciphertext_proof_instruction_correctness() {
123        let keypair = ElGamalKeypair::new_rand();
124
125        // general case: encryption of 0
126        let ciphertext = keypair.pubkey().encrypt(0_u64);
127        let zero_ciphertext_proof_data =
128            ZeroCiphertextProofData::new(&keypair, &ciphertext).unwrap();
129        assert!(zero_ciphertext_proof_data.verify_proof().is_ok());
130
131        // general case: encryption of > 0
132        let ciphertext = keypair.pubkey().encrypt(1_u64);
133        let zero_ciphertext_proof_data =
134            ZeroCiphertextProofData::new(&keypair, &ciphertext).unwrap();
135        assert!(zero_ciphertext_proof_data.verify_proof().is_err());
136    }
137}