@cardanowall/poe-standard 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema.ts"],"names":["z"],"mappings":";;;;;AAsCO,IAAM,0BAA0BA,KAAA,CACpC,KAAA;AAAA,EACCA,KAAA,CAAE,UAAA,CAAW,UAAU,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,IAAU,CAAA,IAAK,CAAA,CAAE,MAAA,IAAU,EAAA,EAAI;AAAA,IACtE,MAAA,EAAQ,EAAE,IAAA,EAAM,iBAAA;AAAkB,GACnC;AACH,CAAA,CACC,IAAI,CAAC;AAMR,IAAM,YAAA,GAAe,IAAI,WAAA,EAAY;AAC9B,IAAM,sBAAsBA,KAAA,CAChC,KAAA;AAAA,EACCA,KAAA,CAAE,QAAO,CAAE,MAAA;AAAA,IACT,CAAC,CAAA,KAAM;AACL,MAAA,MAAM,CAAA,GAAI,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA,CAAE,MAAA;AACjC,MAAA,OAAO,CAAA,IAAK,KAAK,CAAA,IAAK,EAAA;AAAA,IACxB,CAAA;AAAA,IACA,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,mBAAkB;AAAE;AAE1C,CAAA,CACC,IAAI,CAAC;AAgBD,IAAM,gBAAA,GAAmBA,KAAA,CAAE,UAAA,CAAW,UAAU;AAEhD,IAAM,kBAAkBA,KAAA,CAAE,MAAA,CAAOA,KAAA,CAAE,MAAA,IAAU,gBAAgB;AAW7D,IAAM,kBAAA,GAAqBA,MAC/B,MAAA,CAAO;AAAA,EACN,GAAA,EAAKA,MAAE,MAAA,EAAO;AAAA,EACd,IAAA,EAAMA,KAAA,CAAE,UAAA,CAAW,UAAU,CAAA;AAAA,EAC7B,YAAYA,KAAA,CAAE,MAAA,GAAS,GAAA,EAAI,CAAE,IAAI,CAAC,CAAA;AAAA,EAClC,IAAA,EAAMA,MAAE,KAAA,CAAM,mBAAmB,EAAE,GAAA,CAAI,CAAC,EAAE,QAAA;AAC5C,CAAC,EACA,MAAA;AA6BI,IAAM,UAAA,GAAaA,MAAE,MAAA,CAAO;AAAA,EACjC,GAAA,EAAKA,KAAA,CAAE,UAAA,CAAW,UAAU,EAAE,QAAA,EAAS;AAAA,EACvC,MAAA,EAAQ,wBAAwB,QAAA,EAAS;AAAA,EACzC,IAAA,EAAMA,KAAA,CAAE,UAAA,CAAW,UAAU,EAAE,QAAA;AACjC,CAAC;AAQM,IAAM,oBAAA,GAAuBA,MACjC,MAAA,CAAO;AAAA,EACN,CAAA,EAAGA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI;AAAA,EAClB,CAAA,EAAGA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI;AAAA,EAClB,CAAA,EAAGA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA;AAChB,CAAC,EACA,MAAA;AAWI,IAAM,qBAAA,GAAwBA,MAClC,MAAA,CAAO;AAAA,EACN,GAAA,EAAKA,MAAE,MAAA,EAAO;AAAA,EACd,IAAA,EAAMA,MAAE,UAAA,CAAW,UAAU,EAAE,WAAA,CAAY,CAAC,OAAO,GAAA,KAAQ;AACzD,IAAA,IAAI,KAAA,CAAM,SAAS,EAAA,EAAI;AACrB,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAA,EAAM,QAAA;AAAA,QACN,MAAM,EAAC;AAAA,QACP,OAAA,EAAS,CAAA,uBAAA,EAA0B,KAAA,CAAM,MAAM,CAAA,KAAA,CAAA;AAAA,QAC/C,MAAA,EAAQ,EAAE,IAAA,EAAM,+BAAA;AAAgC,OACjD,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,EAAA,EAAI;AAC5B,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAA,EAAM,QAAA;AAAA,QACN,MAAM,EAAC;AAAA,QACP,OAAA,EAAS,CAAA,uBAAA,EAA0B,KAAA,CAAM,MAAM,CAAA,KAAA,CAAA;AAAA,QAC/C,MAAA,EAAQ,EAAE,IAAA,EAAM,8BAAA;AAA+B,OAChD,CAAA;AAAA,IACH;AAAA,EACF,CAAC,CAAA;AAAA,EACD,MAAA,EAAQA,MAAE,MAAA,CAAOA,KAAA,CAAE,QAAO,EAAGA,KAAA,CAAE,SAAS;AAC1C,CAAC,EACA,MAAA;AASI,IAAM,wBAAA,GAA2BA,MACrC,MAAA,CAAO;AAAA,EACN,MAAA,EAAQA,MAAE,OAAA,EAAQ;AAAA,EAClB,IAAA,EAAMA,MAAE,MAAA,EAAO;AAAA,EACf,GAAA,EAAKA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACzB,KAAA,EAAOA,KAAA,CAAE,UAAA,CAAW,UAAU,CAAA;AAAA,EAC9B,KAAA,EAAOA,KAAA,CAAE,KAAA,CAAM,UAAU,EAAE,QAAA,EAAS;AAAA,EACpC,SAAA,EAAWA,KAAA,CACR,UAAA,CAAW,UAAU,CAAA,CACrB,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,EAAA,EAAI;AAAA,IAC9B,MAAA,EAAQ,EAAE,IAAA,EAAM,8BAAA;AAA+B,GAChD,EACA,QAAA,EAAS;AAAA,EACZ,UAAA,EAAY,sBAAsB,QAAA;AACpC,CAAC,EACA,MAAA;AAOI,IAAM,eAAA,GAAkBA,MAC5B,MAAA,CAAO;AAAA,EACN,MAAA,EAAQ,eAAA;AAAA,EACR,IAAA,EAAMA,MAAE,KAAA,CAAM,mBAAmB,EAAE,GAAA,CAAI,CAAC,EAAE,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA,EAInD,GAAA,EAAKA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAA;AACnB,CAAC,EACA,MAAA;AAWI,IAAM,cAAA,GAAiBA,MAC3B,MAAA,CAAO;AAAA,EACN,QAAA,EAAU,wBAAwB,QAAA,EAAS;AAAA,EAC3C,UAAA,EAAY;AACd,CAAC,EACA,MAAA;AAOI,IAAM,gBAAA,GAAmBA,KAAA,CAAE,UAAA,CAAW,UAAU,CAAA,CAAE,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,EAAA,EAAI;AAAA,EACtF,MAAA,EAAQ,EAAE,IAAA,EAAM,8BAAA;AAClB,CAAC;AAgBM,IAAM,oBAAA,GAAuBA,KAAA,CAAE,OAAA,CAAQ,CAAC;AAExC,IAAM,eAAA,GAAkBA,MAAE,WAAA,CAAY;AAAA,EAC3C,CAAA,EAAG,oBAAA;AAAA,EACH,KAAA,EAAOA,KAAA,CAAE,KAAA,CAAM,eAAe,EAAE,QAAA,EAAS;AAAA,EACzC,MAAA,EAAQA,KAAA,CAAE,KAAA,CAAM,kBAAkB,EAAE,QAAA,EAAS;AAAA,EAC7C,UAAA,EAAY,iBAAiB,QAAA,EAAS;AAAA,EACtC,IAAA,EAAMA,KAAA,CAAE,KAAA,CAAM,cAAc,EAAE,QAAA,EAAS;AAAA,EACvC,MAAMA,KAAA,CAAE,KAAA,CAAMA,MAAE,MAAA,EAAQ,EAAE,QAAA;AAC5B,CAAC;AASM,IAAM,mBAAA,uBAA+C,GAAA,CAAI;AAAA,EAC9D,GAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAC;AAQM,IAAM,uBAAA,GAA0B;AAChC,IAAM,0BAAA,GAA6B;AAEnC,SAAS,eAAe,CAAA,EAAoB;AACjD,EAAA,OAAO,wBAAwB,IAAA,CAAK,CAAC,CAAA,IAAK,0BAAA,CAA2B,KAAK,CAAC,CAAA;AAC7E","file":"schema.cjs","sourcesContent":["// CIP-309 v1 PoE record Zod schemas.\n//\n// Scope: structural shape gate. The schema enforces per-field types, length\n// bounds (chunk size, digest length, supersedes length, nonce length,\n// passphrase salt length), closed-map invariants (`sigs[i]`, `slot`,\n// `passphrase`, `merkle[i]`), and the `v == 1` literal. Cross-field rules\n// (item.hashes content-hash binding when `enc` present, slots/passphrase\n// exclusivity, `crit[]` shape, registry membership of algorithm\n// identifiers, COSE_Sign1 structural decode, URI per-scheme shape rules)\n// fire in `validator.ts` so the validator can emit the precise structural\n// codes (`UNSUPPORTED_*_ALG`, `ENC_*`, `SIG_*`, `INVALID_URI`,\n// `CRIT_SHAPE_INVALID`, …) rather than a generic schema-mismatch.\n//\n// Refinements that DO live in the schema (because the validator's domain\n// pass lifts these as `SCHEMA_*` / `*_LENGTH_MISMATCH` codes directly):\n// - chunk size `[1, 64]` → `CHUNK_TOO_LARGE`\n// - 32-byte digest / 32-byte root / 32-byte supersedes → `HASH_DIGEST_LENGTH_MISMATCH`\n// / `SUPERSEDES_TX_INVALID_LENGTH`\n// - 24-byte nonce / 32-byte slots_mac →\n// `NONCE_LENGTH_MISMATCH` / `ENC_SLOTS_MAC_INVALID_LENGTH`\n// - passphrase salt 16..64 bytes → `ENC_PASSPHRASE_SALT_TOO_SHORT` /\n// `ENC_PASSPHRASE_SALT_TOO_LONG`\n//\n// Per-slot recipient lengths (`epk`, `kem_ct`, `wrap`) are NOT enforced here:\n// the required slot shape depends on the envelope-level `kem`, which a slot\n// cannot see in isolation. The KEM-driven slot descriptor in `validator.ts`\n// emits the precise `KEM_EPK_LENGTH_MISMATCH` / `KEM_CT_LENGTH_MISMATCH` /\n// `WRAP_LENGTH_MISMATCH` / `ENC_SLOT_INVALID_SHAPE` codes instead.\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Chunked-bytes / chunked-text arrays\n// =============================================================================\n\n// `[1* bstr .size (1..64)]`. A zero-length chunk (0 < 1) is rejected with the\n// SAME `CHUNK_TOO_LARGE` code as oversized chunks (any length outside\n// `[1, 64]`).\nexport const ChunkedBytesArraySchema = z\n .array(\n z.instanceof(Uint8Array).refine((b) => b.length >= 1 && b.length <= 64, {\n params: { code: 'CHUNK_TOO_LARGE' },\n }),\n )\n .min(1);\nexport type ChunkedBytesArray = z.infer<typeof ChunkedBytesArraySchema>;\n\n// `[1* tstr .size (1..64)]` — chunk byte length is the UTF-8-encoded length\n// (each `tstr` is wire-encoded as UTF-8). The `tstr .size (1..64)` pin is a\n// byte-count constraint, not a code-unit constraint.\nconst UTF8_ENCODER = new TextEncoder();\nexport const UriChunkArraySchema = z\n .array(\n z.string().refine(\n (s) => {\n const n = UTF8_ENCODER.encode(s).length;\n return n >= 1 && n <= 64;\n },\n { params: { code: 'CHUNK_TOO_LARGE' } },\n ),\n )\n .min(1);\nexport type UriChunkArray = z.infer<typeof UriChunkArraySchema>;\n\n// =============================================================================\n// Hashes map\n// =============================================================================\n//\n// `hashes` is a non-empty CBOR map keyed by content-hash algorithm identifier\n// (a CBOR text string from the content-hash registry) with the 32-byte digest\n// as value. cbor2 surfaces a text-keyed CBOR map as a plain JS object — z.record\n// admits any string key here. Both the registry-membership check\n// (`UNSUPPORTED_HASH_ALG`) and the per-algorithm digest-length check\n// (`HASH_DIGEST_LENGTH_MISMATCH`) live in the validator's domain pass so\n// each violation emits its precise code; the schema only enforces the\n// value is a CBOR byte string.\n\nexport const HashDigestSchema = z.instanceof(Uint8Array);\n\nexport const HashesMapSchema = z.record(z.string(), HashDigestSchema);\nexport type HashesMap = z.infer<typeof HashesMapSchema>;\n\n// =============================================================================\n// Top-level `merkle[]`\n// =============================================================================\n//\n// Each commit is a closed map `{alg, root, leaf_count, ? uris}`. `alg` is open\n// (registry membership is enforced in the validator's domain pass — unknown\n// identifiers emit `UNSUPPORTED_MERKLE_COMMIT_ALG`).\n\nexport const MerkleCommitSchema = z\n .object({\n alg: z.string(),\n root: z.instanceof(Uint8Array),\n leaf_count: z.number().int().min(1),\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n })\n .strict();\nexport type MerkleCommit = z.infer<typeof MerkleCommitSchema>;\n\n// =============================================================================\n// Encryption envelope\n// =============================================================================\n\n// Per-slot recipient entry. The slot shape is KEM-driven:\n//\n// - x25519: `{ epk: bstr(32), wrap: bstr(48) }` — `epk` is the\n// ephemeral X25519 public key, `wrap` is the 32-byte CEK + 16-byte\n// ChaCha20-Poly1305 tag.\n// - mlkem768x25519: `{ kem_ct: [ bstr .size (1..64) ], wrap: bstr(48) }` —\n// `kem_ct` is the 1120-byte X-Wing `enc` carried as a chunked byte-string\n// array (the same `bytes-chunk-array` shape `sigs[i].cose_sign1` uses);\n// there is NO per-slot `epk` on the hybrid path.\n//\n// The `kem` identifier is hoisted to envelope scope (a per-slot `kem` would\n// be wire-bloat). The schema is deliberately PERMISSIVE:\n// `epk`, `kem_ct`, and `wrap` are all optional and `.strict()` is NOT applied.\n// Both the per-field length checks (`KEM_EPK_LENGTH_MISMATCH`,\n// `KEM_CT_LENGTH_MISMATCH`, `WRAP_LENGTH_MISMATCH`) and the KEM-driven\n// shape gate (which field MUST/MUST NOT be present for the declared `kem`,\n// emitting `ENC_SLOT_INVALID_SHAPE`) live in the validator's domain pass —\n// the structural schema cannot know the envelope `kem` from a slot in\n// isolation, and we want the precise KEM-aware code rather than a generic\n// schema mismatch. Because `.strict()` is dropped, the domain pass MUST\n// explicitly reject cross-KEM contamination (an x25519 slot carrying\n// `kem_ct`, or a hybrid slot carrying `epk`).\nexport const SlotSchema = z.object({\n epk: z.instanceof(Uint8Array).optional(),\n kem_ct: ChunkedBytesArraySchema.optional(),\n wrap: z.instanceof(Uint8Array).optional(),\n});\nexport type Slot = z.infer<typeof SlotSchema>;\n\n// Argon2id params `{m, t, p}` are a closed map. Each value MUST be a CBOR\n// unsigned integer; the FLOOR check (`m ≥ 65536`,\n// `t ≥ 3`, `p ≥ 1`) emits `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW` in the\n// validator's domain pass — keeping it out of the schema lets us emit the\n// distinct salt-length code when salt itself is malformed too.\nexport const Argon2idParamsSchema = z\n .object({\n m: z.number().int(),\n t: z.number().int(),\n p: z.number().int(),\n })\n .strict();\nexport type Argon2idParams = z.infer<typeof Argon2idParamsSchema>;\n\n// Passphrase block. `alg` is open (registry membership checked in the\n// validator's domain pass → `ENC_PASSPHRASE_ALG_UNSUPPORTED`);\n// `params` is open here (validator narrows on the registered `alg` value and\n// emits `SCHEMA_UNKNOWN_FIELD` for extra keys, `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW`\n// for sub-floor values). `salt` length floor/ceiling are schema-layer\n// refinements with the dedicated `ENC_PASSPHRASE_SALT_TOO_SHORT/TOO_LONG`\n// codes — they belong at the schema layer because a slot cannot otherwise\n// see the salt length.\nexport const PassphraseBlockSchema = z\n .object({\n alg: z.string(),\n salt: z.instanceof(Uint8Array).superRefine((bytes, ctx) => {\n if (bytes.length < 16) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} < 16`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_SHORT' },\n });\n } else if (bytes.length > 64) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} > 64`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_LONG' },\n });\n }\n }),\n params: z.record(z.string(), z.unknown()),\n })\n .strict();\nexport type PassphraseBlock = z.infer<typeof PassphraseBlockSchema>;\n\n// Sealed-PoE envelope. The wire format admits any combination of\n// `kem` / `slots` / `slots_mac` / `passphrase` keys (permissive superset);\n// cross-field invariants (slots ⊕ passphrase, slots ↔ slots_mac, slots\n// requires kem, content-hash binding, slots non-empty) are enforced in the\n// validator's domain pass so each violation emits its typed code rather\n// than a generic shape mismatch.\nexport const EncryptionEnvelopeSchema = z\n .object({\n scheme: z.unknown(),\n aead: z.string(),\n kem: z.string().optional(),\n nonce: z.instanceof(Uint8Array),\n slots: z.array(SlotSchema).optional(),\n slots_mac: z\n .instanceof(Uint8Array)\n .refine((b) => b.length === 32, {\n params: { code: 'ENC_SLOTS_MAC_INVALID_LENGTH' },\n })\n .optional(),\n passphrase: PassphraseBlockSchema.optional(),\n })\n .strict();\nexport type EncryptionEnvelope = z.infer<typeof EncryptionEnvelopeSchema>;\n\n// =============================================================================\n// Item entry\n// =============================================================================\n\nexport const ItemEntrySchema = z\n .object({\n hashes: HashesMapSchema,\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n // Captured as `unknown` so the validator can run the\n // `ENC_REQUIRES_CONTENT_HASH` pre-check ahead of any inner-shape errors\n // and surface the most informative code first.\n enc: z.unknown().optional(),\n })\n .strict();\nexport type ItemEntry = z.infer<typeof ItemEntrySchema>;\n\n// =============================================================================\n// Sig entry\n// =============================================================================\n//\n// Closed CBOR map `{cose_sign1, ? cose_key}`. Canonical CBOR map-key sort\n// (RFC 8949 §4.2.1, bytewise lex on encoded keys) places `cose_key`\n// (length-8 tstr, `0x68`) BEFORE `cose_sign1` (length-10 tstr, `0x6a`); the\n// schema property-order is irrelevant — the canonical encoder handles it.\nexport const SigEntrySchema = z\n .object({\n cose_key: ChunkedBytesArraySchema.optional(),\n cose_sign1: ChunkedBytesArraySchema,\n })\n .strict();\nexport type SigEntry = z.infer<typeof SigEntrySchema>;\n\n// =============================================================================\n// Supersedence\n// =============================================================================\n\nexport const SupersedesSchema = z.instanceof(Uint8Array).refine((b) => b.length === 32, {\n params: { code: 'SUPERSEDES_TX_INVALID_LENGTH' },\n});\nexport type Supersedes = z.infer<typeof SupersedesSchema>;\n\n// =============================================================================\n// Top-level record\n// =============================================================================\n//\n// `v == 1` is a literal — a future major (`v: 2`) MUST be rejected with\n// `SCHEMA_INVALID_LITERAL`. `z.literal(1)` preserves the narrow `1` type for\n// the inferred `PoeRecord[\"v\"]` (so consumers can dispatch on it) and emits\n// Zod's `invalid_value` code which the validator's mapper lifts to\n// `SCHEMA_INVALID_LITERAL`.\n//\n// `looseObject` admits extension keys (matching `^x-.+` or `^[a-z]+-.+`); the\n// validator's domain pass rejects unknown keys that match neither pattern with\n// `SCHEMA_UNKNOWN_FIELD`.\nexport const VersionLiteralSchema = z.literal(1);\n\nexport const PoeRecordSchema = z.looseObject({\n v: VersionLiteralSchema,\n items: z.array(ItemEntrySchema).optional(),\n merkle: z.array(MerkleCommitSchema).optional(),\n supersedes: SupersedesSchema.optional(),\n sigs: z.array(SigEntrySchema).optional(),\n crit: z.array(z.string()).optional(),\n});\nexport type PoeRecord = z.infer<typeof PoeRecordSchema>;\n\n// =============================================================================\n// Closed top-level base-key registry\n// =============================================================================\n//\n// Used by the validator's domain pass to distinguish unknown-typo keys from\n// well-formed extension keys (`^x-.+` / `^[a-z]+-.+`).\nexport const TOP_LEVEL_BASE_KEYS: ReadonlySet<string> = new Set([\n 'v',\n 'items',\n 'merkle',\n 'supersedes',\n 'sigs',\n 'crit',\n]);\n\n// Extension-key namespaces. Anchored at both ends so an\n// embedded newline cannot smuggle a multi-segment key past the check: `.`\n// excludes `\\n` in JS, and the `\\n?$` tail tolerates exactly ONE trailing\n// newline (matching the Python validator's `re.fullmatch(r'^(x-.+|[a-z]+-.+)$')`\n// semantics, where `$` likewise admits a single trailing `\\n`). So `x-note\\n`\n// is an extension key, but `x-a\\nb`, `x-note\\n\\n`, and `x-\\n` are not.\nexport const EXTENSION_KEY_VENDOR_RE = /^x-.+\\n?$/;\nexport const EXTENSION_KEY_COMPANION_RE = /^[a-z]+-.+\\n?$/;\n\nexport function isExtensionKey(k: string): boolean {\n return EXTENSION_KEY_VENDOR_RE.test(k) || EXTENSION_KEY_COMPANION_RE.test(k);\n}\n"]}
1
+ {"version":3,"sources":["../src/schema.ts"],"names":["z"],"mappings":"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","file":"schema.cjs","sourcesContent":["// Label 309 v1 PoE record Zod schemas.\n//\n// Scope: structural shape gate. The schema enforces per-field types, length\n// bounds (chunk size, digest length, supersedes length, nonce length,\n// passphrase salt length), closed-map invariants (`sigs[i]`, `slot`,\n// `passphrase`, `merkle[i]`), and the `v == 1` literal. Cross-field rules\n// (item.hashes content-hash binding when `enc` present, slots/passphrase\n// exclusivity, `crit[]` shape, registry membership of algorithm\n// identifiers, COSE_Sign1 structural decode, URI per-scheme shape rules)\n// fire in `validator.ts` so the validator can emit the precise structural\n// codes (`UNSUPPORTED_*_ALG`, `ENC_*`, `SIG_*`, `INVALID_URI`,\n// `CRIT_SHAPE_INVALID`, …) rather than a generic schema-mismatch.\n//\n// Refinements that DO live in the schema (because the validator's domain\n// pass lifts these as `SCHEMA_*` / `*_LENGTH_MISMATCH` codes directly):\n// - chunk size `[1, 64]` → `CHUNK_TOO_LARGE`\n// - 32-byte digest / 32-byte root / 32-byte supersedes → `HASH_DIGEST_LENGTH_MISMATCH`\n// / `SUPERSEDES_TX_INVALID_LENGTH`\n// - 24-byte nonce / 32-byte slots_mac →\n// `NONCE_LENGTH_MISMATCH` / `ENC_SLOTS_MAC_INVALID_LENGTH`\n// - passphrase salt 16..64 bytes → `ENC_PASSPHRASE_SALT_TOO_SHORT` /\n// `ENC_PASSPHRASE_SALT_TOO_LONG`\n//\n// Per-slot recipient lengths (`epk`, `kem_ct`, `wrap`) are NOT enforced here:\n// the required slot shape depends on the envelope-level `kem`, which a slot\n// cannot see in isolation. The KEM-driven slot descriptor in `validator.ts`\n// emits the precise `KEM_EPK_LENGTH_MISMATCH` / `KEM_CT_LENGTH_MISMATCH` /\n// `WRAP_LENGTH_MISMATCH` / `ENC_SLOT_INVALID_SHAPE` codes instead.\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Chunked-bytes / chunked-text arrays\n// =============================================================================\n\n// `[1* bstr .size (1..64)]`. A zero-length chunk (0 < 1) is rejected with the\n// SAME `CHUNK_TOO_LARGE` code as oversized chunks (any length outside\n// `[1, 64]`).\nexport const ChunkedBytesArraySchema = z\n .array(\n z.instanceof(Uint8Array).refine((b) => b.length >= 1 && b.length <= 64, {\n params: { code: 'CHUNK_TOO_LARGE' },\n }),\n )\n .min(1);\nexport type ChunkedBytesArray = z.infer<typeof ChunkedBytesArraySchema>;\n\n// `[1* tstr .size (1..64)]` — chunk byte length is the UTF-8-encoded length\n// (each `tstr` is wire-encoded as UTF-8). The `tstr .size (1..64)` pin is a\n// byte-count constraint, not a code-unit constraint.\nconst UTF8_ENCODER = new TextEncoder();\nexport const UriChunkArraySchema = z\n .array(\n z.string().refine(\n (s) => {\n const n = UTF8_ENCODER.encode(s).length;\n return n >= 1 && n <= 64;\n },\n { params: { code: 'CHUNK_TOO_LARGE' } },\n ),\n )\n .min(1);\nexport type UriChunkArray = z.infer<typeof UriChunkArraySchema>;\n\n// =============================================================================\n// Hashes map\n// =============================================================================\n//\n// `hashes` is a non-empty CBOR map keyed by content-hash algorithm identifier\n// (a CBOR text string from the content-hash registry) with the 32-byte digest\n// as value. cbor2 surfaces a text-keyed CBOR map as a plain JS object — z.record\n// admits any string key here. Both the registry-membership check\n// (`UNSUPPORTED_HASH_ALG`) and the per-algorithm digest-length check\n// (`HASH_DIGEST_LENGTH_MISMATCH`) live in the validator's domain pass so\n// each violation emits its precise code; the schema only enforces the\n// value is a CBOR byte string.\n\nexport const HashDigestSchema = z.instanceof(Uint8Array);\n\nexport const HashesMapSchema = z.record(z.string(), HashDigestSchema);\nexport type HashesMap = z.infer<typeof HashesMapSchema>;\n\n// =============================================================================\n// Top-level `merkle[]`\n// =============================================================================\n//\n// Each commit is a closed map `{alg, root, leaf_count, ? uris}`. `alg` is open\n// (registry membership is enforced in the validator's domain pass — unknown\n// identifiers emit `UNSUPPORTED_MERKLE_COMMIT_ALG`).\n\nexport const MerkleCommitSchema = z\n .object({\n alg: z.string(),\n root: z.instanceof(Uint8Array),\n leaf_count: z.number().int().min(1),\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n })\n .strict();\nexport type MerkleCommit = z.infer<typeof MerkleCommitSchema>;\n\n// =============================================================================\n// Encryption envelope\n// =============================================================================\n\n// Per-slot recipient entry. The slot shape is KEM-driven:\n//\n// - x25519: `{ epk: bstr(32), wrap: bstr(48) }` — `epk` is the\n// ephemeral X25519 public key, `wrap` is the 32-byte CEK + 16-byte\n// ChaCha20-Poly1305 tag.\n// - mlkem768x25519: `{ kem_ct: [ bstr .size (1..64) ], wrap: bstr(48) }` —\n// `kem_ct` is the 1120-byte X-Wing `enc` carried as a chunked byte-string\n// array (the same `bytes-chunk-array` shape `sigs[i].cose_sign1` uses);\n// there is NO per-slot `epk` on the hybrid path.\n//\n// The `kem` identifier is hoisted to envelope scope (a per-slot `kem` would\n// be wire-bloat). The schema is deliberately PERMISSIVE:\n// `epk`, `kem_ct`, and `wrap` are all optional and `.strict()` is NOT applied.\n// Both the per-field length checks (`KEM_EPK_LENGTH_MISMATCH`,\n// `KEM_CT_LENGTH_MISMATCH`, `WRAP_LENGTH_MISMATCH`) and the KEM-driven\n// shape gate (which field MUST/MUST NOT be present for the declared `kem`,\n// emitting `ENC_SLOT_INVALID_SHAPE`) live in the validator's domain pass —\n// the structural schema cannot know the envelope `kem` from a slot in\n// isolation, and we want the precise KEM-aware code rather than a generic\n// schema mismatch. Because `.strict()` is dropped, the domain pass MUST\n// explicitly reject cross-KEM contamination (an x25519 slot carrying\n// `kem_ct`, or a hybrid slot carrying `epk`).\nexport const SlotSchema = z.object({\n epk: z.instanceof(Uint8Array).optional(),\n kem_ct: ChunkedBytesArraySchema.optional(),\n wrap: z.instanceof(Uint8Array).optional(),\n});\nexport type Slot = z.infer<typeof SlotSchema>;\n\n// Argon2id params `{m, t, p}` are a closed map. Each value MUST be a CBOR\n// unsigned integer; the FLOOR check (`m ≥ 65536`,\n// `t ≥ 3`, `p ≥ 1`) emits `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW` in the\n// validator's domain pass — keeping it out of the schema lets us emit the\n// distinct salt-length code when salt itself is malformed too.\nexport const Argon2idParamsSchema = z\n .object({\n m: z.number().int(),\n t: z.number().int(),\n p: z.number().int(),\n })\n .strict();\nexport type Argon2idParams = z.infer<typeof Argon2idParamsSchema>;\n\n// Passphrase block. `alg` is open (registry membership checked in the\n// validator's domain pass → `ENC_PASSPHRASE_ALG_UNSUPPORTED`);\n// `params` is open here (validator narrows on the registered `alg` value and\n// emits `SCHEMA_UNKNOWN_FIELD` for extra keys, `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW`\n// for sub-floor values). `salt` length floor/ceiling are schema-layer\n// refinements with the dedicated `ENC_PASSPHRASE_SALT_TOO_SHORT/TOO_LONG`\n// codes — they belong at the schema layer because a slot cannot otherwise\n// see the salt length.\nexport const PassphraseBlockSchema = z\n .object({\n alg: z.string(),\n salt: z.instanceof(Uint8Array).superRefine((bytes, ctx) => {\n if (bytes.length < 16) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} < 16`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_SHORT' },\n });\n } else if (bytes.length > 64) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} > 64`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_LONG' },\n });\n }\n }),\n params: z.record(z.string(), z.unknown()),\n })\n .strict();\nexport type PassphraseBlock = z.infer<typeof PassphraseBlockSchema>;\n\n// Sealed-PoE envelope. The wire format admits any combination of\n// `kem` / `slots` / `slots_mac` / `passphrase` keys (permissive superset);\n// cross-field invariants (slots ⊕ passphrase, slots ↔ slots_mac, slots\n// requires kem, content-hash binding, slots non-empty) are enforced in the\n// validator's domain pass so each violation emits its typed code rather\n// than a generic shape mismatch.\nexport const EncryptionEnvelopeSchema = z\n .object({\n scheme: z.unknown(),\n aead: z.string(),\n kem: z.string().optional(),\n nonce: z.instanceof(Uint8Array),\n slots: z.array(SlotSchema).optional(),\n slots_mac: z\n .instanceof(Uint8Array)\n .refine((b) => b.length === 32, {\n params: { code: 'ENC_SLOTS_MAC_INVALID_LENGTH' },\n })\n .optional(),\n passphrase: PassphraseBlockSchema.optional(),\n })\n .strict();\nexport type EncryptionEnvelope = z.infer<typeof EncryptionEnvelopeSchema>;\n\n// =============================================================================\n// Item entry\n// =============================================================================\n\nexport const ItemEntrySchema = z\n .object({\n hashes: HashesMapSchema,\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n // Captured as `unknown` so the validator can run the\n // `ENC_REQUIRES_CONTENT_HASH` pre-check ahead of any inner-shape errors\n // and surface the most informative code first.\n enc: z.unknown().optional(),\n })\n .strict();\nexport type ItemEntry = z.infer<typeof ItemEntrySchema>;\n\n// =============================================================================\n// Sig entry\n// =============================================================================\n//\n// Closed CBOR map `{cose_sign1, ? cose_key}`. Canonical CBOR map-key sort\n// (RFC 8949 §4.2.1, bytewise lex on encoded keys) places `cose_key`\n// (length-8 tstr, `0x68`) BEFORE `cose_sign1` (length-10 tstr, `0x6a`); the\n// schema property-order is irrelevant — the canonical encoder handles it.\nexport const SigEntrySchema = z\n .object({\n cose_key: ChunkedBytesArraySchema.optional(),\n cose_sign1: ChunkedBytesArraySchema,\n })\n .strict();\nexport type SigEntry = z.infer<typeof SigEntrySchema>;\n\n// =============================================================================\n// Supersedence\n// =============================================================================\n\nexport const SupersedesSchema = z.instanceof(Uint8Array).refine((b) => b.length === 32, {\n params: { code: 'SUPERSEDES_TX_INVALID_LENGTH' },\n});\nexport type Supersedes = z.infer<typeof SupersedesSchema>;\n\n// =============================================================================\n// Top-level record\n// =============================================================================\n//\n// `v == 1` is a literal — a future major (`v: 2`) MUST be rejected with\n// `SCHEMA_INVALID_LITERAL`. `z.literal(1)` preserves the narrow `1` type for\n// the inferred `PoeRecord[\"v\"]` (so consumers can dispatch on it) and emits\n// Zod's `invalid_value` code which the validator's mapper lifts to\n// `SCHEMA_INVALID_LITERAL`.\n//\n// `looseObject` admits extension keys (matching `^x-.+` or `^[a-z]+-.+`); the\n// validator's domain pass rejects unknown keys that match neither pattern with\n// `SCHEMA_UNKNOWN_FIELD`.\nexport const VersionLiteralSchema = z.literal(1);\n\nexport const PoeRecordSchema = z.looseObject({\n v: VersionLiteralSchema,\n items: z.array(ItemEntrySchema).optional(),\n merkle: z.array(MerkleCommitSchema).optional(),\n supersedes: SupersedesSchema.optional(),\n sigs: z.array(SigEntrySchema).optional(),\n crit: z.array(z.string()).optional(),\n});\nexport type PoeRecord = z.infer<typeof PoeRecordSchema>;\n\n// =============================================================================\n// Closed top-level base-key registry\n// =============================================================================\n//\n// Used by the validator's domain pass to distinguish unknown-typo keys from\n// well-formed extension keys (`^x-.+` / `^[a-z]+-.+`).\nexport const TOP_LEVEL_BASE_KEYS: ReadonlySet<string> = new Set([\n 'v',\n 'items',\n 'merkle',\n 'supersedes',\n 'sigs',\n 'crit',\n]);\n\n// Extension-key namespaces. Anchored at both ends so an\n// embedded newline cannot smuggle a multi-segment key past the check: `.`\n// excludes `\\n` in JS, and the `\\n?$` tail tolerates exactly ONE trailing\n// newline (matching the Python validator's `re.fullmatch(r'^(x-.+|[a-z]+-.+)$')`\n// semantics, where `$` likewise admits a single trailing `\\n`). So `x-note\\n`\n// is an extension key, but `x-a\\nb`, `x-note\\n\\n`, and `x-\\n` are not.\nexport const EXTENSION_KEY_VENDOR_RE = /^x-.+\\n?$/;\nexport const EXTENSION_KEY_COMPANION_RE = /^[a-z]+-.+\\n?$/;\n\nexport function isExtensionKey(k: string): boolean {\n return EXTENSION_KEY_VENDOR_RE.test(k) || EXTENSION_KEY_COMPANION_RE.test(k);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema.ts"],"names":[],"mappings":";;;AAsCO,IAAM,0BAA0B,CAAA,CACpC,KAAA;AAAA,EACC,CAAA,CAAE,UAAA,CAAW,UAAU,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,IAAU,CAAA,IAAK,CAAA,CAAE,MAAA,IAAU,EAAA,EAAI;AAAA,IACtE,MAAA,EAAQ,EAAE,IAAA,EAAM,iBAAA;AAAkB,GACnC;AACH,CAAA,CACC,IAAI,CAAC;AAMR,IAAM,YAAA,GAAe,IAAI,WAAA,EAAY;AAC9B,IAAM,sBAAsB,CAAA,CAChC,KAAA;AAAA,EACC,CAAA,CAAE,QAAO,CAAE,MAAA;AAAA,IACT,CAAC,CAAA,KAAM;AACL,MAAA,MAAM,CAAA,GAAI,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA,CAAE,MAAA;AACjC,MAAA,OAAO,CAAA,IAAK,KAAK,CAAA,IAAK,EAAA;AAAA,IACxB,CAAA;AAAA,IACA,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,mBAAkB;AAAE;AAE1C,CAAA,CACC,IAAI,CAAC;AAgBD,IAAM,gBAAA,GAAmB,CAAA,CAAE,UAAA,CAAW,UAAU;AAEhD,IAAM,kBAAkB,CAAA,CAAE,MAAA,CAAO,CAAA,CAAE,MAAA,IAAU,gBAAgB;AAW7D,IAAM,kBAAA,GAAqB,EAC/B,MAAA,CAAO;AAAA,EACN,GAAA,EAAK,EAAE,MAAA,EAAO;AAAA,EACd,IAAA,EAAM,CAAA,CAAE,UAAA,CAAW,UAAU,CAAA;AAAA,EAC7B,YAAY,CAAA,CAAE,MAAA,GAAS,GAAA,EAAI,CAAE,IAAI,CAAC,CAAA;AAAA,EAClC,IAAA,EAAM,EAAE,KAAA,CAAM,mBAAmB,EAAE,GAAA,CAAI,CAAC,EAAE,QAAA;AAC5C,CAAC,EACA,MAAA;AA6BI,IAAM,UAAA,GAAa,EAAE,MAAA,CAAO;AAAA,EACjC,GAAA,EAAK,CAAA,CAAE,UAAA,CAAW,UAAU,EAAE,QAAA,EAAS;AAAA,EACvC,MAAA,EAAQ,wBAAwB,QAAA,EAAS;AAAA,EACzC,IAAA,EAAM,CAAA,CAAE,UAAA,CAAW,UAAU,EAAE,QAAA;AACjC,CAAC;AAQM,IAAM,oBAAA,GAAuB,EACjC,MAAA,CAAO;AAAA,EACN,CAAA,EAAG,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI;AAAA,EAClB,CAAA,EAAG,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI;AAAA,EAClB,CAAA,EAAG,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA;AAChB,CAAC,EACA,MAAA;AAWI,IAAM,qBAAA,GAAwB,EAClC,MAAA,CAAO;AAAA,EACN,GAAA,EAAK,EAAE,MAAA,EAAO;AAAA,EACd,IAAA,EAAM,EAAE,UAAA,CAAW,UAAU,EAAE,WAAA,CAAY,CAAC,OAAO,GAAA,KAAQ;AACzD,IAAA,IAAI,KAAA,CAAM,SAAS,EAAA,EAAI;AACrB,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAA,EAAM,QAAA;AAAA,QACN,MAAM,EAAC;AAAA,QACP,OAAA,EAAS,CAAA,uBAAA,EAA0B,KAAA,CAAM,MAAM,CAAA,KAAA,CAAA;AAAA,QAC/C,MAAA,EAAQ,EAAE,IAAA,EAAM,+BAAA;AAAgC,OACjD,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,EAAA,EAAI;AAC5B,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAA,EAAM,QAAA;AAAA,QACN,MAAM,EAAC;AAAA,QACP,OAAA,EAAS,CAAA,uBAAA,EAA0B,KAAA,CAAM,MAAM,CAAA,KAAA,CAAA;AAAA,QAC/C,MAAA,EAAQ,EAAE,IAAA,EAAM,8BAAA;AAA+B,OAChD,CAAA;AAAA,IACH;AAAA,EACF,CAAC,CAAA;AAAA,EACD,MAAA,EAAQ,EAAE,MAAA,CAAO,CAAA,CAAE,QAAO,EAAG,CAAA,CAAE,SAAS;AAC1C,CAAC,EACA,MAAA;AASI,IAAM,wBAAA,GAA2B,EACrC,MAAA,CAAO;AAAA,EACN,MAAA,EAAQ,EAAE,OAAA,EAAQ;AAAA,EAClB,IAAA,EAAM,EAAE,MAAA,EAAO;AAAA,EACf,GAAA,EAAK,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACzB,KAAA,EAAO,CAAA,CAAE,UAAA,CAAW,UAAU,CAAA;AAAA,EAC9B,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,UAAU,EAAE,QAAA,EAAS;AAAA,EACpC,SAAA,EAAW,CAAA,CACR,UAAA,CAAW,UAAU,CAAA,CACrB,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,EAAA,EAAI;AAAA,IAC9B,MAAA,EAAQ,EAAE,IAAA,EAAM,8BAAA;AAA+B,GAChD,EACA,QAAA,EAAS;AAAA,EACZ,UAAA,EAAY,sBAAsB,QAAA;AACpC,CAAC,EACA,MAAA;AAOI,IAAM,eAAA,GAAkB,EAC5B,MAAA,CAAO;AAAA,EACN,MAAA,EAAQ,eAAA;AAAA,EACR,IAAA,EAAM,EAAE,KAAA,CAAM,mBAAmB,EAAE,GAAA,CAAI,CAAC,EAAE,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA,EAInD,GAAA,EAAK,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA;AACnB,CAAC,EACA,MAAA;AAWI,IAAM,cAAA,GAAiB,EAC3B,MAAA,CAAO;AAAA,EACN,QAAA,EAAU,wBAAwB,QAAA,EAAS;AAAA,EAC3C,UAAA,EAAY;AACd,CAAC,EACA,MAAA;AAOI,IAAM,gBAAA,GAAmB,CAAA,CAAE,UAAA,CAAW,UAAU,CAAA,CAAE,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,EAAA,EAAI;AAAA,EACtF,MAAA,EAAQ,EAAE,IAAA,EAAM,8BAAA;AAClB,CAAC;AAgBM,IAAM,oBAAA,GAAuB,CAAA,CAAE,OAAA,CAAQ,CAAC;AAExC,IAAM,eAAA,GAAkB,EAAE,WAAA,CAAY;AAAA,EAC3C,CAAA,EAAG,oBAAA;AAAA,EACH,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,eAAe,EAAE,QAAA,EAAS;AAAA,EACzC,MAAA,EAAQ,CAAA,CAAE,KAAA,CAAM,kBAAkB,EAAE,QAAA,EAAS;AAAA,EAC7C,UAAA,EAAY,iBAAiB,QAAA,EAAS;AAAA,EACtC,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,cAAc,EAAE,QAAA,EAAS;AAAA,EACvC,MAAM,CAAA,CAAE,KAAA,CAAM,EAAE,MAAA,EAAQ,EAAE,QAAA;AAC5B,CAAC;AASM,IAAM,mBAAA,uBAA+C,GAAA,CAAI;AAAA,EAC9D,GAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAC;AAQM,IAAM,uBAAA,GAA0B;AAChC,IAAM,0BAAA,GAA6B;AAEnC,SAAS,eAAe,CAAA,EAAoB;AACjD,EAAA,OAAO,wBAAwB,IAAA,CAAK,CAAC,CAAA,IAAK,0BAAA,CAA2B,KAAK,CAAC,CAAA;AAC7E","file":"schema.js","sourcesContent":["// CIP-309 v1 PoE record Zod schemas.\n//\n// Scope: structural shape gate. The schema enforces per-field types, length\n// bounds (chunk size, digest length, supersedes length, nonce length,\n// passphrase salt length), closed-map invariants (`sigs[i]`, `slot`,\n// `passphrase`, `merkle[i]`), and the `v == 1` literal. Cross-field rules\n// (item.hashes content-hash binding when `enc` present, slots/passphrase\n// exclusivity, `crit[]` shape, registry membership of algorithm\n// identifiers, COSE_Sign1 structural decode, URI per-scheme shape rules)\n// fire in `validator.ts` so the validator can emit the precise structural\n// codes (`UNSUPPORTED_*_ALG`, `ENC_*`, `SIG_*`, `INVALID_URI`,\n// `CRIT_SHAPE_INVALID`, …) rather than a generic schema-mismatch.\n//\n// Refinements that DO live in the schema (because the validator's domain\n// pass lifts these as `SCHEMA_*` / `*_LENGTH_MISMATCH` codes directly):\n// - chunk size `[1, 64]` → `CHUNK_TOO_LARGE`\n// - 32-byte digest / 32-byte root / 32-byte supersedes → `HASH_DIGEST_LENGTH_MISMATCH`\n// / `SUPERSEDES_TX_INVALID_LENGTH`\n// - 24-byte nonce / 32-byte slots_mac →\n// `NONCE_LENGTH_MISMATCH` / `ENC_SLOTS_MAC_INVALID_LENGTH`\n// - passphrase salt 16..64 bytes → `ENC_PASSPHRASE_SALT_TOO_SHORT` /\n// `ENC_PASSPHRASE_SALT_TOO_LONG`\n//\n// Per-slot recipient lengths (`epk`, `kem_ct`, `wrap`) are NOT enforced here:\n// the required slot shape depends on the envelope-level `kem`, which a slot\n// cannot see in isolation. The KEM-driven slot descriptor in `validator.ts`\n// emits the precise `KEM_EPK_LENGTH_MISMATCH` / `KEM_CT_LENGTH_MISMATCH` /\n// `WRAP_LENGTH_MISMATCH` / `ENC_SLOT_INVALID_SHAPE` codes instead.\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Chunked-bytes / chunked-text arrays\n// =============================================================================\n\n// `[1* bstr .size (1..64)]`. A zero-length chunk (0 < 1) is rejected with the\n// SAME `CHUNK_TOO_LARGE` code as oversized chunks (any length outside\n// `[1, 64]`).\nexport const ChunkedBytesArraySchema = z\n .array(\n z.instanceof(Uint8Array).refine((b) => b.length >= 1 && b.length <= 64, {\n params: { code: 'CHUNK_TOO_LARGE' },\n }),\n )\n .min(1);\nexport type ChunkedBytesArray = z.infer<typeof ChunkedBytesArraySchema>;\n\n// `[1* tstr .size (1..64)]` — chunk byte length is the UTF-8-encoded length\n// (each `tstr` is wire-encoded as UTF-8). The `tstr .size (1..64)` pin is a\n// byte-count constraint, not a code-unit constraint.\nconst UTF8_ENCODER = new TextEncoder();\nexport const UriChunkArraySchema = z\n .array(\n z.string().refine(\n (s) => {\n const n = UTF8_ENCODER.encode(s).length;\n return n >= 1 && n <= 64;\n },\n { params: { code: 'CHUNK_TOO_LARGE' } },\n ),\n )\n .min(1);\nexport type UriChunkArray = z.infer<typeof UriChunkArraySchema>;\n\n// =============================================================================\n// Hashes map\n// =============================================================================\n//\n// `hashes` is a non-empty CBOR map keyed by content-hash algorithm identifier\n// (a CBOR text string from the content-hash registry) with the 32-byte digest\n// as value. cbor2 surfaces a text-keyed CBOR map as a plain JS object — z.record\n// admits any string key here. Both the registry-membership check\n// (`UNSUPPORTED_HASH_ALG`) and the per-algorithm digest-length check\n// (`HASH_DIGEST_LENGTH_MISMATCH`) live in the validator's domain pass so\n// each violation emits its precise code; the schema only enforces the\n// value is a CBOR byte string.\n\nexport const HashDigestSchema = z.instanceof(Uint8Array);\n\nexport const HashesMapSchema = z.record(z.string(), HashDigestSchema);\nexport type HashesMap = z.infer<typeof HashesMapSchema>;\n\n// =============================================================================\n// Top-level `merkle[]`\n// =============================================================================\n//\n// Each commit is a closed map `{alg, root, leaf_count, ? uris}`. `alg` is open\n// (registry membership is enforced in the validator's domain pass — unknown\n// identifiers emit `UNSUPPORTED_MERKLE_COMMIT_ALG`).\n\nexport const MerkleCommitSchema = z\n .object({\n alg: z.string(),\n root: z.instanceof(Uint8Array),\n leaf_count: z.number().int().min(1),\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n })\n .strict();\nexport type MerkleCommit = z.infer<typeof MerkleCommitSchema>;\n\n// =============================================================================\n// Encryption envelope\n// =============================================================================\n\n// Per-slot recipient entry. The slot shape is KEM-driven:\n//\n// - x25519: `{ epk: bstr(32), wrap: bstr(48) }` — `epk` is the\n// ephemeral X25519 public key, `wrap` is the 32-byte CEK + 16-byte\n// ChaCha20-Poly1305 tag.\n// - mlkem768x25519: `{ kem_ct: [ bstr .size (1..64) ], wrap: bstr(48) }` —\n// `kem_ct` is the 1120-byte X-Wing `enc` carried as a chunked byte-string\n// array (the same `bytes-chunk-array` shape `sigs[i].cose_sign1` uses);\n// there is NO per-slot `epk` on the hybrid path.\n//\n// The `kem` identifier is hoisted to envelope scope (a per-slot `kem` would\n// be wire-bloat). The schema is deliberately PERMISSIVE:\n// `epk`, `kem_ct`, and `wrap` are all optional and `.strict()` is NOT applied.\n// Both the per-field length checks (`KEM_EPK_LENGTH_MISMATCH`,\n// `KEM_CT_LENGTH_MISMATCH`, `WRAP_LENGTH_MISMATCH`) and the KEM-driven\n// shape gate (which field MUST/MUST NOT be present for the declared `kem`,\n// emitting `ENC_SLOT_INVALID_SHAPE`) live in the validator's domain pass —\n// the structural schema cannot know the envelope `kem` from a slot in\n// isolation, and we want the precise KEM-aware code rather than a generic\n// schema mismatch. Because `.strict()` is dropped, the domain pass MUST\n// explicitly reject cross-KEM contamination (an x25519 slot carrying\n// `kem_ct`, or a hybrid slot carrying `epk`).\nexport const SlotSchema = z.object({\n epk: z.instanceof(Uint8Array).optional(),\n kem_ct: ChunkedBytesArraySchema.optional(),\n wrap: z.instanceof(Uint8Array).optional(),\n});\nexport type Slot = z.infer<typeof SlotSchema>;\n\n// Argon2id params `{m, t, p}` are a closed map. Each value MUST be a CBOR\n// unsigned integer; the FLOOR check (`m ≥ 65536`,\n// `t ≥ 3`, `p ≥ 1`) emits `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW` in the\n// validator's domain pass — keeping it out of the schema lets us emit the\n// distinct salt-length code when salt itself is malformed too.\nexport const Argon2idParamsSchema = z\n .object({\n m: z.number().int(),\n t: z.number().int(),\n p: z.number().int(),\n })\n .strict();\nexport type Argon2idParams = z.infer<typeof Argon2idParamsSchema>;\n\n// Passphrase block. `alg` is open (registry membership checked in the\n// validator's domain pass → `ENC_PASSPHRASE_ALG_UNSUPPORTED`);\n// `params` is open here (validator narrows on the registered `alg` value and\n// emits `SCHEMA_UNKNOWN_FIELD` for extra keys, `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW`\n// for sub-floor values). `salt` length floor/ceiling are schema-layer\n// refinements with the dedicated `ENC_PASSPHRASE_SALT_TOO_SHORT/TOO_LONG`\n// codes — they belong at the schema layer because a slot cannot otherwise\n// see the salt length.\nexport const PassphraseBlockSchema = z\n .object({\n alg: z.string(),\n salt: z.instanceof(Uint8Array).superRefine((bytes, ctx) => {\n if (bytes.length < 16) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} < 16`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_SHORT' },\n });\n } else if (bytes.length > 64) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} > 64`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_LONG' },\n });\n }\n }),\n params: z.record(z.string(), z.unknown()),\n })\n .strict();\nexport type PassphraseBlock = z.infer<typeof PassphraseBlockSchema>;\n\n// Sealed-PoE envelope. The wire format admits any combination of\n// `kem` / `slots` / `slots_mac` / `passphrase` keys (permissive superset);\n// cross-field invariants (slots ⊕ passphrase, slots ↔ slots_mac, slots\n// requires kem, content-hash binding, slots non-empty) are enforced in the\n// validator's domain pass so each violation emits its typed code rather\n// than a generic shape mismatch.\nexport const EncryptionEnvelopeSchema = z\n .object({\n scheme: z.unknown(),\n aead: z.string(),\n kem: z.string().optional(),\n nonce: z.instanceof(Uint8Array),\n slots: z.array(SlotSchema).optional(),\n slots_mac: z\n .instanceof(Uint8Array)\n .refine((b) => b.length === 32, {\n params: { code: 'ENC_SLOTS_MAC_INVALID_LENGTH' },\n })\n .optional(),\n passphrase: PassphraseBlockSchema.optional(),\n })\n .strict();\nexport type EncryptionEnvelope = z.infer<typeof EncryptionEnvelopeSchema>;\n\n// =============================================================================\n// Item entry\n// =============================================================================\n\nexport const ItemEntrySchema = z\n .object({\n hashes: HashesMapSchema,\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n // Captured as `unknown` so the validator can run the\n // `ENC_REQUIRES_CONTENT_HASH` pre-check ahead of any inner-shape errors\n // and surface the most informative code first.\n enc: z.unknown().optional(),\n })\n .strict();\nexport type ItemEntry = z.infer<typeof ItemEntrySchema>;\n\n// =============================================================================\n// Sig entry\n// =============================================================================\n//\n// Closed CBOR map `{cose_sign1, ? cose_key}`. Canonical CBOR map-key sort\n// (RFC 8949 §4.2.1, bytewise lex on encoded keys) places `cose_key`\n// (length-8 tstr, `0x68`) BEFORE `cose_sign1` (length-10 tstr, `0x6a`); the\n// schema property-order is irrelevant — the canonical encoder handles it.\nexport const SigEntrySchema = z\n .object({\n cose_key: ChunkedBytesArraySchema.optional(),\n cose_sign1: ChunkedBytesArraySchema,\n })\n .strict();\nexport type SigEntry = z.infer<typeof SigEntrySchema>;\n\n// =============================================================================\n// Supersedence\n// =============================================================================\n\nexport const SupersedesSchema = z.instanceof(Uint8Array).refine((b) => b.length === 32, {\n params: { code: 'SUPERSEDES_TX_INVALID_LENGTH' },\n});\nexport type Supersedes = z.infer<typeof SupersedesSchema>;\n\n// =============================================================================\n// Top-level record\n// =============================================================================\n//\n// `v == 1` is a literal — a future major (`v: 2`) MUST be rejected with\n// `SCHEMA_INVALID_LITERAL`. `z.literal(1)` preserves the narrow `1` type for\n// the inferred `PoeRecord[\"v\"]` (so consumers can dispatch on it) and emits\n// Zod's `invalid_value` code which the validator's mapper lifts to\n// `SCHEMA_INVALID_LITERAL`.\n//\n// `looseObject` admits extension keys (matching `^x-.+` or `^[a-z]+-.+`); the\n// validator's domain pass rejects unknown keys that match neither pattern with\n// `SCHEMA_UNKNOWN_FIELD`.\nexport const VersionLiteralSchema = z.literal(1);\n\nexport const PoeRecordSchema = z.looseObject({\n v: VersionLiteralSchema,\n items: z.array(ItemEntrySchema).optional(),\n merkle: z.array(MerkleCommitSchema).optional(),\n supersedes: SupersedesSchema.optional(),\n sigs: z.array(SigEntrySchema).optional(),\n crit: z.array(z.string()).optional(),\n});\nexport type PoeRecord = z.infer<typeof PoeRecordSchema>;\n\n// =============================================================================\n// Closed top-level base-key registry\n// =============================================================================\n//\n// Used by the validator's domain pass to distinguish unknown-typo keys from\n// well-formed extension keys (`^x-.+` / `^[a-z]+-.+`).\nexport const TOP_LEVEL_BASE_KEYS: ReadonlySet<string> = new Set([\n 'v',\n 'items',\n 'merkle',\n 'supersedes',\n 'sigs',\n 'crit',\n]);\n\n// Extension-key namespaces. Anchored at both ends so an\n// embedded newline cannot smuggle a multi-segment key past the check: `.`\n// excludes `\\n` in JS, and the `\\n?$` tail tolerates exactly ONE trailing\n// newline (matching the Python validator's `re.fullmatch(r'^(x-.+|[a-z]+-.+)$')`\n// semantics, where `$` likewise admits a single trailing `\\n`). So `x-note\\n`\n// is an extension key, but `x-a\\nb`, `x-note\\n\\n`, and `x-\\n` are not.\nexport const EXTENSION_KEY_VENDOR_RE = /^x-.+\\n?$/;\nexport const EXTENSION_KEY_COMPANION_RE = /^[a-z]+-.+\\n?$/;\n\nexport function isExtensionKey(k: string): boolean {\n return EXTENSION_KEY_VENDOR_RE.test(k) || EXTENSION_KEY_COMPANION_RE.test(k);\n}\n"]}
1
+ {"version":3,"sources":["../src/schema.ts"],"names":[],"mappings":"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","file":"schema.js","sourcesContent":["// Label 309 v1 PoE record Zod schemas.\n//\n// Scope: structural shape gate. The schema enforces per-field types, length\n// bounds (chunk size, digest length, supersedes length, nonce length,\n// passphrase salt length), closed-map invariants (`sigs[i]`, `slot`,\n// `passphrase`, `merkle[i]`), and the `v == 1` literal. Cross-field rules\n// (item.hashes content-hash binding when `enc` present, slots/passphrase\n// exclusivity, `crit[]` shape, registry membership of algorithm\n// identifiers, COSE_Sign1 structural decode, URI per-scheme shape rules)\n// fire in `validator.ts` so the validator can emit the precise structural\n// codes (`UNSUPPORTED_*_ALG`, `ENC_*`, `SIG_*`, `INVALID_URI`,\n// `CRIT_SHAPE_INVALID`, …) rather than a generic schema-mismatch.\n//\n// Refinements that DO live in the schema (because the validator's domain\n// pass lifts these as `SCHEMA_*` / `*_LENGTH_MISMATCH` codes directly):\n// - chunk size `[1, 64]` → `CHUNK_TOO_LARGE`\n// - 32-byte digest / 32-byte root / 32-byte supersedes → `HASH_DIGEST_LENGTH_MISMATCH`\n// / `SUPERSEDES_TX_INVALID_LENGTH`\n// - 24-byte nonce / 32-byte slots_mac →\n// `NONCE_LENGTH_MISMATCH` / `ENC_SLOTS_MAC_INVALID_LENGTH`\n// - passphrase salt 16..64 bytes → `ENC_PASSPHRASE_SALT_TOO_SHORT` /\n// `ENC_PASSPHRASE_SALT_TOO_LONG`\n//\n// Per-slot recipient lengths (`epk`, `kem_ct`, `wrap`) are NOT enforced here:\n// the required slot shape depends on the envelope-level `kem`, which a slot\n// cannot see in isolation. The KEM-driven slot descriptor in `validator.ts`\n// emits the precise `KEM_EPK_LENGTH_MISMATCH` / `KEM_CT_LENGTH_MISMATCH` /\n// `WRAP_LENGTH_MISMATCH` / `ENC_SLOT_INVALID_SHAPE` codes instead.\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Chunked-bytes / chunked-text arrays\n// =============================================================================\n\n// `[1* bstr .size (1..64)]`. A zero-length chunk (0 < 1) is rejected with the\n// SAME `CHUNK_TOO_LARGE` code as oversized chunks (any length outside\n// `[1, 64]`).\nexport const ChunkedBytesArraySchema = z\n .array(\n z.instanceof(Uint8Array).refine((b) => b.length >= 1 && b.length <= 64, {\n params: { code: 'CHUNK_TOO_LARGE' },\n }),\n )\n .min(1);\nexport type ChunkedBytesArray = z.infer<typeof ChunkedBytesArraySchema>;\n\n// `[1* tstr .size (1..64)]` — chunk byte length is the UTF-8-encoded length\n// (each `tstr` is wire-encoded as UTF-8). The `tstr .size (1..64)` pin is a\n// byte-count constraint, not a code-unit constraint.\nconst UTF8_ENCODER = new TextEncoder();\nexport const UriChunkArraySchema = z\n .array(\n z.string().refine(\n (s) => {\n const n = UTF8_ENCODER.encode(s).length;\n return n >= 1 && n <= 64;\n },\n { params: { code: 'CHUNK_TOO_LARGE' } },\n ),\n )\n .min(1);\nexport type UriChunkArray = z.infer<typeof UriChunkArraySchema>;\n\n// =============================================================================\n// Hashes map\n// =============================================================================\n//\n// `hashes` is a non-empty CBOR map keyed by content-hash algorithm identifier\n// (a CBOR text string from the content-hash registry) with the 32-byte digest\n// as value. cbor2 surfaces a text-keyed CBOR map as a plain JS object — z.record\n// admits any string key here. Both the registry-membership check\n// (`UNSUPPORTED_HASH_ALG`) and the per-algorithm digest-length check\n// (`HASH_DIGEST_LENGTH_MISMATCH`) live in the validator's domain pass so\n// each violation emits its precise code; the schema only enforces the\n// value is a CBOR byte string.\n\nexport const HashDigestSchema = z.instanceof(Uint8Array);\n\nexport const HashesMapSchema = z.record(z.string(), HashDigestSchema);\nexport type HashesMap = z.infer<typeof HashesMapSchema>;\n\n// =============================================================================\n// Top-level `merkle[]`\n// =============================================================================\n//\n// Each commit is a closed map `{alg, root, leaf_count, ? uris}`. `alg` is open\n// (registry membership is enforced in the validator's domain pass — unknown\n// identifiers emit `UNSUPPORTED_MERKLE_COMMIT_ALG`).\n\nexport const MerkleCommitSchema = z\n .object({\n alg: z.string(),\n root: z.instanceof(Uint8Array),\n leaf_count: z.number().int().min(1),\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n })\n .strict();\nexport type MerkleCommit = z.infer<typeof MerkleCommitSchema>;\n\n// =============================================================================\n// Encryption envelope\n// =============================================================================\n\n// Per-slot recipient entry. The slot shape is KEM-driven:\n//\n// - x25519: `{ epk: bstr(32), wrap: bstr(48) }` — `epk` is the\n// ephemeral X25519 public key, `wrap` is the 32-byte CEK + 16-byte\n// ChaCha20-Poly1305 tag.\n// - mlkem768x25519: `{ kem_ct: [ bstr .size (1..64) ], wrap: bstr(48) }` —\n// `kem_ct` is the 1120-byte X-Wing `enc` carried as a chunked byte-string\n// array (the same `bytes-chunk-array` shape `sigs[i].cose_sign1` uses);\n// there is NO per-slot `epk` on the hybrid path.\n//\n// The `kem` identifier is hoisted to envelope scope (a per-slot `kem` would\n// be wire-bloat). The schema is deliberately PERMISSIVE:\n// `epk`, `kem_ct`, and `wrap` are all optional and `.strict()` is NOT applied.\n// Both the per-field length checks (`KEM_EPK_LENGTH_MISMATCH`,\n// `KEM_CT_LENGTH_MISMATCH`, `WRAP_LENGTH_MISMATCH`) and the KEM-driven\n// shape gate (which field MUST/MUST NOT be present for the declared `kem`,\n// emitting `ENC_SLOT_INVALID_SHAPE`) live in the validator's domain pass —\n// the structural schema cannot know the envelope `kem` from a slot in\n// isolation, and we want the precise KEM-aware code rather than a generic\n// schema mismatch. Because `.strict()` is dropped, the domain pass MUST\n// explicitly reject cross-KEM contamination (an x25519 slot carrying\n// `kem_ct`, or a hybrid slot carrying `epk`).\nexport const SlotSchema = z.object({\n epk: z.instanceof(Uint8Array).optional(),\n kem_ct: ChunkedBytesArraySchema.optional(),\n wrap: z.instanceof(Uint8Array).optional(),\n});\nexport type Slot = z.infer<typeof SlotSchema>;\n\n// Argon2id params `{m, t, p}` are a closed map. Each value MUST be a CBOR\n// unsigned integer; the FLOOR check (`m ≥ 65536`,\n// `t ≥ 3`, `p ≥ 1`) emits `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW` in the\n// validator's domain pass — keeping it out of the schema lets us emit the\n// distinct salt-length code when salt itself is malformed too.\nexport const Argon2idParamsSchema = z\n .object({\n m: z.number().int(),\n t: z.number().int(),\n p: z.number().int(),\n })\n .strict();\nexport type Argon2idParams = z.infer<typeof Argon2idParamsSchema>;\n\n// Passphrase block. `alg` is open (registry membership checked in the\n// validator's domain pass → `ENC_PASSPHRASE_ALG_UNSUPPORTED`);\n// `params` is open here (validator narrows on the registered `alg` value and\n// emits `SCHEMA_UNKNOWN_FIELD` for extra keys, `ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW`\n// for sub-floor values). `salt` length floor/ceiling are schema-layer\n// refinements with the dedicated `ENC_PASSPHRASE_SALT_TOO_SHORT/TOO_LONG`\n// codes — they belong at the schema layer because a slot cannot otherwise\n// see the salt length.\nexport const PassphraseBlockSchema = z\n .object({\n alg: z.string(),\n salt: z.instanceof(Uint8Array).superRefine((bytes, ctx) => {\n if (bytes.length < 16) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} < 16`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_SHORT' },\n });\n } else if (bytes.length > 64) {\n ctx.addIssue({\n code: 'custom',\n path: [],\n message: `passphrase.salt length ${bytes.length} > 64`,\n params: { code: 'ENC_PASSPHRASE_SALT_TOO_LONG' },\n });\n }\n }),\n params: z.record(z.string(), z.unknown()),\n })\n .strict();\nexport type PassphraseBlock = z.infer<typeof PassphraseBlockSchema>;\n\n// Sealed-PoE envelope. The wire format admits any combination of\n// `kem` / `slots` / `slots_mac` / `passphrase` keys (permissive superset);\n// cross-field invariants (slots ⊕ passphrase, slots ↔ slots_mac, slots\n// requires kem, content-hash binding, slots non-empty) are enforced in the\n// validator's domain pass so each violation emits its typed code rather\n// than a generic shape mismatch.\nexport const EncryptionEnvelopeSchema = z\n .object({\n scheme: z.unknown(),\n aead: z.string(),\n kem: z.string().optional(),\n nonce: z.instanceof(Uint8Array),\n slots: z.array(SlotSchema).optional(),\n slots_mac: z\n .instanceof(Uint8Array)\n .refine((b) => b.length === 32, {\n params: { code: 'ENC_SLOTS_MAC_INVALID_LENGTH' },\n })\n .optional(),\n passphrase: PassphraseBlockSchema.optional(),\n })\n .strict();\nexport type EncryptionEnvelope = z.infer<typeof EncryptionEnvelopeSchema>;\n\n// =============================================================================\n// Item entry\n// =============================================================================\n\nexport const ItemEntrySchema = z\n .object({\n hashes: HashesMapSchema,\n uris: z.array(UriChunkArraySchema).min(1).optional(),\n // Captured as `unknown` so the validator can run the\n // `ENC_REQUIRES_CONTENT_HASH` pre-check ahead of any inner-shape errors\n // and surface the most informative code first.\n enc: z.unknown().optional(),\n })\n .strict();\nexport type ItemEntry = z.infer<typeof ItemEntrySchema>;\n\n// =============================================================================\n// Sig entry\n// =============================================================================\n//\n// Closed CBOR map `{cose_sign1, ? cose_key}`. Canonical CBOR map-key sort\n// (RFC 8949 §4.2.1, bytewise lex on encoded keys) places `cose_key`\n// (length-8 tstr, `0x68`) BEFORE `cose_sign1` (length-10 tstr, `0x6a`); the\n// schema property-order is irrelevant — the canonical encoder handles it.\nexport const SigEntrySchema = z\n .object({\n cose_key: ChunkedBytesArraySchema.optional(),\n cose_sign1: ChunkedBytesArraySchema,\n })\n .strict();\nexport type SigEntry = z.infer<typeof SigEntrySchema>;\n\n// =============================================================================\n// Supersedence\n// =============================================================================\n\nexport const SupersedesSchema = z.instanceof(Uint8Array).refine((b) => b.length === 32, {\n params: { code: 'SUPERSEDES_TX_INVALID_LENGTH' },\n});\nexport type Supersedes = z.infer<typeof SupersedesSchema>;\n\n// =============================================================================\n// Top-level record\n// =============================================================================\n//\n// `v == 1` is a literal — a future major (`v: 2`) MUST be rejected with\n// `SCHEMA_INVALID_LITERAL`. `z.literal(1)` preserves the narrow `1` type for\n// the inferred `PoeRecord[\"v\"]` (so consumers can dispatch on it) and emits\n// Zod's `invalid_value` code which the validator's mapper lifts to\n// `SCHEMA_INVALID_LITERAL`.\n//\n// `looseObject` admits extension keys (matching `^x-.+` or `^[a-z]+-.+`); the\n// validator's domain pass rejects unknown keys that match neither pattern with\n// `SCHEMA_UNKNOWN_FIELD`.\nexport const VersionLiteralSchema = z.literal(1);\n\nexport const PoeRecordSchema = z.looseObject({\n v: VersionLiteralSchema,\n items: z.array(ItemEntrySchema).optional(),\n merkle: z.array(MerkleCommitSchema).optional(),\n supersedes: SupersedesSchema.optional(),\n sigs: z.array(SigEntrySchema).optional(),\n crit: z.array(z.string()).optional(),\n});\nexport type PoeRecord = z.infer<typeof PoeRecordSchema>;\n\n// =============================================================================\n// Closed top-level base-key registry\n// =============================================================================\n//\n// Used by the validator's domain pass to distinguish unknown-typo keys from\n// well-formed extension keys (`^x-.+` / `^[a-z]+-.+`).\nexport const TOP_LEVEL_BASE_KEYS: ReadonlySet<string> = new Set([\n 'v',\n 'items',\n 'merkle',\n 'supersedes',\n 'sigs',\n 'crit',\n]);\n\n// Extension-key namespaces. Anchored at both ends so an\n// embedded newline cannot smuggle a multi-segment key past the check: `.`\n// excludes `\\n` in JS, and the `\\n?$` tail tolerates exactly ONE trailing\n// newline (matching the Python validator's `re.fullmatch(r'^(x-.+|[a-z]+-.+)$')`\n// semantics, where `$` likewise admits a single trailing `\\n`). So `x-note\\n`\n// is an extension key, but `x-a\\nb`, `x-note\\n\\n`, and `x-\\n` are not.\nexport const EXTENSION_KEY_VENDOR_RE = /^x-.+\\n?$/;\nexport const EXTENSION_KEY_COMPANION_RE = /^[a-z]+-.+\\n?$/;\n\nexport function isExtensionKey(k: string): boolean {\n return EXTENSION_KEY_VENDOR_RE.test(k) || EXTENSION_KEY_COMPANION_RE.test(k);\n}\n"]}
@@ -1627,7 +1627,7 @@ function decodeCanonicalCbor(bytes) {
1627
1627
  ...r,
1628
1628
  rejectStreaming: true,
1629
1629
  rejectDuplicateKeys: true,
1630
- // A CIP-309 record carries integers, byte/text strings, arrays, maps and
1630
+ // A Label 309 record carries integers, byte/text strings, arrays, maps and
1631
1631
  // `null` — and nothing else. Without these rejections the major-type-7
1632
1632
  // surface leaks into the decoder: a float16/32/64 that happens to hold an
1633
1633
  // integral value (e.g. 1.0) silently decodes to the integer 1 and passes
@@ -2078,7 +2078,7 @@ function decodeCanonicalCbor2(bytes) {
2078
2078
  ...r,
2079
2079
  rejectStreaming: true,
2080
2080
  rejectDuplicateKeys: true,
2081
- // A CIP-309 record carries integers, byte/text strings, arrays, maps and
2081
+ // A Label 309 record carries integers, byte/text strings, arrays, maps and
2082
2082
  // `null` — and nothing else. Without these rejections the major-type-7
2083
2083
  // surface leaks into the decoder: a float16/32/64 that happens to hold an
2084
2084
  // integral value (e.g. 1.0) silently decodes to the integer 1 and passes
@@ -2622,7 +2622,7 @@ function validateOneUri(chunks, path, errors) {
2622
2622
  const cid = slashIdx === -1 ? rest : rest.slice(0, slashIdx);
2623
2623
  if (!validateCidProfile(cid)) {
2624
2624
  errors.push(
2625
- issue("INVALID_URI", path, "ipfs:// URI is not a valid CID under the CIP-309 profile")
2625
+ issue("INVALID_URI", path, "ipfs:// URI is not a valid CID under the Label 309 profile")
2626
2626
  );
2627
2627
  }
2628
2628
  return;
@@ -2670,7 +2670,7 @@ function checkItemEnc(item, idx, errors) {
2670
2670
  issue(
2671
2671
  "UNAUTHENTICATED_CIPHER_FORBIDDEN",
2672
2672
  [...basePath, "aead"],
2673
- `'${enc.aead}' is an unauthenticated cipher; CIP-309 mandates an authenticated (AEAD) cipher`
2673
+ `'${enc.aead}' is an unauthenticated cipher; Label 309 mandates an authenticated (AEAD) cipher`
2674
2674
  )
2675
2675
  );
2676
2676
  return;