<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     ipr="trust200902"
     docName="draft-kumaresan-counter-sign-00"
     category="info"
     submissionType="IETF"
     consensus="false"
     version="3"
     xml:lang="en">

  <front>
    <title abbrev="counter-sign">counter-sign: An Open Protocol for Agent-to-Human Authorization</title>
    <seriesInfo name="Internet-Draft" value="draft-kumaresan-counter-sign-00"/>

    <author fullname="Haridarman Kumaresan" initials="H." surname="Kumaresan">
      <organization>Countersign Labs</organization>
      <address>
        <email>countersignlabs@gmail.com</email>
        <uri>https://countersignlabs.com/</uri>
      </address>
    </author>

    <date year="2026" month="July" day="21"/>
    <area>Security</area>
    <keyword>authorization</keyword>
    <keyword>AI agents</keyword>
    <keyword>countersignature</keyword>
    <keyword>quorum</keyword>
    <keyword>separation of duty</keyword>
    <keyword>Ed25519</keyword>
    <keyword>WebAuthn</keyword>

    <abstract>
      <t>
        Autonomous software agents can now take consequential actions on an
        organization's behalf, such as moving money or changing production
        systems. Existing agent protocols standardize how an agent
        reaches tools and how agents reach one another, but not how a specific
        action is authorized by a human in a way that stays verifiable after the
        fact. This document describes counter-sign, an open protocol for
        agent-to-human authorization. An agent emits a signed Intent that states
        the action it proposes to take. One or more humans return signed
        Countersignatures that authorize the action, each signed with a key the
        coordinating service does not hold. Every decision produces a
        tamper-evident receipt that can be verified offline. The protocol defines
        the Intent and Countersignature objects, a per-approver-key quorum model
        for separation of duty, an enrollment registry that binds actors to keys,
        and a hash-chained receipt log. Conformance test vectors accompany the
        specification.
      </t>
    </abstract>
  </front>

  <middle>

    <section anchor="introduction">
      <name>Introduction</name>
      <t>
        Deployments of autonomous agents increasingly place a human "in the loop"
        before a high-stakes action executes. In practice, that approval step is
        improvised: a button in a chat tool, or a webhook that sets a flag inside
        one vendor's platform. Such approvals are usually not verifiable after the
        event, and they rarely survive an independent audit. In many designs, the
        service that requested the approval can also fabricate it.
      </t>
      <t>
        counter-sign specifies this missing step. Its central design principle
        is to separate <em>proposing</em> an action from <em>authorizing</em> it.
        An Agent MAY propose an action with full precision but MUST NOT be able to
        authorize it; authorization is conveyed only by a Countersignature
        produced by a key held by an approving party. The artifact of
        authorization is a signed receipt that any relying party can verify
        offline using public keys alone.
      </t>
      <t>
        This category of infrastructure -- cryptographic, human-named,
        mandate-scoped accountability chains for autonomous agents -- is what
        the authors call <em>Agent Accountability Rails</em> (AAR). counter-sign
        specifies one such rail: the agent-to-human authorization step. The
        pattern generalizes: every prior emerging technology that reached
        regulated adoption did so by shipping an accountability rail of the
        same shape (aviation's flight plan and tail number; automotive's VIN
        and driver's license; payments' PCI-DSS and tokenization; commercial
        drones' Part 107 licensing). Autonomous agents are at the moment right
        before their accountability rails exist. counter-sign is a proposed
        first component.
      </t>

      <section anchor="non-goals">
        <name>Non-Goals</name>
        <t>This document does not specify:</t>
        <ul>
          <li>Agent-to-service or agent-to-agent authentication (that is the
            domain of other work).</li>
          <li>A policy language or decision engine. counter-sign carries and
            verifies authorization decisions; it does not compute them.</li>
          <li>Transport. Delivery to an approver ("routing") is pluggable and is
            out of scope beyond the requirement that it not alter the signed
            objects it carries.</li>
        </ul>
      </section>

      <section anchor="requirements-language">
        <name>Requirements Language</name>
        <t>
          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 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and
          only when, they appear in all capitals, as shown here.
        </t>
      </section>

      <section anchor="terminology">
        <name>Terminology</name>
        <dl>
          <dt>Agent</dt><dd>A software actor that proposes an action by emitting a
            signed Intent.</dd>
          <dt>Intent</dt><dd>A signed object describing a proposed action and its
            authorization policy. An Intent asks; it never authorizes.</dd>
          <dt>Approver</dt><dd>A party that can authorize an Intent. A
            <em>keyed</em> approver signs a Countersignature with their own key. A
            <em>vouched</em> approver's decision is attested by the Authority for
            single-approver cases only.</dd>
          <dt>Countersignature (Receipt)</dt><dd>A signed object conveying one
            party's decision (approve or reject) bound to a specific Intent.</dd>
          <dt>Quorum</dt><dd>The number M of distinct keyed approvers whose
            approvals are REQUIRED to authorize an Intent.</dd>
          <dt>Default</dt><dd>The outcome that applies if the Intent's timeout
            elapses without a resolving decision.</dd>
          <dt>Authority key</dt><dd>The key operated by the coordinating service.
            It routes Intents and records outcomes but, for a quorum, holds no
            approver key.</dd>
          <dt>Org-root key</dt><dd>An organization key, distinct from the Authority
            key, that attests actor-to-key bindings in the Enrollment Registry.</dd>
          <dt>Agent Accountability Rails (AAR)</dt><dd>The class of
            cryptographic, human-named, mandate-scoped accountability
            infrastructure for autonomous agents that this document is a
            component of. counter-sign is one AAR rail (agent-to-human
            authorization); other rails may specify related surfaces such as
            credential issuance lifecycle, delegated mandate transport, or
            provider-side receipt verification.</dd>
        </dl>
      </section>
    </section>

    <section anchor="architecture">
      <name>Architecture and Trust Model</name>
      <t>
        The protocol has four nouns in one flow: Intent, Route,
        Countersignature, and Default. An Agent signs an Intent; the Intent is
        Routed to the required Approver(s); each Approver returns a
        Countersignature; if no resolving decision arrives before the timeout, the
        signed Default applies. Every terminal outcome produces a receipt.
      </t>
      <t>
        The core invariant is <em>separation of duty</em>: for a quorum
        greater than one, each approval is a distinct signature produced by a
        distinct key that the coordinating (Authority) service does not hold. A
        party in possession of the Authority key therefore cannot forge a quorum
        or approve on an approver's behalf.
      </t>
    </section>

    <section anchor="canonicalization">
      <name>Canonicalization</name>
      <t>
        All signatures are computed over a deterministic, domain-separated
        canonical serialization of the object being signed. Implementations MUST
        produce byte-identical canonical forms for equal objects. The exact
        canonical JSON rules and byte encoding are defined normatively by the
        specification (comparable in approach to the JSON Canonicalization Scheme
        <xref target="RFC8785"/>) and are exercised by the canonical-JSON
        known-answer conformance vectors (<xref target="vectors"/>).
      </t>
    </section>

    <section anchor="crypto">
      <name>Cryptographic Primitives</name>
      <t>
        Signatures use Ed25519 <xref target="RFC8032"/>. Public keys are encoded
        as base64url without padding. Every signature commits to an
        artifact-type <em>signing context</em> (a domain-separation label) so that
        a signature created for one object type, such as an Intent, cannot be
        replayed as another, such as a Countersignature or a delivery link. Human
        approvers MAY sign using a WebAuthn assertion
        <xref target="WebAuthn"/> from a platform authenticator (a "passkey"); the
        private key never leaves the approver's device and is never transmitted to
        the Authority.
      </t>
    </section>

    <section anchor="intent">
      <name>The Intent Object</name>
      <t>
        An Intent is emitted by an Agent before any consequential action. It binds
        the action to its authorization policy and is signed by the Agent key. An
        Intent carries, at minimum: a protocol version; a unique intent
        identifier; the action and a human-readable summary; a risk tier; the set
        of permitted approvers; the required quorum; a timeout; and the default
        outcome. An Intent MUST be treated as a request only; possession or
        verification of an Intent conveys no authority.
      </t>
    </section>

    <section anchor="enrollment">
      <name>Enrollment Registry</name>
      <t>
        The Enrollment Registry is an append-only, hash-chained log that binds each
        actor to the key(s) it is authorized to sign with. Each binding MUST be
        attested by an Org-root key that is distinct from the Authority key, and
        MUST include proof of possession of the enrolled key (a raw Ed25519
        self-signature, or a WebAuthn assertion for a passkey). The registry
        supports revocation. A verifier uses the registry to establish which key
        legitimately speaks for a given actor.
      </t>
    </section>

    <section anchor="countersignature">
      <name>The Countersignature (Receipt) Object</name>
      <t>
        A Countersignature conveys one party's decision, bound to a specific
        Intent, and signed by that party's key. It carries: the decision (approve
        or reject); the acting actor; the policy under which it was produced; a
        binding to the exact Intent; and a timestamp. A keyed Countersignature is
        signed by the approver's own enrolled key. A verifier MUST confirm both the
        signature and the actor-to-key binding before treating a Countersignature
        as authoritative.
      </t>
    </section>

    <section anchor="quorum">
      <name>Quorum and Separation of Duty</name>
      <t>For a quorum M greater than one:</t>
      <ul>
        <li>M <em>distinct</em> keyed approvers MUST each produce a
          Countersignature; repeated approvals from the same actor count once
          (no self-quorum).</li>
        <li>A single reject resolves the Intent immediately and finally, regardless
          of how many approvals were already collected (any-reject veto).</li>
        <li>A quorum greater than one MUST NOT be combined with a vouched approver,
          and MUST NOT be combined with a default outcome of approve
          (fail-closed).</li>
        <li>Each approver's permitted key is bound in the Agent-signed Intent, so an
          approver's key cannot be silently substituted.</li>
      </ul>
      <t>
        These rules are exercised by accept and reject conformance vectors,
        including under-quorum, forged-quorum, and wrong-authority-key cases.
      </t>
    </section>

    <section anchor="defaults">
      <name>Timeout Defaults</name>
      <t>
        Every Intent declares a signed Default and a timeout. If the timeout
        elapses without a resolving decision, the Default applies and MUST itself
        produce a signed receipt, so that a timeout is never an ambiguous outcome.
      </t>
    </section>

    <section anchor="receipt-log">
      <name>Receipt Log</name>
      <t>
        Receipts MAY be recorded in an append-only, hash-chained Receipt Log. Each
        receipt is independently tamper-evident through its own signature; the
        chain additionally makes any reorder, edit, or deletion detectable relative
        to an externally anchored head. Verification distinguishes authenticity of
        individual receipts from completeness of the log against a caller-held
        anchor (expected head). A keyless hash chain is not, by itself, standalone
        tamper-evidence: its integrity claim is only as strong as the anchor a
        relying party retains.
      </t>
    </section>

    <section anchor="resolution">
      <name>Verification and Resolution Algorithm</name>
      <t>
        A relying party resolves an Intent to "authorized" or "denied" by: (1)
        verifying the Intent's Agent signature and canonical form; (2) collecting
        Countersignatures and verifying each signature and actor-to-key binding
        against the Enrollment Registry; (3) applying the quorum and veto rules of
        <xref target="quorum"/>; and (4) applying the Default if the timeout has
        elapsed. A relying party MUST NOT act on an Intent that has not resolved to
        "authorized", and MUST NOT treat an unverifiable receipt as a decision.
      </t>
    </section>

    <section anchor="conformance">
      <name>Conformance</name>
      <t>
        An implementation claims conformance by reproducing the accompanying
        conformance test vectors (<xref target="vectors"/>). The vectors are
        deterministic and language-neutral: fixed test keys, canonical-JSON
        known-answers, signing-context message and signature vectors, signed
        Intents, keyed/vouched/default receipts, resolution accept and reject
        cases, and the receipt-log chain head. A conforming implementation MUST
        pass all applicable vectors. The reference implementation and vectors are available at <xref target="COUNTERSIGN"/>.
      </t>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>
        The principal threat this protocol addresses is a compromised or dishonest
        coordinating (Authority) service. Because each quorum approval is signed by
        a key the Authority does not hold, a compromised Authority cannot forge a
        quorum or self-approve; at most it can withhold or delay routing, which
        fails closed to the signed Default.
      </t>
      <t>
        Signatures are domain-separated by signing context to prevent
        cross-protocol signature replay. Intents SHOULD carry a unique identifier
        and be bound to their receipts to prevent replay of a stale approval
        against a new action. Key rotation and revocation are handled through the
        Enrollment Registry. Non-repudiation extends only to the keys enrolled for
        an actor; compromise of an approver's device key is out of scope for the
        protocol and is mitigated by platform authenticator protections and
        revocation. The v0.1-to-v0.2 change (see <xref target="changes"/>) is a
        deliberate, non-backward-compatible break: prior integrity-only receipts
        were forgeable by a compromised Authority and MUST be rejected by a v0.2
        verifier rather than silently accepted.
      </t>
    </section>

    <section anchor="privacy">
      <name>Privacy Considerations</name>
      <t>
        Receipts reveal the acting approver's identity, the action, and timing to
        any party that holds them. Deployments SHOULD minimize the identifying
        content carried in Intents and receipts. Selective disclosure of receipt
        fields is future work.
      </t>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>
        This document has no IANA actions at this time. A future revision may
        request registration of the protocol's signing-context labels and any
        media types used for the Intent and Countersignature objects.
      </t>
    </section>

  </middle>

  <back>
    <references>
      <name>Normative References</name>
      <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author initials="S." surname="Bradner" fullname="S. Bradner"/>
          <date year="1997" month="March"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
      </reference>
      <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author initials="B." surname="Leiba" fullname="B. Leiba"/>
          <date year="2017" month="May"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="8174"/>
      </reference>
      <reference anchor="RFC8032" target="https://www.rfc-editor.org/info/rfc8032">
        <front>
          <title>Edwards-Curve Digital Signature Algorithm (EdDSA)</title>
          <author initials="S." surname="Josefsson" fullname="S. Josefsson"/>
          <author initials="I." surname="Liusvaara" fullname="I. Liusvaara"/>
          <date year="2017" month="January"/>
        </front>
        <seriesInfo name="RFC" value="8032"/>
      </reference>
      <reference anchor="WebAuthn" target="https://www.w3.org/TR/webauthn-2/">
        <front>
          <title>Web Authentication: An API for accessing Public Key Credentials Level 2</title>
          <author><organization>W3C</organization></author>
          <date year="2021" month="April"/>
        </front>
      </reference>
    </references>

    <references>
      <name>Informative References</name>
      <reference anchor="RFC8785" target="https://www.rfc-editor.org/info/rfc8785">
        <front>
          <title>JSON Canonicalization Scheme (JCS)</title>
          <author initials="A." surname="Rundgren" fullname="A. Rundgren"/>
          <author initials="B." surname="Jordan" fullname="B. Jordan"/>
          <author initials="S." surname="Erdtman" fullname="S. Erdtman"/>
          <date year="2020" month="June"/>
        </front>
        <seriesInfo name="RFC" value="8785"/>
      </reference>
      <reference anchor="COUNTERSIGN" target="https://github.com/countersign-labs/counter-sign">
        <front>
          <title>counter-sign: reference implementation, specification, and conformance vectors</title>
          <author fullname="Haridarman Kumaresan" initials="H." surname="Kumaresan"/>
          <date year="2026"/>
        </front>
      </reference>
    </references>

    <section anchor="vectors">
      <name>Conformance Test Vectors</name>
      <t>
        The normative conformance vectors are published with the reference
        implementation at
        &lt;https://github.com/countersign-labs/counter-sign/tree/main/vectors&gt;.
        A future revision of this document MAY inline them or a stable subset.
      </t>
    </section>

    <section anchor="example">
      <name>Worked Examples</name>
      <t>
        These examples are illustrative. Field names and encodings are shown for
        clarity; the normative encoding is defined by the specification and the
        conformance vectors (<xref target="vectors"/>). Identifiers and signatures
        are abbreviated.
      </t>

      <section anchor="example-post">
        <name>Publishing a Social-Media Post (Two-of-Two Quorum)</name>
        <t>
          An autonomous content agent proposes publishing a public brand post.
          Because the action communicates publicly on the organization's behalf,
          policy requires two distinct approvers, each signing with their own
          enrolled key. The Agent emits and signs the Intent:
        </t>
        <sourcecode type="json"><![CDATA[
{
  "countersign": "0.2",
  "intent_id": "3f0a2c7e-1d54-4b8a-9e02-6c1b7ad9e921",
  "action": "social.post.publish",
  "summary": "Publish the Q3 campaign post to the company page",
  "risk_tier": "high",
  "approvers": [
    { "actor": "m:cmo", "key": "ed25519:aF92...6c1" },
    { "actor": "m:brand-lead", "key": "ed25519:71bd...d3e" }
  ],
  "quorum": 2,
  "timeout": 900,
  "default": "reject",
  "agent_sig": "ed25519:7bc1...e94a"
}
]]></sourcecode>
        <t>
          Each approver reviews the request and returns a Countersignature signed
          with their own enrolled key:
        </t>
        <sourcecode type="json"><![CDATA[
{ "countersign": "0.2",
  "intent_id": "3f0a2c7e-...-e921",
  "decision": "approve",
  "actor": "m:cmo",
  "time": "2026-07-21T09:14:07Z",
  "actor_sig": "ed25519:2ce7...a10b" }

{ "countersign": "0.2",
  "intent_id": "3f0a2c7e-...-e921",
  "decision": "approve",
  "actor": "m:brand-lead",
  "time": "2026-07-21T09:15:42Z",
  "actor_sig": "ed25519:9f04...77dc" }
]]></sourcecode>
        <t>
          The relying party verifies each signature and each actor-to-key binding
          against the Enrollment Registry, confirms the two approvers are distinct,
          and finds the quorum of two satisfied. The Intent resolves to
          <em>authorized</em>; both Countersignatures are appended to the
          hash-chained Receipt Log, and the post is published.
        </t>
      </section>

      <section anchor="example-api">
        <name>Gating an Outbound API Action (Timeout to Default)</name>
        <t>
          An agent proposes a state-changing API call. Rather than executing it,
          the caller captures the call as an Intent and holds it pending
          authorization. The Intent requires one enrolled approver:
        </t>
        <sourcecode type="json"><![CDATA[
{
  "countersign": "0.2",
  "intent_id": "b41d0e6f-77a2-4c19-8f5e-0aa3d51902af",
  "action": "api.request.execute",
  "summary": "POST /v1/payouts - release vendor payout",
  "risk_tier": "high",
  "approvers": [
    { "actor": "m:controller", "key": "ed25519:c3a8...9f2" }
  ],
  "quorum": 1,
  "timeout": 600,
  "default": "reject",
  "agent_sig": "ed25519:5da2...1b7c"
}
]]></sourcecode>
        <t>
          No approver responds within the 600-second deadline. The Default applies,
          and the Authority records a signed timeout receipt so that the silence is
          itself an auditable outcome:
        </t>
        <sourcecode type="json"><![CDATA[
{ "countersign": "0.2",
  "intent_id": "b41d...02af",
  "decision": "reject",
  "actor": "default:timeout",
  "time": "2026-07-21T11:03:11Z",
  "authority_sig": "ed25519:0ab9...4e5f" }
]]></sourcecode>
        <t>
          The Intent resolves to <em>denied</em>, the captured API call is never
          executed, and the timeout receipt is appended to the Receipt Log.
        </t>
      </section>
    </section>

    <section anchor="changes">
      <name>Changes from v0.1 to v0.2</name>
      <t>
        Version 0.2 introduces per-approver-key quorum (cryptographic separation of
        duty), the Enrollment Registry, and experimental passkey/WebAuthn human
        signing. It is a deliberate wire-format break from v0.1: v0.1 receipts were
        integrity-only and forgeable by a compromised Authority, and are rejected
        by a v0.2 verifier.
      </t>
    </section>


  </back>
</rfc>
