← Back to Chain Agnostic Improvement Proposals
CAIP 380specificationDraftwalletkey-managementchain-identificationauthsigning

Portable Proof

A canonical, chain-agnostic, wallet-signed envelope that applications validate once off-chain and can reference anywhere via a deterministic `qHash` anchor. Identities follow CAIP-10 (`did:pkh`) and chains follow CAIP-2.

No reviews
Chris Leal (@cl34l)·Updated Mar 29, 2026·0 reviews·0 attestations·View source
Collections:CAIPs — Merged

Specification

Simple Summary

A canonical, chain-agnostic, wallet-signed envelope that applications validate once off-chain and can reference anywhere via a deterministic qHash anchor. Identities follow CAIP-10 (did:pkh) and chains follow CAIP-2.

Abstract

Defines a chain-agnostic, wallet-signed proof object anchored by a SHAKE-256 digest (qHash). Applications validate once off-chain; the same qHash may be surfaced on-chain for indexing/transport via [EIP-7683]–compatible vouchers.

Non-goal: CAIP-380 is not an authentication/session protocol. It standardizes a portable, canonical wallet-signed envelope for verifiability; authentication is an application-level choice layered above 380 with additional requirements.

Motivation

Establish a canonical, deterministic envelope that can be validated once and referenced anywhere (off-chain or on-chain) without binding to any vendor, domain, or transport. Align identities with CAIP-10 and chain context with CAIP-2 to avoid namespace collisions.

Rationale

  • Use [EIP-191] for broad wallet compatibility; support [EIP-1271] and detect [EIP-6492] to cover smart accounts and pre-deploy signatures.
  • Keep qHash anchored to a canonical subset to ensure stable, cross-environment equivalence and idempotency.
  • Treat vouchers as optional [EIP-7683]–compatible artifacts keyed by qHash to enable cross-chain transport without constraining settlement designs.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119] and [RFC 8174].

Terminology

  • Envelope: The top-level, signed data structure defined by this CAIP.
  • Canonical Subset: The exact set of Envelope properties that MUST be serialized deterministically (see Deterministic JSON) and hashed to produce the Anchor.
  • Anchor: The 32-byte qHash (SHAKE-256, 32 bytes) computed over the canonical serialization of the Canonical Subset.
  • Verifier: logic module identified by verifierId.
  • Voucher: optional on-chain artifact ([EIP-7683]–compatible) keyed by qHash.

Core Envelope — Canonical Subset (Normative)

The Canonical Subset MUST contain exactly the following top-level properties and MUST NOT contain any others:

  1. "did" (string) — CAIP-10 account identifier of the signer (e.g., did:pkh:<chainRef>:<address>; for EVM, did:pkh:eip155:<chainId>:<address>).
  2. "verifierIds" (array of string) — MUST be a non-empty array of ASCII identifiers.
  3. "data" (object) — application payload object.
  4. "signedTimestamp" (integer) — Unix epoch milliseconds when the user signed.
  5. One of: "chainId" (integer, EVM profile) or "chain" (string, CAIP-2 chain reference for non-EVM or alternate namespaces).

Chain binding: If chainId is present, the signer DID MUST map to did:pkh:eip155:<chainId>:<address>. If chain is present, the signer DID MUST map to did:pkh:<chain>:<address> where <chain> is a CAIP-2 reference (e.g., solana:mainnet, eip155:1). Exactly one of (chainId, chain) MUST be present.

For EVM signatures (eip191/eip1271/eip6492), producers MUST use chainId; using chain = "eip155:*" with EVM is NOT RECOMMENDED.

Extensibility. Any additional top-level properties MUST be outside the Canonical Subset and therefore excluded from the Anchor. Producers MAY add such properties (e.g., signature, signedMessage, signatureMethod, options, meta), but validators MUST ignore unknown non-canonical properties when computing/validating the Anchor, while they MAY apply additional local validation policies to them.

  • Identifiers:
    • Chains: CAIP-2 references (e.g., eip155:1, solana:mainnet)
    • Accounts: CAIP-10 did:pkh:<chainRef>:<address>
  • Envelope (top-level):
    • Note that the fields signedMessage and signatureMethod are OPTIONAL (see Conformance).
  {
    "did": "did:pkh:eip155:1:0xabc000000000000000000000000000000000def0",
    "qHash": "0x<shake256-hex>",
    "verifierIds": ["ownership-basic"],
    "data": {},
    "signature": "0x…",
    "signedMessage": "Portable Proof Verification Request\nWallet: 0xabc000000000000000000000000000000000def0\nChain: 1\nVerifiers: ownership-basic\nData: {}\nTimestamp: 1730000000000",
    "signedTimestamp": 1730000000000,
    "chainId": 1,
    "signatureMethod": "eip191",
    "options": {},
    "meta": {}
  }
  • Canonical signing (MUST): All sub-requirements in this section are MUST unless marked otherwise.

Signature Profiles

Universal signing: The envelope, Canonical Subset, determinism rules, freshness, and qHash are universal across ecosystems; non-EVM (e.g., Solana) follow the same six-line message with profile-specific bindings.

