Skip to main content

Wallet State Attestation: Signed Booleans about On-Chain State
draft-borthwick-wallet-state-attestation-01

Document Type Active Internet-Draft (individual)
Author Douglas Cameron Borthwick
Last updated 2026-09-27
RFC stream (None)
Intended RFC status (None)
Formats
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-borthwick-wallet-state-attestation-01
Network Working Group                                       D. Borthwick
Internet-Draft                                                InsumerAPI
Intended status: Informational                         27 September 2026
Expires: 31 March 2027

     Wallet State Attestation: Signed Booleans about On-Chain State
              draft-borthwick-wallet-state-attestation-01

Abstract

   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.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 31 March 2027.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

Borthwick                 Expires 31 March 2027                 [Page 1]
Internet-Draft          Wallet State Attestation          September 2026

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
     1.1.  Motivation  . . . . . . . . . . . . . . . . . . . . . . .   3
     1.2.  Position Relative to Identity Primitives  . . . . . . . .   3
     1.3.  Position Relative to Credentials  . . . . . . . . . . . .   3
     1.4.  Position Relative to Other Documents  . . . . . . . . . .   4
     1.5.  Use Cases . . . . . . . . . . . . . . . . . . . . . . . .   5
   2.  Terminology . . . . . . . . . . . . . . . . . . . . . . . . .   5
   3.  Architecture Overview . . . . . . . . . . . . . . . . . . . .   7
   4.  Request Shape . . . . . . . . . . . . . . . . . . . . . . . .   8
   5.  Response Shape  . . . . . . . . . . . . . . . . . . . . . . .   9
   6.  Signing Algorithm . . . . . . . . . . . . . . . . . . . . . .  11
     6.1.  Signing Schemes . . . . . . . . . . . . . . . . . . . . .  12
     6.2.  Signature Encoding and Additional Algorithms  . . . . . .  13
     6.3.  Companion Signatures  . . . . . . . . . . . . . . . . . .  13
   7.  JWKS Discovery  . . . . . . . . . . . . . . . . . . . . . . .  16
   8.  JWT Format  . . . . . . . . . . . . . . . . . . . . . . . . .  17
   9.  Optional Merkle Proofs  . . . . . . . . . . . . . . . . . . .  19
   10. Security Considerations . . . . . . . . . . . . . . . . . . .  19
     10.1.  Single-Issuer Architecture . . . . . . . . . . . . . . .  20
     10.2.  Freshness Anchors and Replay Considerations  . . . . . .  20
     10.3.  Refuse-on-Observation-Failure  . . . . . . . . . . . . .  21
     10.4.  What This Primitive Does Not Protect Against . . . . . .  21
     10.5.  Algorithm Agility  . . . . . . . . . . . . . . . . . . .  21
   11. Privacy Considerations  . . . . . . . . . . . . . . . . . . .  22
     11.1.  Boolean by Default . . . . . . . . . . . . . . . . . . .  22
     11.2.  Merkle Mode Trade-Off  . . . . . . . . . . . . . . . . .  22
     11.3.  No Identity Attributes . . . . . . . . . . . . . . . . .  23
     11.4.  Wallet Address Handling  . . . . . . . . . . . . . . . .  23
     11.5.  Awareness of Observation . . . . . . . . . . . . . . . .  23
   12. Verification Procedure  . . . . . . . . . . . . . . . . . . .  23
   13. What This Is Not  . . . . . . . . . . . . . . . . . . . . . .  25
   14. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  26
   15. References  . . . . . . . . . . . . . . . . . . . . . . . . .  26
     15.1.  Normative References . . . . . . . . . . . . . . . . . .  26
     15.2.  Informative References . . . . . . . . . . . . . . . . .  27
   Changes from -00  . . . . . . . . . . . . . . . . . . . . . . . .  28
   Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . .  30

Borthwick                 Expires 31 March 2027                 [Page 2]
Internet-Draft          Wallet State Attestation          September 2026

   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  30

1.  Introduction

