<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-toraman-noa-settlement-evidence-01" submissionType="IETF" category="std" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true">

<front>
<title abbrev="NOA Settlement Evidence">Settlement Evidence for Human-Approved Agent Payments</title><seriesInfo value="draft-toraman-noa-settlement-evidence-01" stream="IETF" status="standard" name="Internet-Draft"></seriesInfo>
<author initials="T." surname="Toraman" fullname="Tora Toraman"><organization>NordenSoft</organization><address><postal><street></street>
<country>Denmark</country>
</postal><email>hello@ordeliya.com</email>
</address></author><date year="2026" month="August" day="14"></date>
<area>Security</area>
<workgroup>Network Working Group</workgroup>
<keyword>AI agent</keyword>
<keyword>payment</keyword>
<keyword>settlement</keyword>
<keyword>correlation</keyword>
<keyword>ledger</keyword>
<keyword>evidence</keyword>
<keyword>authorization</keyword>
<keyword>accountability</keyword>

<abstract>
<t>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 <strong>that obtains ledger facts itself</strong> to establish that a payment
authorization bearing the correlation value <strong>was consumed on the token contract and network the
mandate itself committed to, transferring a stated value to a stated recipient</strong>, 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. <strong>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.</strong> Those scope limits are permanent and are restated wherever this
artifact is described.</t>
</abstract>

</front>

<middle>

<section anchor="intro"><name>Introduction</name>
<t>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.</t>
<t>This is a narrow and specific gain, and the narrowness is the point: it converts <em>&quot;the party we
paid says we paid&quot;</em> into <em>&quot;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.&quot;</em> It converts nothing else.</t>

<section anchor="philosophy"><name>Verification philosophy</name>
<t>This document is written under the same discipline as its companion
<xref target="I-D.toraman-noa-action-digest"></xref>, which its authors call the <strong>Copenhagen model</strong>: a verifier never
states what happened; it states what the supplied evidence supports, and it records what it does
<strong>not</strong> claim. That discipline is why <xref target="scope-limit"></xref> exists as a permanent section rather than a
caveat, why <xref target="anchors"></xref> is an open list rather than a taxonomy, and why <xref target="observer"></xref> 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.</t>
<t>The name is a nod to a remark about physics — that the subject concerns what we can <em>say</em> 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.</t>
</section>

<section anchor="editorial-gate"><name>Editorial note on this revision</name>
<t><em>This subsection is non-normative and is to be removed before publication as an RFC.</em></t>
<t>Two conditions govern the release of this document:</t>

<ol spacing="compact">
<li>This document is not submitted while any placeholder-marked item in <xref target="artifact"></xref> or in
<xref target="security"></xref> is unresolved. <strong>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: <xref target="availability"></xref> 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.</strong> Exactly one
<em>unresolved</em> item remains anywhere in this document — the registry request awaiting
working-group direction in <xref target="iana"></xref> — and this condition does not cover it.</li>
<li>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 <xref target="checks"></xref> is the <strong>Copenhagen
vectors</strong>, the cross-language conformance corpus published with the reference implementation;
<xref target="impl-status"></xref> names its location and its contents.</li>
</ol>
</section>
</section>

<section anchor="terms"><name>Conventions and Terminology</name>
<t>The key words <bcp14>MUST</bcp14>, <bcp14>MUST NOT</bcp14>, <bcp14>REQUIRED</bcp14>, <bcp14>SHALL</bcp14>, <bcp14>SHALL NOT</bcp14>, <bcp14>SHOULD</bcp14>,
<bcp14>SHOULD NOT</bcp14>, <bcp14>RECOMMENDED</bcp14>, <bcp14>NOT RECOMMENDED</bcp14>, <bcp14>MAY</bcp14>, and <bcp14>OPTIONAL</bcp14> in this document
are to be interpreted as described in BCP 14 <xref target="RFC2119"></xref> <xref target="RFC8174"></xref> when, and only when, they appear
in all capitals, as shown here.</t>

<ul spacing="compact">
<li><strong>Mandate</strong> — a human-approved, parameter-bound, single-use authorization for one action.
Realized as an Authorization Record plus an Execution Grant
(<xref target="I-D.toraman-noa-action-digest"></xref>).</li>
<li><strong>Parameter Commitment</strong> — a cryptographic commitment to the canonical byte encoding of the
action's parameters, carried inside the Authorization Record
(<xref target="I-D.toraman-noa-action-digest"></xref>).</li>
<li><strong>Parameter Pre-image</strong> — those canonical bytes themselves. For a payment action class they carry
the payer, the payee, the asset, the network, a maximum amount and the resource identifier. The
Pre-image is a verifier input, not a member of this artifact, and it is verified against the
Parameter Commitment before it is used for anything.</li>
<li><strong>Correlation Value</strong> — the 32-octet value carried in the payment authorization's caller-chosen
identifier field. It is <strong>derived</strong> from the Action Digest (<xref target="I-D.toraman-noa-action-digest"></xref>)
together with the chain identifier, the token contract and the payer address the mandate
committed to, and a high-entropy seed. <strong>The Action Digest itself is never placed in the payment
authorization and never reaches the ledger.</strong> See <xref target="derivation"></xref>.</li>
<li><strong>Payment Authorization</strong> — a client-signed structure that a ledger contract verifies before
transferring value, and which contains a caller-chosen 32-octet identifier the contract enforces
as unique per authorizer.</li>
<li><strong>Settlement Observation</strong> — a record of a ledger event indicating that a specific Payment
Authorization was consumed.</li>
<li><strong>Observer</strong> — the party that queried the ledger and produced the Settlement Observation. <strong>The
Observer is not a witness and its signature is not the evidence.</strong> See <xref target="observer"></xref>.</li>
<li><strong>Finality Policy</strong> — the rule by which a Relying Party decides a ledger observation is durable.</li>
</ul>
</section>

<section anchor="frozen"><name>Relationship to the Frozen Base Record</name>
<t>As in <xref target="I-D.toraman-noa-action-digest"></xref>. The Authorization Record is not modified. This artifact
references it by hash.</t>
</section>

<section anchor="mechanism"><name>The Correlation Mechanism</name>
<t>The mechanism relies on three properties of the target payment rail. All three were verified
against <xref target="EIP3009"></xref> and against live testnet and mainnet state on 2026-08-12:</t>

