| Internet-Draft | EP Multi-Party Quorum | September 2026 |
| Schrock | Expires 10 March 2027 | [Page] |
This document defines a multi-party approval predicate over action-bound human signoffs: valid signatures, admitted roles, distinct approvers and keys, threshold, and an optional ordered trail. The relying party pins the governing policy and approver directory independently. Passing the predicate is approval evidence, not a complete authorization decision, proof of execution, or proof of unused authority.¶
This revision repairs the strong ordered profile. A successor signs a digest of the completed predecessor signoff, including its signature, rather than a precomputable context. The versioned profile establishes causal dependence on a completed prior proof under the cryptographic assumptions; it does not establish trusted wall-clock time or human comprehension. Legacy context-only chains cannot satisfy it. JavaScript, Python, and Go reference verifiers share a corpus in one repository. Agreement is a same-team consistency check, not independent interoperability evidence or a formal proof of the new construction.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 10 March 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The base EP authorization receipt closes the gap between "is this actor authorized in general?" and "should this exact action happen, and which accountable human said yes?" by binding one named approver's device-held signature to one exact action (see [EP-RECEIPTS]). For the highest-consequence actions, one approver is not the right control. The discipline that governs nuclear release, large-value treasury movement, and production-credential change is the two-person rule: no single human -- however well-authenticated, however senior -- can unilaterally cause the action. Two or more distinct, accountable humans must each independently authorize, and the action proceeds only when all of them have.¶
As autonomous agents acquire credentials sufficient for irreversible operations, the two-person rule is exactly the control that lets an organization grant an agent real authority without creating a single point of failure: a compromised, misaligned, or prompt-injected agent cannot act alone, and neither can a single compromised or coerced approver. EP-QUORUM specifies how to express that control as a cryptographic predicate over EP signoffs and how to enforce it both at the moment each approver signs and at the moment the action would execute.¶
The base draft already contemplates multi-approver policies ([EP-RECEIPTS]): each approver signs an individual Authorization Context sharing the same action hash, and commitment occurs only when k valid, distinct signoffs exist before expiry. This document makes that sketch normative and testable. It adds: ordered approval trails (Section 4); an explicit role roster and admission semantics (Section 3); a bounded approval window with an asserted-time consistency constraint for ordered mode (Section 4); an incremental server-side admission rule that keeps a non-conforming signer out of the trail in the first place (Section 6); the consolidated fail-closed quorum predicate (Section 5); and an adversarial conformance suite (Section 9).¶
EP-QUORUM inherits design goals G1-G7 of the base draft and adds:¶
In addition to the terminology of [EP-RECEIPTS]:¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
A Quorum Policy is a JSON object:¶
{
"mode": "ordered",
"required": 3,
"approvers": [
{
"role": "program_officer",
"approver": "ep:approver:po_rivera"
},
{
"role": "authorizing_official",
"approver": "ep:approver:ao_chen"
},
{
"role": "inspector_general",
"approver": "ep:approver:ig_okafor"
}
],
"distinct_humans": true,
"window_sec": 900
}
¶
Members:¶
| Field | Required | Type | Meaning |
|---|---|---|---|
mode
|
REQUIRED | string (enum) |
threshold or ordered
(Section 4). |
required
|
REQUIRED | integer >= 1 | The quorum size k. |
approvers
|
REQUIRED | array | The roster of eligible {role, approver} slots. MUST be non-empty. |
distinct_humans
|
OPTIONAL (default true) | boolean | When true, no approver identifier may fill more than one slot. Device-key distinctness is unconditional (Section 5, checks 5 and 5a). Implementations MUST treat a missing value as true. |
window_sec
|
OPTIONAL (default 900) | integer > 0 | Maximum span, in seconds, between the first and any later admitted signature. |
ordered_chain
|
OPTIONAL (default false) | boolean | When true in ordered mode, requires the completed-signoff chain in Section 4. Context-only links do not satisfy this requirement. |
ordered_chain_profile
|
Required when ordered_chain is true |
string | Exactly EP-QUORUM-SIGNOFF-CHAIN-v1. A missing or
unrecognized profile fails the strong-chain check. |
Rules:¶
approver slot identifies an eligible approver in the
Approver Directory ([EP-RECEIPTS]). The
role is the organizational role under which that approver
is admitted to this quorum; it is the unit of role eligibility
(Section 5, check 4).¶
wrong_role).¶
required MUST NOT exceed the number of
distinct human approvers the roster can supply under
distinct_humans. A policy that cannot be satisfied is a
misconfiguration; verifiers treat an unsatisfiable trail as not
satisfied, as always.¶
policy_hash of every
member's Authorization Context ([EP-RECEIPTS]). A signature collected under one Quorum Policy version
MUST NOT satisfy a requirement evaluated under
another. The relying party MUST obtain the complete
expected policy, including the chain profile, from an authenticated
source outside the supplied quorum. It MUST reject a
mismatching supplied policy before crediting the quorum; a matching
artifact-supplied policy is not itself an authority source.¶
Threshold mode. Any required distinct
eligible approvers satisfy the count requirement. Presentation order
has no causal meaning.¶
Plain ordered mode. Each presented member matches
the corresponding roster slot in role and approver. The number
presented is at least required and no greater than the roster
size. Thus ordered k-of-n admits the first k slots, not automatically
all n. Presented issued_at values MUST strictly
increase and their span MUST fit window_sec.
These are checks on asserted metadata, not proof of actual signing
order or trusted elapsed time.¶
Completed-signoff chain. A relying party requiring
causal dependence between approvals MUST pin
mode: "ordered", ordered_chain: true, and
ordered_chain_profile: "EP-QUORUM-SIGNOFF-CHAIN-v1" in the
governing policy. It MUST NOT infer this requirement
from whichever fields the supplied artifact happens to retain.¶
Let S[i] be the complete predecessor signoff JSON object, including its signed context and native proof fields, with the actual signature encoded exactly as carried. Define:¶
D = UTF8("EP-QUORUM-SIGNOFF-CHAIN-v1") || 0x00
link(S) = lowercase_hex(SHA-256(D || UTF8(JCS(S))))
¶
JCS is the JSON Canonicalization Scheme [RFC8785]. The separator is one zero octet, not printable backslash and zero. Hash the complete signoff, not the member wrapper, the predecessor context alone, or a reconstructed proof that drops fields.¶
The first signed context MUST omit
prev_signoff_hash. Each successor signed context
MUST carry prev_signoff_hash = link(S[i-1]).
Every context MUST omit the legacy
prev_context_hash field; mixed links and a null first
predecessor are invalid. Every native signature and the full quorum
predicate MUST also verify. A digest match alone is
insufficient.¶
The successor challenge depends on a completed predecessor proof, not merely its public context. Substituting a different valid predecessor signature over the same context changes the link and requires a new successor signature. This establishes a dependency between proof artifacts under signature-unforgeability and hash preimage/collision-resistance assumptions. It does not identify a trusted signing time, establish that the human saw the prior proof, or prove understanding or voluntary approval.¶
Revisions through -03 used prev_context_hash. All such
contexts can be prepared before any predecessor signs, then signed
in reverse order and presented in roster order. They do not prove
the causal property previously claimed. Under this revision's
strong requirement a verifier MUST reject missing or
unknown profiles and every context-only chain, even when all
member signatures verify.¶
An existing chain MUST NOT be upgraded by changing its policy label or inserting unsigned hashes. Fresh contexts and signatures are required. Historical evidence may be evaluated under an explicitly permitted weaker threshold or plain ordered policy, but MUST NOT be credited as a completed-signoff chain. This compatibility break does not change the native signature algorithms or threshold checks.¶
A trail is a satisfied quorum for an action with hash H under policy P if and only if ALL of the following hold. A verifier MUST return "satisfied" only when every check passes, and MUST return "not satisfied" on the first failure, on a malformed policy or member, or on any unrecognized condition (Q2):¶
mode, an integer required >= 1, and a non-empty
approvers roster. Otherwise: not satisfied.¶
approver_public_key, with the
assertion challenge equal to the member's context hash and user
verification asserted. One invalid signature
(one_bad_signature) fails the whole quorum.¶
action_hash == H
(action_mismatch). A member bound to any other action does
not count.¶
wrong_role).¶
distinct_humans
is true (the default), approvers are pairwise distinct and (per the
base draft's SelfApprovalImpossible) distinct from the initiator
(duplicate_human).¶
distinct_humans, the admitted members' canonically decoded
approver_public_key values are pairwise distinct
(duplicate_key). This defends against a single device key
enrolled under two approver identifiers, which would otherwise pass
the distinct-humans check by name while being one signer.¶
required admitted
members exist (under_threshold).¶
approvers[i-1]; signature times are strictly
increasing (out_of_order,
non_increasing_time).¶
ordered_chain is true, the exact pinned profile and
every completed-signoff link satisfy Section 4
(broken_chain). A missing or unknown profile, a legacy link,
a null first link, or a substituted predecessor proof fails. When
ordered_chain is false this check supplies no causal claim.¶
issued_at is within window_sec of the first
member's issued_at (window_exceeded).¶
The predicate is the same whether computed by the orchestrating operator before consumption or by an independent Verifying Executor or auditor offline (Q5): it is a pure function of (P, H, members). Organizational acceptance additionally requires that P, H, and the approver directory are authenticated inputs, not arbitrary values chosen by the artifact supplier. Check 2 invokes the appropriate native signoff verifier per member; EP-QUORUM adds the set-level checks. A successful standalone wrapper check does not substitute for a carrier's own schema and signature requirements (Section 8).¶
To keep a non-conforming signer out of the trail rather than discovering it only at consume time (Q4), an orchestrator MUST evaluate an incremental admission rule before recording each new signoff. Given the policy P, the action hash H, the already-admitted trail, and one incoming candidate member, the rule ADMITS the candidate only if all of the following hold, and otherwise REJECTS it with the named reason:¶
no_policy / no_eligible_approvers).¶
action_hash == H (else
action_mismatch).¶
ineligible_role).¶
distinct_humans is true, no already-admitted
member shares the candidate's approver (else
duplicate_human).¶
approvers[len(trail)]) in both role and approver
(else out_of_order).¶
issued_at is
within window_sec of the first member's issued_at
(else window_exceeded); and in ordered mode it is strictly
greater than the last admitted member's issued_at (else
non_increasing_time).¶
unsupported_chain_profile /
broken_chain).¶
invalid_signature).¶
A rejected candidate MUST NOT be written into the trail. Incremental admission is an enforcement convenience and an early-rejection UX; it is not a substitute for the Quorum Gate. A conforming Verifying Executor MUST re-evaluate the full Quorum Gate (Section 5) over the assembled trail before performing the action, regardless of incremental admission, because the executor does not trust the orchestrator to have applied admission honestly (this mirrors the base draft's execution-side enforcement, [EP-RECEIPTS]).¶
This section is informative. It describes, end to end, the process by which the normative machinery of Section 3 through Section 6 composes individual signing ceremonies into one multi-party decision. Every property named here is required elsewhere in this document or in the base draft; this section introduces no new requirements.¶
The handshake is the unit of approval. Each quorum member is one complete run of the base draft's single-approver signing ceremony ([EP-RECEIPTS]): a fresh, one-time challenge is issued for that approver against a verified handshake, bound to the exact action bytes through the action hash and binding hash it commits to, and bounded by an explicit validity window; the approver answers it with a user-verified, device-held signature whose WebAuthn assertion challenge is the member's context hash (the per-member requirement is check 2 of the Quorum Gate, Section 5). Nothing about the ceremony is weakened or amortized when the action needs several approvers: there is no batch signature and no shared signing session; k approvals are k separate ceremonies, each independently verifiable and each independently refusable.¶
Admission rejects known-invalid candidates early. Composition is incremental and server-side. Each candidate member is evaluated against the admission rule of Section 6 before it is recorded, so a wrong-action, off-roster, duplicate, out-of-order, stale, or invalidly signed member handshake is refused at the moment it is presented and never enters the trail. The stored trail has passed the listed admission checks. This does not establish every final set-level check or the authenticity of the supplied policy and enrollment data. The Verifying Executor still re-evaluates the full Quorum Gate over the assembled trail, as Section 6 requires, precisely because it does not trust the orchestrator to have admitted honestly.¶
The next proof depends on the completed prior proof. In the versioned strong profile, a successor signs the digest of the predecessor's complete signoff, including its actual signature. The chain transitively commits to prior proof artifacts. It does not establish trusted wall-clock order, backdating resistance, rendered pixels, or comprehension. Plain ordered mode checks only roster position and asserted timestamp consistency.¶
Denial is a first-class terminal outcome. Any approver in the trail can refuse, and a refusal is not the mere absence of an approval. Under the base draft a denial is signed over the same context hash with a decision of "denied" and is terminal: a denied authorization can never later become approved ([EP-RECEIPTS]). At the composition level, one dissenting approver leaves durable, attributable evidence of the dissent, which a relying party can weigh as adverse evidence rather than as a gap in the trail.¶
The composed decision is accepted at most once. A satisfied quorum is authorization evidence for one exact action and is consumable at most once within its atomic consumption domain; the relying party separately decides AUTHORIZED. Each member's signoff individually retains the base draft's one-time-consumption rule (Section 10.4), and consumption of the composed decision is refused until the Quorum Gate is satisfied; a partial trail confers no partial authority (Section 10.3).¶
The whole composition is verifiable offline. Because the satisfied/not-satisfied judgment is a pure function of the policy, the action hash, and the members (Section 5, Q5), an auditor holding the members and authenticated policy and enrollment material can recompute the checks -- per-member signatures, action binding, distinctness of humans and keys, roster admission, threshold, order and ordering chain, and window -- without contacting the operator. As in the base draft, such offline verification establishes integrity relative to the pinned keys and policy, not a trusted signing time; it does not by itself establish current validity, such as the present enrollment or revocation status of the approvers involved.¶
The quorum computation uses native signoffs, an authenticated action hash, an independently pinned policy, and enrolled approver keys. The following is the repository's standalone member wrapper. It is not an instruction to add an inline policy or other extension to the closed Trust Receipt schema of [EP-RECEIPTS]. A carrier profile using a base receipt MUST preserve that carrier's extension rules and authenticate any external policy or member mapping. The completed-signoff chain is an explicit signed context profile, not a claim that this wrapper by itself conforms to every current base-receipt wire requirement. Each member is the triple:¶
{
"role": "program_officer",
"approver_public_key": "<SPKI of the approver's enrolled key>",
"signoff": {
"@type": "ep.signoff",
"context": {
"context_type": "ep.signoff.v1",
"action_hash": "...",
"approver": "...",
"issued_at": "...",
"...": "..."
},
"webauthn": {
"authenticator_data": "...",
"client_data_json": "...",
"signature": "..."
}
}
}
¶
The context and webauthn members are exactly as
verified by the native signoff verifier. The new chain adds a
domain-separated hash over the complete prior signoff without
changing the native signature algorithm. The
role and approver_public_key are the join keys
against the Quorum Policy roster and the Approver Directory.¶
An implementation conforms to EP-QUORUM if, for the published adversarial conformance vectors, it returns the expected satisfied/not-satisfied verdict for every vector and rejects every non-conforming candidate at incremental admission with the expected reason. The reference suite (EP-QUORUM-v1) comprises the following vectors, each carrying real Class-A WebAuthn assertions:¶
| Vector | Expect | Exercises |
|---|---|---|
accept_ordered_3of3
|
satisfied | Ordered PO, AO, IG; distinct; increasing time; all action-bound |
accept_threshold_2of3
|
satisfied | Any 2 distinct approvers from a 3-slot roster |
reject_under_threshold
|
not satisfied | Fewer than required valid members |
reject_duplicate_human
|
not satisfied | One human filling two slots |
reject_out_of_order
|
not satisfied | Ordered mode, members out of roster order |
reject_action_mismatch
|
not satisfied | A member bound to a different action hash |
reject_expired_window
|
not satisfied | A member outside window_sec
|
reject_one_bad_signature
|
not satisfied | One invalid member signature |
reject_wrong_role
|
not satisfied | A correct signature by an off-roster approver |
reject_duplicate_key
|
not satisfied | One device key signing under two approver identifiers (check 5a) |
reject_broken_chain
|
not satisfied | Strong ordered mode, a member committing to the wrong completed predecessor signoff hash (check 7a) |
The reference suite is maintained such that three cross-language reference verifiers (JavaScript, Python, Go), which share one repository and are therefore a consistency check rather than clean-room independent implementations, MUST agree on every vector; divergence is a conformance defect in at least one verifier. Independent implementations remain future interoperability evidence. The "accept" vectors guard against a verifier that is too strict (denying valid quorums); the "reject" vectors guard against a verifier that is too lenient (the security-critical direction).¶
The updated corpus additionally includes an ordered 2-of-3 positive, reverse-signed context-only chains with and without the new profile label, missing and unknown profiles, and a null first predecessor. Unit tests separately exercise substitution of a different valid predecessor signature, stripping or mutation of an expected policy, and incremental admission. The corpus version is 1.1.0.¶
Earlier symbolic evidence over a fixed 2-of-2 instance does not establish the new completed-signoff construction. No formal proof of this revision's causal-chain profile is claimed. Neither old symbolic results nor same-team cross-language tests prove general k-of-n security, WebAuthn internals, directory correctness, parser behavior, collusion resistance, human comprehension, or deployment behavior.¶
EP-QUORUM inherits all Security Considerations of [EP-RECEIPTS] and adds the following. Several restate, honestly, what a quorum does not buy.¶
With authenticated enrollment and policy inputs, a satisfied distinct-human quorum establishes valid, action-bound signatures under k distinct enrolled keys and the declared quorum checks. Timestamp checks apply to asserted values; only the completed-signoff profile adds causal proof dependence. Under the signature assumptions the orchestrator cannot forge an uncompromised approver's signature ([EP-RECEIPTS]). It raises the cost of unilateral action: a single compromised agent, a single stolen key, a single coerced or malicious approver is insufficient. It does not defeat collusion among the required number of distinct humans, nor one human who controls multiple enrolled identities (an enrollment control -- the base draft's Approver Directory requirements), nor simultaneous coercion of a full quorum. As in the base draft, EP-QUORUM makes such events attributable -- named, signed, and evidenced for every member -- which is a deterrent and an audit primitive, not an impossibility proof. Implementations MUST NOT claim a quorum is collusion-proof.¶
The dangerous error in a multi-party gate is to treat ambiguity as approval. EP-QUORUM is specified so that a malformed policy, a missing or unparseable member, a partial trail, or any single failed check yields "not satisfied." A verifier MUST NOT default to satisfied on any unrecognized condition. The "reject" conformance vectors exist to catch a regression in this direction.¶
A trail short of the threshold, or one in which incremental admission has accepted some but not all required slots, authorizes nothing. A Verifying Executor presented with a partial trail MUST refuse, exactly as it refuses a missing single signoff. This is the multi-party form of the base draft's NoBypassWrite invariant.¶
The approval window (window_sec) bounds how long a
partial quorum remains completable, limiting the value to an
attacker of compromising a remaining approver after some
approvals already exist. Each member's signoff retains the base
draft's one-time-consumption nonce (G3); the window is an
additional, quorum-level constraint, not a substitute for
per-signoff replay protection. Comparing asserted timestamps
does not prevent backdating or establish freshness. The relying
party separately enforces expiry, a trusted current-time policy,
and replay state. The completed-signoff chain does not supply
those properties.¶
Because each approver signs their own Authorization Context, a malicious orchestrator can attempt to show different approvers different renderings or different initiator attestations ([EP-RECEIPTS]) while each individual signature remains valid. EP-QUORUM does not change the base draft's cross-context consistency requirement; verifiers SHOULD surface per-member context differences, and high-value ordered policies SHOULD render the prior approvers' decisions to each subsequent approver so that the trail is a chain of informed approvals rather than parallel ones. The presentation-attack mitigations of the base draft's Security Considerations apply per member.¶
Requiring more humans does not help if each rubber-stamps; it can hurt, by diffusing responsibility across a group in which no member feels decisive (the base draft's Security Considerations). Quorum policies MUST be scoped to genuinely high-consequence, low-frequency actions, and deployments SHOULD monitor per-role time-to-sign and deny rates rather than assume that more signers means more scrutiny.¶
Signing-ceremony telemetry -- the times at which each member's challenge was issued, first viewed, and approved -- SHOULD be retained alongside the trail as evidence of how much review each approval actually received. A relying party MAY apply a minimum-review-latency policy when weighing an approval: an approval recorded a very short interval after the challenge was first viewed is evidence of rubber-stamping rather than review, and a relying party may discount it or treat the bundle as containing conflicting evidence accordingly. This raises the probability that rubber-stamping is detected after the fact and makes it attributable to a named approver; it does not prevent rubber-stamping, and it does not defeat collusion or coercion, for which Section 10.1 applies. Ceremony telemetry originates with the orchestrator, so a relying party that depends on it should weigh unsigned telemetry with the same skepticism as any other operator-supplied metadata; deployments that need it to serve as evidence rather than log data can have the operator sign the ceremony record so that its integrity is independently checkable.¶
EP-QUORUM verifies distinct enrolled approvers, action-bound signatures, roles, order, and timing. Reconstructing a capability holder secret from m-of-n shares proves only that enough shares were combined. Unless each share holder separately performs the EP-QUORUM ceremony, threshold reconstruction does not prove that distinct humans reviewed or approved the action. Implementations and public claims MUST NOT describe threshold secret custody as EP-QUORUM authorization.¶
Replaced the unsound context-only chronology claim with the versioned EP-QUORUM-SIGNOFF-CHAIN-v1 completed-proof dependency profile. Specified the domain-separated digest, first-member and mixed-link rejection, authenticated policy pin, and explicit compatibility break. Clarified ordered k-of-n, unconditional key distinctness, asserted timestamp limits, carrier boundaries, and the absence of a formal proof for the new chain.¶
Clarified that EP-QUORUM's distinct-human, action-bound approval ceremony is not interchangeable with m-of-n reconstruction of a capability holder secret.¶
This document has no IANA actions.¶