Internet-Draft NOA Settlement Evidence August 2026
Toraman Expires 15 February 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-toraman-noa-settlement-evidence-01
Published:
Intended Status:
Standards Track
Expires:
Author:
T. Toraman
NordenSoft

Settlement Evidence for Human-Approved Agent Payments

Abstract

This document defines a signed side artifact that records the observation, on a public ledger, of a payment authorization whose correlation value was committed before dispatch. The artifact references the authorization record and the execution grant by hash and does not modify either. It permits a Relying Party that obtains ledger facts itself to establish that a payment authorization bearing the correlation value was consumed on the token contract and network the mandate itself committed to, transferring a stated value to a stated recipient, and that the correlation value is recomputable from a specific authorization record, a specific single-use execution grant, and the parameter pre-image that record commits to. It does not establish that any service was delivered, that any obligation was discharged, or that the payment achieved its purpose. It does not establish that the approving principal — as opposed to the payer key — authorized the transfer. Those scope limits are permanent and are restated wherever this artifact is described.

Status of This Memo

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 15 February 2027.

Table of Contents

1. Introduction

Agent payment protocols increasingly carry a client-signed payment authorization that a token contract verifies on-chain before moving funds. Where that authorization contains a caller-chosen, contract-verified, uniqueness-only identifier, a mandate can commit to that identifier before dispatch, and the ledger's own record of the authorization's consumption becomes a correlation witness that requires no cooperation from the payee, the facilitator, or any other intermediary.

This is a narrow and specific gain, and the narrowness is the point: it converts "the party we paid says we paid" into "the token contract, executing inside the ledger's consensus, recorded that this exact authorization — the one whose 32 octets we committed before dispatch — was consumed." It converts nothing else.

1.1. Verification philosophy

This document is written under the same discipline as its companion [I-D.toraman-noa-action-digest], which its authors call the Copenhagen model: a verifier never states what happened; it states what the supplied evidence supports, and it records what it does not claim. That discipline is why Section 7.1 exists as a permanent section rather than a caveat, why Section 7.2 is an open list rather than a taxonomy, and why Section 6.4 states in plain words the one thing about this artifact that is uncomfortable — that it is the single artifact in this family that cannot be fully verified offline.

The name is a nod to a remark about physics — that the subject concerns what we can say about nature — attributed to Niels Bohr, secondhand, via Aage Petersen's 1963 recollection; it does not appear in Bohr's own writings, and this document claims no more for the attribution than that.

1.2. Editorial note on this revision

This subsection is non-normative and is to be removed before publication as an RFC.

Two conditions govern the release of this document:

  1. This document is not submitted while any placeholder-marked item in Section 5 or in Section 7 is unresolved. No such item is open. The last one, deployment availability, is resolved not by settling the question universally but by replacing an unmeasured assumption with a measurement whose scope is stated in full: Section 7.7 now reports what was measured on 2026-08-14, and names the three things that measurement does not establish — including that it never exercised a funded settlement. Exactly one unresolved item remains anywhere in this document — the registry request awaiting working-group direction in Section 9 — and this condition does not cover it.
  2. No claimed defence is submitted without a conformance vector that enforces it. A stated requirement on an unimplemented interface is a request, not a control, until a conformance vector enforces it. The corpus that enforces the checks in Section 6.2 is the Copenhagen vectors, the cross-language conformance corpus published with the reference implementation; Appendix A names its location and its contents.

2. Conventions and Terminology

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.

3. Relationship to the Frozen Base Record

As in [I-D.toraman-noa-action-digest]. The Authorization Record is not modified. This artifact references it by hash.

4. The Correlation Mechanism

The mechanism relies on three properties of the target payment rail. All three were verified against [EIP3009] and against live testnet and mainnet state on 2026-08-12:

Ordering requirement. The Correlation Value is computed and committed at approval time and injected at dispatch (P5 of [I-D.toraman-noa-action-digest]). The reverse construction — hashing the payment payload into the mandate's parameter commitment — is not viable, for two independent reasons: the payload does not exist at approval time (it carries dispatch-time validity timestamps and must be signed by the payer key at dispatch), and the settlement transaction embeds the payload's fields, never a hash of the payload as a blob, so there would be nothing on-chain to compare against.

4.1. The two-stage derivation

The Correlation Value is not the Action Digest. The Action Digest is a dispatch-time input to a derivation that also binds the rail coordinates the mandate committed to and a high-entropy seed; the value that reaches the ledger is the derivation's output. The Action Digest itself never touches the ledger.

dispatchId  = the Action Digest, as its ASCII string form "sha256:" || 64 lowercase hex
              (the "sha256:" prefix IS part of the pre-image)
seed        = the 32 octets hex-decoded from the Execution Grant's single-use nonce
chainId     = the ledger's numeric chain identifier, from the verified Parameter Pre-image
token       = the token contract address, from the verified Parameter Pre-image
payer       = the payer address, from the verified Parameter Pre-image

field(n,v)  = UTF8( n || ":" || decimal-ASCII(octet-length(v)) || ":" ) || v
PREIMAGE    = field("domain",   UTF8("noa.x402.correlation-nonce/0.1"))
           || field("chainId",  decimal-ASCII(chainId))
           || field("token",    lowercase-ASCII(token))
           || field("payer",    lowercase-ASCII(payer))
           || field("dispatch", ASCII(dispatchId))
           || field("seed",     seed)
CORRELATION = "0x" || lowercase-hex(SHA-256(PREIMAGE))

Every field is length-prefixed because dispatchId is variable-length text: concatenating variable-length fields without their lengths lets two different input sets produce one pre-image. The domain string is a version tag; a change to any of the encoding rules above is a new tag, never a silent re-derivation, so a value can always be attributed to the rule that produced it.

