xoonya 3.1.12 → 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 +1,4 @@
1
- `xoonya` 3.1.12 is a synchronized-line release with no change to the proof engine, verification behavior, vectors, routes, or package contents. It keeps the naxytra release line aligned across xytara, xoonya, and the naxytra bootstrap package.
1
+ `xoonya` 3.1.13 adds ML-DSA (FIPS-204) to detached-signature verification.
2
+
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.12",
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