Powered by zk-SNARKs, TRONZ achieved TRON-based TRC20 token shielded transaction, which is one of the few account model-based shield transaction solutions.
This article is mainly intended to help smart contract developers understand the design and implementation of TRC20 token shielded transaction.
Currently, most shielded transaction solutions in the blockchain industry are built with UTXO model using technologies like zk-SNARKs and ring signature. For instance, Zcash adopts zk-SNARKs technology while Monero uses ring signature and Bulletproff. Few, however, adopts account model-based shielded transaction scheme because within the account model user’s funds are fluid, and zk-SNARKs generated based on the account balance is only valid for a limited period of time, thus making it extremely difficult to implement a shielded transaction scheme.
In 2019, Benedikt Bünz and some others proposed a shielded transaction scheme for account-based system – Zether Protocol[1]. Adopting a new zk-SNARKs mechanism Σ-Bullets, Zether Protocol is able to hide the transfer amount and addresses. This technology was deployed and tested on Ethereum, and was proved to be flawed as it consumed too much gas. Even worse, every transaction has to be completed within one epoch or otherwise would fail. So in cases when the network is busy, transactions may often fail as they can not be packed or recorded on-chain.
In a bid to safeguard users’ privacy in TRC20 token transactions, the TRONZ team adopts zk-SNARKs to realize the TRC20 token shielded transaction, protecting the confidentiality of both the amount and addresses of each transaction. Here we provide the standard implementation scheme of shielded transaction for TRC20 tokens[2], which is fully compatible with standard TRC20 tokens and is able to hide both the amount and addresses of each transaction.
To achieve TRC20 token shielded transaction, a smart contract is deployed to receive users’ TRC20 tokens and execute the shielded transaction. In this way, the current UTXO-based shielded transaction scheme can be used to implement shielded transaction based on the account model.
Our shielded transaction scheme employs two types of accounts: the public account and the shielded account. Public accounts are simply TRON accounts. Shielded accounts are similar to the account system of Zcash Salping.
We designed three shielded transaction modes: MINT, TRANSFER and BURN.
Shielded accounts employ a different key system from the public accounts, as is shown below.
Here is the usage for each key:
Every anonymous output is a note. Note = (d, pk_d, value, rcm). (d, pk_d)is the transaction address, value is the transaction amount, and rcm is the random number that falls in the scalar range of the Jubjub elliptic curve, namely rcm < 0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7. The getrcm interface provided by the chain can randomly generate rcm. To ensure the anonymity and privacy of the transaction, the note is not on chain. It is the commitment of the note that is on the chain, which is called note_commitment. After each shielded transaction is successfully verified, the note_commitment will be stored at the leaf node of the Merkle tree. Similarly, every anonymous input is also a note.
When spending a note, user needs to provide zero-knowledge proof to prove that the user knows the private information of the note being spent. When verifying the proof onchain, a public input is required.
Users can spend a certain note by verifing the proof, but others have no way to know which note on the Merkle tree is being spent, which means they won’t be able to know the exact amount and the address of the transaction. The privacy and the anonymity of the sender can thus be protected.
In addition to verifying the proof, it is also required to provide Spend Authority Signature to complete an onchain verification for each anonymous input.
When performing a transaction, every shielded output also needs zero-knowledge proof to ensure that the user knows the amount of the transaction and the address o the recipient. When verifying a proof, following public inputs are required:
Verifiying proof confirms the recipient address and amount of the transaction, which are known to no one except the sender and the receiver. Thus, the privacy and anonymity of the receiver is guaranteed.
Every shielded output requires extra ciphertext fields C_enc and C_out, so that the sender and the receiver can decipher information from the note.
Besides, verification of a transaction requires verifying Binding, which ensures the balance of transaction amount for the sender and the recipient.
For details of the protocol, please refer to TRONZ Privacy Protocol [3]
TRC20 token shielded transaction is realized through smart contracts (hereafter referred to as shielded contract).
In deploying the shielded contract, the TRC20 contract address is binded so that the privacy protocol is applied only to the shielded transaction of the TRC20 tokens.
Apart from TRC20 contract address, scalingFactorExponent needs to be set up in the contract, mainly for supporting TRC20 tokens with higher precision (Decimals). The shielded contract requires that the transfer amount must be a multiple of scalingFactor.
The variable frontier stores Merkle tree, and leafCount represents the number of nodes on the current Merkle tree.
MINT transaction transfers a certain amount of TRC20 tokens to a shielded contract address, and adds the shielded output note_commitment to a leaf node on the shielded contract Merkle tree.
MINT transaction transfers TRC20 tokens from a user account to a shielded contract account, therefore, before implementing MINT, the approve(address _spender, uint256 _value) function of the TRC20 contract needs to be called to allow a certain amount of TRC20 tokens to be transferred to the shielded contract account. _spender represents the shielded contract address and _value represents the transfer amount.
MINT transaction is executed through triggering the shielded contract’s mint function. Parameters of the function include:
Perform the following steps in the shielded contract:
1.Transfer the designated amount of TRC20 tokens from a user address to a shielded contract address.
2.Verify zk-SNARKs and Binding signature. If the verification is successful, update the Merkle tree by adding note_commitment to the leaf node. This step is realized in verifyMintProof pre-compiled contract and is added specially for zk-SNARKs. verifyMintProof returns the latest root and node that needs to be updated of the Merkle tree.
3.The root and node of the Merkle tree, which returned by verifyMintProof, needs to be updated in the contract.
Also, as tree stores the complete Merkle tree, all updated nodes of the Merkle tree will be updated to tree.
4.Add note_commitment, value_commitment, epk, c and the location of the newly-added leaf node to transaction log.
TRANSFER transaction supports transfers from multiple shielded inputs to multiple shielded outputs. Once a transaction is confirmed, note_commitment of the shielded outputs will be added to the leaf node of the shielded contract’s Merkle tree.
TRANSFER transaction is executed through triggering the shielded contract’s transfer function.
Parameters of the function include:
Perform the following steps in the shielded contract:
1.Limit the number of shielded inputs and outputs. In order to verify the efficiency of zk-SNARKS, set the upper limit of the number of shielded inputs and outputs to two.
2.Verify the validity of double spending and Merkle root.
Verify whether nf is in nullifiers for each shielded input. If the result is no, then it is verified that the note has not been spent. It also needs to be verified whether anchor exists in the historical roots of Merkle tree.
3.Verify zk-SNARKs, the authentication signature and Binding signature of shielded input. If the verification is successful, update the Merkle tree by adding note_commitment to the leaf node. This step is realized in verifyTransferProof pre-compiled contract and is added specially for zk-SNARKs. verifyTransferProof returns the latest root and node that needs to be updated of the Merkle tree.
4.The root and nodes that need to be updated of the `Merkle` tree, which returned by `verifyTransferProof`, need to be updated to the contract.
5.Add the nf of each shielded input to nullifier to signify the note has been spent.
6.Add note_commitment, value_commitment, epk, c and the location of the newly-added leaf node of each shielded output to transaction log.
BURN transaction enables transfers from a shielded input to a public address. Once a transaction is confirmed, a certain amount of TRC20 token will be transferred from the shielded contract address to the user’s public address through TRC20 contract’s transfer function.
BURN transaction is executed through triggering the burn function of the shielded contract.
Parameters of the function include:
Perform the function following the steps below:
1.Verify nf and anchor to determine whether the shielded input has been double spent and whether anchor is the historical root of the Merkle tree.
2.Decide the burn scenario based on the length of output, if it is scenario one(transfer from a shielded input to a public address), execute the step 2.1, it it is the scenario two(transfer from a shielded input to a public address and a shielded output), execute the step 2.2
2.1 For scenario one, Verify zk-SNARKs, the authentication signature and Binding signature of shielded input. This step is implemented in verifyBurnProof pre-compiled contract and is specially added for zk-SNARKs.
2.2 For scenario two, Verify zk-SNARKs of shielded input and shielded output, verify the authentication signature and Binding signature of shielded input. This step is implemented in verifyTransferProof pre-compiled contract and is specially added for zk-SNARKs.
The root and nodes that need to be updated, which returned by `verifyTransferProof`, needs to be updated to the contract.
3.Add the nf of the shielded input to nullifier to signify the note has been spent.
4.Call the transfer function of TRC20 contract to transfer a certain amount of TRC20 token from the shielded contract address to the public address specified by the user.
In the construction of a shielded input, private information of note includes the Merkle path and Merkle tree root of note_commitment. To help users construct zk-SNARKs with ease, shielded contract provides getPath as a method to calculate the Merkle path of a leaf node at a specified location.
Enter the location of the leaf node using getPath. Merkle root and Merkle path will be returned.
In a significant move within the cryptocurrency space, VOD Wallet has announced a strategic partnership…
In the realm of cryptocurrency, stablecoins serve as an anchor, providing stability in a notoriously…
In the constantly evolving landscape of cryptocurrency, it's always exciting to see substantial developments that…
In a significant milestone for the cryptocurrency industry, the TRON blockchain has officially exceeded 217…
In a groundbreaking move set to redefine the landscape of blockchain interoperability and financial inclusivity,…
TRON, the powerhouse behind the globe's most expansive stablecoin domain and a significant player in…