Six requirements follow. D1, D2, D5 and D6 are each enforced by a named case in the conformance corpus, and Appendix A names them. D3 and D4 are not, and this document says so rather than implying an enforcement that does not exist: D3 is enforced by the derivation refusing at its own boundary and by the grant schema pinning the nonce to a fixed 64 hex characters, and D4 follows from SHA-256's output width. Neither is a defence a settlement vector could add anything to.

  • D1 — the derivation inputs are the mandate's, never the artifact's. chainId, token and payer MUST be taken from the Parameter Pre-image after it has been verified against the Parameter Commitment, and from nowhere else — never from the artifact's own reported coordinates, and never from the Authorization Record's chain identifier, which names a record chain and not a ledger. A verifier that lifts them from the artifact accepts a uniformly self-consistent testnet settlement as a mainnet one, because it has compared attacker-chosen values against an event whose location the attacker chose.
  • D2 — the seed is the grant's nonce, and there is no seed member. The signed, single-use Execution Grant is the pre-dispatch seed commitment: one grant yields exactly one derivable Correlation Value, and that singularity is a property of the derivation rather than a rule an implementation is asked to keep. The artifact MUST NOT carry the seed and MUST NOT carry a seed commitment; a value the verifier is handed is a value an attacker can choose.
  • D3 — a short seed is a refusal, never a padding. The seed MUST be at least 32 octets and a derivation given fewer MUST fail rather than pad. A short seed is the one input whose weakness is invisible in the output: the value still looks like 32 random octets while being searchable.
  • D4 — the width is set by the derivation, not by the digest. SHA-256 [FIPS180-4] output is exactly the 32 octets the identifier field takes, with no truncation or padding step to get wrong.
  • D5 — the Relying Party recomputes; it never accepts. The artifact carries the Correlation Value so that a reader can see it, not so that a verifier can use it: the verifier derives the value from the bundle's own documents and compares byte-exactly, lowercased, against both the artifact's member and the ledger event's identifier topic. Item 6 of Section 6.2 states this mechanically.
  • D6 — the one-stage form MUST be refused, not normalized. A correlation member carrying the Action Digest's sha256: string form is structurally invalid under this document. A verifier that accepts it and re-encodes it has accepted the construction this one replaced.

Why the seed is there, in one sentence, with its cost stated in Section 7.8: without it the other five inputs are enumerable by anyone who knows the deal, and a deterministic public identifier on a permanent ledger both leaks equality between settlements and permits recovery of the mandate's parameters by guessing them — neither of which is repairable after the fact.

5. The noa.settlement-evidence/0.1 Artifact

The artifact follows the reference implementation's side-artifact conventions, which are frozen and non-obvious, and it must be registered in that implementation's artifact authority table — with a signer type and role — before it can verify at all.

5.1. Members

The root members are, in the schema's required order: spec, tenant, chain, authorizationReceiptHash, executionGrantHash, correlation, railFamily, chainWitness, railReceipt, observerKid, observedAt, sig. The object is closed (additionalProperties:false) at the root and on every nested object, so a member not in this table is a refusal, not a tolerance.

Table 1
Member Meaning Role
spec "noa.settlement-evidence/0.1" fixed
tenant the tenant the bundle belongs to reject-only
chain the Authorization Record's record-chain id reject-only
authorizationReceiptHash the ALLOWED Authorization Record receipt-reference rule
executionGrantHash the Execution Grant side-artifact rule
correlation the Correlation Value recomputed, never accepted
railFamily the rail family reject-only, fail-closed
chainWitness the observed ledger facts reported; reject-only
railReceipt the counterparty's receipt, or null never load-bearing
observerKid who queried the ledger MUST equal sig.kid
observedAt when the Observer looked time qualifier
sig the Observer's signature not the evidence

Exact forms are in Section 5.1.1. What each member is for, in the order above:

  • tenant and chain are reject-only. Both are compared against the verifier's own expected values and are never lifted from the bundle. chain names a record chain, not a ledger, and is never a derivation input (D1 of Section 4.1).
  • The two reference hashes use two different rules, and they must not be interchanged. authorizationReceiptHash is the receipt-reference rule: sha256: over the canonical bytes of the record without its own chain hash and without its signature value — which is exactly the record's own committed chain hash. executionGrantHash is the side-artifact rule: sha256: over the canonical bytes of the whole signed grant, signature included.
  • correlation is recomputed, never accepted (item 6 of Section 6.2). A value written in the Action Digest's sha256: string form is a structural refusal (D6), and uppercase hex is a refusal rather than something to normalize. There is deliberately no actionDigest member and no seed member: the digest is a derivation input the verifier reconstructs, and the seed is the grant's.
  • railFamily fails closed. It is the Observer's reported family, checked against the closed set the verifier ships; an unknown family MUST refuse, because different rails bind the identifier with different strength. It is not a derivation input.
  • chainWitness carries the reported ledger facts — nine always-present members, the last five nullable. network and asset carry the chain and token coordinates; payee and amount are the recipient and the value. They are nullable on the wire, but a SETTLED status forbids payee or amount being null, because without them Section 6.2 cannot bound the recipient or the amount. amount is a decimal string, never a JSON number — canonical-JSON hardening ([RFC8785]) rejects non-safe integers and uint256 exceeds them, so a numeric amount would make ordinary payments uncanonicalizable. status has three values, never two: we did not look and we looked and saw nothing yet are different statements — and there is deliberately no FAILED, because a ledger can witness a non-consumption-so-far, never a failure. Every one of these fields is reject-only: they are compared against the verified Parameter Pre-image and can only ever cause a refusal. An implementation that derives from them has replaced the mandate's commitment with the artifact's self-declaration — see Section 7.3. A confirmation count is deliberately absent: it would be the Observer's count of a moving quantity, and item 9 of Section 6.2 requires the Relying Party to compute depth against its own head.
  • railReceipt is structurally non-load-bearing. No verifier validates its signature, parses it for a load-bearing fact, or lets its presence, absence, content or format move any verdict. See Section 7.1 on counterparty evidence.
  • observedAt is the instant the Observer looked; a NOT_SETTLED conclusion is time-qualified against it (Section 7.4). observerKid and sig are covered by Section 6.4 — the signature is not the evidence.

5.1.1. Wire grammars

