@blamejs/core 0.12.42 → 0.12.43

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.12.x
10
10
 
11
+ - v0.12.43 (2026-05-25) — **`b.crypto.selfTest` — FIPS 140-3-style power-on self-test for the crypto stack.** 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. **Added:** *`b.crypto.selfTest(opts?)`* — 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.
12
+
11
13
  - v0.12.42 (2026-05-24) — **`b.vc.present` / `b.vc.verifyPresentation` — W3C Verifiable Presentations.** 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. **Added:** *`b.vc.present(opts)` / `b.vc.verifyPresentation(secured, opts)`* — `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.
12
14
 
13
15
  - v0.12.41 (2026-05-24) — **`b.did` — W3C DID resolution (did:key + did:web) feeding the credential verifiers.** 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. **Added:** *`b.did.resolve(did, opts?)` / `b.did.keyToDid(publicKey)` / `b.did.parse(did)`* — `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.
package/README.md CHANGED
@@ -92,6 +92,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
92
92
  ### Crypto
93
93
 
94
94
  - **At-rest envelope** — envelope-versioned PQC (ML-KEM-1024 + P-384 hybrid, XChaCha20-Poly1305, SHAKE256); vault sealing (`b.crypto`, `b.vault`)
95
+ - **Power-on self-test** — `b.crypto.selfTest()` runs FIPS 140-3-style integrity checks: NIST FIPS 202 known-answer tests (SHA3-256/512, SHAKE256), AEAD round-trip + tamper-detect, and ML-KEM-1024 / ML-DSA-87 / SLH-DSA-SHAKE-256f pairwise-consistency + negative tests; fails closed (throws) on any mismatch
95
96
  - **Field-level + crypto-shred** — `b.cryptoField.eraseRow`; per-column data residency tagging + per-row keys (`K_row = HKDF(K_table, rowId)`) so erasing the per-row key makes WAL / replica residuals undecryptable (`b.cryptoField.declareColumnResidency`, `b.cryptoField.declarePerRowKey`)
96
97
  - **AAD-bound sealed columns** — AEAD tag tied to `(table, rowId, column, schemaVersion)`; copy-paste between rows or schema-version replay surfaces as refused decrypt (`b.vault.aad`)
97
98
  - **Signed webhooks + API encryption** — SLH-DSA-SHAKE-256f default; ML-DSA-65 opt-in; ECIES API encryption (`b.webhook`, `b.crypto`)
package/lib/crypto.js CHANGED
@@ -62,6 +62,9 @@ var audit = lazyRequire(function () { return require("./audit"); });
62
62
  // loaded because safe-buffer.js itself imports b.crypto for
63
63
  // hex-compare helpers (circular).
64
64
  var safeBuffer = lazyRequire(function () { return require("./safe-buffer"); });
65
+ // pqc-software requires this module (b.crypto) — lazy-load to break the
66
+ // cycle. Only the power-on self-test needs it here.
67
+ var pqcSoftware = lazyRequire(function () { return require("./pqc-software"); });
65
68
 
66
69
  // Streaming-hash algorithm allowlist. Mirrors the framework's PQC-
67
70
  // first crypto policy: SHA3 / SHAKE family is the default surface;