1.1.  Motivation

   Existing access-control primitives --- OAuth 2.0 [RFC6749], OIDC
   [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.

   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.

   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.

1.2.  Position Relative to Identity Primitives

   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.

   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.

1.3.  Position Relative to Credentials

   Wallet State Attestation is *not* a Verifiable Credential (W3C VC)
   [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.

Borthwick                 Expires 31 March 2027                 [Page 3]
Internet-Draft          Wallet State Attestation          September 2026

   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.

   This document treats the credential / VC / eIDAS / SAML stack as
   adjacent prior art for explicit non-conflation purposes.  See
   Section 13 ("What This Is Not") for the full enumeration.

1.4.  Position Relative to Other Documents

   Wallet State Attestation is referenced or adopted in the following
   public artifacts (all informative; see References):

   *  *Knowledge Context Protocol (KCP) RFC-0004* [KCP-0004] --- defines
      an attestation_url / attestation_jwks mechanism in KCP YAML
      manifests for verifying agent eligibility before access.  The
      pattern is mechanism-agnostic; wallet state attestation is one
      valid implementation shape.

   *  *Verifiable Intent (VI) environment.* constraint family*
      [VI-ENVIRONMENT] --- defines an environmental constraint family
      for autonomous-mode (L3) execution in agent commerce, establishing
      family-wide vocabulary (attestation_url, max_attestation_age),
      composition rules, and IANA registry mechanics for individual
      constraint types.  The wallet-state-attestation primitive is the
      reference implementation shape used by VI's
      environment.wallet_state constraint type.

   *  *Agent Governance Vocabulary* [AGV] --- defines wallet_state as a
      foundation-layer signal type with named production issuers.

   *  *Open Agent Trust Registry (OATR)* [OATR] --- third-party trust
      registry of attestation issuers, including wallet-state-
      attestation issuers.

   *  *Draft ERC-8210 Agent Assurance Protocol* [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).

   *  *Universal Commerce Protocol (UCP) identity-linking*
      [UCP-IDENTITY] --- wallet attestation acknowledged as a reserved
      extensibility point for future non-OAuth identity-mechanism types.

Borthwick                 Expires 31 March 2027                 [Page 4]
Internet-Draft          Wallet State Attestation          September 2026

   Wallet state attestation is *not* 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.

1.5.  Use Cases

   Common deployments of wallet state attestation include:

   *  *Condition-based access* --- 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.

   *  *Agent-to-agent trust verification* --- autonomous agents
      verifying counterparty wallet state (holdings, attested compliance
      status, etc.) before transaction execution.

   *  *Compliance gating* --- 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
      (Section 11.3).

   *  *Pre-commitment eligibility* --- agent commerce protocols
      verifying that an agent wallet satisfies pre-conditions
      (sufficient stake, sufficient balance, attested status) before
      contract commitment.

   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 Section 5).

2.  Terminology

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in BCP
   14 [RFC2119] [RFC8174] when, and only when, they appear in all
   capitals, as shown here.

   The following terms are used throughout this document:

   *Issuer* --- 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
   Section 10.1).

Borthwick                 Expires 31 March 2027                 [Page 5]
Internet-Draft          Wallet State Attestation          September 2026

   *Holder* --- 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.

   *Verifier* --- The party that consumes the attestation, checks the
   signature against the issuer's published JWKS, and acts on the
   result.

   *Condition* --- 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".

   *Attestation* --- 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.

   *Fact profile* --- 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.

   *Block reference* --- 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 Section 9 for Bitcoin-
   specific considerations).  Any other chain family MUST include the
   reference the issuer documents for it.

   *Condition hash* --- A SHA-256 hash of the JSON serialization of the
   evaluated condition, with member names sorted as [RFC8785] sorts them
   and no insignificant whitespace, represented as a 0x-prefixed
   lowercase hexadecimal string, included in the response for tamper-
   evidence over the condition itself.

Borthwick                 Expires 31 March 2027                 [Page 6]
Internet-Draft          Wallet State Attestation          September 2026

3.  Architecture Overview

   The primitive operates as a three-step pipeline: *read -> evaluate ->
   sign*.

   1.  The issuer receives a request specifying a wallet address, a
       chain identifier, and one or more conditions.

   2.  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.

   3.  The issuer evaluates each condition against the observed state.

   4.  The issuer signs the result and returns the attestation.

   The evaluation is *deterministic*: 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 attestedAt timestamp,
   and the signature value differ from one issuance to the next.

   The primitive is *read-only*: 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.

   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 Section 10.3.

   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.

Borthwick                 Expires 31 March 2027                 [Page 7]
Internet-Draft          Wallet State Attestation          September 2026

4.  Request Shape

   A request is a JSON object with the following structure:

   {
     "wallet": "<address>",
     "conditions": [
       {
         "type": "<condition type>",
         "chainId": "<chain identifier>",
         "...": "<condition-specific parameters>"
       }
     ],
     "format": "jwt",
     "proof": "merkle"
   }

   *wallet* --- 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 wallet for one address family and carry
   the others in chain-specific members (e.g., solanaWallet, xrplWallet,
   bitcoinWallet).

   *chainId* --- 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 [CAIP-2]), expressible as
   integer (e.g., 1 for Ethereum mainnet) or string.  Non-EVM chains use
   a string identifier naming the chain (for example solana, xrpl,
   bitcoin).

   *conditions* --- A list of one or more condition objects.  Each has a
   type 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.

   *format* --- OPTIONAL.  When "jwt", the response additionally carries
   the attestation as an RFC 7519 JWT (Section 8).  When omitted, the
   response is the JSON format of Section 5 alone.