<ul spacing="compact">
<li><strong>M1 — the identifier is inside the signed structure.</strong> The typehash is
<tt>TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256
validBefore,bytes32 nonce)</tt>. 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.</li>
<li><strong>M2 — the identifier is emitted as an indexed event topic.</strong>
<tt>event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)</tt>. Both fields are
directly filterable, so a third party can resolve the settling transaction from <strong>the correlation
value together with the mandate-bound authorizer</strong>, with no intermediary-supplied transaction
hash. <strong>Not from the correlation value alone:</strong> a filter on the identifier topic alone resolves
to <em>any</em> 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 <xref target="checks"></xref> and
<xref target="forgery"></xref>.</li>
<li><strong>M3 — the identifier is required to be UNIQUE, not RANDOM.</strong> The rail's specification states
that the nonces are &quot;random 32-byte nonces&quot; as a <em>usage pattern</em>; the <strong>transfer function</strong>
enforces only that the <tt>(authorizer, nonce)</tt> pair is not already consumed, and the corresponding
state getter is the permissionless <strong>read</strong> of that state. <strong>This is what makes a deterministic
correlation value legal</strong>, 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 <strong>not</strong> a settlement oracle, because cancellation sets the same state
bit (<xref target="absence"></xref>). If a deployment or intermediary requires the identifier to be produced by a
named random-generation process, this mechanism is unavailable there. See <xref target="availability"></xref>.</li>
</ul>
<t><strong>Ordering requirement.</strong> The Correlation Value is computed and committed at approval time and
injected at dispatch (P5 of <xref target="I-D.toraman-noa-action-digest"></xref>). The reverse construction — hashing
the payment payload into the mandate's parameter commitment — is <strong>not</strong> 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 <em>fields</em>, never a hash of the payload as a blob, so there would be nothing on-chain
to compare against.</t>

<section anchor="derivation"><name>The two-stage derivation</name>
<t>The Correlation Value is <strong>not</strong> the Action Digest. The Action Digest is a <em>dispatch-time input</em> 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. <strong>The Action Digest itself never
touches the ledger.</strong></t>

<artwork><![CDATA[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))
]]></artwork>
<t>Every field is length-prefixed because <tt>dispatchId</tt> 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 <strong>new tag</strong>,
never a silent re-derivation, so a value can always be attributed to the rule that produced it.</t>
<t>Six requirements follow. D1, D2, D5 and D6 are each enforced by a named case in the conformance
corpus, and <xref target="impl-status"></xref> names them. <strong>D3 and D4 are not, and this document says so rather than
implying an enforcement that does not exist:</strong> 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.</t>

<ul spacing="compact">
<li><strong>D1 — the derivation inputs are the mandate's, never the artifact's.</strong> <tt>chainId</tt>, <tt>token</tt> and
<tt>payer</tt> <bcp14>MUST</bcp14> be taken from the Parameter Pre-image <em>after</em> 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.</li>
<li><strong>D2 — the seed is the grant's nonce, and there is no seed member.</strong> The signed, single-use
Execution Grant <strong>is</strong> 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 <bcp14>MUST NOT</bcp14> carry the seed and <bcp14>MUST NOT</bcp14> carry
a seed commitment; a value the verifier is handed is a value an attacker can choose.</li>
<li><strong>D3 — a short seed is a refusal, never a padding.</strong> The seed <bcp14>MUST</bcp14> be at least 32 octets and
a derivation given fewer <bcp14>MUST</bcp14> 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.</li>
<li><strong>D4 — the width is set by the derivation, not by the digest.</strong> SHA-256 <xref target="FIPS180-4"></xref> output is
exactly the 32 octets the identifier field takes, with no truncation or padding step to get
wrong.</li>
<li><strong>D5 — the Relying Party recomputes; it never accepts.</strong> 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 <xref target="checks"></xref> states this
mechanically.</li>
<li><strong>D6 — the one-stage form MUST be refused, not normalized.</strong> A correlation member carrying the
Action Digest's <tt>sha256:</tt> string form is structurally invalid under this document. A verifier
that accepts it and re-encodes it has accepted the construction this one replaced.</li>
</ul>
<t><strong>Why the seed is there, in one sentence, with its cost stated in <xref target="privacy"></xref>:</strong> 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.</t>
</section>
</section>

<section anchor="artifact"><name>The noa.settlement-evidence/0.1 Artifact</name>
<t>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 <strong>type and role</strong> — before it can verify at all.</t>

<section anchor="members"><name>Members</name>
<t>The root members are, in the schema's required order: <tt>spec</tt>, <tt>tenant</tt>, <tt>chain</tt>,
<tt>authorizationReceiptHash</tt>, <tt>executionGrantHash</tt>, <tt>correlation</tt>, <tt>railFamily</tt>, <tt>chainWitness</tt>,
<tt>railReceipt</tt>, <tt>observerKid</tt>, <tt>observedAt</tt>, <tt>sig</tt>. The object is closed
(<tt>additionalProperties:false</tt>) at the root and on every nested object, so a member not in this
table is a refusal, not a tolerance.</t>
<table>
<thead>
<tr>
<th>Member</th>
<th>Meaning</th>
<th>Role</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>spec</tt></td>
<td><tt>&quot;noa.settlement-evidence/0.1&quot;</tt></td>
<td>fixed</td>
</tr>

<tr>
<td><tt>tenant</tt></td>
<td>the tenant the bundle belongs to</td>
<td>reject-only</td>
</tr>

<tr>
<td><tt>chain</tt></td>
<td>the Authorization Record's record-chain id</td>
<td>reject-only</td>
</tr>

<tr>
<td><tt>authorizationReceiptHash</tt></td>
<td>the ALLOWED Authorization Record</td>
<td>receipt-reference rule</td>
</tr>

<tr>
<td><tt>executionGrantHash</tt></td>
<td>the Execution Grant</td>
<td>side-artifact rule</td>
</tr>

<tr>
<td><tt>correlation</tt></td>
<td>the Correlation Value</td>
<td>recomputed, never accepted</td>
</tr>

<tr>
<td><tt>railFamily</tt></td>
<td>the rail family</td>
<td>reject-only, fail-closed</td>
</tr>

