Skip to main content

gorb_ctpg/
burn.rs

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;
22/// The padding bit length in range proofs to make the bit-length power-of-2
23const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
24
25/// The proof data required for a confidential burn instruction
26pub 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    // split the burn amount into low and high bits
46    let (burn_amount_lo, burn_amount_hi) = try_split_u64(burn_amount, BURN_AMOUNT_LO_BIT_LENGTH)
47        .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
48
49    // encrypt the burn amount under the source and auditor's ElGamal public key
50    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    // decrypt the current available balance at the source
65    let current_decrypted_available_balance = current_decryptable_available_balance
66        .decrypt(source_aes_key)
67        .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
68
69    // compute the remaining balance ciphertext
70    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    // compute the remaining balance at the source
89    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    // generate equality proof data
97    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    // generate ciphertext validity data
107    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    // generate range proof data
140    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}