Every member's exact accepted form, so that a conforming artifact can be constructed from this document alone. All hex is lowercase; uppercase is a refusal, never a normalization. Instants are RFC 3339 [RFC3339]. The network identifier is a CAIP-2 [CAIP2] chain ID and the asset identifier is a CAIP-19 [CAIP19] asset ID, each further narrowed by the grammar below to the eip155 namespace this version covers.

spec        = "noa.settlement-evidence/0.1"           ; exact
tenant      = 1..256 characters
chain       = 1..256 characters
authorizationReceiptHash = "sha256:" 64LOWHEX
executionGrantHash       = "sha256:" 64LOWHEX
correlation = "0x" 64LOWHEX
railFamily  = 1..64 chars matching
              ^[a-z0-9]([a-z0-9./+-]{0,62}[a-z0-9])?$
              ; the ONE value this verifier ships:
              ;   "x402/exact/eip3009"

chainWitness.status  = "SETTLED"
                     / "NOT_SETTLED_AT_OBSERVATION"
                     / "NOT_OBSERVED"
chainWitness.network = CAIP-2 form, narrowed to:
                       ^eip155:[1-9][0-9]{0,18}$      ; eip155:8453
chainWitness.asset   = CAIP-19 form, narrowed to:
                       ^eip155:[1-9][0-9]{0,18}
                        /erc20:0x[0-9a-f]{40}$        ; one string
chainWitness.payer   = "0x" 40LOWHEX                  ; never null
chainWitness.payee   = "0x" 40LOWHEX / null
chainWitness.amount  = minor-units decimal / null
                       ^(0|[1-9][0-9]{0,77})$
                       ; NO leading zeros, max 78 digits; a STRING
                       ; because uint256 exceeds float-safe integers
chainWitness.txHash      = "0x" 64LOWHEX / null
chainWitness.blockNumber = integer >= 0  / null
chainWitness.settledAt   = RFC 3339 instant / null

railReceipt = null
            / { disclosure: "FULL", format,
                encoding: "base64", bytes }
            / { disclosure: "HASH_ONLY", format, bytesHash }
  format    = "x402-offer-receipt/eip712"
            / "x402-offer-receipt/jws"
            / "opaque"
  bytes     = strict base64, 1..262144 chars, and MUST
              survive a decode/re-encode round trip exactly
  bytesHash = "sha256:" 64LOWHEX   ; over RAW received bytes

observerKid = 1..128 characters    ; MUST equal sig.kid
observedAt  = RFC 3339 instant
sig         = { alg: "ed25519", kid, value }
  alg       = "ed25519"            ; exact, no other value
  kid       = 1..128 characters
  value     = 1..512 characters; base64 of the 64-octet
              Ed25519 signature (88 base64 chars in practice)

64LOWHEX = 64 * %x30-39 / %x61-66      ; [0-9a-f]
40LOWHEX = 40 * %x30-39 / %x61-66

Both objects are closed: the root admits exactly the twelve members above and chainWitness exactly its nine. Any other member, at any depth, is a refusal.

An Observer-supplied finality policy is deliberately not a member of this artifact. A verdict computed under an Observer-supplied policy is a verdict the Observer chose, and an inert field is a field an implementer will eventually honour. txStatus is likewise not a member of this artifact: the transaction status the verifier requires lives in the chain-facts input (noa.chain-facts/0.1), an external verifier input obtained by the Relying Party from its own node, not in this signed observation. There is no logIndex, and no confirmation count, on the wire.

The artifact's member set distinguishes the Observer's reported fields, which are reject-only, from the Relying Party's own ledger facts. That distinction is what makes Section 6.4 mechanical rather than aspirational.

Three hash rules exist in the reference implementation and are deliberately distinct: a receipt reference, a side-artifact reference including the signature, and a signing pre-image with the entire signature object removed. The receipt rule strips the chain hash and the signature value; the side-artifact rule keeps the whole signature object; the signing pre-image removes it entirely. Conflating them is a forgery channel. The table above names which applies to each member; an implementation that uses the wrong one produces an artifact that verifies against the wrong bytes.

5.2. Prohibited content

The artifact MUST NOT contain any member whose semantics are, or could be read as, service delivery, goods receipt, obligation discharge, satisfaction, or fulfilment. A profile that adds such a member is not a profile of this document. See Section 7.1.

6. Verification

6.1. Authenticate the artifact before trusting any field

This step is normative and it comes first. A positive result computed over an unauthenticated Settlement Evidence artifact means nothing, and the reference reconciler says so in those words: it operates over pre-verified inputs, and its own positive result "over unauthenticated inputs means nothing." Before reading a single field of the artifact for the checks in Section 6.2, a Relying Party MUST:

  • validate the artifact against its own schema — root and every nested object closed (additionalProperties:false), every required member present, correlation lowercase 0x + 64 hex, chainWitness well-formed. A member the schema does not define is a refusal, not a tolerance;
  • verify the Observer's Ed25519 [RFC8032] signature (sig.value, base64) against a key the Relying Party resolves through its own keyring — never a key named only by the artifact. The signed octets are exactly:
  MESSAGE = UTF8("NOA-SettlementEvidence-v0.1-sig:")
         || SHA-256( JCS( artifact with the whole `sig` removed ) )

— the domain tag, a literal colon, then the raw 32-octet SHA-256 [FIPS180-4] of the RFC 8785 [RFC8785] canonical JSON of the document with the entire sig object removed (not merely sig.value blanked). The domain tag is what stops a signature over one artifact kind from ever verifying as another; - check the signer's TYPE and ROLE against the trust matrix: the signer MUST be of type GATE and hold the role settlement-observer, and MUST satisfy observerKid == sig.kid. A key of any other type or role MUST be refused. Provisioning a settlement-observer key is an operator ceremony; a deployment that has enrolled none refuses every such artifact at this check, fail-closed; - check key activation and revocation, and note carefully whose clock decides. Activation is evaluated at a verifier-controlled time — the caller-supplied authorization time, or, absent that, the verifier's own "now" — and never at a time the artifact itself supplies. The reason is an asymmetry worth stating: a stolen key chooses every timestamp inside the document it signs, so only caller-controlled time may move a verdict toward accept. The artifact's own observedAt is used in one direction only: if it precedes the key's validFrom, that self-contradiction is a reject-only signal; it can never activate a key. Revocation is blunter still: any non-null revocation timestamp on the key rejects the artifact outright. The verifier does not ask whether the observation predates the revocation, because a signer-chosen artifact time is not an independent witness of when the key was still good.

