Transaction Creation

Just like accounts, transactions are heavily influenced by the Sapling protocol with some differences. All Iron Fish transactions are shielded transactions, meaning they do not reveal any information to any onlooker who does not have explicit access.

This level of privacy is achieved through the use of zero-knowledge proofs, which allow transaction details to be encrypted with an accompanying zero-knowledge proof attesting to the validity of transaction details.

There is a lot to cover in this section, so here’s a quick guide to the pieces we’ll be reviewing:

  1. the components of a transaction
  2. the Spend description component (the one that dictates how an account can spend a note)
  3. the Output description component (the one one that creates new notes)
  4. how a transaction balances to ensure that appropriate amounts were spent and paid out
  5. how a a validator (such as a miner) can verify any transaction
  6. a special type of transaction called the Miner Fee transaction, which is used to reward a miner for successfully mining a block
  7. how notes are encrypted and decrypted such that only the appropriate parties are privy to viewing transaction details

Transaction Components

A transaction is a list of Spend and Output descriptions:

  • A Spend description spends notes that are used up in a transaction.
  • An Output description creates new notes that result as part of that transaction, including the change back to the sender if the note they’ve spent is greater than what is intended to the recipient.

Notes that are spent in the Spend description cannot be spent again in the future due to the unique nullifier that must be revealed when spending it as subsequent attempts will be rejected by validators (e.g. miners) if that nullifier has been revealed in the past.

For example, if Alice has a note of value five coins, and wants to send Bob four coins, then the transaction would look like this:

  • It would have one Spend description for the note she’s spending (in this example a note with a value of five coins) along with the unique nullifier for that note.
  • And it would have two Output descriptions: one for Bob of four coins, and one note as change for herself of one coin since the note used in the Spend description cannot be spent again.

To ensure privacy, a Spend description spends a note without revealing which note was spent through the help of a zero knowledge proof (specifically zk-SNARK Groth16 Sapling proof). The Output description similarly creates an encrypted note with a zero-knowledge proof that the newly created note was created correctly. The circuit construction for these proofs is taken from Sapling primitive gadgets which in turn were constructed using the bellman circuit building tool.

While explaining zk-SNARKs is outside the scope of this paper, for readers who want to learn more, zk-SNARK construction can be broken up into these 5 steps:

  1. Computation
  2. Arithmetic Circuit
  3. R1CS (rank 1 constraint system)
  4. QAP (quadratic arithmetic program)
  5. SNARK

For learning more about steps 1-4 we recommend this tutorial by Stefan Demil from Decentriq, and for step 5 we recommend this excellent tutorial by Maksym Petkus.

The structure of an Iron Fish transaction is constructed with these parts:

  • Transaction Fee: The fee (in plaintext) that’ll go to any miner that successfully includes this transaction in a block.
  • Spends: The list of Spend Descriptions.
  • Outputs: The list of Output Descriptions.
  • Binding Signature: A binding signature that both signs the transaction and is used to verify that it balances — meaning that it did not destroy or create money out of thin air, and that indeed all the funds in the spend descriptions minus the funds in the output descriptions equal to transaction fee. The message that is signed here is the transaction hash, which is a blake2b hash of the serialized transaction fee, spend descriptions and output descriptions.

Remember that the miner’s reward for mining a block is also a type of a transaction. We’ll go over the miners reward transaction and cover all these transaction concepts in greater detail further along in this section.

Spend Description

The Spend description is a part of the transaction that spends notes associated with an account. The goal of the Spend description is to spend notes without revealing which notes were actually spent with the help of zero-knowledge proofs (specifically Groth16 zk-SNARK type proofs).

The high level overview of the Spend description is that it spends a note by using a zero knowledge proof to prove the following:

  1. it is attempting to spend a note that the spender can decrypt
  2. this note exists in the Merkle Tree of Notes
  3. the value commitment (cm) for that note was constructed using the true value of that note
  4. the revealed nullifier is the unique nullifier for that note and was constructed correctly
  5. the signature maps to the spender’s authorization key