Borthwick                 Expires 31 March 2027                 [Page 8]
Internet-Draft          Wallet State Attestation          September 2026

   *proof* --- OPTIONAL.  Requests a cryptographic proof layer.  When
   omitted, no proof is requested.  Value: "merkle" (where supported by
   the underlying chain; returns [EIP-1186] Merkle storage proofs
   anchored to the block header).

5.  Response Shape

   A response contains a signed attestation object together with the
   signature and key identifier.  The signed attestation object has the
   following structure:

   {
     "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>"
   }

   The signature is computed over signed bytes derived from this object
   under the signing scheme that the transmitted kid selects
   (Section 6).  The signature, the key identifier (kid), expiry
   timestamp (expiresAt), 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 attestation
   object other than id, pass, results, and attestedAt: 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 evaluatedCondition).
   The JWT format names the wallet in its sub claim (Section 8).  A
   typical wire response carries the signed object plus these adjacent
   fields:

Borthwick                 Expires 31 March 2027                 [Page 9]
Internet-Draft          Wallet State Attestation          September 2026

   {
     "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)>"
   }

   Field semantics (signed object):

   *id* --- An opaque attestation identifier.  Implementation-specific
   format.  Useful for correlation, logging, and replay detection.  It
   is covered by the signature.

   *pass* --- The aggregate result.  Default semantics: logical AND of
   all per-condition met values.  Operators MAY specify alternate
   combination logic in extensions.

   *results* --- An array of per-condition result objects.  Each MUST
   include the per-condition met boolean, the condition type, the
   verbatim evaluatedCondition (as it was evaluated, after any
   normalization), and the conditionHash (SHA-256 of the UTF-8 JSON
   serialization of evaluatedCondition with member names sorted as
   [RFC8785] sorts them (by UTF-16 code units) and no insignificant
   whitespace, as a 0x-prefixed lowercase hexadecimal string).  The
   evaluatedCondition 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 Section 6.1.  A
   verifier MUST recompute conditionHash from the transmitted
   evaluatedCondition and MUST reject the result as altered if the two
   differ.  Each result MUST include chain-specific freshness evidence:
   blockNumber and blockTimestamp for EVM chains; ledgerIndex and (where
   available) ledgerHash for XRPL; slot for Solana; blockHeight and
   blockHash for Bitcoin; for any other chain family, the reference the
   issuer documents for it.  All such freshness fields are inside the
   signed results array and are therefore covered by the issuer's
   signature.  Implementations MAY include additional informational
   fields (condition index, label, etc.) echoed from the request.

   *attestedAt* --- ISO 8601 timestamp of when the attestation was
   signed.

   Field semantics (adjacent, unsigned):

Borthwick                 Expires 31 March 2027                [Page 10]
Internet-Draft          Wallet State Attestation          September 2026

   *expiresAt* --- ISO 8601 timestamp of when the attestation expires.
   Verifiers SHOULD check expiry (step 6 of Section 12).  Recommended
   default: thirty minutes after attestedAt.  Implementations MAY use
   shorter expiries; longer expiries SHOULD be justified by the use
   case. expiresAt 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 (Section 8), where exp is a
   signed claim.

   *kid* --- 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. kid is transmitted alongside the
   signed payload but is not part of the signed bytes.  The kid selects
   both the verification key and the signing scheme (Section 6.1);
   verifiers resolve it against the issuer's published JWKS as described
   in Section 7.  The JWT format embeds kid in the protected header
   where it is covered by the JWT signature.

   *sig* --- The signature.  Default: ECDSA P-256 signature over the
   signed bytes defined in Section 6, in P1363 (raw R || S) format,
   base64-encoded with the standard alphabet and padding (Section 4 of
   [RFC4648]).

   *pqSig*, *pqKid* --- OPTIONAL.  A companion signature under a second
   algorithm and the identifier of the key that produced it
   (Section 6.3).  When present they are additive: sig and kid are
   unchanged by their presence, and a verifier that does not implement
   companion verification ignores them.

   The signature scope includes the evaluatedCondition and conditionHash
   fields nested inside results, ensuring the signature is tamper-
   evident over both the result and the condition that produced it.

6.  Signing Algorithm

   The default signing algorithm is *ECDSA with P-256 curve and SHA-256
   (ES256)* per [RFC7518].

   The signed bytes are produced under one of the signing schemes in
   Section 6.1.  An issuer MAY additionally attach a companion signature
   under a second algorithm (Section 6.3).

Borthwick                 Expires 31 March 2027                [Page 11]
Internet-Draft          Wallet State Attestation          September 2026

