xoonya 3.1.11 → 3.1.13

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/RELEASE_NOTES.md CHANGED
@@ -1,4 +1,4 @@
1
- `xoonya` 3.1.11 is a packaging and classification refresh. The proof engine, verification behavior, vectors, and routes are unchanged.
1
+ `xoonya` 3.1.13 adds ML-DSA (FIPS-204) to detached-signature verification.
2
2
 
3
- - The published package no longer carries a Python bytecode cache (it embedded a local build path); a `.gitignore` and a nested `.npmignore` keep caches, env files, and key material out of the repository and the tarball.
4
- - The crypto-agility matrix now classifies ML-DSA and SLH-DSA as runtime-supported and vector-covered, with the boundary stated honestly: the Ed25519 + ML-DSA-65 hybrid is exercised live via the paid x402 lane; SLH-DSA hybrids are implemented and vector-covered but not yet run through the paid lane. This is machine-evidence verification only — not an identity, custody, or chain-truth claim.
3
+ - `verifyExternalProofSignature` (and the import-path detached verify) now accepts `ML-DSA-44`, `ML-DSA-65`, and `ML-DSA-87` with SPKI public keys, alongside EdDSA, ECDSA, and RSA. The declared algorithm is bound to the key's parameter set, so an ML-DSA signature cannot be checked under another algorithm, and key refs use the same `<key type>-spki-sha256:<hex>` format, so a declared `expected_key_ref` anchors trust exactly as before.
4
+ - This lets xoonya independently verify both legs of a hybrid Ed25519 + ML-DSA-65 signature, such as the signed delivery receipts the naxytra runtime issues. Machine-evidence verification only — not identity, custody, or chain truth.
@@ -17,8 +17,9 @@
17
17
  // itself, treated as trusted.
18
18
  //
19
19
  // Dependency-free (node crypto). Supports compact JWS (EdDSA, ES256/384/512,
20
- // ES256K, RS256/384/512, PS256/384/512) and generic detached signatures. Private
21
- // key material (e.g. a JWK with a `d` component) is refused.
20
+ // ES256K, RS256/384/512, PS256/384/512) and generic detached signatures, which
21
+ // additionally accept ML-DSA-44/65/87 (FIPS-204, SPKI keys). Private key material
22
+ // (e.g. a JWK with a `d` component) is refused.
22
23
 
23
24
  const crypto = require("crypto");
24
25
 
@@ -41,7 +42,12 @@ const JWS_ALG = {
41
42
  RS512: { hash: "sha512", kind: "rsa" },
42
43
  PS256: { hash: "sha256", kind: "rsapss" },
43
44
  PS384: { hash: "sha384", kind: "rsapss" },
44
- PS512: { hash: "sha512", kind: "rsapss" }
45
+ PS512: { hash: "sha512", kind: "rsapss" },
46
+ // FIPS-204 ML-DSA (pure mode, as named by the JOSE ML-DSA draft). Needs Node >= 24
47
+ // (node:crypto ML-DSA); on an older runtime the key cannot be parsed and verify fails closed.
48
+ "ML-DSA-44": { hash: null, kind: "mldsa", keyType: "ml-dsa-44" },
49
+ "ML-DSA-65": { hash: null, kind: "mldsa", keyType: "ml-dsa-65" },
50
+ "ML-DSA-87": { hash: null, kind: "mldsa", keyType: "ml-dsa-87" }
45
51
  };
46
52
 
47
53
  function isPrivateJwk(obj) {
@@ -82,6 +88,7 @@ function keyMatchesAlg(keyObject, alg) {
82
88
  if (!spec || !keyObject) return false;
83
89
  const t = keyObject.asymmetricKeyType;
84
90
  if (spec.kind === "eddsa") return t === "ed25519";
91
+ if (spec.kind === "mldsa") return t === spec.keyType;
85
92
  if (spec.kind === "ecdsa") {
86
93
  if (t !== "ec") return false;
87
94
  const details = keyObject.asymmetricKeyDetails || {};
@@ -97,7 +104,7 @@ function verifyWithAlg(alg, signingInput, signature, keyObject) {
97
104
  if (!keyMatchesAlg(keyObject, alg)) return false; // bind declared alg to the key
98
105
  const data = Buffer.isBuffer(signingInput) ? signingInput : Buffer.from(String(signingInput), "utf8");
99
106
  try {
100
- if (spec.kind === "eddsa") return crypto.verify(null, data, keyObject, signature);
107
+ if (spec.kind === "eddsa" || spec.kind === "mldsa") return crypto.verify(null, data, keyObject, signature);
101
108
  if (spec.kind === "ecdsa") return crypto.verify(spec.hash, data, { key: keyObject, dsaEncoding: "ieee-p1363" }, signature);
102
109
  if (spec.kind === "rsa") return crypto.verify(spec.hash, data, keyObject, signature);
103
110
  if (spec.kind === "rsapss") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xoonya",
3
- "version": "3.1.11",
3
+ "version": "3.1.13",
4
4
  "description": "Deterministic proof and review engine with post-quantum verification (Ed25519 + ML-DSA-65 hybrid) for receipts, handoffs, governance, and the native XNYA machine-evidence format. Verified live via the paid x402 lane.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -122,6 +122,20 @@ function main() {
122
122
  assert.strictEqual(proofSdk.verifyExternalProofSignature({ jws: edJws, trusted_public_key: edPubPem }).signature_valid, true);
123
123
  assert.strictEqual(proofSdk.verifyExternalProofSignature({}).reason, "no_signature_verification_input");
124
124
 
125
+ // Detached ML-DSA-65 (FIPS-204): valid verifies + anchors to the declared key ref; tampered
126
+ // material fails; the declared alg is bound to the key type (no cross-alg confusion).
127
+ const mldsa = crypto.generateKeyPairSync("ml-dsa-65");
128
+ const mlSpki = mldsa.publicKey.export({ type: "spki", format: "der" }).toString("base64");
129
+ const mlRef = "ml-dsa-65-spki-sha256:" + crypto.createHash("sha256").update(mldsa.publicKey.export({ type: "spki", format: "der" })).digest("hex");
130
+ const mlMaterial = '{"receipt":"x","v":1}';
131
+ const mlSig = crypto.sign(null, Buffer.from(mlMaterial, "utf8"), mldsa.privateKey).toString("base64");
132
+ const mlOk = proofSdk.verifyExternalProofSignature({ material: mlMaterial, signature: mlSig, algorithm: "ML-DSA-65", public_key: mlSpki, expected_key_ref: mlRef });
133
+ assert.strictEqual(mlOk.signature_valid, true, "ML-DSA-65 detached signature verifies");
134
+ assert.strictEqual(mlOk.trust_anchored, true, "ML-DSA-65 anchors to the declared key ref");
135
+ assert.strictEqual(proofSdk.verifyExternalProofSignature({ material: mlMaterial + " ", signature: mlSig, algorithm: "ML-DSA-65", public_key: mlSpki }).signature_valid, false, "ML-DSA-65 tampered material fails");
136
+ assert.strictEqual(proofSdk.verifyExternalProofSignature({ material: mlMaterial, signature: mlSig, algorithm: "ML-DSA-44", public_key: mlSpki }).signature_valid, false, "ML-DSA alg bound to the key's parameter set");
137
+ assert.strictEqual(proofSdk.verifyExternalProofSignature({ material: mlMaterial, signature: mlSig, algorithm: "EdDSA", public_key: mlSpki }).signature_valid, false, "EdDSA cannot verify with an ML-DSA key");
138
+
125
139
  console.log("verify_external_proof_signature: all checks passed");
126
140
  }
127
141