It does all this by having some public data that is needed to verify the proof and balance the transaction (more on this in the Transaction Balancing section), as well as the proof.

The structure of the Spend description looks like this:

ElementDescription
cvcvvalue commitment of the note being spent
rtrtroot anchor – for the Merkle tree of notes root that was used to generate the proof
nfnfnullifier for the note
rkrkrandomized public key for the authorization key (ak)
sigsigsignature for the transaction hash
proofproofzk-SNARK proof that allows one to hide the private values needed to validate

The cv is the value commitment (as a Pedersen commitment) for the note. It’s computed during the construction of the Spend description as:

cv=vGv+rcvGrcvcv = v * G_v + rcv * G_{rcv}

Where v is the value of the note, GvG_v is the generator point used for the value, rcv is the randomness to further obscure the value commitment hash, and GrcvG_{rcv} is the generator point used for the randomness.

The rt is the root anchor to specify which Merkle root was used to construct the zero knowledge proof. The proof will validate that there is a note that exists in the tree with that specified Merkle root. It is the miner’s job however to make sure that that Merkle root is one that is associated with a valid tree.

The nf is the nullifier, and it is unique to the note. The nullifier’s construction is verified in the proof, but once again it is the miner’s job to check that this nullifier has not been revealed in the past. The nullifier is computed by utilizing the blake2s hash function, the note commitment (cm), the position of the note being spent in the Merkle tree, and the nullifier deriving key (nk):

nf=blake2s(nkcm+note_positionGnullifierposition)nf = blake2s(nk \enspace | \enspace cm + note \_ position * G_{nullifierposition})

Where | denotes creating one byte array to hold both elements together.

The rk is the randomized public key that is used to sign the spend description. It’s randomized so that nothing is revealed from a single authorization key being used multiple times to sign various spend descriptions. The proof contains information about the actual authorization key and proves it’s valid transformation into a randomized key.

rsk==askαrsk == ask * α

Where rsk is the private key of the randomized public key, ask is the private key of the authorization key, and α is the randomness

rk==(ask+α)Gspendingkeygeneratorrk == (ask + α) * G_{spendingkeygenerator}

(which is the same as rk=αGspendingkeygenerator+akrk = α * G_{spendingkeygenerator} + ak since ak=askGspendingkeygeneratorak = ask * G_{spendingkeygenerator})

The sig is the signature that signs the transaction hash of the transaction the Spend description is in using the randomized key (rk).

And finally, we have the proof, which is a Groth16 zk-SNARK proof verifying in zero-knowledge the validity of the entire Spend description.

How is the proof generated?

The private parameters that are used to generate the proof (and are not revealed afterwards) are:

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

The merkle path is the Merkle path from the given root (the rt, root anchor) to the note being spent (specifically its note commitment), using Pedersen hashes. The proof verifies that the path is valid and correct, and that the given position is the correct position for the note’s commitment in the Merkle tree at the lowest level, (you can think of the position like an entry in an index).

The gdg_d is the diversifier (converted into an affine point on the Jubjub curve) of the sender, and pkdpk_d being the transmission key of the sender. The proof checks that gdg_d is not of small order and that pkdpk_d was properly computed.

Remember that pkd=gdivkpk_d = g_d * ivk (the incoming view key). Even though the incoming view key isn’t passed in here directly, we have everything we need to recompute it since ivk is derived from hashing (using the blake2s hash function) the authorization key (ak) and nullifier deriving key (nk) along with some params. We don’t have the nullifier deriving key (nk) directly here either, but we can derive it using the passed in proof authorization key (nsk) since nk=Gproof_generation_keynsknk = G_{proof\_generation\_key} * nsk.

