gorb_ctpg/
transfer.rs

1use {
2    crate::{
3        encryption::TransferAmountCiphertext, errors::TokenProofGenerationError,
4        try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext,
5        REMAINING_BALANCE_BIT_LENGTH, TRANSFER_AMOUNT_HI_BITS, TRANSFER_AMOUNT_LO_BITS,
6    },
7    solana_zk_sdk::{
8        encryption::{
9            auth_encryption::{AeCiphertext, AeKey},
10            elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
11            pedersen::Pedersen,
12        },
13        zk_elgamal_proof_program::proof_data::{
14            BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
15            CiphertextCommitmentEqualityProofData, ZkProofData,
16        },
17    },
18};
19
20/// The padding bit length in range proofs that are used for a confidential
21/// token transfer
22const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
23
24/// The proof data required for a confidential transfer instruction when the
25/// mint is not extended for fees
26pub struct TransferProofData {
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 transfer_split_proof_data(
34    current_available_balance: &ElGamalCiphertext,
35    current_decryptable_available_balance: &AeCiphertext,
36    transfer_amount: u64,
37    source_elgamal_keypair: &ElGamalKeypair,
38    aes_key: &AeKey,
39    destination_elgamal_pubkey: &ElGamalPubkey,
40    auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
41) -> Result<TransferProofData, 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 transfer amount into the low and high bit components
46    let (transfer_amount_lo, transfer_amount_hi) =
47        try_split_u64(transfer_amount, TRANSFER_AMOUNT_LO_BITS)
48            .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
49
50    // Encrypt the `lo` and `hi` transfer amounts
51    let (transfer_amount_grouped_ciphertext_lo, transfer_amount_opening_lo) =
52        TransferAmountCiphertext::new(
53            transfer_amount_lo,
54            source_elgamal_keypair.pubkey(),
55            destination_elgamal_pubkey,
56            auditor_elgamal_pubkey,
57        );
58
59    let (transfer_amount_grouped_ciphertext_hi, transfer_amount_opening_hi) =
60        TransferAmountCiphertext::new(
61            transfer_amount_hi,
62            source_elgamal_keypair.pubkey(),
63            destination_elgamal_pubkey,
64            auditor_elgamal_pubkey,
65        );
66
67    // Decrypt the current available balance at the source
68    let current_decrypted_available_balance = current_decryptable_available_balance
69        .decrypt(aes_key)
70        .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
71
72    // Compute the remaining balance at the source
73    let new_decrypted_available_balance = current_decrypted_available_balance
74        .checked_sub(transfer_amount)
75        .ok_or(TokenProofGenerationError::NotEnoughFunds)?;
76
77    // Create a new Pedersen commitment for the remaining balance at the source
78    let (new_available_balance_commitment, new_source_opening) =
79        Pedersen::new(new_decrypted_available_balance);
80
81    // Compute the remaining balance at the source as ElGamal ciphertexts
82    let transfer_amount_source_ciphertext_lo = transfer_amount_grouped_ciphertext_lo
83        .0
84        .to_elgamal_ciphertext(0)
85        .unwrap();
86    let transfer_amount_source_ciphertext_hi = transfer_amount_grouped_ciphertext_hi
87        .0
88        .to_elgamal_ciphertext(0)
89        .unwrap();
90
91    #[allow(clippy::arithmetic_side_effects)]
92    let new_available_balance_ciphertext = current_available_balance
93        - try_combine_lo_hi_ciphertexts(
94            &transfer_amount_source_ciphertext_lo,
95            &transfer_amount_source_ciphertext_hi,
96            TRANSFER_AMOUNT_LO_BITS,
97        )
98        .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
99
100    // generate equality proof data
101    let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
102        source_elgamal_keypair,
103        &new_available_balance_ciphertext,
104        &new_available_balance_commitment,
105        &new_source_opening,
106        new_decrypted_available_balance,
107    )
108    .map_err(TokenProofGenerationError::from)?;
109
110    // generate ciphertext validity data
111    let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
112        source_elgamal_keypair.pubkey(),
113        destination_elgamal_pubkey,
114        auditor_elgamal_pubkey,
115        &transfer_amount_grouped_ciphertext_lo.0,
116        &transfer_amount_grouped_ciphertext_hi.0,
117        transfer_amount_lo,
118        transfer_amount_hi,
119        &transfer_amount_opening_lo,
120        &transfer_amount_opening_hi,
121    )
122    .map_err(TokenProofGenerationError::from)?;
123
124    let transfer_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data
125        .context_data()
126        .grouped_ciphertext_lo
127        .try_extract_ciphertext(2)
128        .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
129
130    let transfer_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data
131        .context_data()
132        .grouped_ciphertext_hi
133        .try_extract_ciphertext(2)
134        .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
135
136    let ciphertext_validity_proof_data_with_ciphertext =
137        CiphertextValidityProofWithAuditorCiphertext {
138            proof_data: ciphertext_validity_proof_data,
139            ciphertext_lo: transfer_amount_auditor_ciphertext_lo,
140            ciphertext_hi: transfer_amount_auditor_ciphertext_hi,
141        };
142
143    // generate range proof data
144    let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
145    let range_proof_data = BatchedRangeProofU128Data::new(
146        vec![
147            &new_available_balance_commitment,
148            transfer_amount_grouped_ciphertext_lo.get_commitment(),
149            transfer_amount_grouped_ciphertext_hi.get_commitment(),
150            &padding_commitment,
151        ],
152        vec![
153            new_decrypted_available_balance,
154            transfer_amount_lo,
155            transfer_amount_hi,
156            0,
157        ],
158        vec![
159            REMAINING_BALANCE_BIT_LENGTH,
160            TRANSFER_AMOUNT_LO_BITS,
161            TRANSFER_AMOUNT_HI_BITS,
162            RANGE_PROOF_PADDING_BIT_LENGTH,
163        ],
164        vec![
165            &new_source_opening,
166            &transfer_amount_opening_lo,
167            &transfer_amount_opening_hi,
168            &padding_opening,
169        ],
170    )
171    .map_err(TokenProofGenerationError::from)?;
172
173    Ok(TransferProofData {
174        equality_proof_data,
175        ciphertext_validity_proof_data_with_ciphertext,
176        range_proof_data,
177    })
178}