6.1.  Signing Schemes

   This document defines two signing schemes.  The type of message a
   verifier holds is evident from its shape (an attestation of Section 5
   carries pass and results; a fact profile does not); the kid
   transmitted with it selects both the verification key and the signing
   scheme.  The issuer's documentation states, for each kid it
   publishes, which scheme that kid selects and, for the domain-
   separated scheme, the domain tag.  Verifiers MUST determine the
   scheme from the kid, MUST NOT assume that an issuer uses a single
   scheme, and MUST NOT verify an attestation under any scheme other
   than the one its kid selects.  A single public key MAY be published
   under more than one kid, 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 kid that the issuer
   documents for a different type of message.

   Signing schemes apply to the JSON format of Section 5.  In the JWT
   format (Section 8) the signed bytes are the JWS Signing Input
   [RFC7515] of the JWT [RFC7519], whatever scheme the kid would select
   in the JSON format; there the kid selects the verification key (and,
   through its JWKS entry, the algorithm) but no scheme.

   *Bare scheme.* The signed bytes are the JSON serialization of an
   object carrying exactly the four members of the signed object, in the
   order id, pass, results, attestedAt, with no insignificant
   whitespace, encoded as UTF-8.  Inside results, every member of every
   result object, including the members of evaluatedCondition, 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
   Section 5 applies only to the computation of conditionHash.  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
   [RFC8785].

   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 attestation object other than the four named above.

   *Domain-separated scheme.* 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 v
   (the scheme version, the integer 2 for the scheme defined here)

Borthwick                 Expires 31 March 2027                [Page 12]
Internet-Draft          Wallet State Attestation          September 2026

   together with the four members of the signed object (id, pass,
   results, attestedAt).  The canonical serialization is the JSON
   Canonicalization Scheme [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.

   Signed bytes are stable per kid.  An issuer introducing a new scheme
   MUST do so under a new kid and MUST NOT change the signed bytes
   produced under a kid it has already published.  Attestations signed
   under an earlier scheme therefore remain verifiable, unchanged, by
   verifiers deployed before the new scheme existed.

6.2.  Signature Encoding and Additional Algorithms

   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.

   Implementations MAY support additional algorithms (ES384, EdDSA /
   Ed25519).  Algorithm selection is encoded in the JWKS entry indexed
   by kid.  Verifiers MUST NOT verify a signature under an algorithm
   other than the one the selected JWKS entry implies (its alg, and the
   key type and curve it carries).

6.3.  Companion Signatures

   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 Section 6.1, nor the
   values of sig and kid.  A verifier that does not implement companion
   verification ignores the companion fields and is otherwise
   unaffected.

Borthwick                 Expires 31 March 2027                [Page 13]
Internet-Draft          Wallet State Attestation          September 2026

   In the JSON format the companion is carried in two adjacent fields:
   pqSig, the companion signature, base64-encoded with the standard
   alphabet and padding (Section 4 of [RFC4648]), and pqKid, 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 kid selects under
   Section 6.1, 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 pqKid it
   publishes.

   The companion algorithm defined by this document is ML-DSA-65
   [FIPS204].  Signatures are produced as in Algorithm 2 of [FIPS204]
   (ML-DSA.Sign, not the pre-hash variant) with an empty context string;
   these are the parameters that [RFC9964] specifies for JOSE.  The
   companion key is published in the issuer's JWKS (Section 7 of this
   document) as an AKP key, the key type defined by [RFC9964].

   In the JWT format (Section 8) the companion is a sibling token,
   pqJwt, returned beside the ES256 JWT: a JWS in compact serialization
   [RFC7515] whose protected header carries alg ML-DSA-65 and the
   companion kid, and whose claims are the same as those of the ES256
   JWT.  No companion domain tag is used in the JWT format: pqJwt is
   verified over its own JWS signing input.  A verifier that checks
   pqJwt 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.