Default freshness window (applies to all profiles): verifiers MUST reject if signedTimestamp is older than 5 minutes from verification time or more than 60 seconds in the future (clock-skew allowance).

EVM Profile (Normative)

Signatures MUST use [EIP-191] (personal_sign) over the exact six-line message below. Signature encoding: signature MUST be 0x-prefixed, lowercase hex (applies to [EIP-191], [EIP-1271], and [EIP-6492]).

Six-line signer message (ABNF):

signer-msg = line1 LF line2 LF line3 LF line4 LF line5 LF line6

line1 = "Portable Proof Verification Request" ; fixed context label
line2 = "Wallet: " eth-addr
line3 = "Chain: " 1*DIGIT               ; numeric chain id
line4 = "Verifiers: " verifier-id *("," verifier-id)
line5 = "Data: " 1*VCHAR                ; canonical JSON of envelope.data (no spaces)
line6 = "Timestamp: " 1*DIGIT           ; unix ms

eth-addr = "0x" 40HEXDIG               ; MUST be lowercased in practice
verifier-id = 1*(ALPHA / DIGIT / "-" / "_" / ".")
LF = %x0A

Binding rules.

  • line2 address MUST equal the lowercased address component of did.
  • line3 MUST equal chainId.
  • line4 MUST equal the ASCII join of verifierIds with "," (no spaces).
  • line5 MUST equal the deterministic (canonical) JSON bytes of data as defined in Deterministic JSON. It MUST be the exact byte sequence used inside the Canonical Subset's data portion.
  • line6 MUST equal signedTimestamp.
  • The recovered EOA address (for EOA signers) MUST match the address component of did.

Address Case. Implementations MUST compare addresses case-insensitively. For canonicalization and binding, the address component of did MUST be normalized to lowercase. Examples in this CAIP show lowercase addresses.

Freshness window.

  • Verifiers MUST reject if signedTimestamp is older than 5 minutes from verification time or more than 60 seconds in the future (clock-skew allowance). Implementations SHOULD make this window configurable, but MUST default to these values.

  • Verification time is the validator’s local wall clock; implementations SHOULD use a synchronized time source (e.g., NTP).

  • Deterministic JSON (MUST): See the following normative rules.

Deterministic JSON (Normative)

All canonicalization in this CAIP follows a JCS-style profile (RFC 8785).

  • Scope. The rules apply to: (1) the Canonical Subset object, and (2) the "data" object contained within it. Non-canonical, top-level extension properties are not included in the Anchor and MUST NOT be fed into the Canonical Subset digest.

  • Objects. Keys MUST be UTF-8 and lexicographically sorted by Unicode code point, with no duplicate keys.

  • Arrays. Order MUST be preserved as provided.

  • Values.

    • The value undefined MUST NOT appear anywhere in the Canonical Subset or the "data" object. Producers MUST omit such keys entirely.
    • The value null MUST be preserved as a value if present.
    • Strings MUST use JSON escapes per RFC 8259; numbers SHOULD use shortest round-trip representation; booleans unchanged.
  • Whitespace. No insignificant whitespace MUST be present in the serialized canonical form.

  • Encoding. Canonical byte sequence MUST be UTF-8.

  • Recommendation. Implementations SHOULD conform to RFC 8785 (JSON Canonicalization Scheme, JCS) or an equivalent deterministic algorithm to produce canonical bytes.

Signer bytes normalization (Normative): Implementations MUST produce the six-line signer message as UTF-8 without BOM, using LF ("\n", 0x0A) line endings only, and strings MUST be NFC-normalized prior to serialization. This requirement prevents cross-environment drift (e.g., differing newline conventions or BOM insertion).

Canonicalization example (informative)

Input (producer view):

{
  "did": "did:pkh:eip155:1:0xabc000000000000000000000000000000000def0",
  "verifierIds": ["ownership-basic", "x-bonus"],
  "data": { "owner": "0xabc000000000000000000000000000000000def0" },
  "signedTimestamp": 1738532812345,
  "chainId": 1,
  "meta": { "debug": true }    // extension, not canonical
}

Canonical Subset serialized (bytes fed to Anchor):

{"chainId":1,"data":{"owner":"0xabc000000000000000000000000000000000def0"},"did":"did:pkh:eip155:1:0xabc000000000000000000000000000000000def0","signedTimestamp":1738532812345,"verifierIds":["ownership-basic","x-bonus"]}
  • Anchor (MUST): qHash = "0x" + hex_lower( SHAKE-256_32( canonical_json( CanonicalSubset ) ) ), where CanonicalSubset is exactly { did, verifierIds, data, signedTimestamp, chainId } serialized per Deterministic JSON.
    • The hex representation MUST be 64 lowercase hexadecimal characters prefixed with 0x.
    • Cross-domain portability is maximized with the canonical subset.
    • The Canonical Subset includes exactly one of chainId (EVM) or chain (CAIP-2 string) and the Anchor is computed over whichever is present.
  • Voucherization (SHOULD):
    • provide exactly one [EIP-7683]–compatible voucher per target chain, keyed by qHash;
    • creation SHOULD be access-controlled and idempotent.
  • [EIP-712] (Future Work): A typed-data variant may be standardized in a future revision. This document defines only the EIP-191 string for canonical signing.