Only once the artifact is authenticated do the recompute-and-reconcile checks below have anything to operate on. These four are owned by the generic side-artifact verifier, not by the reconciler that performs Section 6.2; a Relying Party that runs Section 6.2 over an artifact it did not first authenticate has verified nothing.

6.2. Required checks

Over an artifact that has passed Section 6.1, a Relying Party MUST:

  1. verify the Authorization Record and Execution Grant and their mutual bindings ([I-D.toraman-noa-action-digest]), including its non-optional signature checks, through the authenticating entry point — the one that verifies the record chain and checks the grant's signature against a keyring the Relying Party holds independently. A bare digest builder verifies no signature and will compute a perfectly well-formed digest over a fully forged pair;
  2. recompute the Action Digest from those verified documents, and verify the Parameter Pre-image against the Authorization Record's Parameter Commitment. Both are inputs to item 6, not values compared against anything the artifact supplies: the artifact carries neither, so there is nothing here for an attacker to substitute. If the Pre-image is absent, or if the Parameter Commitment is a keyed hash the Relying Party cannot reproduce, the Correlation Value cannot be derived at all and the verifier MUST return SETTLEMENT_BOUNDS_UNCHECKABLE (Section 6.3) — a non-positive, non-failure result: never a positive verdict, and never a positive verdict qualified by a warning;
  3. check railFamily is a member of the set this verifier ships, and fail closed on an unknown family;
  4. check that the resolved event is of the expected type on the chain named by chainWitness.network (CAIP-2) at the token contract named by chainWitness.asset (CAIP-19), and that the chain and token contract are values the mandate committed to — the network and the asset are derived from the verified Parameter Pre-image's own networkCaip2 and assetCaip19 and compared, never lifted from chainWitness — see Section 7.3;
  5. check that the event's authorizer topic equals chainWitness.payer, and that chainWitness.payer is the payer the mandate committed to — see Section 7.3;
  6. recompute the Correlation Value per Section 4.1 — from the Action Digest of item 2, the Execution Grant's nonce as the seed, and the chain identifier, token contract and payer address taken only from the verified Parameter Pre-image — and check that it equals both the event's identifier topic and the artifact's correlation member, comparing the 64 hex characters lowercased on both sides. A supplied Correlation Value MUST NOT be trusted, and a Correlation Value in the Action Digest's sha256: form MUST be refused rather than re-encoded (D5, D6);
  7. check that exactly one such event exists for (authorizer, identifier) on that contract, and that its transaction status is success;
  8. recover the transfer and bound it. Decode the settling transaction's calldata, or take the token Transfer(from,to,value) log emitted in the same transaction — the log MUST be emitted by the token contract named in the mandate-committed asset, its from MUST equal chainWitness.payer, and there MUST be exactly one such log (more than one -> not reconfirmed, never a pick). Then check the recovered to equals chainWitness.payee, the recovered value equals chainWitness.amount, and both lie within the bounds the mandate committed to;
  9. compute confirmation depth against its own ledger head and apply its own finality threshold;
  10. on any mismatch, return a distinct machine-readable code from the closed registry of Section 6.3, never a generic invalid.

Items 4 and 8 close two attacks that a weaker check set leaves open, and they are the reason chainWitness — with its network, asset, payer, payee and amount — is a required member. Neither is closed by Section 4.1: binding the rail coordinates inside the Correlation Value fixes which coordinates a recomputation can be performed against, and it does not stop a settlement from being performed somewhere else.

(a) Approve-A-run-B on the rail. AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce) carries two fields. to, value, validAfter and validBefore are inside the signed struct and not in the event. A check set that verifies the authorizer and the identifier and stops there has stopped at the least economically significant of the three: a dispatcher — which this document's threat model declares untrusted — takes the approved 32 octets, has the payer key sign to = attacker, value = anything, settles, and every remaining check passes. Item 8 closes it.

(b) Counterfeit asset or foreign chain. If the mandate commits to the payer and nothing else, then chainWitness.network and chainWitness.asset are checked against the artifact's own self-declared values — an attacker-chosen value compared against an event whose location the attacker chose. Any contract may implement this interface: deploy a valueless token, sign an authorization in its signing domain carrying the correlation, settle for cents, fill in chainWitness to match, and every remaining check passes while no value moved. Note the attacker here holds the payer key, so the authorizer-topic check does not fire. Item 4 closes it.

A permanent scoping consequence: because the identifier's uniqueness is enforced per (authorizer, nonce) per contract per chain, the mechanism is defensible only for explicitly enumerated token deployments. A profile MUST name the (network, token contract) pairs it covers — in the CAIP-2 / CAIP-19 forms of Section 5.1.1 — and a generic "any token implementing this interface" is not in scope. This is a requirement on the profile, not a description of the check in Section 6.2: the verification path above compares the observed coordinates against the mandate's own verified Parameter Pre-image and consults no registry of its own, so a deployment that wants an allowlist enforced MUST enforce it itself. See Section 7.1.

6.3. Result codes

Every outcome carries exactly one code from this closed registry, and the registry is closed in both directions: a conforming verifier emits no code outside it, and every code in it is reachable. The three-way split is normative, and the middle class is the one implementers get wrong: a non-positive result is not a rejection.

Positive — exactly one. SETTLEMENT_CORRELATED_AND_RECONFIRMED. Only this code may upgrade any outcome anywhere.

