<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-borthwick-wallet-state-attestation-01" category="info" tocInclude="true" sortRefs="true" symRefs="true" version="3">
  <!-- xml2rfc v2v3 conversion 3.33.0 -->
  <front>
    <title abbrev="Wallet State Attestation">Wallet State Attestation: Signed Booleans about On-Chain State</title>
    <seriesInfo name="Internet-Draft" value="draft-borthwick-wallet-state-attestation-01"/>
    <author initials="D." surname="Borthwick" fullname="Douglas Borthwick">
      <organization>InsumerAPI</organization>
      <address>
        <postal>
          <street/>
          <city>New Canaan</city>
          <region>CT</region>
          <code/>
          <country>US</country>
        </postal>
        <email>douglas@insumermodel.com</email>
      </address>
    </author>
    <date year="2026" month="September" day="27"/>
    <keyword>wallet</keyword>
    <keyword>attestation</keyword>
    <keyword>blockchain</keyword>
    <keyword>JWKS</keyword>
    <keyword>JWT</keyword>
    <keyword>condition-based access</keyword>
    <abstract>
      
<t>This document defines Wallet State Attestation: a primitive in which an issuer reads on-chain state for a wallet address, evaluates one or more operator-defined conditions, and returns a cryptographically signed boolean (or structured fact profile) that any verifier can check offline using a published JWKS endpoint. The primitive enables condition-based access decisions without identity presentation, credential exchange, or contact with the issuer at verification time beyond a copy of its published key set and the documentation that accompanies it. This document describes the request and response surface, the signing algorithm posture, the JWKS discovery pattern, and the security and privacy considerations of the primitive. It does not define a new wire protocol; rather, it profiles existing IETF building blocks (JWT, JWKS, JOSE) for the wallet-state-attestation use case.</t>
    </abstract>
  </front>
  <middle>
    
