Using Shielded Tokens
RAILGUN’s interaction system is similar to the Bitcoin network’s, with the notable exception that everything is anonymized by ZK proofs. The Merkle Tree keeps track of all balances held by 0zk addresses in the system which can only be updated if proof that passes the cryptographic validation rules is submitted to the RAILGUN smart contracts.
UTXOs & Nullifiers
RAILGUN operates on a (U)TXO (unspent transaction output) model, (U) is in brackets as transaction outputs are completely hidden from outside observers. Each UTXO is an encrypted note of a public key that establishes who can spend the underlying asset, amount, token ID, and a randomness field to maintain encryption.
The output of RAILGUN circuits are UTXOs and Nullifiers, which is a hash generated from private keys that cannot be linked to a UTXO by anyone who is not a party to the transaction. Nullifiers are deterministically generated to further guard against double spends - put simply, they nullify a UTXO and disallow it from being spent again in the system. Nullifiers (and therefore circuit outputs) are calculated by a hash of the Spending Key combined with path indices of the Merkle Root/Leaf note, meaning that each note will always generate a unique Nullifier. The party holding the Spending Key is the only actor who can link which Nullifier as belonging to a corresponding UTXO. This also means that Broadcasters cannot change the transaction values, such as amount or destination address, without rendering the hash and note invalid and therefore failing the ZK proof checks.
With RAILGUN’s zk-SNARK circuits, users can arithmetically prove that they have valid UTXOs and therefore prevent double spend or false transactions whilst not revealing any underlying information. Once all the cryptographic validation tests are passed, the Broadcaster will then submit the note to the blockchain for consensus and confirmation and the spend transaction is complete.
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.
The transact()
function emits the following events which are triggered when the RAILGUN contract detects any balance changes in the system:
CommitmentBatch
– New zk-SNARK circuit outputs (i.e. new notes) occurring from the send transaction and transact() call.Nullifiers
– Private double spend markers that nullify the zk-SNARK circuit inputs used in the send transaction to ensure the UTXOs cannot be used again.
For the purposes of integration, only a surface level understanding of the transact()
function is required to integrate the RAILGUN SDK as the SDK again, and handles the computation on the integrating dApp’s behalf.
For RAILGUN transactions to execute, some publicly observable input data will be broadcast to the wider blockchain network. These hashed public inputs are used to represent and validate outputs from the zk-SNARK circuits. This input data is always hashed and cannot be unencrypted by anyone who is not a party to the transaction. Hashing means that whilst the hashed values can be seen, no outside observer can reverse the hashing algorithm to arrive at the unencrypted input data, maintaining complete privacy throughout.
The RAILGUN transact()
function has the following public inputs:
merkleRoot
Any previous Merkle Root from the entire Merkle Tree. The RAILGUN smart contract computes a new Merkle Root each time funds are transferred. This does not need to be the newest Merkle Root, rather it is a race condition for the first Merkle Root picked up by the function.
boundParamsHash
Each dApp integrated with RAILGUN has its own unique parameters. Due to the constraints of circuit design, i.e., all circuits must be fixed before computation, this hash is computed at the user side for each required parameter instead of designing a custom circuit for each parameter.
Hash of the following values:
formattedRandom
– Randomization factor to preserve security in hashingrequireSuccess
– Boolean value that requires the contract call to be successful before progressingminGas
– Minimum amount of gas to be supplied to the transactionto/value/datanullifiers[nInputs]
– Nullifiers for the input notes
nullifiers [nInputs]
Nullifiers themselves are also hashed values. This public input is used to nullify the UTXO being sent in the transaction. The nullification computation occurs in such a way that only the user can determine which UTXO has been spent, and an outside observer cannot link a Nullifier and note. The
[nInputs]
value is the number of inputs.
commitmentsOut [noutputs]
Any new inputs being created requires new notes and this parameter is the hashed note of the balance changes from the spend transaction.
Last updated