Non-positive, and NOT failures. These say "the evidence does not support a positive verdict", never "something is wrong". Reporting one as a rejection is a conformance error:

  • SETTLEMENT_CORRELATED_UNRECONFIRMED — correlated, but the ledger facts did not (or could not) confirm it: no chain facts supplied, or the caller's own depth/freshness thresholds unmet. This is the code for "not final";
  • SETTLEMENT_NOT_OBSERVED — the Observer did not look;
  • SETTLEMENT_NOT_SETTLED_AT_OBSERVATION — the Observer looked and saw no consumption yet;
  • SETTLEMENT_BOUNDS_UNCHECKABLE — the Parameter Pre-image is absent, mismatched, or carries a keyed commitment the Relying Party cannot reproduce, so neither the bounds nor the derivation can run (item 2 of Section 6.2);
  • SETTLEMENT_CORRELATION_BURNED — the correlation was consumed by a successful cancellation. Terminal, and specifically not "not yet" (Section 7.2, availability).

Rejections. Sixteen, and this list is complete:

ARTIFACT_TAMPERED                    SETTLEMENT_BOUNDS_EXCEEDED
AUTHORIZATION_RECEIPT_MISMATCH       SETTLEMENT_ASSET_UNEXPECTED
EXECUTION_GRANT_MISMATCH             SETTLEMENT_NETWORK_UNEXPECTED
SETTLEMENT_RECEIPT_ROLE_UNFIT        SETTLEMENT_PAYER_UNEXPECTED
SETTLEMENT_CORRELATION_MISMATCH      SETTLEMENT_CHAIN_CONTRADICTED
SETTLEMENT_TENANT_MISMATCH           SETTLEMENT_RAIL_FAMILY_UNKNOWN
SETTLEMENT_STATUS_INCONSISTENT       SETTLEMENT_OBSERVER_IDENTITY_SPLIT
SETTLEMENT_ORDERING_INVALID          SETTLEMENT_CHAIN_FACTS_UNTRUSTED

Mapping the checks of Section 6.2 onto them: item 3 → SETTLEMENT_RAIL_FAMILY_UNKNOWN; item 4 → SETTLEMENT_NETWORK_UNEXPECTED / SETTLEMENT_ASSET_UNEXPECTED; item 5 → SETTLEMENT_PAYER_UNEXPECTED; item 6 → SETTLEMENT_CORRELATION_MISMATCH; item 8 → SETTLEMENT_BOUNDS_EXCEEDED. A wrong payee and a wrong amount deliberately share one code (SETTLEMENT_BOUNDS_EXCEEDED): both are violations of the bounds the mandate committed to, and splitting them would imply a distinction the bounds check does not make.

Warnings — never rejections. SETTLEMENT_AFTER_GRANT_EXPIRY, SETTLEMENT_OBSERVER_SAME_KEY_AS_EXECUTION_SIGNER, RAIL_RECEIPT_PROVIDED_UNPARSEABLE, SETTLEMENT_OVER_RAW_MODE_HOLD. A warning caps the outcome; it never converts one into a failure.

There is deliberately no code meaning "the settlement did not occur", and no FAILED: absence of a settlement observation is not evidence of non-payment. What the two nearest codes report is narrower and is worth stating exactly — SETTLEMENT_NOT_OBSERVED means the Observer looked and saw nothing at that moment, and SETTLEMENT_NOT_SETTLED_AT_OBSERVATION means it saw the authorization unconsumed at that moment. Both are statements about one observation instant, not about the world.

6.4. The Observer's signature is not the evidence

The ledger is the fact; the Observer merely looked. The artifact's chainWitness carries the Observer's reported txHash and blockNumber (both nullable, and both reject-only) so that a Relying Party can locate what the Observer says it saw — but the artifact does not carry a logIndex, and it does not carry the transaction status. The evidence that a positive verdict rests on is not any of these reported fields: it is the noa.chain-facts/0.1 record the Relying Party obtains from its own node (item 8 of Section 6.2), against which the Observer's report is merely corroborated, reject-only. A Relying Party that can reach the ledger SHOULD obtain those facts itself rather than rely on sig or on the Observer's reported coordinates.

Consequence, stated because it is uncomfortable and must not be discovered later: this is the one artifact in the system that cannot be fully verified offline. Every other artifact verifies from bytes plus a trust anchor. This one's central claim is about external state. A Relying Party without ledger access can verify the artifact's internal consistency and the Observer's signature, and that is all — it has verified that someone claimed to have seen something. Any product surface describing this artifact as offline-verifiable is wrong.

7. Security Considerations

7.1. Settled is not delivered — the permanent scope limit

A positive verdict establishes that the ledger facts the Relying Party supplied are consistent with a payer-authorized transfer, matching a locally correlated mandate artifact, that settled on the token contract and network the mandate's own verified Parameter Pre-image names. It is a statement about the caller's ledger facts, not about the ledger.

Say precisely what fixes the contract, because "allowlisted" would overstate it. The check this document requires is a comparison against the mandate's own committed coordinates, not a lookup in a verifier-held registry of blessed assets: item 4 of Section 6.2 compares the network and asset against the verified Parameter Pre-image, and that is the whole of it. A deployment MAY additionally maintain an allowlist of (network, token contract) pairs it is willing to reason about — Section 6.2 requires a profile to name the pairs it covers, and the reference implementation ships such a list on a separate code path — but the acceptance path described here does not consult one, and an implementer MUST NOT assume an allowlist is being enforced on their behalf.

And the ledger facts themselves are the caller's, not the verifier's. This implementation performs no network I/O: it reads a noa.chain-facts/0.1 record the caller asserts came from its own node (RELYING_PARTY_NODE is a caller-supplied provenance assertion, not something the verifier can confirm), and it cannot detect a lagging, lying, or hostile RPC. A caller that reads a bad node gets a wrong answer and this system will not catch it; that failure mode is inherent and is stated, not mitigated. The verdict says nothing about whether a resource was returned, a service rendered, or an obligation discharged. In the target rail, the resource identifier never reaches the ledger at all — only payer, payee, value and the correlation identifier do. The purpose of the payment lives entirely in the preimage the mandating party holds, and a Relying Party that does not hold that preimage cannot bound the purpose at all.

