Transaction Parameters
High-Level Transaction Parameters:
- 1.Verifier
- 2.Verifier App
- 3.Input Utxos
- 4.Output Utxos
- 5.Relayer
- 6.Signer
- 7.Accounts
- 1.Sender Spl
- 2.Recipient Spl
- 3.Sender Sol
- 4.Recipient Sol
Verifier Selection:
We would probably select verifier_zero for a normal shield because it is fast and cheap (it only takes one transaction).
If we have a lot smaller UTXOs, we would want to select verifier_one, which allows for ten input UTXOs.
For app UTXOs, we would need to select verifier two, which is the only one that enables application UTXOs.
We want to shield 1 sol and 10 USDC.
Select Relayer: We do not need a relayer for a deposit
Select Utxos:
Let's assume we do not have any input UTXOs we want to merge yet, and for performance, we select verifier_zero, which takes two inputs and two outputs. Therefore, we set all input UTXOs to 0, meaning all amounts to 0.
Because we do not have any input UTXOs to pass the sum check, the public amount must be the same as the deposit UTXO. The compilation step in the Transaction class defines public inputs such as the public amount.
In a zero-knowledge proof circuit, every input must be set regardless of whether it is checked. Therefore we need to pass in dummy values. For UTXOs where amounts = 0, the circuit does not check the Merkle proof.
Input Utxos:
Parameter | Utxo0 | Utxo1 |
---|---|---|
| 0 | 0 |
| 0 | 0 |
Amounts for input UTXOs used to fill inputs up for the circuit need to be 0. The rest of the UTXO can be random since the UTXO is not checked.
Output Utxos:
Parameter | Utxo0 | Utxo1 |
---|---|---|
| 1_000_000_000 lamports | 0 |
| 10 | 0 |
| USDC | 0 |
| Poseidon(31 random Bytes, shielded private Key) | 31 random Bytes |
| User PublicKey | Random |
| 0 | 0 |
| 0 | 0 |
| 0 | 0 |
*4. By hashing the random bytes, which add randomness to the commitment hash with the shielded private key, we can distinguish between our own UTXOs and UTXOs others sent to us. Others will not know our private key; therefore, they can only send us transactions with random blinding.
Public Inputs:
public Amount Spl = 10
SumOutput Spl = SumInputSpl + public amount Spl;
public Amount Sol = 1_000_000_000
SumOutput Sol = SumInputSol + public amount Sol;
Select Relayer: We need a relayer for an anonymous transfer.
Input Utxos:
Parameter | Utxo0 | Utxo1 |
---|---|---|
| 1_000_000_000 lamports | 0 |
| 10 | 0 |
| USDC | 0 |
| Poseidon(31 random Bytes, shielded private Key) | 31 random Bytes |
| User PublicKey | Random |
| 0 | 0 |
| 0 | 0 |
| 0 | 0 |
Output Utxos:
Parameter | Utxo0 | Utxo1 |
---|---|---|
| 999_900_000 lamports | 0 |
| 0 | 10 |
| 0 | USDC |
| Poseidon(31 random Bytes, shielded private Key) | 31 random Bytes |
| User Public Key | Recipient Public Key |
| 0 | 0 |
| 0 | 0 |
| 0 | 0 |
Public Inputs:
public Amount Spl = 0
SumOutput Spl = SumInputSpl + public amount Spl;
public Amount Sol = 21888242871839275222246405745257275088548364400416034343698204186575808395617
SumOutput Sol = SumInputSol + public amount Sol;
A transfer is essentially a withdrawal of the relayer fee to the relayer. In this example, we use a relayer fee of 100_000 lamports. To keep the sum of input and output UTXO amounts consistent, we must subtract the amount we want to withdraw. However, the circuit does not allow for negative numbers, and we need the additional operation to make deposits. The arithmetic inside the circuit is modulo the field size of the finite field the circuit is defined over. We can use this property to subtract by adding this very high number you see as the public amount sol above by creating a number larger than the field size. When creating a number larger than the field size, the modulo is triggered, reducing our value to the amount sol minus the relayer fee.
Computation of Public Sol Amount:
SumOutputUtxosSol = 999_900_000
SumInputUtxosSol = 1_000_000_000
bn254ScalarFieldSize = 21888242871839275222246405745257275088548364400416034343698204186575808495617
public Amount Sol = SumOutputUtxosSol - SumInputUtxosSol + bn254ScalarFieldSize mod bn254ScalarFieldSize
SumOutputUtxosSol = SumOutputSol + public amount Sol;
const anchor = require("@coral-xyz/anchor");
const bn254ScalarFieldSize = new anchor.BN("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const relayerFee = new anchor.BN(100_000)
const sumInputAmount = new anchor.BN(1_000_000_000)
const sumOutputAmount = sumInputAmount.sub(relayerFee)
const publicAmountSol = sumOutputAmount.sub(sumInputAmount).add(bn254ScalarFieldSize).mod(bn254ScalarFieldSize);
console.log(sumOutputAmount.toString() === sumInputAmount.add(publicAmountSol).mod(bn254ScalarFieldSize).toString());
Select Relayer: We need a relayer for truly private transfers and unshields.
Input Utxos:
Parameter | Utxo0 | Utxo1 |
---|---|---|
| 999_900_000 lamports | 0 |
| 0 | 0 |
| 0 | 0 |
| Poseidon(31 random Bytes, shielded private Key) | 31 random Bytes |
| User Public Key | Random |
| 0 | 0 |
| 0 | 0 |
| 0 | 0 |
Output Utxos:
Parameter | Utxo0 | Utxo1 |
---|---|---|
| 0 | 0 |
| 0 | 0 |
| 0 | 0 |
| 31 random Bytes | 31 random Bytes |
| Random | Random |
| 0 | 0 |
| 0 | 0 |
| 0 | 0 |
Public Amounts
public Amount Spl = 0
SumOutput Spl = SumInputSpl + public amount Spl;
public Amount Sol = 21888242871839275222246405745257275088548364400416034343698204186575808395617
SumOutput Sol = SumInputSol + public amount Sol;
Computation of Public Sol Amount:
SumOutputUtxosSol = 0
SumInputUtxosSol = 999_900_000
bn254ScalarFieldSize = 21888242871839275222246405745257275088548364400416034343698204186575808495617
public Amount Sol = SumOutputUtxosSol - SumInputUtxosSol + bn254ScalarFieldSize mod bn254ScalarFieldSize
SumOutputUtxosSol = SumOutputSol + public amount Sol;
const anchor = require("@coral-xyz/anchor");
const bn254ScalarFieldSize = new anchor.BN("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const relayerFee = new anchor.BN(100_000)
const sumInputAmount = new anchor.BN(999_900_000)
const sumOutputAmount = sumInputAmount.sub(relayerFee)
const publicAmountSol = sumOutputAmount.sub(sumInputAmount).add(bn254ScalarFieldSize).mod(bn254ScalarFieldSize)
console.log(sumOutputAmount.toString() === sumInputAmount.add(publicAmountSol).mod(bn254ScalarFieldSize).toString());
Name | Shield | Transaction/Unshield | Note |
---|---|---|---|
Verifier State | pda/not used | pda/not used | For verifiers which need two transactions because of the amount of data being sent.
Is created in the first transaction and closed after the second. |
sender spl | user | merkle tree pda | |
recipient spl | merkle tree pda | user | |
sender sol | user | merkle tree pda | |
recipient sol | merkle tree pda | user | |
relayer fee recipient | not used | relayers choice | The recipient of the relayer fee. |
escrow | ecrow pda | not used | is created and closed in the same transaction to deposit sol to the merkle tree without an extra transfer instruction |
token Authority | pda | pda | |
Leaves Pdas | pda | pda | Number depends on the verifier |
Nullifier Pdas | pda | pda | |
Last modified 20d ago