@blamejs/blamejs-shop 0.1.32 → 0.1.33

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +2 -0
  3. package/lib/admin.js +18 -10
  4. package/lib/asset-manifest.json +17 -5
  5. package/lib/storefront.js +326 -44
  6. package/lib/vendor/MANIFEST.json +2 -2
  7. package/lib/vendor/blamejs/CHANGELOG.md +8 -0
  8. package/lib/vendor/blamejs/README.md +3 -1
  9. package/lib/vendor/blamejs/api-snapshot.json +99 -2
  10. package/lib/vendor/blamejs/index.js +3 -0
  11. package/lib/vendor/blamejs/lib/crypto-oprf.js +110 -0
  12. package/lib/vendor/blamejs/lib/network-tsig.js +404 -0
  13. package/lib/vendor/blamejs/lib/network.js +1 -0
  14. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +44 -9
  15. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +19 -0
  16. package/lib/vendor/blamejs/lib/worm.js +246 -0
  17. package/lib/vendor/blamejs/package.json +1 -1
  18. package/lib/vendor/blamejs/release-notes/v0.12.x.json +1844 -0
  19. package/lib/vendor/blamejs/release-notes/v0.13.0.json +22 -0
  20. package/lib/vendor/blamejs/release-notes/v0.13.1.json +18 -0
  21. package/lib/vendor/blamejs/scripts/vendor-update.sh +11 -1
  22. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +3 -1
  23. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +101 -0
  24. package/lib/vendor/blamejs/test/layer-0-primitives/network-tsig.test.js +149 -0
  25. package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +2 -2
  26. package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +3 -3
  27. package/lib/vendor/blamejs/test/layer-0-primitives/worm.test.js +126 -0
  28. package/package.json +2 -2
  29. package/lib/vendor/blamejs/release-notes/v0.12.0.json +0 -64
  30. package/lib/vendor/blamejs/release-notes/v0.12.1.json +0 -32
  31. package/lib/vendor/blamejs/release-notes/v0.12.10.json +0 -65
  32. package/lib/vendor/blamejs/release-notes/v0.12.11.json +0 -39
  33. package/lib/vendor/blamejs/release-notes/v0.12.12.json +0 -48
  34. package/lib/vendor/blamejs/release-notes/v0.12.13.json +0 -31
  35. package/lib/vendor/blamejs/release-notes/v0.12.14.json +0 -18
  36. package/lib/vendor/blamejs/release-notes/v0.12.15.json +0 -27
  37. package/lib/vendor/blamejs/release-notes/v0.12.16.json +0 -18
  38. package/lib/vendor/blamejs/release-notes/v0.12.17.json +0 -22
  39. package/lib/vendor/blamejs/release-notes/v0.12.18.json +0 -22
  40. package/lib/vendor/blamejs/release-notes/v0.12.19.json +0 -22
  41. package/lib/vendor/blamejs/release-notes/v0.12.2.json +0 -45
  42. package/lib/vendor/blamejs/release-notes/v0.12.20.json +0 -18
  43. package/lib/vendor/blamejs/release-notes/v0.12.21.json +0 -27
  44. package/lib/vendor/blamejs/release-notes/v0.12.22.json +0 -18
  45. package/lib/vendor/blamejs/release-notes/v0.12.23.json +0 -18
  46. package/lib/vendor/blamejs/release-notes/v0.12.24.json +0 -18
  47. package/lib/vendor/blamejs/release-notes/v0.12.25.json +0 -18
  48. package/lib/vendor/blamejs/release-notes/v0.12.26.json +0 -30
  49. package/lib/vendor/blamejs/release-notes/v0.12.27.json +0 -26
  50. package/lib/vendor/blamejs/release-notes/v0.12.28.json +0 -26
  51. package/lib/vendor/blamejs/release-notes/v0.12.29.json +0 -31
  52. package/lib/vendor/blamejs/release-notes/v0.12.3.json +0 -23
  53. package/lib/vendor/blamejs/release-notes/v0.12.30.json +0 -18
  54. package/lib/vendor/blamejs/release-notes/v0.12.31.json +0 -18
  55. package/lib/vendor/blamejs/release-notes/v0.12.32.json +0 -27
  56. package/lib/vendor/blamejs/release-notes/v0.12.33.json +0 -31
  57. package/lib/vendor/blamejs/release-notes/v0.12.34.json +0 -18
  58. package/lib/vendor/blamejs/release-notes/v0.12.35.json +0 -22
  59. package/lib/vendor/blamejs/release-notes/v0.12.36.json +0 -18
  60. package/lib/vendor/blamejs/release-notes/v0.12.37.json +0 -27
  61. package/lib/vendor/blamejs/release-notes/v0.12.38.json +0 -18
  62. package/lib/vendor/blamejs/release-notes/v0.12.39.json +0 -18
  63. package/lib/vendor/blamejs/release-notes/v0.12.4.json +0 -19
  64. package/lib/vendor/blamejs/release-notes/v0.12.40.json +0 -18
  65. package/lib/vendor/blamejs/release-notes/v0.12.41.json +0 -18
  66. package/lib/vendor/blamejs/release-notes/v0.12.42.json +0 -18
  67. package/lib/vendor/blamejs/release-notes/v0.12.43.json +0 -18
  68. package/lib/vendor/blamejs/release-notes/v0.12.44.json +0 -18
  69. package/lib/vendor/blamejs/release-notes/v0.12.45.json +0 -18
  70. package/lib/vendor/blamejs/release-notes/v0.12.46.json +0 -18
  71. package/lib/vendor/blamejs/release-notes/v0.12.47.json +0 -18
  72. package/lib/vendor/blamejs/release-notes/v0.12.48.json +0 -22
  73. package/lib/vendor/blamejs/release-notes/v0.12.49.json +0 -31
  74. package/lib/vendor/blamejs/release-notes/v0.12.5.json +0 -40
  75. package/lib/vendor/blamejs/release-notes/v0.12.50.json +0 -18
  76. package/lib/vendor/blamejs/release-notes/v0.12.51.json +0 -18
  77. package/lib/vendor/blamejs/release-notes/v0.12.52.json +0 -18
  78. package/lib/vendor/blamejs/release-notes/v0.12.53.json +0 -18
  79. package/lib/vendor/blamejs/release-notes/v0.12.54.json +0 -18
  80. package/lib/vendor/blamejs/release-notes/v0.12.55.json +0 -18
  81. package/lib/vendor/blamejs/release-notes/v0.12.56.json +0 -27
  82. package/lib/vendor/blamejs/release-notes/v0.12.57.json +0 -18
  83. package/lib/vendor/blamejs/release-notes/v0.12.58.json +0 -22
  84. package/lib/vendor/blamejs/release-notes/v0.12.6.json +0 -45
  85. package/lib/vendor/blamejs/release-notes/v0.12.60.json +0 -18
  86. package/lib/vendor/blamejs/release-notes/v0.12.61.json +0 -18
  87. package/lib/vendor/blamejs/release-notes/v0.12.62.json +0 -18
  88. package/lib/vendor/blamejs/release-notes/v0.12.63.json +0 -27
  89. package/lib/vendor/blamejs/release-notes/v0.12.64.json +0 -18
  90. package/lib/vendor/blamejs/release-notes/v0.12.65.json +0 -27
  91. package/lib/vendor/blamejs/release-notes/v0.12.66.json +0 -18
  92. package/lib/vendor/blamejs/release-notes/v0.12.68.json +0 -27
  93. package/lib/vendor/blamejs/release-notes/v0.12.69.json +0 -27
  94. package/lib/vendor/blamejs/release-notes/v0.12.7.json +0 -86
  95. package/lib/vendor/blamejs/release-notes/v0.12.8.json +0 -81
  96. package/lib/vendor/blamejs/release-notes/v0.12.9.json +0 -61