The ledger knows nothing about a mandate: the correlation is this document's own construction, and the bound-ness of the transfer terms is established by the mandate's own parameter commitment, not by the ledger.

Any counterparty-signed receipt covering delivery is counterparty evidence: signed by the party being paid, about its own performance. That is a legitimate artifact and a self-serving one, and it MUST NOT be described as an independent witness.

7.2. Residual trust anchors

The anchors of [I-D.toraman-noa-action-digest] apply unchanged and are restated wherever this artifact's posture is described: infrastructure root · key custody · governance capture · the deceived approver · cross-action composition · the ungated surfacean OPEN list, never a complete taxonomy. Three further classes are specifically live for this artifact and are named here rather than left to the omitted set:

  • Availability / denial of service. A party holding the payer key can consume the correlation without paying, by issuing a cancellation for the same (authorizer, nonce). The identifier is burned permanently and the approved payment can never settle under it. A verifier sees no consumption event and reports "not settled yet" for something that will never settle; Section 7.4's absence rule must therefore distinguish not yet from burned, and a profile MUST carry the cancellation event in its fact set.
  • Front-running. The target rail uses a transfer function whose own security considerations warn that a signed authorization can be extracted from the mempool and executed by a third party first. The payment happens; the facilitator re-verifies, sees a consumed identifier, and reports failure without locating the transaction that consumed it. An implementation MUST reconcile consumed identifiers or it will report failure for payments that actually settled.
  • Privacy leakage. See Section 7.8.

The key-custody anchor is open in the reference implementation; see Appendix A. No claim of unforgeable mandate authority may be made until it closes.

Activity outside enforced gateways is outside coverage — no mandate, no correlation value and no settlement evidence exists for it, including no record of the absence. This is a scope statement, not a mitigation, and it carries no claim about what any particular escaped process chooses to do.

7.3. Correlation forgery by an unbound payer

The ledger event is keyed on (authorizer, nonce). An attacker who learns a Correlation Value can, cheaply, sign their own payment authorization using that same 32-octet value, on any compatible token, on any compatible chain, and cause an AuthorizationUsed event carrying our correlation value to exist. They can then present a settlement-evidence artifact pointing at their own transaction. This is the reason the full chainWitness — carrying the network, asset and payer — is mandatory.

Mitigations, all required:

  • the artifact MUST carry chainWitness.network, chainWitness.asset and chainWitness.payer (Section 5.1);
  • the verifier MUST check the event's authorizer topic equals chainWitness.payer (item 5 of Section 6.2);
  • the mandate MUST commit to the network, the token contract AND the payer before dispatch. Otherwise those are attacker-chosen values compared against an event whose location the attacker chose, and the checks are circular. Committing the payer alone applies a correct circularity argument to one of three fields and leaves the other two open — which is attack (b) of Section 6.2.

The Action Digest's own projection is frozen and carries no ledger/rail chain id, no token contract and no payer, so the binding is carried twice, in two different ways, and the difference matters:

  1. By the mandate's Parameter Commitment. For a payment action class whose Parameter Pre-image carries payer, payee, asset, network, a maximum amount and the resource identifier, the Commitment covers all three bindings — and the same commitment is what item 8 of Section 6.2 bounds the transfer against.
  2. Inside the Correlation Value itself. The chain identifier, the token contract and the payer are derivation inputs (Section 4.1), so changing any one of them changes the 32 octets. This is the binding the one-stage construction could not have: it makes the three coordinates non-substitutable in the recomputation. A verifier can no longer be handed a value that "belongs" to some other triple, and the artifact's own chainWitness coordinates drop to reject-only.

What that does not do, stated plainly, because the inverse is the easy misreading. Binding the coordinates inside the value does not stop the value from appearing elsewhere. Once 32 octets are on a public ledger they are public: anyone may sign an authorization carrying them in another contract's or another chain's signing domain, and the contract's uniqueness rule reaches only (authorizer, nonce) per contract per chain. What refuses that decoy is still items 4 and 5 of Section 6.2, run against the verified Parameter Pre-image. The derivation removes a substitution; it does not remove an event.

Two residuals, both live, both written as non-claims:

(i) Verification is fail-closed on the Parameter Pre-image, and there is no partial tier. A Relying Party that does not hold the Pre-image cannot derive the Correlation Value, therefore cannot run item 6, and MUST return SETTLEMENT_BOUNDS_UNCHECKABLE (Section 6.3) rather than a weaker positive. This is deliberate — a positive verdict over coordinates nothing bound was the defect the earlier construction carried — and it is also a real cost: no minimized, commitment-only presentation of this artifact exists in this version. Recompute-and-compare verification makes the Parameter Pre-image and the grant's nonce load-bearing, so disclosing the evidence discloses the mandate's payment parameters and its single-use nonce. A minimized presentation is a schema change, not a profile option.

(ii) Money can move without a positive verdict, and this document does not claim otherwise. A party holding the payer key can derive a different 32 octets from a different seed, settle them for real, and produce a genuine consumption event on the ledger. Recomputation from the bundle's own documents yields exactly one value, so that settlement can never earn a positive verdict — but the transfer still happened and the funds are still gone. What this mechanism bounds is which settlements a Relying Party will accept as correlated, never which transfers a compromised payer key can cause. The honest ceiling is at most one settlement correlatable to this grant, never exactly one settlement per mandate.

Note the Parameter Commitment may legitimately be a keyed hash in the reference implementation's frozen record format. Under Section 4.1 that is no longer a partial loss: a keyed commitment blocks the derivation as well as the bounds, so the only correct outcome is SETTLEMENT_BOUNDS_UNCHECKABLE (item 2) — a non-positive, non-failure result, not a rejection: the evidence does not support a verdict, and nothing is thereby asserted to be wrong. A keyed-commitment bundle therefore yields no positive verdict at all — not a weaker one — for any party that cannot reproduce the commitment. A profile SHOULD require the unkeyed form for rail-bound action classes, and MUST state that consequence otherwise.

7.4. Absence is not proof of non-settlement

