@blamejs/blamejs-shop 0.1.32 → 0.1.34

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 (99) hide show
  1. package/CHANGELOG.md +4 -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 +10 -0
  8. package/lib/vendor/blamejs/README.md +4 -2
  9. package/lib/vendor/blamejs/api-snapshot.json +107 -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/iab-tcf.js +277 -2
  13. package/lib/vendor/blamejs/lib/network-tsig.js +404 -0
  14. package/lib/vendor/blamejs/lib/network.js +1 -0
  15. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +44 -9
  16. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +19 -0
  17. package/lib/vendor/blamejs/lib/worm.js +246 -0
  18. package/lib/vendor/blamejs/package.json +1 -1
  19. package/lib/vendor/blamejs/release-notes/v0.12.x.json +1844 -0
  20. package/lib/vendor/blamejs/release-notes/v0.13.0.json +22 -0
  21. package/lib/vendor/blamejs/release-notes/v0.13.1.json +18 -0
  22. package/lib/vendor/blamejs/release-notes/v0.13.2.json +27 -0
  23. package/lib/vendor/blamejs/scripts/vendor-update.sh +11 -1
  24. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +3 -1
  25. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +101 -0
  26. package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +61 -1
  27. package/lib/vendor/blamejs/test/layer-0-primitives/network-tsig.test.js +149 -0
  28. package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +2 -2
  29. package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +3 -3
  30. package/lib/vendor/blamejs/test/layer-0-primitives/worm.test.js +126 -0
  31. package/package.json +2 -2
  32. package/lib/vendor/blamejs/release-notes/v0.12.0.json +0 -64
  33. package/lib/vendor/blamejs/release-notes/v0.12.1.json +0 -32
  34. package/lib/vendor/blamejs/release-notes/v0.12.10.json +0 -65
  35. package/lib/vendor/blamejs/release-notes/v0.12.11.json +0 -39
  36. package/lib/vendor/blamejs/release-notes/v0.12.12.json +0 -48
  37. package/lib/vendor/blamejs/release-notes/v0.12.13.json +0 -31
  38. package/lib/vendor/blamejs/release-notes/v0.12.14.json +0 -18
  39. package/lib/vendor/blamejs/release-notes/v0.12.15.json +0 -27
  40. package/lib/vendor/blamejs/release-notes/v0.12.16.json +0 -18
  41. package/lib/vendor/blamejs/release-notes/v0.12.17.json +0 -22
  42. package/lib/vendor/blamejs/release-notes/v0.12.18.json +0 -22
  43. package/lib/vendor/blamejs/release-notes/v0.12.19.json +0 -22
  44. package/lib/vendor/blamejs/release-notes/v0.12.2.json +0 -45
  45. package/lib/vendor/blamejs/release-notes/v0.12.20.json +0 -18
  46. package/lib/vendor/blamejs/release-notes/v0.12.21.json +0 -27
  47. package/lib/vendor/blamejs/release-notes/v0.12.22.json +0 -18
  48. package/lib/vendor/blamejs/release-notes/v0.12.23.json +0 -18
  49. package/lib/vendor/blamejs/release-notes/v0.12.24.json +0 -18
  50. package/lib/vendor/blamejs/release-notes/v0.12.25.json +0 -18
  51. package/lib/vendor/blamejs/release-notes/v0.12.26.json +0 -30
  52. package/lib/vendor/blamejs/release-notes/v0.12.27.json +0 -26
  53. package/lib/vendor/blamejs/release-notes/v0.12.28.json +0 -26
  54. package/lib/vendor/blamejs/release-notes/v0.12.29.json +0 -31
  55. package/lib/vendor/blamejs/release-notes/v0.12.3.json +0 -23
  56. package/lib/vendor/blamejs/release-notes/v0.12.30.json +0 -18
  57. package/lib/vendor/blamejs/release-notes/v0.12.31.json +0 -18
  58. package/lib/vendor/blamejs/release-notes/v0.12.32.json +0 -27
  59. package/lib/vendor/blamejs/release-notes/v0.12.33.json +0 -31
  60. package/lib/vendor/blamejs/release-notes/v0.12.34.json +0 -18
  61. package/lib/vendor/blamejs/release-notes/v0.12.35.json +0 -22
  62. package/lib/vendor/blamejs/release-notes/v0.12.36.json +0 -18
  63. package/lib/vendor/blamejs/release-notes/v0.12.37.json +0 -27
  64. package/lib/vendor/blamejs/release-notes/v0.12.38.json +0 -18
  65. package/lib/vendor/blamejs/release-notes/v0.12.39.json +0 -18
  66. package/lib/vendor/blamejs/release-notes/v0.12.4.json +0 -19
  67. package/lib/vendor/blamejs/release-notes/v0.12.40.json +0 -18
  68. package/lib/vendor/blamejs/release-notes/v0.12.41.json +0 -18
  69. package/lib/vendor/blamejs/release-notes/v0.12.42.json +0 -18
  70. package/lib/vendor/blamejs/release-notes/v0.12.43.json +0 -18
  71. package/lib/vendor/blamejs/release-notes/v0.12.44.json +0 -18
  72. package/lib/vendor/blamejs/release-notes/v0.12.45.json +0 -18
  73. package/lib/vendor/blamejs/release-notes/v0.12.46.json +0 -18
  74. package/lib/vendor/blamejs/release-notes/v0.12.47.json +0 -18
  75. package/lib/vendor/blamejs/release-notes/v0.12.48.json +0 -22
  76. package/lib/vendor/blamejs/release-notes/v0.12.49.json +0 -31
  77. package/lib/vendor/blamejs/release-notes/v0.12.5.json +0 -40
  78. package/lib/vendor/blamejs/release-notes/v0.12.50.json +0 -18
  79. package/lib/vendor/blamejs/release-notes/v0.12.51.json +0 -18
  80. package/lib/vendor/blamejs/release-notes/v0.12.52.json +0 -18
  81. package/lib/vendor/blamejs/release-notes/v0.12.53.json +0 -18
  82. package/lib/vendor/blamejs/release-notes/v0.12.54.json +0 -18
  83. package/lib/vendor/blamejs/release-notes/v0.12.55.json +0 -18
  84. package/lib/vendor/blamejs/release-notes/v0.12.56.json +0 -27
  85. package/lib/vendor/blamejs/release-notes/v0.12.57.json +0 -18
  86. package/lib/vendor/blamejs/release-notes/v0.12.58.json +0 -22
  87. package/lib/vendor/blamejs/release-notes/v0.12.6.json +0 -45
  88. package/lib/vendor/blamejs/release-notes/v0.12.60.json +0 -18
  89. package/lib/vendor/blamejs/release-notes/v0.12.61.json +0 -18
  90. package/lib/vendor/blamejs/release-notes/v0.12.62.json +0 -18
  91. package/lib/vendor/blamejs/release-notes/v0.12.63.json +0 -27
  92. package/lib/vendor/blamejs/release-notes/v0.12.64.json +0 -18
  93. package/lib/vendor/blamejs/release-notes/v0.12.65.json +0 -27
  94. package/lib/vendor/blamejs/release-notes/v0.12.66.json +0 -18
  95. package/lib/vendor/blamejs/release-notes/v0.12.68.json +0 -27
  96. package/lib/vendor/blamejs/release-notes/v0.12.69.json +0 -27
  97. package/lib/vendor/blamejs/release-notes/v0.12.7.json +0 -86
  98. package/lib/vendor/blamejs/release-notes/v0.12.8.json +0 -81
  99. package/lib/vendor/blamejs/release-notes/v0.12.9.json +0 -61
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "../scripts/release-notes-schema.json",
3
+ "version": "0.13.0",
4
+ "date": "2026-05-26",
5
+ "headline": "`b.crypto.oprf` — RFC 9497 Oblivious PRFs",
6
+ "summary": "Compute F(serverKey, input) without the server learning the input and without the client learning the key — the Oblivious PRF primitive behind password hardening (the server peppers a password it never sees), private set intersection, and Privacy Pass. b.crypto.oprf.suite(name) returns an RFC 9497 ciphersuite — ristretto255-sha512, p256-sha256, p384-sha384, or p521-sha512 — each exposing the base oprf mode and the verifiable voprf mode (a DLEQ proof lets the client confirm the server used the key committed in its public key). The client blinds its input, the server blind-evaluates with its secret key, and the client finalizes by un-blinding and hashing; because un-blinding cancels the blind, the output depends only on key and input. Validated byte-for-byte against the RFC 9497 Appendix-A test vectors. Group and hash-to-curve operations come from the newly vendored @noble/curves (Paul Miller, MIT) — the same maintainer as the framework's existing vendored @noble/post-quantum and @noble/ciphers, with no added npm runtime dependency.",
7
+ "sections": [
8
+ {
9
+ "heading": "Added",
10
+ "items": [
11
+ {
12
+ "title": "`b.crypto.oprf` — RFC 9497 OPRF / VOPRF",
13
+ "body": "`suite(name)` returns `{ name, oprf, voprf }` for one of the four RFC 9497 ciphersuites (ristretto255-SHA512 / P-256-SHA256 / P-384-SHA384 / P-521-SHA512). The `oprf` (base) mode provides `deriveKeyPair` / `generateKeyPair` / `blind` / `blindEvaluate` / `finalize` / `evaluate`; `voprf` (verifiable) adds a DLEQ proof so the client can prove the server used the committed key. Use it for password hardening, private set intersection, and OPRF-based tokens. Verified against the RFC 9497 Appendix-A vectors. The partially-oblivious `poprf` mode is not yet exposed (the vendored `@noble/curves` does not implement it) and will follow upstream."
14
+ },
15
+ {
16
+ "title": "Vendored `@noble/curves`",
17
+ "body": "`@noble/curves` 2.2.0 (Paul Miller, MIT) is vendored under `lib/vendor/` (no npm runtime dependency), supplying the ristretto255 / NIST-curve group and hash-to-curve operations behind `b.crypto.oprf`. It joins the existing vendored `@noble/post-quantum` and `@noble/ciphers` from the same maintainer; tracked in the SBOM and the vendor-currency gate."
18
+ }
19
+ ]
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "../scripts/release-notes-schema.json",
3
+ "version": "0.13.1",
4
+ "date": "2026-05-26",
5
+ "headline": "`b.worm` — write-once-read-many retention",
6
+ "summary": "Store records that cannot be altered or deleted before a retention period elapses — the immutable-storage discipline regulators require (SEC 17a-4(f), CFTC 1.31, FINRA 4511). b.worm.create(opts) returns a WORM store that enforces, on every mutating call, that a record is not overwritten or deleted while it is within its retainUntil window or under a legal hold. Two modes mirror cloud Object-Lock: compliance (the default — no one, including the operator, can delete before expiry) and governance (a privileged caller may override with an audited reason). Retention can only be extended, never shortened; every record carries a SHA3-512 digest that get verifies, so tampering with the underlying bytes is detected on read; every allow/refuse decision is audited. Storage is pluggable via a synchronous store adapter, so the policy layer sits over a sealed DB table, a filesystem, or any non-S3 backend — the store-agnostic, application-level companion to b.objectStore's S3 Object Lock, with content-integrity verification that native Object Lock does not provide.",
7
+ "sections": [
8
+ {
9
+ "heading": "Added",
10
+ "items": [
11
+ {
12
+ "title": "`b.worm.create` — write-once-read-many retention",
13
+ "body": "Returns a store with `put` / `get` / `delete` / `extendRetention` / `placeLegalHold` / `releaseLegalHold` / `list`. `put` is write-once (an overwrite of a retained or held record is refused); `delete` is gated by the retention window, legal holds, and the mode (`compliance` refuses any early delete; `governance` allows a privileged override with a required, audited reason); `extendRetention` is extend-only; `get` verifies the stored SHA3-512 digest and throws `worm/tampered` on a mismatch. Storage is a pluggable synchronous adapter (`get` / `set` / `delete` / `has` / `keys`), defaulting to in-memory for tests. Use it for SEC 17a-4 / CFTC / FINRA immutable records on backends without native Object Lock; `b.objectStore` remains the path for S3 Object Lock."
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "../scripts/release-notes-schema.json",
3
+ "version": "0.13.2",
4
+ "date": "2026-05-26",
5
+ "headline": "`b.iabTcf.encode` — write TCF consent strings, and a TC-string timestamp fix",
6
+ "summary": "b.iabTcf gains the encode half of its consent-string codec: b.iabTcf.encode(obj) serialises a parsed object back into an IAB TCF v2 TC string, and b.iabTcf.isValid(tcString) is a total never-throwing validity check. Vendor and purpose collections may be Sets, id arrays, or the parsed sections parseString returns; vendor sections are written with whichever of the bit-field and range forms is smaller, matching the reference CMP encoders, so a parsed string round-trips to an equivalent signal. parseString now fully decodes the Core publisher-restrictions list and the PublisherTC segment's publisher and custom purposes, where it previously reported only the segment's presence. The encoder is verified against the worked-example string in the IAB Tech Lab consent-string specification: it re-encodes that string's Core segment byte-for-byte. This release also fixes a TC-string parsing bug — the bit reader accumulated values with a 32-bit shift, so the 36-bit Created and LastUpdated timestamp fields were silently truncated for any real date; they now decode and round-trip exactly.",
7
+ "sections": [
8
+ {
9
+ "heading": "Added",
10
+ "items": [
11
+ {
12
+ "title": "`b.iabTcf.encode` / `b.iabTcf.isValid`",
13
+ "body": "`encode(obj)` serialises a TCF object (the shape `parseString` returns) into a TC string — Core plus optional DisclosedVendors, AllowedVendors, and PublisherTC segments — choosing the smaller of the bit-field and range vendor encodings. `isValid(tcString)` returns whether a string parses as a well-formed Core segment without throwing. `parseString` now fully decodes Core publisher restrictions and the PublisherTC purposes that were previously reported only as present."
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "heading": "Fixed",
19
+ "items": [
20
+ {
21
+ "title": "TC-string 36-bit timestamps were truncated on parse",
22
+ "body": "`b.iabTcf.parseString` read multi-bit fields with a 32-bit left-shift accumulation. The 36-bit Created and LastUpdated fields hold deciseconds-since-epoch, which exceeds 2^31 for any date after 1976, so those timestamps were silently corrupted. The reader now accumulates without the 32-bit truncation; timestamps decode correctly and round-trip through `encode`."
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -33,7 +33,7 @@ DATE=$(date +%Y-%m-%d)
33
33
  # (see lib/argon2-builtin.js). The case-block below preserves the
34
34
  # `argon2` operator-friendly error message for anyone who still tries
35
35
  # `vendor-update.sh argon2`.
36
- VENDORED_PACKAGES=("@noble/ciphers" "@noble/post-quantum" "@simplewebauthn/server" "peculiar-pki")
36
+ VENDORED_PACKAGES=("@noble/ciphers" "@noble/curves" "@noble/post-quantum" "@simplewebauthn/server" "peculiar-pki")
37
37
 
38
38
  get_vendored_ver() {
39
39
  node -e "var m=require('./$MANIFEST'); var p=m.packages['$1']; console.log(p?p.version:'?')"
@@ -133,6 +133,16 @@ case "$PKG" in
133
133
  sed -i "1s|^|// XChaCha20-Poly1305 — vendored from @noble/ciphers v${INSTALLED_VER} by Paul Miller\n// License: MIT — https://github.com/paulmillr/noble-ciphers\n// Bundled with esbuild. Exports: xchacha20poly1305\n|" lib/vendor/noble-ciphers.cjs
134
134
  ;;
135
135
 
136
+ "@noble/curves")
137
+ cat > _entry.mjs <<'ENTRY'
138
+ export { ristretto255_oprf } from "@noble/curves/ed25519.js";
139
+ export { p256_oprf, p384_oprf, p521_oprf } from "@noble/curves/nist.js";
140
+ ENTRY
141
+ npx esbuild _entry.mjs --bundle --format=cjs --minify --platform=node --outfile=lib/vendor/noble-curves.cjs
142
+ rm _entry.mjs
143
+ sed -i "1s|^|// @noble/curves v${INSTALLED_VER} — vendored from Paul Miller\n// License: MIT — https://github.com/paulmillr/noble-curves\n// Bundled with esbuild. Exports the RFC 9497 OPRF suites:\n// ristretto255_oprf (ristretto255-SHA512), p256_oprf (P-256-SHA256),\n// p384_oprf (P-384-SHA384), p521_oprf (P-521-SHA512) — each with\n// oprf / voprf / poprf modes. Backs b.crypto.oprf.\n|" lib/vendor/noble-curves.cjs
144
+ ;;
145
+
136
146
  "@noble/post-quantum")
137
147
  cat > _entry.mjs <<'ENTRY'
138
148
  export { ml_kem512, ml_kem768, ml_kem1024 } from "@noble/post-quantum/ml-kem.js";
@@ -2292,8 +2292,10 @@ async function testNoDuplicateCodeBlocks() {
2292
2292
  "lib/privacy-pass.js:_bytes",
2293
2293
  "lib/content-digest.js:_bodyBytes",
2294
2294
  "lib/tsa.js:_bytes",
2295
+ "lib/eat.js:_toBuf",
2296
+ "lib/worm.js:_toBytes",
2295
2297
  ],
2296
- reason: "v0.12.48 / v0.12.51 / v0.12.52 / v0.12.53 — Buffer-coercion guard (`if (Buffer.isBuffer(x)) return x; if (x instanceof Uint8Array) return Buffer.from(x); throw <Error>`) repeats across byte-string-consuming primitives. Each throws a MODULE-LOCAL typed error code (cose/bad-cose-key, mdoc/bad-input, dnssec/bad-bytes, dane/bad-bytes, tsa/bad-input) naming the local argument; network-dane additionally coerces a hex string. The duplicated three-line shape is the symptom, the cause is that JS can't throw a caller-namespaced ErrorClass without the local closure. Same documented exception as the v0.12.7 require-non-empty-string cluster — the typed-error CODE is the divergence the dup detector can't see.",
2298
+ reason: "v0.12.48 / v0.12.51 / v0.12.52 / v0.12.53 / v0.13.x — Buffer-coercion guard (`if (Buffer.isBuffer(x)) return x; if (x instanceof Uint8Array) return Buffer.from(x); ...`) repeats across byte-string-consuming primitives. The throw-on-unknown variants (cose / mdoc / dnssec / dane / tsa) each raise a MODULE-LOCAL typed error code naming the local argument; the JSON-fallback variants (eat._toBuf serializing a CBOR/EAT claims payload, worm._toBytes serializing a record to hash) instead JSON.stringify a non-bytes value. The duplicated prefix is the symptom; the cause is that JS can't throw a caller-namespaced ErrorClass (or choose the domain's serialization) without the local closure. Same documented exception as the v0.12.7 require-non-empty-string cluster — the per-domain error code / serialization is the divergence the dup detector can't see.",
2297
2299
  },
2298
2300
  {
2299
2301
  mode: "family-subset",
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.crypto.oprf (RFC 9497).
4
+ * Oracle: the official RFC 9497 Appendix A test vectors (A.1.1
5
+ * ristretto255-SHA512 OPRF, A.3.1 P-256-SHA256 OPRF) — deriveKeyPair must
6
+ * reproduce skSm and the server-side evaluate must reproduce Output (both
7
+ * deterministic). Round-trips cover oprf + voprf; poprf is not exposed
8
+ * (the vendored @noble/curves does not implement it).
9
+ */
10
+
11
+ var b = require("../../index");
12
+ var helpers = require("../helpers");
13
+ var check = helpers.check;
14
+ function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
15
+ function hb(s) { return Buffer.from(s, "hex"); }
16
+ function toHex(u) { return Buffer.from(u).toString("hex"); }
17
+
18
+ var SEED = Buffer.alloc(32, 0xa3); // RFC 9497 vectors use Seed = 0xA3 × 32
19
+ var KEYINFO = hb("74657374206b6579"); // "test key"
20
+ var INPUT0 = hb("00");
21
+
22
+ // RFC 9497 Appendix A — OPRF (mode 0x00) vectors.
23
+ var VEC = {
24
+ "ristretto255-sha512": {
25
+ skSm: "5ebcea5ee37023ccb9fc2d2019f9d7737be85591ae8652ffa9ef0f4d37063b0e",
26
+ out: "527759c3d9366f277d8c6020418d96bb393ba2afb20ff90df23fb7708264e2f3ab9135e3bd69955851de4b1f9fe8a0973396719b7912ba9ee8aa7d0b5e24bcf6",
27
+ },
28
+ "p256-sha256": {
29
+ skSm: "159749d750713afe245d2d39ccfaae8381c53ce92d098a9375ee70739c7ac0bf",
30
+ out: "a0b34de5fa4c5b6da07e72af73cc507cceeb48981b97b7285fc375345fe495dd",
31
+ },
32
+ };
33
+
34
+ function testSurface() {
35
+ check("b.crypto.oprf.suite is a function", typeof b.crypto.oprf.suite === "function");
36
+ check("SUITES lists the four RFC 9497 ciphersuites", b.crypto.oprf.SUITES.join(",") === "ristretto255-sha512,p256-sha256,p384-sha384,p521-sha512");
37
+ check("OprfError is a class", typeof b.crypto.oprf.OprfError === "function");
38
+ var s = b.crypto.oprf.suite("ristretto255-sha512");
39
+ check("suite exposes oprf + voprf, not poprf", typeof s.oprf === "object" && typeof s.voprf === "object" && s.poprf === undefined);
40
+ check("suite name is case-insensitive", b.crypto.oprf.suite("P256-SHA256").name !== undefined);
41
+ check("unknown suite throws", code(function () { b.crypto.oprf.suite("x25519-sha256"); }) === "oprf/bad-suite");
42
+ }
43
+
44
+ function testRfc9497Vectors() {
45
+ Object.keys(VEC).forEach(function (name) {
46
+ var s = b.crypto.oprf.suite(name);
47
+ var kp = s.oprf.deriveKeyPair(SEED, KEYINFO);
48
+ check(name + ": deriveKeyPair reproduces skSm", toHex(kp.secretKey) === VEC[name].skSm);
49
+ check(name + ": evaluate reproduces Output", toHex(s.oprf.evaluate(kp.secretKey, INPUT0)) === VEC[name].out);
50
+ });
51
+ }
52
+
53
+ function testOprfRoundTrip() {
54
+ var s = b.crypto.oprf.suite("ristretto255-sha512");
55
+ var kp = s.oprf.deriveKeyPair(SEED, KEYINFO);
56
+ var input = Buffer.from("user@example.com", "utf8");
57
+ var c = s.oprf.blind(input);
58
+ var ev = s.oprf.blindEvaluate(kp.secretKey, c.blinded);
59
+ var out = toHex(s.oprf.finalize(input, c.blind, ev));
60
+ // The output is blind-independent: the oblivious client result equals the
61
+ // server's direct evaluation of the same key + input.
62
+ check("oprf finalize == server evaluate (blind-independent)", out === toHex(s.oprf.evaluate(kp.secretKey, input)));
63
+ // A fresh blind of the same input still finalizes to the same output.
64
+ var c2 = s.oprf.blind(input);
65
+ var ev2 = s.oprf.blindEvaluate(kp.secretKey, c2.blinded);
66
+ check("oprf output stable across blinds", toHex(s.oprf.finalize(input, c2.blind, ev2)) === out);
67
+ // Different input → different output.
68
+ check("oprf output differs for different input", toHex(s.oprf.evaluate(kp.secretKey, Buffer.from("other"))) !== out);
69
+ }
70
+
71
+ function testVoprfRoundTripAndProof() {
72
+ var s = b.crypto.oprf.suite("p256-sha256");
73
+ var v = s.voprf;
74
+ var kp = v.deriveKeyPair(SEED, KEYINFO);
75
+ var input = Buffer.from("verifiable-input", "utf8");
76
+ var c = v.blind(input);
77
+ var ev = v.blindEvaluate(kp.secretKey, kp.publicKey, c.blinded);
78
+ check("voprf blindEvaluate yields evaluated + proof", ev.evaluated && ev.proof);
79
+ var out = toHex(v.finalize(input, c.blind, ev.evaluated, c.blinded, kp.publicKey, ev.proof));
80
+ check("voprf finalize produces output", out.length > 0);
81
+ // The verifiable client output matches the server-side VOPRF evaluate
82
+ // (same mode). It is NOT the base-OPRF output — each mode mixes a
83
+ // distinct context string, so the two modes produce different values.
84
+ check("voprf finalize == server voprf.evaluate", out === toHex(v.evaluate(kp.secretKey, input)));
85
+ check("voprf output differs from oprf output (distinct mode DST)", out !== toHex(s.oprf.evaluate(kp.secretKey, input)));
86
+ // A tampered proof must make finalize reject (proof verification fails).
87
+ var badProof = Buffer.from(ev.proof); badProof[0] ^= 0xff;
88
+ check("voprf rejects a tampered proof", code(function () { v.finalize(input, c.blind, ev.evaluated, c.blinded, kp.publicKey, badProof); }) !== "NO-THROW");
89
+ // Wrong public key must also reject.
90
+ var otherKp = v.deriveKeyPair(Buffer.alloc(32, 0x11), KEYINFO);
91
+ check("voprf rejects a mismatched public key", code(function () { v.finalize(input, c.blind, ev.evaluated, c.blinded, otherKp.publicKey, ev.proof); }) !== "NO-THROW");
92
+ }
93
+
94
+ async function run() {
95
+ testSurface();
96
+ testRfc9497Vectors();
97
+ testOprfRoundTrip();
98
+ testVoprfRoundTripAndProof();
99
+ }
100
+ module.exports = { run: run };
101
+ if (require.main === module) { run().then(function () { console.log("[crypto-oprf] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }); }
@@ -41,7 +41,8 @@ async function run() {
41
41
  // bytes with a zero pad.
42
42
  var totalBits = 6 + 36 + 36 + 12 + 12 + 6 + 6 + 6 + 12 + 6 + 1 + 1 +
43
43
  12 + 24 + 24 + 1 + 6 + 6 +
44
- 16 + 1 + 16 + 1; // two empty vendor sections
44
+ 16 + 1 + 16 + 1 + // two empty vendor sections
45
+ 12; // NumPubRestrictions (mandatory, =0)
45
46
  var byteLen = Math.ceil(totalBits / 8); // allow:raw-byte-literal — bits-per-byte
46
47
  var buf = Buffer.alloc(byteLen);
47
48
  // Set version field (top 6 bits of byte 0).
@@ -68,6 +69,65 @@ async function run() {
68
69
  check("core version=4", parsed.core.version === 4);
69
70
  check("checkVendor handles bare core",
70
71
  b.iabTcf.checkVendor(parsed, 755).consented === false);
72
+
73
+ // ---- encode + isValid + completed parse --------------------------------
74
+ check("iabTcf.encode is fn", typeof b.iabTcf.encode === "function");
75
+ check("iabTcf.isValid is fn", typeof b.iabTcf.isValid === "function");
76
+
77
+ // Independent oracle: the worked-example string from the IAB Tech Lab
78
+ // "Consent string and vendor list formats v2" specification.
79
+ var SPEC = "CQSbk4AQSbk4ANwAAAENAwCgAAAAAAAAAAYgACPAAAAA.IDKQA4AAgAKAGQAygAAA.YAAAAAAAAAAA";
80
+ var SPEC_CORE = "CQSbk4AQSbk4ANwAAAENAwCgAAAAAAAAAAYgACPAAAAA";
81
+ var sp = b.iabTcf.parseString(SPEC);
82
+ check("spec core version 2", sp.core.version === 2);
83
+ check("spec cmpId 880", sp.core.cmpId === 880);
84
+ check("spec vendorListVersion 48", sp.core.vendorListVersion === 48);
85
+ check("spec created 2025-06-03", new Date(sp.core.createdAt).toISOString() === "2025-06-03T00:00:00.000Z");
86
+ check("spec vendorConsents [1,2,3,4]", JSON.stringify(Array.from(sp.core.vendorConsents.ids)) === "[1,2,3,4]");
87
+ check("spec disclosedVendors decoded", JSON.stringify(Array.from(sp.disclosedVendors.vendorIds)) === "[1,2,3,4,5,100,404]");
88
+ check("spec publisherTC fully parsed", sp.publisherTC && sp.publisherTC.present === true && typeof sp.publisherTC.numCustomPurposes === "number");
89
+ check("core.publisherRestrictions array", Array.isArray(sp.core.publisherRestrictions));
90
+ // Strong oracle: re-encoding the parsed core reproduces the spec Core
91
+ // segment byte-for-byte.
92
+ check("re-encoded core is byte-identical to the spec", b.iabTcf.encode({ core: sp.core }) === SPEC_CORE);
93
+ // Disclosed content round-trips (the encoder writes the minimal valid form).
94
+ var rt = b.iabTcf.parseString(b.iabTcf.encode({ core: sp.core, disclosedVendors: sp.disclosedVendors }));
95
+ check("disclosed content round-trips", JSON.stringify(Array.from(rt.disclosedVendors.vendorIds)) === "[1,2,3,4,5,100,404]");
96
+
97
+ // 36-bit timestamp regression: a real date (deciseconds > 2^31) must survive
98
+ // parse → encode unchanged. Guards the reader's `* 2` (not `<< 1`)
99
+ // accumulation, without which Created / LastUpdated truncate at 32 bits.
100
+ var bigCreated = Date.UTC(2026, 4, 26);
101
+ var encTs = b.iabTcf.encode({ core: { version: 2, createdAt: bigCreated, lastUpdatedAt: bigCreated, cmpId: 300, vendorListVersion: 100, consentLanguage: "EN", publisherCC: "DE", vendorConsents: [], vendorLIs: [] } });
102
+ var decTs = b.iabTcf.parseString(encTs);
103
+ check("36-bit timestamp survives round-trip", decTs.core.createdAt === bigCreated);
104
+ check("cmpId survives round-trip", decTs.core.cmpId === 300);
105
+ check("consentLanguage survives round-trip", decTs.core.consentLanguage === "EN");
106
+
107
+ // Vendor-section round-trip across range-favouring and sparse sets.
108
+ [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [12, 37, 199, 755], [1]].forEach(function (vc, idx) {
109
+ var s = b.iabTcf.encode({ core: { version: 2, createdAt: bigCreated, lastUpdatedAt: bigCreated, cmpId: 5, vendorListVersion: 10, consentLanguage: "EN", publisherCC: "FR", purposesConsent: [1, 2, 3], vendorConsents: vc, vendorLIs: [] } });
110
+ var d = b.iabTcf.parseString(s);
111
+ check("vendorConsents round-trip " + idx, JSON.stringify(Array.from(d.core.vendorConsents.ids)) === JSON.stringify(vc));
112
+ check("purposesConsent round-trip " + idx, JSON.stringify(Array.from(d.core.purposesConsent)) === "[1,2,3]");
113
+ });
114
+
115
+ // Publisher restrictions round-trip.
116
+ var prDec = b.iabTcf.parseString(b.iabTcf.encode({ core: { version: 2, createdAt: bigCreated, lastUpdatedAt: bigCreated, cmpId: 9, vendorListVersion: 1, consentLanguage: "IT", publisherCC: "IT", vendorConsents: [], vendorLIs: [], publisherRestrictions: [{ purposeId: 2, restrictionType: 1, vendorIds: [5, 6, 7, 50] }] } }));
117
+ check("publisher restriction round-trips",
118
+ prDec.core.publisherRestrictions.length === 1 &&
119
+ prDec.core.publisherRestrictions[0].purposeId === 2 &&
120
+ JSON.stringify(prDec.core.publisherRestrictions[0].vendorIds) === "[5,6,7,50]");
121
+
122
+ check("isValid true for the spec vector", b.iabTcf.isValid(SPEC) === true);
123
+ check("isValid false for garbage", b.iabTcf.isValid("nonsense!!") === false);
124
+ // A truncated core (a dropped trailing character cuts into the mandatory
125
+ // NumPubRestrictions field) must NOT validate — the reader's bounds check
126
+ // rejects it rather than treating the gap as "no restrictions".
127
+ check("isValid false for a truncated core", b.iabTcf.isValid(SPEC_CORE.slice(0, -1)) === false);
128
+ rejects("encode without core throws", function () { b.iabTcf.encode({}); }, "BAD_INPUT");
129
+ rejects("encode rejects a non-positive id",
130
+ function () { b.iabTcf.encode({ core: { consentLanguage: "EN", publisherCC: "DE", vendorConsents: [0], vendorLIs: [] } }); }, "BAD_VALUE");
71
131
  }
72
132
 
73
133
  module.exports = { run: run };
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.network.dns.tsig (RFC 8945).
4
+ * Oracle: dnspython 2.8.0 — reference TSIG-signed DNS messages. The signed
5
+ * wire + MAC for a fixed key / message / time are frozen below; `sign`
6
+ * must reproduce them byte-for-byte and `verify` must accept them.
7
+ */
8
+
9
+ var b = require("../../index");
10
+ var helpers = require("../helpers");
11
+ var check = helpers.check;
12
+ var tsig = b.network.dns.tsig;
13
+ function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
14
+ function hex(s) { return Buffer.from(s, "hex"); }
15
+
16
+ // dnspython keyring secret = base64("0123456789abcdef0123456789abcdef").
17
+ var SECRET = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=";
18
+ var KEY = "test.key.";
19
+
20
+ // Vector 1 — HMAC-SHA256, fudge 300, time_signed 1779813293.
21
+ var V1 = {
22
+ unsigned: hex("123401000001000000000000076578616d706c6503636f6d0000010001"),
23
+ signed: hex("123401000001000000000001076578616d706c6503636f6d00000100010474657374036b65790000fa00ff00000000003d0b686d61632d7368613235360000006a15cbad012c0020f8edb5dfdb6717c4bf6a3e50d9883be981ec862458101432ff7fe9348b127dcd123400000000"),
24
+ mac: "f8edb5dfdb6717c4bf6a3e50d9883be981ec862458101432ff7fe9348b127dcd",
25
+ time: 1779813293, fudge: 300, alg: "hmac-sha256",
26
+ };
27
+ // Vector 2 — HMAC-SHA512, fudge 600, time_signed 1779813320.
28
+ var V2 = {
29
+ unsigned: hex("abcd01000001000000000000046d61696c076578616d706c65036f726700000f0001"),
30
+ signed: hex("abcd01000001000000000001046d61696c076578616d706c65036f726700000f00010474657374036b65790000fa00ff00000000005d0b686d61632d7368613531320000006a15cbc8025800402c9b1ce5273ed4cf11067dcb8994606e8a5ea09fa5337eda6e6907e3bba08956375853771bf52567875dc3771acd67cf31ad2c6985914fd92b5b1440a1ee3c68abcd00000000"),
31
+ mac: "2c9b1ce5273ed4cf11067dcb8994606e8a5ea09fa5337eda6e6907e3bba08956375853771bf52567875dc3771acd67cf31ad2c6985914fd92b5b1440a1ee3c68",
32
+ time: 1779813320, fudge: 600, alg: "hmac-sha512",
33
+ };
34
+
35
+ function testSurface() {
36
+ check("tsig.sign is a function", typeof tsig.sign === "function");
37
+ check("tsig.verify is a function", typeof tsig.verify === "function");
38
+ check("tsig.ERROR has BADSIG/BADKEY/BADTIME", tsig.ERROR.BADSIG === 16 && tsig.ERROR.BADKEY === 17 && tsig.ERROR.BADTIME === 18);
39
+ check("tsig.TsigError is a class", typeof tsig.TsigError === "function");
40
+ }
41
+
42
+ function testSignMatchesDnspython() {
43
+ var s1 = tsig.sign(V1.unsigned, { keyName: KEY, secret: SECRET, algorithm: "hmac-sha256", fudge: V1.fudge, time: V1.time });
44
+ check("sign SHA-256 MAC matches dnspython", s1.mac.toString("hex") === V1.mac);
45
+ check("sign SHA-256 wire matches dnspython byte-for-byte", s1.wire.equals(V1.signed));
46
+
47
+ var s2 = tsig.sign(V2.unsigned, { keyName: KEY, secret: SECRET, algorithm: "hmac-sha512", fudge: V2.fudge, time: V2.time });
48
+ check("sign SHA-512 MAC matches dnspython", s2.mac.toString("hex") === V2.mac);
49
+ check("sign SHA-512 wire matches dnspython byte-for-byte", s2.wire.equals(V2.signed));
50
+ }
51
+
52
+ function testVerifyAcceptsDnspython() {
53
+ var r1 = tsig.verify(V1.signed, { keys: { "test.key.": { secret: SECRET } }, now: V1.time });
54
+ check("verify SHA-256 dnspython message is valid", r1.valid && r1.macValid && r1.timeValid);
55
+ check("verify reports keyName + algorithm + timeSigned", r1.keyName === "test.key." && r1.algorithm === "hmac-sha256" && r1.timeSigned === V1.time);
56
+
57
+ var r2 = tsig.verify(V2.signed, { keys: { "test.key.": { secret: SECRET } }, now: V2.time });
58
+ check("verify SHA-512 dnspython message is valid", r2.valid && r2.macValid);
59
+
60
+ // Single-key form.
61
+ var r1b = tsig.verify(V1.signed, { keyName: "test.key.", secret: SECRET, now: V1.time });
62
+ check("verify single-key form works", r1b.valid);
63
+ }
64
+
65
+ function testVerifyRejects() {
66
+ // Wrong secret → MAC mismatch (BADSIG), never throws.
67
+ var bad = tsig.verify(V1.signed, { keys: { "test.key.": { secret: Buffer.alloc(32, 9) } }, now: V1.time });
68
+ check("wrong secret → !valid + BADSIG", !bad.valid && !bad.macValid && bad.error === tsig.ERROR.BADSIG);
69
+
70
+ // Outside the fudge window → BADTIME.
71
+ var late = tsig.verify(V1.signed, { keys: { "test.key.": { secret: SECRET } }, now: V1.time + 100000 });
72
+ check("time outside fudge → !valid + BADTIME", !late.valid && late.macValid && !late.timeValid && late.error === tsig.ERROR.BADTIME);
73
+
74
+ // Unknown key → BADKEY.
75
+ var nokey = tsig.verify(V1.signed, { keys: { "other.key.": { secret: SECRET } }, now: V1.time });
76
+ check("unknown key → !valid + BADKEY", !nokey.valid && nokey.error === tsig.ERROR.BADKEY);
77
+
78
+ // Tampered message body → MAC mismatch.
79
+ var tampered = Buffer.from(V1.signed); tampered[2] ^= 0x20; // flip a bit in the question/flags region
80
+ var tr = tsig.verify(tampered, { keys: { "test.key.": { secret: SECRET } }, now: V1.time });
81
+ check("tampered message → !valid", !tr.valid);
82
+ }
83
+
84
+ function testRoundTripAndPolicy() {
85
+ // sign → verify round-trip with a fresh time.
86
+ var signed = tsig.sign(V1.unsigned, { keyName: KEY, secret: SECRET, time: 1700000000, fudge: 300 });
87
+ var rt = tsig.verify(signed.wire, { keys: { "test.key.": { secret: SECRET } }, now: 1700000000 });
88
+ check("sign→verify round-trip is valid", rt.valid);
89
+
90
+ // Default algorithm is HMAC-SHA256.
91
+ check("default algorithm is hmac-sha256", tsig.verify(signed.wire, { keys: { "test.key.": { secret: SECRET } }, now: 1700000000 }).algorithm === "hmac-sha256");
92
+
93
+ // Broken algorithms refused unless allowLegacy.
94
+ check("hmac-md5 refused by default", code(function () { tsig.sign(V1.unsigned, { keyName: KEY, secret: SECRET, algorithm: "hmac-md5" }); }) === "tsig/legacy-algorithm");
95
+ check("hmac-sha1 permitted under allowLegacy", typeof tsig.sign(V1.unsigned, { keyName: KEY, secret: SECRET, algorithm: "hmac-sha1", allowLegacy: true }).mac === "object");
96
+ check("unknown algorithm refused", code(function () { tsig.sign(V1.unsigned, { keyName: KEY, secret: SECRET, algorithm: "hmac-foo" }); }) === "tsig/bad-algorithm");
97
+
98
+ // Missing keyName / non-Buffer message refused.
99
+ check("missing keyName refused", code(function () { tsig.sign(V1.unsigned, { secret: SECRET }); }) === "tsig/bad-opt");
100
+ check("non-Buffer message refused", code(function () { tsig.sign("not a buffer", { keyName: KEY, secret: SECRET }); }) === "tsig/bad-message");
101
+ }
102
+
103
+ // dnspython vector with an UPPERCASE key name "Test.Key." — proves the MAC
104
+ // digest downcases names (DNS names are case-insensitive) and the wire
105
+ // preserves the sender's case.
106
+ var UC = {
107
+ signed: hex("123401000001000000000001076578616d706c6503636f6d00000100010454657374034b65790000fa00ff00000000003d0b686d61632d7368613235360000006a15d3d9012c00202d9c8c40b15f04407fc096200b1403a6535ee57a37580b636690b7e8146a0642123400000000"),
108
+ mac: "2d9c8c40b15f04407fc096200b1403a6535ee57a37580b636690b7e8146a0642",
109
+ time: 1779815385,
110
+ };
111
+
112
+ function testCaseInsensitive() {
113
+ // Verify a message whose TSIG key name is "Test.Key." against a key map
114
+ // keyed lower-case — must match (case-insensitive) and the downcased
115
+ // digest must validate against dnspython's MAC.
116
+ var r = tsig.verify(UC.signed, { keys: { "test.key.": { secret: SECRET } }, now: UC.time });
117
+ check("case-insensitive key match + downcased digest verifies", r.valid && r.macValid);
118
+
119
+ // Signing with a mixed-case key name reproduces dnspython's MAC byte-exact
120
+ // (proves the digest downcases, not the wire).
121
+ var s = tsig.sign(V1.unsigned, { keyName: "Test.Key.", secret: SECRET, time: UC.time, fudge: 300 });
122
+ check("sign mixed-case key matches dnspython MAC", s.mac.toString("hex") === UC.mac);
123
+ check("sign preserves the key-name case on the wire", s.wire.includes(Buffer.from("Test", "ascii")));
124
+ }
125
+
126
+ function testRrHeaderTamper() {
127
+ // RFC 8945 §4.2 — TSIG RR CLASS must be ANY and TTL must be 0. These
128
+ // bytes are outside the MAC, so they must be validated explicitly.
129
+ var tc = Buffer.from(V1.signed);
130
+ var idx = tc.indexOf(Buffer.from("00fa00ff", "hex")); // TSIG type (250) + CLASS ANY (255)
131
+ tc[idx + 3] = 0x01; // CLASS ANY -> IN
132
+ check("tampered TSIG CLASS is rejected", code(function () { tsig.verify(tc, { keys: { "test.key.": { secret: SECRET } }, now: V1.time }); }) === "tsig/bad-rr");
133
+
134
+ var tt = Buffer.from(V1.signed);
135
+ tt[idx + 7] = 0x01; // a TTL byte -> non-zero
136
+ check("tampered TSIG TTL is rejected", code(function () { tsig.verify(tt, { keys: { "test.key.": { secret: SECRET } }, now: V1.time }); }) === "tsig/bad-rr");
137
+ }
138
+
139
+ async function run() {
140
+ testSurface();
141
+ testSignMatchesDnspython();
142
+ testVerifyAcceptsDnspython();
143
+ testVerifyRejects();
144
+ testCaseInsensitive();
145
+ testRrHeaderTamper();
146
+ testRoundTripAndPolicy();
147
+ }
148
+ module.exports = { run: run };
149
+ if (require.main === module) { run().then(function () { console.log("[network-tsig] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }); }
@@ -26,7 +26,7 @@ async function testHappyPath() {
26
26
  var r = await b.sandbox.run({
27
27
  source: "return { upper: input.name.toUpperCase(), len: input.name.length };",
28
28
  input: { name: "alice" },
29
- timeoutMs: 2000,
29
+ timeoutMs: 5000,
30
30
  });
31
31
  check("happy path returns wrapped result", typeof r === "object");
32
32
  check("happy path produces result.result.upper", r.result && r.result.upper === "ALICE");
@@ -40,7 +40,7 @@ async function testAllowedBuiltins() {
40
40
  source: "return { ts: Date.now() > 0, sqrt: Math.sqrt(16) };",
41
41
  input: {},
42
42
  allowed: ["Date", "Math"],
43
- timeoutMs: 2000,
43
+ timeoutMs: 5000,
44
44
  });
45
45
  check("allowed Date works", r.result.ts === true);
46
46
  check("allowed Math works", r.result.sqrt === 4);
@@ -382,10 +382,10 @@ async function testWaitForResolves() {
382
382
  var v = await t.waitFor(function () {
383
383
  counter++;
384
384
  return counter >= 3 ? "done" : false;
385
- }, { timeoutMs: 200, intervalMs: 5 });
385
+ }, { timeoutMs: 5000, intervalMs: 5 });
386
386
  check("waitFor resolves with predicate value", v === "done");
387
387
  check("waitFor invoked predicate at least 3 times", counter >= 3);
388
- check("waitFor finished within timeout", Date.now() - startedAt < 2000);
388
+ check("waitFor finished within timeout", Date.now() - startedAt < 5000);
389
389
  }
390
390
 
391
391
  async function testWaitForTimeoutFromSafeAsync() {
@@ -404,7 +404,7 @@ async function testWaitForAsyncPredicate() {
404
404
  var v = await t.waitFor(async function () {
405
405
  counter++;
406
406
  return counter >= 2 ? 42 : null;
407
- }, { timeoutMs: 200, intervalMs: 5 });
407
+ }, { timeoutMs: 5000, intervalMs: 5 });
408
408
  check("waitFor awaits async predicate", v === 42);
409
409
  }
410
410
 
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.worm (write-once-read-many retention).
4
+ * Behavioral oracle: a WORM store must enforce write-once, retain-until
5
+ * immutability, legal holds, extend-only retention, compliance vs
6
+ * governance delete semantics, and tamper-evidence. A controllable clock
7
+ * drives the time-window cases deterministically.
8
+ */
9
+
10
+ var b = require("../../index");
11
+ var helpers = require("../helpers");
12
+ var check = helpers.check;
13
+ function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
14
+
15
+ // Controllable clock.
16
+ function clockAt(ref) { return function () { return ref.t; }; }
17
+
18
+ function testSurface() {
19
+ check("b.worm.create is a function", typeof b.worm.create === "function");
20
+ check("b.worm.MODES lists compliance + governance", b.worm.MODES.indexOf("compliance") !== -1 && b.worm.MODES.indexOf("governance") !== -1);
21
+ check("b.worm.WormError is a class", typeof b.worm.WormError === "function");
22
+ check("bad mode rejected", code(function () { b.worm.create({ mode: "loose" }); }) === "worm/bad-mode");
23
+ check("bad store adapter rejected", code(function () { b.worm.create({ store: { get: function () {} } }); }) === "worm/bad-store");
24
+ check("put without any retention rejected", code(function () { b.worm.create().put("x", "data"); }) === "worm/no-retention");
25
+ }
26
+
27
+ function testWriteOnceAndRetain() {
28
+ var ref = { t: 1000 };
29
+ var w = b.worm.create({ clock: clockAt(ref), defaultRetentionMs: 5000 });
30
+ var r = w.put("rec1", "hello");
31
+ check("put returns a receipt with digest + retainUntil", r.id === "rec1" && r.digest.length === 128 && r.retainUntil === 6000);
32
+ check("get returns the data", w.get("rec1").data.toString() === "hello");
33
+
34
+ // Write-once: cannot overwrite while retained.
35
+ check("overwrite of retained record refused", code(function () { w.put("rec1", "changed"); }) === "worm/already-exists");
36
+ // Cannot delete while retained (compliance default — no override).
37
+ check("delete while retained refused (compliance)", code(function () { w.delete("rec1"); }) === "worm/retained");
38
+ // Governance override is rejected in compliance mode regardless.
39
+ check("override ignored in compliance mode", code(function () { w.delete("rec1", { override: true, reason: "x" }); }) === "worm/retained");
40
+
41
+ // After expiry, delete is allowed.
42
+ ref.t = 7000;
43
+ check("delete allowed after retention expires", w.delete("rec1") === true);
44
+ check("record gone after delete", code(function () { w.get("rec1"); }) === "worm/not-found");
45
+ }
46
+
47
+ function testExtendOnly() {
48
+ var ref = { t: 1000 };
49
+ var w = b.worm.create({ clock: clockAt(ref) });
50
+ w.put("e", "x", { retainUntil: 5000 });
51
+ check("extendRetention forward allowed", w.extendRetention("e", 9000) === 9000);
52
+ check("shortening retention refused", code(function () { w.extendRetention("e", 6000); }) === "worm/retention-shorten");
53
+ }
54
+
55
+ function testLegalHold() {
56
+ var ref = { t: 10000 };
57
+ var w = b.worm.create({ clock: clockAt(ref) });
58
+ w.put("h", "x", { retainUntil: 1 }); // already past retention
59
+ check("expired record deletable before a hold", true);
60
+ w.placeLegalHold("h", "case-123");
61
+ check("delete refused under legal hold even after expiry", code(function () { w.delete("h"); }) === "worm/legal-hold");
62
+ check("overwrite refused under legal hold", code(function () { w.put("h", "y", { retainUntil: 1 }); }) === "worm/already-exists");
63
+ w.releaseLegalHold("h", "case-123");
64
+ check("delete allowed after hold released + expired", w.delete("h") === true);
65
+ }
66
+
67
+ function testGovernanceOverride() {
68
+ var ref = { t: 1000 };
69
+ var w = b.worm.create({ mode: "governance", clock: clockAt(ref) });
70
+ w.put("g", "x", { retainUntil: 9999 });
71
+ check("governance override without reason refused", code(function () { w.delete("g", { override: true }); }) === "worm/override-reason");
72
+ check("governance override with reason allowed", w.delete("g", { override: true, reason: "court order #5" }) === true);
73
+ // But a legal hold beats even a governance override.
74
+ w.put("g2", "x", { retainUntil: 9999, legalHold: "hold-1" });
75
+ check("legal hold beats governance override", code(function () { w.delete("g2", { override: true, reason: "x" }); }) === "worm/legal-hold");
76
+ }
77
+
78
+ function testTamperEvidence() {
79
+ var ref = { t: 1000 };
80
+ var store = (function () {
81
+ var m = new Map();
82
+ return { get: function (k) { return m.get(k); }, set: function (k, v) { m.set(k, v); }, delete: function (k) { m.delete(k); }, has: function (k) { return m.has(k); }, keys: function () { return Array.from(m.keys()); }, _raw: m };
83
+ })();
84
+ var w = b.worm.create({ store: store, clock: clockAt(ref), defaultRetentionMs: 1000 });
85
+ w.put("t", "trustworthy");
86
+ // Tamper with the stored bytes behind the WORM layer's back.
87
+ store._raw.get("t").bytes = Buffer.from("tampered!!!!");
88
+ check("get detects byte tampering vs the digest", code(function () { w.get("t"); }) === "worm/tampered");
89
+ check("list reflects stored ids", w.list().indexOf("t") !== -1);
90
+ }
91
+
92
+ function testCallerCannotMutateThroughInput() {
93
+ // A caller that keeps a reference to the Buffer it put must not be able to
94
+ // change the stored record after the fact — the store owns a private copy.
95
+ var w = b.worm.create({ defaultRetentionMs: 1000 });
96
+ var input = Buffer.from("original");
97
+ w.put("k", input);
98
+ input.write("XXXXXXXX"); // mutate the caller's buffer
99
+ var got = w.get("k");
100
+ check("post-put input mutation does not change stored bytes", got.data.toString() === "original");
101
+ }
102
+
103
+ function testCallerCannotMutateThroughOutput() {
104
+ // The buffer get() returns is a copy; mutating it must not corrupt the
105
+ // record or trip a false tamper on the next read.
106
+ var w = b.worm.create({ defaultRetentionMs: 1000 });
107
+ w.put("k", "original");
108
+ var first = w.get("k");
109
+ first.data.write("XXXXXXXX"); // mutate the returned buffer
110
+ var second = w.get("k");
111
+ check("mutating get() output does not corrupt the record", second.data.toString() === "original");
112
+ check("record still verifies after output mutation", code(function () { w.get("k"); }) === "NO-THROW");
113
+ }
114
+
115
+ async function run() {
116
+ testSurface();
117
+ testWriteOnceAndRetain();
118
+ testExtendOnly();
119
+ testLegalHold();
120
+ testGovernanceOverride();
121
+ testTamperEvidence();
122
+ testCallerCannotMutateThroughInput();
123
+ testCallerCannotMutateThroughOutput();
124
+ }
125
+ module.exports = { run: run };
126
+ if (require.main === module) { run().then(function () { console.log("[worm] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -40,6 +40,6 @@
40
40
  "homepage": "https://blamejs.shop",
41
41
  "devDependencies": {
42
42
  "@cloudflare/containers": "^0.3.4",
43
- "wrangler": "^4.93.0"
43
+ "wrangler": "^4.95.0"
44
44
  }
45
45
  }