Implementation of TRONZ Shielded Smart Contract

Background

Design

  • 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

  • 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. 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_dand ivk produces pk_d, and (d, pk_d) constitutes the shielded address.

Theory Behind Shielded Transaction

  • 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
  • note_commitment: the commitment to note
  • value_commitment: the commitment to note amount
  • epk: the temporary public key for deciphering note

Implementation of Shielded Transactions

constructor (address trc20ContractAddress, uint256 scalingFactorExponent) public {
require(scalingFactorExponent < 77, "The scalingFactorLogarithm is out of range!");
scalingFactor = 10 ** scalingFactorExponent;
owner = msg.sender;
trc20Token = TokenTRC20(trc20ContractAddress);
}
bytes32[33] frontier;
uint256 public leafCount;

MINT Transaction

function mint(uint256 rawValue, bytes32[9] calldata output, bytes32[2] calldata bindingSignature, bytes32[21] calldata c) external {}
  • 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.
bool transferResult = trc20Token.transferFrom(sender, address(this), rawValue); 
require(transferResult, “TransferFrom failed!”);
bytes32 signHash = sha256(abi.encodePacked(address(this), value, output, c));
(bytes32[] memory ret) = verifyMintProof(output, bindingSignature, value, signHash, frontier, leafCount);
uint256 result = uint256(ret[0]);
require(result == 1, "The proof and signature have not been verified by the contract!");
mapping(bytes32 => bytes32) public roots;
roots[latestRoot] = latestRoot;
mapping(uint256 => bytes32) public tree;
emit NewLeaf(leafCount - 1, output[0], output[1], output[2], c);

TRANSFER Transaction

function transfer(bytes32[10][] calldata input, bytes32[2][] calldata spendAuthoritySignature, bytes32[9][] calldata output, bytes32[2] calldata bindingSignature, bytes32[21][] calldata c) external {}
  • 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.
  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.
require(input.length >= 1 && input.length <= 2, "Input number must be 1 or 2!");
require(input.length == spendAuthoritySignature.length, "Input number must be equal to spendAuthoritySignature number!");
require(output.length >= 1 && output.length <= 2, "Output number must be 1 or 2!");
require(output.length == c.length, "Output number must be equal to c number!");
for (uint256 i = 0; i < input.length; i++) {
require(nullifiers[input[i][0]] == 0, "The note has already been spent!");
require(roots[input[i][1]] != 0, "The anchor must exist!");
}
bytes32 signHash = sha256(abi.encodePacked(address(this), input, output, c));
(bytes32[] memory ret) = verifyTransferProof(input, spendAuthoritySignature, output, bindingSignature, signHash, frontier, leafCount);
uint256 result = uint256(ret[0]);
require(result == 1, "The proof and signature have not been verified by the contract!");
for (uint256 i = 0; i < input.length; i++) {
bytes32 nf = input[i][0];
nullifiers[nf] = nf;
}
for (uint256 i = 0; i < output.length; i++) {
emit NewLeaf(leafCount - (output.length - i), output[i][0], output[i][1], output[i][2], c[i]);
}

BURN Transaction

function burn(bytes32[10] calldata input, bytes32[2] calldata spendAuthoritySignature, uint256 rawValue, bytes32[2] calldata bindingSignature, address payTo, bytes32[3] calldata burnCipher, bytes32[9][] calldata output, bytes32[21][] calldata c) external {}
  • 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. The 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.
  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.
require(nullifiers[nf] == 0, "The note has already been spent!");
require(roots[anchor] != 0, "The anchor must exist!");
bytes32 signHash = sha256(abi.encodePacked(address(this), input, output, c, payTo, value));
(bool result) = verifyBurnProof(input, spendAuthoritySignature, value, bindingSignature, signHash);
require(result, "The proof and signature have not been verified by the contract!");
bytes32 signHash = sha256(abi.encodePacked(address(this), input, output, c, payTo, value));
(bytes32[] memory ret) = verifyTransferProof(inputs, spendAuthoritySignatures, output, bindingSignature, signHash, value, frontier, leafCount);
uint256 result = uint256(ret[0]);
require(result == 1, "The proof and signature have not been verified by the contract!");
bool transferResult = trc20Token.transfer(payTo, rawValue);
require(transferResult, "Transfer failed!");

Merkle Path

function getPath(uint256 position) public view returns (bytes32, bytes32[32] memory) {}

References

For more information

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Is Dark Web always used for Bad Business?

{UPDATE} Jigsaw Puzzles for You Hack Free Resources Generator

Equifax Data Breach: How Can You File a Claim?

How to Build Fail-Safe Web Scrapers

From 2 Billion to 200 Users, GDPR is Changing Data Compliance Landscape

{UPDATE} Koiran taustakuvia ja taustat Hack Free Resources Generator

Data Brokers Operate “Data Removal” Websites (In Secret)

OPERATION: LONDON BRIDGE

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
TRON Core Devs

TRON Core Devs

More from Medium

How DeHive Deals with Bugs in Clusters: Interview with Our CTO, Pavel Horbonos

How DeHive Deals with Bugs in Clusters: Interview with Our CTO, Pavel Horbonos

MM72 Farm and Pool on Ampleswap

How to add Polygon Network to Metamask

POLYGON

bnbstaker.xyz — Earn upto 20% Daily on your Staked BNB Token