Zero-Knowledge Proofs
Zero-knowledge proofs are tools that allow users to prove certain properties of encrypted data. Most of the zero-knowledge proofs that are used in the confidential extension are relatively small systems that are specifically designed for the simple use-case of the confidential extension. Due to their simplicity, none of the zero-knowledge systems that are used in the program require any trusted setup or sophisticated circuit design.
The zero-knowledge proofs that are used in the confidential extension can be divided into two categories: sigma protocols and bulletproofs. Sigma protocols are simple systems that are tailor designed for the confidential extension use-cases. Bulletproofs is an existing range proof system that was developed in the specified paper.
Transfer Instruction Data
The confidential extension Transfer
instruction data requires a number of
cryptographic components. Here, we provide intuition for each of these
components by building the transfer data in a series of steps.
struct TransferData {
...
}
ElGamal Public Keys
A transfer instruction has three associated ElGamal public keys: sender, receiver, and auditor. A transfer instruction data must include these three encryption public keys.
struct TransferPubkeys {
source_pubkey: ElGamalPubkey,
destination_pubkey: ElGamal Pubkey,
auditor_pubkey: ElGamalPubkey,
}
struct TransferData {
transfer_pubkeys: TransferPubkeys,
}
If there is no associated auditor associated with the mint, then the auditor pubkey is simply 32 zero bytes.
Low and High-bit Encryption
Transfer instruction data must include the transfer amount that is encrypted
under the three ElGamal public keys associated with the instruction. To cope
with ElGamal decryption as discussed in the previous section, the transfer
amount is restricted to 48-bit numbers and is encrypted as two separate
numbers: amount_lo
that represents the low 16-bits and amount_hi
that
represents the high 32-bits.
Each amount_lo
and amount_hi
is encrypted under the three ElGamal public
keys associated with a transfer. Instead of including three independent
ciphertexts as part of the transfer data, we use the randomness-reuse property
of ElGamal encryption to minimize the size of ciphertexts.
/// Ciphertext structure of the transfer amount encrypted under three ElGamal
/// public keys
struct TransferAmountEncryption {
commitment: PedersenCommitment,
source_handle: DecryptHandle,
destination_handle: DecryptionHandle,
auditor_handle: DecryptHandle,
}
struct TransferData {
ciphertext_lo: TransferAmountEncryption,
ciphertext_hi: TransferAmountEncryption,
transfer_pubkeys: TransferPubkeys,
}
In addition to these ciphertexts, transfer data must include proofs that these ciphertexts are generated properly. There are two ways that a user can potentially cheat the program. First a user may provide ciphertexts that are malformed. For example, even if a user may encrypt the transfer amount under a wrong public key, there is no way for the program to check the validity of a ciphertext. Therefore, we require that transfer data require a ciphertext validity proof that certifies that the ciphertexts are properly generated.
Ciphertext validity proof only guarantees that a twisted ElGamal ciphertext is
properly generated. However, it does not certify any property regarding the
encrypted amount in a ciphertext. For example, a malicious user can encrypt
negative values, but there is no way for the program to detect this by simply
inspecting the ciphertext. Therefore, in addition to a ciphertext validity
proof, a transfer instruction must include a range proof that certifies that
the encrypted amounts amount_lo
and amount_hi
are positive 16 and 32-bit
values respectively.
struct TransferProof {
validity_proof: ValidityProof,
range_proof: RangeProof,
}
struct TransferData {
ciphertext_lo: TransferAmountEncryption,
ciphertext_hi: TransferAmountEncryption,
transfer_pubkeys: TransferPubkeys,
proof: TransferProof,
}
Verifying Net-Balance
Finally, in addition to proving that the transfer amount is properly encrypted,
a user must include a proof that the source account has enough balance to
make the transfer. The canonical way to do this is for the user to generate a
range proof that certifies that the ciphertext
source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)
, which holds
the available balance of the source account subtracted by the transfer amount,
encrypts a positive 64-bit value. Since Bulletproofs supports proof
aggregation, this additional range proof can be aggregated into the original
range proof on the transfer amount.
struct TransferProof {
validity_proof: ValidityProof,
range_proof: RangeProof, // certifies ciphertext amount and net-balance
}
struct TransferData {
ciphertext_lo: TransferAmountEncryption,
ciphertext_hi: TransferAmountEncryption,
transfer_pubkeys: TransferPubkeys,
proof: TransferProof,
}
One technical problem with the above is that although the sender of a transfer
knows an ElGamal decryption key for the ciphertext source_available_balance
,
it does not necessarily know a Pedersen opening for the ciphertext, which is
needed to generate the range proofs on the ciphertext
source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)
. Therefore,
in a transfer instruction, we require that the sender decrypt the ciphertext
source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)
on the
client side and include a new Pedersen commitment on the new source balance
new_source_commitment
along with an equality proof that certifies that the
ciphertext source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)
and new_source_commitment
encrypt the same message.
struct TransferProof {
new_source_commitment: PedersenCommitment,
equality_proof: CtxtCommEqualityProof,
validity_proof: ValidityProof,
range_proof: RangeProof,
}
struct TransferData {
ciphertext_lo: TransferAmountEncryption,
ciphertext_hi: TransferAmountEncryption,
transfer_pubkeys: TransferPubkeys,
proof: TransferProof,
}
Transfer With Fee Instruction Data
The confidential extension can be enabled for mints that are extended for fees.
If a mint is extended for fees, then any confidential transfer of the
corresponding tokens must use the confidential extension TransferWithFee
instruction. In addition to the data that are required for the Transfer
instruction, the TransferWithFee
instruction requires additional cryptographic
components associated with fees.
Background on Transfer Fees
If a mint is extended for fees, then transfers of tokens that pertains to the mint requires a transfer fee that is calculated as a percentage of the transfer amount. Specifically, a transaction fee is determined by two parameters:
-
bp
: The base point representing the fee rate. It is a positive integer that represents a percentage rate that is two points to the right of the decimal place.For example,
bp = 1
represents the fee rate of 0.01%,bp = 100
represents the fee rate of 1%, andbp = 10000
represents the fee rate of 100%. -
max_fee
: the max fee rate. A transfer fee is calculated using the fee rate that is determined bybp
, but it is capped bymax_fee
.For example, consider a transfer amount of 200 tokens.
- For fee parameter
bp = 100
andmax_fee = 3
, the fee is simply 1% of the transfer amount, which is 2. - For fee parameter
bp = 200
andmax_fee = 3
, the fee is 3 since 2% of 200 is 4, which is greater than the max fee of 3.
- For fee parameter
The transfer fee is always rounded up to the nearest positive integer. For
example, if a transfer amount is 100
and the fee parameter is bp = 110
and
max_fee = 3
, then the fee is 2
, which is rounded up from 1.1% of the
transfer amount.
The fee parameters can be specified in mints that are extended for fees. In
addition to the fee parameters, mints that are extended for fees contain the
withdraw_withheld_authority
field, which specifies the public key of an
authority that can collect fees that are withheld from transfer amounts.
A Token account that is extended for fees has an associated field
withheld_amount
. Any transfer fee that is deducted from a transfer amount is
aggregated into the withheld_amount
field of the destination account of the
transfer. The withheld_amount
can be collected by the withdraw-withheld
authority into a specific account using the
TransferFeeInstructions::WithdrawWithheldTokensFromAccounts
or into the mint
account using the TransferFeeInstructions::HarvestWithheldTokensToMint
. The
withheld fees that accumulate in a mint can be collected into an account using
the TransferFeeInstructions::WithdrawWithheldTokensFromMint
.
Fee Encryption
The actual amount of a transfer fee cannot be included in the confidential
extension TransferWithFee
instruction in the clear since the transfer amount
can be inferred from the fee. Therefore, in the confidential extension, the
transfer fee is encrypted under the destination and withheld authority ElGamal
public key.
struct FeeEncryption {
commitment: PedersenCommitment,
destination_handle: DecryptHandle,
withdraw_withheld_authority_handle: DecryptHandle,
}
struct TransferWithFeeData {
... // `TransferData` components
fee_ciphertext: FeeEncryption,
}
Upon receiving a TransferWithFee
instruction, the Token program deducts the
encrypted fee under the destination ElGamal public key from the encrypted
transfer amount under the same public key. Then it aggregates the ciphertext
that encrypts the fee under the withdraw withheld authority's ElGamal public key
into the withheld_fee
component of the destination account.
Verifying the Fee Ciphertext
The remaining pieces of the TransferWithFee
instruction data are fields that
are required to verify the validity of the encrypted fee. Since the fee is
encrypted, the Token program cannot check that the fee was computed correctly by
simply inspecting the ciphertext. A TransferWithFee
must include three
additional proofs to certify that the fee ciphertext is valid.
- ciphertext validity proof: This proof component certifies that the actual fee ciphertext is properly generated under the correct destination and withdraw withheld authority ElGamal public key.
- fee sigma proof: In combination with range proof component, the fee sigma
proof certifies that the fee that is encrypted in
fee_ciphertext
is properly calculated according to the fee parameter. - range proof: In combination with the fee sigma proof components, the range
proof component certifies that the encrypted fee in
fee_ciphertext
is properly calculated according to the fee parameter.
We refer to the proof specifications below for the additional details.
Sigma Protocols
(Public-key) Validity Proof
A public-key validity proof certifies that a twisted ElGamal public-key is a well-formed public key. The precise description of the system is specified in the following notes.
The public-key validity proof is required for the ConfigureAccount
instruction.
(Ciphertext) Validity Proof
A ciphertext validity proof certifies that a twisted ElGamal ciphertext is a well-formed ciphertext. The precise description of the system is specified in the following notes.
Validity proofs are required for the Withdraw
, Transfer
, and
TransferWithFee
instructions. These instructions require the client to include
twisted ElGamal ciphertexts as part of the instruction data. Validity proofs
that are attached with these instructions certify that these ElGamal ciphertexts
are well-formed.
Zero-balance Proof
A zero-balance proof certifies that a twisted ElGamal ciphertext encrypts the number zero. The precise description of the system is specified in the following notes.
Zero-balance proofs are required for the EmptyAccount
instruction, which
prepares a token account for closing. An account may only be closed if the
balance in an account is zero. Since the balance is encrypted in the
confidential extension, the Token program cannot directly check that the
encrypted balance in an account is zero by inspecting the account state.
Instead, the program verifies the zero-balance proof that is attached in the
EmptyAccount
instruction to check that the balance is indeed zero.
Equality Proof
The confidential extension makes use of two kinds of equality proof. The first variant ciphertext-commitment equality proof certifies that a twisted ElGamal ciphertext and a Pedersen commitment encode the same message. The second variant ciphertext-ciphertext equality proof certifies that two twisted ElGamal ciphertexts encrypt the same message. The precise description of the system is specified in the following notes.
Ciphertext-commitment equality proofs are required for the Transfer
and
TransferWithFee
instructions. Ciphertext-ciphertext equality proofs are
required for the WithdrawWithheldTokensFromMint
and
WithdrawWithheldTokensFromAccounts
instructions.
Fee Sigma Proof
The fee sigma proof certifies that a committed transfer fee is computed correctly. The precise description of the system is specified in the following notes.
[Notes]
The fee sigma proof is required for the TransferWithFee
instruction.
Range Proofs
The confidential extension uses Bulletproofs for range proofs. We refer to the academic paper and the dalek implementation for the details.