<section anchor="introduction">
      <name>Introduction</name>
      <section anchor="motivation">
        <name>Motivation</name>
        <t>Existing access-control primitives --- OAuth 2.0 <xref target="RFC6749"/>, OIDC <xref target="OIDC-CORE"/>, SAML, and the broader credential-presentation family --- answer the question "who is this user?" by accepting a presented artifact (token, assertion, credential) from a holder.</t>
        <t>Wallet State Attestation answers a different question: "does this wallet currently satisfy a specified condition over on-chain state?" The holder presents nothing. An issuer reads canonical on-chain state for a specified wallet address, evaluates the operator-defined condition, and returns a signed boolean. Verifiers check the signature offline using a published JWKS endpoint.</t>
        <t>This is a distinct primitive from identity verification, credential issuance, or holder-presentation flows. It belongs in a parallel category --- what this document terms wallet-state-attestation --- alongside, not within, the existing credential-presentation stack.</t>
      </section>
      <section anchor="position-relative-to-identity-primitives">
        <name>Position Relative to Identity Primitives</name>
        <t>OAuth and OIDC prove who a subject is. Wallet State Attestation proves what a wallet currently holds, owns, or has been attested to in on-chain state.</t>
        <t>These are complementary, not competing. A system may use OAuth to authenticate a human or service principal, then call a wallet-state attestation issuer to verify that a wallet associated with that principal satisfies an on-chain condition. The two primitives operate on different subjects (identity vs. wallet state) and answer different questions.</t>
      </section>
      <section anchor="position-relative-to-credentials">
        <name>Position Relative to Credentials</name>
        <t>Wallet State Attestation is <strong>not</strong> a Verifiable Credential (W3C VC) <xref target="VC-DATA-MODEL"/>, an mDoc / ISO/IEC 18013-5 mobile document, an eIDAS attestation, a SAML assertion, or an OIDC ID token. These are credential-presentation primitives: an authority issues a credential, the holder presents it, the verifier checks the issuer's signature over the holder's presented bytes.</t>
        <t>Wallet State Attestation does not involve credential presentation. The holder does not present anything. The issuer pulls public on-chain state directly, evaluates a condition, and signs the result. The signed payload is an observation about external state, not a credential about the holder's identity.</t>
        <t>This document treats the credential / VC / eIDAS / SAML stack as adjacent prior art for explicit non-conflation purposes. See <xref target="whatisnot"/> ("What This Is Not") for the full enumeration.</t>
      </section>
      <section anchor="position-relative-to-other-documents">
        <name>Position Relative to Other Documents</name>
        <t>Wallet State Attestation is referenced or adopted in the following public artifacts (all informative; see References):</t>
        <ul spacing="normal">
          <li>
            <t><strong>Knowledge Context Protocol (KCP) RFC-0004</strong> <xref target="KCP-0004"/> --- defines an <tt>attestation_url</tt> / <tt>attestation_jwks</tt> mechanism in KCP YAML manifests for verifying agent eligibility before access. The pattern is mechanism-agnostic; wallet state attestation is one valid implementation shape.</t>
          </li>
          <li>
            <t><strong>Verifiable Intent (VI) <tt>environment.*</tt> constraint family</strong> <xref target="VI-ENVIRONMENT"/> --- defines an environmental constraint family for autonomous-mode (L3) execution in agent commerce, establishing family-wide vocabulary (<tt>attestation_url</tt>, <tt>max_attestation_age</tt>), composition rules, and IANA registry mechanics for individual constraint types. The wallet-state-attestation primitive is the reference implementation shape used by VI's <tt>environment.wallet_state</tt> constraint type.</t>
          </li>
          <li>
            <t><strong>Agent Governance Vocabulary</strong> <xref target="AGV"/> --- defines <tt>wallet_state</tt> as a foundation-layer signal type with named production issuers.</t>
          </li>
          <li>
            <t><strong>Open Agent Trust Registry (OATR)</strong> <xref target="OATR"/> --- third-party trust registry of attestation issuers, including wallet-state-attestation issuers.</t>
          </li>
          <li>
            <t><strong>Draft ERC-8210 Agent Assurance Protocol</strong> <xref target="ERC-8210"/> --- proposed IRiskHook verification taxonomy identifies wallet state attestation as the representative shape for pre-commitment eligibility verification (one of three categories in the verification framework, alongside post-hoc resolution evidence and behavioral reputation).</t>
          </li>
          <li>
            <t><strong>Universal Commerce Protocol (UCP) identity-linking</strong> <xref target="UCP-IDENTITY"/> --- wallet attestation acknowledged as a reserved extensibility point for future non-OAuth identity-mechanism types.</t>
          </li>
        </ul>
        <t>Wallet state attestation is <strong>not</strong> dependent on or normatively related to any of these documents. They are listed as evidence of adoption and as informative context for readers seeking to understand where the primitive is in use.</t>
      </section>
      <section anchor="use-cases">
        <name>Use Cases</name>
        <t>Common deployments of wallet state attestation include:</t>
        <ul spacing="normal">
          <li>
            <t><strong>Condition-based access</strong> --- gating API access, content access, or commerce flows on whether a specified wallet holds a specified token, NFT, or attested status at request time, without exposing actual balance or transaction history.</t>
          </li>
          <li>
            <t><strong>Agent-to-agent trust verification</strong> --- autonomous agents verifying counterparty wallet state (holdings, attested compliance status, etc.) before transaction execution.</t>
          </li>
          <li>
            <t><strong>Compliance gating</strong> --- verifying on-chain compliance attestations (KYC status from external attestors, country attestations, sanctions clearance) against a wallet without the verifier receiving any identity attribute beyond the boolean it asked for (<xref target="no-pii"/>).</t>
          </li>
          <li>
            <t><strong>Pre-commitment eligibility</strong> --- agent commerce protocols verifying that an agent wallet satisfies pre-conditions (sufficient stake, sufficient balance, attested status) before contract commitment.</t>
          </li>
        </ul>
        <t>The primitive is read-only and does not interact with chain state beyond observation. Implementations issue attestations on demand; attestations are short-lived and expire (typically thirty minutes; see <xref target="response"/>).</t>
      </section>
    </section>
    <section anchor="terminology">
      <name>Terminology</name>
      <t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they appear in all capitals, as shown here.</t>
      <t>The following terms are used throughout this document:</t>
      <t><strong>Issuer</strong> --- The party that reads on-chain state, evaluates conditions, and signs the resulting attestation. The issuer is the sole signer of attestations within its declared scope (see <xref target="single-issuer"/>).</t>
      <t><strong>Holder</strong> --- The wallet whose on-chain state is observed. The holder does not present anything in this primitive; the issuer reads chain state directly. The holder need not be aware of any individual attestation.</t>
      <t><strong>Verifier</strong> --- The party that consumes the attestation, checks the signature against the issuer's published JWKS, and acts on the result.</t>
      <t><strong>Condition</strong> --- An operator-defined predicate over wallet state. Examples include: "balance of token T at address A is greater than or equal to value V", "wallet A owns NFT N", "wallet A has a valid on-chain attestation of type T from attestor X".</t>
      <t><strong>Attestation</strong> --- The signed payload returned by the issuer in response to a request. Distinguish explicitly from a credential (which is presented by a holder), from a JWT claim in the credential sense (which carries identity information), and from a VC (which is issued by an authority over a holder's presented identity). An attestation in this document's sense is an issuer-signed observation about public on-chain state.</t>
      <t><strong>Fact profile</strong> --- A multi-condition, multi-dimension attestation payload consisting of a structured collection of condition results, organized by dimension and covered by a single issuer signature. A fact profile is signed as one object under the algorithms of this document; its detailed format is not specified here. Used for richer wallet-state queries where a single boolean is insufficient. Distinguish explicitly from a "trust score" or "reputation rating" --- a fact profile contains observed evidence organized by dimension, not an opinion or aggregate.</t>
      <t><strong>Block reference</strong> --- Chain-specific freshness evidence included in every attestation. EVM chains MUST include block number and block timestamp. XRPL MUST include ledger index and, where available, ledger hash. Solana MUST include slot. Bitcoin MUST include block height and block hash (chain-tip evidence; see <xref target="merkleproofs"/> for Bitcoin-specific considerations). Any other chain family MUST include the reference the issuer documents for it.</t>
      <t><strong>Condition hash</strong> --- A SHA-256 hash of the JSON serialization of the evaluated condition, with member names sorted as <xref target="RFC8785"/> sorts them and no insignificant whitespace, represented as a <tt>0x</tt>-prefixed lowercase hexadecimal string, included in the response for tamper-evidence over the condition itself.</t>
    </section>
    <section anchor="architecture-overview">
      <name>Architecture Overview</name>
      <t>The primitive operates as a three-step pipeline: <strong>read -&gt; evaluate -&gt; sign</strong>.</t>
      <ol spacing="normal" type="1"><li>
          <t>The issuer receives a request specifying a wallet address, a chain identifier, and one or more conditions.</t>
        </li>
        <li>
          <t>The issuer reads canonical on-chain state for the specified wallet at or near the current chain tip. The issuer selects the block and reports it in the block reference.</t>
        </li>
        <li>
          <t>The issuer evaluates each condition against the observed state.</t>
        </li>
        <li>
          <t>The issuer signs the result and returns the attestation.</t>
        </li>
      </ol>
      <t>The evaluation is <strong>deterministic</strong>: the same condition evaluated against the same chain state at the same block produces the same per-condition result. A verifier can independently re-derive each result by reading the same chain state and applying the same condition logic, exactly where the block reference names the state that was read (an EVM block, an XRPL ledger) and to within the anchor where the reference is a tip marker or a floor, as the issuer documents per chain family. The attestation identifier, the <tt>attestedAt</tt> timestamp, and the signature value differ from one issuance to the next.</t>
      <t>The primitive is <strong>read-only</strong>: the issuer does not modify chain state, does not request a wallet signature from the holder, and does not custody any assets. The issuer is a passive reader of public chain state.</t>
      <t>For each chain referenced in the request, the issuer captures a block reference (block number and block timestamp for EVM chains; ledger index and, where available, ledger hash for XRPL; slot for Solana; block height and block hash for Bitcoin; for any other chain family, the reference the issuer documents) at attestation time. The block reference is carried inside each per-condition result and is therefore covered by the issuer's signature. If a required chain observation cannot be acquired, the issuer MUST NOT sign the attestation; see <xref target="refuse"/>.</t>
      <t>This document does not specify how the issuer reads chain state (RPC routing, indexer choice, archive node selection), how it manages signing keys (HSM, software, federated), or how it handles chain-specific edge cases (reorgs, finality, data availability). These are implementation concerns, deliberately left out of scope.</t>
    </section>
    <section anchor="request-shape">
      <name>Request Shape</name>
      <t>A request is a JSON object with the following structure:</t>
      <sourcecode type="json"><![CDATA[
{
  "wallet": "<address>",
  "conditions": [
    {
      "type": "<condition type>",
      "chainId": "<chain identifier>",
      "...": "<condition-specific parameters>"
    }
  ],
  "format": "jwt",
  "proof": "merkle"
}
]]></sourcecode>
      <t><strong><tt>wallet</tt></strong> --- A chain-specific address string. EVM addresses use the standard 0x-prefixed hexadecimal form. Solana addresses use base58. XRPL addresses use the r-address format. Bitcoin addresses MAY use any standard format (P2PKH, P2SH, bech32 / SegWit, bech32m / Taproot). Because one request can span several chains, an implementation MAY reserve <tt>wallet</tt> for one address family and carry the others in chain-specific members (e.g., <tt>solanaWallet</tt>, <tt>xrplWallet</tt>, <tt>bitcoinWallet</tt>).</t>
      <t><strong><tt>chainId</tt></strong> --- A chain identifier, carried by each condition whose type is not bound to a single chain, so that one request can span several chains. EVM chains use the numeric EIP-155 chain identifier (the reference part of a CAIP-2 identifier <xref target="CAIP-2"/>), expressible as integer (e.g., <tt>1</tt> for Ethereum mainnet) or string. Non-EVM chains use a string identifier naming the chain (for example <tt>solana</tt>, <tt>xrpl</tt>, <tt>bitcoin</tt>).</t>
      <t><strong><tt>conditions</tt></strong> --- A list of one or more condition objects. Each has a <tt>type</tt> field discriminating the condition category, and type-specific fields specifying the parameters of that category. This document does not enumerate all possible condition types. Common categories include token balance evaluation, NFT ownership evaluation, on-chain attestation reference (e.g., reference to an Ethereum Attestation Service entry), and identity-registry membership evaluation. Implementations MAY define additional condition types.</t>
      <t><strong><tt>format</tt></strong> --- OPTIONAL. When <tt>"jwt"</tt>, the response additionally carries the attestation as an RFC 7519 JWT (<xref target="jwt"/>). When omitted, the response is the JSON format of <xref target="response"/> alone.</t>
      <t><strong><tt>proof</tt></strong> --- OPTIONAL. Requests a cryptographic proof layer. When omitted, no proof is requested. Value: <tt>"merkle"</tt> (where supported by the underlying chain; returns <xref target="EIP-1186"/> Merkle storage proofs anchored to the block header).</t>
    </section>
    <section anchor="response">
      <name>Response Shape</name>
      <t>A response contains a signed attestation object together with the signature and key identifier. The signed attestation object has the following structure:</t>
      <sourcecode type="json"><![CDATA[
{
  "id": "<opaque attestation identifier>",
  "pass": true,
  "results": [
    {
      "condition": 0,
      "label": "<operator-provided label>",
      "type": "<condition type>",
      "chainId": "<chain identifier>",
      "met": true,
      "evaluatedCondition": { /* condition object */ },
      "conditionHash": "0x<sha256 of evaluatedCondition>",
      "blockNumber": "<chain-specific block reference>",
      "blockTimestamp": "<ISO 8601 timestamp>"
    }
  ],
  "attestedAt": "<ISO 8601 timestamp>"
}
]]></sourcecode>
      <t>The signature is computed over signed bytes derived from this object under the signing scheme that the transmitted <tt>kid</tt> selects (<xref target="signing"/>). The signature, the key identifier (<tt>kid</tt>), expiry timestamp (<tt>expiresAt</tt>), and any deployment-specific wrappers are transmitted alongside the signed object but are NOT part of the signed bytes. Nor is any member of the transmitted <tt>attestation</tt> object other than <tt>id</tt>, <tt>pass</tt>, <tt>results</tt>, and <tt>attestedAt</tt>: an issuer MAY add informational members there (for example, counts of met and unmet conditions), and they are unsigned. Note that the signed object does not in general name the wallet: the requester knows which wallet it asked about, but a party that receives a JSON-format attestation second-hand cannot bind it to a wallet from the signed bytes alone (some condition types carry the address inside <tt>evaluatedCondition</tt>). The JWT format names the wallet in its <tt>sub</tt> claim (<xref target="jwt"/>). A typical wire response carries the signed object plus these adjacent fields:</t>
      <sourcecode type="json"><![CDATA[
{
  "attestation": {
    /* signed object above */
    "expiresAt": "<ISO 8601 timestamp>"
  },
  "sig": "<base64 P1363 ECDSA signature>",
  "kid": "<key identifier for JWKS lookup>",
  "pqSig": "<base64 companion signature (optional)>",
  "pqKid": "<companion key identifier (optional)>"
}
]]></sourcecode>
      <t>Field semantics (signed object):</t>
      <t><strong><tt>id</tt></strong> --- An opaque attestation identifier. Implementation-specific format. Useful for correlation, logging, and replay detection. It is covered by the signature.</t>
      <t><strong><tt>pass</tt></strong> --- The aggregate result. Default semantics: logical AND of all per-condition <tt>met</tt> values. Operators MAY specify alternate combination logic in extensions.</t>
      <t><strong><tt>results</tt></strong> --- An array of per-condition result objects. Each MUST include the per-condition <tt>met</tt> boolean, the condition <tt>type</tt>, the verbatim <tt>evaluatedCondition</tt> (as it was evaluated, after any normalization), and the <tt>conditionHash</tt> (SHA-256 of the UTF-8 JSON serialization of <tt>evaluatedCondition</tt> with member names sorted as <xref target="RFC8785"/> sorts them (by UTF-16 code units) and no insignificant whitespace, as a <tt>0x</tt>-prefixed lowercase hexadecimal string). The <tt>evaluatedCondition</tt> object is flat: its member values are strings, numbers, booleans, or null, never objects or arrays, so that sorting its member names fully determines the serialization; strings and numbers are serialized as in the bare scheme of <xref target="signing-schemes"/>. A verifier MUST recompute <tt>conditionHash</tt> from the transmitted <tt>evaluatedCondition</tt> and MUST reject the result as altered if the two differ. Each result MUST include chain-specific freshness evidence: <tt>blockNumber</tt> and <tt>blockTimestamp</tt> for EVM chains; <tt>ledgerIndex</tt> and (where available) <tt>ledgerHash</tt> for XRPL; <tt>slot</tt> for Solana; <tt>blockHeight</tt> and <tt>blockHash</tt> for Bitcoin; for any other chain family, the reference the issuer documents for it. All such freshness fields are inside the signed <tt>results</tt> array and are therefore covered by the issuer's signature. Implementations MAY include additional informational fields (<tt>condition</tt> index, <tt>label</tt>, etc.) echoed from the request.</t>
      <t><strong><tt>attestedAt</tt></strong> --- ISO 8601 timestamp of when the attestation was signed.</t>
      <t>Field semantics (adjacent, unsigned):</t>
      <t><strong><tt>expiresAt</tt></strong> --- ISO 8601 timestamp of when the attestation expires. Verifiers SHOULD check expiry (step 6 of <xref target="verification-procedure"/>). Recommended default: thirty minutes after <tt>attestedAt</tt>. Implementations MAY use shorter expiries; longer expiries SHOULD be justified by the use case. <tt>expiresAt</tt> is transmitted with the attestation but is not part of the signed payload; verifiers concerned with tamper-evidence over the expiry SHOULD use the JWT format (<xref target="jwt"/>), where <tt>exp</tt> is a signed claim.</t>
      <t><strong><tt>kid</tt></strong> --- Key identifier (RFC 7517) for the public key used to sign this attestation. Verifiers use this to look up the appropriate public key in the issuer's JWKS. <tt>kid</tt> is transmitted alongside the signed payload but is not part of the signed bytes. The <tt>kid</tt> selects both the verification key and the signing scheme (<xref target="signing-schemes"/>); verifiers resolve it against the issuer's published JWKS as described in <xref target="jwks-discovery"/>. The JWT format embeds <tt>kid</tt> in the protected header where it is covered by the JWT signature.</t>
      <t><strong><tt>sig</tt></strong> --- The signature. Default: ECDSA P-256 signature over the signed bytes defined in <xref target="signing"/>, in P1363 (raw R || S) format, base64-encoded with the standard alphabet and padding (Section 4 of <xref target="RFC4648"/>).</t>
      <t><strong><tt>pqSig</tt></strong>, <strong><tt>pqKid</tt></strong> --- OPTIONAL. A companion signature under a second algorithm and the identifier of the key that produced it (<xref target="companion-signatures"/>). When present they are additive: <tt>sig</tt> and <tt>kid</tt> are unchanged by their presence, and a verifier that does not implement companion verification ignores them.</t>
      <t>The signature scope includes the <tt>evaluatedCondition</tt> and <tt>conditionHash</tt> fields nested inside <tt>results</tt>, ensuring the signature is tamper-evident over both the result and the condition that produced it.</t>
    </section>
    <section anchor="signing">
      <name>Signing Algorithm</name>
      <t>The default signing algorithm is <strong>ECDSA with P-256 curve and SHA-256 (ES256)</strong> per <xref target="RFC7518"/>.</t>
      <t>The signed bytes are produced under one of the signing schemes in <xref target="signing-schemes"/>. An issuer MAY additionally attach a companion signature under a second algorithm (<xref target="companion-signatures"/>).</t>
      <section anchor="signing-schemes">
        <name>Signing Schemes</name>
        <t>This document defines two signing schemes. The type of message a verifier holds is evident from its shape (an attestation of <xref target="response"/> carries <tt>pass</tt> and <tt>results</tt>; a fact profile does not); the <tt>kid</tt> transmitted with it selects both the verification key and the signing scheme. The issuer's documentation states, for each <tt>kid</tt> it publishes, which scheme that <tt>kid</tt> selects and, for the domain-separated scheme, the domain tag. Verifiers MUST determine the scheme from the <tt>kid</tt>, MUST NOT assume that an issuer uses a single scheme, and MUST NOT verify an attestation under any scheme other than the one its <tt>kid</tt> selects. A single public key MAY be published under more than one <tt>kid</tt>, so that one key serves more than one scheme. An issuer that signs more than one type of message under the domain-separated scheme MUST use a distinct domain tag for each type, and a verifier MUST NOT accept an attestation under a <tt>kid</tt> that the issuer documents for a different type of message.</t>
        <t>Signing schemes apply to the JSON format of <xref target="response"/>. In the JWT format (<xref target="jwt"/>) the signed bytes are the JWS Signing Input <xref target="RFC7515"/> of the JWT <xref target="RFC7519"/>, whatever scheme the <tt>kid</tt> would select in the JSON format; there the <tt>kid</tt> selects the verification key (and, through its JWKS entry, the algorithm) but no scheme.</t>
        <t><strong>Bare scheme.</strong> The signed bytes are the JSON serialization of an object carrying exactly the four members of the signed object, in the order <tt>id</tt>, <tt>pass</tt>, <tt>results</tt>, <tt>attestedAt</tt>, with no insignificant whitespace, encoded as UTF-8. Inside <tt>results</tt>, every member of every result object, including the members of <tt>evaluatedCondition</tt>, is serialized in the order in which it was transmitted, and array element order is preserved. Nothing inside the signed bytes of this scheme is re-sorted: the sorting of member names described in <xref target="response"/> applies only to the computation of <tt>conditionHash</tt>. Strings and numbers are serialized as the ECMAScript JSON.stringify function serializes them, which for well-formed Unicode strings and finite numbers is also the serialization of primitive values specified by <xref target="RFC8785"/>.</t>
        <t>The signed bytes are not transmitted as such; a verifier reconstructs them from the transmitted response. Under the bare scheme a verifier therefore MUST use a JSON parser that preserves the order of object members, and MUST omit from the reconstruction every member of the transmitted <tt>attestation</tt> object other than the four named above.</t>
        <t><strong>Domain-separated scheme.</strong> The signed bytes are the concatenation of (1) a domain tag, (2) a single line feed character (U+000A), and (3) the canonical serialization of a JSON object carrying the member <tt>v</tt> (the scheme version, the integer 2 for the scheme defined here) together with the four members of the signed object (<tt>id</tt>, <tt>pass</tt>, <tt>results</tt>, <tt>attestedAt</tt>). The canonical serialization is the JSON Canonicalization Scheme <xref target="RFC8785"/>: member names sorted at every level of nesting, array element order preserved, no insignificant whitespace, and strings and numbers serialized as that document specifies. (RFC 8785 requires well-formed Unicode strings; an issuer MUST NOT sign a string that is not.) Because the member names are sorted, a verifier needs no order-preserving parser under this scheme. The domain tag is an ASCII string, fixed by the issuer, that identifies the message type and the scheme version. It binds a signature to its message type, so that a signature produced over one type of message cannot be presented as a signature over another. An issuer using this scheme SHOULD carry numeric quantities inside the signed object (thresholds, amounts, ratios) as decimal strings and SHOULD confine JSON numbers to small integers, so that the signed bytes do not depend on any implementation's floating-point number formatting and can be reproduced exactly in any language. The signed bytes are encoded as UTF-8.</t>
        <t>Signed bytes are stable per <tt>kid</tt>. An issuer introducing a new scheme MUST do so under a new <tt>kid</tt> and MUST NOT change the signed bytes produced under a <tt>kid</tt> it has already published. Attestations signed under an earlier scheme therefore remain verifiable, unchanged, by verifiers deployed before the new scheme existed.</t>
      </section>
      <section anchor="signature-encoding">
        <name>Signature Encoding and Additional Algorithms</name>
        <t>The signature output is the raw P1363 (R || S) representation of the ECDSA signature, base64-encoded (88 characters for P-256). Implementations producing DER-encoded ECDSA signatures MUST convert to P1363 before transmission.</t>
      <t>Implementations MAY support additional algorithms (ES384, EdDSA / Ed25519). Algorithm selection is encoded in the JWKS entry indexed by <tt>kid</tt>. Verifiers MUST NOT verify a signature under an algorithm other than the one the selected JWKS entry implies (its <tt>alg</tt>, and the key type and curve it carries).</t>
      </section>
      <section anchor="companion-signatures">
        <name>Companion Signatures</name>
        <t>An issuer MAY attach to an attestation a companion signature produced under a second algorithm, for example a post-quantum signature algorithm. A companion signature is additive: it changes neither the signed object, nor the signed bytes defined in <xref target="signing-schemes"/>, nor the values of <tt>sig</tt> and <tt>kid</tt>. A verifier that does not implement companion verification ignores the companion fields and is otherwise unaffected.</t>
        <t>In the JSON format the companion is carried in two adjacent fields: <tt>pqSig</tt>, the companion signature, base64-encoded with the standard alphabet and padding (Section 4 of <xref target="RFC4648"/>), and <tt>pqKid</tt>, the key identifier of the companion key in the issuer's JWKS. The companion signature is computed over the concatenation of (1) a companion domain tag, (2) a single line feed character (U+000A), and (3) the exact signed bytes that the attestation's <tt>kid</tt> selects under <xref target="signing-schemes"/>, encoded as UTF-8. A verifier therefore reuses the bytes it has already reconstructed for the primary signature. The companion domain tag is distinct from any domain tag used by a signing scheme and is documented by the issuer for each <tt>pqKid</tt> it publishes.</t>
        <t>The companion algorithm defined by this document is ML-DSA-65 <xref target="FIPS204"/>. Signatures are produced as in Algorithm 2 of <xref target="FIPS204"/> (ML-DSA.Sign, not the pre-hash variant) with an empty context string; these are the parameters that <xref target="RFC9964"/> specifies for JOSE. The companion key is published in the issuer's JWKS (<xref target="jwks-discovery"/> of this document) as an <tt>AKP</tt> key, the key type defined by <xref target="RFC9964"/>.</t>
        <t>In the JWT format (<xref target="jwt"/>) the companion is a sibling token, <tt>pqJwt</tt>, returned beside the ES256 JWT: a JWS in compact serialization <xref target="RFC7515"/> whose protected header carries <tt>alg</tt> <tt>ML-DSA-65</tt> and the companion <tt>kid</tt>, and whose claims are the same as those of the ES256 JWT. No companion domain tag is used in the JWT format: <tt>pqJwt</tt> is verified over its own JWS signing input. A verifier that checks <tt>pqJwt</tt> MUST also confirm that its payload and the ES256 JWT's payload carry the identical claim set: the same member names and, for every member, a deeply equal JSON value (objects compared without regard to member order, arrays in order, numbers by value). Any difference, whether a changed value, a missing member, or an extra member on either side, makes the companion refuted. Byte-identical payload segments satisfy this trivially, but a verifier MUST NOT require byte-for-byte equality, since two serializers may order members differently. The companion then vouches for every claim a relying party reads from the ES256 JWT, and cannot be transplanted from another attestation.</t>
        <t>A verifier that implements companion verification MUST report the companion result as a verdict separate from the primary signature result, with one of four values: <em>verified</em> (transmitted, and verifies under the key that <tt>pqKid</tt> selects; in the JWT format, also carrying the same claim set as the ES256 JWT); <em>refuted</em> (transmitted, and either fails verification under that key or, in the JWT format, carries a different claim set); <em>absent</em> (no companion was transmitted, that is, no <tt>pqSig</tt> in the JSON format and no <tt>pqJwt</tt> in the JWT format); or <em>unverifiable</em> (transmitted, but the verifier could not check it). Unverifiable records a gap in the verifier's knowledge; refuted records positive evidence of alteration. A verifier determines the verdict by the first of these that applies:</t>
        <ol spacing="normal" type="1">
          <li><t>No companion was transmitted: absent.</t></li>
          <li><t>The attestation's own <tt>kid</tt> is missing, or is one the verifier has never met, so the signed bytes the companion covers cannot be reconstructed: unverifiable. A verifier MUST NOT substitute the signed bytes of another scheme for a <tt>kid</tt> it does not know, since that could only produce a false refutation.</t></li>
                    <li><t><tt>pqKid</tt> resolves to no JWKS entry, resolves to a key of the wrong type, or names a companion key the issuer documents for a different type of message; or <tt>pqSig</tt> is transmitted without <tt>pqKid</tt>; or the verifier has no implementation of the algorithm: unverifiable. A verifier applies no companion domain tag other than the one <tt>pqKid</tt> names, so a <tt>pqKid</tt> of the wrong type leaves it nothing it is entitled to check.</t></li>
          <li><t>Otherwise the companion signature is checked, and in the JWT format the claim sets are compared: verified or refuted. An attestation whose own <tt>kid</tt> the verifier knows but the issuer documents for a different type of message reaches this step with well-defined signed bytes, and its companion is checked against them; a relabelled artifact is therefore never unverifiable.</t></li>
        </ol>
        <t>Item 2, and the relabelling case of item 4, concern the signed bytes of the JSON format. In the JWT format the companion is a JWS over its own signing input and is bound to the ES256 JWT by its claim set, so it is evaluated whether or not the JWT's <tt>kid</tt> selects a key; a genuine <tt>pqJwt</tt> beside a JWT whose <tt>kid</tt> the verifier cannot place is reported verified while the JWT's own signature verdict is unverifiable.</t>
        <t>In the JWT format, a <tt>pqJwt</tt> that does not parse as a compact JWS, or whose protected header lacks either the companion algorithm or a <tt>kid</tt>, is malformed and is refuted, since the header is covered by the companion signature and the issuer always emits both; this rule is applied before item 3 of the list. A refuted companion MUST cause the attestation to be rejected. An unverifiable companion MUST NOT be treated as verified. Whether an absent or unverifiable companion causes rejection is a matter of verifier policy: a verifier MAY require a verified companion from a date of its own choosing, and it MUST judge that date by its own clock at the time of verification, never by a timestamp carried in the attestation. A verifier reading an attestation after the fact, as a record rather than for a live decision, MUST NOT refuse it for lacking a companion; it reports the absence.</t>
      </section>
    </section>
    <section anchor="jwks-discovery">
      <name>JWKS Discovery</name>
      <t>The issuer publishes its public key set at a discoverable HTTPS endpoint following <xref target="RFC7517"/>. A commonly used path is <tt>/.well-known/jwks.json</tt>, under the well-known URI prefix of <xref target="RFC8615"/>; this document does not register that path.</t>
      <t>Each JWKS entry MUST include <tt>kid</tt>, <tt>kty</tt>, and <tt>alg</tt>, together with the public key parameters that its key type requires: <tt>crv</tt>, <tt>x</tt>, and <tt>y</tt> for an elliptic curve key (<tt>kty</tt> <tt>EC</tt>, <xref target="RFC7518"/>); <tt>pub</tt> for an Algorithm Key Pair key (<tt>kty</tt> <tt>AKP</tt>, <xref target="RFC9964"/>); and correspondingly for any other key type. A JWKS MAY therefore hold keys of more than one key type, and verifiers MUST NOT require the parameters of one key type on an entry of another. Issuers MAY publish multiple active keys simultaneously, to support key rotation, more than one signing scheme (<xref target="signing-schemes"/>), or companion signatures (<xref target="companion-signatures"/>). An issuer adding entries of a new key type to an existing JWKS SHOULD append them after the existing entries, so that the position of every existing entry is unchanged for deployed verifiers.</t>
      <t>Key selection: verifiers MUST select the verification key by the transmitted <tt>kid</tt>, never by position in the JWKS and never by default. If the <tt>kid</tt> is missing, or resolves to no entry in the JWKS available to the verifier, the verifier MUST NOT accept the attestation and MUST NOT substitute any other key, including the first entry in the JWKS or a key built into the verifier. Such an attestation is <em>unverifiable</em>, which is distinct from <em>refuted</em>: a refuted attestation carries a signature that fails under the key its <tt>kid</tt> selects, whereas nothing has been shown about the signature of an unverifiable one. Verifiers SHOULD report the two outcomes distinctly. Neither outcome is a successful verification.</t>
      <t>Key rotation: when the issuer rotates signing keys, the new public key MUST be published in JWKS before the issuer signs any attestation with the corresponding private key. Retired public keys SHOULD remain published in JWKS for a duration at least equal to the longest possible outstanding attestation expiry (typically the default thirty-minute window). An issuer MAY instead retain every key it has ever published, under its original <tt>kid</tt>, so that an attestation kept as a record remains checkable for as long as anyone holds it; retention does not extend an attestation's validity, which its expiry still governs. Relying parties that keep attestations as records SHOULD keep a copy of the JWKS with them.</t>
      <t>Because JWKS documents are cached, by verifiers and by intermediaries, different verifiers can hold different versions of an issuer's JWKS for up to the cache lifetime after it changes. During that interval a verifier holding the earlier version will find that a newly introduced <tt>kid</tt> resolves to no entry; the attestation is then unverifiable, not refuted, as described above. A verifier that meets an unknown <tt>kid</tt> MAY refresh its copy of the JWKS and retry.</t>
      <t>Verifiers SHOULD cache the JWKS according to the HTTP cache headers the issuer publishes, and MAY be supplied with a copy of the JWKS out of band instead of fetching it. The cache lifetime an issuer sets bounds how quickly a newly published <tt>kid</tt> becomes visible to verifiers; an issuer SHOULD document it.</t>
    </section>
    <section anchor="jwt">
      <name>JWT Format</name>
      <t>When <tt>format</tt> is <tt>"jwt"</tt>, the response additionally carries the attestation as an RFC 7519 JWT, in a <tt>jwt</tt> member beside the members of the JSON format (<xref target="response"/>).</t>
      <t>Protected header: <tt>{"alg": "ES256", "typ": "JWT", "kid": "&lt;key id&gt;"}</tt> per <xref target="RFC7519"/>. The <tt>kid</tt> in the protected header is covered by the JWT signature.</t>
      <t>Standard claims:</t>
      <ul spacing="normal">
        <li>
          <t><tt>iss</tt> --- issuer identifier (e.g., the issuer's base URL)</t>
        </li>
        <li>
          <t><tt>sub</tt> --- the wallet address a condition in the request evaluated. When the conditions span several address families, the issuer documents which family's wallet <tt>sub</tt> names; a wallet the request supplied but no condition evaluated is not named.</t>
        </li>
        <li>
          <t><tt>jti</tt> --- JWT identifier (typically the attestation <tt>id</tt>)</t>
        </li>
        <li>
          <t><tt>iat</tt> --- issuance timestamp (Unix seconds)</t>
        </li>
        <li>
          <t><tt>exp</tt> --- expiry timestamp (Unix seconds)</t>
        </li>
      </ul>
      <t>Custom claims carrying the attestation payload:</t>
      <ul spacing="normal">
        <li>
          <t><tt>pass</tt> --- aggregate result</t>
        </li>
        <li>
          <t><tt>results</tt> --- per-condition result array</t>
        </li>
        <li>
          <t><tt>conditionHash</tt> --- array of per-condition <tt>conditionHash</tt> values, in <tt>results</tt> order</t>
        </li>
        <li>
          <t><tt>blockNumber</tt> --- chain block reference (typically the first result's <tt>blockNumber</tt>, where chain-homogeneous)</t>
        </li>
        <li>
          <t><tt>blockTimestamp</tt> --- chain block timestamp (typically the first result's <tt>blockTimestamp</tt>)</t>
        </li>
      </ul>
      <t>The JWT signature is computed by the issuer using the corresponding private key. Verifiers verify using any standard JWT library that supports ES256 and JWKS discovery. When the issuer attaches a companion signature, the JWT format carries it as the sibling token <tt>pqJwt</tt> (<xref target="companion-signatures"/>); the ES256 JWT is unchanged by its presence.</t>
      <t>The JWT format provides signed coverage of <tt>exp</tt> and <tt>kid</tt> (via standard claim and protected header, respectively); deployments requiring tamper-evident expiry or signed key-identifier binding SHOULD use the JWT format rather than the JSON format described in <xref target="response"/>.</t>
      <t>A response in the JWT format carries the token beside the JSON-format members, so a verifier may hold both. A relying party that reads claims from the JWT MUST have verified the JWT itself; verifying the JSON-format attestation beside it is not a substitute, because the two are separately signed. A verifier handed both SHOULD verify the JWT as well as the attestation, and report the JWT's outcome as its own verdict: its signature under the same <tt>kid</tt> as the response, its <tt>results</tt> under step 4 of <xref target="verification-procedure"/>, its companion under <xref target="companion-signatures"/>, and that it is the token of this attestation (its <tt>kid</tt> equals the response's <tt>kid</tt>, <tt>jti</tt> equals <tt>id</tt>, and <tt>pass</tt>, <tt>results</tt>, and <tt>exp</tt> equal the attestation's <tt>pass</tt>, <tt>results</tt>, and <tt>expiresAt</tt> truncated to whole seconds). A transmitted JWT that fails any of these rejects the response: a relying party cannot tell which of the two it will be shown. The correspondence check does not cover <tt>sub</tt>, which the JSON-format attestation does not carry; the wallet named in <tt>sub</tt> is vouched for by the JWT's own signature alone. A JWT MAY also be presented on its own; it is then a complete attestation, verified as <xref target="verification-procedure"/> describes for the JWT format, without the correspondence check.</t>
    </section>
    <section anchor="merkleproofs">
      <name>Optional Merkle Proofs</name>
      <t>When <tt>proof</tt> is <tt>"merkle"</tt> and the underlying chain supports Merkle storage proofs (typically EVM chains for storage-slot-mappable conditions), the response MAY include <xref target="EIP-1186"/> Merkle storage proofs anchored to the block header for each per-condition result.</t>
      <t>Merkle proofs permit trustless verification: a verifier can independently check the Merkle proof against the block reference without trusting the issuer's evaluation. When a Merkle proof is included (<tt>proof.available</tt> is <tt>true</tt> in the per-result proof object), the proof object MUST include the block reference and the proof nodes needed to check the proven value against that block's state root (for a storage-slot proof, including the storage slot path).</t>
      <t>Not all conditions or chains support Merkle proofs. Implementations MUST indicate proof availability per result and MAY return <tt>proof.available: false</tt> with a <tt>reason</tt> field when proof generation is unsupported for the condition or chain (e.g., NFT ownership conditions, non-storage-slot-mappable conditions, non-EVM chains).</t>
      <t><strong>Privacy trade-off:</strong> Merkle proofs reveal raw on-chain values (e.g., the actual token balance). When no proof is requested, the wallet's balance is not returned. Operators MUST consider whether the trustlessness benefit of Merkle proofs is worth the privacy cost in each deployment.</t>
      <t>Bitcoin, XRPL, and Solana have different state-proof models and may not support Merkle proofs in the <xref target="EIP-1186"/> sense. Implementations targeting those chains MAY define chain-appropriate proof shapes or MAY return <tt>proof.available: false</tt>.</t>
      <t>For Bitcoin specifically: the UTXO model does not permit reproducible balance-at-block queries. Bitcoin implementations anchor the attestation to the chain tip (block height + block hash) at observation time as the cryptographically-bound freshness evidence appropriate to the UTXO model. The <tt>blockHeight</tt> and <tt>blockHash</tt> are covered by the issuer's signature inside the result and bind the observation to a recent chain tip rather than to a specific block's historical state. Issuers MUST refuse to sign Bitcoin attestations when the chain-tip lookup fails; see <xref target="refuse"/>.</t>
    </section>
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <section anchor="single-issuer">
        <name>Single-Issuer Architecture</name>
        <t>This document describes a single-issuer attestation primitive. The issuer is the sole signer of attestations within its declared scope. This is a deliberate design choice for several reasons:</t>
        <ol spacing="normal" type="1"><li>
            <t><strong>Deterministic signature surface.</strong> Verifiers can independently re-derive an attestation's results from chain state without resolving issuer trust hierarchies, federation policies, or multi-party signing logic.</t>
          </li>
          <li>
            <t><strong>No re-signing or wrapping.</strong> This document defines no re-signing, aggregation under a wrapper signature, or repackaging of attestations by any party other than the issuer; a wrapper or aggregate signature confers no authority, and a verifier accepts no signature other than the issuer's own (its primary signature, and any companion or JWT signature the issuer itself produced, <xref target="companion-signatures"/> and <xref target="jwt"/>). The original observation's signature is the authoritative artifact end-to-end.</t>
          </li>
          <li>
            <t><strong>Unambiguous attribution.</strong> For audit, dispute resolution, and accountability, the issuer is unambiguously identified by the JWKS endpoint and signing key.</t>
          </li>
        </ol>
        <t>Multi-issuer federation, key delegation, cross-issuer attestation aggregation, and threshold signing are explicitly out of scope for this document. Future Internet-Drafts MAY define such mechanisms as separate primitives, but they are distinct from the single-issuer wallet-state attestation defined here.</t>
      </section>
      <section anchor="freshness-anchors-and-replay-considerations">
        <name>Freshness Anchors and Replay Considerations</name>
        <t>The cryptographically-bound freshness anchors are the block reference inside each per-condition result and the <tt>attestedAt</tt> timestamp in the outer signed payload (the <tt>iat</tt> claim in the JWT format). Both are covered by the issuer's signature. Verifiers requiring strict tamper-evident freshness derive their policy from these signed anchors (e.g., "reject if <tt>attestedAt</tt> is older than thirty minutes", or "reject if <tt>blockTimestamp</tt> is older than sixty seconds").</t>
        <t>The <tt>expiresAt</tt> field is an issuer-provided TTL hint, transmitted alongside the attestation but outside the signed payload in the JSON format described in <xref target="response"/>. Because <tt>expiresAt</tt> is unsigned in the JSON format, a verifier that relies on it SHOULD confirm that it is no later than the signed <tt>attestedAt</tt> plus the issuer's documented validity window, and SHOULD reject the attestation as altered otherwise. Deployments requiring tamper-evident expiry SHOULD use the JWT format described in <xref target="jwt"/>, where <tt>exp</tt> is a signed claim.</t>
        <t>For replay-sensitive deployments, implementations MAY include a nonce binding or audience binding in custom JWT claims (when the JWT format is used). This document does not normatively specify these extensions; deployments requiring replay protection beyond freshness checking SHOULD define them explicitly in their integration profile.</t>
      </section>
      <section anchor="refuse">
        <name>Refuse-on-Observation-Failure</name>
        <t>Issuers MUST refuse to sign an attestation when any required observation fails: chain-tip lookup, balance query, on-chain attestation registry lookup, or any other data source the condition depends on. A successfully signed attestation therefore implies that all underlying observations were acquired against a current chain reference at attestation time. Partial or degraded observations MUST NOT be signed.</t>
        <t>Verifiers MAY cross-check the included block reference against an independent chain source if the deployment requires it.</t>
      </section>
      <section anchor="what-this-primitive-does-not-protect-against">
        <name>What This Primitive Does Not Protect Against</name>
        <ul spacing="normal">
          <li>
            <t><strong>Chain-level reorganizations after attestation.</strong> If a chain reorgs after an attestation has been signed, the historical state observed in the attestation may no longer be canonical. Operators handling this concern SHOULD use chains with strong finality, SHOULD use sufficiently old block references, or SHOULD include reorg-handling logic in their verification flow.</t>
          </li>
          <li>
            <t><strong>Off-chain state changes.</strong> This primitive observes on-chain state only. It does not attest to off-chain identity, off-chain agreements, or off-chain commitments.</t>
          </li>
          <li>
            <t><strong>Malicious issuer.</strong> A compromised or malicious issuer could sign false attestations. This is mitigated by: (a) JWKS publication of signing keys, (b) optional Merkle proofs for trustless verification (where supported), (c) condition hashes for tamper-evidence over the evaluated logic, and (d) deterministic re-derivation by independent verifiers.</t>
          </li>
        </ul>
      </section>
      <section anchor="algorithm-agility">
        <name>Algorithm Agility</name>
        <t>The default ES256 algorithm is widely supported and provides 128-bit security. Implementations supporting additional algorithms MUST include the algorithm in the JWKS entry indexed by <tt>kid</tt>. Verifiers MUST check the algorithm before verifying.</t>
        <t>Algorithm migration: when the cryptographic landscape changes (e.g., post-quantum migration), this document provides two paths, and an issuer can use either or both. <em>Rotation</em>: the issuer publishes a key for the new algorithm in JWKS under a new <tt>kid</tt> and begins signing with it; verifiers that do not support the new algorithm can no longer verify new attestations. <em>Composition</em>: the issuer continues to sign with the existing algorithm and attaches a companion signature under the new algorithm (<xref target="companion-signatures"/>). Composition leaves the existing signature, its signed bytes, and its key unchanged, so deployed verifiers that ignore unrecognized members keep working, and each verifier adopts the new algorithm on its own schedule by choosing the date from which it requires a verified companion. Composition is an application of post-quantum/traditional hybrid signing, for which <xref target="RFC9794"/> gives terminology. In the JSON format a companion signature covers the same signed bytes as the primary signature, so an attacker must defeat both algorithms to forge an attestation that is accepted by a relying party requiring both signatures to verify. In the JWT format the two tokens are signed separately, and the same property holds when the verifier requires their claim sets to be identical, as <xref target="companion-signatures"/> requires: an attacker who defeats only the primary algorithm then cannot alter any claim without the companion refuting it. Under either path the protocol surface remains stable.</t>
      </section>
    </section>
    <section anchor="privacy-considerations">
      <name>Privacy Considerations</name>
      <section anchor="boolean-by-default">
        <name>Boolean by Default</name>
        <t>The default response mode returns the result of condition evaluation, not the wallet's balance. A verifier learns whether a wallet satisfies a condition (e.g., "holds at least 100 USDC") without being told the balance itself. An issuer MAY return public reference values that the evaluation used (for example, a token's total supply); it SHOULD NOT return a value from which the wallet's balance can be derived unless the caller requested proof mode.</t>
        <t>This is the privacy-preserving default and SHOULD be used unless specific deployment requirements justify a less-private mode.</t>
      </section>
      <section anchor="merkle-mode-trade-off">
        <name>Merkle Mode Trade-Off</name>
        <t>When <tt>proof</tt> is <tt>"merkle"</tt>, raw on-chain values are revealed for trustless verification. Operators MUST consider the privacy implications of revealing actual balances or asset holdings in each deployment.</t>
      </section>
      <section anchor="no-pii">
        <name>No Identity Attributes</name>
        <t>The primitive handles no identity attributes: no name, document, contact, or account data is collected, signed, or transmitted. The one identifier it handles is the wallet address (<xref target="wallet-address-handling"/>), which is public on the chain but persistent, and so may be personal data where it can be linked to a natural person. The signed output is a boolean about a condition; where the condition itself concerns a person (for example, a KYC or jurisdiction attestation issued by a third party), the boolean relates to that person even though no attribute is disclosed, and deployers should handle it as information about that person. The results echo operator-provided text (<tt>label</tt>, and the condition parameters in <tt>evaluatedCondition</tt>); operators SHOULD NOT place identity attributes in them.</t>
      </section>
      <section anchor="wallet-address-handling">
        <name>Wallet Address Handling</name>
        <t>The wallet address is the subject of the query but is public information on the underlying chain. Issuers SHOULD NOT log or retain wallet addresses beyond what is required for service delivery, abuse prevention, and operational integrity. Implementations targeting privacy-conscious deployments SHOULD document their address-handling policy.</t>
      </section>
      <section anchor="awareness-of-observation">
        <name>Awareness of Observation</name>
        <t>This primitive observes public chain state without holder participation. The holder is not required to consent to nor be aware of any individual attestation, consistent with the public nature of blockchain state. Deployers operating in regulated or consumer-facing contexts SHOULD consider whether their deployment context requires additional transparency mechanisms; such mechanisms are out of scope for this document.</t>
      </section>
    </section>
    <section anchor="verification-procedure">
      <name>Verification Procedure</name>
      <t>This section collects the checks of the preceding sections into one procedure for the JSON format, naming the verdict at each branch. Steps 1 to 4 are REQUIRED; steps 5 and 6 are RECOMMENDED; step 7 is OPTIONAL. A verifier SHOULD report each step's outcome separately rather than as one boolean, so that a relying party can apply its own policy to the recommended and optional steps.</t>
      <ol spacing="normal" type="1">
        <li>
          <t><strong>Select the key and scheme.</strong> Read the transmitted <tt>kid</tt>. If it is absent, or resolves to no entry in the JWKS available to the verifier, stop: the signature verdict is <em>unverifiable</em> (<xref target="jwks-discovery"/>). Do not substitute any other key. If the <tt>kid</tt> resolves but the issuer documents it for a different type of message, stop: the artifact is relabelled, and the signature verdict is <em>refuted</em>. Otherwise the <tt>kid</tt> selects the key, the algorithm (the JWKS entry's <tt>alg</tt> and key type), and the signing scheme with its domain tag (<xref target="signing-schemes"/>).</t>
        </li>
        <li>
          <t><strong>Reconstruct the signed bytes</strong> from the transmitted <tt>attestation</tt> object under the selected scheme: under the bare scheme, exactly the members <tt>id</tt>, <tt>pass</tt>, <tt>results</tt>, and <tt>attestedAt</tt> in that order, every nested member in transmitted order, no whitespace, UTF-8; under the domain-separated scheme, the domain tag, a line feed, and the RFC 8785 serialization of the object <tt>{v, id, pass, results, attestedAt}</tt> with <tt>v</tt> equal to the scheme version. Members of the transmitted object other than the four named are not part of the signed bytes.</t>
        </li>
        <li>
          <t><strong>Verify the primary signature</strong>: decode <tt>sig</tt> (standard base64, P1363), and verify it under the selected algorithm over the signed bytes. Failure is <em>refuted</em>; the attestation is rejected.</t>
        </li>
        <li>
          <t><strong>Recompute each condition hash</strong>: for every result, serialize <tt>evaluatedCondition</tt> with member names sorted and no whitespace (<xref target="response"/>), hash it, and compare with the transmitted <tt>conditionHash</tt>. A mismatch marks the result as altered; the attestation is rejected.</t>
        </li>
        <li>
          <t><strong>Check freshness</strong> against the signed anchors (<tt>attestedAt</tt> and each result's block reference) under the verifier's own policy (<xref target="freshness-anchors-and-replay-considerations"/>), and reject identifiers already seen if replay protection is required.</t>
        </li>
        <li>
          <t><strong>Check expiry</strong>: reject if the current time is past <tt>expiresAt</tt>, and, because <tt>expiresAt</tt> is unsigned in this format, reject as altered an <tt>expiresAt</tt> later than the signed <tt>attestedAt</tt> plus the issuer's documented validity window (<xref target="freshness-anchors-and-replay-considerations"/>). A verifier MAY allow a small clock skew.</t>
        </li>
        <li>
          <t><strong>Check the companion</strong>, if one is transmitted, in the order given in <xref target="companion-signatures"/>, and report one of verified, refuted, absent, or unverifiable as its own verdict. Refuted rejects the attestation; absent and unverifiable are subject to the verifier's own policy.</t>
        </li>
      </ol>
      <t>After a stop at step 1, the verifier reports the signature verdict it reached and the companion verdict of <xref target="companion-signatures"/>; steps 4 to 6 need no key: step 4 MUST, and steps 5 and 6 SHOULD, still be performed and reported; a step the verifier did not perform is reported as not performed, never as passed; a verifier whose report has no separate state for an unperformed step reports it as failed with a reason that says it was not performed. The attestation is rejected whenever the signature verdict is not verified.</t>
      <t>In the JWT format, steps 1 to 3 are performed by a JWS library over the token's own signing input, with the library pinned to the algorithm the selected JWKS entry implies rather than to the token's own <tt>alg</tt> header (the <tt>kid</tt> still selects the key, and step 1's rules for a missing, unknown, or wrong-type <tt>kid</tt> still apply), step 4 applies to the <tt>results</tt> claim, expiry is the signed <tt>exp</tt> claim, and the companion is <tt>pqJwt</tt> bound by the full claim set (<xref target="companion-signatures"/>). A verifier holding both formats of the same attestation also verifies the token beside the attestation as <xref target="jwt"/> describes.</t>
      <t>Issuers SHOULD publish test vectors covering, at least: a genuine attestation under each scheme they use; a tampered condition, a tampered signature, and a tampered condition hash; a missing and an unknown <tt>kid</tt>; and, where companions are used, a genuine, a tampered, and a mislabelled companion. A verifier that reproduces the published verdicts on such a set has evidence of interoperability that reading this document alone cannot give.</t>
    </section>
    <section anchor="whatisnot">
      <name>What This Is Not</name>
      <t>This section explicitly enumerates adjacent primitives that wallet state attestation is <strong>not</strong>, to prevent conflation:</t>
      <ul spacing="normal">
        <li>
          <t><strong>Not a Verifiable Credential (W3C VC) <xref target="VC-DATA-MODEL"/>.</strong> VCs are issued by an authority, presented by a holder, and verified against the holder's presented bytes. Wallet state attestations are pulled by the issuer from public chain state without holder presentation.</t>
        </li>
        <li>
          <t><strong>Not an mDoc or eIDAS attestation.</strong> mDocs (ISO/IEC 18013-5) are pre-issued, batched, and stored by the holder for later presentation. Wallet state attestations are issued on demand, are short-lived, and are not stored by the holder.</t>
        </li>
        <li>
          <t><strong>Not a SAML assertion or OIDC ID token.</strong> These primitives carry identity claims about a user. Wallet state attestations carry observations about wallet state, with no identity attribute (<xref target="no-pii"/>).</t>
        </li>
        <li>
          <t><strong>Not a reputation score, trust score, or credit rating.</strong> A wallet state attestation contains observed facts (boolean results, optionally with structured fact profiles). It does not contain opinions, scores, ratings, or aggregated judgments. A separate primitive built on top of wallet state attestations might produce a score; this primitive does not.</t>
        </li>
        <li>
          <t><strong>Not a settlement attestation, delivery attestation, or transaction proof.</strong> Wallet state attestation is read-only state observation at observation time. It does not attest to action confirmation, transaction completion, or delivery of goods or services.</t>
        </li>
        <li>
          <t><strong>Not an oracle in the price-feed sense.</strong> This primitive observes specific wallet state on demand for a specific request; it does not publish continuous data streams or aggregate market data.</t>
        </li>
      </ul>
      <t>These distinctions are essential to keeping wallet state attestation as a distinct primitive in the agent-commerce, condition-based-access, and verification ecosystems.</t>
    </section>
    <section anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions.</t>
    </section>
  </middle>
  <back>
    <references anchor="sec-combined-references">
      <name>References</name>
      <references anchor="sec-normative-references">
        <name>Normative References</name>
        <reference anchor="RFC4648" target="https://www.rfc-editor.org/rfc/rfc4648">
          <front>
            <title>The Base16, Base32, and Base64 Data Encodings</title>
            <author fullname="S. Josefsson" initials="S." surname="Josefsson"/>
            <date month="October" year="2006"/>
          </front>
          <seriesInfo name="RFC" value="4648"/>
          <seriesInfo name="DOI" value="10.17487/RFC4648"/>
        </reference>
        <reference anchor="RFC7515" target="https://www.rfc-editor.org/rfc/rfc7515">
          <front>
            <title>JSON Web Signature (JWS)</title>
            <author fullname="M. Jones" initials="M." surname="Jones"/>
            <author fullname="J. Bradley" initials="J." surname="Bradley"/>
            <author fullname="N. Sakimura" initials="N." surname="Sakimura"/>
            <date month="May" year="2015"/>
          </front>
          <seriesInfo name="RFC" value="7515"/>
          <seriesInfo name="DOI" value="10.17487/RFC7515"/>
        </reference>
        <reference anchor="RFC8785">
          <front>
            <title>JSON Canonicalization Scheme (JCS)</title>
            <author fullname="A. Rundgren" initials="A." surname="Rundgren"/>
            <author fullname="B. Jordan" initials="B." surname="Jordan"/>
            <author fullname="S. Erdtman" initials="S." surname="Erdtman"/>
            <date month="June" year="2020"/>
            <abstract>
              <t>Cryptographic operations like hashing and signing need the data to be expressed in an invariant format so that the operations are reliably repeatable. One way to address this is to create a canonical representation of the data. Canonicalization also permits data to be exchanged in its original form on the "wire" while cryptographic operations performed on the canonicalized counterpart of the data in the producer and consumer endpoints generate consistent results.</t>
              <t>This document describes the JSON Canonicalization Scheme (JCS). This specification defines how to create a canonical representation of JSON data by building on the strict serialization methods for JSON primitives defined by ECMAScript, constraining JSON data to the Internet JSON (I-JSON) subset, and by using deterministic property sorting.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="8785"/>
          <seriesInfo name="DOI" value="10.17487/RFC8785"/>
        </reference>
        <reference anchor="FIPS204" target="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf">
          <front>
            <title>Module-Lattice-Based Digital Signature Standard</title>
            <author>
              <organization>National Institute of Standards and Technology</organization>
            </author>
            <date month="August" year="2024"/>
          </front>
          <seriesInfo name="NIST FIPS" value="204"/>
          <seriesInfo name="DOI" value="10.6028/NIST.FIPS.204"/>
        </reference>
        <reference anchor="RFC9964" target="https://www.rfc-editor.org/rfc/rfc9964">
          <front>
            <title>ML-DSA for JSON Object Signing and Encryption (JOSE) and CBOR Object Signing and Encryption (COSE)</title>
            <author fullname="M. Prorock" initials="M." surname="Prorock"/>
            <author fullname="O. Steele" initials="O." surname="Steele"/>
            <date month="May" year="2026"/>
          </front>
          <seriesInfo name="RFC" value="9964"/>
          <seriesInfo name="DOI" value="10.17487/RFC9964"/>
        </reference>
        <reference anchor="RFC2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author fullname="S. Bradner" initials="S." surname="Bradner"/>
            <date month="March" year="1997"/>
            <abstract>
              <t>In many standards track documents several words are used to signify the requirements in the specification. These words are often capitalized. This document defines these words as they should be interpreted in IETF documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>
        <reference anchor="RFC7517">
          <front>
            <title>JSON Web Key (JWK)</title>
            <author fullname="M. Jones" initials="M." surname="Jones"/>
            <date month="May" year="2015"/>
            <abstract>
              <t>A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. This specification also defines a JWK Set JSON data structure that represents a set of JWKs. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON Web Algorithms (JWA) specification and IANA registries established by that specification.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="7517"/>
          <seriesInfo name="DOI" value="10.17487/RFC7517"/>
        </reference>
        <reference anchor="RFC7518">
          <front>
            <title>JSON Web Algorithms (JWA)</title>
            <author fullname="M. Jones" initials="M." surname="Jones"/>
            <date month="May" year="2015"/>
            <abstract>
              <t>This specification registers cryptographic algorithms and identifiers to be used with the JSON Web Signature (JWS), JSON Web Encryption (JWE), and JSON Web Key (JWK) specifications. It defines several IANA registries for these identifiers.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="7518"/>
          <seriesInfo name="DOI" value="10.17487/RFC7518"/>
        </reference>
        <reference anchor="RFC7519">
          <front>
            <title>JSON Web Token (JWT)</title>
            <author fullname="M. Jones" initials="M." surname="Jones"/>
            <author fullname="J. Bradley" initials="J." surname="Bradley"/>
            <author fullname="N. Sakimura" initials="N." surname="Sakimura"/>
            <date month="May" year="2015"/>
            <abstract>
              <t>JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="7519"/>
          <seriesInfo name="DOI" value="10.17487/RFC7519"/>
        </reference>
        <reference anchor="RFC8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author fullname="B. Leiba" initials="B." surname="Leiba"/>
            <date month="May" year="2017"/>
            <abstract>
              <t>RFC 2119 specifies common key words that may be used in protocol specifications. This document aims to reduce the ambiguity by clarifying that only UPPERCASE usage of the key words have the defined special meanings.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>
      </references>
      <references anchor="sec-informative-references">
        <name>Informative References</name>
        <reference anchor="RFC8615" target="https://www.rfc-editor.org/rfc/rfc8615">
          <front>
            <title>Well-Known Uniform Resource Identifiers (URIs)</title>
            <author fullname="M. Nottingham" initials="M." surname="Nottingham"/>
            <date month="May" year="2019"/>
          </front>
          <seriesInfo name="RFC" value="8615"/>
          <seriesInfo name="DOI" value="10.17487/RFC8615"/>
        </reference>
        <reference anchor="RFC9794" target="https://www.rfc-editor.org/rfc/rfc9794">
          <front>
            <title>Terminology for Post-Quantum Traditional Hybrid Schemes</title>
            <author fullname="F. Driscoll" initials="F." surname="Driscoll"/>
            <author fullname="M. Parsons" initials="M." surname="Parsons"/>
            <author fullname="B. Hale" initials="B." surname="Hale"/>
            <date month="June" year="2025"/>
          </front>
          <seriesInfo name="RFC" value="9794"/>
          <seriesInfo name="DOI" value="10.17487/RFC9794"/>
        </reference>
        <reference anchor="RFC6749">
          <front>
            <title>The OAuth 2.0 Authorization Framework</title>
            <author fullname="D. Hardt" initials="D." role="editor" surname="Hardt"/>
            <date month="October" year="2012"/>
            <abstract>
              <t>The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf. This specification replaces and obsoletes the OAuth 1.0 protocol described in RFC 5849. [STANDARDS-TRACK]</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="6749"/>
          <seriesInfo name="DOI" value="10.17487/RFC6749"/>
        </reference>
        <reference anchor="CAIP-2" target="https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md">
          <front>
            <title>Chain Agnostic Improvement Proposal 2: Blockchain ID Specification</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="EIP-1186" target="https://eips.ethereum.org/EIPS/eip-1186">
          <front>
            <title>Ethereum Improvement Proposal 1186: RPC-Method to get Merkle Proofs</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="OIDC-CORE" target="https://openid.net/specs/openid-connect-core-1_0.html">
          <front>
            <title>OpenID Connect Core 1.0</title>
            <author initials="N." surname="Sakimura">
              <organization/>
            </author>
            <author initials="J." surname="Bradley">
              <organization/>
            </author>
            <author initials="M." surname="Jones">
              <organization/>
            </author>
            <author initials="B." surname="de Medeiros">
              <organization/>
            </author>
            <author initials="C." surname="Mortimore">
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="VC-DATA-MODEL" target="https://www.w3.org/TR/vc-data-model-2.0/">
          <front>
            <title>Verifiable Credentials Data Model</title>
            <author initials="M." surname="Sporny">
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="KCP-0004" target="https://github.com/Cantara/knowledge-context-protocol/blob/main/RFC-0004-Trust-and-Compliance.md">
          <front>
            <title>Knowledge Context Protocol RFC-0004: Trust, Provenance, and Compliance</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="VI-ENVIRONMENT" target="https://datatracker.ietf.org/doc/draft-borthwick-msebenzi-environment-state/">
          <front>
            <title>Verifiable Intent --- environment.* Constraint Family</title>
            <author initials="D." surname="Borthwick">
              <organization/>
            </author>
            <author initials="M." surname="Msebenzi">
              <organization/>
            </author>
            <date year="2026" month="May"/>
          </front>
        </reference>
        <reference anchor="AGV" target="https://github.com/aeoess/agent-governance-vocabulary">
          <front>
            <title>Agent Governance Vocabulary</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="OATR" target="https://github.com/FransDevelopment/open-agent-trust-registry">
          <front>
            <title>Open Agent Trust Registry</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="ERC-8210" target="https://ethereum-magicians.org/t/erc-8210-agent-assurance/28097">
          <front>
            <title>Draft Ethereum Improvement Proposal 8210: Agent Assurance Protocol</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
        <reference anchor="UCP-IDENTITY" target="https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/common/identity-linking/index.md">
          <front>
            <title>Universal Commerce Protocol: Identity Linking</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
      </references>
    </references>
    <section anchor="changes" numbered="false">
      <name>Changes from -00</name>
      <t>This revision extends the format in two ways, restates the request shape, and states the existing format more precisely. Nothing an issuer signs changes: the signature over every attestation issued under -00 verifies under this revision exactly as before. Where a verifier built from this revision behaves differently from one built from -00, the difference is listed in the last two items or, for the added scheme and the correspondence check, in the item that introduces it.</t>
      <ul spacing="normal">
        <li>
          <t><xref target="signing"/> now has subsections and defines two signing schemes selected by <tt>kid</tt>: the bare scheme and a domain-separated scheme based on <xref target="RFC8785"/>. It states that signed bytes are stable per <tt>kid</tt>, that one key may be published under several <tt>kid</tt> values, that the schemes apply to the JSON format, that the signed bytes are reconstructed from the transmitted object under the bare scheme with every member in transmitted order, and that sorting of member names applies to <tt>conditionHash</tt> alone.</t>
        </li>
        <li>
          <t>New <xref target="companion-signatures"/> defines optional companion signatures under a second algorithm (ML-DSA-65), carried as <tt>pqSig</tt> and <tt>pqKid</tt> in the JSON format and as <tt>pqJwt</tt> in the JWT format, bound in the JWT format by the full claim set, with four reported verdicts and the order in which they are determined. <xref target="algorithm-agility"/> describes composition alongside rotation as a path for algorithm migration.</t>
        </li>
        <li>
          <t>New <xref target="verification-procedure"/> collects the checks into one numbered procedure, names the verdict at each branch, states what is reported after a stop, maps the procedure onto the JWT format, and recommends that issuers publish test vectors.</t>
        </li>
        <li>
          <t><xref target="jwks-discovery"/> states the required JWK members per key type, so that a JWKS can hold elliptic curve and AKP keys together; the key selection rule (by <tt>kid</tt>, never by position or default; a missing or unknown <tt>kid</tt> is unverifiable, reported distinctly from refuted); that an issuer may retain published keys permanently; guidance on cached JWKS documents, including that a verifier MAY hold a copy of the JWKS supplied out of band and MAY refresh and retry on an unknown <tt>kid</tt>; and that new key types are appended after existing entries. Caching is stated in terms of the issuer's published headers, without a figure.</t>
        </li>
        <li>
          <t><xref target="response"/> states that members of the transmitted <tt>attestation</tt> object other than the four signed members are unsigned, that the signed object does not in general name the wallet, that <tt>evaluatedCondition</tt> is flat, the base64 alphabet, and the serialization used for <tt>conditionHash</tt>. <xref target="request-shape"/>: <tt>chainId</tt> is carried by each condition; <tt>format</tt> and <tt>proof</tt> are top-level members; the chain-family lists are open-ended. <xref target="jwt"/>: the JWT is carried beside the JSON format; <tt>sub</tt> is the wallet a condition evaluated; a relying party that reads claims from the JWT verifies the JWT itself; a correspondence check between the two formats is given.</t>
        </li>
        <li>
          <t>Statements made more precise: a fact profile is covered by a single signature; the XRPL ledger hash is included where available; the evaluation, rather than the attestation, is deterministic, and re-derivation is exact where the block reference names the state read; the issuer selects the anchored block; the contents of a proof object are stated for proofs that are not storage-slot proofs; <tt>id</tt> is signed and usable for replay detection; <tt>iat</tt> is the JWT counterpart of <tt>attestedAt</tt>; <tt>format</tt> and <tt>proof</tt> are omitted rather than set to <tt>"json"</tt> or <tt>"none"</tt>; checking expiry is RECOMMENDED, consistent with <xref target="verification-procedure"/>. The Abstract states that verification needs no contact with the issuer beyond a copy of its published key set and documentation. <xref target="boolean-by-default"/> states that the default mode does not return the balance and may return public reference values the evaluation used. <xref target="no-pii"/> names the one identifier the primitive handles, the wallet address, and how a boolean about a person is to be treated; <xref target="use-cases"/> is aligned with it. <xref target="single-issuer"/> states that a wrapper signature confers no authority.</t>
        </li>
        <li>
          <t>References: added <xref target="FIPS204"/>, <xref target="RFC4648"/>, <xref target="RFC7515"/>, <xref target="RFC8615"/>, <xref target="RFC9964"/> and <xref target="RFC9794"/>; <xref target="RFC8785"/> is normative; <xref target="RFC8615"/> takes the place of RFC 5785; one informative URL is updated.</t>
        </li>
        <li>
          <t>Verifier behaviour that differs from a verifier built from -00: a missing or unknown <tt>kid</tt> is not accepted and no other key is substituted; a <tt>kid</tt> documented for a different type of message is not accepted; <tt>conditionHash</tt> is recomputed, and a mismatch causes rejection; a refuted companion causes rejection; a transmitted JWT that fails verification or does not correspond to the attestation beside it rejects the response; a verifier relying on the unsigned <tt>expiresAt</tt> checks it against the signed <tt>attestedAt</tt>. In each case the verifier accepts less.</t>
        </li>
        <li>
          <t>Requirements stated more loosely than in -00: checking expiry is RECOMMENDED rather than required (<xref target="response"/>, consistent with <xref target="verification-procedure"/>); verifier caching of the JWKS is a SHOULD rather than a MUST, and the -00 recommendation of a one-hour maximum cache lifetime is not carried forward (<xref target="jwks-discovery"/>); an XRPL ledger hash is included where available rather than always (<xref target="terminology"/>). The issuer selects the anchored block; the -00 option of a caller-specified block is not carried forward (<xref target="architecture-overview"/>). A verifier built from this revision also verifies attestations under the domain-separated scheme and accepts a JWKS that holds more than one key type, neither of which a verifier built from -00 does.</t>
        </li>
      </ul>
    </section>
    
<section numbered="false" anchor="acknowledgments">
      <name>Acknowledgments</name>
      <t>The author thanks the IETF community for ongoing discussion of attestation primitives in the agentic and wallet-state space.</t>
    </section>
  </back>

</rfc>
