Phoenix
Phoenix is the privacy-preserving transaction model used by Dusk. In privacy-preserving blockchains there are no accounts or wallets at the protocol layer. Instead, coins are stored as a list of UTXOs with specific quantities and criteria for spending. Transactions are created by consuming existing UTXOs and producing new ones in their place. In the Phoenix model, UTXOs are called notes.
Phoenix is designed to balance privacy with regulatory compliance. Unlike anonymity protocols, where transaction details are entirely hidden, Phoenix ensures that the receiver can provably see who the sender is, but no one else can. This allows users to comply with regulations while maintaining their privacy from external observers.
The network tracks all notes ever created by storing their hashes in the leaves of a Merkle tree of notes. When a transaction is validated, the network includes the hashes of the new notes in the leaves of this tree.
All notes in the network have the same structure:
N = {type, com, enc, npk, R, encsender }.
The parameters above correspond to the following:
type
indicates the type of the note, either transparent or obfuscated;com
is a commitment to the value of the note;enc
is an encryption of the opening ofcom
that can be decrypted using the recipient’s view key;npk
is the note’s public key, whose associated private keynsk
can only be computed by the recipient of the noteR
is a point in the Jubjub subgroupJ
that allows the recipient to computensk
and also identify that he is the recipient of the transaction.encsender
is the encryption of a public key owned by the sender of the note that allows the recipient of the note to identify them.
To prevent double spending, transactions include a list of deterministic values called nullifiers, one for each note being spent. The network nodes must check that nullifiers were not used before. This ensures that the network knows that some notes are nullified and can no longer be spent, but without linking the nullifiers to specific notes.
Transactions
Phoenix transactions consist of two parts, a header (txmetadata
) which includes metadata, and the payload (txpayload
) with the actual contents of the transaction that have been set by the transaction sender.
A transaction in Dusk is composed by the following elements:
tx_skeleton
=[root, nullifiers, notes, deposit, max_fee]
tx_payload
=[txskeleton,data]
tx
=[txpayload,payload_hash, tx_proof]
tx_metadata
=[timestamp, block_height, status, type, tx_hash, gas_price, gas_spent, tx_fee, positions]
The sender first sets txskeleton
, which contains the following data:
root
is a recent root of the Merkle tree of notesnullifiers
is the list of deterministic values that nullify the notes being spentnotes
is the list of new notes being minted (each of them with the structure as described above);deposit
is the amount that will be sent to a contract -max_fee
is the maximum fee the sender is willing to pay for the execution of the transaction.
The tx_payload
includes all the above information, plus a data
field that contains additional information needed for transaction execution, such as smart contract IDs, call data, or the stealth address for fee change.
Once tx_payload
is set, the sender calculates payload_hash
, which is the hash of all elements in tx_payload
. These elements, along with the hash, are used to compute tx_proof
, a zero-knowledge proof that verifies the transaction’s adherence to network rules.
tx
consists of all the parameters that are set by the sender of the transaction, txmetadata
contains the information set by the network once the transaction is processed and included in a block:
timestamp
is the the date and time the transaction was processedblock_height
is the number of the block the transaction was included instatus
indicates if the transaction was successful or nottx_hash
is the hash of txgas_price
is the price at which gas was paidgas_spent
is the amount of gas spentfee
is the amount of $DUSK spent in the transaction, which is the result ofgas_price
×gas_spent
positions
is the list of positions of the newly minted notes in the Merkle tree of notes.
Protocol keys
Each note in Phoenix is associated with a unique public key instead of using the static public key of the recipient. Phoenix uses two-element keys, allowing users to delegate the process of scanning for notes addressed to them.
Keys in Phoenix can be categorized into user’s static keys and note keys.
User static keys
– Secret key: sk = (a,b)
- used for decryption and signing data.
– Publickey: pk=(A,B)
- derived from the secret key, used to encrypt data or verify signatures.
– View key: vk = (a, B)
- enables viewing but not spending of funds.
Note keys
In addition to the user static keys, Phoenix assigns a one-time key pair to each note issued in the network, computed using the Diffie–Hellman key exchange protocol. The recipient’s Diffie–Hellman partial key will be the first part of its public key, A
, whereas the sender will use a fresh key.
The sender of a note will attach to it the note public key npk
and the partial Diffie–Hellman R
used to create npk
.
The recipient can identify whether the note was sent to them using only the recipient’s view key, and not its whole secret key.
By using the note’s public key, a user can delegate the job of scanning the different transactions of the network to retrieve their notes by sharing their view key vk
with an external entity, which we call a network-listening helper. Thus, the user could delegate the scanning of all transactions to a different entity by sharing (a,B)
with the helper. Even with that information, such an entity could not spend R
’s money, since they can not derive skR
without the second part of R
’s private key.
On the other hand, the note’s secret key can only be computed by the recipient of the note, since they are the only ones holding the whole secret key sk = (a,b)
. The recipient can use the note secret key to spend the note.
Privacy & Compliance
Phoenix ensures that transaction details, such as the sender’s public key, are accessible only to the intended recipient, enabling users to prove the origin of their funds without public disclosure. This approach aligns with regulations like TRF, AMLD5, MiCA, and GDPR, mitigating compliance risks associated with public transactions.
Phoenix transactions include a zero-knowledge proof that the sender is indeed the owner of the associated private key and wallet. If the sender’s key was incorrect or if they were not the actual initiator of the transaction, the transaction would be invalid and discarded by the network.