<tr>
<td><tt>chainWitness</tt></td>
<td>the observed ledger facts</td>
<td>reported; reject-only</td>
</tr>

<tr>
<td><tt>railReceipt</tt></td>
<td>the counterparty's receipt, or <tt>null</tt></td>
<td>never load-bearing</td>
</tr>

<tr>
<td><tt>observerKid</tt></td>
<td>who queried the ledger</td>
<td>MUST equal <tt>sig.kid</tt></td>
</tr>

<tr>
<td><tt>observedAt</tt></td>
<td>when the Observer looked</td>
<td>time qualifier</td>
</tr>

<tr>
<td><tt>sig</tt></td>
<td>the Observer's signature</td>
<td>not the evidence</td>
</tr>
</tbody>
</table><t>Exact forms are in <xref target="grammars"></xref>. What each member is <em>for</em>, in the order above:</t>

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

<section anchor="grammars"><name>Wire grammars</name>
<t>Every member's exact accepted form, so that a conforming artifact can be constructed from this
document alone. All hex is <strong>lowercase</strong>; uppercase is a refusal, never a normalization. Instants
are RFC 3339 <xref target="RFC3339"></xref>. The network identifier is a CAIP-2 <xref target="CAIP2"></xref> chain ID and the asset
identifier is a CAIP-19 <xref target="CAIP19"></xref> asset ID, each further narrowed by the grammar below to the
<tt>eip155</tt> namespace this version covers.</t>

<artwork><![CDATA[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
]]></artwork>
<t>Both objects are <strong>closed</strong>: the root admits exactly the twelve members above and <tt>chainWitness</tt>
exactly its nine. Any other member, at any depth, is a refusal.</t>
<t>An Observer-supplied finality policy is deliberately <strong>not</strong> 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. <tt>txStatus</tt> is likewise <strong>not</strong> a member of this
artifact: the transaction status the verifier requires lives in the chain-facts <em>input</em>
(<tt>noa.chain-facts/0.1</tt>), an external verifier input obtained by the Relying Party from its own
node, not in this signed observation. There is no <tt>logIndex</tt>, and no confirmation count, on the
wire.</t>
<t>The artifact's member set distinguishes the Observer's <strong>reported</strong> fields, which are reject-only,
from the Relying Party's <strong>own</strong> ledger facts. That distinction is what makes <xref target="observer"></xref>
mechanical rather than aspirational.</t>
<t><strong>Three hash rules exist in the reference implementation and are deliberately distinct</strong>: 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.</t>
</section>
</section>

<section anchor="prohibited"><name>Prohibited content</name>
<t>The artifact <bcp14>MUST NOT</bcp14> 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 <xref target="scope-limit"></xref>.</t>
</section>
</section>

<section anchor="verification"><name>Verification</name>

<section anchor="authenticate"><name>Authenticate the artifact before trusting any field</name>
<t><strong>This step is normative and it comes first. A positive result computed over an unauthenticated
Settlement Evidence artifact means <em>nothing</em>, and the reference reconciler says so in those words:
it operates over pre-verified inputs, and its own positive result &quot;over unauthenticated inputs
means nothing.&quot;</strong> Before reading a single field of the artifact for the checks in <xref target="checks"></xref>, a
Relying Party <bcp14>MUST</bcp14>:</t>

<ul spacing="compact">
<li><strong>validate the artifact against its own schema</strong> — root and every nested object closed
(<tt>additionalProperties:false</tt>), every required member present, <tt>correlation</tt> lowercase <tt>0x</tt> + 64
hex, <tt>chainWitness</tt> well-formed. A member the schema does not define is a refusal, not a
tolerance;</li>
<li><strong>verify the Observer's Ed25519 <xref target="RFC8032"></xref> signature</strong> (<tt>sig.value</tt>, base64) against a key the
Relying Party resolves through its <strong>own</strong> keyring — never a key named only by the artifact. The
signed octets are exactly:</li>
</ul>

<artwork><![CDATA[  MESSAGE = UTF8("NOA-SettlementEvidence-v0.1-sig:")
         || SHA-256( JCS( artifact with the whole `sig` removed ) )
]]></artwork>
<t>— the domain tag, a literal colon, then the <strong>raw 32-octet</strong> SHA-256 <xref target="FIPS180-4"></xref> of the RFC 8785
  <xref target="RFC8785"></xref> canonical JSON of the document with the <strong>entire <tt>sig</tt> object removed</strong> (not merely
  <tt>sig.value</tt> blanked). The domain tag is what stops a signature over one artifact kind from ever
  verifying as another;
- <strong>check the signer's TYPE and ROLE</strong> against the trust matrix: the signer <bcp14>MUST</bcp14> be of type
  <tt>GATE</tt> and hold the role <tt>settlement-observer</tt>, and <bcp14>MUST</bcp14> satisfy <tt>observerKid == sig.kid</tt>. A
  key of any other type or role <bcp14>MUST</bcp14> 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;
- <strong>check key activation and revocation, and note carefully whose clock decides.</strong> Activation is
  evaluated at a <strong>verifier-controlled</strong> time — the caller-supplied authorization time, or, absent
  that, the verifier's own &quot;now&quot; — and <strong>never</strong> 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 <strong>only caller-controlled time may move a verdict toward accept</strong>. The artifact's own
  <tt>observedAt</tt> is used in one direction only: if it precedes the key's <tt>validFrom</tt>, that
  self-contradiction is a <strong>reject-only</strong> signal; it can never activate a key. Revocation is
  blunter still: <strong>any</strong> non-null revocation timestamp on the key rejects the artifact outright.
  The verifier does <strong>not</strong> 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.</t>
<t>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 <xref target="checks"></xref>; a Relying Party that runs <xref target="checks"></xref> over an artifact it did not first
authenticate has verified nothing.</t>
</section>

<section anchor="checks"><name>Required checks</name>
<t>Over an artifact that has passed <xref target="authenticate"></xref>, a Relying Party <bcp14>MUST</bcp14>:</t>

