[ciphertext-arithmetic, program, interface] Add add_to_with_offset and update tests#1116
Conversation
1cea6ef to
e57563b
Compare
joncinque
left a comment
There was a problem hiding this comment.
Just a couple of layperson questions, likely nothing stopping this from going in.
| 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, | ||
| ]); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Thanks for the explanation!
| /// Convert a `PodElGamalPubkey` into `PodRistrettoPoint` | ||
| fn elgamal_pubkey_to_ristretto(pubkey: &PodElGamalPubkey) -> PodRistrettoPoint { | ||
| let bytes = bytes_of(pubkey); | ||
| PodRistrettoPoint(bytes.try_into().unwrap()) | ||
| } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Problem
The new
solana-zk-sdkv5 and newer ships with extra safety checks on sigma proof verification (solana-program/zk-elgamal-proof#199). Specifically, when the inputs like ElGamal public key, ciphertexts and Pedersen commitments to the zk statement to be proved are all-zero values, then the proof rejects for extra safety.This is nice and fine, but it does have some implications for confidential transfer extensions:
EmptyAccountinstruction will fail if the confidential transfer account is already emptiedWithdrawWithheldTokensFromMintwill fail if the encrypted fees in the mint is an all zero ciphertextRotateSupplyElGamalPubkeywill fail if the encrypted supply is an all zero ciphertextSummary of Changes
Withdrawissue via ciphertext offsets: Modified the Deposit logic to homomorphically add a fixed cryptographic offset (an encryption of 0 with a Pedersen randomness of 1) to the user's balance. This ensures that even when a user's numeric token balance drops to zero after a full withdrawal, the underlying ciphertext retains randomness and does not evaluate to the identity point, allowing the zero-knowledge proofs to succeed seamlessly.The issue with
WithdrawWithheldTokensFromMintandRotateSupplyElGamalPubkeyare pretty niche cases, but the case ofEmptyAccountcan occur in practice, so I added a note on this.I also adjusted the test suite to align with the new SDK constraints and validate the new Deposit offset behavior.
The CI is failing with
cargo audit, so I will rebase after #1115 is merged.