Let $\t{PoseidonHash}$ be defined as in the section PoseidonHash Function.
Let $G, H$ be the Pedersen commitment generators VALUE_COMMIT_VALUE
(src/sdk/src/crypto/constants/fixed_bases/value_commit_v.rs) and
VALUE_COMMIT_RANDOM (src/sdk/src/crypto/constants/fixed_bases/value_commit_r.rs),
and let $K$ be the NULLIFIER_K generator
(src/sdk/src/crypto/constants/fixed_bases/nullifier_k.rs).
This function transfers value by burning a set of coins $𝐂$, and minting a set of coins, such that the value spent and created are equal. Value conservation is enforced per token: for every token commitment, the sum of the input Pedersen value commitments must equal the sum of the output ones.
src/contract/money/src/client/transfer_v1/builder.rssrc/contract/money/src/client/transfer_v1/mod.rssrc/contract/money/src/client/transfer_v1/proof.rssrc/contract/money/src/entrypoint/transfer_v1.rssrc/contract/money/proof/burn_v1.zksrc/contract/money/proof/mint_v1.zkLet $\t{MoneyInput}, \t{MoneyOutput}$ be defined as in Inputs and Outputs.
Define the Money transfer function params $$ \begin{aligned} 𝐢 &∈ \t{MoneyInput}^* \ 𝐨 &∈ \t{MoneyOutput}^* \end{aligned} $$
{{#include ../../../../../src/contract/money/src/model/mod.rs:money-params}}
Let $π\t{mint}, π\t{burn}$ be defined as in ZK Proofs.
Each input $i ∈ 𝐢$ carries a Burn_V1 proof, and each output $o ∈ 𝐨$
carries a Mint_V1 proof. Additionally the contract verifies:
Burn_V1 proofs is computed as the function ID of the parent call, or
FuncId::none() if the transfer is a root call. This way coins carrying
a spend hook can only be spent by the contract function they point to.Mint_V1Using the Mint_V1 circuit, we are able to create outputs
in our UTXO set. It is used along with the Burn_V1 circuit in
MoneyFunction::TransferV1 where we perform a payment to some address
on the network.
Denote this proof by $π_\t{mint}$.
Circuit witnesses:
Circuit public inputs:
Circuit:
$$ C = \text{PoseidonHash}(P_x, Py, v, τ, h, u, b) $$ $$ V = vG + v{\text{blind}}H $$ $$ T = \text{PoseidonHash}(τ, τ_{\text{blind}}) $$
The 64-bit range of $v$ is enforced implicitly: $G$ is a short fixed
base (VALUE_COMMIT_VALUE) and its scalar multiplication (ec_mul_short)
only admits witnesses of at most $L_\t{value} = 64$ bits.
Burn_V1Using the Burn_V1 circuit, we are able to create inputs in
our UTXO set. It is used along with the Mint_V1 circuit in
MoneyFunction::TransferV1 where we perform a payment to some address
on the network.
Denote this proof by $π_\t{burn}$.
Circuit witnesses:
Circuit public inputs:
Circuit:
$$ P = xK $$ $$ C = \text{PoseidonHash}(\mathcal{X}(P), \mathcal{Y}(P), v, τ, h, u, b) $$ $$ N = \text{PoseidonHash}(x, C) $$ $$ V = vG + v{\text{blind}}H $$ $$ T = \text{PoseidonHash}(τ, τ{\text{blind}}) $$ $$ C' = \text{ZeroCond}(v, C) $$ $$ R = \text{MerkleRoot}(l, p, C') $$ $$ U = \text{PoseidonHash}(u, u_{\text{blind}}) $$ $$ Z = zK $$
ZeroCond is a conditional selection: f(a, b) = if a == 0 {a} else {b}.
We use this because the Merkle tree is instantiated with a fake coin of
value 0 and so we're able to produce dummy inputs of value 0.
Assuming a coin $C$ exists on the blockchain on leaf position $l$ and does not have a corresponding published nullifier $N$, it can be spent. To create the necessary proofs, Alice uses the known values of her coin $C$ and picks other values that are needed to create a new coin $C'$ that will be minted to Bob after $C$ is spent.
Values for Burn_V1:
Burn_V1 ZK proof using the existing known values
of her coin $C$ and the values picked above.Values for Mint_V1:
ZERO
if the coin does not have to call another contract.ZERO
if no data has to be passed.Mint_V1 ZK proof using the existing known values
and the values picked above.A single token blind $τ_\t{blind}$ is drawn for the whole call and reused for every token commitment, so that all inputs and outputs of the call commit to the same token.
After creating the proofs, Alice builds a transaction containing a
number of inputs that were created with Burn_V1 and a number of
outputs created with Mint_V1.
{{#include ../../../../../src/contract/money/src/model/mod.rs:money-params}}
This gets encoded into the Transaction format and the transaction is
signed with a Schnorr signature scheme using the $z$ secret keys chosen
when building the Burn_V1 proofs.
For MoneyFunction::TransferV1, the get_metadata, process_instruction
and process_update entrypoint phases
(src/contract/money/src/entrypoint/transfer_v1.rs) do the following:
get_metadata verifies one Burn_V1 proof per input against the
public inputs $(N, V_x, V_y, T, R, U, h, Z_x, Z_y)$ and one Mint_V1
proof per output against $(C, V_x, V_y, T)$, and gathers all the $Z$
signature public keys.process_instruction enforces the contract statement described above,
and produces a state update with the new nullifiers, the new on-chain
coins, and the new transaction-local coins.process_update inserts the nullifiers into the nullifier sparse
Merkle tree, and appends the new coins to the on-chain or
transaction-local coin Merkle trees.This function attaches a fee to a transaction so it gets included in
a block. Fees are paid in the native token, accumulate per block
height, and are later claimed together with the block reward via
Money::PoWRewardV1.
The call data begins with the u64 fee value, followed by the params.
A single Fee_V1 proof burns one input coin and mints one output coin
(usually the change) back to the same public key.
src/contract/money/src/client/fee_v1.rssrc/contract/money/src/entrypoint/fee_v1.rssrc/contract/money/proof/fee_v1.zk$$ \begin{aligned} \t{input} &∈ \t{MoneyInput} \ \t{output} &∈ \t{MoneyOutput} \ f_\t{blind} &∈ 𝔽q \ t\t{blind} &∈ 𝔽ₚ \ \t{fee} &∈ ℕ₆₄ \ \end{aligned} $$
The Fee_V1 circuit is a fusion of Burn_V1 and Mint_V1 for a
single input and output sharing the same public key $P = xK$ and token
$τ$. Besides the constraints of both circuits (with the input's spend
hook additionally constrained to be ZERO, since fee coins cannot be
hooked, and no ZeroCond applied to the input coin), the contract
verifies:
The remaining checks (valid merkle root, unused nullifier, unique coin) are as in Transfer.
A single signature is attached, using $\t{input}.Z$ as the signature public key.
This function mints the initial supply of the native token. It is only
valid when verified against the genesis block (height 0), and reuses
the Mint_V1 proofs for its outputs.
src/contract/money/src/client/genesis_mint_v1.rssrc/contract/money/src/entrypoint/genesis_mint_v1.rssrc/contract/money/proof/mint_v1.zk$$ \begin{aligned} \t{input} &∈ \t{MoneyClearInput} \ 𝐨 &∈ \t{MoneyOutput}^* \ \end{aligned} $$
A single signature is attached, using $\t{input}.Z$ as the signature public key.
This function mints the proof-of-work reward for a block, including the
fees accumulated for that height via Money::FeeV1. It reuses
the Mint_V1 proof for its output.
src/contract/money/src/client/pow_reward_v1.rssrc/contract/money/src/entrypoint/pow_reward_v1.rssrc/contract/money/proof/mint_v1.zk$$ \begin{aligned} \t{input} &∈ \t{MoneyClearInput} \ \t{output} &∈ \t{MoneyOutput} \ \end{aligned} $$
A single signature is attached, using $\t{input}.Z$ as the signature public key.
This function mints arbitrary tokens and coins for them, authorizing the mint via a child auth module call. The token ID is derived from the token attributes (see Token), where the parent authority is the function ID of the attached auth call.
src/contract/money/src/client/token_mint_v1.rssrc/contract/money/src/entrypoint/token_mint_v1.rssrc/contract/money/proof/token_mint_v1.zk$$ \begin{aligned} C &∈ 𝔽ₚ \ \t{note} &∈ \t{AeadEncNote} \ \end{aligned} $$
The call must have exactly one child call, acting as the auth module
(e.g. Money::AuthTokenMintV1). Let $h_\t{auth}$ be
the function ID of this child call. The TokenMint_V1 proof enforces:
Additionally the contract verifies the minted coin $C$ is unique, and the encrypted note $\t{note}$ carries the coin secrets to the receiver.
No signatures are attached. Authorization is delegated to the child auth module call.
This is an auth module for Money::TokenMintV1 which
authorizes token mints with a Schnorr signature from the token's mint
authority key. Token IDs are bound to their authority by deriving the
token attributes' user data from the authority's public key:
$\t{UD}τ = \t{PoseidonHash}(\mathcal{X}(\t{PK}\t{mint}), \mathcal{Y}(\t{PK}_\t{mint}))$.
src/contract/money/src/client/auth_token_mint_v1.rssrc/contract/money/src/entrypoint/auth_token_mint_v1.rssrc/contract/money/proof/auth_token_mint_v1.zk$$ \begin{aligned} τ &∈ 𝔽ₚ \ \t{PK}_\t{mint} &∈ ℙₚ \ \end{aligned} $$
The parent call must be Money::TokenMintV1. Let $h$ be the function
ID of this function itself. The AuthTokenMint_V1 proof enforces:
Money::TokenMintV1 call) commits to the derived token ID $τ$.Additionally the contract verifies the token mint is not frozen in the token freezes DB.
A single signature is attached, using $\t{PK}_\t{mint}$ as the signature public key.
This function permanently freezes a token so it can no longer be minted
via Money::AuthTokenMintV1. It is authorized by the
same mint authority key the token ID is bound to.
src/contract/money/src/client/auth_token_freeze_v1.rssrc/contract/money/src/entrypoint/auth_token_freeze_v1.rssrc/contract/money/proof/auth_token_freeze_v1.zk$$ \begin{aligned} \t{PK}_\t{mint} &∈ ℙₚ \ τ &∈ 𝔽ₚ \ \end{aligned} $$
Let $h$ be the function ID of Money::AuthTokenMintV1. The
AuthTokenFreeze_V1 proof enforces the same mint authority key
ownership and token ID integrity relations as
AuthTokenMint. Additionally the contract verifies
the token is not already frozen, and the state update freezes it in the
token freezes DB.
A single signature is attached, using $\t{PK}_\t{mint}$ as the signature public key.
This function burns (destroys) coins, permanently removing value from
circulation. The call only has inputs and no outputs; the value
committed in the inputs is destroyed. It reuses the
Burn_V1 proofs.
src/contract/money/src/client/burn_v1.rssrc/contract/money/src/entrypoint/burn_v1.rssrc/contract/money/proof/burn_v1.zk$$ 𝐢 ∈ \t{MoneyInput}^* $$
Identical to the input part of Transfer: valid coin merkle roots, unused nullifiers, and spend hook enforcement via the parent call's function ID. Since there are no outputs, no value conservation check is performed — the burned value is simply gone.
For each $i ∈ 𝐢$, a signature corresponding to the public key $i.Z$ is attached.