<ol spacing="compact">
<li>verify the Authorization Record and Execution Grant and their mutual bindings
(<xref target="I-D.toraman-noa-action-digest"></xref>), <strong>including its non-optional signature checks</strong>, through the
<strong>authenticating</strong> 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;</li>
<li>recompute the Action Digest from those verified documents, <strong>and</strong> verify the Parameter
Pre-image against the Authorization Record's Parameter Commitment. Both are <strong>inputs to item
6</strong>, 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 <strong>keyed</strong> hash the Relying Party cannot reproduce, the Correlation
Value cannot be derived at all and the verifier <bcp14>MUST</bcp14> return <tt>SETTLEMENT_BOUNDS_UNCHECKABLE</tt>
(<xref target="codes"></xref>) — a non-positive, non-failure result: never a positive verdict, and never a positive
verdict qualified by a warning;</li>
<li>check <tt>railFamily</tt> is a member of the set this verifier ships, and <strong>fail closed on an unknown
family</strong>;</li>
<li>check that the resolved event is of the expected type on the chain named by <tt>chainWitness.network</tt>
(CAIP-2) at the token contract named by <tt>chainWitness.asset</tt> (CAIP-19), <strong>and that the chain and
token contract are values the mandate committed to</strong> — the network and the asset are derived from
the verified Parameter Pre-image's own <tt>networkCaip2</tt> and <tt>assetCaip19</tt> and compared, never lifted
from <tt>chainWitness</tt> — see <xref target="forgery"></xref>;</li>
<li>check that the event's authorizer topic equals <tt>chainWitness.payer</tt>, <strong>and</strong> that <tt>chainWitness.payer</tt>
is the payer the mandate committed to — see <xref target="forgery"></xref>;</li>
<li><strong>recompute the Correlation Value</strong> per <xref target="derivation"></xref> — 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 <strong>only</strong> from the verified Parameter Pre-image — and check that it equals <strong>both</strong> the
event's identifier topic <strong>and</strong> the artifact's <tt>correlation</tt> member, comparing the 64 hex
characters lowercased on both sides. A supplied Correlation Value <bcp14>MUST NOT</bcp14> be trusted, and a
Correlation Value in the Action Digest's <tt>sha256:</tt> form <bcp14>MUST</bcp14> be refused rather than
re-encoded (D5, D6);</li>
<li>check that <strong>exactly one</strong> such event exists for <tt>(authorizer, identifier)</tt> on that contract, and
that its transaction status is success;</li>
<li><strong>recover the transfer and bound it.</strong> Decode the settling transaction's calldata, or take the
token <tt>Transfer(from,to,value)</tt> log emitted in the same transaction — the log <bcp14>MUST</bcp14> be
emitted by the token contract named in the mandate-committed asset, its <tt>from</tt> <bcp14>MUST</bcp14> equal
<tt>chainWitness.payer</tt>, and there <bcp14>MUST</bcp14> be exactly one such log (more than one -&gt; not
reconfirmed, <strong>never a pick</strong>). Then check the recovered <tt>to</tt> equals <tt>chainWitness.payee</tt>, the
recovered <tt>value</tt> equals <tt>chainWitness.amount</tt>, and <strong>both lie within the bounds the mandate
committed to</strong>;</li>
<li>compute confirmation depth against <strong>its own</strong> ledger head and apply <strong>its own</strong> finality
threshold;</li>
<li>on any mismatch, return a distinct machine-readable code from the closed registry of
<xref target="codes"></xref>, never a generic invalid.</li>
</ol>
<t>Items 4 and 8 close two attacks that a weaker check set leaves open, and they are the reason
<tt>chainWitness</tt> — with its <tt>network</tt>, <tt>asset</tt>, <tt>payer</tt>, <tt>payee</tt> and <tt>amount</tt> — is a required
member. Neither is closed by <xref target="derivation"></xref>: binding the rail coordinates <em>inside</em> the Correlation
Value fixes which coordinates a <strong>recomputation</strong> can be performed against, and it does not stop a
settlement from being performed somewhere else.</t>
<t><strong>(a) Approve-A-run-B on the rail.</strong> <tt>AuthorizationUsed(address indexed authorizer, bytes32 indexed
nonce)</tt> carries <strong>two</strong> fields. <tt>to</tt>, <tt>value</tt>, <tt>validAfter</tt> and <tt>validBefore</tt> are inside the signed
struct and <strong>not in the event</strong>. 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 <tt>to = attacker, value = anything</tt>, settles, and every remaining check passes. Item 8 closes
it.</t>
<t><strong>(b) Counterfeit asset or foreign chain.</strong> If the mandate commits to the payer and nothing else,
then <tt>chainWitness.network</tt> and <tt>chainWitness.asset</tt> 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 <strong>its</strong> signing domain carrying the correlation, settle for cents, fill in
<tt>chainWitness</tt> 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.</t>
<t><strong>A permanent scoping consequence:</strong> because the identifier's uniqueness is enforced per
<tt>(authorizer, nonce)</tt> <strong>per contract per chain</strong>, the mechanism is defensible only for
<strong>explicitly enumerated</strong> token deployments. A profile <bcp14>MUST</bcp14> name the <tt>(network, token contract)</tt>
pairs it covers — in the CAIP-2 / CAIP-19 forms of <xref target="grammars"></xref> — and a generic &quot;any token
implementing this interface&quot; is not in scope. <strong>This is a requirement on the profile, not a
description of the check in <xref target="checks"></xref>:</strong> 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 <bcp14>MUST</bcp14> enforce it itself. See
<xref target="scope-limit"></xref>.</t>
</section>

<section anchor="codes"><name>Result codes</name>
<t>Every outcome carries exactly one code from this closed registry, and <strong>the registry is closed in
both directions</strong>: 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: <strong>a
non-positive result is not a rejection.</strong></t>
<t><strong>Positive — exactly one.</strong> <tt>SETTLEMENT_CORRELATED_AND_RECONFIRMED</tt>. Only this code may upgrade any
outcome anywhere.</t>
<t><strong>Non-positive, and NOT failures.</strong> These say <em>&quot;the evidence does not support a positive verdict&quot;</em>,
never <em>&quot;something is wrong&quot;</em>. Reporting one as a rejection is a conformance error:</t>

