Implementation of Shielded TRC20 Token Contract Powered by zk-SNARKs

By June 29, 2020Tron | TRX
Tron Vote

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.

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

 

Designed

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.

  • 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 refers to transfers from a shielded input to a public address. After the validity of such shielded input 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.

Implementation

Implementation: Shielded Account System

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

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 bysk 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. dwill perform a DiversifyHash (namely, hashd 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 Transactions

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.

  • nf: every note matches a unique nf. The positions of nf and note on the Merkle tree is related to note_commitment in order 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 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:

  • note_commitment: the commitment to note
  • value_commitment: the commitment to note amount
  • epk: the temporary public key for deciphering note

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]

Implementation of Shielded Transactions

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

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 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.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_commitmentvalue_commitmentepkand the location of the newly-added leaf node to 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 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

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:

  • 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.
  • c: Encryption of the receiving address and transfer amount. Encryption key is the sender’s ovk. This parameter is mainly used by the transaction sender to track his transaction history.

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.

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, 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 getPathMerkle root and Merkle path will be returned.

 

References

For more information

Comments