@cardanowall/poe-standard 0.1.0 → 0.3.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":"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","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":"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","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
@@ -2185,6 +2185,60 @@ function decodeCoseSign1(bytes) {
2185
2185
  };
2186
2186
  }
2187
2187
 
2188
+ // ../crypto-core/dist/sealed-poe.js
2189
+ var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
2190
+ "cardano-poe-slots-transcript-v1"
2191
+ );
2192
+ var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
2193
+ "cardano-poe-payload-v1"
2194
+ );
2195
+ var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
2196
+ "cardano-poe-payload-passphrase-v1"
2197
+ );
2198
+ var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
2199
+ "cardano-poe-xwing-kek-salt-v1"
2200
+ );
2201
+ if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
2202
+ throw new Error(
2203
+ "CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
2204
+ );
2205
+ }
2206
+ if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
2207
+ throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
2208
+ }
2209
+ if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
2210
+ throw new Error(
2211
+ "CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
2212
+ );
2213
+ }
2214
+ if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
2215
+ throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
2216
+ }
2217
+ var MAX_SLOTS = 1024;
2218
+ var MAX_DECODED_ENVELOPE_BYTES = 65536;
2219
+ var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
2220
+ var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
2221
+ "cardano-poe-kek-mlkem768x25519-v1"
2222
+ );
2223
+ var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
2224
+ "cardano-poe-slots-mac-v1"
2225
+ );
2226
+ var ZERO_NONCE_12 = new Uint8Array(12);
2227
+ if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
2228
+ throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
2229
+ }
2230
+ if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
2231
+ throw new Error(
2232
+ "CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
2233
+ );
2234
+ }
2235
+ if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
2236
+ throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
2237
+ }
2238
+ if (ZERO_NONCE_12.length !== 12) {
2239
+ throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
2240
+ }
2241
+
2188
2242
  // src/chunked.ts
2189
2243
  var UTF8_ENCODER = new TextEncoder();
