| Internet-Draft | NOA Settlement Evidence | August 2026 |
| Toraman | Expires 15 February 2027 | [Page] |
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.¶
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.¶
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.¶
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.¶
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.¶
This subsection is non-normative and is to be removed before publication as an RFC.¶
Two conditions govern the release of this document:¶
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.¶
As in [I-D.toraman-noa-action-digest]. The Authorization Record is not modified. This artifact references it by hash.¶
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:¶
TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256
validBefore,bytes32 nonce). An intermediary that alters the identifier invalidates the payer's
signature, and the contract re-derives the digest and recovers the signer before moving funds.¶
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce). Both fields are
directly filterable, so a third party can resolve the settling transaction from the correlation
value together with the mandate-bound authorizer, with no intermediary-supplied transaction
hash. Not from the correlation value alone: a filter on the identifier topic alone resolves
to any event bearing that value, including a decoy an attacker minted from their own address
after observing the value in a shared bundle or in the mempool. See item 5 of Section 6.2 and
Section 7.3.¶
(authorizer, nonce) pair is not already consumed, and the corresponding
state getter is the permissionless read of that state. This is what makes a deterministic
correlation value legal, and it is the load-bearing fact of this document. Two consequences
follow and must not be lost: enforcement happens inside the transfer function, not in the view
function — an implementer told the getter is the enforcement point may build a check-then-act
race — and the getter is not a settlement oracle, because cancellation sets the same state
bit (Section 7.4). If a deployment or intermediary requires the identifier to be produced by a
named random-generation process, this mechanism is unavailable there. See Section 7.7.¶
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.¶
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.¶
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.¶
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.¶
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.¶
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.¶
| 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).¶
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.¶
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.¶
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.¶
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:¶
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;¶
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.¶
Over an artifact that has passed Section 6.1, a Relying Party MUST:¶
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;¶
railFamily is a member of the set this verifier ships, and fail closed on an unknown
family;¶
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;¶
chainWitness.payer, and that chainWitness.payer
is the payer the mandate committed to — see Section 7.3;¶
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);¶
(authorizer, identifier) on that contract, and
that its transaction status is success;¶
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;¶
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.¶
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.¶
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.¶
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.¶
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 surface — an 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:¶
(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.¶
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.¶
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:¶
chainWitness.network, chainWitness.asset and chainWitness.payer
(Section 5.1);¶
chainWitness.payer (item 5 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:¶
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.¶
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.¶
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.¶
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.¶
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:¶
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:¶
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.¶
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:¶
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".¶
[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.¶
[[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.¶
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:¶
packages/rail-x402/src/correlation-nonce.mjs — the
domain string, the six length-prefixed fields in the order given, the single SHA-256, the
minimum seed length enforced by refusal, and the 0x output form. Its recompute-and-compare use
is packages/rail-x402/src/settlement-evidence.mjs, which derives only from the verified
Parameter Pre-image and the authenticated bundle documents, never from the artifact.¶
packages/rail-x402/conformance/settlement-evidence/
— 69 cases (1 ACCEPT, 54 REJECT, 14 CONTROL), generated, committed and drift-gated: the runner
re-derives the corpus in memory and diffs it. The corpus header states the derivation contract
this document describes. Named cases cover each attack this document names, including counterfeit
asset, wrong payer, wrong payee, amount over bound, zero-value burn, reverted transaction, decoy
transfer log, two transfer logs, unknown rail family, insufficient confirmation depth,
cancellation of the correlation, and an offline-verifier control. The cases that pin
Section 4.1 specifically are reject-sibling-seed (same grant, a different seed, a real
settlement on the ledger, and still no positive verdict), reject-chainid-from-artifact (D1),
reject-hmac-params-derivation (a keyed Parameter Commitment blocks the derivation, not only the
bounds), reject-digest-over-other-grant and reject-cross-tenant-correlation (recomputation,
not comparison, is what refuses a genuine settlement filed under the wrong mandate).¶
packages/approval-artifacts/conformance/settlement-evidence/ covers the signature, signer-role
and unknown-member refusals for the artifact itself. It also carries
reject-correlation-digest-form, which refuses a correlation member written in the Action
Digest's sha256: string form — the case that pins D6 of Section 4.1, and the evidence that
the earlier one-stage construction is a structural refusal in the reference implementation rather
than a form it quietly normalizes.¶
NON-CLAIMS.md — the execution-grant signing key is held as a string in the
process the grants are meant to defend.¶
TransferWithAuthorization structures — derived, random, and structured low-entropy identifiers
— signed from one unfunded throwaway account and submitted to a public hosted facilitator's
verification endpoint on a testnet. All three were refused identically, at the token contract's
balance check, with the identifier passed through verbatim to the simulated call. Nothing was
broadcast and no funds were involved — so the settlement path is unexercised and unmeasured, as
Section 7.7 states. The measurement script is not part of the reference implementation; the
reproduction recipe is the three-way comparison described in Section 7.7, which any
implementer can re-run against their own intermediary.¶
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.¶