Ledger API
@midnight/ledger v7.0.0
Ledger TypeScript API
This document outlines the flow of transaction assembly and usage with the ledger TS API.
Proof stages
Most transaction components will be in one of three stages: X, UnprovenX,
or ProofErasedX. The UnprovenX stage is always the first one. It is
possible to transition to the X stage by proving an UnprovenTransaction
through the proof server. For testing, and where proofs aren't necessary, the
ProofErasedX stage is used, which can be reached via eraseProof[s] from the
other two stages.
Transaction structure
A Transaction runs in two phases: a guaranteed segment, handling fee payments and fast-to-verify operations, and a series of fallible segments. Each segment may fail atomically, separately from the guaranteed segment. It therefore contains:
- A "guaranteed" ZswapOffer
- A map of segment IDs to "fallible" ZswapOffers.
- A map of segment IDs to Intents, which include UnshieldedOffers and ContractActions.
It also contains additional cryptographic glue that will be omitted in this document.
Zswap
A ZswapOffer consists of:
- A set of ZswapInputs, burning coins.
- A set of ZswapOutputs, creating coins.
- A set of ZswapTransients, indicating a coin that is created and burnt in the same transaction.
- A mapping from RawTokenTypes to offer balance, positive when there are more inputs than outputs and vice versa.
ZswapInputs can be created either from a QualifiedShieldedCoinInfo and a contract address, if the coin is contract-owned, or from a QualifiedShieldedCoinInfo and a ZswapLocalState, if it is user-owned. Similarly, ZswapOutputs can be created from a ShieldedCoinInfo and a contract address for contract-owned coins, or from a ShieldedCoinInfo and a user's public key(s), if it is user-owned. A ZswapTransient is created similarly to a ZswapInput, but directly converts an existing ZswapOutput.
A QualifiedShieldedCoinInfo is a ShieldedCoinInfo with an index into the Merkle tree of coin commitments that can be used to find the relevant coin to spend, while a ShieldedCoinInfo consists of a coin's RawTokenType, value, and a nonce.
Calls
A ContractDeploy consists of an initial contract state, and a nonce.
A ContractCall consists of a contract's address, the entry point used on this contract, a guaranteed and a fallible public oracle transcript, a communication commitment, and a proof. ContractCalls are constructed via ContractCallPrototypes, which consist of the following raw pieces of data:
- The contract address
- The contract's entry point
- The contract operation expected (that is, the verifier key and transcript shape expected to be at this contract address and entry point)
- The guaranteed transcript (as produced by the generated JS code)
- The fallible transcript (as produced by the generated JS code)
- The outputs of the private oracle calls (As a FAB AlignedValues)
- The input(s) to the call, concatenated together (As a FAB AlignedValue)
- The output(s) to the call, concatenated together (As a FAB AlignedValue)
- The communications commitment randomness (As a hex-encoded field element string)
- A unique identifier for the ZK circuit used (used by the proof server to index for the prover key)
NOTE: currently the JS code only generates a single transcript. We probably just want a canonical way to split this into guaranteed/fallible?
A Intent object is assembed, and ContractCallPrototypes / ContractDeploys are added to this directly. This can then be inserted into an Transaction.
State Structure
The LedgerState is the primary entry point for Midnight's ledger state, and it consists of a ZswapChainState, as well as a mapping from ContractAddresses to ContractStates. States are immutable, and applying transactions always produce new outputs states.