Table of Contents
- 1. Abstract
- 2. Scope and Dependencies
- 3. Conventions
- 4. Core Concepts
- 5. Value ABI
- 6. Entrypoints and Invocations
- 7. P2SH Covenant Envelope
- 8. Covenant State, Templates, and Continuations
- 9. ID-Aware Transitions and Roles
- 10. Virtual Elements
- 11. Conformance Vectors
- 12. References
1. Abstract
This specification defines common terminology, byte layouts, and application binary interface conventions for covenants on Kaspa. It defines:
- the canonical state and argument codecs;
- logical value locations and non-encoding semantic annotations;
- entrypoints, arguments, and dispatch tags;
- the P2SH envelope of a covenant program;
- covenant state and templates;
- Covenant ID lineage across stateful continuations;
- leader and delegator roles in multi-input transitions; and
- virtual elements.
KCC1 is independent of any source language, compiler, framework, or artifact format. It does not change consensus rules. Later specifications MAY use the terms and layouts defined here without redefining them.
2. Scope and Dependencies
The base KCC1 ABI does not require covenant bindings. The ID-aware portions of this specification use the covenant bindings, authorized-output context, shared Covenant ID context, and same-ID continuation rules defined by KIP-20 [1]. Programs without covenant bindings remain valid KCC1 programs, but do not obtain KIP-20 lineage or authorization guarantees.
The ABI defined by KCC1 is extensible. The following concepts remain for later specifications:
- source languages;
- artifact formats;
- inter-covenant communication (ICC);
- transaction builders; and
- applications.
3. Conventions
The following notation is used:
| Notation | Meaning |
|---|---|
x || y | Concatenation of byte strings x and y |
len(x) | Length of x in bytes |
x[a:b] | Bytes of x beginning at a and ending before b |
x[a:] | Bytes of x beginning at a |
UTF8(s) | UTF-8 encoding of string s, without a terminator |
LE16(n) | Unsigned 16-bit little-endian encoding of n |
LE32(n) | Unsigned 32-bit little-endian encoding of n |
LE64(n) | Unsigned 64-bit little-endian encoding of n |
x^n | Byte string x repeated n times |
Offsets are zero-based. Ranges are half-open. Hexadecimal strings are written without a 0x prefix.
3.1 Hash Function
The Hash Function is BLAKE3 with an output length of exactly 32 bytes. It supports both unkeyed and keyed operation.
Hash(x) denotes unkeyed BLAKE3 applied to x.
The keyed form accepts a byte string key of at most 32 bytes. KCC1 expands it to the 32-byte BLAKE3 key:
Key32(key) = key || 00^(32 - len(key))Hash(x, key) denotes BLAKE3 keyed mode applied to x using Key32(key). A later specification that uses the keyed form MUST define the key byte string directly.
All hashes defined directly by KCC1 use the unkeyed Hash(x) form except the P2SH redeem-script commitment, which uses BLAKE2b as specified in Section 7.
4. Core Concepts
Covenant: A spending program that inspects transaction context and restricts how outputs of this transaction might be spent by future transactions. A covenant can require continuation outputs, permit termination, or coordinate multiple inputs and outputs.
Covenant program: The complete executable byte string implementing a covenant, including every embedded value and its current encoded state. In this specification it is the P2SH redeem script, denoted R. Two covenant programs can have the same template while containing different state.
Covenant instance: A UTXO locked to a covenant program. An instance is identified uniquely by its outpoint.
Transition: A transaction in which one or more covenant instances are consumed and zero or more continuation instances are created.
Continuation: An output accepted by a consumed covenant instance as a successor in the transition. A continuation can preserve the same template or use a different template when the covenant permits it.
5. Value ABI
The Program ABI is the program-specific interface exposed by a covenant under KCC1. It declares entrypoints, argument types, records, state layout, template views, and other facts required to construct or decode invocations and state. It does not assume a compiler, can be authored by hand, and need not use any particular serialized representation.
5.1 Canonical type names
TypeName(T) is defined by the following grammar:
| Type | Canonical type name |
|---|---|
| Integer | int |
| Boolean | bool |
| Byte | byte |
| UTF-8 string | string |
| 32-byte public key | pubkey |
| 65-byte transaction signature | sig |
| 64-byte data signature | datasig |
Fixed byte string of length N | byte[N] |
Fixed array of T with length N | TypeName(T)[N] |
Dynamic array of T | TypeName(T)[] |
| Record | Its exact case-sensitive record name |
Every type identifier, including a record name, MUST match [A-Za-z_][A-Za-z0-9_]*.
N MUST be written in base ten without a sign or leading zero, except that zero is written 0.
KCC1 uses two related push encodings:
- invocation arguments use
PushMinimal; and - encoded state fields use
PushExplicit.
Both encodings produce ordinary Kaspa Script data pushes. They differ for one-byte payloads that have special numeric opcodes.
5.2 Data pushes
For a payload b of length n, PushMinimal(b) is:
| Condition | Encoding |
|---|---|
n = 0 | OP_0 |
n = 1 and b = 01..10 | OP_1..OP_16 respectively |
n = 1 and b = 81 | OP_1NEGATE |
1 <= n <= 75, otherwise | OP_DATA_n || b |
76 <= n <= 255 | OP_PUSHDATA1 || n || b |
256 <= n <= 65535 | OP_PUSHDATA2 || LE16(n) || b |
65536 <= n <= 2^32 - 1 | OP_PUSHDATA4 || LE32(n) || b |
For the table above, the length following OP_PUSHDATA1 is one unsigned byte. Consensus script and element-size limits apply in addition to the representable lengths shown here.
PushExplicit(b) uses OP_0 when n = 0 and the length-based forms in the last four rows for every non-empty payload. In particular:
PushMinimal(01) = OP_1
PushExplicit(01) = OP_DATA_1 015.3 Integers
The KCC1 int range is:
-(2^63 - 1) <= value <= 2^63 - 1An int invocation argument uses PushMinimal over its minimal ScriptNum representation. An int state payload uses an eight-byte little-endian signed-magnitude encoding.
5.4 Scalar payloads
The canonical payload of each scalar type is:
| Type | Payload |
|---|---|
int | Eight-byte ScriptNum; minimal ScriptNum as a standalone argument |
bool | Exactly 00 for false or 01 for true |
byte | The byte value |
string | The UTF-8 bytes of the string, without a terminator |
pubkey | Exactly 32 bytes |
sig | Exactly 65 bytes |
datasig | Exactly 64 bytes |
byte[N] | Exactly N bytes |
A standalone bool argument uses OP_0 or OP_1. All other standalone scalar arguments use PushMinimal over their payload, except for int as specified in Section 5.3.
5.5 Arrays
For a type T with fixed payload width width(T), the width of T[N] is N * width(T).
The payload of an array is the concatenation of its elements' fixed payloads in index order. Each array is one logical argument stack element unless record lowering in Section 5.6 applies.
A dynamic array T[] is permitted only when T has a fixed payload width, width(T) > 0. Its length is inferred as len(payload) / width(T); a payload whose length is not a multiple of width(T) is invalid. A fixed array MUST contain exactly its declared number of elements.
byte[] is the canonical KCC1 type for a variable-length byte string. Its payload is the concatenation of its byte elements in index order. Its TypeName MUST be byte[], including when constructing a FunctionSignature.
5.6 Records
A record is identified by its exact case-sensitive name and has an ordered list of named fields. Field order is part of the program ABI.
A record argument is recursively expanded into the argument encodings of its fields in declaration order. It is not wrapped in an additional push.
An array of records is encoded by grouping the corresponding field values from every record. For records r_0, ..., r_(n-1) with fields f_0, ..., f_(m-1), the argument stack elements are encoded in this order:
[r_0.f_0, ..., r_(n-1).f_0]
[r_0.f_1, ..., r_(n-1).f_1]
...
[r_0.f_(m-1), ..., r_(n-1).f_(m-1)]Nested records are recursively lowered by the same rule.
An array of records is permitted only when every recursively lowered leaf field has a positive fixed payload width.
When decoding an array of records, every grouped field payload MUST decode to the same element count. For a fixed-length record array, that count MUST equal its declared length.
This restriction applies only to arrays of records. A record outside an array MAY contain otherwise supported variable-width fields; recursive lowering gives each leaf field its own push.
5.7 Value locations
A value location identifies a logical value exposed by a Program ABI. It consists of a root and a path:
ValueLocation = (root, path)
root = named(name) | argument(entrypoint, index)A named root uses an exact, case-sensitive name for a logical value exposed by the Program ABI. The complete covenant state, when present, is the named state root. A Program ABI MAY expose additional named roots, such as immutable values, and MUST define their KCC1 types and how they are obtained or decoded.
An argument root selects one argument of one exact KCC1 entrypoint. entrypoint is identified by its case-sensitive name and ordered argument types as defined in Section 6. index is the zero-based position in that entrypoint's ordered argument list. It selects the declared logical argument, not a stack element produced by recursive lowering. The root's KCC1 type is the declared type of the selected argument.
Within ABI information already scoped to one exact entrypoint, argument(index) is shorthand for argument(entrypoint, index).
A path is evaluated after KCC1 decoding. It is a sequence of:
- a field segment, which selects a named field of a record; or
- an index segment, which selects an element of a fixed or dynamic array.
Array indices are zero-based. An empty path selects the root itself. Paths refer to the logical KCC1 value tree, not byte offsets, data pushes, or the lowered representation of records and arrays.
For a state layout containing int[4] example_state_values, the location of the first example state value can be written as:
root = state
path = field("example_state_values"), index(0)Here a bare root name such as state denotes named("state"). The shorthand state.example_state_values[0] denotes the same location, but is not a required serialized syntax. A field segment is valid only for a record containing that field, and an index segment is valid only for an array containing that index in the decoded value. A location that fails these conditions is invalid for the inspected program and value.
For the entrypoint transfer(byte[32],sig,pubkey), the location of its pubkey argument can be written as:
root = argument(transfer(byte[32],sig,pubkey), 2)
path = emptyValue locations are semantic ABI information. KCC1 does not prescribe their artifact representation or add their names and paths to covenant bytecode.
5.8 Semantic annotations
A Program ABI MAY attach non-encoding semantic names or annotations to its typed values and value locations. An annotation assigns meaning defined by the Program ABI or a later specification while retaining the value's underlying KCC1 type. For example, public key, program hashes, and Covenant IDs can remain byte[32] values with distinct semantic annotations.
An annotation MUST NOT change TypeName(T), canonical encoding or decoding, record lowering, an entrypoint's FunctionSignature, or its dispatch tag. It does not create a new nominal KCC1 type.
6. Entrypoints and Invocations
An entrypoint is a named externally invocable branch of a covenant program, with an ordered list of argument types.
An invocation consists of the entrypoint arguments, dispatch tag, and covenant program supplied in the spending input's signature script.
The program ABI identifies an entrypoint by:
- its case-sensitive name; and
- its ordered list of argument types
T_1, ..., T_n.
Covenant programs MUST contain at least one entrypoint.
6.1 Dispatch tag
For an entrypoint named name, define:
FunctionSignature = UTF8("{name}({comma-separated canonical type names})")
dispatch_tag = Hash(FunctionSignature)[0:4]The type names are enclosed in parentheses and separated by commas. No whitespace, length field, or terminator is inserted into FunctionSignature. For an entrypoint with no arguments, the sequence of type names and commas is empty, so the signature ends with UTF8("()").
The four-byte dispatch_tag value is the entrypoint's dispatch tag.
All dispatch tags in a multi-entrypoint program MUST be distinct. A program producer MUST reject a program whose entrypoint signatures produce duplicate dispatch tags. This requirement covers both truncated hash collisions and identical signature preimages.
6.2 Argument sequence
PushArguments(arguments) is a script byte string containing the concatenation, in parameter order, of the canonical data-push instructions for all recursively lowered arguments. It may contain zero, one, or multiple push instructions.
7. P2SH Covenant Envelope
Let R be a covenant program. A KCC1 covenant instance uses script public key version 0 with the following script:
OP_BLAKE2B OP_DATA_32 Blake2b(R) OP_EQUALBlake2b(x) denotes unkeyed BLAKE2b configured for a 32-byte output, with no key or salt.
The spending input's signature script MUST be push-only and MUST have the following logical layout:
PushArguments(arguments)
OP_DATA_4 dispatch_tag
PushMinimal(R)The dispatch-tag element MUST be present, MUST contain the selected entrypoint's dispatch tag as defined in Section 6.1, and MUST use OP_DATA_4 followed by the four dispatch-tag bytes. PushMinimal(R) MUST be the final push in the signature script.
P2SH evaluation first applies Blake2b to the final pushed element and checks that it equals the 32-byte value committed by the script public key. If this succeeds, the final element is removed and executed as a script against the remaining stack. The remaining stack therefore contains the encoded entrypoint arguments and the dispatch tag.
The P2SH script hash commits to the entire covenant program. A change to an embedded state byte changes Blake2b(R), even when the template is unchanged.
8. Covenant State, Templates, and Continuations
Covenant state is the ordered values carried by a covenant program and encoded into a contiguous range of its bytecode. A template is the parts of the bytecode that remain when a declared state range is removed, represented as an ordered prefix and suffix. A template view selects a contiguous subset of the encoded state as its variable range, leaving encoded state outside that range as part of the template.
The program ABI describes the location of the complete encoded state within a covenant program R using two non-negative integers:
state.start
state.lenThe complete encoded state and canonical template are:
template.prefix = R[0 : state.start]
encoded_state = R[state.start : state.start + state.len]
template.suffix = R[state.start + state.len :]
template = (template.prefix, template.suffix)
R = template.prefix || encoded_state || template.suffixA stateless program MUST use:
state.start = 0
state.len = 0Its template is therefore ("", R).
8.1 State encoding
The state layout MUST have a statically known fixed payload width. After recursive lowering, every resulting field MUST have a fixed-width type. A string or dynamic array T[] MUST NOT occur anywhere in a state field, including within a fixed array or record.
State fields are recursively lowered in declaration order.
For a lowered state field of type T and value v, StatePayload(T, v) is:
- for
int, the eight-byte encoding defined in Section 5.3; - for any other fixed-width scalar type, the canonical payload defined in Section 5.4; and
- for a fixed array
U[N], the concatenation ofStatePayload(U, v_i)fori = 0, ..., N - 1.
Each resulting field is encoded as:
encoded_field = PushExplicit(StatePayload(field_type, field_value))The canonical encoding of an ordered sequence of state values state is the concatenation of its resulting field encodings:
EncodeState(state) = encoded_field_0 || encoded_field_1 || ... || encoded_field_nEncodeState(state) includes both payload bytes and their push opcodes. It is executable bytecode. A state decoder MUST consume exactly one push for each lowered field. For a decoded field of type T and value v, the consumed bytes MUST exactly equal PushExplicit(StatePayload(T, v)). A decoder MUST also reject malformed pushes, invalid payloads, missing fields, or trailing bytes.
The location of the encoded state is independent of entrypoint dispatch. A program producer MAY place dispatch-preservation bytecode in the template prefix or suffix, provided the declared state range and runtime stack behavior remain correct.
8.2 Reading consumed state
An unspent P2SH output exposes only Blake2b(R), not R or its encoded state. When that output is consumed, R is the final data element produced by its input's signature script as specified in Section 7. Given the applicable state layout, the consumed encoded state is:
R[state.start : state.start + state.len]An input-state decoder MUST first validate the P2SH invocation structure and the bounds of the encoded state. It MUST then decode those bytes according to Section 8.1.
8.3 Template hash
For a template (prefix, suffix), its template hash is:
TemplateHash(prefix, suffix) =
Hash(LE64(len(prefix)) || prefix || LE64(len(suffix)) || suffix)The two length fields bind the prefix/suffix boundary. Implementations MUST NOT replace this construction with Hash(prefix || suffix).
A claimed template hash MUST be verified by recomputing it from both template parts before those parts are used to reconstruct a covenant program.
8.4 Template views
The program ABI MAY declare one or more template views of a stateful program. A template view selects a non-empty contiguous range within the complete encoded state:
state.start <= view.start
view.start + view.len <= state.start + state.lenThe selected range MUST consist of one or more complete consecutive encoded fields after the lowering defined in Section 8.1. For a covenant program R, the view is:
view.prefix = R[0 : view.start]
view.encoded_state = R[view.start : view.start + view.len]
view.suffix = R[view.start + view.len :]
view.template = (view.prefix, view.suffix)The view's template hash is TemplateHash(view.prefix, view.suffix) as defined in Section 8.3. The canonical template defined at the beginning of Section 8 is the view that selects the complete encoded state.
For example, one covenant program can have two logical cuts:
R = prefix || state_a || state_b || suffix
complete-state view:
[ prefix ][ state_a || state_b ][ suffix ]
state_b view:
[ prefix || state_a ][ state_b ][ suffix ]Under the state_b view, state_a is part of the template and MUST remain byte-for-byte unchanged in a continuation using that view. Changing bytes outside a view's selected range requires an independently authorized template.
Template views MAY overlap or be nested. Different views of the same covenant program generally have different template hashes because each hash identifies a specific cut and commits to every byte outside that cut.
8.5 Continuation programs
For a continuation under the same template, including a declared template view:
encoded_next_state = EncodeState(next_state)
R_next = template.prefix || encoded_next_state || template.suffixHere next_state contains the ordered state values selected by that template. For the canonical template it contains all state fields; for a template view it contains only the fields selected by the view. All other state bytes are already committed by the template prefix or suffix.
The expected continuation script public key is then the version-0 P2SH script committing to Blake2b(R_next) as specified in Section 7.
A covenant MUST authenticate the template used to construct every continuation. For a same-template continuation, the template MUST match the consumed covenant's authenticated template. For a different-template continuation, the protocol MUST define how the target template is authorized. Supplied prefix and suffix bytes are not authenticated unless they are verified against the expected template hash.
9. ID-Aware Transitions and Roles
An ID-aware transition is a transition whose rules use the Covenant IDs or covenant bindings defined by KIP-20 [1] for lineage, output authorization, or participant grouping. A continuation in such a transition must also be a same-ID continuation under KIP-20.
ID-aware KCC1 rules adopt the local authorized-output context and shared Covenant ID context defined by KIP-20. Every required continuation MUST appear in the applicable context. When validation is scoped to one authorizing input, the covenant MUST use that input's authorized-output context. When validation is scoped to all participants sharing a Covenant ID, it MUST cover the complete shared transition.
This requirement MAY be satisfied either by inspecting the shared Covenant ID output context directly or by distributed validation across the participants' authorized-output contexts. When using distributed validation, the covenant MUST verify that those contexts collectively cover every required continuation.
In either case, the covenant MUST validate the required number of continuations against their reconstructed script public keys, thereby authenticating each complete continuation program, including any encoded state.
9.1 Leader and delegator roles
An ID-aware shared transition obtains its participating inputs and continuation outputs from the shared context defined by KIP-20 [1] for the active Covenant ID. KCC1 assigns the first input in that context as the leader and every later input as a delegator. Since the context is ordered by transaction input index, the leader is the lowest-indexed input carrying that Covenant ID. A one-input group has a leader and no delegators.
The following rules apply:
- Every participating input MUST take the role fixed by its position in the shared input context: the first input takes the leader path and every later input takes a delegator path. Each path MUST reject the opposite position.
- The leader MUST validate the complete shared transition defined by the protocol. This includes participant and continuation cardinality and coverage, and authentication of every program, template, or state on which that validation relies.
- Each delegator MUST validate any protocol-specific local conditions needed to rely safely on the leader.
If a program can serve as leader in any shared transition, every entrypoint of that program MUST either reject shared execution, reject the leader position and act as a delegator, or accept the leader position and validate the complete shared transition.
When the active Covenant ID is obtained from the active input, membership does not require a separate check: the KIP-20 shared context includes that input by definition. KCC1 does not prescribe how source languages or artifacts expose the leader and delegator paths.
One way to implement distributed validation is for the leader to validate every output in its authorized-output context while each delegator verifies that its own authorized-output context is empty.
10. Virtual Elements
A virtual element is a value represented in covenant-program bytecode by a commitment c instead of its complete canonical encoding. The complete value is supplied as an opening when it is needed. A virtual element therefore consists of:
- the commitment
cin encoded state; - a canonical encoding of the complete value;
- an opening supplied as invocation data; and
- a verification rule binding the opening to
c.
A protocol defining a virtual element MUST specify:
- the value type and its canonical encoding;
- the commitment scheme and commitment byte length;
- the opening encoding and where it appears among the program ABI's invocation arguments;
- the verification rule; and
- how a changed value produces the next commitment.
The opening is witness data and is not part of the encoded covenant state. A covenant MUST verify an opening before using the complete value. When a transition changes that value, its continuation state MUST contain a commitment to the new value's canonical encoding.
Template views and virtual elements are independent techniques. A template view selects which encoded state fields may change while preserving a template hash; a virtual element commits to a complete value that is not stored in the covenant program. A protocol MAY combine them by selecting a template view that contains the virtual element's commitment field. A continuation using that view can then change the commitment while all program and state bytes outside the view remain immutable.
10.1 Hash-committed virtual element scheme
It uses the Hash Function as its commitment scheme:
payload = Packed(value)
commitment = Hash(payload)The encoded state field is byte[32] commitment. The opening is payload. The covenant verifies Hash(payload) = commitment, decodes the canonical value, and computes Hash(Packed(next_value)) for a changed value.
Packed concatenates the fixed payloads from Section 5 in declaration order, without Script push opcodes. This realization is valid only when the value layout has a statically known packed width, including after recursive record lowering. Later specifications MAY define other commitment schemes or opening formats and MUST identify them distinctly.
11. Conformance Vectors
All hashes and byte strings in this section are hexadecimal.
11.1 Dispatch tag and argument encoding
For the entrypoint:
name = "step"
types = (int, byte[4], bool, byte)the dispatch-tag preimage and dispatch tag are:
FunctionSignature = UTF8("step(int,byte[4],bool,byte)")
dispatch_tag = 2c49ed65For argument values (17, 01020304, true, 01), the argument encodings and dispatch-tag push are:
int 17 = 0111
byte[4] = 0401020304
bool true = 51
byte 01 = 51
dispatch-tag push = 042c49ed65
combined = 011104010203045151042c49ed6511.2 P2SH envelope
For the one-byte covenant program R = 51 (OP_1):
Blake2b(R) = ce57216285125006ec18197bd8184221cefa559bb0798410d99a5bba5b07cd1d
scriptPublicKey.version = 0
scriptPublicKey.script = aa20ce57216285125006ec18197bd8184221cefa559bb0798410d99a5bba5b07cd1d87
signature_script = 0151Here aa, 20, and 87 encode OP_BLAKE2B, OP_DATA_32, and OP_EQUAL respectively. 0151 pushes the one-byte redeem script as data; it does not execute 51 during signature-script evaluation.
11.3 State encoding
For the ordered state fields:
pubkey key = 07^32
int counter = -5
bool enabled = truethe encoded fields are:
key = 20 || 07^32
counter = 08 || 0500000000000080
enabled = 01 || 01The 44-byte encoded state is:
200707070707070707070707070707070707070707070707070707070707070707080500000000000080010111.4 Template hashes
| Prefix | Suffix | Template hash |
|---|---|---|
| empty | empty | e572dff82304700b856a555ac3a4558d0df3646a3727816500270a93c66aac1e |
61 | 6263 | 405e183e2494cdbe2df89349cc0ffa5b77fb885ad97a1d5660ecd0692ef8142a |
6162 | 63 | a0968c014f3fc7bd1a7d9a8d1ad1177eb379bd2f05e56309eb4e20347c5e7eba |
00ff | 100080 | 6616a66757315de0221cb2acba729113cebde31f8d3ca7fa93878a0584b96905 |
11.5 Template views
Consider this program with three encoded state fields:
template.prefix = 51
byte[2] a = 02aabb
bool b = 0101
byte[2] c = 02ccdd
template.suffix = 75
R = 5102aabb010102ccdd75
state.start = 1
state.len = 8The following template views are declared over R:
| Fields | view.start | view.len | view.prefix | view.encoded_state | view.suffix | Template hash |
|---|---|---|---|---|---|---|
a, b | 1 | 5 | 51 | 02aabb0101 | 02ccdd75 | 2e2c28131760a1c0942842f8b54fe686321d0080f5161fa803e27af6a15799a4 |
b, c | 4 | 5 | 5102aabb | 010102ccdd | 75 | 0eb10580bcb608ab9efab6e256d4bac6671d724a72951a058d37b42fa205c1ee |
b | 4 | 2 | 5102aabb | 0101 | 02ccdd75 | 7b96ebb8023373bfe7aa81f7db8e0e0d9ce492d2b0382fb5586ac60beea8ed12 |
The a, b and b, c views overlap on the complete encoding of b. The b view is nested in both.
11.6 Hash-committed virtual element scheme
For a fixed record containing (int -5, bool true):
Packed(value) = 050000000000008001
commitment = 15e006c7c506fb20b6de9573e31bdc47591e937c38f9fcf31cdfabe55d122bda12. References
- [1] KIP-20, Covenant IDs.