Covenants++
A collection of notes on Covenants++, Kaspa's advanced UTXO-based covenant environment.Covenants++ is a broad term referring to the advanced UTXO-based covenants enabled by covenant primitives/building blocks on Kaspa. Covenants are (soon to be) first-class citizens on the Kaspa network. From a technical perspective, these building blocks can be grouped into two approximate (but related) categories:
- Covenant IDs
- Opcodes
These building blocks are in various statuses - some are active on mainnet while others are currently on testnet.
Use Cases
Kaspaâs covenant building blocks enable many use cases, including (but certainly not limited to):
- Tokens (fungible & non-fungible). See CAT Protocol for reference though a standard for covenant based tokens on Kaspa is WIP.
- Vaults (reference)
- Congestion control (reference)
- Whitelisting
- Escrow
- Time-delayed spending
Credit KIP-17 for many of these.
KIPs
- KIP-10 - New Transaction Opcodes for Enhanced Script Functionality
- KIP-17 - Covenants and Improved Scripting Capabilities
- KIP-20 - Covenant IDs
Covenant IDs (& Covenant Bindings)
Introduced by KIP-20, covenant IDs are a consensus mechanism that stores, tracks, and enforces covenant lineage. Covenant IDs greatly simplify lineage related transaction scripting logic.
A Covenant ID is a optional 32 byte hash stored on a UTXO in the field covenant_id. When Some(covenant_id), the UTXO belongs to the given covenantâs lineage. It can be thought of as a member UTXO of the given covenant.
A Covenant Binding is an optional field on a transaction output. Covenant bindings contain two pieces of data:
authorizing_input- the index of transaction input that âauthorizesâ the outputâs creationcovenant_id- the covenant that the output belongs to
Covenant bindings are only allowed on transaction versions >= 1.
Covenant UTXOs can be created in two ways:
- Genesis: The creation of a covenant ID. The authorizing input does not have the same covenant ID as the output. Covenant ID is computed via blake2b hash of select data in a manner that ensures uniqueness.
- Continuation: The output inherits the covenant ID of the UTXO spent by the authorizing input.
Genesis
The genesis process requires that the transaction creator compute the same covenant ID as the consensus engine. So, the creator must create a blake2b hash (in a manner commensurate with consensus), store it in the covenant_id. field of the transaction outputâs covenant binding, and submit the transaction. If the consensus engine calculates the same covenant ID hash, it is valid. The authorizing_input field can be any valid input index.
The genesis process guarantees covenant id uniqueness and prevents forgery. More detail is available in KIP-20.
covenant_id = BLAKE2b("CovenantID",
outpoint.tx_id
|| le_u32(outpoint.index)
|| le_u64(len(auth_outputs))
|| for each output:
le_u32(output_index)
|| le_u64(output.value)
|| le_u16(spk.version)
|| le_u64(len(spk.script))
|| spk.script
) Continuation
The outputâs covenant binding ties the output back to an input with the same covenant ID. The output inherits the covenant ID from the input. This is referred to as authorization - the authorizing input for the given output. Consensus validates that the covenant IDs match and the transactionâs script validates transition.
The outputâs covenant binding looks like:
authorizing_input: the index of the input from the same covenant lineage (with the same covenant ID).covenant_id: the covenant ID
Opcodes
Kaspaâs covenant related opcodes enable introspection and other necessary script features. These have been added through the KIPs mentioned above.
| Opcode | Hex | Notes |
|---|---|---|
| OpBlake2bWithKey | 0xa7 | Pops 2 items off the stack - key and data. Creates a 32 byte keyed hash using blake2b. Pushes the hash onto the stack. |
| OpTxVersion | 0xb2 | Pushes the transactionâs version onto the stack. |
| OpTxInputCount | 0xb3 | Pushes number of transaction inputs onto the stack. |
| OpTxOutputCount | 0xb4 | Pushes number of transaction outputs onto the stack. |
| OpTxLockTime | 0xb5 | Pushes the transactionâs lock time onto the stack. |
| OpTxSubnetId | 0xb6 | Pushes the transactionâs subnetwork ID onto the stack. |
| OpTxGas | 0xb7 | Pushes the transactionâs gas onto the stack. |
| OpTxPayloadSubstr | 0xb8 | Pops 2 items off the stack - start and end. Pushes the substring of payload from start to end onto the stack. |
| OpTxInputIndex | 0xb9 | Pushes this transaction inputâs index onto the stack. |
| OpOutpointTxId | 0xba | Pops 1 item off the stack as idx. Of the input at idx, pushes the previous outpointâs transaction ID onto the stack. |
| OpOutpointIndex | 0xbb | Pops 1 item off the stack as idx. Of the input at idx, pushes the previous outpointâs index onto the stack. |
| OpTxInputScriptSigSubstr | 0xbc | Pops 3 items off the stack - idx, start, and end. Of the input at idx, pushes the substring of signature_script from start to end onto the stack. |
| OpTxInputSeq | 0xbd | Pops 1 item off the stack as idx. Of the input at idx, pushes the sequence field as little-endian bytes onto the stack. |
| OpTxInputAmount | 0xbe | Pops 1 item off the stack as idx. Of the input at idx, pushes the UTXOâs amount onto the stack. |
| OpTxInputSpk | 0xbf | Pops 1 item off the stack as idx. Of the input at idx, pushes the UTXOâs script public key (including version prefix) onto the stack. |
| OpTxInputDaaScore | 0xc0 | Pops 1 item off the stack as idx. Of the input at idx, pushes the UTXOâs DAA score onto the stack. |
| OpTxInputIsCoinbase | 0xc1 | Pops 1 item off the stack as idx. Of the input at idx, pushes the UTXOâs is coinbase flag onto the stack. |
| OpTxOutputAmount | 0xc2 | Pops 1 item off the stack as idx. Of the output at idx, pushes the outputâs value (amount) onto the stack. |
| OpTxOutputSpk | 0xc3 | Pops 1 item off the stack as idx. Of the output at idx, pushes the outputâs value script public key (including version prefix) onto the stack. |
| OpTxPayloadLen | 0xc4 | Pushes the transactionâs payload length onto the stack. |
| OpTxInputSpkLen | 0xc5 | Pops 1 item off the stack as idx. Of the input at idx, pushes the script public keyâs byte length (including version prefix) onto the stack. |
| OpTxInputSpkSubstr | 0xc6 | Pops 3 items off the stack - idx, start, and end. Of the input at idx, pushes the substring of the UTXOâs script public key from start to end onto the stack. |
| OpTxOutputSpkLen | 0xc7 | Pops 1 item off the stack as idx. Of the output at idx, pushes the script public keyâs bytes length (including version prefix) onto the stack. |
| OpTxOutputSpkSubstr | 0xc8 | Pops 3 items off the stack - idx, start, and end. Of the output at idx, pushes the substring of the script public key from start to end onto the stack. |
| OpTxInputScriptSigLen | 0xc9 | Pops 1 item off the stack as idx. Of the input at idx, pushes the signature scriptâs length onto the stack. |
| OpAuthOutputCount | 0xcb | Pops 1 item off the stack as input_idx. Of the input at input_idx, pushes the number of outputs authorized via covenant binding onto the stack. |
| OpAuthOutputIdx | 0xcc | Pops 2 items off the stack - input_idx and k. Finds the k-th output that has a covenant binding pointing to the input at input_idx. Pushes the outputâs actual index onto the stack. |
| OpNum2Bin | 0xcd | Pops 2 items off the stack, treating values as size and num. size must be at most 8 bytes. Serializes num into exactly size bytes. |
| OpBin2Num | 0xce | Pops 1 item off the stack, deserializes it as an i64, and pushes it back onto the stack as minimally encoded bytes. |
| OpInputCovenantId | 0xcf | Pops 1 item off the stack as idx. Of the input at idx, pushes the UTXOâs covenant ID onto the stack, or an empty byte vector if the UTXO does not have a covenant ID. |
| OpCovInputCount | 0xd0 | Pops 1 item off the stack as covenant_id. Pushes the number of inputs with this covenant id onto the stack. |
| OpCovInputIdx | 0xd1 | Pops 2 items off the stack - k and covenant_id. Finds the k-th transaction input with covenant_id and pushes the inputâs actual index onto the stack. |
| OpCovOutCount | 0xd2 | Pops 1 item off the stack as covenant_id. Pushes the number of outputs with this covenant id onto the stack. |
| OpCovOutputIdx | 0xd3 | Pops 2 items off the stack - k and covenant_id. Finds the k-th transaction output with covenant_id and pushes the outputâs actual index onto the stack. |
| OpChainblockSeqCommit | 0xd4 | Pops 1 item off the stack as block (32 byte block hash). Checks if block is a chain block from the POV of the current selected parent. If block has been pruned, is not a chain block, is from pre-covenant activation, or is beyond an allowed depth (currently finality depth), a script error is raised. The blockâs accepted ID merkle root is retrieved and pushed onto the stack as the sequence commitment. |
Other Resources
- ZK covenants and verifiable programs (vprogs)
- CAT Protocol tokens (fungible & nonfungible)
- Bitcoin Covenants Use Cases
- UTXOs.org
- OP_CAT use cases
- In Bitcoin, the primary covenant BIPs are BIP-119 (OP_CHECKTEMPLATEVERIFY), BIP-345 (OP_VAULT), and BIP-347 (OP_CAT).
Examples
- covenant_id.rs in rusty-kaspa covpp branch
- covenants.rs in rusty-kaspa covpp branch
- forced_recipient.py in Kaspa Python SDK tn12 branch