Covenant IDs give a lineage an identity that consensus tracks. The moment more than one instance of that lineage is spent in a single transaction, a coordination problem appears that consensus does not solve for you: every input runs its own script, and each one is asked to approve the same transition.
Left alone, that goes wrong in two directions. Every input redundantly re-validating everything is expensive and easy to get subtly inconsistent. Every input validating only its own slice is worse — it can be individually correct and collectively broken.
KCC1 §9 settles both with a convention.
Picking the right context
The two scopes consensus exposes — local authorized-output, and shared Covenant ID — are not interchangeable. The rule KCC1 states:
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 use the shared Covenant ID output context.
The failure mode is asymmetric, which is why it matters. A covenant that validates only its local authorized outputs, while the protocol’s rule is actually about the whole covenant group, will pass each input individually and still let the transaction as a whole break the invariant. Three inputs each correctly checking their own slice can collectively mint tokens if nobody checks the total.
The reverse mistake — using the shared context for a genuinely local rule — is merely wasteful rather than unsound, so in practice the dangerous direction is only one.
Roles by position
Rather than have every participant re-derive who is responsible for what, KCC1 assigns roles by position:
In the shared context for the active Covenant ID, the first input is the leader and every later input is a delegator. Since the context is ordered by input index, the leader is the lowest-indexed input carrying that Covenant ID. A one-input group has a leader and no delegators.
Every participating input takes the role fixed by its position. Each path must reject the opposite position.
The three rules:
- Position fixes the role. The first input takes the leader path; every later input takes a delegator path. Each path must reject the opposite position — a leader entrypoint running at input 3 must fail, and a delegator entrypoint running at input 0 must fail.
- The leader validates the complete shared transition — participant and continuation cardinality, coverage, and authentication of every program, template, or state that validation relies on.
- Each delegator validates any protocol-specific local conditions needed to rely safely on the leader.
Membership needs no separate check when the active Covenant ID comes from the active input: the shared context includes that input by definition.
What the checks look like
Here is the shape the SilverScript compiler generates for a covenant-bound declaration. The leader:
entry __leader_transition(State[] new_states, sig leader_sig) {
byte[32] cov_id = OpInputCovenantId(this.activeInputIndex);
int in_count = OpCovInputCount(cov_id);
int out_count = OpCovOutputCount(cov_id);
require(out_count == new_states.length);
// k = 0 must be this input, or we are not the leader
require(OpCovInputIdx(cov_id, 0) == this.activeInputIndex);
// gather every participating input's prior state
State[] prev_states = [];
for (k, 0, in_count, max_ins) {
int in_idx = OpCovInputIdx(cov_id, k);
prev_states = prev_states.append(readInputState(in_idx));
}
__policy(prev_states, new_states, leader_sig);
// validate every continuation output
for (k, 0, out_count, max_outs) {
validateOutputState(OpCovOutputIdx(cov_id, k), new_states[k]);
}
} And the delegator, which is almost nothing:
entry __delegate_transition() {
byte[32] cov_id = OpInputCovenantId(this.activeInputIndex);
// must NOT be the leader position
require(OpCovInputIdx(cov_id, 0) != this.activeInputIndex);
} The delegator looks almost vacuous, and that is the point worth sitting with. It does not verify the policy — it verifies that somebody at position 0 was obliged to. Since every input’s script must succeed for the transaction to be valid, and input 0 can only succeed by running the full leader validation, the delegator’s one-line check is enough.
Why delegation is contract-wide
There is a trap here that KCC1 does not spell out but SilverScript’s design notes do, and it is worth understanding because it explains an otherwise puzzling compiler restriction.
A generated delegate authenticates the contract at input 0, but it cannot tell which entrypoint that input selected. If the same contract also exposed an authorized-output-bound entrypoint, that entrypoint could occupy input 0 — satisfying every delegator’s check — while never validating the complete covenant group.
The delegators would all defer to a leader that was not actually acting as one.
SilverScript therefore rejects contracts that mix authorized-output-bound and covenant-bound declarations: any contract with a covenant-bound declaration is a leader contract, and all its covenant declarations must be covenant-bound. A hand-written entrypoint in a leader contract must explicitly pick one of three roles:
- reject shared execution entirely, by requiring
OpCovInputCount(cov_id) == 1; - act as a delegate, by rejecting covenant input 0; or
- act as a leader, by requiring covenant input 0 and validating the complete group.
The compiler cannot verify hand-written covenant-group logic, so it requires an explicit acknowledgment attribute instead of silently trusting it.
Single-group enforcement
One more check worth knowing. A covenant using the local authorized-output context can additionally require that its Covenant ID has exactly one continuation group in the transaction:
byte[32] cov_id = OpInputCovenantId(this.activeInputIndex);
require(OpCovOutputCount(cov_id) == OpAuthOutputCount(this.activeInputIndex)); This says: every output carrying my Covenant ID is authorized by me. Without it, a transaction could contain several independent authorization groups for the same covenant, each locally valid.
No separate validity check on cov_id is needed — OpCovOutputCount fails if the value is not valid covenant-id data.
Next
Control Principals — KCC2, and how an ABI declares who controls a covenant.
References
- KCC1 §9 — ID-Aware Transitions and Roles
- Covenant IDs & Bindings — the consensus layer this builds on
- Covenant Opcodes