Shielding Tokens

The first step in using the RAILGUN Privacy System is to privatize cryptocurrency tokens. Shielding is the process whereby funds enter the wallet’s private 0zk address from the public 0x address. Once funds are in the 0zk address, users can send, swap, and interact with DeFi privately. Further, any token that exists on the public blockchain can be shielded, which would include any ERC-20 token as well as ERC-721 NFTs.

Users initiate shielding by sending a shield transaction which contains public data (the asset, the amount, and other values called) from their public wallet. The RAILGUN smart contracts then take these values and computes a commitment (called a note). Notes are a hashed value of the public data sent by the user at the start of the shielding process. The hashed value of a note (a circuit output) cannot be reversed to uncover the original value (an input). Inputs and outputs in this context are the transaction/asset data which a RAILGUN user wishes to obscure.

RAILGUN Smart Contract: shield()

When a user initiates a shielding transaction, they call the Solidity function, shield(). This function includes everything required to shield assets into the RAILGUN privacy system, including the Poseidon hash function for encrypting notes. The Merkle Tree to which new notes are committed is also stored in the smart contract storage in variables called merkleRoot and merkleLeaf, and is handled by the Commitments.sol contract.

The shield() function emits the Shield event upon completion.

Events within the RAILGUN smart contract contain data within them. Due to RAILGUN’s hashing functions and zk-SNARK proving systems, this data can only be read by the holder of either the Spending Key or Viewing Key, preserving privacy each time the smart contract is called.

For developers integrating RAILGUN SDK, the shield() function and anything related to shielding assets will not require specific integration efforts as the SDK will abstract all encryption and transaction generation functions.

RAILGUN Smart Contract: transact()

Spend transactions call the transact() function. This function is used to interact with your private balance in a number of ways. This is accomplished by verifying that the Nullifiers in the internal Merkle Tree path correspond to the circuit-inputs and number of circuit-outputs being spent. Essentially, this function feeds to a zk-SNARK proof to ensure that the number of inputs matches the number of outputs on the circuit, and enforces the rule that Nullifiers must not have been seen anywhere on the Merkle Tree before. If these nullifiers have been seen before, it is then verified that this is not a valid spend transaction and the user does not have the requisite UTXOs (insufficient funds) to send the transaction. RAILGUN Smart Contract: transact()

Spend transactions call the transact() function. This function is used to interact with your private balance in a number of ways. This is accomplished by verifying that the Nullifiers in the internal Merkle Tree path correspond to the circuit-inputs and number of circuit-outputs being spent. Essentially, this function feeds to a zk-SNARK proof to ensure that the number of inputs matches the number of outputs on the circuit, and enforces the rule that Nullifiers must not have been seen anywhere on the Merkle Tree before. If these nullifiers have been seen before, it is then verified that this is not a valid spend transaction and the user does not have the requisite UTXOs (insufficient funds) to send the transaction.

Private Pools & Merkle Trees

Once assets are shielded by the cryptographic methods described above, they’re added to an anonymized pool held by the RAILGUN smart contract. The RAILGUN system contains an efficient internal implementation of a batch-incremental Merkle Tree. Each shield transaction generates a new note which takes the form of a new Root/Leaf on the Merkle Tree. All assets within the RAILGUN system share the same Merkle Tree. Every transaction that updates the state of the Merkle Tree will generate a new Merkle Root/Leaf that is hashed and secured by the zk-SNARK circuits.

During the shielding process, the RAILGUN smart contract requires the amount and sending wallet address. This means that the sender of a shield transaction’s public key is broadcast to the wider blockchain network. However, this is a regular blockchain transaction and not a centralized retrieval of the public key and is necessary to send funds from the public ledger. Shields and Unshields are the only types of RAILGUN transactions that contain identifiable information as they interact directly with public addresses.

Once the appropriate proofs and notes have been added to the RAILGUN smart contract Merkle Tree, privacy is achieved. Transactions appear to originate from a Broadcaster, and therefore technically-speaking can have come from anyone in the private pool who has sufficient funds to send the UTXO associated with the transaction. What’s more, all identifying information such as amount of transaction is obfuscated in block explorers.

Shielding NFTs

The RAILGUN smart contracts supports the shielding of ERC-721 non-fungible tokens. Users are able to add NFTs to their private balance in order to disguise the source of NFTs.

This creates interesting integration possibilities, like the private auctioning of popular NFTs. But NFTs are much larger than merely JPEG images of primates. Many DeFi protocols, like Uniswap v3, which issues NFTs to represent LP positions.

With the integration of NFTs into RAILGUN, a user are able to shield NFTs in order to:

  • Auction NFT assets to private buyers

  • Provide liquidity privately on Uniswap v3

Last updated