<ul spacing="compact">
<li><tt>SETTLEMENT_CORRELATED_UNRECONFIRMED</tt> — 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 <strong>&quot;not final&quot;</strong>;</li>
<li><tt>SETTLEMENT_NOT_OBSERVED</tt> — the Observer did not look;</li>
<li><tt>SETTLEMENT_NOT_SETTLED_AT_OBSERVATION</tt> — the Observer looked and saw no consumption yet;</li>
<li><tt>SETTLEMENT_BOUNDS_UNCHECKABLE</tt> — the Parameter Pre-image is absent, mismatched, or carries a
<strong>keyed</strong> commitment the Relying Party cannot reproduce, so neither the bounds nor the derivation
can run (item 2 of <xref target="checks"></xref>);</li>
<li><tt>SETTLEMENT_CORRELATION_BURNED</tt> — the correlation was consumed by a successful <strong>cancellation</strong>.
Terminal, and specifically <em>not</em> &quot;not yet&quot; (<xref target="anchors"></xref>, availability).</li>
</ul>
<t><strong>Rejections.</strong> Sixteen, and this list is complete:</t>

<artwork><![CDATA[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
]]></artwork>
<t>Mapping the checks of <xref target="checks"></xref> onto them: item 3 → <tt>SETTLEMENT_RAIL_FAMILY_UNKNOWN</tt>; item 4 →
<tt>SETTLEMENT_NETWORK_UNEXPECTED</tt> / <tt>SETTLEMENT_ASSET_UNEXPECTED</tt>; item 5 →
<tt>SETTLEMENT_PAYER_UNEXPECTED</tt>; item 6 → <tt>SETTLEMENT_CORRELATION_MISMATCH</tt>; item 8 →
<tt>SETTLEMENT_BOUNDS_EXCEEDED</tt>. <strong>A wrong payee and a wrong amount deliberately share one code</strong>
(<tt>SETTLEMENT_BOUNDS_EXCEEDED</tt>): both are violations of the bounds the mandate committed to, and
splitting them would imply a distinction the bounds check does not make.</t>
<t><strong>Warnings — never rejections.</strong> <tt>SETTLEMENT_AFTER_GRANT_EXPIRY</tt>,
<tt>SETTLEMENT_OBSERVER_SAME_KEY_AS_EXECUTION_SIGNER</tt>, <tt>RAIL_RECEIPT_PROVIDED_UNPARSEABLE</tt>,
<tt>SETTLEMENT_OVER_RAW_MODE_HOLD</tt>. A warning caps the outcome; it never converts one into a failure.</t>
<t>There is deliberately no code meaning <em>&quot;the settlement did not occur&quot;</em>, and no <tt>FAILED</tt>: absence of
a settlement observation is not evidence of non-payment. What the two nearest codes report is
narrower and is worth stating exactly — <tt>SETTLEMENT_NOT_OBSERVED</tt> means the Observer looked and saw
nothing at that moment, and <tt>SETTLEMENT_NOT_SETTLED_AT_OBSERVATION</tt> means it saw the authorization
unconsumed at that moment. Both are statements about one observation instant, not about the world.</t>
</section>

<section anchor="observer"><name>The Observer's signature is not the evidence</name>
<t>The ledger is the fact; the Observer merely looked. The artifact's <tt>chainWitness</tt> carries the
Observer's <strong>reported</strong> <tt>txHash</tt> and <tt>blockNumber</tt> (both nullable, and both reject-only) <strong>so that a
Relying Party can locate what the Observer says it saw</strong> — but the artifact does <strong>not</strong> carry a
<tt>logIndex</tt>, and it does <strong>not</strong> carry the transaction status. The evidence that a positive verdict
rests on is not any of these reported fields: it is the <tt>noa.chain-facts/0.1</tt> record the Relying
Party obtains <strong>from its own node</strong> (item 8 of <xref target="checks"></xref>), against which the Observer's report is
merely corroborated, reject-only. A Relying Party that can reach the ledger <bcp14>SHOULD</bcp14> obtain those
facts itself rather than rely on <tt>sig</tt> or on the Observer's reported coordinates.</t>
<t><strong>Consequence, stated because it is uncomfortable and must not be discovered later:</strong> this is the
<strong>one artifact in the system that cannot be fully verified offline.</strong> 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 <strong>that is all</strong> — it has verified that someone claimed to have seen something. Any product
surface describing this artifact as offline-verifiable is wrong.</t>
</section>
</section>

<section anchor="security"><name>Security Considerations</name>

<section anchor="scope-limit"><name>Settled is not delivered — the permanent scope limit</name>
<t>A positive verdict establishes that <strong>the ledger facts the Relying Party supplied are consistent
with</strong> a payer-authorized transfer, matching a locally correlated mandate artifact, that settled on
<strong>the token contract and network the mandate's own verified Parameter Pre-image names</strong>. It is <strong>a
statement about the caller's ledger facts, not about the ledger.</strong></t>
<t><strong>Say precisely what fixes the contract, because &quot;allowlisted&quot; would overstate it.</strong> The check this
document requires is a <strong>comparison against the mandate's own committed coordinates</strong>, not a lookup
in a verifier-held registry of blessed assets: item 4 of <xref target="checks"></xref> compares the network and asset
against the verified Parameter Pre-image, and that is the whole of it. A deployment <bcp14>MAY</bcp14>
additionally maintain an allowlist of <tt>(network, token contract)</tt> pairs it is willing to reason
about — <xref target="checks"></xref> requires a profile to name the pairs it covers, and the reference implementation
ships such a list on a <em>separate</em> code path — but the acceptance path described here does <strong>not</strong>
consult one, and an implementer <bcp14>MUST NOT</bcp14> assume an allowlist is being enforced on their behalf.</t>
<t><strong>And the ledger facts themselves are the caller's, not the verifier's.</strong> This implementation
performs <strong>no network I/O</strong>: it reads a <tt>noa.chain-facts/0.1</tt> record
the caller asserts came from its own node (<tt>RELYING_PARTY_NODE</tt> is a <strong>caller-supplied provenance
assertion</strong>, not something the verifier can confirm), and it <strong>cannot detect a lagging, lying, or
hostile RPC.</strong> 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 <strong>nothing</strong> 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, <strong>and a Relying Party that does not hold that preimage cannot bound the purpose at
all.</strong></t>
<t>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.</t>
<t>Any counterparty-signed receipt covering delivery is <strong>counterparty evidence</strong>: signed by the party
being paid, about its own performance. That is a legitimate artifact and a self-serving one, and it
<bcp14>MUST NOT</bcp14> be described as an independent witness.</t>
</section>

