solana_zk_token_sdk/instruction/
zero_balance.rs

1//! The zero-balance proof instruction.
2//!
3//! A zero-balance 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(not(target_os = "solana"))]
8use {
9    crate::{
10        encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair},
11        errors::{ProofGenerationError, ProofVerificationError},
12        sigma_proofs::zero_balance_proof::ZeroBalanceProof,
13        transcript::TranscriptProtocol,
14    },
15    merlin::Transcript,
16    std::convert::TryInto,
17};
18use {
19    crate::{
20        instruction::{ProofType, ZkProofData},
21        zk_token_elgamal::pod,
22    },
23    bytemuck_derive::{Pod, Zeroable},
24};
25
26/// The instruction data that is needed for the `ProofInstruction::ZeroBalance` instruction.
27///
28/// It includes the cryptographic proof as well as the context data information needed to verify
29/// the proof.
30#[derive(Clone, Copy, Pod, Zeroable)]
31#[repr(C)]
32pub struct ZeroBalanceProofData {
33    /// The context data for the zero-balance proof
34    pub context: ZeroBalanceProofContext, // 96 bytes
35
36    /// Proof that the source account available balance is zero
37    pub proof: pod::ZeroBalanceProof, // 96 bytes
38}
39
40/// The context data needed to verify a zero-balance proof.
41#[derive(Clone, Copy, Pod, Zeroable)]
42#[repr(C)]
43pub struct ZeroBalanceProofContext {
44    /// The source account ElGamal pubkey
45    pub pubkey: pod::ElGamalPubkey, // 32 bytes
46
47    /// The source account available balance in encrypted form
48    pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
49}
50
51#[cfg(not(target_os = "solana"))]
52impl ZeroBalanceProofData {
53    pub fn new(
54        keypair: &ElGamalKeypair,
55        ciphertext: &ElGamalCiphertext,
56    ) -> Result<Self, ProofGenerationError> {
57        let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().into());
58        let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
59
60        let context = ZeroBalanceProofContext {
61            pubkey: pod_pubkey,
62            ciphertext: pod_ciphertext,
63        };
64
65        let mut transcript = context.new_transcript();
66        let proof = ZeroBalanceProof::new(keypair, ciphertext, &mut transcript).into();
67
68        Ok(ZeroBalanceProofData { context, proof })
69    }
70}
71
72impl ZkProofData<ZeroBalanceProofContext> for ZeroBalanceProofData {
73    const PROOF_TYPE: ProofType = ProofType::ZeroBalance;
74
75    fn context_data(&self) -> &ZeroBalanceProofContext {
76        &self.context
77    }
78
79    #[cfg(not(target_os = "solana"))]
80    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
81        let mut transcript = self.context.new_transcript();
82        let pubkey = self.context.pubkey.try_into()?;
83        let ciphertext = self.context.ciphertext.try_into()?;
84        let proof: ZeroBalanceProof = self.proof.try_into()?;
85        proof
86            .verify(&pubkey, &ciphertext, &mut transcript)
87            .map_err(|e| e.into())
88    }
89}
90
91#[allow(non_snake_case)]
92#[cfg(not(target_os = "solana"))]
93impl ZeroBalanceProofContext {
94    fn new_transcript(&self) -> Transcript {
95        let mut transcript = Transcript::new(b"ZeroBalanceProof");
96
97        transcript.append_pubkey(b"pubkey", &self.pubkey);
98        transcript.append_ciphertext(b"ciphertext", &self.ciphertext);
99
100        transcript
101    }
102}
103
104#[cfg(test)]
105mod test {
106    use super::*;
107
108    #[test]
109    fn test_zero_balance_proof_instruction_correctness() {
110        let keypair = ElGamalKeypair::new_rand();
111
112        // general case: encryption of 0
113        let ciphertext = keypair.pubkey().encrypt(0_u64);
114        let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap();
115        assert!(zero_balance_proof_data.verify_proof().is_ok());
116
117        // general case: encryption of > 0
118        let ciphertext = keypair.pubkey().encrypt(1_u64);
119        let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap();
120        assert!(zero_balance_proof_data.verify_proof().is_err());
121    }
122}