JSON Schema draft-07 validates structure — field types, string patterns, array shapes, required properties. Some NIP and MIP requirements are semantic — they depend on decoded values, cross-field relationships, cryptographic correctness, or external state. These cannot be expressed in JSON Schema and must be enforced by runtime validators, codegen-generated validators, or application code.
This document catalogs known gaps. See #136 for the full audit.
A comprehensive audit of all ~300 schemas against their upstream NIP/MIP specs identified ~270 validation gaps across 65+ NIPs and 4 MIPs, organized into 8 categories:
| Category | Count | Description |
|---|---|---|
| Inter-event references | ~52 | e/a-tag kind checks, pubkey matching across events |
| Decoded content | ~51 | JSON-in-string, Markdown, BOLT11, bech32 TLV, PGN |
| Cross-field constraints | ~50 | Tag-to-tag comparisons, content-to-tag matching |
| Protocol state | ~45 | Relay behavior, HTTP context, authorization |
| Encrypted content | ~25 | NIP-44/NIP-04 ciphertext hiding inner structure |
| Computed values | ~21 | SHA-256, Schnorr signatures, bech32 checksums |
| Temporal | ~16 | Wall-clock comparisons, freshness windows |
| Cryptographic | ~10 | ECDH, DLEQ, P2PK, Cashu proofs |
Several gap patterns recur because of specific draft-07 limitations:
-
No
maxContains(added in draft 2019-09): Cannot enforce "exactly one p-tag" or "at most one e-tag" in heterogeneous tag arrays. Affects NIP-57 (exactly 1 p-tag), NIP-88 (unique option IDs). -
No
contentSchema/contentMediaTypeenforcement: Cannot validate that a string field contains valid JSON matching another schema. Affects ~20 NIPs with JSON-in-string content fields. -
No cross-property value comparison: Cannot express
since <= until,start < end, or "tag A value equals tag B value". Affects NIP-01 filters, NIP-52/53 calendars, NIP-32 labels. -
No "last item matching" in arrays:
containschecks existence anywhere; no way to constrain the last item matching a pattern. Affects NIP-25 (last e-tag is reaction target). -
No current-time reference: Cannot express "created_at must be within 10 minutes of now". Affects NIP-42, NIP-43, NIP-98 authentication events.
- Schema:
nips/nip-01/note/schema.yaml - Spec: NIP-01 —
id= SHA-256 of[0, pubkey, created_at, kind, tags, content] - What the schema checks: 64-char lowercase hex string
- What it cannot check: the value is the correct hash of the serialized event
- Where to enforce: application code, runtime validator
- Schema:
nips/nip-01/note/schema.yaml - Spec: NIP-01 —
sig= Schnorr signature ofidbypubkey - What the schema checks: 128-char lowercase hex string
- What it cannot check: cryptographic signature validity
- Where to enforce: application code, runtime validator
- Schemas:
nips/nip-19/npub/schema.yaml,nips/nip-19/note/schema.yaml, etc. - Spec: NIP-19 — bech32 encoding includes a 6-character BCH error-detecting checksum
- What the schema checks: character set regex and prefix matching
- What it cannot check: the checksum is valid
- Where to enforce: application code
- Schema:
nips/nip-57/kind-9735/schema.yaml - Spec: NIP-57 — hash of zap request must match the bolt11 invoice description hash
- What the schema checks: bolt11 superficial bech32 format, description tag is a string
- What it cannot check: SHA-256 computation, BOLT11 internal field extraction
- Where to enforce: application code
- Schema:
nips/nip-94/kind-1063/schema.yaml - Spec: NIP-94 —
xtag is the SHA-256 of the referenced file - What the schema checks: 64-char hex string format
- What it cannot check: the hash matches the actual file at the URL
- Where to enforce: application code
- Schema:
mips/mip-05/kind-446/schema.yaml - Spec: MIP-05 — decoded byte length must be a multiple of 280
- What the schema checks: base64 format,
minLength: 376 - What it cannot check: decoded length is exactly 280n bytes
- Where to enforce: runtime validator or codegen (schemata-codegen#35)
- Schema:
mips/mip-00/tag/i/schema.yaml,mips/mip-00/kind-30443/schema.yaml - Spec: MIP-00 — KeyPackageRef hash size depends on ciphersuite
- What the schema checks: hex format, length is one of 64/96/128 chars
- What it cannot check: the i-tag length matches the mls_ciphersuite tag value
- Where to enforce: runtime validator or codegen (schemata-codegen#36)
- Schema:
nips/nip-01/messages/filter/schema.yaml - Spec: NIP-01 —
since <= created_at <= until - What the schema checks: both are integers >= 0
- What it cannot check:
since <= untilcross-field comparison - Where to enforce: application code
- Schema:
nips/nip-25/kind-7/schema.yaml - Spec: NIP-25 — "The last e-tag MUST be the id of the note that is being reacted to"
- What the schema checks: at least one e-tag exists (via
contains) - What it cannot check: it is the last e-tag (no "last matching item" in draft-07)
- Where to enforce: application code, codegen validators
- Schema:
nips/nip-32/kind-1985/schema.yaml - Spec: NIP-32 — l-tag's third element must correspond to an L-tag with that namespace value
- What the schema checks: at least one l-tag and one target tag exist
- What it cannot check: cross-tag value equality between l namespace and L value
- Where to enforce: codegen validators
- Schemas:
nips/nip-52/kind-31922/schema.yaml,nips/nip-53/kind-30311/schema.yaml - Spec: start date/timestamp must be less than end
- What the schema checks: format validation independently
- What it cannot check: cross-tag value comparison
- Where to enforce: codegen validators
- Schema:
nips/nip-57/kind-9734/schema.yaml - Spec: NIP-57 — "It MUST have only one p tag"
- What the schema checks: at least one p-tag via
contains - What it cannot check: at most one (needs
maxContainsfrom draft 2019-09) - Where to enforce: codegen validators
- Schema:
nips/nip-01/kind-0/schema.yaml - Spec: NIP-01 — content is
{name, about, picture}stringified JSON - What the schema checks: content is a string;
schema.content.yamlvalidates the parsed structure separately - What it cannot check: the string parses as valid JSON matching the content schema
- Where to enforce: application code
- Schema:
nips/nip-57/kind-9735/schema.yaml - Spec: NIP-57 — description tag value is JSON-encoded kind:9734 event
- What the schema checks: tag is
["description", <string>] - What it cannot check: the string parses as valid JSON matching kind:9734 schema
- Where to enforce: application code
- Schemas:
nips/nip-19/nprofile/schema.yaml,nips/nip-19/nevent/schema.yaml,nips/nip-19/naddr/schema.yaml - Spec: NIP-19 — bech32 data contains binary TLV with required fields
- What the schema checks: prefix matching and character set
- What it cannot check: decoded binary TLV structure and required fields
- Where to enforce: application code
- Schemas:
nips/nip-59/kind-13/schema.yaml,nips/nip-59/kind-1059/schema.yaml - Spec: NIP-59 — seal content is NIP-44 encrypted rumor; wrap content is NIP-44 encrypted seal
- What the schema checks: non-empty content string, correct kind
- What it cannot check: decryption correctness, pubkey consistency across layers
- Where to enforce: application code
- Schemas:
nips/nip-17/kind-14/schema.yaml,nips/nip-17/kind-15/schema.yaml - Spec: NIP-17 — unsigned DMs must be sealed (kind:13) then gift-wrapped (kind:1059)
- What the schema checks: kind 14/15 structure in isolation
- What it cannot check: multi-layer nesting, encryption correctness
- Where to enforce: application code
- Schema:
nips/nip-46/kind-24133/schema.yaml - Spec: NIP-46 — content is NIP-44 encrypted JSON-RPC with method enum
- What the schema checks: content is non-empty string
- What it cannot check: inner JSON-RPC structure, method names, request/response correlation
- Where to enforce: application code
- Schemas:
nips/nip-47/kind-23194/schema.yamlthroughkind-23197/schema.yaml - Spec: NIP-47 — content is NIP-44 encrypted JSON with method, params, result, error
- What the schema checks: content is non-empty string
- What it cannot check: inner payload structure, method enum, error codes
- Where to enforce: application code
- Schema:
nips/nip-09/kind-5/schema.yaml - Spec: NIP-09 — referenced event pubkeys must match deletion event pubkey
- What the schema checks: at least one e/a tag
- What it cannot check: referenced events share the same pubkey
- Where to enforce: application code
- Schema:
nips/nip-57/kind-9735/schema.yaml - Spec: NIP-57 — receipt must include p, e, a tags from the zap request embedded in description
- What the schema checks: p, bolt11, description tags required
- What it cannot check: tag mirroring from the JSON-encoded request
- Where to enforce: application code
- Schema:
nips/nip-88/kind-1018/schema.yaml - Spec: NIP-88 — response tag option ID must exist in the referenced poll's option tags
- What the schema checks: response tag has alphanumeric ID
- What it cannot check: ID exists in the referenced poll event
- Where to enforce: application code
- Schema:
nips/nip-42/kind-22242/schema.yaml - Spec: NIP-42 —
created_atmust be within ~10 minutes of current time - What the schema checks: integer >= 0
- What it cannot check: comparison with wall-clock time
- Where to enforce: application code
- Schema:
nips/nip-98/kind-27235/schema.yaml - Spec: NIP-98 —
created_atwithin ~60 seconds of current time - What the schema checks: integer >= 0
- What it cannot check: comparison with wall-clock time
- Where to enforce: application code
- Spec: NIP-01 — kinds 10000-19999 replaceable, 20000-29999 ephemeral, 30000-39999 addressable
- What the schema checks: specific kind constants
- What it cannot check: replacement policy, tiebreaker rules, storage behavior
- Where to enforce: relay implementation
- Schemas:
nips/nip-29/kind-9000/schema.yamlthroughkind-9022/schema.yaml - Spec: NIP-29 — group events are only valid on the hosting relay
- What the schema checks: h-tag exists
- What it cannot check: relay affinity
- Where to enforce: relay implementation
- Schema:
nips/nip-60/kind-7375/schema.yaml - Spec: NIP-60 — proofs must have valid blinded signatures, unspent status
- What the schema checks: nothing (encrypted content)
- What it cannot check: keyset existence, signature verification, unspent status
- Where to enforce: application code, mint API
- Schema:
nips/nip-61/kind-9321/schema.yaml - Spec: NIP-61 — Cashu proofs must include verifiable DLEQ proofs
- What the schema checks: proof tag is
["proof", <string>] - What it cannot check: DLEQ cryptographic validity
- Where to enforce: application code, crypto libraries
| Category | Example | Enforceable by |
|---|---|---|
| Computed values | id = SHA-256 of event, sig = Schnorr signature, file hashes |
Application code |
| Decoded-value constraints | JSON-in-string, BOLT11, bech32 TLV, Markdown, PGN | Codegen validators, application code |
| Cross-field tag constraints | Tag A's value determines valid values for Tag B, start < end | Codegen validators |
| Cryptographic validation | NIP-44 encryption, ECDH, DLEQ, P2PK, Cashu proofs | Application code, crypto libraries |
| Inter-event references | e-tag must reference event of specific kind, pubkey matching | Application code, relay queries |
| Encrypted content | NIP-44/NIP-04 ciphertext hiding inner JSON structure | Application code (decrypt first) |
| Protocol state | Replaceable events, relay routing, HTTP context, authorization | Relay implementation, client code |
| Temporal constraints | Freshness windows, expiration, start < end timestamps | Application code |
Content schemas (schema.content.yaml) close many of the ~51 decoded-content gaps listed above by validating the decoded content field. However, they have their own boundaries:
- Base64 content: Content schemas validate the encoded string (pattern, length) but cannot validate decoded binary. MLS KeyPackageBundle and EncryptedToken structure requires protocol-specific parsers beyond JSON Schema.
- Plaintext content: Content schemas provide format-level validation (e.g., space-separated tokens, non-empty strings) but cannot validate semantic correctness (e.g., valid PGN moves, valid NWC method names).
- Decode step is external: JSON Schema cannot express "parse this string as JSON and validate the result". The decode step (
JSON.parse, base64 decode) must happen in application code or a validator wrapper before the content schema is applied.
When adding new schemas, if you identify a spec requirement that JSON Schema cannot express, add it to this document and consider filing a schemata-codegen issue if the gap is enforceable by generated validators.