safe_zk_token_sdk/instruction/
withdraw_withheld.rs

1use {
2    crate::zk_token_elgamal::pod,
3    bytemuck::{Pod, Zeroable},
4};
5#[cfg(not(target_os = "solana"))]
6use {
7    crate::{
8        encryption::{
9            elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
10            pedersen::PedersenOpening,
11        },
12        errors::ProofError,
13        instruction::Verifiable,
14        sigma_proofs::equality_proof::CtxtCtxtEqualityProof,
15        transcript::TranscriptProtocol,
16    },
17    merlin::Transcript,
18    std::convert::TryInto,
19};
20
21/// This struct includes the cryptographic proof *and* the account data information needed to verify
22/// the proof
23///
24/// - The pre-instruction should call WithdrawData::verify_proof(&self)
25/// - The actual program should check that `current_ct` is consistent with what is
26///   currently stored in the confidential token account TODO: update this statement
27///
28#[derive(Clone, Copy, Pod, Zeroable)]
29#[repr(C)]
30pub struct WithdrawWithheldTokensData {
31    pub withdraw_withheld_authority_pubkey: pod::ElGamalPubkey,
32
33    pub destination_pubkey: pod::ElGamalPubkey,
34
35    pub withdraw_withheld_authority_ciphertext: pod::ElGamalCiphertext,
36
37    pub destination_ciphertext: pod::ElGamalCiphertext,
38
39    pub proof: WithdrawWithheldTokensProof,
40}
41
42#[cfg(not(target_os = "solana"))]
43impl WithdrawWithheldTokensData {
44    pub fn new(
45        withdraw_withheld_authority_keypair: &ElGamalKeypair,
46        destination_pubkey: &ElGamalPubkey,
47        withdraw_withheld_authority_ciphertext: &ElGamalCiphertext,
48        amount: u64,
49    ) -> Result<Self, ProofError> {
50        // encrypt withdraw amount under destination public key
51        let destination_opening = PedersenOpening::new_rand();
52        let destination_ciphertext = destination_pubkey.encrypt_with(amount, &destination_opening);
53
54        let pod_withdraw_withheld_authority_pubkey =
55            pod::ElGamalPubkey(withdraw_withheld_authority_keypair.public.to_bytes());
56        let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
57        let pod_withdraw_withheld_authority_ciphertext =
58            pod::ElGamalCiphertext(withdraw_withheld_authority_ciphertext.to_bytes());
59        let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
60
61        let mut transcript = WithdrawWithheldTokensProof::transcript_new(
62            &pod_withdraw_withheld_authority_pubkey,
63            &pod_destination_pubkey,
64            &pod_withdraw_withheld_authority_ciphertext,
65            &pod_destination_ciphertext,
66        );
67
68        let proof = WithdrawWithheldTokensProof::new(
69            withdraw_withheld_authority_keypair,
70            destination_pubkey,
71            withdraw_withheld_authority_ciphertext,
72            amount,
73            &destination_opening,
74            &mut transcript,
75        );
76
77        Ok(Self {
78            withdraw_withheld_authority_pubkey: pod_withdraw_withheld_authority_pubkey,
79            destination_pubkey: pod_destination_pubkey,
80            withdraw_withheld_authority_ciphertext: pod_withdraw_withheld_authority_ciphertext,
81            destination_ciphertext: pod_destination_ciphertext,
82            proof,
83        })
84    }
85}
86
87#[cfg(not(target_os = "solana"))]
88impl Verifiable for WithdrawWithheldTokensData {
89    fn verify(&self) -> Result<(), ProofError> {
90        let mut transcript = WithdrawWithheldTokensProof::transcript_new(
91            &self.withdraw_withheld_authority_pubkey,
92            &self.destination_pubkey,
93            &self.withdraw_withheld_authority_ciphertext,
94            &self.destination_ciphertext,
95        );
96
97        let withdraw_withheld_authority_pubkey =
98            self.withdraw_withheld_authority_pubkey.try_into()?;
99        let destination_pubkey = self.destination_pubkey.try_into()?;
100        let withdraw_withheld_authority_ciphertext =
101            self.withdraw_withheld_authority_ciphertext.try_into()?;
102        let destination_ciphertext = self.destination_ciphertext.try_into()?;
103
104        self.proof.verify(
105            &withdraw_withheld_authority_pubkey,
106            &destination_pubkey,
107            &withdraw_withheld_authority_ciphertext,
108            &destination_ciphertext,
109            &mut transcript,
110        )
111    }
112}
113
114/// This struct represents the cryptographic proof component that certifies the account's solvency
115/// for withdrawal
116#[derive(Clone, Copy, Pod, Zeroable)]
117#[repr(C)]
118#[allow(non_snake_case)]
119pub struct WithdrawWithheldTokensProof {
120    pub proof: pod::CtxtCtxtEqualityProof,
121}
122
123#[allow(non_snake_case)]
124#[cfg(not(target_os = "solana"))]
125impl WithdrawWithheldTokensProof {
126    fn transcript_new(
127        withdraw_withheld_authority_pubkey: &pod::ElGamalPubkey,
128        destination_pubkey: &pod::ElGamalPubkey,
129        withdraw_withheld_authority_ciphertext: &pod::ElGamalCiphertext,
130        destination_ciphertext: &pod::ElGamalCiphertext,
131    ) -> Transcript {
132        let mut transcript = Transcript::new(b"WithdrawWithheldTokensProof");
133
134        transcript.append_pubkey(
135            b"withdraw-withheld-authority-pubkey",
136            withdraw_withheld_authority_pubkey,
137        );
138        transcript.append_pubkey(b"dest-pubkey", destination_pubkey);
139
140        transcript.append_ciphertext(
141            b"ciphertext-withdraw-withheld-authority",
142            withdraw_withheld_authority_ciphertext,
143        );
144        transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext);
145
146        transcript
147    }
148
149    pub fn new(
150        withdraw_withheld_authority_keypair: &ElGamalKeypair,
151        destination_pubkey: &ElGamalPubkey,
152        withdraw_withheld_authority_ciphertext: &ElGamalCiphertext,
153        amount: u64,
154        destination_opening: &PedersenOpening,
155        transcript: &mut Transcript,
156    ) -> Self {
157        let equality_proof = CtxtCtxtEqualityProof::new(
158            withdraw_withheld_authority_keypair,
159            destination_pubkey,
160            withdraw_withheld_authority_ciphertext,
161            amount,
162            destination_opening,
163            transcript,
164        );
165
166        Self {
167            proof: equality_proof.into(),
168        }
169    }
170
171    pub fn verify(
172        &self,
173        source_pubkey: &ElGamalPubkey,
174        destination_pubkey: &ElGamalPubkey,
175        source_ciphertext: &ElGamalCiphertext,
176        destination_ciphertext: &ElGamalCiphertext,
177        transcript: &mut Transcript,
178    ) -> Result<(), ProofError> {
179        let proof: CtxtCtxtEqualityProof = self.proof.try_into()?;
180        proof.verify(
181            source_pubkey,
182            destination_pubkey,
183            source_ciphertext,
184            destination_ciphertext,
185            transcript,
186        )?;
187
188        Ok(())
189    }
190}
191
192#[cfg(test)]
193mod test {
194    use super::*;
195
196    #[test]
197    fn test_withdraw_withheld() {
198        let withdraw_withheld_authority_keypair = ElGamalKeypair::new_rand();
199        let dest_keypair = ElGamalKeypair::new_rand();
200
201        let amount: u64 = 0;
202        let withdraw_withheld_authority_ciphertext =
203            withdraw_withheld_authority_keypair.public.encrypt(amount);
204
205        let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
206            &withdraw_withheld_authority_keypair,
207            &dest_keypair.public,
208            &withdraw_withheld_authority_ciphertext,
209            amount,
210        )
211        .unwrap();
212
213        assert!(withdraw_withheld_tokens_data.verify().is_ok());
214
215        let amount: u64 = 55;
216        let withdraw_withheld_authority_ciphertext =
217            withdraw_withheld_authority_keypair.public.encrypt(amount);
218
219        let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
220            &withdraw_withheld_authority_keypair,
221            &dest_keypair.public,
222            &withdraw_withheld_authority_ciphertext,
223            amount,
224        )
225        .unwrap();
226
227        assert!(withdraw_withheld_tokens_data.verify().is_ok());
228
229        let amount = u64::max_value();
230        let withdraw_withheld_authority_ciphertext =
231            withdraw_withheld_authority_keypair.public.encrypt(amount);
232
233        let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
234            &withdraw_withheld_authority_keypair,
235            &dest_keypair.public,
236            &withdraw_withheld_authority_ciphertext,
237            amount,
238        )
239        .unwrap();
240
241        assert!(withdraw_withheld_tokens_data.verify().is_ok());
242    }
243}