Absence of a ledger event proves only that the Observer did not observe one. The authorization may settle later, within its validity window. A NOT_SETTLED conclusion MUST therefore be time-qualified: not settled as of block N and the authorization's validity window has closed. An implementation that reports unqualified NOT_SETTLED is making a claim it cannot support. The chainWitness.status field (Section 5.1) is what carries this, and it distinguishes we did not look from we looked and saw nothing yet.

And the converse is equally important, because the obvious oracle is not one. A state query returning "this identifier is consumed" is NOT proof of settlement: at least one major deployment sets the same state bit for cancellation as for use. Settlement proof requires the conjunction — the consumption event, plus a successful transaction status, plus matching calldata or a corresponding token transfer. A profile MUST NOT specify a state-getter-only check.

7.5. Reorganization

A ledger observation can be reversed by a chain reorganization. A Relying Party MUST apply its own finality threshold against its own view of the chain head (item 9 of Section 6.2) rather than inherit the Observer's. A verdict computed under an Observer-supplied policy is a verdict the Observer chose.

7.6. Liveness failures are not correlation failures

An intermediary may decline to settle, delay, or fail. These are liveness failures and they are all detectable as "no event for our correlation value" — they are not evidence of tampering, and an implementation MUST NOT report them as such.

7.7. Deployment-dependent availability

A settlement intermediary that policed the content of the authorization identifier — demanding that it be drawn by a generator the intermediary names, registers or attests — would make this mechanism unavailable in that deployment, however sound the derivation. This is an availability question, not a security one, and it is answered by measurement rather than by reading a specification: the identifier's binding is enforced by the token contract, but nothing prevents an intermediary from refusing to relay an authorization before the contract ever sees it.

Measured, 2026-08-14, against a public hosted facilitator on a testnet. Three TransferWithAuthorization structures were signed from one throwaway account that holds no balance, differing only in their 32-octet identifier: (A) a value produced by the derivation in Section 4.1, (B) a freshly drawn random value, (C) a deliberately structured low-entropy value (28 zero octets followed by four fixed octets). Each was submitted to the facilitator's verification endpoint, which applies the intermediary's acceptance policy without broadcasting. All three were refused at exactly the same stage and for exactly the same reason — the account's insufficient token balance, reported from a simulated contract call whose arguments included the identifier verbatim. Two facts follow, and neither depends on the author's intent:

  1. No identifier-content policy was applied at or before the stage all three reached. A derived identifier reached the same processing stage as a freshly random one, and the structured control was not distinguished from either. An intermediary policing identifier shape or provenance at verification time would have separated (C) from (B) at minimum. This is a statement about the stages the three requests actually traversed; it is not a statement about stages none of them reached.
  2. The refusal came from beyond signature verification. The simulated call reverted inside the token contract's balance check, which sits after the contract's own recovery of the signer from the EIP-712 digest — the digest that commits to the identifier. A derived identifier is therefore signature-valid at the contract, not merely accepted at the wire.

Scope of that measurement, stated so it is not overread. It covers one hosted facilitator, one testnet, one asset-transfer method, one endpoint, at one date. Three limits are load-bearing and are stated rather than implied:

  • The settlement path was not exercised. The requests were refused for want of funds, so no authorization was ever broadcast. A policy applied at settlement time — on the funded path, at broadcast, or by a settlement endpoint that verification does not share — is not excluded by this measurement.
  • Nothing is established about stages beyond the refusal. The three requests are comparable only up to the point where all three stopped for the same unrelated reason.
  • One deployment is not every deployment. An implementation SHOULD repeat this three-way comparison against the intermediary it intends to use before publishing an availability claim for that deployment; a divergence between (A) and (B) is the signal that the mechanism is unavailable there.

What the comparison does buy, cheaply, is the elimination of the cheapest and most likely refusal: a shape or provenance filter applied to the identifier at admission. It requires no funded account, broadcasts nothing, and consumes no gas, because an unfunded refusal at the balance check is itself the evidence that everything before it — including the contract's own recovery of the signer over the digest that commits to the identifier — passed. An implementation that needs the stronger statement, and can fund an account, SHOULD extend the same comparison through a real settlement and record which of (A) and (B) settles.

Section 4.1 also narrows what such a policy could even detect. The value the derivation produces is a SHA-256 image over a pre-image containing 32 octets of cryptographically secure random seed, so no statistical test applied to the value distinguishes it from a freshly drawn random one. Only a deployment demanding attested provenance — that the identifier be generated by a named or registered process, rather than merely appear random — could refuse it on content, and the measurement above found no such demand at admission in the deployment tested. Whether such a demand exists further along that deployment's settlement path is unmeasured.

Additionally out of scope and unverified: alternative transfer mechanisms whose identifier is not a 32-octet contract-verified value (for example, bitmap-consumed integer nonces, or delegation paths with no client-supplied identifier field), and contract-account signature paths.

7.8. Privacy

The privacy section of [I-D.toraman-noa-action-digest] applies with full force, and Section 4.1 changes where its weight falls. Three statements, in the order in which they are easiest to overstate:

  1. The Action Digest is not on the ledger. It is a dispatch-time input; what becomes permanently public is the derivation's output. An observer holding the ledger value alone cannot recover the digest from it and cannot test a guessed mandate against it, because the pre-image contains a seed the observer does not hold. The confirm-by-guess channel that a bare public digest would open on a permanent ledger is therefore closed against an outside observer. That is the whole reason the second stage exists, and it is the only privacy claim this document makes for it.
  2. It is not closed against a bundle holder, and that is not the smaller problem. The seed is the Execution Grant's nonce and the grant travels inside the evidence bundle. Anyone handed the bundle can recompute the Correlation Value, locate the settlement on the ledger, and read the payer wallet from it — permanently, before or after settlement, with no way to withdraw the capability once granted. The mandate documents alone carry no wallet identity; the bundle creates that link. Disclosing an evidence bundle is a durable capability grant, not a one-time view, and Section 7.3's residual (i) says why this version offers no minimized alternative.
  3. Payer-level correlation was already available to any ledger observer via the authorizer topic, so a per-approval-unique, unguessable identifier adds no new linkage at that level. A low-entropy identifier would have added a new enumerable channel where none existed; the seed is what keeps that from happening, and the seed's entropy is a producer property that no verifier and no conformance vector can check (P4 of [I-D.toraman-noa-action-digest]).