Borthwick                 Expires 31 March 2027                [Page 14]
Internet-Draft          Wallet State Attestation          September 2026

   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: _verified_ (transmitted, and
   verifies under the key that pqKid selects; in the JWT format, also
   carrying the same claim set as the ES256 JWT); _refuted_
   (transmitted, and either fails verification under that key or, in the
   JWT format, carries a different claim set); _absent_ (no companion
   was transmitted, that is, no pqSig in the JSON format and no pqJwt in
   the JWT format); or _unverifiable_ (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:

   1.  No companion was transmitted: absent.

   2.  The attestation's own kid 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 kid it does not know, since
       that could only produce a false refutation.

   3.  pqKid 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 pqSig is transmitted without pqKid;
       or the verifier has no implementation of the algorithm:
       unverifiable.  A verifier applies no companion domain tag other
       than the one pqKid names, so a pqKid of the wrong type leaves it
       nothing it is entitled to check.

   4.  Otherwise the companion signature is checked, and in the JWT
       format the claim sets are compared: verified or refuted.  An
       attestation whose own kid 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.

   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 kid selects a key; a
   genuine pqJwt beside a JWT whose kid the verifier cannot place is
   reported verified while the JWT's own signature verdict is
   unverifiable.

   In the JWT format, a pqJwt that does not parse as a compact JWS, or
   whose protected header lacks either the companion algorithm or a kid,
   is malformed and is refuted, since the header is covered by the
   companion signature and the issuer always emits both; this rule is

Borthwick                 Expires 31 March 2027                [Page 15]
Internet-Draft          Wallet State Attestation          September 2026

   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.

7.  JWKS Discovery

   The issuer publishes its public key set at a discoverable HTTPS
   endpoint following [RFC7517].  A commonly used path is /.well-known/
   jwks.json, under the well-known URI prefix of [RFC8615]; this
   document does not register that path.

   Each JWKS entry MUST include kid, kty, and alg, together with the
   public key parameters that its key type requires: crv, x, and y for
   an elliptic curve key (kty EC, [RFC7518]); pub for an Algorithm Key
   Pair key (kty AKP, [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 (Section 6.1),
   or companion signatures (Section 6.3).  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.

   Key selection: verifiers MUST select the verification key by the
   transmitted kid, never by position in the JWKS and never by default.
   If the kid 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
   _unverifiable_, which is distinct from _refuted_: a refuted
   attestation carries a signature that fails under the key its kid
   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.

   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

Borthwick                 Expires 31 March 2027                [Page 16]
Internet-Draft          Wallet State Attestation          September 2026

   thirty-minute window).  An issuer MAY instead retain every key it has
   ever published, under its original kid, 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.

   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 kid resolves to no entry; the attestation is then
   unverifiable, not refuted, as described above.  A verifier that meets
   an unknown kid MAY refresh its copy of the JWKS and retry.

   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 kid becomes visible to
   verifiers; an issuer SHOULD document it.

8.  JWT Format

   When format is "jwt", the response additionally carries the
   attestation as an RFC 7519 JWT, in a jwt member beside the members of
   the JSON format (Section 5).

   Protected header: {"alg": "ES256", "typ": "JWT", "kid": "<key id>"}
   per [RFC7519].  The kid in the protected header is covered by the JWT
   signature.

   Standard claims:

   *  iss --- issuer identifier (e.g., the issuer's base URL)

   *  sub --- the wallet address a condition in the request evaluated.
      When the conditions span several address families, the issuer
      documents which family's wallet sub names; a wallet the request
      supplied but no condition evaluated is not named.

   *  jti --- JWT identifier (typically the attestation id)

   *  iat --- issuance timestamp (Unix seconds)

   *  exp --- expiry timestamp (Unix seconds)

   Custom claims carrying the attestation payload:

Borthwick                 Expires 31 March 2027                [Page 17]
Internet-Draft          Wallet State Attestation          September 2026

   *  pass --- aggregate result

   *  results --- per-condition result array

   *  conditionHash --- array of per-condition conditionHash values, in
      results order

   *  blockNumber --- chain block reference (typically the first
      result's blockNumber, where chain-homogeneous)

   *  blockTimestamp --- chain block timestamp (typically the first
      result's blockTimestamp)

   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
   pqJwt (Section 6.3); the ES256 JWT is unchanged by its presence.

   The JWT format provides signed coverage of exp and kid (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 Section 5.

   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 kid as the response, its
   results under step 4 of Section 12, its companion under Section 6.3,
   and that it is the token of this attestation (its kid equals the
   response's kid, jti equals id, and pass, results, and exp equal the
   attestation's pass, results, and expiresAt 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 sub, which the JSON-
   format attestation does not carry; the wallet named in sub 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 Section 12
   describes for the JWT format, without the correspondence check.

Borthwick                 Expires 31 March 2027                [Page 18]
Internet-Draft          Wallet State Attestation          September 2026

9.  Optional Merkle Proofs

   When proof is "merkle" and the underlying chain supports Merkle
   storage proofs (typically EVM chains for storage-slot-mappable
   conditions), the response MAY include [EIP-1186] Merkle storage
   proofs anchored to the block header for each per-condition result.

   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 (proof.available is true 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).

   Not all conditions or chains support Merkle proofs.  Implementations
   MUST indicate proof availability per result and MAY return
   proof.available: false with a reason field when proof generation is
   unsupported for the condition or chain (e.g., NFT ownership
   conditions, non-storage-slot-mappable conditions, non-EVM chains).

   *Privacy trade-off:* 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.

   Bitcoin, XRPL, and Solana have different state-proof models and may
   not support Merkle proofs in the [EIP-1186] sense.  Implementations
   targeting those chains MAY define chain-appropriate proof shapes or
   MAY return proof.available: false.

   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 blockHeight and blockHash 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 Section 10.3.

10.  Security Considerations

Borthwick                 Expires 31 March 2027                [Page 19]
Internet-Draft          Wallet State Attestation          September 2026

10.1.  Single-Issuer Architecture

   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:

   1.  *Deterministic signature surface.* Verifiers can independently
       re-derive an attestation's results from chain state without
       resolving issuer trust hierarchies, federation policies, or
       multi-party signing logic.

   2.  *No re-signing or wrapping.* 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,
       Section 6.3 and Section 8).  The original observation's signature
       is the authoritative artifact end-to-end.

   3.  *Unambiguous attribution.* For audit, dispute resolution, and
       accountability, the issuer is unambiguously identified by the
       JWKS endpoint and signing key.

   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.

10.2.  Freshness Anchors and Replay Considerations

   The cryptographically-bound freshness anchors are the block reference
   inside each per-condition result and the attestedAt timestamp in the
   outer signed payload (the iat 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 attestedAt is older than thirty minutes",
   or "reject if blockTimestamp is older than sixty seconds").

   The expiresAt field is an issuer-provided TTL hint, transmitted
   alongside the attestation but outside the signed payload in the JSON
   format described in Section 5.  Because expiresAt is unsigned in the
   JSON format, a verifier that relies on it SHOULD confirm that it is
   no later than the signed attestedAt 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 Section 8, where exp is a signed claim.

Borthwick                 Expires 31 March 2027                [Page 20]
Internet-Draft          Wallet State Attestation          September 2026

   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.

10.3.  Refuse-on-Observation-Failure

   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.

   Verifiers MAY cross-check the included block reference against an
   independent chain source if the deployment requires it.

10.4.  What This Primitive Does Not Protect Against

   *  *Chain-level reorganizations after attestation.* 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.

   *  *Off-chain state changes.* This primitive observes on-chain state
      only.  It does not attest to off-chain identity, off-chain
      agreements, or off-chain commitments.

   *  *Malicious issuer.* 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.

10.5.  Algorithm Agility

   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 kid.  Verifiers
   MUST check the algorithm before verifying.

Borthwick                 Expires 31 March 2027                [Page 21]
Internet-Draft          Wallet State Attestation          September 2026

   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. _Rotation_: the issuer publishes a key
   for the new algorithm in JWKS under a new kid and begins signing with
   it; verifiers that do not support the new algorithm can no longer
   verify new attestations. _Composition_: the issuer continues to sign
   with the existing algorithm and attaches a companion signature under
   the new algorithm (Section 6.3).  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
   [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 Section 6.3 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.

11.  Privacy Considerations

11.1.  Boolean by Default

   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.

   This is the privacy-preserving default and SHOULD be used unless
   specific deployment requirements justify a less-private mode.

11.2.  Merkle Mode Trade-Off

   When proof is "merkle", 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.

Borthwick                 Expires 31 March 2027                [Page 22]
Internet-Draft          Wallet State Attestation          September 2026

11.3.  No Identity Attributes

   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 (Section 11.4), 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 (label,
   and the condition parameters in evaluatedCondition); operators SHOULD
   NOT place identity attributes in them.

11.4.  Wallet Address Handling

   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.

11.5.  Awareness of Observation

   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.

12.  Verification Procedure

   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.

   1.  *Select the key and scheme.* Read the transmitted kid.  If it is
       absent, or resolves to no entry in the JWKS available to the
       verifier, stop: the signature verdict is _unverifiable_
       (Section 7).  Do not substitute any other key.  If the kid
       resolves but the issuer documents it for a different type of

Borthwick                 Expires 31 March 2027                [Page 23]
Internet-Draft          Wallet State Attestation          September 2026

       message, stop: the artifact is relabelled, and the signature
       verdict is _refuted_. Otherwise the kid selects the key, the
       algorithm (the JWKS entry's alg and key type), and the signing
       scheme with its domain tag (Section 6.1).

   2.  *Reconstruct the signed bytes* from the transmitted attestation
       object under the selected scheme: under the bare scheme, exactly
       the members id, pass, results, and attestedAt 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 {v, id, pass,
       results, attestedAt} with v equal to the scheme version.  Members
       of the transmitted object other than the four named are not part
       of the signed bytes.

   3.  *Verify the primary signature*: decode sig (standard base64,
       P1363), and verify it under the selected algorithm over the
       signed bytes.  Failure is _refuted_; the attestation is rejected.

   4.  *Recompute each condition hash*: for every result, serialize
       evaluatedCondition with member names sorted and no whitespace
       (Section 5), hash it, and compare with the transmitted
       conditionHash.  A mismatch marks the result as altered; the
       attestation is rejected.

   5.  *Check freshness* against the signed anchors (attestedAt and each
       result's block reference) under the verifier's own policy
       (Section 10.2), and reject identifiers already seen if replay
       protection is required.

   6.  *Check expiry*: reject if the current time is past expiresAt,
       and, because expiresAt is unsigned in this format, reject as
       altered an expiresAt later than the signed attestedAt plus the
       issuer's documented validity window (Section 10.2).  A verifier
       MAY allow a small clock skew.

   7.  *Check the companion*, if one is transmitted, in the order given
       in Section 6.3, 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.

Borthwick                 Expires 31 March 2027                [Page 24]
Internet-Draft          Wallet State Attestation          September 2026

   After a stop at step 1, the verifier reports the signature verdict it
   reached and the companion verdict of Section 6.3; 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.

   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 alg header (the kid still selects the key, and step 1's rules for
   a missing, unknown, or wrong-type kid still apply), step 4 applies to
   the results claim, expiry is the signed exp claim, and the companion
   is pqJwt bound by the full claim set (Section 6.3).  A verifier
   holding both formats of the same attestation also verifies the token
   beside the attestation as Section 8 describes.

   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 kid; 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.

13.  What This Is Not

   This section explicitly enumerates adjacent primitives that wallet
   state attestation is *not*, to prevent conflation:

   *  *Not a Verifiable Credential (W3C VC) [VC-DATA-MODEL].* 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.

   *  *Not an mDoc or eIDAS attestation.* 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.

   *  *Not a SAML assertion or OIDC ID token.* These primitives carry
      identity claims about a user.  Wallet state attestations carry
      observations about wallet state, with no identity attribute
      (Section 11.3).

Borthwick                 Expires 31 March 2027                [Page 25]
Internet-Draft          Wallet State Attestation          September 2026

   *  *Not a reputation score, trust score, or credit rating.* 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.

   *  *Not a settlement attestation, delivery attestation, or
      transaction proof.* 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.

   *  *Not an oracle in the price-feed sense.* This primitive observes
      specific wallet state on demand for a specific request; it does
      not publish continuous data streams or aggregate market data.

   These distinctions are essential to keeping wallet state attestation
   as a distinct primitive in the agent-commerce, condition-based-
   access, and verification ecosystems.

14.  IANA Considerations

   This document has no IANA actions.

15.  References

15.1.  Normative References

   [FIPS204]  National Institute of Standards and Technology, "Module-
              Lattice-Based Digital Signature Standard", NIST FIPS 204,
              DOI 10.6028/NIST.FIPS.204, August 2024,
              <https://nvlpubs.nist.gov/nistpubs/FIPS/
              NIST.FIPS.204.pdf>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/rfc/rfc2119>.

   [RFC4648]  Josefsson, S., "The Base16, Base32, and Base64 Data
              Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006,
              <https://www.rfc-editor.org/rfc/rfc4648>.

   [RFC7515]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web
              Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May
              2015, <https://www.rfc-editor.org/rfc/rfc7515>.

Borthwick                 Expires 31 March 2027                [Page 26]
Internet-Draft          Wallet State Attestation          September 2026

   [RFC7517]  Jones, M., "JSON Web Key (JWK)", RFC 7517,
              DOI 10.17487/RFC7517, May 2015,
              <https://www.rfc-editor.org/rfc/rfc7517>.

   [RFC7518]  Jones, M., "JSON Web Algorithms (JWA)", RFC 7518,
              DOI 10.17487/RFC7518, May 2015,
              <https://www.rfc-editor.org/rfc/rfc7518>.

   [RFC7519]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token
              (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015,
              <https://www.rfc-editor.org/rfc/rfc7519>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/rfc/rfc8174>.

   [RFC8785]  Rundgren, A., Jordan, B., and S. Erdtman, "JSON
              Canonicalization Scheme (JCS)", RFC 8785,
              DOI 10.17487/RFC8785, June 2020,
              <https://www.rfc-editor.org/rfc/rfc8785>.

   [RFC9964]  Prorock, M. and O. Steele, "ML-DSA for JSON Object Signing
              and Encryption (JOSE) and CBOR Object Signing and
              Encryption (COSE)", RFC 9964, DOI 10.17487/RFC9964, May
              2026, <https://www.rfc-editor.org/rfc/rfc9964>.

15.2.  Informative References

   [AGV]      "Agent Governance Vocabulary", n.d.,
              <https://github.com/aeoess/agent-governance-vocabulary>.

   [CAIP-2]   "Chain Agnostic Improvement Proposal 2: Blockchain ID
              Specification", n.d.,
              <https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/
              caip-2.md>.

   [EIP-1186] "Ethereum Improvement Proposal 1186: RPC-Method to get
              Merkle Proofs", n.d.,
              <https://eips.ethereum.org/EIPS/eip-1186>.

   [ERC-8210] "Draft Ethereum Improvement Proposal 8210: Agent Assurance
              Protocol", n.d., <https://ethereum-magicians.org/t/erc-
              8210-agent-assurance/28097>.

   [KCP-0004] "Knowledge Context Protocol RFC-0004: Trust, Provenance,
              and Compliance", n.d., <https://github.com/Cantara/
              knowledge-context-protocol/blob/main/RFC-0004-Trust-and-
              Compliance.md>.

Borthwick                 Expires 31 March 2027                [Page 27]
Internet-Draft          Wallet State Attestation          September 2026

   [OATR]     "Open Agent Trust Registry", n.d.,
              <https://github.com/FransDevelopment/open-agent-trust-
              registry>.

   [OIDC-CORE]
              Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., and
              C. Mortimore, "OpenID Connect Core 1.0", n.d.,
              <https://openid.net/specs/openid-connect-core-1_0.html>.

   [RFC6749]  Hardt, D., Ed., "The OAuth 2.0 Authorization Framework",
              RFC 6749, DOI 10.17487/RFC6749, October 2012,
              <https://www.rfc-editor.org/rfc/rfc6749>.

   [RFC8615]  Nottingham, M., "Well-Known Uniform Resource Identifiers
              (URIs)", RFC 8615, DOI 10.17487/RFC8615, May 2019,
              <https://www.rfc-editor.org/rfc/rfc8615>.

   [RFC9794]  Driscoll, F., Parsons, M., and B. Hale, "Terminology for
              Post-Quantum Traditional Hybrid Schemes", RFC 9794,
              DOI 10.17487/RFC9794, June 2025,
              <https://www.rfc-editor.org/rfc/rfc9794>.

   [UCP-IDENTITY]
              "Universal Commerce Protocol: Identity Linking", n.d.,
              <https://github.com/Universal-Commerce-
              Protocol/ucp/blob/main/docs/specification/common/identity-
              linking/index.md>.

   [VC-DATA-MODEL]
              Sporny, M., "Verifiable Credentials Data Model", n.d.,
              <https://www.w3.org/TR/vc-data-model-2.0/>.

   [VI-ENVIRONMENT]
              Borthwick, D. and M. Msebenzi, "Verifiable Intent ---
              environment.* Constraint Family", May 2026,
              <https://datatracker.ietf.org/doc/draft-borthwick-
              msebenzi-environment-state/>.

Changes from -00

   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.

Borthwick                 Expires 31 March 2027                [Page 28]
Internet-Draft          Wallet State Attestation          September 2026

   *  Section 6 now has subsections and defines two signing schemes
      selected by kid: the bare scheme and a domain-separated scheme
      based on [RFC8785].  It states that signed bytes are stable per
      kid, that one key may be published under several kid 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 conditionHash alone.

   *  New Section 6.3 defines optional companion signatures under a
      second algorithm (ML-DSA-65), carried as pqSig and pqKid in the
      JSON format and as pqJwt 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.  Section 10.5 describes
      composition alongside rotation as a path for algorithm migration.

   *  New Section 12 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.

   *  Section 7 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 kid, never by position or default; a missing or
      unknown kid 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 kid; and that new key types are appended after existing
      entries.  Caching is stated in terms of the issuer's published
      headers, without a figure.

   *  Section 5 states that members of the transmitted attestation
      object other than the four signed members are unsigned, that the
      signed object does not in general name the wallet, that
      evaluatedCondition is flat, the base64 alphabet, and the
      serialization used for conditionHash.  Section 4: chainId is
      carried by each condition; format and proof are top-level members;
      the chain-family lists are open-ended.  Section 8: the JWT is
      carried beside the JSON format; sub 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.

   *  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

Borthwick                 Expires 31 March 2027                [Page 29]
Internet-Draft          Wallet State Attestation          September 2026

      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; id is signed and usable for replay
      detection; iat is the JWT counterpart of attestedAt; format and
      proof are omitted rather than set to "json" or "none"; checking
      expiry is RECOMMENDED, consistent with Section 12.  The Abstract
      states that verification needs no contact with the issuer beyond a
      copy of its published key set and documentation.  Section 11.1
      states that the default mode does not return the balance and may
      return public reference values the evaluation used.  Section 11.3
      names the one identifier the primitive handles, the wallet
      address, and how a boolean about a person is to be treated;
      Section 1.5 is aligned with it.  Section 10.1 states that a
      wrapper signature confers no authority.

   *  References: added [FIPS204], [RFC4648], [RFC7515], [RFC8615],
      [RFC9964] and [RFC9794]; [RFC8785] is normative; [RFC8615] takes
      the place of RFC 5785; one informative URL is updated.

   *  Verifier behaviour that differs from a verifier built from -00: a
      missing or unknown kid is not accepted and no other key is
      substituted; a kid documented for a different type of message is
      not accepted; conditionHash 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
      expiresAt checks it against the signed attestedAt.  In each case
      the verifier accepts less.

   *  Requirements stated more loosely than in -00: checking expiry is
      RECOMMENDED rather than required (Section 5, consistent with
      Section 12); 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 (Section 7); an XRPL ledger hash
      is included where available rather than always (Section 2).  The
      issuer selects the anchored block; the -00 option of a caller-
      specified block is not carried forward (Section 3).  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.

Acknowledgments

   The author thanks the IETF community for ongoing discussion of
   attestation primitives in the agentic and wallet-state space.

Author's Address

Borthwick                 Expires 31 March 2027                [Page 30]
Internet-Draft          Wallet State Attestation          September 2026

   Douglas Borthwick
   InsumerAPI
   New Canaan, CT
   United States of America
   Email: douglas@insumermodel.com

Borthwick                 Expires 31 March 2027                [Page 31]