<section anchor="anchors"><name>Residual trust anchors</name>
<t>The anchors of <xref target="I-D.toraman-noa-action-digest"></xref> apply unchanged and are restated wherever this
artifact's posture is described: <strong>infrastructure root · key custody · governance capture · the
deceived approver · cross-action composition · the ungated surface</strong> — <strong>an OPEN list, never a
complete taxonomy.</strong> Three further classes are specifically live for this artifact and are named
here rather than left to the omitted set:</t>

<ul spacing="compact">
<li><strong>Availability / denial of service.</strong> A party holding the payer key can consume the correlation
<strong>without paying</strong>, by issuing a cancellation for the same <tt>(authorizer, nonce)</tt>. The identifier
is burned permanently and the approved payment can never settle under it. A verifier sees no
consumption event and reports &quot;not settled yet&quot; for something that will never settle;
<xref target="absence"></xref>'s absence rule must therefore distinguish <em>not yet</em> from <em>burned</em>, and a profile MUST
carry the cancellation event in its fact set.</li>
<li><strong>Front-running.</strong> 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
<strong>reports failure without locating the transaction that consumed it</strong>. An implementation MUST
reconcile consumed identifiers or it will report failure for payments that actually settled.</li>
<li><strong>Privacy leakage.</strong> See <xref target="privacy"></xref>.</li>
</ul>
<t>The key-custody anchor is open in the reference implementation; see <xref target="impl-status"></xref>. No claim of
unforgeable mandate authority may be made until it closes.</t>
<t><strong>Activity outside enforced gateways is outside coverage</strong> — no mandate, no correlation value and
no settlement evidence exists for it, <strong>including no record of the absence</strong>. This is a scope
statement, not a mitigation, and it carries no claim about what any particular escaped process
chooses to do.</t>
</section>

<section anchor="forgery"><name>Correlation forgery by an unbound payer</name>
<t>The ledger event is keyed on <tt>(authorizer, nonce)</tt>. An attacker who learns a Correlation Value can,
cheaply, sign their <strong>own</strong> payment authorization using that same 32-octet value, on any compatible
token, on any compatible chain, and cause an <tt>AuthorizationUsed</tt> 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 <tt>chainWitness</tt> — carrying the network, asset and payer —
is mandatory.</t>
<t><strong>Mitigations, all required:</strong></t>

<ul spacing="compact">
<li>the artifact <bcp14>MUST</bcp14> carry <tt>chainWitness.network</tt>, <tt>chainWitness.asset</tt> and <tt>chainWitness.payer</tt>
(<xref target="members"></xref>);</li>
<li>the verifier <bcp14>MUST</bcp14> check the event's authorizer topic equals <tt>chainWitness.payer</tt> (item 5 of
<xref target="checks"></xref>);</li>
<li><strong>the mandate MUST commit to the network, the token contract AND the payer before dispatch.</strong>
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 <xref target="checks"></xref>.</li>
</ul>
<t>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:</t>

<ol spacing="compact">
<li><strong>By the mandate's Parameter Commitment.</strong> 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 <xref target="checks"></xref>
bounds the transfer against.</li>
<li><strong>Inside the Correlation Value itself.</strong> The chain identifier, the token contract and the payer
are derivation inputs (<xref target="derivation"></xref>), 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
<strong>non-substitutable in the recomputation</strong>. A verifier can no longer be handed a value that
&quot;belongs&quot; to some other triple, and the artifact's own <tt>chainWitness</tt> coordinates drop to
reject-only.</li>
</ol>
<t><strong>What that does not do, stated plainly, because the inverse is the easy misreading.</strong> 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
<tt>(authorizer, nonce)</tt> <strong>per contract per chain</strong>. What refuses that decoy is still items 4 and 5 of
<xref target="checks"></xref>, run against the verified Parameter Pre-image. The derivation removes a <em>substitution</em>;
it does not remove an <em>event</em>.</t>
<t><strong>Two residuals, both live, both written as non-claims:</strong></t>
<blockquote><t><strong>(i) Verification is fail-closed on the Parameter Pre-image, and there is no partial tier.</strong> A
Relying Party that does not hold the Pre-image cannot derive the Correlation Value, therefore
cannot run item 6, and <bcp14>MUST</bcp14> return <tt>SETTLEMENT_BOUNDS_UNCHECKABLE</tt> (<xref target="codes"></xref>) 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: <strong>no minimized, commitment-only
presentation of this artifact exists in this version.</strong> 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.</t>
<t><strong>(ii) Money can move without a positive verdict, and this document does not claim otherwise.</strong> A
party holding the payer key can derive a <em>different</em> 32 octets from a <em>different</em> 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 <strong>which settlements a Relying Party will accept as correlated</strong>, never <strong>which
transfers a compromised payer key can cause.</strong> The honest ceiling is <em>at most one settlement
correlatable to this grant</em>, never <em>exactly one settlement per mandate</em>.</t>
</blockquote><t>Note the Parameter Commitment may legitimately be a <strong>keyed</strong> hash in the reference
implementation's frozen record format. Under <xref target="derivation"></xref> that is no longer a partial loss: a
keyed commitment blocks the derivation as well as the bounds, so the only correct outcome is
<tt>SETTLEMENT_BOUNDS_UNCHECKABLE</tt> (item 2) — a <strong>non-positive, non-failure</strong> 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
<bcp14>SHOULD</bcp14> require the unkeyed form for rail-bound action classes, and <bcp14>MUST</bcp14> state that
consequence otherwise.</t>
</section>

<section anchor="absence"><name>Absence is not proof of non-settlement</name>
<t>Absence of a ledger event proves only that the Observer did not observe one. The authorization may
settle later, within its validity window. A <tt>NOT_SETTLED</tt> conclusion <bcp14>MUST</bcp14> therefore be
time-qualified: not settled as of block N <strong>and</strong> the authorization's validity window has closed.
An implementation that reports unqualified <tt>NOT_SETTLED</tt> is making a claim it cannot support. The
<tt>chainWitness.status</tt> field (<xref target="members"></xref>) is what carries this, and it distinguishes <em>we did not
look</em> from <em>we looked and saw nothing yet</em>.</t>
<t><strong>And the converse is equally important, because the obvious oracle is not one.</strong> A state query
returning &quot;this identifier is consumed&quot; is <strong>NOT</strong> proof of settlement: at least one major
deployment sets the same state bit for <strong>cancellation</strong> as for use. Settlement proof requires the
conjunction — the consumption <strong>event</strong>, plus a successful transaction status, plus matching
calldata or a corresponding token transfer. <strong>A profile MUST NOT specify a state-getter-only
check.</strong></t>
</section>

