diff --git a/clients/rust-legacy/tests/confidential_mint_burn.rs b/clients/rust-legacy/tests/confidential_mint_burn.rs index d549f89d6..6c170f8f3 100644 --- a/clients/rust-legacy/tests/confidential_mint_burn.rs +++ b/clients/rust-legacy/tests/confidential_mint_burn.rs @@ -278,6 +278,24 @@ async fn confidential_mint_burn_rotate_supply_elgamal_pubkey_with_option( let new_supply_elgamal_keypair = ElGamalKeypair::new_rand(); let new_supply_elgamal_pubkey = new_supply_elgamal_keypair.pubkey(); + let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await; + let mint_amount = 120; + + mint_with_option( + &token, + &mint_authority.pubkey(), + &alice_meta.token_account, + mint_amount, + &supply_elgamal_keypair, + alice_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + &supply_aes_key, + &[&mint_authority], + option, + ) + .await + .unwrap(); + rotate_supply_elgamal_pubkey( &token, &mint_authority.pubkey(), @@ -306,28 +324,9 @@ async fn confidential_mint_burn_rotate_supply_elgamal_pubkey_with_option( confidential_supply .decrypt_u32(new_supply_elgamal_keypair.secret()) .unwrap(), - 0 + mint_amount ); - // check that rotation fails when pending burn is non-zero - let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await; - let mint_amount = 120; - - mint_with_option( - &token, - &mint_authority.pubkey(), - &alice_meta.token_account, - mint_amount, - &new_supply_elgamal_keypair, - alice_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &supply_aes_key, - &[&mint_authority], - option, - ) - .await - .unwrap(); - token .confidential_transfer_apply_pending_balance( &alice_meta.token_account, diff --git a/clients/rust-legacy/tests/confidential_transfer.rs b/clients/rust-legacy/tests/confidential_transfer.rs index 37b62b6c7..9e18981d2 100644 --- a/clients/rust-legacy/tests/confidential_transfer.rs +++ b/clients/rust-legacy/tests/confidential_transfer.rs @@ -621,9 +621,37 @@ async fn confidential_transfer_empty_account_with_option(option: ConfidentialTra .await .unwrap(); - let TokenContext { token, alice, .. } = context.token_context.unwrap(); + let TokenContext { + token, + alice, + decimals, + .. + } = context.token_context.unwrap(); let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; + token + .confidential_transfer_deposit( + &alice_meta.token_account, + &alice.pubkey(), + 0, + decimals, + &[&alice], + ) + .await + .unwrap(); + + token + .confidential_transfer_apply_pending_balance( + &alice_meta.token_account, + &alice.pubkey(), + None, + alice_meta.elgamal_keypair.secret(), + &alice_meta.aes_key, + &[&alice], + ) + .await + .unwrap(); + empty_account_with_option( &token, &alice_meta.token_account, diff --git a/clients/rust-legacy/tests/confidential_transfer_fee.rs b/clients/rust-legacy/tests/confidential_transfer_fee.rs index 227c4e7ec..cdedd018d 100644 --- a/clients/rust-legacy/tests/confidential_transfer_fee.rs +++ b/clients/rust-legacy/tests/confidential_transfer_fee.rs @@ -628,34 +628,6 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_mint_with_option( .await .unwrap(); - let new_decryptable_available_balance = alice_meta.aes_key.encrypt(0); - token - .confidential_transfer_withdraw_withheld_tokens_from_mint( - &alice_meta.token_account, - &withdraw_withheld_authority.pubkey(), - None, - None, - &withdraw_withheld_authority_elgamal_keypair, - alice_meta.elgamal_keypair.pubkey(), - &new_decryptable_available_balance.into(), - &[&withdraw_withheld_authority], - ) - .await - .unwrap(); - - // withheld fees are not harvested to mint yet - alice_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 0, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - token .confidential_transfer_harvest_withheld_tokens_to_mint(&[&bob_meta.token_account]) .await diff --git a/confidential/ciphertext-arithmetic/src/lib.rs b/confidential/ciphertext-arithmetic/src/lib.rs index a61b0a4de..0b35caa4a 100644 --- a/confidential/ciphertext-arithmetic/src/lib.rs +++ b/confidential/ciphertext-arithmetic/src/lib.rs @@ -4,7 +4,7 @@ use { ristretto::{add_ristretto, multiply_ristretto, subtract_ristretto, PodRistrettoPoint}, scalar::PodScalar, }, - solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, + solana_zk_sdk::encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, }; const SHIFT_BITS: usize = 16; @@ -14,6 +14,11 @@ const G: PodRistrettoPoint = PodRistrettoPoint([ 130, 221, 141, 182, 166, 89, 69, 224, 141, 45, 118, ]); +const H: PodRistrettoPoint = PodRistrettoPoint([ + 140, 146, 64, 180, 86, 169, 230, 220, 101, 195, 119, 161, 4, 141, 116, 95, 148, 160, 140, 219, + 127, 68, 203, 205, 123, 70, 243, 64, 72, 135, 17, 52, +]); + /// Add two ElGamal ciphertexts pub fn add( left_ciphertext: &PodElGamalCiphertext, @@ -132,6 +137,28 @@ pub fn add_to(ciphertext: &PodElGamalCiphertext, amount: u64) -> Option Option { + let amount_scalar = u64_to_scalar(amount); + let amount_point = multiply_ristretto(&amount_scalar, &G)?; + let amount_point_with_offset = add_ristretto(&amount_point, &H)?; + let pubkey_point = elgamal_pubkey_to_ristretto(pubkey); + + let (commitment, handle) = elgamal_ciphertext_to_ristretto(ciphertext); + + let result_commitment = add_ristretto(&commitment, &amount_point_with_offset)?; + let result_handle = add_ristretto(&handle, &pubkey_point)?; + + Some(ristretto_to_elgamal_ciphertext( + &result_commitment, + &result_handle, + )) +} + /// Subtract a constant amount to a ciphertext pub fn subtract_from( ciphertext: &PodElGamalCiphertext, @@ -154,6 +181,12 @@ fn u64_to_scalar(amount: u64) -> PodScalar { PodScalar(amount_bytes) } +/// Convert a `PodElGamalPubkey` into `PodRistrettoPoint` +fn elgamal_pubkey_to_ristretto(pubkey: &PodElGamalPubkey) -> PodRistrettoPoint { + let bytes = bytes_of(pubkey); + PodRistrettoPoint(bytes.try_into().unwrap()) +} + /// Convert a `PodElGamalCiphertext` into a tuple of commitment and decrypt /// handle `PodRistrettoPoint` fn elgamal_ciphertext_to_ristretto( diff --git a/interface/src/extension/confidential_transfer/instruction.rs b/interface/src/extension/confidential_transfer/instruction.rs index 325ea6204..827703778 100644 --- a/interface/src/extension/confidential_transfer/instruction.rs +++ b/interface/src/extension/confidential_transfer/instruction.rs @@ -134,7 +134,8 @@ pub enum ConfidentialTransferInstruction { /// instruction is not required prior to account closing if no /// instructions beyond /// `ConfidentialTransferInstruction::ConfigureAccount` have affected the - /// token account. + /// token account. Furthermore, if the available balance is already + /// empty, attempting to execute this instruction will fail. /// /// In order for this instruction to be successfully processed, it must be /// accompanied by the `VerifyZeroCiphertext` instruction of the diff --git a/program/src/extension/confidential_transfer/processor.rs b/program/src/extension/confidential_transfer/processor.rs index 3aa802c3d..e22c9e41d 100644 --- a/program/src/extension/confidential_transfer/processor.rs +++ b/program/src/extension/confidential_transfer/processor.rs @@ -474,22 +474,22 @@ fn process_deposit( // A deposit amount must be a 48-bit number let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; - // Prevent unnecessary ciphertext arithmetic syscalls if `amount_lo` or - // `amount_hi` is zero - if amount_lo > 0 { - confidential_transfer_account.pending_balance_lo = ciphertext_arithmetic::add_to( - &confidential_transfer_account.pending_balance_lo, - amount_lo, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; - } - if amount_hi > 0 { - confidential_transfer_account.pending_balance_hi = ciphertext_arithmetic::add_to( - &confidential_transfer_account.pending_balance_hi, - amount_hi, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; - } + // The ZK ElGamal Proof program does not accept all-zero ciphertext + // on ciphertext-commitment equality proof. + // Use ciphertext arithmetic with offset to prevent all-zero ciphertext + // from ocurring when a balance is deposited and immediately withdrawn + confidential_transfer_account.pending_balance_lo = ciphertext_arithmetic::add_to_with_offset( + &confidential_transfer_account.elgamal_pubkey, + &confidential_transfer_account.pending_balance_lo, + amount_lo, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + confidential_transfer_account.pending_balance_hi = ciphertext_arithmetic::add_to_with_offset( + &confidential_transfer_account.elgamal_pubkey, + &confidential_transfer_account.pending_balance_hi, + amount_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; confidential_transfer_account.increment_pending_balance_credit_counter()?;