Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 19 additions & 20 deletions clients/rust-legacy/tests/confidential_mint_burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand Down
30 changes: 29 additions & 1 deletion clients/rust-legacy/tests/confidential_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 0 additions & 28 deletions clients/rust-legacy/tests/confidential_transfer_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 34 additions & 1 deletion confidential/ciphertext-arithmetic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
]);
Comment on lines +17 to +20
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my layperson brain here -- is H typically the term for 0? If so, all good! If not, can you add a comment to that effect? (Assuming I've correctly understood this as 0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can definitely add a clarifying comment here (but let me do it in a follow-up 🙏 ).

H actually isn't 0. In Pedersen commitments, G and H are the two standard generator points on the curve. G is used for the token amount, and H is used for the randomness (blinding factor).

A commitment is mathematically calculated as (amount & G) + (randomness * H). Because we want to create a dummy ciphertext that encrypts an amount of 0 with a fixed randomness of 1 (so the resulting ciphertext isn't an all-zero identity point), the commitment part evaluates to 0 * G + 1 * H, which just leaves us with exactly H.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation!


/// Add two ElGamal ciphertexts
pub fn add(
left_ciphertext: &PodElGamalCiphertext,
Expand Down Expand Up @@ -132,6 +137,28 @@ pub fn add_to(ciphertext: &PodElGamalCiphertext, amount: u64) -> Option<PodElGam
Some(ristretto_to_elgamal_ciphertext(&result_commitment, &handle))
}

/// Add a constant amount to a ciphertext with a fixed offset
pub fn add_to_with_offset(
pubkey: &PodElGamalPubkey,
ciphertext: &PodElGamalCiphertext,
amount: u64,
) -> Option<PodElGamalCiphertext> {
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,
Expand All @@ -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())
}
Comment on lines +184 to +188
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layperson here again: I've never seen a conversion from the encryption key to a ristretto point in our code before -- is the idea to use that as the randomness since it'll never be 0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. A Twisted ElGamal ciphertext consists of two halves, the commitment (H from above) and a decryption handle. The decryption handle is calculated as randomness * Pubkey.

Here, we want to intentionally use a fixed randomness of 1. So whenever we deposit, we want to add 1 * H to the commitment and 1 * Pubkey to the decryption handle.

The representation that the addition syscall expects is PodRistrettoPoint, so I added a elgamal_pubkey_to_ristretto function to convert the PodElGamalPubkey as PodRistrettoPoint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!


/// Convert a `PodElGamalCiphertext` into a tuple of commitment and decrypt
/// handle `PodRistrettoPoint`
fn elgamal_ciphertext_to_ristretto(
Expand Down
3 changes: 2 additions & 1 deletion interface/src/extension/confidential_transfer/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 16 additions & 16 deletions program/src/extension/confidential_transfer/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;

Expand Down
Loading