Private transaction
Private transactions are very useful in the context of blockchain as they allow you to send a hidden amount of currency to someone else.
In this paradigm, your account balance, the recipient’s account balance, and the transfer amount are all hidden (i.e. encrypted). This prevents other parties from knowing how much money you have or how much money you’re sending another party.
Please note that to properly implement a private transaction on an actual blockchain, you would want to use threshold FHE rather than single-key FHE. This experience is possible via our SPF offering :)
How will our algorithm work?
The transfer algorithm works quite simply; it adds the transfer amount to the recipient’s balance and subtracts it from the sender’s balance. There are however two potential issues that need to be addressed:
- Underflow: If the sender does not have enough funds to perform the transfer, the transfer should not be performed.
- Overflow: If adding the transfer amount to the recipient’s balance would cause it to overflow, the transfer should not be performed. For example, if the recipient’s balance is
UINT32_MAX
and the transfer amount is1
, the recipient’s balance would overflow to0
, which should not be allowed.
To check for these issues, we use the ternary operator ? :
to conditionally set the transfer amount to zero if the sender does not have enough funds to perform the transfer or if the transfer would cause the recipient’s balance to overflow. Even if the transfer amount ends up being zero, two new ciphertexts for the balances will be generated, ensuring that the result of the transfer is kept private.
“Normal” program (aka no privacy)
Here is plaintext version of the program, where neither the sender not the recipient has privacy for the transaction.
void transfer(uint32_t *sender_balance_pointer,
uint32_t *recipient_balance_pointer,
uint32_t transfer_amount) {
uint32_t sender_balance = *sender_balance_pointer;
uint32_t recipient_balance = *recipient_balance_pointer;
// Conditions that need to be met for a transfer to be valid
bool sender_can_transfer = transfer_amount <= sender_balance;
bool recipient_can_receive =
recipient_balance + transfer_amount >= recipient_balance;
// Determine the fundable transfer amount
uint32_t fundable_transfer_amount =
(sender_can_transfer && recipient_can_receive) ? transfer_amount : 0;
// Update the balances
*sender_balance_pointer = sender_balance - fundable_transfer_amount;
*recipient_balance_pointer = recipient_balance + fundable_transfer_amount;
}
We define the function that takes in the balances and transfer amount via transfer
. In this code we will be updating the balance values in-place through their pointers; for FHE programs, all inputs are required to be passed in by reference and hence the plaintext version follows this convention.
We check if the sender has enough funds to perform the transfer (sender_can_transfer
) and if the transfer would cause the recipient’s balance to overflow (recipient_can_receive
), handling the two edge cases mentioned earlier.
If the sender is able to transfer the funds and the receiver is able to receive them, we keep the transfer amount specified. Otherwise, we set the transfer amount to zero to prevent any transfer from occurring. This can be seen in fundable_transfer_amount
. Finally, we update the balances!
Private program walkthrough
To transform the above program into one providing privacy for both the sender and receiver, we include [[clang:fhe_program]]
for the transfer
function so that our compiler knows that this should be transformed into an FHE program operating on private data.
To indicate that the sender’s balance, recipient’s balance, and transfer amount should be hidden from others view, we include [[clang:encrypted]]]
.
And that’s it, you now have an FHE program where only the sender and recipient know the transfer amount!
[[clang::fhe_program]]
void transfer([[clang::encrypted]] uint32_t *sender_balance_pointer,
[[clang::encrypted]] uint32_t *recipient_balance_pointer,
[[clang::encrypted]] uint32_t transfer_amount) {
uint32_t sender_balance = *sender_balance_pointer;
uint32_t recipient_balance = *recipient_balance_pointer;
// Conditions that need to be met for a transfer to be valid
bool sender_can_transfer = transfer_amount <= sender_balance;
bool recipient_can_receive =
recipient_balance + transfer_amount >= recipient_balance;
// Determine the fundable transfer amount
uint32_t fundable_transfer_amount =
(sender_can_transfer && recipient_can_receive) ? transfer_amount : 0;
// Update the balances
*sender_balance_pointer = sender_balance - fundable_transfer_amount;
*recipient_balance_pointer = recipient_balance + fundable_transfer_amount;
}
Running the program
If you’d like to run the program, feel free to look at the corresponding code here.
Performance
Running the program takes 0.327 seconds on an AWS c7a.16xlarge machine [64 cores] and 1.28 seconds on an 14 inch M2 Macbook Pro with 10 cores (6 performance, 4 efficiency).
What’s missing?
For simplicity, we’ve omitted details that are needed to properly execute a private transaction in real life, such as where to store the encrypted values and providing a private funding method for the accounts.