<section anchor="reorg"><name>Reorganization</name>
<t>A ledger observation can be reversed by a chain reorganization. A Relying Party <bcp14>MUST</bcp14> apply its
own finality threshold against its own view of the chain head (item 9 of <xref target="checks"></xref>) rather than
inherit the Observer's. A verdict computed under an Observer-supplied policy is a verdict the
Observer chose.</t>
</section>

<section anchor="liveness"><name>Liveness failures are not correlation failures</name>
<t>An intermediary may decline to settle, delay, or fail. These are liveness failures and they are all
detectable as &quot;no event for our correlation value&quot; — they are <strong>not</strong> evidence of tampering, and an
implementation <bcp14>MUST NOT</bcp14> report them as such.</t>
</section>

<section anchor="availability"><name>Deployment-dependent availability</name>
<t>A settlement intermediary that policed the <em>content</em> 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.</t>
<t><strong>Measured, 2026-08-14, against a public hosted facilitator on a testnet.</strong> Three
<tt>TransferWithAuthorization</tt> 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
<xref target="derivation"></xref>, (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:</t>

<ol spacing="compact">
<li><strong>No identifier-content policy was applied at or before the stage all three reached.</strong> 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.</li>
<li><strong>The refusal came from beyond signature verification.</strong> The simulated call reverted inside the
token contract's balance check, which sits <em>after</em> 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.</li>
</ol>
<t><strong>Scope of that measurement, stated so it is not overread.</strong> 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:</t>

<ul spacing="compact">
<li><strong>The settlement path was not exercised.</strong> 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 <strong>not excluded</strong> by
this measurement.</li>
<li><strong>Nothing is established about stages beyond the refusal.</strong> The three requests are comparable
only up to the point where all three stopped for the same unrelated reason.</li>
<li><strong>One deployment is not every deployment.</strong> An implementation <bcp14>SHOULD</bcp14> 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.</li>
</ul>
<t>What the comparison does buy, cheaply, is the elimination of the <em>cheapest and most likely</em> 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, <bcp14>SHOULD</bcp14> extend the same comparison through a real settlement
and record which of (A) and (B) settles.</t>
<t><strong><xref target="derivation"></xref> also narrows what such a policy could even detect.</strong> 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 <em>attested provenance</em> — 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.</t>
<t>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.</t>
</section>

<section anchor="privacy"><name>Privacy</name>
<t>The privacy section of <xref target="I-D.toraman-noa-action-digest"></xref> applies with full force, and <xref target="derivation"></xref>
changes where its weight falls. Three statements, in the order in which they are easiest to
overstate:</t>

<ol spacing="compact">
<li><strong>The Action Digest is not on the ledger.</strong> 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 <strong>closed against an outside observer</strong>.
That is the whole reason the second stage exists, and it is the only privacy claim this document
makes for it.</li>
<li><strong>It is not closed against a bundle holder, and that is not the smaller problem.</strong> 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; <strong>the bundle
creates that link.</strong> Disclosing an evidence bundle is a durable capability grant, not a one-time
view, and <xref target="forgery"></xref>'s residual (i) says why this version offers no minimized alternative.</li>
<li><strong>Payer-level correlation was already available</strong> to any ledger observer via the authorizer
topic, so a per-approval-unique, unguessable identifier adds <strong>no new linkage</strong> 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 <xref target="I-D.toraman-noa-action-digest"></xref>).</li>
</ol>
</section>
</section>

<section anchor="outcome"><name>Interaction With Outcome Semantics</name>
<t>This document supports a verifier rule of the form: <strong>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 <tt>DISPATCHED</tt>.</strong></t>
<t><strong>Two qualifiers that must travel with that rule.</strong> <em>(i)</em> A Relying Party that lacks the Parameter
Pre-image cannot derive the Correlation Value at all, so it gets <strong>no</strong> verdict from this artifact
rather than a weaker one (residual (i) of <xref target="forgery"></xref>); the verdict word gets stronger only for a
Relying Party that holds the Pre-image and runs the checks. <em>(ii)</em> An <strong>offline</strong> verifier — one that holds the artifact and no ledger
access — can establish the artifact's internal consistency and its bindings and <strong>nothing about the
ledger</strong>; a profile MUST give that a distinct, weaker verdict tier and MUST NOT let it satisfy the
rule. It is the <em>ledger facts</em>, obtained by the Relying Party, that lift the cap — not the
artifact's presence.</t>
<t><strong>Still open, and correctly so: no per-tenant, authenticated action-class enrolment registry exists
in the reference verifier today.</strong> A design for one exists — as an <strong>external verifier input</strong>,
alongside the trust root and checkpoint keyring the verifier already requires, rather than as a
bundle member — but it is unimplemented. <strong>This document therefore cannot specify the rule
normatively</strong>, 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 <em>&quot;trusted CODE rather than
authenticated POLICY&quot;</em> — code, not an authenticated per-tenant policy. <strong>A rule keyed on &quot;enrolled
action classes&quot; cannot be specified normatively until such an authenticated registry exists.</strong> Inventing the registry here would be the dishonest
alternative.</t>
<t>In the reference implementation today, <tt>EXECUTED</tt> means exactly: a gate-signed consumption artifact
whose result field is <tt>DISPATCHED</tt> — one of that wire enum's two values (<tt>DISPATCHED</tt> |
<tt>FAILED_BEFORE_DISPATCH</tt>), and the only one the gate ever signs — transitively bound to an
approver-signed ALLOWED record. <strong>Nothing in that bundle is evidence of a side effect</strong> — the enforcement layer says so in
its own source: <em>&quot;Nothing here observes what the executor DID… The gate reports EXECUTED for a
dispatch it authorized, never for an outcome it witnessed&quot;</em>.</t>
</section>