8. Interaction With Outcome Semantics

This document supports a verifier rule of the form: for enrolled action classes, a positive execution-side outcome requires a settlement-evidence artifact whose ledger facts the Relying Party obtained itself; an executor's self-report caps at DISPATCHED.

Two qualifiers that must travel with that rule. (i) A Relying Party that lacks the Parameter Pre-image cannot derive the Correlation Value at all, so it gets no verdict from this artifact rather than a weaker one (residual (i) of Section 7.3); the verdict word gets stronger only for a Relying Party that holds the Pre-image and runs the checks. (ii) An offline verifier — one that holds the artifact and no ledger access — can establish the artifact's internal consistency and its bindings and nothing about the ledger; a profile MUST give that a distinct, weaker verdict tier and MUST NOT let it satisfy the rule. It is the ledger facts, obtained by the Relying Party, that lift the cap — not the artifact's presence.

Still open, and correctly so: no per-tenant, authenticated action-class enrolment registry exists in the reference verifier today. A design for one exists — as an external verifier input, alongside the trust root and checkpoint keyring the verifier already requires, rather than as a bundle member — but it is unimplemented. This document therefore cannot specify the rule normatively, and saying so remains the honest position. The only class-shaped values reaching the verifier are the risk class lifted from the authorization record and the action's own identifier strings; the nearest thing in the reference tree is a hard-coded risk-floor allowlist of reviewed executables in the enforcement layer, whose own comment describes it as "trusted CODE rather than authenticated POLICY" — code, not an authenticated per-tenant policy. A rule keyed on "enrolled action classes" cannot be specified normatively until such an authenticated registry exists. Inventing the registry here would be the dishonest alternative.

In the reference implementation today, EXECUTED means exactly: a gate-signed consumption artifact whose result field is DISPATCHED — one of that wire enum's two values (DISPATCHED | FAILED_BEFORE_DISPATCH), and the only one the gate ever signs — transitively bound to an approver-signed ALLOWED record. Nothing in that bundle is evidence of a side effect — the enforcement layer says so in its own source: "Nothing here observes what the executor DID… The gate reports EXECUTED for a dispatch it authorized, never for an outcome it witnessed".

9. IANA Considerations

[PLACEHOLDER — pending working group direction]. Provisional: media type application/noa-settlement-evidence+json (and +cose under a COSE profile [RFC9052] [RFC9053]); the signing-domain-tag registry proposed in [I-D.toraman-noa-action-digest] would carry this artifact's tag. No ledger-side registration is required or requested — the mechanism uses an existing caller-chosen field and asks nothing of the rail.

10. Change log

[[RFC EDITOR: please remove this section before publication.]]

Changes since -00: author metadata completed (country, explicit submission date). No technical changes; every normative statement, conformance count and reference is byte-identical to -00.

11. References

11.1. Normative References

[CAIP19]
Milosevic, A. and P. Kumar, "CAIP-19: Asset Type and Asset ID Specification", Chain Agnostic Improvement Proposals, , <https://standards.chainagnostic.org/CAIPs/caip-19>.
[CAIP2]
Gosselin, S., Kumar, P., and J. Holmes, "CAIP-2: Blockchain ID Specification", Chain Agnostic Improvement Proposals, , <https://standards.chainagnostic.org/CAIPs/caip-2>.
[EIP3009]
Kim, P., Britz, K., and D. Knott, "ERC-3009: Transfer With Authorization", Status: Draft, as rendered 2026-08-12, Ethereum Improvement Proposals no. 3009, , <https://eips.ethereum.org/EIPS/eip-3009>.
[FIPS180-4]
National Institute of Standards and Technology, "Secure Hash Standard (SHS)", FIPS PUB 180-4, , <https://csrc.nist.gov/pubs/fips/180-4/upd1/final>.
[I-D.toraman-noa-action-digest]
Toraman, T., "The NOA Action Digest: a Domain-Separated Correlation Value for Human-Approved Agent Actions", Work in Progress, Internet-Draft, draft-toraman-noa-action-digest-00, , <https://datatracker.ietf.org/doc/draft-toraman-noa-action-digest/>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3339]
Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, , <https://www.rfc-editor.org/info/rfc3339>.
[RFC8032]
Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, DOI 10.17487/RFC8032, , <https://www.rfc-editor.org/info/rfc8032>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8785]
Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, DOI 10.17487/RFC8785, , <https://www.rfc-editor.org/info/rfc8785>.

11.2. Informative References

[RFC7942]
Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, , <https://www.rfc-editor.org/info/rfc7942>.
[RFC9052]
Schaad, J., "CBOR Object Signing and Encryption (COSE): Structures and Process", STD 96, RFC 9052, DOI 10.17487/RFC9052, , <https://www.rfc-editor.org/info/rfc9052>.
[RFC9053]
Schaad, J., "CBOR Object Signing and Encryption (COSE): Initial Algorithms", RFC 9053, DOI 10.17487/RFC9053, , <https://www.rfc-editor.org/info/rfc9053>.

Appendix A. Implementation status

This section is to be removed before publication as an RFC, per [RFC7942].

A reference implementation, together with the Copenhagen vectors — the cross-language conformance corpus that enforces the checks in Section 6.2 — is available under the Apache-2.0 license at https://github.com/NordenSoft/noa.

The following were measured, in that repository, on 2026-08-14, at main commit 7aa8e37:

Anchoring note. Section 4.1, item 6 of Section 6.2 and the residuals in Section 7.3 and Section 7.8 are written against the two-stage construction above as it is implemented at the commit named in this section. An earlier revision of this document described a one-stage construction in which the Action Digest was itself the on-chain identifier; that text has been replaced, not annotated, because a specification that describes a construction nobody implements is worse than one that is late.

Author's Address

Tora Toraman
NordenSoft
Denmark