Also remember that the value commitment is computed as cv=vGv+rcvGrcvcv = v * G_v + rcv * G_{rcv} and so we pass in the value (v) and the randomness for the value (rcv) into the proof to validate the construction of the value commitment. The note commitment (cm) is a Pedersen commitment (resulting in a full point) of the note’s contents (value(v), gdg_d, pkdpk_d) and the randomness used for the note commitment (rcm) cm=pedersen_hash(v,gd,pkd)+rcmGnotecommitmentrandomnesscm = pedersen\_hash(v, g_d, pk_d) + rcm * G_{notecommitmentrandomness}

The alpha (α) along with the authorization key (ak) is used to construct the randomized public key that is used to sign the spend description. Here in the proof we verify that that the randomized key was created correctly by verifying that:

rk=αGspendingkeygenerator+akrk = α * G_{spendingkeygenerator} + ak

Finally, we check that the nullifier is computed correctly. The proof first checks that nk=nskGproof_generation_keynk = nsk * G_{proof\_generation\_key} and then checks that the nullifier was indeed computed as:

nf=blake2s(nkcm+note_positionGnullifierposition)nf = blake2s(nk \enspace | \enspace cm + note \_ position * G_{nullifierposition})

In summary, the proof checks:

  1. Note Commitment Integrity
  • Check that the note commitment (cm) is derived from gdg_d, pkdpk_d, valuevalue, and rcmrcm
  1. Merkle Path Validity
  • Check that the Merkle path is valid from the given merkle root to the leaf (that this note exists in a given tree)

    3.Value Commitment

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

    4.Nullifier

  • Check that the nullifier is derived from nk (the owner’s nullifier deriving key), the cm (note commitment) and position

    5.Random Authorization Key

  • Check that the random authorization key (rk) that is used to sign the transaction is correctly derived from the spend authorization key (ak) and alpha (α) for randomness.

How is the proof verified?

In order to verify the proof, we simply need to pass in public parameters that validate all of the above mentioned statements.

The public variables necessary for the Spend description proof verification are most of the other fields of the Spend description:

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

Given these parameters, the proof will return a True/False statement whether or not all of the above statements are true given these public inputs.

Output Description

The Output description is the part of the transaction that produces new notes. The note it produces is encrypted such that only the holder of the incoming view key for the recipient and holder of the outgoing view key for the sender can decrypt the note. It also contains a zero-knowledge proof (also Groth16 zk-SNARK proof) that verifies that the newly created note was created correctly, with the correct value.

The structure of the Output description contains these fields:

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key (more on this in Note Encryption and Decryption)
CencC^{enc}encrypted plaintext of the note
CoutC^{out}encrypted blob that allows the holder of the viewing key to decrypt a decryption key for CencC^{enc} (more on this in Note Encryption and Decryption)
proofproofthe zero-knowledge proof

Below is a deeper dive into these fields.

The cv is the value commitment (as a Pedersen commitment) of the note being created, computed as:

cv=vGv+rcvGrcvcv = v * G_v + rcv * G_{rcv}

Where rcv is the randomness for the value commitment and is a private parameter to the zero-knowledge proof to validate this computation.

The cm is the note commitment (as a Pedersen commitment) for the new note being created that is added to the Merkle tree of notes by the miner who mines the transaction containing this Output description. It is computed as:

cm=pedersen_hash(v,gd,pkd)+rcmGnote_commitment_randomnesscm = pedersen\_hash(v, g_d, pk_d) + rcm * G_{note\_commitment\_randomness}

Where rcm is is the note commitment randomness used in this Pedersen commitment computation, and verified in the zero-knowledge proof.

The epk is the ephemeral public key that is used to facilitate the recipient of the note decrypting it.

The CencC^{enc} is the actual encrypted note that results as part of this Output description. It is encrypted such that the recipient’s incoming view key can decrypt it.

The CoutC^{out} is an encrypted blob of data to facilitate the holder of the sender’s outgoing key to decrypt the encrypted note.

