safe_zk_token_sdk/instruction/
close_account.rs

1use {
2    crate::zk_token_elgamal::pod,
3    bytemuck::{Pod, Zeroable},
4};
5#[cfg(not(target_os = "solana"))]
6use {
7    crate::{
8        encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
9        errors::ProofError,
10        instruction::Verifiable,
11        sigma_proofs::zero_balance_proof::ZeroBalanceProof,
12        transcript::TranscriptProtocol,
13    },
14    merlin::Transcript,
15    std::convert::TryInto,
16};
17
18/// This struct includes the cryptographic proof *and* the account data information needed to verify
19/// the proof
20///
21/// - The pre-instruction should call CloseAccountData::verify_proof(&self)
22/// - The actual program should check that `balance` is consistent with what is
23///   currently stored in the confidential token account
24///
25#[derive(Clone, Copy, Pod, Zeroable)]
26#[repr(C)]
27pub struct CloseAccountData {
28    /// The source account ElGamal pubkey
29    pub pubkey: pod::ElGamalPubkey, // 32 bytes
30
31    /// The source account available balance in encrypted form
32    pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
33
34    /// Proof that the source account available balance is zero
35    pub proof: CloseAccountProof, // 96 bytes
36}
37
38#[cfg(not(target_os = "solana"))]
39impl CloseAccountData {
40    pub fn new(
41        keypair: &ElGamalKeypair,
42        ciphertext: &ElGamalCiphertext,
43    ) -> Result<Self, ProofError> {
44        let pod_pubkey = pod::ElGamalPubkey((&keypair.public).to_bytes());
45        let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
46
47        let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext);
48
49        let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript);
50
51        Ok(CloseAccountData {
52            pubkey: pod_pubkey,
53            ciphertext: pod_ciphertext,
54            proof,
55        })
56    }
57}
58
59#[cfg(not(target_os = "solana"))]
60impl Verifiable for CloseAccountData {
61    fn verify(&self) -> Result<(), ProofError> {
62        let mut transcript = CloseAccountProof::transcript_new(&self.pubkey, &self.ciphertext);
63
64        let pubkey = self.pubkey.try_into()?;
65        let ciphertext = self.ciphertext.try_into()?;
66        self.proof.verify(&pubkey, &ciphertext, &mut transcript)
67    }
68}
69
70/// This struct represents the cryptographic proof component that certifies that the encrypted
71/// balance is zero
72#[derive(Clone, Copy, Pod, Zeroable)]
73#[repr(C)]
74#[allow(non_snake_case)]
75pub struct CloseAccountProof {
76    pub proof: pod::ZeroBalanceProof,
77}
78
79#[allow(non_snake_case)]
80#[cfg(not(target_os = "solana"))]
81impl CloseAccountProof {
82    fn transcript_new(
83        pubkey: &pod::ElGamalPubkey,
84        ciphertext: &pod::ElGamalCiphertext,
85    ) -> Transcript {
86        let mut transcript = Transcript::new(b"CloseAccountProof");
87
88        transcript.append_pubkey(b"pubkey", pubkey);
89        transcript.append_ciphertext(b"ciphertext", ciphertext);
90
91        transcript
92    }
93
94    pub fn new(
95        keypair: &ElGamalKeypair,
96        ciphertext: &ElGamalCiphertext,
97        transcript: &mut Transcript,
98    ) -> Self {
99        let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
100
101        Self {
102            proof: proof.into(),
103        }
104    }
105
106    pub fn verify(
107        &self,
108        pubkey: &ElGamalPubkey,
109        ciphertext: &ElGamalCiphertext,
110        transcript: &mut Transcript,
111    ) -> Result<(), ProofError> {
112        let proof: ZeroBalanceProof = self.proof.try_into()?;
113        proof.verify(pubkey, ciphertext, transcript)?;
114
115        Ok(())
116    }
117}
118
119#[cfg(test)]
120mod test {
121    use super::*;
122
123    #[test]
124    fn test_close_account_correctness() {
125        let keypair = ElGamalKeypair::new_rand();
126
127        // general case: encryption of 0
128        let ciphertext = keypair.public.encrypt(0_u64);
129        let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
130        assert!(close_account_data.verify().is_ok());
131
132        // general case: encryption of > 0
133        let ciphertext = keypair.public.encrypt(1_u64);
134        let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
135        assert!(close_account_data.verify().is_err());
136    }
137}