1use {
2 crate::{
3 encryption::BurnAmountCiphertext, errors::TokenProofGenerationError,
4 try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext,
5 },
6 solana_zk_sdk::{
7 encryption::{
8 auth_encryption::{AeCiphertext, AeKey},
9 elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
10 pedersen::Pedersen,
11 },
12 zk_elgamal_proof_program::proof_data::{
13 BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
14 CiphertextCommitmentEqualityProofData, ZkProofData,
15 },
16 },
17};
18
19const REMAINING_BALANCE_BIT_LENGTH: usize = 64;
20const BURN_AMOUNT_LO_BIT_LENGTH: usize = 16;
21const BURN_AMOUNT_HI_BIT_LENGTH: usize = 32;
22const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
24
25pub struct BurnProofData {
27 pub equality_proof_data: CiphertextCommitmentEqualityProofData,
28 pub ciphertext_validity_proof_data_with_ciphertext:
29 CiphertextValidityProofWithAuditorCiphertext,
30 pub range_proof_data: BatchedRangeProofU128Data,
31}
32
33pub fn burn_split_proof_data(
34 current_available_balance_ciphertext: &ElGamalCiphertext,
35 current_decryptable_available_balance: &AeCiphertext,
36 burn_amount: u64,
37 source_elgamal_keypair: &ElGamalKeypair,
38 source_aes_key: &AeKey,
39 auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
40 supply_elgamal_pubkey: &ElGamalPubkey,
41) -> Result<BurnProofData, TokenProofGenerationError> {
42 let default_auditor_pubkey = ElGamalPubkey::default();
43 let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or(&default_auditor_pubkey);
44
45 let (burn_amount_lo, burn_amount_hi) = try_split_u64(burn_amount, BURN_AMOUNT_LO_BIT_LENGTH)
47 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
48
49 let (burn_amount_ciphertext_lo, burn_amount_opening_lo) = BurnAmountCiphertext::new(
51 burn_amount_lo,
52 source_elgamal_keypair.pubkey(),
53 auditor_elgamal_pubkey,
54 supply_elgamal_pubkey,
55 );
56
57 let (burn_amount_ciphertext_hi, burn_amount_opening_hi) = BurnAmountCiphertext::new(
58 burn_amount_hi,
59 source_elgamal_keypair.pubkey(),
60 auditor_elgamal_pubkey,
61 supply_elgamal_pubkey,
62 );
63
64 let current_decrypted_available_balance = current_decryptable_available_balance
66 .decrypt(source_aes_key)
67 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
68
69 let burn_amount_ciphertext_source_lo = burn_amount_ciphertext_lo
71 .0
72 .to_elgamal_ciphertext(0)
73 .unwrap();
74 let burn_amount_ciphertext_source_hi = burn_amount_ciphertext_hi
75 .0
76 .to_elgamal_ciphertext(0)
77 .unwrap();
78
79 #[allow(clippy::arithmetic_side_effects)]
80 let new_available_balance_ciphertext = current_available_balance_ciphertext
81 - try_combine_lo_hi_ciphertexts(
82 &burn_amount_ciphertext_source_lo,
83 &burn_amount_ciphertext_source_hi,
84 BURN_AMOUNT_LO_BIT_LENGTH,
85 )
86 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
87
88 let remaining_balance = current_decrypted_available_balance
90 .checked_sub(burn_amount)
91 .ok_or(TokenProofGenerationError::NotEnoughFunds)?;
92
93 let (new_available_balance_commitment, new_available_balance_opening) =
94 Pedersen::new(remaining_balance);
95
96 let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
98 source_elgamal_keypair,
99 &new_available_balance_ciphertext,
100 &new_available_balance_commitment,
101 &new_available_balance_opening,
102 remaining_balance,
103 )
104 .map_err(TokenProofGenerationError::from)?;
105
106 let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
108 source_elgamal_keypair.pubkey(),
109 auditor_elgamal_pubkey,
110 supply_elgamal_pubkey,
111 &burn_amount_ciphertext_lo.0,
112 &burn_amount_ciphertext_hi.0,
113 burn_amount_lo,
114 burn_amount_hi,
115 &burn_amount_opening_lo,
116 &burn_amount_opening_hi,
117 )
118 .map_err(TokenProofGenerationError::from)?;
119
120 let burn_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data
121 .context_data()
122 .grouped_ciphertext_lo
123 .try_extract_ciphertext(2)
124 .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
125
126 let burn_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data
127 .context_data()
128 .grouped_ciphertext_hi
129 .try_extract_ciphertext(2)
130 .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
131
132 let ciphertext_validity_proof_data_with_ciphertext =
133 CiphertextValidityProofWithAuditorCiphertext {
134 proof_data: ciphertext_validity_proof_data,
135 ciphertext_lo: burn_amount_auditor_ciphertext_lo,
136 ciphertext_hi: burn_amount_auditor_ciphertext_hi,
137 };
138
139 let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
141 let range_proof_data = BatchedRangeProofU128Data::new(
142 vec![
143 &new_available_balance_commitment,
144 burn_amount_ciphertext_lo.get_commitment(),
145 burn_amount_ciphertext_hi.get_commitment(),
146 &padding_commitment,
147 ],
148 vec![remaining_balance, burn_amount_lo, burn_amount_hi, 0],
149 vec![
150 REMAINING_BALANCE_BIT_LENGTH,
151 BURN_AMOUNT_LO_BIT_LENGTH,
152 BURN_AMOUNT_HI_BIT_LENGTH,
153 RANGE_PROOF_PADDING_BIT_LENGTH,
154 ],
155 vec![
156 &new_available_balance_opening,
157 &burn_amount_opening_lo,
158 &burn_amount_opening_hi,
159 &padding_opening,
160 ],
161 )
162 .map_err(TokenProofGenerationError::from)?;
163
164 Ok(BurnProofData {
165 equality_proof_data,
166 ciphertext_validity_proof_data_with_ciphertext,
167 range_proof_data,
168 })
169}