{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://real-life.org/rltp/v1/schemas/contact-card.schema.json",
  "title": "RLTP Contact Card (rltp-encounter@0.19)",
  "description": "Normative wire format per RLTP Encounter Layer, section 6. WIRE FORM UNCHANGED SINCE 0.19 (the version strings below are the wire versions); the proof and timestamp bounds were added by casting 0.20 and narrow admitted values only. Every field is now upper-bounded, so a card has a finite maximum JCS size: 26 683 bytes at the adversarial escaping maximum of its free text (C0 control characters, 6 bytes each), 5203 bytes with UNESCAPED one-byte free text — the qualifier matters, since quotes and backslashes are one-byte ASCII and still cost two bytes each (9499 bytes). No layer places an acceptance cap on a card — where a card travels inside another document, fit is that document's sender duty (see OI-6). The root is CLOSED. The challenge is REQUIRED whenever the card is used in an enactment. sentTo and boundTo are REQUIRED on sent cards (sentTo names the recipient anchor, boundTo the counterparty displayed-challenge value the enactment answers; receivers reject sent cards missing either or with a foreign sentTo) and MUST be absent on displayed cards. Keys are verified decoded (multicodec + 32 bytes).",
  "type": "object",
  "required": [
    "version",
    "anchor",
    "keyAgreement",
    "proof"
  ],
  "additionalProperties": false,
  "dependentRequired": {
    "sentTo": ["boundTo"],
    "boundTo": ["sentTo"]
  },
  "properties": {
    "version": {
      "const": "rltp-card/0.19"
    },
    "anchor": {
      "type": "string",
      "pattern": "^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]{44}$"
    },
    "keyAgreement": {
      "type": "string",
      "description": "X25519 Multikey: 'z6LS' + 44 base58btc chars; MUST decode to multicodec x25519-pub + 32 bytes",
      "pattern": "^z6LS[1-9A-HJ-NP-Za-km-z]{44}$"
    },
    "challenge": {
      "type": "object",
      "required": [
        "value",
        "issuedAt"
      ],
      "additionalProperties": false,
      "properties": {
        "value": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{22,88}$"
        },
        "issuedAt": {
          "$ref": "#/$defs/rfc3339utc"
        }
      }
    },
    "sentTo": {
      "type": "string",
      "description": "recipient anchor; REQUIRED on sent cards, absent on displayed cards (spec 5.3, 6)",
      "pattern": "^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]{44}$"
    },
    "boundTo": {
      "type": "string",
      "description": "the counterparty displayed-challenge value this enactment answers; REQUIRED on sent cards (selects the receiver's own challenge under the resolution rule, spec 5.3), absent on displayed cards; in a bundle MUST equal the enclosed credential's bound challenge",
      "pattern": "^[A-Za-z0-9_-]{22,88}$"
    },
    "name": {
      "type": "string",
      "maxLength": 200
    },
    "deliveryHints": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 512
      },
      "maxItems": 8
    },
    "proof": {
      "type": "object",
      "required": [
        "type",
        "cryptosuite",
        "created",
        "verificationMethod",
        "proofPurpose",
        "proofValue"
      ],
      "additionalProperties": false,
      "properties": {
        "type": {
          "const": "DataIntegrityProof"
        },
        "cryptosuite": {
          "const": "eddsa-jcs-2022"
        },
        "created": {
          "$ref": "#/$defs/rfc3339utc"
        },
        "verificationMethod": {
          "type": "string",
          "pattern": "^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]{44}#z6Mk[1-9A-HJ-NP-Za-km-z]{44}$"
        },
        "proofPurpose": {
          "const": "assertionMethod"
        },
        "proofValue": {
          "type": "string",
          "description": "multibase base58btc of an Ed25519 signature, which is exactly 64 bytes (RFC 8032): 'z' plus 64 to 88 base58 characters, so 65 to 89 characters and nothing else. Upper end: 58^87 <= 2^512-1 < 58^88. Lower end: base58btc renders each leading zero byte as one '1', so the all-zero 64-byte string is the shortest at 64. A value outside this interval could never have verified (spec 2.3).",
          "minLength": 65,
          "maxLength": 89,
          "pattern": "^z[1-9A-HJ-NP-Za-km-z]+$"
        }
      }
    }
  },
  "$defs": {
    "rfc3339utc": {
      "type": "string",
      "description": "RFC3339 UTC 'Z', seconds 00-59, AT MOST 3 fractional-second digits (spec 2.3) — 24 characters maximum. Fractional seconds carry no protocol meaning because spec 2.3 requires every Encounter comparison — the Encounter Layer's own, and those a companion delegates to it, but not a companion's own time windows — to normalize every operand (including the local 'now') to whole seconds by truncation toward the past (delete the '.' and everything between it and the 'Z') before comparing; normalization is for comparison only and never precedes JCS. Producers SHOULD emit whole seconds.",
      "maxLength": 24,
      "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]{1,3})?Z$"
    }
  }
}