<section anchor="iana"><name>IANA Considerations</name>
<t><tt>[PLACEHOLDER — pending working group direction]</tt>. Provisional: media type
<tt>application/noa-settlement-evidence+json</tt> (and <tt>+cose</tt> under a COSE profile <xref target="RFC9052"></xref> <xref target="RFC9053"></xref>);
the
signing-domain-tag registry proposed in <xref target="I-D.toraman-noa-action-digest"></xref> 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.</t>
</section>

<section anchor="changelog"><name>Change log</name>
<t>[[RFC EDITOR: please remove this section before publication.]]</t>
<t>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.</t>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<reference anchor="CAIP19" target="https://standards.chainagnostic.org/CAIPs/caip-19">
  <front>
    <title>CAIP-19: Asset Type and Asset ID Specification</title>
    <author initials="A." surname="Milosevic">
      <organization></organization>
    </author>
    <author initials="P." surname="Kumar">
      <organization></organization>
    </author>
    <date year="2020" month="August"></date>
  </front>
  <refcontent>Chain Agnostic Improvement Proposals</refcontent>
</reference>
<reference anchor="CAIP2" target="https://standards.chainagnostic.org/CAIPs/caip-2">
  <front>
    <title>CAIP-2: Blockchain ID Specification</title>
    <author initials="S." surname="Gosselin">
      <organization></organization>
    </author>
    <author initials="P." surname="Kumar">
      <organization></organization>
    </author>
    <author initials="J." surname="Holmes">
      <organization></organization>
    </author>
    <date year="2020" month="March"></date>
  </front>
  <refcontent>Chain Agnostic Improvement Proposals</refcontent>
</reference>
<reference anchor="EIP3009" target="https://eips.ethereum.org/EIPS/eip-3009">
  <front>
    <title>ERC-3009: Transfer With Authorization</title>
    <author initials="P." surname="Kim">
      <organization></organization>
    </author>
    <author initials="K." surname="Britz">
      <organization></organization>
    </author>
    <author initials="D." surname="Knott">
      <organization></organization>
    </author>
    <date year="2020" month="September"></date>
  </front>
  <seriesInfo name="Ethereum Improvement Proposals" value="no. 3009"></seriesInfo>
  <refcontent>Status: Draft, as rendered 2026-08-12</refcontent>
</reference>
<reference anchor="FIPS180-4" target="https://csrc.nist.gov/pubs/fips/180-4/upd1/final">
  <front>
    <title>Secure Hash Standard (SHS)</title>
    <author>
      <organization>National Institute of Standards and Technology</organization>
    </author>
    <date year="2015" month="August"></date>
  </front>
  <seriesInfo name="FIPS" value="PUB 180-4"></seriesInfo>
</reference>
<reference anchor="I-D.toraman-noa-action-digest" target="https://datatracker.ietf.org/doc/draft-toraman-noa-action-digest/">
  <front>
    <title>The NOA Action Digest: a Domain-Separated Correlation Value for Human-Approved Agent Actions</title>
    <author fullname="Tora Toraman" initials="T." surname="Toraman">
      <organization>NordenSoft</organization>
    </author>
    <date year="2026"></date>
  </front>
  <seriesInfo name="Internet-Draft" value="draft-toraman-noa-action-digest-00"></seriesInfo>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3339.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8032.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8785.xml"/>
</references>
<references><name>Informative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7942.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9052.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9053.xml"/>
</references>
</references>

<section anchor="impl-status"><name>Implementation status</name>
<t><em>This section is to be removed before publication as an RFC, per <xref target="RFC7942"></xref>.</em></t>
<t>A reference implementation, together with the <strong>Copenhagen vectors</strong> — the cross-language
conformance corpus that enforces the checks in <xref target="checks"></xref> — is available under the Apache-2.0
license at <eref target="https://github.com/NordenSoft/noa">https://github.com/NordenSoft/noa</eref>.</t>
<t>The following were measured, in that repository, on 2026-08-14, at <tt>main</tt> commit <tt>7aa8e37</tt>:</t>

<ul spacing="compact">
<li>The two-stage derivation of <xref target="derivation"></xref>: <tt>packages/rail-x402/src/correlation-nonce.mjs</tt> — 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 <tt>0x</tt> output form. Its recompute-and-compare use
is <tt>packages/rail-x402/src/settlement-evidence.mjs</tt>, which derives only from the verified
Parameter Pre-image and the authenticated bundle documents, never from the artifact.</li>
<li>The settlement-evidence Copenhagen vectors: <tt>packages/rail-x402/conformance/settlement-evidence/</tt>
— 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
<xref target="derivation"></xref> specifically are <tt>reject-sibling-seed</tt> (same grant, a different seed, a real
settlement on the ledger, and still no positive verdict), <tt>reject-chainid-from-artifact</tt> (D1),
<tt>reject-hmac-params-derivation</tt> (a keyed Parameter Commitment blocks the derivation, not only the
bounds), <tt>reject-digest-over-other-grant</tt> and <tt>reject-cross-tenant-correlation</tt> (recomputation,
not comparison, is what refuses a genuine settlement filed under the wrong mandate).</li>
<li>A second, artifact-level corpus at
<tt>packages/approval-artifacts/conformance/settlement-evidence/</tt> covers the signature, signer-role
and unknown-member refusals for the artifact itself. It also carries
<tt>reject-correlation-digest-form</tt>, which refuses a <tt>correlation</tt> member written in the Action
Digest's <tt>sha256:</tt> string form — the case that pins D6 of <xref target="derivation"></xref>, and the evidence that
the earlier one-stage construction is a structural refusal in the reference implementation rather
than a form it quietly normalizes.</li>
<li>Key custody, the second anchor of <xref target="anchors"></xref>: the reference implementation documents its own
open weakness in <tt>NON-CLAIMS.md</tt> — the execution-grant signing key is held as a string in the
process the grants are meant to defend.</li>
<li>The deployment-availability measurement reported in <xref target="availability"></xref>: three
<tt>TransferWithAuthorization</tt> 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
<xref target="availability"></xref> states. The measurement script is not part of the reference implementation; the
reproduction recipe is the three-way comparison described in <xref target="availability"></xref>, which any
implementer can re-run against their own intermediary.</li>
</ul>
<t><strong>Anchoring note.</strong> <xref target="derivation"></xref>, item 6 of <xref target="checks"></xref> and the residuals in <xref target="forgery"></xref> and
<xref target="privacy"></xref> 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.</t>
</section>

</back>

</rfc>
