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 transactions.

# Background

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 `Bulletproof`. 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, 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 the 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 implement 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 the 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.

# Design

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 the shielded transactions 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`.

  • `MINT` refers to the transfer of `TRC20` tokens from public addresses to a shielded address. To be more specific, `TRC20` tokens are transferred from the user's address to the contract address and a commitment to this shielded output is added to the smart contract.

  • `TRANSFER` supports transfers from up to 2 shielded inputs to no more than 2 shielded outputs (By nature it is many-to-many transfer. Here we add a limit at the implementation level). After the validity of the shielded inputs and outputs is confirmed in the smart contract, a commitment to such shielded output will be added.

  • `BURN` supports two scenarios, the first scenario is to transfer from a shielded input to a public address. The other scenario is to transfer from a shielded input to a public address and a shielded output. After the validity of shielded input and shielded output is confirmed in the smart contract, a certain amount of `TRC20` token will be transferred from the contract address to the user's public address. For the second scenario, it will also add the commitment of shielded output.

# Implementation

## Shielded Account System

Shielded accounts employ a different key system from the public accounts, as is shown below.

924


Here is the usage for each key:

  • `sk(Spending Key)`: the 32-byte bit string randomly generated by the user. It is the core key from which all other keys derive;

  • `ask`: the BLAKE2b hash calculated from `sk` and `0`. It is used to generate the key for signing the shielded input using `Spend Authority Signature` algorithm;

  • `ak`: the value returned by multiplying a coordinate on the elliptic curve by `ask` (scalar). It is used to generate the public key for verifying the shielded input using the Spend Authority Signature algorithm;

  • `nsk`: the BLAKE2b hash calculated from `sk` and `1`. It is used to generate `nk`;

  • `nk`: generated by the scalar multiplication of nsk and a coordinate on the elliptic curve. It is used to generate `nullifier`(prevent double-spending);

  • `ivk`: generated by `ak` and `nk` performing a BLAKE2s hash. It's mostly used by the recipient to view the shielded transactions he/she receives;

  • `ovk`: generated by` sk` and `2` performing a BLAKE2b hash. It's mostly used by the sender to view the shielded transactions.

  • `d (Diversifier)`: the 11-byte random number selected by the user. It is a part of the address and is mainly used for generating different addresses to break the relation between addresses and transactions;

  • `pk_d`: it is a part of the address. `d` will perform a `DiversifyHash` (namely, hash `d` to the coordinate of the elliptic curve) first to generate `g_d`. The scalar multiplication of `g_d` and `ivk` produces `pk_d`, and `(d, pk_d)` constitutes the shielded address.

## Theory Behind Shielded Transaction

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, the 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 on the chain, public input is required.

  • `nf`: every `note` matches a unique `nf`, the positions of `nf` and `note` on the `Merkle` tree is related to `note_commitment` , it is used to prevent the double spending of `note`.

  • `anchor`: the root of the `Merkle` tree.

  • `value_commitment`: the commitment to the amount of the `note`.

  • `rk`: the public key that verifies the Spend Authority Signature of the `note`

Users can spend a certain `note` by verifying 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 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 on-chain 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:

  • `note_commitment`: the commitment to `note`

  • `value_commitment`: the commitment to `note` amount

  • `epk`: the temporary public key for deciphering `note`

Verifying proof confirms the recipient‘s 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 are 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` signature, which ensures the balance of the transaction amount for the sender and the recipient.

For details of the protocol, please refer to TRONZ Privacy Protocol [3]

## Implementation of Shielded Transactions

`TRC20` token shielded transaction is implemented 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 the `Merkle` tree, and `leafCount` represents the number of nodes on the current `Merkle` tree.



### `MINT` Transaction

`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:

  • `rawValue`: Amount of transfer

  • `output`: `{note_commitment||value_commitment||epk||proof}`

  • `bindingSignature`: Binding signature of a transaction that is used to verify the balance of input and output amount within a transaction

  • `c`: `{C_enc||C_out}`, ciphertext field.

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 implemented 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.

    

    `signHash` is the message Hash of Binding signature.

  3. The root and node of the `Merkle` tree, which returned by `verifyMintProof`, needs to be updated in the contract.

    

    `roots` stores all historical roots of the `Merkle` tree.

    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 the transaction log.

    

### `TRANSFER` Transaction

`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:

  • `input`: `{nf||anchor||value_commitment||rk||proof}`, variable-length array. Multiple shielded inputs are supported.

  • `spendAuthoritySignature`: Authentication signature of the shielded input. Each shielded input has a corresponding authentication signature.

  • `output`: `{note_commitment||value_commitment||epk||proof}`, each shielded output has a corresponding `output`.

  • `bindingSignature`: Binding signature of a transaction that is used to verify the balance of input and output amount within a transaction.

  • `c`: `{C_enc||C_out}`, ciphertext field. Each shielded output has a corresponding `c`.

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 implemented in `verifyTransferProof` pre-compiled contract and is added specially for zk-SNARKs. `verifyTransferProof` returns the latest root and nodess 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

`BURN` transaction enables transfers from a shielded input to either a public address or a public address and a shielded output. 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. For the second scenario, the `note_commitment` of the shielded output will also be added to the `Merkle` tree.

`BURN` transaction is executed through triggering the `burn` function of the shielded contract.



Parameters of the function include:

  • `input`: `{nf||anchor||value_commitment||rk||proof}`

  • `spendAuthoritySignature`: Authentication signature of shielded input.

  • `rawValue`: Amount of transfer.

  • `bindingSignature`: Binding signature of a transaction that is used to verify the balance of input and output amount within a transaction.

  • `payTo`: Public address of the transaction's receiver.

  • `burnCipher`: Encryption of the receiving address and the transfer amount. Encryption key is the sender's `ovk`. This parameter is mainly used by the transaction sender to track his transaction history.

  • `output`: `{note_commitment||value_commitment||epk||proof}`

  • `c`: `{C_enc||C_out}`,ciphertext field. Each shielded output has a corresponding `c`.

Perform the function following the steps below:

  1. Verify `nf` and `anchor` to determine whether the shielded input has been double-spent and whether the `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.

    

### Merkle Path

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, the 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.

# References

[1] Zether protocol https://crypto.stanford.edu/~buenz/papers/zether.pdf

[2] TIP135 https://github.com/tronprotocol/tips/blob/master/tip-135.md

[3] TRONZ shielded protocol https://www.tronz.io/Shielded%20Transaction%20Protocol.pdf