2190
2244
  function bytesChunkArrayConcat(chunks) {
@@ -2230,6 +2284,9 @@ var SEVERITY = Object.freeze({
2230
2284
  UNSUPPORTED_ENVELOPE_SCHEME: "error",
2231
2285
  ENC_SLOTS_EMPTY: "error",
2232
2286
  ENC_SLOT_INVALID_SHAPE: "error",
2287
+ ENC_SLOTS_DUPLICATE_KEM_MATERIAL: "error",
2288
+ ENC_SLOTS_TOO_MANY: "error",
2289
+ ENC_ENVELOPE_TOO_LARGE: "error",
2233
2290
  UNSUPPORTED_KEM_ALG: "error",
2234
2291
  ENC_KEM_REQUIRED: "error",
2235
2292
  KEM_EPK_LENGTH_MISMATCH: "error",
@@ -2409,6 +2466,8 @@ var KEM_FIELD_LENGTH_CODE = {
2409
2466
  epk: "KEM_EPK_LENGTH_MISMATCH",
2410
2467
  kem_ct: "KEM_CT_LENGTH_MISMATCH"
2411
2468
  };
2469
+ var NONCE_LENGTH = 24;
2470
+ var SLOTS_MAC_LENGTH = 32;
2412
2471
  var PASSPHRASE_KDF_ALGS = /* @__PURE__ */ new Set(["argon2id"]);
2413
2472
  var KNOWN_SIG_ALG_IDS = /* @__PURE__ */ new Set([-8, -19]);
2414
2473
  function validatePoeRecord(bytes) {
@@ -2622,7 +2681,7 @@ function validateOneUri(chunks, path, errors) {
2622
2681
  const cid = slashIdx === -1 ? rest : rest.slice(0, slashIdx);
2623
2682
  if (!validateCidProfile(cid)) {
2624
2683
  errors.push(
2625
- issue("INVALID_URI", path, "ipfs:// URI is not a valid CID under the CIP-309 profile")
2684
+ issue("INVALID_URI", path, "ipfs:// URI is not a valid CID under the Label 309 profile")
2626
2685
  );
2627
2686
  }
2628
2687
  return;
@@ -2670,7 +2729,7 @@ function checkItemEnc(item, idx, errors) {
2670
2729
  issue(
2671
2730
  "UNAUTHENTICATED_CIPHER_FORBIDDEN",
2672
2731
  [...basePath, "aead"],
2673
- `'${enc.aead}' is an unauthenticated cipher; CIP-309 mandates an authenticated (AEAD) cipher`
2732
+ `'${enc.aead}' is an unauthenticated cipher; Label 309 mandates an authenticated (AEAD) cipher`
2674
2733
  )
2675
2734
  );
2676
2735
  return;
@@ -2725,24 +2784,62 @@ function checkItemEnc(item, idx, errors) {
2725
2784
  );
2726
2785
  }
2727
2786
  if (hasSlots) {
2728
- if (enc.slots.length < 1) {
2787
+ const slotCount = enc.slots.length;
2788
+ if (slotCount < 1) {
2729
2789
  errors.push(
2730
- issue("ENC_SLOTS_EMPTY", [...basePath, "slots"], `slots length ${enc.slots.length} < 1`)
2790
+ issue("ENC_SLOTS_EMPTY", [...basePath, "slots"], `slots length ${slotCount} < 1`)
2731
2791
  );
2732
- }
2733
- const descriptor = enc.kem !== void 0 ? KEM_SLOT_DESCRIPTORS[enc.kem] : void 0;
2734
- if (descriptor !== void 0) {
2735
- const rawSlotKeys = rawSlotKeySets(item.enc);
2736
- enc.slots.forEach((slot, si) => {
2737
- checkSlotShape(
2738
- slot,
2739
- rawSlotKeys[si] ?? /* @__PURE__ */ new Set(),
2740
- descriptor,
2741
- enc.kem,
2742
- [...basePath, "slots", si],
2743
- errors
2744
- );
2745
- });
2792
+ } else if (slotCount > MAX_SLOTS) {
2793
+ errors.push(
2794
+ issue(
2795
+ "ENC_SLOTS_TOO_MANY",
2796
+ [...basePath, "slots"],
2797
+ `slots length ${slotCount} exceeds MAX_SLOTS=${MAX_SLOTS}`
2798
+ )
2799
+ );
2800
+ } else {
2801
+ const descriptor = enc.kem !== void 0 ? KEM_SLOT_DESCRIPTORS[enc.kem] : void 0;
2802
+ if (descriptor !== void 0) {
2803
+ const rawSlotKeys = rawSlotKeySets(item.enc);
2804
+ const seenKemMaterial = /* @__PURE__ */ new Set();
2805
+ enc.slots.forEach((slot, si) => {
2806
+ const slotPath = [...basePath, "slots", si];
2807
+ checkSlotShape(
2808
+ slot,
2809
+ rawSlotKeys[si] ?? /* @__PURE__ */ new Set(),
2810
+ descriptor,
2811
+ enc.kem,
2812
+ slotPath,
2813
+ errors
2814
+ );
2815
+ const material = slotKemMaterial(slot, descriptor);
2816
+ if (material !== void 0) {
2817
+ const key = bytesToHex2(material);
2818
+ if (seenKemMaterial.has(key)) {
2819
+ errors.push(
2820
+ issue(
2821
+ "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
2822
+ [...slotPath, descriptor.field],
2823
+ `slot ${si} ${descriptor.field} duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
2824
+ )
2825
+ );
2826
+ } else {
2827
+ seenKemMaterial.add(key);
2828
+ }
2829
+ }
2830
+ });
2831
+ const perSlotBytes = descriptor.fieldLength + descriptor.wrapLength;
2832
+ const decodedEnvelopeBytes = NONCE_LENGTH + SLOTS_MAC_LENGTH + slotCount * perSlotBytes;
2833
+ if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
2834
+ errors.push(
2835
+ issue(
2836
+ "ENC_ENVELOPE_TOO_LARGE",
2837
+ [...basePath, "slots"],
2838
+ `decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
2839
+ )
2840
+ );
2841
+ }
2842
+ }
2746
2843
  }
2747
2844
  }
2748
2845
  if (hasPassphrase) {
@@ -2899,6 +2996,20 @@ function checkSlotShape(slot, rawKeys, descriptor, kem, slotPath, errors) {
2899
2996
  );
2900
2997
  }
2901
2998
  }
2999
+ function slotKemMaterial(slot, descriptor) {
3000
+ if (descriptor.field === "epk") {
3001
+ return slot.epk;
3002
+ }
3003
+ if (slot.kem_ct === void 0) return void 0;
3004
+ return bytesChunkArrayConcat(slot.kem_ct);
3005
+ }
3006
+ function bytesToHex2(bytes) {
3007
+ let hex = "";
3008
+ for (let i = 0; i < bytes.length; i++) {
3009
+ hex += bytes[i].toString(16).padStart(2, "0");
3010
+ }
3011
+ return hex;
3012
+ }
2902
3013
  function rawSlotKeySets(rawEnc) {
2903
3014
  const slots = mapLikeGet(rawEnc, "slots");
2904
3015
  if (!Array.isArray(slots)) return [];