And finally we have the proof (Groth16 zk-SNARK proof) for the Outgoing description that validates all these public parameters against the private ones that were used to create them. For more on the keys and notes discussed above, see the Note Encryption and Decryption section.

How is the proof generated and verified?

The Outgoing description proof is a lot less complicated than the Spend description proof. A lot of the terms you’ll see here will be familiar to the ones you’ve seen in the Spend description proof generation.

The private parameters that are used to create the proof are:

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 — a random number chosen by the sender (more on this in the Note Encryption and Decryption section

And the public parameters that are used to verify the proof are

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key

The proof validates that:

  1. gdg_d for the recipient is not of small order and that the ephemeral public key was computed as: epk=gdeskepk = g_d * esk
  2. That the value commitment (cm) is properly computed as a Pedersen commitment of: cm=pedersen_hash(v,gd,pkd)+rcmGnote_commitment_randomnesscm = pedersen\_hash(v, g_d, pk_d) + rcm * G_{note\_commitment\_randomness}

Adding a Merkle Tree Note from the Outgoing description

The outputs in the Output description are stored as a Merkle Tree Note in addition to being part of the transaction. A Merkle Tree Note consists of these fields taken from the Output description:

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

Transaction Balancing

So far, we went over how zero-knowledge proofs are used to prove ownership of an existing note in order to spend it or create valid new notes, but we’re still missing verifying one of the most important rules in cryptocurrencies: no coins can be created or destroyed in a transaction. Validators still need to verify that the transaction balances, meaning that the sum of all the funds being spent, minus the funds being created, equals the transaction fee (or zero if there is no transaction fee).

inputvaluesoutputvalues=transactionfeeinput \enspace values - output \enspace values = transaction \enspace fee

Balancing a transaction happens through the value commitments in the Spend and Output descriptions and the binding signature in the transaction. Remember that the structure of the Iron Fish transaction consists of these parts:

  • Transaction Fee: the fee that’ll go to any miner that successfully includes this transaction in a block
  • Spends: the list of Spend Descriptions
  • Outputs: the list of Output Descriptions
  • Binding Signature: a binding signature that both signs the transaction and is used to verify that it balances

And both the list of Spend descriptions and list of Output descriptions contain their appropriate value commitments. Remember that a value commitment is constructed as a Pedersen commitment in this format:

cv=vGv+rcvGrcvcv = v * G_v + rcv * G_{rcv}

The transaction is cryptographically signed by a binding validating key (bvk) resulting in the binding signature in the transaction. The binding validating key is constructed by adding all the randomness (e.g. the rcv values) from the input value commitments, and subtracting all the randomness from the output value commitments. It becomes more clear why this signature is necessary to balance a transaction if we first try to balance it without it.

As an example, say we have a transaction with two inputs, and two outputs:

Inputs
cv1=v1Gv+rcv1Grcvcv1 = v1 * G_v + rcv1 * G_{rcv}
cv2=v2Gv+rcv2Grcvcv2 = v2 * G_v + rcv2 * G_{rcv}
Outputs
cv3=v3Gv+rcv3Grcvcv3 = v3 * G_v + rcv3 * G_{rcv}
cv4=v4Gv+rcv4Grcvcv4 = v4 * G_v + rcv4 * G_{rcv}

The rule that we have to follow, is that inputvaluesoutputvalues=transactionfeeinput \enspace values - output \enspace values = transaction \enspace fee. Since the generator points (such as GvG_v and GrcvG_{rcv}) are the same for all value commitments, we can safely add all the value commitments from the inputs and subtract them from the outputs and simplify. Remember that since these operations are on an elliptic curve, we are not going to perform division operations as that would be logarithmically hard. Instead, we’ll multiply the transaction fee by the same generator point as the values (G_v) to check equality.

InputcommitmentsOutputcommitments=Gv(v1+v2v3v4)+Grcv(rcv1+rcv2rcv3rcv4)Input \enspace commitments - Output \enspace commitments = \\ G_v * (v1 + v2 - v3 - v4) + G_{rcv} * ( rcv1 + rcv2 - rcv3 - rcv4)

We know that the sum of all the input values minus the sum of all the output values should be the transaction fee, meaning that:

Gv(v1+v2v3v4)=Gv(transaction_fee)G_v * (v1 + v2 - v3 - v4) = G_v * (transaction\_fee)

There is still that second part of the equation that deals with randomness that wasn’t addressed:

Grcv(rcv1+rcv2rcv3rcv4)G_{rcv} * (rcv1 + rcv2 - rcv3 - rcv4)

This part of the equation is the binding validating key, which acts as a public key with which we can verify the transaction signature. The private key counterpart to bvkbvk that was used to sign the transaction is bskbsk (the binding signing key) which is the sum of all the randomness from the input descriptions minus the sum of all the randomness in the output descriptions.

For this example:

ElementDescription
bsk(bindingsigningkey)bsk \enspace (binding \enspace signing \enspace key)=rcv1+rcv2rcv3rcv4= rcv1 + rcv2 - rcv3 - rcv4
bvk(bindingvalidatingkey)bvk \enspace (binding \enspace validating \enspace key)=Grcv(rcv1+rcv2rcv3rcv4)=Grcv(bsk)= G_{rcv} * (rcv1 + rcv2 - rcv3 - rcv4) \\ = G_{rcv} * (bsk)

The transaction doesn’t reveal the binding validating key (bvkbvk) directly (the transaction only contains the cryptographic signature of the transaction signed by the binding signing key), however, it can be derived from the publicly available information.

During transaction validation, the validator computes the sum of all input value commitments, minus the sum of all output value commitments, minus the transaction fee multiplied by the value commitment generator point. For our example, this step would look like this:

(Gvv1+Grcvrcv1+Gvv2+Grcvrcv2)(Gvv3+Grcvrcv3+Gvv3+Grcvrcv3)(G_v * v1 + G_{rcv} * rcv1 + G_v * v2 + G_{rcv} * rcv2) – (G_v * v3 + G_{rcv} * rcv3 + G_v * v3 + G_{rcv} * rcv3)

equivalent to:

Gv(v1+v2v3v4)+Grcv(rcv1+rcv2rcv3rcv4)Gv(transaction_fee)G_v * (v1 + v2 - v3 - v4) + G_{rcv} * (rcv1 + rcv2 - rcv3 - rcv4) - G_v * (transaction\_fee)

Since a valid transaction would have Gv(v1+v2v3v4)=Gv(transaction_fee)G_v (v1 + v2 - v3 - v4) = G_v (transaction\_fee) the validator computes the binding validating key as:

Gv(v1+v2v3v4)+Grcv(rcv1+rcv2rcv3rcv4)Gv(transaction_fee)=bvkG_v * (v1 + v2 - v3 - v4) + G_{rcv} * (rcv1 + rcv2 - rcv3 - rcv4) - G_v * (transaction\_fee) = bvk

If indeed all the values of the input descriptions minus all the values of the output descriptions equal transaction fee, then bvkbvk must equal the left over randomness:

bvk=Grcv(rcv1+rcv2rcv3rcv4)bvk = G_{rcv} ( rcv1 + rcv2 - rcv3 - rcv4)

To validate that the transaction balances the validator checks that computed bvkbvk is indeed the public key corresponding to the transaction signature that signed the transaction hash. This means that the sender of the transaction must have used the same bvkbvk with a corresponding private key bskbsk to sign the transaction.

Finalstep:verifysignature(bvk,transactionhash)Final \enspace step: \enspace verify \enspace signature \enspace (bvk, \enspace transaction \enspace hash)

That is all that is necessary to check that the transaction balances since the bskbsk used to sign the transaction hash must have been the sum of all randomness from the input descriptions minus the sum of all randomness of the output descriptions (in this example bsk must have been rcv1+rcv2rcv3rcv4rcv1 + rcv2 - rcv3 - rcv4) as that is the only valid solution (called an opening) to this overall equation which is still in the format of a Pedersen commitment. It is not possible for there to be any other solution or opening since Pedersen commitments have a property that there can only be one opening per commitment.

Transaction Verification

The last section went over how to balance a transaction — to make sure that no coins were created or destroyed as part of that transaction. Balancing is just one step in the verification process.

In whole, a validator must perform a series of checks to validate a transaction:

  1. Verify all the zero-knowledge proofs against the public parameters from the Spend description
  2. Verify all the zero-knowledge proofs against the public parameters from the Output description
  3. Check that the transaction balances
  4. Check that every signature in the Spend description signed the transaction hash
  5. Check that the root anchors (rtrt) in all the Spend transactions are valid past Merkle tree roots on the validator’s Merkle tree
  6. Check that none of the nullifiers in the spend descriptions were revealed in the past

Miner Reward Transaction

As mentioned above, an important rule of cryptocurrencies is that no coins can ever be created or destroyed. Except in special cases where the protocol does allow for new coins to be created out of thin air. This is what happens in the special form of a transaction called the Miner Reward transaction. A Miner Reward transaction is a special transaction that awards the miner a set amount for mining the block as well as the sum of all the transaction fees for that block. The exact set amount for mining a block is variable, as described in the previous section on mining and coin emission schedule. This section will go over how such a transaction is made.

The Miner Reward transaction looks a lot like a regular transaction, except that it is stored in the block header (and not the block body), contains no Spend description, and has a negative transaction fee. This negative transaction fee contains the set amount allotted for the miner for mining the block as well as the sum of all transaction fees in the block body. Any number of Output descriptions can exist on this transaction with output values adding up to the allotted amount. Remember that a transaction fee on a transaction is in plaintext, and any validator can verify that inputvaluesoutputvalues=transactionfeeinput \enspace values - output \enspace values = transaction \enspace fee.

Let’s say that a Miner awards itself five coins for mining a block, then that Miner Reward transaction would look like this:

Transaction(Miner award allotted_amount = 5 $IRON)
SpendsNone
Output(s):Output Description(s)
Transaction fee-1 * allotted_amount
Binding Signature

Note that this transaction will balance with the negative transaction fee. The miner is able to preserve their privacy by making a transaction with all the privacy guarantees of a regular transaction.

For validation, all other validators can easily check that the Miner reward transaction has the appropriate alloted_amount by checking that the values of the spend description minus the values of the output descriptions equals to the negative allotted amount. Validators can also verify that the allotted amount is exactly the block reward plus transaction fees associated with transactions in that block.

Note Encryption and Decryption

Lastly, this section will go over how exactly the recipient of a transaction is able to decrypt a newly created note that is sent to them in the Output description. The note in the Output description is encrypted such that the recipient’s incoming view key and the sender’s outgoing view key is able to decrypt it. This technique shares a lot of commonalities with the Diffie-Hellman key exchange algorithm.

Remember that the note plaintext (np) is made up of:

  • (pkdpk_{d}, d): the transmission key and the diversifier of the recipient’s address
  • v: the plaintext value that the note holds
  • rcm: note randomness used to generate the Pedersen commitment for the note
  • memo: a 32-byte memo field

The Output description stores this note in its encrypted form as CencC^{enc}.

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key
CencC^{enc}encrypted plaintext of the note
CoutC^{out}encrypted blob that allows the holder of the viewing key to decrypt a decryption key for C^{enc}
proofproofthe zero-knowledge proof

Note Encryption by the Sender

The sender has to know the recipient’s public key, which is a combination of the transmission key and the diversifier (dd, pkdpk_d). With this information the sender’s wallet can create a shared_secret with which to encrypt the note such that the recipient’s incoming view key can decrypt it. Let’s go over how the sender’s wallet creates this shared secret.

  1. The sender’s wallet generates a random number and uses it to create an ephemeral secret key (esk) by converting this number to a scalar on the Jubjub curve.

  2. It then creates an ephemeral public key (epk) by using scalar multiplication between the diversifier of the recipient represented as a field point and esk. This ephemeral public key is a publicly known component of the Output description and is seen by everyone.

    1. epk=eskgdepk = esk * g_d
    2. Note: gdg_d is the diversifier, dd, represented as a field a point on the Jubjub curve so we can do scalar multiplication (elliptic curve multiplication) using it.
  3. It then derives a shared_secret using Diffie Hellman Key Exchange between esk and pkd (diversified public address of the recipient):

    1. shared_secret=eskpkdshared\_secret = esk * pk_d
  4. The note is then encrypted using the shared_secret and a form of symmetric encryption (specifically ChaCha20Poly1305 symmetric encryption algorithm).

Note Decryption by the Recipient

The recipient’s wallet can then decrypt the encrypted note in the Outgoing description using the recipient’s incoming view key. Remember that the recipient’s transmission key (pkdpk_d) is derived from the diversifier (converted to a point on the Jubjub curve as gdg_d) and the incoming view key: pkd=gdivkpk_d = g_d * ivk

The recipient’s wallet can then calculate the shared secret using the epk (ephemeral public key) provided on the Outgoing description: shared_secret=epkivkshared\_secret = epk * ivk

This is the same shared_secret that the sender’s wallet used. Note that:

epk=eskgdpkd=gdivkepk = esk * g_d\\pk_d = g_d * ivk

The recipient’s wallet calculates:

shared_secret=epkivk=eskgdivkshared\_secret = epk * ivk = esk * g_d * ivk

The sender’s wallet calculates:

shared_secret=eskpkd=eskgdivkshared\_secret = esk * pkd = esk * g_d * ivk

Now the recipient’s wallet can use the same symmetric encryption algorithm (ChaCha20Poly1305) to use the shared_secret and decrypt the CencC^{enc} field on the Output description.

Note Decryption by the Sender Using the Sender’s Outgoing View Key

If at some later time after the transaction has been sent, the sender’s wallet needs to recreate the transaction history and decrypt the notes it sent in the past, it can do that with the help of the outgoing view key.

Remember that initially the sender’s wallet was able to encrypt the note plaintext (using the symmetric encryption algorithm ChaCha20Poly1305) into CencC^{enc} by calculating a shared secret as shared_secret=eskpkdshared\_secret = esk * pk_d.

Since the sender’s wallet doesn’t have access to either eskesk or pkdpk_d after the transaction has been sent, that information is stored in the second encrypted field on the Outgoing description: the CoutC^{out} field. This field is created by the sender of the transaction at the time it is made and stored on the Output description.

The CoutC^{out} field is an encryption of (eskesk, pkdpk_d) concatenated together, also using the symmetric ChaCha20Poly1305 encryption algorithm. The symmetric key used for CoutC^{out} is calculated as:

symmetric_encryption_key=blake2bhash(ovk,cv,cm,epk)symmetric\_encryption\_key = blake2b_hash(ovk, cv, cm, epk)

Where ovk is the wallet’s outgoing view key, and the rest of the fields are taken from the Output description.

Output Description:

ElementDescription
cvcvvalue commitment
cmcmnote commitment
epkepkephemeral public key
CencC^{enc}encrypted plaintext of the note
CoutC^{out}encrypted blob that allows the holder of the viewing key to decrypt a decryption key for CencC^{enc}
proofproofthe zero-knowledge proof

At any given time, the holder of the outgoing view key (e.g. a wallet) can recreate the symmetric_encryption_key to decrypt the CoutC^{out} field to retrieve (eskesk, pkdpk_d). Then, using (eskesk, pkdpk_d) the wallet can recreate the shared_secret=eskpkdshared\_secret = esk * pk_d and decrypt the CencC^{enc} field to finally retrieve the plaintext.