Non-EVM example (Informative: ed25519 / Solana)

Signatures SHOULD use Ed25519 over the same six-line message (non-EVM, e.g., Solana) with the following substitutions: Signature encoding: signature SHOULD be base58 (64-byte Ed25519).

ABNF differences:

line2 = "Wallet: " base58-addr         ; base58 account, case-sensitive
line3 = "Chain: " chain-ref            ; CAIP-2 chain string (e.g., "solana:mainnet")
base58-addr = 1*(ALPHA / DIGIT)        ; simplified; wallets enforce full alphabet
chain-ref = 1*VCHAR                    ; CAIP-2 reference

Binding rules:

  • did MUST be did:pkh:solana:<ref>:<base58Address> and MUST match Wallet: and Chain:.
  • signatureMethod SHOULD be ed25519.
  • Data: line canonicalization is identical to EVM.

Validation sketch: verify Ed25519 over the exact six-line bytes. Freshness and qHash rules are unchanged.

Smart-Account Support (EIP-1271) and 6492 Detection (Normative)

Implementations MUST support contract-based accounts ([EIP-1271]) and MUST detect/verify [EIP-6492] signature wrappers.

Verification algorithm:

  1. Parse signature.
    • If input bytes match the EIP-6492 wrapper format, unwrap to obtain the inner signature bytes and deployment proof metadata (e.g., factory address, initCode). Record provenance metadata as needed.
  2. Determine signer type.
    • If there is code at the DID’s address on chainId (or 6492 proves a counterfactual deployment), treat as a smart account; otherwise treat as EOA.
  3. EOA path.
    • Recover address with EIP-191 over the exact six-line message. If recovered address ≠ the address component of did, fail.
  4. Smart-account path.
    • Call isValidSignature(<message-bytes>, <signature-bytes>) on the contract at the DID’s address on the chain identified by chainId (or on the counterfactual proven by 6492). The call MUST return magic value 0x1626ba7e; any other result or a revert MUST be treated as invalid. When using 6492, validators MUST validate the deployment proof per [EIP-6492].
  5. Result.
    • On success, proceed to freshness checks and Anchor matching; on failure, reject.

Notes.

  • Implementations MUST verify the same message bytes for both EOA and 1271 paths (no hashing differences).
  • If both EOA recovery and 1271 succeed (unexpected), prefer 1271 and emit a warning.

380↔7683 Composition (Informative)

380 and 7683 are complementary: 380 provides a portable, off-chain proof keyed by qHash; 7683 provides cross-chain intent/settlement. Two common compositions:

  1. 7683 carries 380: include qHash (and optional verifier summary) in a 7683 intent. Settlement looks up and verifies by qHash.
  2. 380 carries 7683: embed a 7683 intent object inside data; the 380 signature attests to it; settlement proceeds per 7683.

Example (illustrative):

{
  "voucher": {
    "intent": { "payload": { "qHash": "0x…" } }
  }
}

Conformance

  • Clients MUST:
    1. construct the six-line signer string;
    2. sign per the selected Signature Profile;
    3. include did, qHash, verifierIds, data, signature, signedTimestamp, and exactly one of (chainId, chain).
  • Clients MAY:
    1. include signedMessage (diagnostic);
    2. include signatureMethod (default eip191).
  • Validators/Servers MUST:
    1. Reconstruct the signer string exactly (LF line breaks, field order, and canonicalized data).
    2. Verify signature:
      • EVM: attempt [EIP-191] recovery; if it fails, call [EIP-1271]; if that fails, accept [EIP-6492] only if the deployment proof validates.
      • Non-EVM: verify Ed25519 over the same bytes.
    3. Bind identity/chain: the recovered/validated address MUST match the did, and did MUST match chainId or chain per profile.
    4. MUST reject if both chainId and chain are present, or both are absent.
    5. Enforce freshness: reject if signedTimestamp is older than 5 minutes or more than +60 seconds ahead (clock skew).
    6. Compute and compare qHash from the Canonical Subset bytes; reject on mismatch.
    7. SHOULD deduplicate by qHash when persisting, indexing, or transporting.
  • Wallets SHOULD:
    1. support [EIP-1271] and [EIP-6492].

Security Considerations

  • Validators MUST enforce the freshness window (5m TTL, +60s skew) and signer determinism.

  • Baseline (normative):

    • Enforce TTL and clock-skew limits.
    • Reconstruct the six-line signer string exactly (UTF-8, LF, canonical data bytes).
    • Bind DID ↔ chain (address matches did; did matches asserted chainId or chain).
    • Canonicalize JSON (data) per JCS-style rules.
    • When storing or transporting, SHOULD deduplicate by qHash to avoid cross-transport replay.
  • Threats → required behavior (normative):

    • Envelope replay across time: MUST reject if signedTimestamp is older than 5m or > +60s ahead.
    • Cross-transport replay: SHOULD treat qHash as an idempotent

[Content truncatedview full spec at source]

Discussion (0 threads)

Loading discussions...