@@ -1,26 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.28",
4
- "date": "2026-05-24",
5
- "headline": "`b.ai.capability` — model-capability registry + cheapest-satisfying-model router",
6
- "summary": "`b.ai.capability.create({ models })` turns a fleet of AI model descriptors into a routing decision: given a set of requirements (context window, input/output modalities, tool use, structured output, reasoning tier, citation support, prompt-caching size), it picks the cheapest model that satisfies all of them. NIST AI RMF (AI 100-1) MAP 2.x requires documenting each model's capabilities and limitations; the Model Cards convention (Mitchell et al., 2019) formalizes that descriptor — this primitive makes the descriptor actionable. Routing to the cheapest sufficient model is a front-line defense against over-provisioning spend and composes directly with `b.ai.quota`'s `cost-usd` dimension (the chosen descriptor's rate feeds the budget charge); refusing to route a request to a model that cannot satisfy it (missing modality, too-small context window, no tool use) catches a capability mismatch before the inference call burns tokens on a guaranteed-bad result. Cost ranking uses a supplied `costBasis` (`{ inputTokens, outputTokens }`) for real per-call spend, else the sum of the per-1k rates; ties break by model id so the choice is deterministic across calls and nodes.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.ai.capability.create({ models })` — capability registry + router",
13
- "body": "Returns `{ describe, list, register, satisfies, route }`. A descriptor carries `maxContextTokens`, `maxOutputTokens`, `modalitiesIn` / `modalitiesOut` (arrays), `toolUse`, `structuredOutput`, `fineTunable`, `reasoningTier` (`none` / `basic` / `standard` / `advanced`, ordered), `citationSupport`, `promptCachingMaxTokens`, and the cost rates `costPer1kInputTokens` / `costPer1kOutputTokens`. Descriptors are validated + frozen at registration so a typo (negative cost, unknown reasoning tier, non-array modality list) surfaces at config time rather than as a silent mis-route. `describe(modelId)` returns the frozen descriptor; `register(modelId, descriptor)` adds or replaces one at runtime."
14
- },
15
- {
16
- "title": "`route({ requirements, fallback?, costBasis? })` — cheapest-satisfying selection",
17
- "body": "Collects every model whose descriptor satisfies all requirements, then returns the cheapest (`{ modelId, descriptor, estimatedCost, reason }`). Requirements: `minContextTokens`, `minOutputTokens`, `modalitiesIn` / `modalitiesOut` (model must support every listed modality), `toolUse`, `structuredOutput`, `fineTunable`, `minReasoningTier` (tier ordering — `standard` is met by `standard` or `advanced`), `citationSupport`, `minPromptCachingTokens`. When no model matches, `fallback` (a registered model id) is returned with `reason: \"fallback\"`, or the call refuses with `aiCapability/no-candidate` if no fallback was supplied. Routing decisions emit `ai/capability-routed` / `ai/capability-fallback` / `ai/capability-no-candidate` through the drop-silent audit chain."
18
- },
19
- {
20
- "title": "`satisfies(modelId, requirements)` — precise capability-mismatch reasons",
21
- "body": "Returns `{ ok, failures }` where each failure names the `requirement`, the `need`, and what the model `have`s — so a caller surfaces a precise reason (e.g. `minReasoningTier need advanced have basic`) instead of a bare boolean. Use it to explain a routing miss or to gate a request against a specific model before calling it."
22
- }
23
- ]
24
- }
25
- ]
26
- }
@@ -1,31 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.29",
4
- "date": "2026-05-24",
5
- "headline": "`b.ai.dp` — float-safe differential privacy: snapping-mechanism Laplace + discrete Gaussian + Rényi-DP budgets",
6
- "summary": "Differential privacy adds calibrated noise so an aggregate is provably insensitive to any single record — but the guarantee is fragile: Mironov (2012) showed that a Laplace mechanism sampled with naive double-precision floats lets an attacker distinguish neighbouring datasets with > 35% probability from a single output, silently destroying the promise. `b.ai.dp` ships only mechanisms whose sampling is hardened against that attack class: Laplace via the snapping mechanism (clamp + CSPRNG sign + full-mantissa uniform + power-of-two-grid rounding) and the discrete Gaussian (Canonne–Kamath–Steinke 2020) via integer-exact rejection sampling built from Bernoulli(exp(−γ)) over exact rationals — no floating-point noise at all. All randomness comes from `b.crypto.generateBytes` (SHAKE256 over the OS CSPRNG), never `Math.random`. `b.ai.dp.budget({ scope, epsilon, delta })` tracks a privacy budget per scope and refuses a `consume` that would exceed it, accounting composition either by basic summation (default) or a Rényi-DP accountant (Mironov 2017) for a much tighter bound under repeated Gaussian releases. NIST SP 800-226 (2025) is the evaluation standard; Dwork & Roth is the canonical reference. The exponential and sparse-vector mechanisms are deferred-with-condition — their float-safe constructions (base-2 / permute-and-flip; snapped SVT) re-open on operator demand, since shipping them float-unsafe would defeat the module's purpose.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.ai.dp.mechanism({ type, sensitivity, epsilon, ... })` — float-safe noise mechanisms",
13
- "body": "`type: \"laplace\"` is the snapping mechanism (pure ε-DP, real-valued, requires a clamp `bound` the guarantee depends on); `type: \"gaussian\"` is the discrete Gaussian (integer-valued, (ε, δ)-DP, requires `delta`). The Gaussian uses the classic calibration σ = √(2 ln(1.25/δ))·Δ/ε, proven for ε ≤ 1 — larger ε is refused with a pointer to splitting the release under an rdp budget. Descriptors are validated + frozen at construction so a malformed parameter fails fast."
14
- },
15
- {
16
- "title": "`b.ai.dp.budget({ scope, epsilon, delta, accounting })` — per-scope privacy budget",
17
- "body": "Returns `{ consume, remaining, spent, reset }`. `consume(mechanism, value)` adds the mechanism's noise, charges the accountant, and throws `aiDp/budget-exhausted` if the release would push the scope past its (ε, δ). `accounting: \"basic\"` (default) sums per-release ε and δ; `accounting: \"rdp\"` runs a Rényi-DP accountant across a grid of orders and converts to (ε, δ) at the scope's δ for a tight composition bound under repeated Gaussian releases (requires `delta > 0`). The scope budget is enforced on both ε and δ independently."
18
- }
19
- ]
20
- },
21
- {
22
- "heading": "Security",
23
- "items": [
24
- {
25
- "title": "`b.crypto.generateBytes` uniformity fix at 1-byte length",
26
- "body": "Node's SHAKE256 XOF is non-uniform at `outputLength: 1` — the byte values 0x00 and 0xff never occur and the low bit skews to ~0.54. `b.crypto.generateBytes(1)` (and the underlying `random(1)`) now draws at least 2 bytes and slices, so a single-byte CSPRNG request is uniform. Surfaced by `b.ai.dp` per-byte noise sampling; any per-byte consumer of `generateBytes` inherits the fix. A regression test asserts 0x00 / 0xff occur and the low bit is balanced."
27
- }
28
- ]
29
- }
30
- ]
31
- }
@@ -1,23 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.3",
4
- "date": "2026-05-22",
5
- "headline": "README \"What ships in the box\" backfill — mail-stack listeners + JSCalendar + new postures",
6
- "summary": "The README's \"Communication\" + \"Compliance regimes\" bullets lagged behind the v0.11.24-v0.12.1 ship cadence. Backfilled: `b.mail.send.deliver` (turnkey outbound delivery chain), the four mail-server listeners (mx / submission / imap / jmap), the JMAP EmailSubmission/set reference handler, mail-crypto (CMS + PGP+WKD), the mail-stack agent, `b.calendar` (RFC 8984 JSCalendar substrate with full BY*+BYSETPOS+multi-rule expansion), and the 16 newly-promoted postures from v0.12.1 (`42-cfr-part-2` / `hti-1` / `uscdi-v4` / `irs-1075` / `nist-800-172-r3` / `tlp-2.0` / `soci-au` / `ffiec-cat-2` / `cri-profile-v2.0` / `m-22-09` / `m-22-18` / `nist-800-53-r5-privacy` / `nist-ai-600-1-genai` / `nist-csf-2.0` / `sb-53` / `nyc-ll144-2024`).",
7
- "sections": [
8
- {
9
- "heading": "Changed",
10
- "items": [
11
- {
12
- "title": "Communication section names every mail-stack listener + delivery chain + crypto primitive",
13
- "body": "New entries: `b.mail.send.deliver` (MX → MTA-STS → DANE → REQUIRETLS → SMTP → DSN chain), four `b.mail.server.*` listeners, JMAP EmailSubmission reference handler, `b.mail.crypto.cms` + `b.mail.crypto.pgp`, `b.mail.agent` + `b.mailStore`, and `b.calendar` (JSCalendar / iCalendar bridge for JMAP Calendars interop)."
14
- },
15
- {
16
- "title": "Compliance regimes section lists the 16 v0.12.1 backfilled postures",
17
- "body": "New rows organise the additions under three sub-bullets: AI governance adds `nyc-ll144-2024` / `sb-53` / `nist-ai-rmf-1.0` / `nist-ai-600-1-genai` alongside the existing AI-act / NYC-LL144 / Colorado / Illinois entries; a new \"Federal / sectoral\" row covers `42-cfr-part-2` / `hti-1` / `uscdi-v4` / `irs-1075` / `nist-csf-2.0` / `nist-800-53-r5-privacy` / `nist-800-172-r3` / `m-22-09` / `m-22-18` / `ffiec-cat-2` / `cri-profile-v2.0`; a new \"Critical infrastructure / info-sharing\" row covers `soci-au` / `tlp-2.0`."
18
- }
19
- ]
20
- }
21
- ],
22
- "references": []
23
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.30",
4
- "date": "2026-05-24",
5
- "headline": "`bundleAdapterStorage.keyRotation(opts)` — verified whole-repository envelope key rotation",
6
- "summary": "Rotating the key that wraps a backup repository is only safe if you can prove every bundle still reads under the new key — a rotation that silently corrupts one bundle is a time-bomb the operator discovers at restore time, exactly when they can least afford it. `storage.keyRotation(opts)` rotates every bundle's envelope from the old key to the new key (composing `rewrapAllBundles`) and then re-reads every bundle under the NEW key (composing `verifyAllBundles`), so a bad rotation surfaces as `verifyFailed > 0` immediately instead of at restore. It emits a `backup/key-rotated` audit event with the rotation id + per-status counts — a key-rotation event is a compliance record (SOC 2 CC6.1, PCI DSS 3.6.4) operators wire into their signed audit chain. Works for both `recipient` (hybrid PQC envelope) and `passphrase` (Argon2id) storage; refused cleanly on plaintext (`cryptoStrategy: \"none\"`) storage and when the new key is missing.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`bundleAdapterStorage.keyRotation(opts)` — rotate then prove",
13
- "body": "`opts.newRecipient` / `opts.newPassphrase` is the key bundles rotate TO (matched to the storage's `cryptoStrategy`); `opts.oldRecipient` / `opts.oldPassphrase` unwraps the current envelope when it differs from the configured key. Returns `{ rotationId, rotatedAt, total, rotated, skipped, failed, verified, verifyFailed, rotateResults, verifyResults }`. `opts.verify` (default true) runs the post-rotation read-back under the new key; `opts.concurrency` / `opts.stopOnFirstFailure` forward to the batch passes. Plaintext bundles + non-wrappable formats are skipped cleanly; a rotation that leaves any bundle unreadable reports `verifyFailed > 0` and emits the audit event with `outcome: \"failure\"`. A true overlap window where BOTH the old and new key decrypt a bundle (`dualWrap: true`) is refused with `backup/dual-wrap-unsupported` — it needs multi-recipient archive envelopes `b.archive.wrap` does not yet emit, and re-opens when the wrap layer gains them; until then stage a rotation by keeping the old key available to readers until `keyRotation` reports `failed: 0` + `verifyFailed: 0`, then retire it."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.31",
4
- "date": "2026-05-24",
5
- "headline": "`b.auth.jar.parse` — verify RFC 9101 JWT-Secured Authorization Requests (server side)",
6
- "summary": "A plain OAuth authorization request carries its parameters in the URL query string, where a browser, proxy, or referer log can tamper with or leak them. RFC 9101 JAR packs those parameters into a JWT the client signs — the request object — so the authorization server can confirm they arrived exactly as sent. `b.auth.jar.parse(jar, opts)` is the server-side verifier and the request-side counterpart to the existing JARM response handling (`b.auth.oauth.parseJarmResponse`). It delegates the signature check to `b.auth.jwt.verifyExternal` — which already enforces a mandatory `algorithms` allowlist and refuses the alg-confusion (`alg: \"none\"`, HMAC-vs-RSA) and JWE-on-a-JWS-verifier shapes against a JWKS public-key trust source — then pins `iss` and the `client_id` claim to the expected client, pins `aud` to this server's issuer identifier, refuses a nested `request` / `request_uri` (RFC 9101 §6.3 recursion / confused-deputy vector), and returns the authorization parameters with the JWT envelope claims stripped.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.auth.jar.parse(jar, opts)` — request-object verification",
13
- "body": "`opts.clientId` (the expected client — pins `iss` + the `client_id` claim), `opts.audience` (this server's issuer identifier — pins `aud`), `opts.algorithms` (required signature allowlist — no defaults, the alg-confusion defense), and one of `opts.jwks` / `opts.jwksUri` / `opts.keyResolver` (the client's verification key). Returns `{ params, claims }` where `params` is the authorization parameters (`response_type`, `redirect_uri`, `scope`, `state`, `nonce`, …) with the JWT envelope claims (`iss`, `aud`, `exp`, `iat`, `nbf`, `jti`) removed. A request object whose `client_id` claim disagrees with `opts.clientId`, or that nests a `request` / `request_uri`, is refused. Emitting a request object (the client side) is deferred-with-condition: it requires signing with the client's key under a classical JWS algorithm, and the framework's own JWT signer is PQC-only for the tokens it issues — a PQC-signed request object would not interoperate with a standard authorization server; client-side emission re-opens when a classical JWS signer lands or operators surface the need. Until then clients sign request objects with their existing JOSE tooling."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.32",
4
- "date": "2026-05-24",
5
- "headline": "`b.cbor` — bounded, deterministic in-tree CBOR codec (RFC 8949)",
6
- "summary": "CBOR is the binary serialization underneath COSE (RFC 9052), CWT, SCITT, and WebAuthn attestation — a foundational substrate the framework needs in-tree to build signed-statement primitives without a third-party parser. `b.cbor` is that codec, bounded by default like every parser the framework ships: a binary decoder is attack surface, so the defaults refuse the shapes a hostile encoder uses to exhaust memory or stack. The encoder emits Deterministically Encoded CBOR (RFC 8949 §4.2) — shortest-form heads, definite lengths, map keys sorted by encoded bytes, no indefinite-length items — so two semantically-equal values encode to byte-identical output, the property COSE signatures and SCITT receipts depend on.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cbor.encode(value, opts?)` / `b.cbor.decode(buffer, opts?)` / `b.cbor.Tag`",
13
- "body": "`encode` produces deterministic CBOR from numbers (integers + float64), bigint (64-bit range), strings, `Buffer` / `Uint8Array`, arrays, `Map` or plain objects, `b.cbor.Tag`, and the simple values. `decode` returns the value with maps decoded to a `Map` (CBOR keys may be integers — COSE header labels are) and byte strings to `Buffer`. `b.cbor.Tag(tag, value)` carries a major-type-6 tagged item. `decode(buf, { requireDeterministic: true })` additionally asserts the input was itself canonically encoded (decode → re-encode → byte-compare), refusing a non-canonical re-encoding on a signature-verify path where it would be a malleability vector."
14
- }
15
- ]
16
- },
17
- {
18
- "heading": "Security",
19
- "items": [
20
- {
21
- "title": "Bounded-by-default decoder",
22
- "body": "`maxDepth` (default 64, ceiling 256) caps nesting against stack exhaustion; `maxBytes` (default 16 MiB, ceiling 64 MiB) caps total input, and a declared string / array / map length exceeding the remaining bytes is refused before any allocation (no length-prefix memory bomb). Indefinite-length items (additional-info 31) are refused — a streaming-complexity / DoS vector forbidden by deterministic encoding. Reserved additional-info (28–30) is refused. Tags are refused unless allowlisted via `allowedTags` (a tag triggers semantic reprocessing — an un-vetted tag is a confused-deputy vector). Duplicate map keys (RFC 8949 §5.6) and trailing bytes after the data item are refused."
23
- }
24
- ]
25
- }
26
- ]
27
- }
@@ -1,31 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.33",
4
- "date": "2026-05-24",
5
- "headline": "`b.cose` — COSE_Sign1 sign / verify (RFC 9052) over the in-tree CBOR codec",
6
- "summary": "COSE is the signed-statement substrate under SCITT, CWT, and C2PA — the CBOR-native counterpart to JWS. `b.cose` ships COSE_Sign1 signing and verification composing the v0.12.32 `b.cbor` codec for the deterministic Sig_structure encoding. It signs with the classical COSE algorithms that interoperate today — ES256 / ES384 / ES512 (ECDSA) and EdDSA (Ed25519), all with final IANA algorithm ids (RFC 9053) — and with ML-DSA-87 (FIPS 204) for PQC-forward deployments. Verification accepts the same set, so the framework both produces COSE other implementations read today and consumes third-party COSE. There is no classical default: the caller names the algorithm and supplies the key.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cose.sign(payload, opts)` / `b.cose.verify(coseSign1, opts)`",
13
- "body": "`sign` produces a tagged COSE_Sign1 with `alg` in the integrity-protected header; `verify` returns `{ payload, alg, protectedHeaders, unprotectedHeaders }`. The Sig_structure (`[\"Signature1\", protected, external_aad, payload]`) is deterministically CBOR-encoded; ECDSA signatures use the IEEE-P1363 fixed-width encoding COSE mandates (RFC 9053 §2.1), not ASN.1 DER. `external_aad` is bound into the signature. v1 is single-signer with an attached payload; detached payload, COSE_Sign (multi-signer), COSE_Mac0, and COSE_Encrypt are deferred-with-condition (operator demand)."
14
- }
15
- ]
16
- },
17
- {
18
- "heading": "Security",
19
- "items": [
20
- {
21
- "title": "Bounded, alg-allowlisted, crit-checked verification",
22
- "body": "`verify` decodes the COSE_Sign1 bytes AND the protected-header bstr through the bounded `b.cbor.decode` (depth + size caps, indefinite-length / tag / duplicate-key refusal). `opts.algorithms` is a required allowlist (no defaults — name the accepted algorithms). A `crit` header (label 2) listing a header label the verifier does not understand is refused (RFC 9052 §3.1 crit-bypass defense), as is a `crit` label absent from the protected header. The COSE algorithm switch refuses any unrecognized id at the default branch."
23
- },
24
- {
25
- "title": "ML-DSA-87 COSE algorithm id is a non-final draft",
26
- "body": "ML-DSA-87 uses COSE algorithm id `-50`, a requested (non-final) IANA assignment from draft-ietf-cose-dilithium — an ML-DSA-87 COSE_Sign1 is not yet broadly interoperable and the id may change; it is pinned deliberately with the re-open condition being IANA finalization. SLH-DSA-SHAKE-256f has no registered COSE algorithm id at all and cannot be represented in COSE. The COSE_Sign1 mechanism and the classical algorithms are stable; ML-DSA-87 is the forward-looking opt-in."
27
- }
28
- ]
29
- }
30
- ]
31
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.34",
4
- "date": "2026-05-24",
5
- "headline": "`b.cwt` — CBOR Web Token (RFC 8392) sign / verify over `b.cose`",
6
- "summary": "A CWT is the CBOR-native counterpart to JWT — a signed claims set for constrained / IoT, FIDO attestation, and verifiable-credential contexts. `b.cwt` composes the v0.12.33 `b.cose` (COSE_Sign1 signature + mandatory algorithm allowlist) and v0.12.32 `b.cbor` (deterministic claims encoding) and layers the standard-claim handling on top: `sign` takes a friendly claims object, maps the standard claims to their RFC 8392 §3.1.1 integer labels (iss=1, sub=2, aud=3, exp=4, nbf=5, iat=6, cti=7), and signs; `verify` checks the COSE signature, decodes the claims, and enforces the time + identity claims — a passed `exp` (with clock-skew tolerance), a future `nbf`, and an `iss` / `aud` mismatch against the expected values are each refused. Signing algorithms follow `b.cose`: classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) and ML-DSA-87 (PQC-forward). RFC 8392 is a finalized standard, so CWTs produced here interoperate with other COSE/CWT implementations.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cwt.sign(claims, opts)` / `b.cwt.verify(cwt, opts)`",
13
- "body": "`sign` maps standard claim names to integer labels and keeps custom claims verbatim; `exp` / `nbf` / `iat` must be non-negative integer NumericDates. `opts.tagged` wraps the COSE_Sign1 in the CWT CBOR tag 61 (RFC 8392 §6); `verify` accepts tagged or bare input. `verify` returns `{ claims, raw, alg, protectedHeaders }` — `claims` is the friendly object (labels mapped back to names), `raw` the integer-keyed Map. Standard-claim enforcement: `exp` past `now + clockSkewSec` (default 60s) is refused with `cwt/expired`, `nbf` beyond `now - skew` with `cwt/not-yet-valid`, and `expectedIssuer` / `expectedAudience` mismatches with `cwt/issuer-mismatch` / `cwt/audience-mismatch` (aud may be a single value or an array). `opts.now` overrides the clock for testing. The signature itself is verified by `b.cose.verify`, so a tampered token fails there."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,22 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.35",
4
- "date": "2026-05-24",
5
- "headline": "`b.eat` — Entity Attestation Token (RFC 9711) over `b.cwt`",
6
- "summary": "An EAT is the token a Relying Party asks a device or software entity to produce to prove what it is and what state it is in — a freshness nonce, a Universal Entity ID, OEM / hardware identifiers, debug status, software measurements, and nested submodule attestations. `b.eat` is the RFC 9711 profile over the v0.12.34 `b.cwt`: it maps the EAT claim names to their IANA CWT claim-key integer labels and adds the attestation-specific verification on top of the CWT signature + time checks. The central control is the verifier-nonce binding: when the Relying Party supplies a fresh `expectedNonce`, the token's `eat_nonce` (claim 10) must match it (constant-time compare) — without it a captured attestation replays forever. `verify` also enforces a debug-status policy (`requireDebugDisabled` refuses an `enabled` or absent `dbgstat`) and pins the `eat_profile`. RFC 9711 is a finalized standard; signing follows `b.cwt` / `b.cose` (ES256/384/512 + EdDSA interoperable today, ML-DSA-87 PQC-forward).",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.eat.sign(claims, opts)` / `b.eat.verify(eat, opts)`",
13
- "body": "`sign` maps EAT claim names (`nonce`, `ueid`, `oemid`, `hwmodel`, `dbgstat`, `eat_profile`, `swname`/`swversion`, `measurements`, `submods`, …) to their RFC 9711 integer labels and accepts the `dbgstat` enum by name (`disabled-since-boot` → 2); standard CWT claims (`iss` / `exp` / …) pass through. `verify` returns `{ claims, raw, alg, protectedHeaders }` with the labels mapped back to friendly names and `dbgstat` decoded to its enum name. Attestation enforcement: `expectedNonce` requires a matching `eat_nonce` (refused `eat/nonce-mismatch`, missing `eat/nonce-missing` — `eat_nonce` may be a single byte string or an array for multiple verifiers), `requireDebugDisabled` refuses a non-disabled `dbgstat` (`eat/debug-not-disabled`), and `expectedProfile` pins `eat_profile`. The signature, algorithm allowlist, and `exp`/`nbf` checks delegate to `b.cwt` / `b.cose`."
14
- },
15
- {
16
- "title": "`b.cwt.sign` accepts a `Map`",
17
- "body": "`b.cwt.sign` now takes either a plain object (string keys, standard claims mapped by name) or a `Map`, which preserves integer claim keys verbatim — profiles like `b.eat` resolve their claim names to integer labels and pass them through without the keys being stringified. The plain-object path is unchanged."
18
- }
19
- ]
20
- }
21
- ]
22
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.36",
4
- "date": "2026-05-24",
5
- "headline": "`b.cose.encrypt0` / `b.cose.decrypt0` — COSE_Encrypt0 single-recipient AEAD (RFC 9052 §5.2)",
6
- "summary": "Completes the COSE family with encryption alongside the v0.12.33 signing: COSE_Encrypt0 is the single-recipient AEAD container where the recipient already holds the symmetric key (direct mode). The default algorithm is ChaCha20/Poly1305 (COSE alg 24) — AES-GCM stays opt-in, since hard-rule #2 forbids AES-GCM as a default. The Enc_structure (`[\"Encrypt0\", protected, external_aad]`) is bound as the AEAD associated data so the algorithm + any external context are authenticated, and the authentication tag is appended to the ciphertext per COSE. Composes the in-tree `b.cbor` codec and `node:crypto` AEAD.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cose.encrypt0(plaintext, opts)` / `b.cose.decrypt0(coseEncrypt0, opts)`",
13
- "body": "`encrypt0` produces a tagged COSE_Encrypt0 with `alg` in the protected header and a random 12-byte IV in the unprotected header (label 5); `alg` is `\"ChaCha20-Poly1305\"` (default), `\"A256GCM\"`, or `\"A128GCM\"`, with the key length enforced (32 / 16 bytes). `decrypt0` reads the algorithm from the protected header (must be in the required `opts.algorithms` allowlist), reconstructs the Enc_structure as the AEAD AAD, and returns `{ plaintext, alg, protectedHeaders, unprotectedHeaders }`; a wrong key, tampered ciphertext, or `external_aad` mismatch fails AEAD authentication and is refused with `cose/decrypt-failed`. `external_aad` binds request context into the tag."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.37",
4
- "date": "2026-05-24",
5
- "headline": "`b.scitt.signStatement` / `b.scitt.verifyStatement` — SCITT signed statements over COSE (RFC 9052 + RFC 9597)",
6
- "summary": "A SCITT signed statement is a signed, attributable claim about an artifact — a signed SBOM, a build attestation, a release approval. It is a COSE_Sign1 (b.cose) whose integrity-protected CWT_Claims header (label 15, RFC 9597) binds the issuer (who makes the statement) and the subject (the artifact it is about); the artifact, or a hash/reference to it, is the payload. signStatement places iss/sub in the protected header and declares the payload media type; verifyStatement checks the COSE signature (the algorithm allowlist is mandatory) and refuses any statement that lacks the iss/sub binding, with optional expected-issuer/subject matching. Signing uses the same algorithms as b.cose — classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) plus ML-DSA-87 (PQC-forward). This is the issuer side of SCITT, buildable today on finalized RFCs; the transparency receipt (an inclusion proof from a transparency service, the COSE Receipts draft) is not yet shipped — a statement produced here is the input a transparency service registers, and the receipt format is the part still in flux. It opts in when COSE Receipts publishes.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.scitt.signStatement(payload, opts)` / `b.scitt.verifyStatement(statement, opts)`",
13
- "body": "`signStatement` produces a COSE_Sign1 whose protected CWT_Claims header (label 15) carries `iss` (`opts.issuer`) and `sub` (`opts.subject`), with the payload media type declared via `opts.contentType` and extra CWT claims allowed by integer label (iss/sub cannot be overridden through `opts.claims`). `verifyStatement` verifies the signature through `b.cose.verify` (passing `opts.algorithms` as the mandatory allowlist), then requires a CWT_Claims header with both `iss` and `sub` — a bare COSE_Sign1 with no such binding is refused with `scitt/missing-cwt-claims` — and enforces `expectedIssuer` / `expectedSubject` when given. Returns `{ payload, issuer, subject, cwtClaims, alg, protectedHeaders, unprotectedHeaders }`. Because the identity binding lives in the integrity-protected header it is covered by the signature and cannot be substituted without detection."
14
- }
15
- ]
16
- },
17
- {
18
- "heading": "Changed",
19
- "items": [
20
- {
21
- "title": "`b.cose.sign` accepts `protectedHeaders` and a media-type-string `contentType`",
22
- "body": "`opts.protectedHeaders` (a numeric-keyed object or Map) adds extra integrity-protected header parameters — the CWT_Claims map (label 15) is the SCITT case. Label 1 (alg) is reserved and managed via `opts.alg`; setting it through `protectedHeaders` is refused with `cose/reserved-header`. `opts.contentType` now accepts a media-type string (RFC 9052 §3.1 tstr form, e.g. `\"application/spdx+json\"`) in addition to a CoAP Content-Format uint; a string was previously dropped."
23
- }
24
- ]
25
- }
26
- ]
27
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.38",
4
- "date": "2026-05-24",
5
- "headline": "`b.tsa` — RFC 3161 trusted timestamping client (build / parse / verify)",
6
- "summary": "A timestamp authority binds a hash of your data to a trusted time, producing a token that proves the data existed at that instant — timestamp a release artifact, an audit-log checkpoint, a b.scitt signed statement, or a contract. b.tsa is the requester/verifier side of RFC 3161: buildRequest produces the DER TimeStampReq (the message imprint plus an optional nonce and a cert request), parseResponse reads the TimeStampResp (PKIStatus, failure-info bits, and the token), and verifyToken checks a token against your data and returns the asserted time. Verification is done in full per §2.4.2 / §2.3: the token is a CMS SignedData (b.cms) whose eContentType must be id-ct-TSTInfo; the message imprint must equal the hash of your data (constant-time); a sent nonce must round-trip; the signer certificate's extendedKeyUsage must be a critical, sole id-kp-timeStamping; and the CMS signature over the signed attributes must verify after the messageDigest attribute is matched to the recomputed eContent digest. An optional trust-anchor set verifies the certificate chain and validity at the asserted time. The HTTP transport to the TSA is the operator's to make. Composes b.cms and the in-tree ASN.1 DER codec; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.tsa.buildRequest(data, opts?)` / `b.tsa.parseResponse(der)` / `b.tsa.verifyToken(token, opts)`",
13
- "body": "`buildRequest` returns `{ der, nonce, hashAlg, messageImprint }`; the imprint hash defaults to SHA-512 and may be SHA-256/384/512 or SHA3-256/512, a random 64-bit nonce and a certificate request are included by default, and a pre-hashed input is accepted with `hashed: true`. `parseResponse` returns `{ granted, status, statusString, failInfo, token }`, decoding the PKIFailureInfo bits for a non-granted response rather than throwing. `verifyToken` enforces the imprint match (`opts.data` or `opts.hash`), the nonce round-trip, the critical/sole `id-kp-timeStamping` EKU, and the CMS signature, returning `{ genTime, policy, serialHex, accuracy, hashAlg, signerCertPem }`; pass `opts.trustAnchorsPem` to also verify the certificate chain and validity at the asserted time. Timestamp tokens are third-party artifacts, so verification accepts the classical RSA (PKCS#1 v1.5 and PSS) and ECDSA-over-SHA-2 signatures that public TSAs emit — the same consume-what-exists posture as `b.cose` verification, not a framework signing default."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.39",
4
- "date": "2026-05-24",
5
- "headline": "`b.vc` — W3C Verifiable Credentials 2.0 (issue / verify, JOSE + COSE securing)",
6
- "summary": "Issue and verify W3C Verifiable Credentials (VC Data Model 2.0, a W3C Recommendation) secured per Securing Verifiable Credentials using JOSE and COSE (VC-JOSE-COSE, also a W3C Recommendation, May 2025). A verifiable credential is a tamper-evident, signed set of claims an issuer makes about a subject — a diploma, a membership, a license, an age assertion. Two securing mechanisms are supported, both signing the credential itself (no JWT/CWT claims wrapper): JOSE produces a compact JWS with the vc+jwt media type, signed with ES256/384/512 or EdDSA; COSE produces a COSE_Sign1 (application/vc+cose) over b.cose, which also accepts ML-DSA-87 for PQC-forward deployments. b.vc.verify auto-detects the form from the input, requires an algorithm allowlist, always refuses the JOSE none algorithm, re-checks the VCDM 2.0 structural rules, and enforces the validFrom / validUntil window. This is the W3C credential model, distinct from the IETF SD-JWT VC already at b.auth.sdJwtVc. Composes b.cose; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.vc.issue(credential, opts)` / `b.vc.verify(secured, opts)`",
13
- "body": "`issue` validates the credential against the VCDM 2.0 structural rules (the `credentials/v2` context first, a `VerifiableCredential` type, an issuer, a credential subject) and signs it: `securing: \"jose\"` returns a compact JWS string (`typ` header `vc+jwt`), `securing: \"cose\"` returns COSE_Sign1 bytes (`typ` header `application/vc+cose`, content type `application/vc`) via `b.cose`. The credential is the exact signed payload — no JWT/CWT claims are injected. `verify` auto-detects the securing form from the input (compact-JWS string vs. COSE_Sign1 bytes), verifies the signature against the mandatory `opts.algorithms` allowlist (the JOSE `none` algorithm is always refused), re-checks the structural rules, enforces the `validFrom` / `validUntil` window against `opts.at` (default now; must be a valid Date), and optionally matches `opts.expectedIssuer` against the credential issuer id. Returns `{ credential, securing, alg, issuer }`."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,19 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.4",
4
- "date": "2026-05-22",
5
- "headline": "`SECURITY.md` Watch list — remove stale \"framework doesn't ship CMS / S/MIME\" entry",
6
- "summary": "The Watch list bullet claiming `framework does not ship a CMS / S/MIME / PKCS#7 surface today` has been wrong since v0.10.13 — `b.cms.encodeSignedData` / `decode` / `encodeEnvelopedData` / `parseSignedData` shipped then, and `b.mail.crypto.smime.sign` / `verify` / `verifyAll` / `checkCert` shipped under the mail-stack. The Watch list is for CVE classes the framework deliberately doesn't ship a primitive for; CMS no longer fits that shape. Entry removed.",
7
- "sections": [
8
- {
9
- "heading": "Fixed",
10
- "items": [
11
- {
12
- "title": "Watch list no longer claims CMS / S/MIME are unshipped",
13
- "body": "`b.cms` exposes RFC 5652 ContentInfo / SignedData / EnvelopedData encode + decode with PQC signer support (ML-DSA-65 per RFC 9909 §5, ML-DSA-87 per RFC 9909 §6, SLH-DSA-SHAKE-256f per RFC 9881). `b.mail.crypto.smime` builds on it for RFC 8551 S/MIME signed + enveloped mail with `checkCert` for X.509 chain validation. The SECURITY.md Watch list entry that pointed operators to external CMS libraries is gone; operators on regulated mail interop reach for the in-framework primitives instead."
14
- }
15
- ]
16
- }
17
- ],
18
- "references": []
19
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.40",
4
- "date": "2026-05-24",
5
- "headline": "`b.mdoc` — ISO 18013-5 mdoc / mDL issuer-data verification",
6
- "summary": "Verify the issuer-signed data of an ISO/IEC 18013-5 mdoc — the credential format behind mobile driving licences (mDL) and the ISO track of the EU Digital Identity Wallet. This is the relying-party side: confirm that the data elements a holder presents were signed by the issuer and have not been altered. An mdoc's IssuerSigned carries the disclosed data elements and an issuerAuth that is a COSE_Sign1 (b.cose) over a Mobile Security Object (MSO) holding a per-element digest. b.mdoc.verifyIssuerSigned verifies the COSE signature with the issuer certificate from the COSE x5chain header, parses the MSO, enforces its validityInfo window, and recomputes each disclosed element's digest (the full Tag-24 IssuerSignedItemBytes) to match it against the MSO constant-time — the integrity check that makes selective disclosure trustworthy. An absent or mismatched digest is refused. Signing algorithms follow b.cose verification (the classical ES256/384/512 + EdDSA that real mDL issuers use; the caller names the allowlist); opts.trustAnchorsPem additionally verifies the issuer certificate chain. This completes the credential trio alongside W3C VCDM (b.vc) and IETF SD-JWT VC (b.auth.sdJwtVc). Composes b.cose + b.cbor; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.mdoc.verifyIssuerSigned(issuerSigned, opts)`",
13
- "body": "Takes the CBOR `IssuerSigned` map (the operator extracts it from the device response / QR) and returns `{ docType, version, digestAlgorithm, validityInfo, namespaces, signerCert, alg }`. Verifies the COSE_Sign1 `issuerAuth` against the mandatory `opts.algorithms` allowlist using the issuer certificate from its `x5chain` (label 33) header; parses the Tag-24 Mobile Security Object; enforces the MSO `validityInfo` window against `opts.at` (default now; must be a valid Date; malformed dates fail closed); and recomputes the digest of every disclosed `IssuerSignedItem` (over the full Tag-24 bytes, with the MSO `digestAlgorithm` — SHA-256/384/512) to match the MSO `valueDigests` constant-time — an absent or mismatched digest is refused with `mdoc/digest-mismatch`. `opts.expectedDocType` pins the document type; `opts.trustAnchorsPem` (a PEM string or array) additionally verifies the issuer certificate chain and validity at the asserted time. A malformed `x5chain` certificate is refused with a clean `mdoc/bad-cert`. The mdoc device-authentication half (the SessionTranscript-bound holder-binding proof) is a presentation-protocol concern and is not part of issuer-data verification."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.41",
4
- "date": "2026-05-24",
5
- "headline": "`b.did` — W3C DID resolution (did:key + did:web) feeding the credential verifiers",
6
- "summary": "Resolve W3C Decentralized Identifiers (DID Core 1.0) to verification keys — the link that lets a credential's issuer be named by a DID rather than a raw key. Resolve the issuer DID of a b.vc / b.mdoc / b.scitt credential to a node:crypto KeyObject and hand it to the verifier. did:key encodes the public key in the identifier (multicodec + base58btc), so resolution is deterministic and offline — Ed25519, P-256, P-384, and secp256k1 round-trip; did:web places the DID document at an HTTPS URL derived from the identifier, with the network fetch left to the operator (the framework parses the operator-fetched document and extracts its verification methods, as publicKeyMultibase or publicKeyJwk). b.did.keyToDid encodes a KeyObject as a did:key (an issuer naming itself), b.did.parse splits the identifier (and returns the did:web URL to fetch), and b.did.resolve returns the document and verification keys. DID Core 1.0 is a W3C Recommendation; the method specs (did:key W3C CCG report, did:web DID method registry — EUDI-mandated) are deployed-stable. Composes node:crypto; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.did.resolve(did, opts?)` / `b.did.keyToDid(publicKey)` / `b.did.parse(did)`",
13
- "body": "`resolve` returns `{ didDocument, verificationMethods: [{ id, controller, type, publicKey }] }` with each `publicKey` a `node:crypto` KeyObject ready for `b.vc.verify` / `b.mdoc.verifyIssuerSigned` / `b.scitt.verifyStatement`. did:key resolves deterministically and offline (base58btc + multicodec → Ed25519 raw key or EC compressed point, rebuilt via SPKI); did:web requires the operator to pass the fetched DID document as `opts.document` (the URL to GET is on `b.did.parse(did).url`) and the document `id` must match the requested DID. A publicKeyJwk in a DID document is imported only after its `kty`/`crv` is allowlisted (Ed25519 / P-256 / P-384 / secp256k1) — an unexpected key type from an untrusted document is refused, not blindly imported. `keyToDid` encodes an Ed25519 / P-256 / P-384 / secp256k1 KeyObject as a did:key; `parse` derives the did:web HTTPS URL (`host[:port][:path]` → `https://host/path/did.json`, or `/.well-known/did.json`). Unknown methods, malformed base58, unsupported multicodec codes, and unsupported key types are each refused."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.42",
4
- "date": "2026-05-24",
5
- "headline": "`b.vc.present` / `b.vc.verifyPresentation` — W3C Verifiable Presentations",
6
- "summary": "Completes b.vc with the holder side: a Verifiable Presentation is a holder-signed envelope wrapping one or more credentials, proving the presenter controls the key the credentials were issued to. b.vc.present builds and signs a VerifiablePresentation (each credential enveloped per VC-JOSE-COSE) as a compact JWS (vp+jwt) or COSE_Sign1 (application/vp+cose), matching b.vc.issue's algorithms; an optional nonce / audience is embedded in the signed presentation for holder-binding and replay protection. b.vc.verifyPresentation verifies the holder signature (auto-detected jose/cose, mandatory algorithm allowlist, JOSE none refused), the VCDM structure, and the embedded nonce / audience / expectedHolder when given, and — with verifyCredentials: true — verifies each enveloped credential through b.vc.verify and returns them. The holder is typically a DID, resolved to a key via b.did. Composes b.cose; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.vc.present(opts)` / `b.vc.verifyPresentation(secured, opts)`",
13
- "body": "`present` wraps `opts.credentials` (secured VCs — compact-JWS strings or COSE_Sign1 bytes, each enveloped as an `EnvelopedVerifiableCredential` data: URI) in a `VerifiablePresentation` signed by the holder, with optional `nonce` / `audience` embedded for binding. `verifyPresentation` verifies the holder signature against the mandatory `opts.algorithms` allowlist (JOSE `none` always refused), re-checks the VCDM structure, enforces `expectedHolder` / `nonce` / `audience` when supplied, and with `verifyCredentials: true` verifies each enveloped credential through `b.vc.verify` (using `opts.credentialOpts`), returning `{ presentation, holder, credentials, securing, alg }`. The enveloped-credential count is bounded. A `vp+jwt` presentation is refused by `b.vc.verify` and a `vc+jwt` credential is refused by `verifyPresentation` — the media-type binding keeps the two surfaces distinct."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.43",
4
- "date": "2026-05-25",
5
- "headline": "`b.crypto.selfTest` — FIPS 140-3-style power-on self-test for the crypto stack",
6
- "summary": "A power-on self-test over the framework's cryptographic primitives — the integrity check a FIPS 140-3-validated module runs at start-up. The hash / XOF checks are known-answer tests against NIST FIPS 202 published vectors (SHA3-256 / SHA3-512 / SHAKE256), so they confirm the framework's hashing matches the standard rather than merely itself; the AEAD check round-trips XChaCha20-Poly1305 and confirms a tampered ciphertext is rejected; and the post-quantum checks run a pairwise-consistency + negative test for ML-KEM-1024, ML-DSA-87, and SLH-DSA-SHAKE-256f (a fresh keypair must encaps/decaps and sign/verify consistently and reject a tampered signature — FIPS 140-3 §10.3 pairwise consistency, since the runtime exposes no seed-injection API for a fixed-seed KAT). selfTest returns a structured report and, by default, throws on any failure so a broken crypto stack fails closed at boot rather than silently producing bad output. Operators in regulated deployments can run it at start-up as a self-integrity gate.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.crypto.selfTest(opts?)`",
13
- "body": "Runs eight checks — SHA3-512 / SHA3-256 / SHAKE256 known-answer tests (NIST FIPS 202), HMAC-SHA3-512 determinism, XChaCha20-Poly1305 round-trip + tamper-detect, and ML-KEM-1024 / ML-DSA-87 / SLH-DSA-SHAKE-256f pairwise-consistency + negative tests — and returns `{ ok, results: [{ name, ok, detail? }], failures, ranAt }`. Throws `crypto/self-test-failed` (with the report attached) on any failure unless `opts.throwOnFailure` is `false`. Exercises the framework's real primitive paths so a self-test failure means the shipped crypto is broken."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.44",
4
- "date": "2026-05-25",
5
- "headline": "`b.did` adds the did:jwk method",
6
- "summary": "Completes b.did's method set with did:jwk alongside did:key and did:web. did:jwk encodes a public key as a base64url-encoded JWK directly in the identifier, so resolution is deterministic and offline — the same self-contained shape as did:key but in JWK form, which is what OpenID4VCI and the EU Digital Identity Wallet ecosystem commonly use. b.did.resolve(\"did:jwk:…\") returns the verification key as a node:crypto KeyObject (kty/crv allowlisted — Ed25519 / P-256 / P-384 / secp256k1 — so an unexpected key type is refused, not blindly imported), and b.did.keyToDid(publicKey, { method: \"jwk\" }) produces a did:jwk from a key (the private member is stripped). No new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "did:jwk in `b.did.resolve` / `b.did.keyToDid`",
13
- "body": "`resolve` decodes the base64url JWK (bounded via `b.safeJson`), allowlists its `kty`/`crv`, and returns `{ didDocument, verificationMethods: [{ publicKey, … }] }` with the key as a KeyObject ready for `b.vc` / `b.mdoc` / `b.scitt`; `keyToDid(publicKey, { method: \"jwk\" })` encodes a public key as `did:jwk:<base64url-JWK>` (default remains `did:key`). Malformed base64url-JSON is refused with `did/bad-jwk` and an unsupported key type with `did/unsupported-key`."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.45",
4
- "date": "2026-05-25",
5
- "headline": "`b.cose` adds detached-payload sign/verify + `b.cose.importKey` (COSE_Key)",
6
- "summary": "Two RFC 9052 / 9053 completions to the COSE substrate, both useable today and the prerequisites for mdoc device authentication and C2PA claim verification. Detached payloads (RFC 9052 §4.1): b.cose.sign with detached:true emits a COSE_Sign1 whose payload slot is nil — the signature still covers the payload, and the caller transmits it out of band; b.cose.verify takes the payload back as opts.externalPayload and binds it into the Sig_structure. A detached token verified without externalPayload is refused, and supplying externalPayload for an attached token is refused as ambiguous. COSE_Key import (RFC 9052 §7): b.cose.importKey turns a COSE_Key CBOR map into a node:crypto public KeyObject for b.cose.verify, accepting EC2 (P-256 / P-384 / P-521) and OKP (Ed25519) with the curve allowlisted so an unexpected key type is refused. No new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "Detached COSE_Sign1 payloads + `b.cose.importKey(coseKey)`",
13
- "body": "`b.cose.sign(payload, { detached: true })` emits a nil-payload COSE_Sign1 (the signature covers the payload regardless); `b.cose.verify(coseSign1, { externalPayload })` reconstructs the Sig_structure from the supplied payload, refusing a detached token with no `externalPayload` (`cose/detached-no-payload`) and refusing `externalPayload` on an attached token (`cose/payload-ambiguous`). `b.cose.importKey(coseKey)` maps a COSE_Key map (`kty` 2/EC2 with `crv` P-256/384/521, or `kty` 1/OKP with Ed25519) to a public KeyObject, allowlisting `kty`/`crv` and refusing anything else with `cose/unsupported-key` — the verification key embedded in an mdoc MSO or COSE_Key header is consumed this way."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.46",
4
- "date": "2026-05-25",
5
- "headline": "`b.mdoc.verifyDeviceAuth` — ISO 18013-5 mdoc device authentication",
6
- "summary": "Completes mdoc verification with the holder-binding half (ISO 18013-5 §9.1.3, signature variant). verifyIssuerSigned proves the data is issuer-signed; verifyDeviceAuth proves the presenter controls the device key the issuer bound into the MSO, so a captured issuer-signed document cannot be replayed by anyone else. The device's COSE_Sign1 (deviceSigned.deviceAuth.deviceSignature) is verified over the detached DeviceAuthentication structure [\"DeviceAuthentication\", SessionTranscript, DocType, DeviceNameSpacesBytes] using the device key from verifyIssuerSigned().deviceKey (now surfaced) and the operator-supplied SessionTranscript that binds the proof to this exact exchange (the presentation protocol — e.g. OpenID4VP — defines the transcript). Composes the v0.12.45 b.cose detached-payload verify + importKey. The MAC variant (deviceMac / COSE_Mac0, used in proximity flows with a reader ephemeral key) is deferred and refused with mdoc/device-mac-unsupported. No new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.mdoc.verifyDeviceAuth(opts)` + `deviceKey` on the verifyIssuerSigned result",
13
- "body": "`verifyDeviceAuth({ deviceKey, deviceSigned, docType, sessionTranscript, algorithms })` imports the device key (a COSE_Key via `b.cose.importKey`, or a KeyObject), reconstructs the detached `DeviceAuthentication` payload, and verifies the `deviceSignature` COSE_Sign1 against the mandatory algorithm allowlist — a mismatched `sessionTranscript` or `docType` fails the signature. `verifyIssuerSigned` now returns `deviceKey` (the MSO `deviceKeyInfo.deviceKey`) so the two checks chain. The MAC variant (`deviceMac`) is refused with `mdoc/device-mac-unsupported` pending COSE_Mac0 + reader-key support."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.47",
4
- "date": "2026-05-25",
5
- "headline": "`b.cose.mac0` / `b.cose.macVerify0` — COSE_Mac0 (RFC 9052 §6.2)",
6
- "summary": "Completes the COSE message-type set (COSE_Sign1 / COSE_Encrypt0 / COSE_Mac0) with single shared-key MACs. b.cose.mac0 produces a tagged COSE_Mac0 over a payload using HMAC-SHA-256/384/512 (the COSE-standard MAC algorithms; HMAC is symmetric, so its post-quantum strength is preserved). b.cose.macVerify0 recomputes the tag over the MAC_structure and compares it in constant time, with a mandatory algorithm allowlist. Use when both parties hold a shared key — e.g. an ECDH-derived key — and a non-repudiable signature is not wanted; detached payloads are supported (the proximity mdoc device-MAC variant and MACed CWTs are the consumers). Composes b.cbor + the framework's constant-time compare; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cose.mac0(payload, opts)` / `b.cose.macVerify0(coseMac0, opts)`",
13
- "body": "`mac0` emits a tagged COSE_Mac0 (tag 17) with `alg` (`HMAC-256/256` | `HMAC-384/384` | `HMAC-512/512`) in the protected header and the HMAC tag computed over the MAC_structure `[\"MAC0\", protected, external_aad, payload]`; `detached: true` emits a nil payload. `macVerify0` reads the algorithm from the protected header (must be in the required `opts.algorithms` allowlist), recomputes the tag, and compares it constant-time — a wrong key, tampered tag, or `external_aad` mismatch is refused with `cose/bad-tag`; a detached payload is supplied via `opts.externalPayload`. `external_aad` binds context into the tag."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,22 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.48",
4
- "date": "2026-05-25",
5
- "headline": "`b.network.dns.dnssec` — local DNSSEC signature verification (RFC 4035)",
6
- "summary": "Verify a DNS answer's RRSIG signature yourself instead of trusting the upstream resolver's AD bit. b.network.dns.dnssec.verifyRrset reconstructs the RFC 4034 §3.1.8.1 signed data — the RRSIG RDATA without the signature, followed by the RRset in canonical form (owner names lowercased, RRs ordered by canonical RDATA, the RRSIG's Original TTL) — and checks the signature against the DNSKEY, enforcing the inception / expiration window. Supports RSA/SHA-256 (alg 8), ECDSA P-256/SHA-256 (13), ECDSA P-384/SHA-384 (14), and Ed25519 (15) — the modern deployed set. verifyDs checks a delegation-signer digest against a DNSKEY (SHA-256 / SHA-384) and keyTag computes the RFC 4034 Appendix B key tag. The verification core is what a chain-walker composes; it defends against a compromised or on-path resolver that lies about authentication.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.network.dns.dnssec.verifyRrset(opts)`",
13
- "body": "Verifies an RRSIG over a canonicalised RRset against a DNSKEY. `opts` carries the owner `name`, the RR `type`, the wire-format `rdatas`, the parsed `rrsig` (algorithm / labels / originalTtl / inception / expiration / keyTag / signerName / signature), and the `dnskey` (algorithm + raw public key). The signed data is rebuilt per RFC 4034 §3.1.8.1: the RRSIG prefix (type covered | algorithm | labels | original TTL | expiration | inception | key tag | canonical signer name) followed by each RR in canonical form (lowercased owner | type | class | original TTL | rdlen | rdata), sorted by `Buffer.compare` on the RDATA. The validity window is enforced against `opts.at` (defaults to now; an invalid Date is refused, not treated as now). An RRSIG whose algorithm disagrees with the DNSKEY is refused before any key is built. RR types that embed domain names in their RDATA (NS, CNAME, SOA, MX, SRV, …) need RDATA-internal name-lowercasing this version does not perform, so they are refused with `dnssec/uncanonicalizable-type` rather than mis-validated; the security-critical DNSKEY / DS and the name-free address / text types (A, AAAA, TXT, CAA, TLSA, …) are fully supported."
14
- },
15
- {
16
- "title": "`b.network.dns.dnssec.verifyDs(opts)` / `b.network.dns.dnssec.keyTag(dnskeyRdata)`",
17
- "body": "`verifyDs` confirms a delegation-signer record matches a DNSKEY: it checks the key tag, then compares the DS digest (SHA-256 type 2 / SHA-384 type 4) against the digest computed over the canonical owner name and the DNSKEY RDATA, constant-time. `keyTag` computes the RFC 4034 Appendix B 16-bit key tag from a DNSKEY's full RDATA — the identifier an RRSIG or DS uses to select the signing key. Together with `verifyRrset` these are the per-RRset building blocks a recursive chain-walk (root → TLD → zone) composes; the chain-walk itself, NSEC / NSEC3 denial-of-existence, and the bundled IANA root trust anchor are not part of this core."
18
- }
19
- ]
20
- }
21
- ]
22
- }
@@ -1,31 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.49",
4
- "date": "2026-05-25",
5
- "headline": "`b.network.dns.dnssec.verifyDenial` — NSEC / NSEC3 denial-of-existence",
6
- "summary": "Prove a DNS name does not exist, or has no records of a given type, from the signed NSEC (RFC 4034 §4) or NSEC3 (RFC 5155) records a server returns. This is the other half of local DNSSEC verification: verifyRrset proves a positive answer, verifyDenial proves a negative — so a resolver client can confirm an NXDOMAIN / NODATA itself instead of trusting the upstream resolver. NSEC3 proofs run the closest-encloser / next-closer / covering-range logic over iterated-SHA-1 hashes, with the iteration count capped (default 500) to bound the work an attacker can force, and an Opt-Out NXDOMAIN refused unless explicitly accepted (opt-out only proves 'no signed records', not non-existence). The companion b.network.dns.dnssec.nsec3Hash computes the RFC 5155 §5 hash directly. NSEC verifyRrset support is also enabled: per RFC 6840 §5.1 the NSEC Next Domain Name is not downcased, so its RDATA is verbatim-canonical.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.network.dns.dnssec.verifyDenial(opts)`",
13
- "body": "Proves NXDOMAIN or NODATA from already-verified NSEC / NSEC3 records (supply one of `opts.nsec3` or `opts.nsec`). Like `verifyDs`, it checks the denial RELATION — closest-encloser matching, covering ranges, and type-bitmap absence — not the record signatures, which the caller verifies with `verifyRrset` first. NSEC3 supports name-error proofs (matching closest encloser + covered next-closer + covered wildcard), NODATA (matching record with the type and CNAME absent from the bitmap), Opt-Out DS NODATA, and wildcard NODATA. The iterated-SHA-1 count is capped by `opts.maxIterations` (default 500); an NXDOMAIN proof that depends on an Opt-Out NSEC3 is refused unless `opts.allowOptOut` is set. NSEC supports covering-name NXDOMAIN (with the source-of-synthesis wildcard) and matching-name NODATA. Verified end-to-end against a live iana.org NXDOMAIN proof."
14
- },
15
- {
16
- "title": "`b.network.dns.dnssec.nsec3Hash(name, opts)`",
17
- "body": "Computes the RFC 5155 §5 NSEC3 hash of a name — iterated SHA-1 over the canonical (lowercased, root-terminated) wire form with the zone salt. The base32hex encoding of the result is the NSEC3 owner label. SHA-1 is the only hash IANA registers for NSEC3, so this is a wire-protocol constant rather than a cryptographic default. Useful for checking an owner label or analyzing a zone's hashing parameters."
18
- }
19
- ]
20
- },
21
- {
22
- "heading": "Changed",
23
- "items": [
24
- {
25
- "title": "`verifyRrset` now accepts NSEC and NSEC3 RRsets",
26
- "body": "NSEC (type 47) and NSEC3 (type 50) are no longer refused as uncanonicalizable: NSEC3's next-owner is a hash, and per RFC 6840 §5.1 the NSEC Next Domain Name field is not downcased for DNSSEC canonical form, so both RDATAs are verbatim-canonical. This lets a caller verify the signatures on the records that `verifyDenial` then reasons over."
27
- }
28
- ]
29
- }
30
- ]
31
- }