@@ -1873,8 +1876,124 @@ var SUPPORTED_KEM_ALGORITHMS = Object.freeze([
1873
1876
  // var fixtures = require("blamejs/lib/_test/crypto-fixtures");
1874
1877
  // var blob = fixtures.mintLegacyEnvelope0xE1(plaintext, recipient);
1875
1878
 
1879
+ // ---- FIPS 140-3-style power-on self-test ----
1880
+ //
1881
+ // Known-answer tests (KATs) for the deterministic primitives — the
1882
+ // hash / XOF digests are NIST FIPS 202 published vectors, so this
1883
+ // confirms the framework's hashing matches the standard, not merely
1884
+ // itself. The PQC algorithms have no seed-injection API (node generates
1885
+ // the keypair randomness internally), so they get FIPS 140-3 §10.3
1886
+ // pairwise-consistency + negative tests (a fresh keypair must
1887
+ // sign->verify / encaps->decaps consistently, and a tampered signature
1888
+ // must be rejected) rather than a fixed-seed KAT.
1889
+ var KAT_SHA3_512_ABC = "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0";
1890
+ var KAT_SHA3_256_ABC = "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532";
1891
+ var KAT_SHAKE256_ABC_32 = "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739";
1892
+
1893
+ /**
1894
+ * @primitive b.crypto.selfTest
1895
+ * @signature b.crypto.selfTest(opts?)
1896
+ * @since 0.12.43
1897
+ * @status stable
1898
+ * @compliance soc2, hipaa, pci-dss
1899
+ * @related b.crypto.sha3Hash, b.crypto.encrypt
1900
+ *
1901
+ * Run a power-on self-test over the framework's cryptographic
1902
+ * primitives — the integrity check FIPS 140-3 requires of a validated
1903
+ * module. The hash / XOF checks are known-answer tests against NIST FIPS
1904
+ * 202 vectors (SHA3-256 / SHA3-512 / SHAKE256); the AEAD check
1905
+ * round-trips XChaCha20-Poly1305 and confirms a tampered ciphertext is
1906
+ * rejected; the post-quantum checks run a pairwise-consistency +
1907
+ * negative test for ML-KEM-1024, ML-DSA-87, and SLH-DSA-SHAKE-256f.
1908
+ * Returns a structured report and, by default, throws on any failure so
1909
+ * a broken crypto stack fails closed at boot rather than silently
1910
+ * producing bad output.
1911
+ *
1912
+ * @opts
1913
+ * {
1914
+ * throwOnFailure?: boolean, // default true — throw if any check fails
1915
+ * }
1916
+ *
1917
+ * @example
1918
+ * var report = b.crypto.selfTest();
1919
+ * // -> { ok: true, results: [ { name, ok }, ... ], failures: [], ranAt }
1920
+ */
1921
+ function selfTest(opts) {
1922
+ opts = opts || {};
1923
+ var results = [];
1924
+ function record(name, fn) {
1925
+ try { fn(); results.push({ name: name, ok: true }); }
1926
+ catch (e) { results.push({ name: name, ok: false, detail: (e && e.message) || String(e) }); }
1927
+ }
1928
+ function assert(cond, msg) { if (!cond) throw new Error(msg); }
1929
+
1930
+ record("SHA3-512 KAT (FIPS 202)", function () {
1931
+ assert(sha3Hash("abc") === KAT_SHA3_512_ABC, "SHA3-512(\"abc\") does not match the FIPS 202 vector");
1932
+ });
1933
+ record("SHA3-256 KAT (FIPS 202)", function () {
1934
+ assert(hash("abc", "sha3-256").toString("hex") === KAT_SHA3_256_ABC, "SHA3-256(\"abc\") does not match the FIPS 202 vector");
1935
+ });
1936
+ record("SHAKE256 KAT (FIPS 202)", function () {
1937
+ assert(hash("abc", "shake256", C.BYTES.bytes(32)).toString("hex") === KAT_SHAKE256_ABC_32, "SHAKE256(\"abc\") does not match the FIPS 202 vector");
1938
+ });
1939
+ record("HMAC-SHA3-512 determinism", function () {
1940
+ var k = Buffer.from("self-test-hmac-key", "utf8");
1941
+ assert(timingSafeEqual(hmacSha3(k, "abc"), hmacSha3(k, "abc")), "HMAC-SHA3-512 is not deterministic");
1942
+ assert(!timingSafeEqual(hmacSha3(k, "abc"), hmacSha3(k, "abd")), "HMAC-SHA3-512 collided on distinct inputs");
1943
+ });
1944
+ record("XChaCha20-Poly1305 round-trip + tamper-detect", function () {
1945
+ var key = generateBytes(C.BYTES.bytes(32));
1946
+ var pt = Buffer.from("blamejs crypto self-test plaintext", "utf8");
1947
+ var packed = encryptPacked(pt, key);
1948
+ assert(decryptPacked(packed, key).equals(pt), "AEAD round-trip did not recover the plaintext");
1949
+ var bad = Buffer.from(packed); bad[bad.length - 1] ^= 0xff;
1950
+ var rejected = false;
1951
+ try { decryptPacked(bad, key); } catch (_e) { rejected = true; }
1952
+ assert(rejected, "AEAD accepted a tampered ciphertext");
1953
+ });
1954
+
1955
+ // Post-quantum pairwise-consistency + negative tests. PQC is an
1956
+ // optional vendored dependency — a load failure surfaces as a failed
1957
+ // check rather than a silent skip.
1958
+ var pqc = pqcSoftware();
1959
+ record("ML-KEM-1024 encaps/decaps pairwise consistency", function () {
1960
+ var kp = pqc.ml_kem_1024.keygen();
1961
+ var enc = pqc.ml_kem_1024.encapsulate(kp.publicKey);
1962
+ var ss = pqc.ml_kem_1024.decapsulate(enc.cipherText, kp.secretKey);
1963
+ assert(timingSafeEqual(Buffer.from(ss), Buffer.from(enc.sharedSecret)), "ML-KEM-1024 decapsulated secret does not match");
1964
+ });
1965
+ record("ML-DSA-87 sign/verify + negative", function () {
1966
+ var kp = pqc.ml_dsa_87.keygen();
1967
+ var msg = Buffer.from("blamejs ML-DSA self-test", "utf8");
1968
+ var sig = pqc.ml_dsa_87.sign(msg, kp.secretKey);
1969
+ assert(pqc.ml_dsa_87.verify(sig, msg, kp.publicKey), "ML-DSA-87 rejected a valid signature");
1970
+ var bad = Buffer.from(sig); bad[0] ^= 0xff;
1971
+ assert(!pqc.ml_dsa_87.verify(bad, msg, kp.publicKey), "ML-DSA-87 accepted a tampered signature");
1972
+ });
1973
+ record("SLH-DSA-SHAKE-256f sign/verify + negative", function () {
1974
+ var kp = pqc.slh_dsa_shake_256f.keygen();
1975
+ var msg = Buffer.from("blamejs SLH-DSA self-test", "utf8");
1976
+ var sig = pqc.slh_dsa_shake_256f.sign(msg, kp.secretKey);
1977
+ assert(pqc.slh_dsa_shake_256f.verify(sig, msg, kp.publicKey), "SLH-DSA-SHAKE-256f rejected a valid signature");
1978
+ var bad = Buffer.from(sig); bad[0] ^= 0xff;
1979
+ assert(!pqc.slh_dsa_shake_256f.verify(bad, msg, kp.publicKey), "SLH-DSA-SHAKE-256f accepted a tampered signature");
1980
+ });
1981
+
1982
+ var failures = results.filter(function (r) { return !r.ok; });
1983
+ var report = { ok: failures.length === 0, results: results, failures: failures, ranAt: new Date().toISOString() };
1984
+ if (failures.length && opts.throwOnFailure !== false) {
1985
+ var err = new Error("crypto.selfTest: " + failures.length + " self-test(s) failed: " +
1986
+ failures.map(function (f) { return f.name; }).join("; "));
1987
+ err.code = "crypto/self-test-failed";
1988
+ err.report = report;
1989
+ throw err;
1990
+ }
1991
+ return report;
1992
+ }
1993
+
1876
1994
  module.exports = {
1877
1995
  sri: sri,
1996
+ selfTest: selfTest,
1878
1997
  // Hashing
1879
1998
  sha3Hash: sha3Hash,
1880
1999
  hmacSha3: hmacSha3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.12.42",
3
+ "version": "0.12.43",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:313dd43c-bb3f-4c16-a801-2af63046b56f",
5
+ "serialNumber": "urn:uuid:2659855c-303c-4b3d-931b-a39839633612",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-25T06:48:54.423Z",
8
+ "timestamp": "2026-05-25T07:30:43.059Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.12.42",
22
+ "bom-ref": "@blamejs/core@0.12.43",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.12.42",
25
+ "version": "0.12.43",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.12.42",
29
+ "purl": "pkg:npm/%40blamejs/core@0.12.43",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.12.42",
57
+ "ref": "@blamejs/core@0.12.43",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]