Panther's unique UTXO model
UTXOs are used to represent:
Digital assets (zAssets)
zAccounts
To spend or open a zAsset UTXO the spender must provide a private key
This requirement for a unique private key for each UTXO contributes to the Protocol's privacy layer
UTXOs underpin the Protocol's privacy-enhancing solution. A UTXO is the hash of a data package committed to a leaf of a Merkle Tree. The commitment of a UTXO on-chain and the data contained therein, is verifiable thanks to a SNARK proof generated at the time of the commit.
The commitment of a UTXO data package on-chain provides an immutable record of a transaction. However, data contained within the UTXO can only be accessed by providing the correct cryptographic solution.
UTXOs are used in different ways within the Protocol, which makes the "unspent transaction" a misnomer in certain instances. Let’s first consider the model that does create "change" or an unspent amount from a transaction by creating a UTXO worth the difference between its value and the amount spent: the zAsset UTXO.
zAssets represent digital assets as UTXOs. Every UTXO has one “owner” or zAccount able to spend it. This is achieved by including a public (spending) key in the generation of the UTXO commitment, for which the corresponding private (spending) key is only known by the recipient.
Spending UTXOs
During a spend event, the zAccount holder is either receiving an input UTXO, as in they are the recipient of a digital asset; or they are creating an output UTXO, as in they are spending/sending a digital asset.
When Alice transfers a UTXO to Bob, she transfers the UTXO's read and spend rights to Bob. This means that only Bob's zAccount may open and read the UTXO or spend the UTXO.
If a zAccount holder receives a UTXO, they must successfully decrypt the UTXO’s commitment. If they are able to calculate the child private spending key (childPrivSpendingKey
), then they may spend the UTXO in the future, i.e. transfer ownership of all or part of the value of that UTXO.
This is how the Protocol protects the transactional privacy of the recipient. A transfer of a zAsset from sender to recipient is a non-interactive transfer i.e. the sender and recipient don’t have to communicate in order to facilitate the transaction. The only prerequisite is that the sender knows the recipient’s two root public keys (both spending and reading).
To access the receiver’s public keys, the sender’s zAccount looks up the user's zAccount registry. On zAccount activation, this registry establishes a link between the public root spending key, public reading key, and an EOA (externally owned account) i.e. a wallet address associated with the user. This allows an untracable method for the sender to access the receiver's data.
Shielded Pool contract responsibilities on spend
The Shielded Pool contract performs several checks when Alice sends a UTXO to Bob, it:
Verifies that there is no double-spend of the UTXO
nullifierHash is calculated correctly and has not been used before
Verifies the ZK proof:
the opening of the UTXO commitment is correct
the Merkle proof is correct
the derived identity is entitled to spend the UTXO
Child Public Spending Key is derived from the Child Private Spending Key
Child Private Spending Key is the same input used in the opening of the UTXO commitment
A zAsset transfer is a non-interactive, asynchronous spend/receive event that involves two UTXO models, the zAsset and the zAccount.
Let's consider the outcome of a spend event that results in the transfer of 1 zAsset UTXO.
The Shielded Pool supports the spending and creation of multiple UTXOs, all within a single transaction using a single proof.
As a result of the spender initiating a zAsset transfer to a different zAccount, at least 2 new UTXOs are committed to the Merkle Tree:
Token of transaction: zAsset UTXO created to the value of the transfer amount
With a unique key based on the intended recipient's public key
Token of transaction: zAsset UTXO representing any change, if a 0 amount i.e. no "unspent" value remains, then no UTXO is created
If Alice has a UTXO representing 5 zETH and sends Bob 2 zETH, the unspent amount is represented by at least 1 UTXO to the value of 3 zETH
zAccount UTXO: the sender's zAccount UTXO is updated to deduct the value of asset sent from balance, fee deduction in zZKP, and an increment to their PRP reward
As a result of the receiver (asynchronously) unlocking the UTXO with their key:
zAccount UTXO: receivers zAccount UTXO is updated to reflect the value of asset received
In the case that the sender chooses to use a Relayer service to increase the privacy set of the transaction, further UTXO updates are implemented to pay the Relayer fee.
The dApp maintains the user's balance in one UTXO for optimal clarity and efficiency. The preference is to have a single large UTXO instead of numerous smaller ones. This structure allows users to engage any part of their balance in just one transaction, mitigating the necessity for a supplementary MASP transaction to consolidate smaller UTXOs. However, certain circumstances don't permit the merging of UTXOs. For instance, when another user transfers zAsset to the user, the sender generates an independent UTXO, regardless of the recipient's existing UTXOs in the zAsset sent. Similarly, in a zSwap, the user always gets a new UTXO, irrespective of any existing UTXOs in the received asset. In such scenarios, no attempt to merge UTXOs takes place. When handling a deposit transaction, the dApp merges the available largest by amount UTXO with the deposit amount, rather than initiating a new UTXO. As a result, the new UTXO displays the total amount collected. During both deposit and regular transactions, advanced settings should allow users to select the input UTXO for the merge. This becomes particularly relevant when a user intends to transfer tokens to a CEX/DEX, and they must supply a 'proof of innocence' to demonstrate the origin of the funds.