Transaction lifetime
Let be a transaction on the DarkFi network. Each transaction consists of multiple ordered contract calls:
Associate with each contract call an operator
contract_function. Each contract consists of arbitrary data which
is interpreted by the contract. So for example sending money to a
person, the transaction has a single call with
Money::Transfer
. To enforce a transaction fee, we can add another
call to Money::Fee
and now our transaction would have two calls:
.
To move money from a DAO's treasury, we can build a transaction where:
-
Money::Fee
-
Money::Transfer
-
DAO::Exec
This illustrates the concept of chaining function calls together in a single transaction.
Money::Transfer
Denote the call data here simply by . Since payments on DarkFi use the Sapling UTXO model, there are inputs and outputs in . There are also input burn zero-knowledge proofs, and output mint zero-knowledge proofs.
Each input contains a nullifier which is deterministically generated from the previous output's (the output which is being spent) serial code and secret key . The ZK burn proof states:
- Correct construction of the nullifier , revealing this value publicly.
- Derive the public key .
- Construct the coin commitment , where is the coin value, is the token ID, and is a random blinding factor. Additional metadata may be stored in this coin commitment for additional functionality.
- Set membership proof that where represents the set of all presently existing coins.
- Any additional checks such as value and token commitments.
Outputs contain the public coin commitment , a proof of their construction , and corresponding value/token commitments. The unlinkability property comes from only the nullifier being revealed in inputs (while is hidden), while the coin appears in outputs (but without nullifiers). Since there is a deterministic derivation of nullifiers from , you cannot double spend coins.
The ZK mint proof is simpler and consists of proving the correct construction of and the corresponding value/token commitments.
To hide amounts, both proofs export value commitments on the coin amounts. They use a commitment function with a homomorphic property:
So to check value is preserved across inputs and outputs, it's merely sufficient to check:
DAO::Exec
Earlier we mentioned that bullas/coins can contain arbitrary metadata (indicated by …). This allows us to construct the concept of protocol owned liquidity. Inside the coin we can store metadata that is checked for correctness by subsequent contract calls within the same transaction. Take for example mentioned earlier. We have:
-
Money::Transfer
-
DAO::Exec
Now the contract will use the encrypted DAO value exported from in its ZK proof when attempting to debit money from the DAO treasury. This enables secure separation of contracts and also enables composability in the anonymous smart contract context.
The DAO proof states:
- There is a valid active proposal , and , where are the destination public key and amount, and is the DAO commitment.
- That where is the quorum threshold that must be met (minimum voting activity) and is the required approval ratio for votes to pass (e.g. 0.6).
- Correct construction of the output coins for which are sending money from the DAO treasury to are specified by the proposal, and returning the change back to the DAO's treasury.
- Total sum of votes meet the required thresholds and as specified by the DAO.
By sending money to the DAO's treasury, you add metadata into the coin which when spent requires additional contract calls to be present in the transaction . These additional calls then enforce additional restrictions on the structure and data of such as is specified above.