Transactions

In IronFish, each transaction has 0 or more Spend Descriptions and 0 or more Output Descriptions. Every transaction uses zk-SNARK (groth16) Sapling proof both on the Spend and Output description. The circuit construction for these proofs is taken from Sapling primitive gadgets which in turn were constructed using the bellman circuit building tool.

As with any zk-SNARK based system, there are hardcoded parameters that are a result of a trusted setup. In order to generate the Spend Description proof and the Output Description proof one needs access to the appropriate proving key, and the verifier needs access to the appropriate verifying key. These will be generated using a multi party computation to allow as many participants as possible to contribute randomness and ensure the system is sound. Those parameters will be specified here at a later time.

While explaining zk-SNARKs is outside the scope of this paper, for readers who want to learn more, I recommend this excellent tutorial by Maksym Petkus.

The transaction, in plaintext, has the numerical difference between inputs(spends) and outputs which is the transaction fee.

inputsoutputs=transactionfeeinputs - outputs = transaction fee

Spend Description

The full format of the Spend Description:

ElementDescription
cvcvvalue commitment
rtrtroot anchor – which Merkle root is used for the proof
nfnfnullifier for the note
rkrkrandomized public key for the authorization key (ak)
(private key of the randomized ak): rsk=askαrsk = ask * α
rk is the public key for the randomized authorized key private key
sigsigsign hash of the spend description using rsk
proofproofzk-SNARK proof that allows one to hide the private values needed to validate

Private variables for proof generation:

ElementDescription
merklepathmerkle paththe Merkle path to the commitment note being spent
positionpositionthe position of the commitment note (e.g. index)
gdgdthe diversifier of the public address owning this note
pkdpkdthe transmission key of the owner
vvvalue of the note
rcvrcvrandomness used in the Pedersen Hash for value commitment
cmcmnote commitment of the note being spent
rcmrcmthe randomness used in the Pedersen Hash for note commitment
ααalpha used to hide the authorization key that signs the spend
akakthe owner’s authorization key (that was randomized)
nsknskthe proof authorization key used for the nullifier

Public variables for verification:

ElementDescription
rtrtroot anchor that was used for the Merkle path in the proof
cvcvPedersen Hash (commitment) of the value
nfnfnullifier to spend the note
rkrkthe randomized authorization public key
What exactly gets checked in the proof?
  1. Note Commitment integrity

    1. Check that the note commitment (cm) is derived from gdg_d, pkdpk_{d'}, valuevalue, and rcmrcm (randomness for the Pedersen Hash)
  2. Merkle Path Validity

    1. Check that the Merkle path is valid from the given merkle root to the leaf
  3. Value Commitment

    1. Check that the value commitment (cv) was indeed constructed as cv==vGv+rcvGrcvcv == v * G_v + rcv * G_{rcv}
  4. Nullifier

    1. Check that the nullifier is derived from ivk (the owner’s incoming view key), the cm (note commitment) and position (the index in the Merkle tree on the lowest level)
  5. Random Authorization Key

    1. Check that the random authorization key (rsk) that is used to sign the Spend Description is correctly derived from the spend authorization key and the provided α. This is so that no spend transactions can ever be linked by signatures using the same key
      1. rsk==askαrsk == ask * α

It is important to note that it is necessary to know which notes are spendable and the Merkle Path to them. Logistically, this means that a user or a wallet has to:

  1. Continuously scan for new notes created and try brute force.
  2. Be able to create (or otherwise acquire) a valid Merkle Path from the root to the leaf note being spent. This Merkle root must be one that was previously seen by a miner or validator.

Output Description

The full format of the Output Description:

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key
CencC^encencrypted plaintext of the note
CoutC^outephemeral public key
proofproofzk-SNARK proof that allows one to hide the private values needed to validate

Public variables for verification:

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key

Private variables for proof generation:

ElementDescription
gdg_drecipient’s diversifier
pkdpk_dpublic diversifier address for the recipient
vvvalue
rcvrcvrandomness for the value
rcmrcmrandomness for the note commitment
eskeskephemeral private key

From every Output Description, we create a Merkle Tree Note that consists of:

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key
CencC^encencrypted plaintext of the note
CoutC^outallows the holder of the viewing key to decrypt a decryption key for CencC^enc

What happens during the Output Description proof generation?
  1. Value commitment (cv) is created correctly using ephemeral randomness (rcv)
  2. Note commitment (cm) is created correctly using the supplied randomness for the note commitment (rcm)
  3. The ephemeral public key (epk) is created correctly given then ephemeral secret key (esk) for note encryption/decryption
  4. And that the diversifier for the recipient’s public key is a valid one

Transaction Balancing

Both the Spend and Output Description contain a value commitment which is in a form of a Pedersen commitment cv=vG+rHcv = v * G + r * H where GG and HH are known generator points on the Jubjub curve, vv is the plaintext value of the note, and rr is the randomness.

The transaction fee is known (plaintext) without ever revealing the plaintext values for any of the value commitments. The transaction also commits to the randomness of every note. Therefore, to check that a transaction committed to a valid transaction fee, we can check that inputsoutputs=transactionfeeinputs - outputs = transaction fee

Verifying transaction fee matches value commitments of the notes:

G(vv1)+H(rr1)=G(txfee)+H(sk)G ( v - v1) + H (r - r1) = G (tx_fee) + H (sk)

Transaction Verification

Transaction verification is a process of a miner or a validator performing a series of checks:

  • Validate that all the merkle root hashes for each Spend Description are ones that have (at some point in the past) been valid merkle roots for the global merkle note tree
  • Validate all the Spend Description proofs inside that transaction against its public parameters
  • Validate all the Output Description proofs inside that transaction against its public parameters