xoonya 3.1.8 → 3.1.9

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,10 +1,11 @@
1
- `xoonya` 3.1.8 is the synchronized release that keeps the proof line aligned with the runtime.
1
+ `xoonya` 3.1.9 turns proof verification into a paid, pay-per-call machine service while keeping every existing free verification path and the deterministic proof engine unchanged.
2
2
 
3
- This release carries no change to the proof engine, the native XNYA standard, the verification methods, the conformance vectors, or the public proof surfaces. It exists to keep the proof package on the same synchronized line as the runtime, which in this cycle refreshed the public MCP executor's machine-readable description to lead with its pay-per-call story.
3
+ This is the first release to carry the monetization backend: the proof verifier can now be offered as a metered service behind the runtime's x402 payment spine, and imported external proofs are verified cryptographically rather than only hash-bound.
4
4
 
5
5
  Highlights:
6
6
 
7
- - keeps the deterministic hash, sign, and verify core, the verification-class and strict-mode behavior, and the trust-root options unchanged
8
- - keeps the conformance and forgery-resistance vectors and the full verifier suite passing
9
- - keeps the canonical `https://naxytra.com/xoonya` public product URL and the synchronized package posture intact
10
- - tracks the runtime's discovery-description refresh so the proof and runtime packages stay on one release number
7
+ - adds a bearer-authed `POST /execute` backend so proof verification can be sold per call behind the runtime's x402 settlement (fail-closed: 503 when the bearer is unset, 401 on a bad token, constant-time token comparison)
8
+ - bills only on a positively-successful verification — unknown tool, unprocessable input, a structural reject, or a negative verdict all return non-2xx so the runtime never settles for a non-result
9
+ - adds an optional paywall flag (`XOONYA_REQUIRE_PAID_VERIFICATION`, default off) that makes the free direct verify routes return 402 pointing to the paid lane, so the free HTTP path cannot undercut the paid product; `/execute` and `/health` always stay free
10
+ - verifies imported external proofs (compact JWS and detached signatures) cryptographically with `node:crypto` instead of only binding their hash — a real cross-runtime verifier, with `alg:none`/alg-confusion/private-key material rejected
11
+ - keeps the deterministic hash/sign/verify core, the native XNYA standard, the classical and post-quantum (ML-DSA-65, SLH-DSA-SHA2-128f) verification suites, the conformance and forgery-resistance vectors, strict-mode and trust-root options, and the canonical `https://naxytra.com/xoonya` product URL unchanged
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+
3
+ // Cryptographic verification of IMPORTED external proofs.
4
+ //
5
+ // xoonya binds external proof material into its own bundles. This module lets it
6
+ // additionally VERIFY the external material's own signature — reporting the
7
+ // cryptographic FACTS it can establish, without over-claiming trust.
8
+ //
9
+ // Honesty model (important): from a single self-contained request, xoonya can
10
+ // only establish that "a signature is valid under key K" (signature_valid) and
11
+ // report K's fingerprint (signing_key_ref). It CANNOT establish that K is the
12
+ // legitimate issuer/attester, nor revocation or DID-resolution status. Trust in K
13
+ // is the consumer's decision. The only way a caller gets a trust-anchored result
14
+ // (expected_key_match=true) is by DECLARING, out of band, the key fingerprint they
15
+ // already trust (expected_key_ref) and having the signer match it. A key supplied
16
+ // in the same request — or a self-asserted key embedded in the proof — is NOT, by
17
+ // itself, treated as trusted.
18
+ //
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.
22
+
23
+ const crypto = require("crypto");
24
+
25
+ function b64urlToBuffer(s) {
26
+ const str = String(s || "").replace(/-/g, "+").replace(/_/g, "/");
27
+ const pad = str.length % 4 === 0 ? "" : "=".repeat(4 - (str.length % 4));
28
+ return Buffer.from(str + pad, "base64");
29
+ }
30
+
31
+ // alg -> { hash, kind, keyType?/curve? } ; curve/keyType bind the declared alg to
32
+ // the actual key so the reported alg describes how it was really validated.
33
+ const JWS_ALG = {
34
+ EdDSA: { hash: null, kind: "eddsa", keyType: "ed25519" },
35
+ ES256: { hash: "sha256", kind: "ecdsa", curve: "prime256v1" },
36
+ ES384: { hash: "sha384", kind: "ecdsa", curve: "secp384r1" },
37
+ ES512: { hash: "sha512", kind: "ecdsa", curve: "secp521r1" },
38
+ ES256K: { hash: "sha256", kind: "ecdsa", curve: "secp256k1" },
39
+ RS256: { hash: "sha256", kind: "rsa" },
40
+ RS384: { hash: "sha384", kind: "rsa" },
41
+ RS512: { hash: "sha512", kind: "rsa" },
42
+ PS256: { hash: "sha256", kind: "rsapss" },
43
+ PS384: { hash: "sha384", kind: "rsapss" },
44
+ PS512: { hash: "sha512", kind: "rsapss" }
45
+ };
46
+
47
+ function isPrivateJwk(obj) {
48
+ return Boolean(obj && typeof obj === "object" && !Buffer.isBuffer(obj) && typeof obj.d === "string" && obj.d.length > 0);
49
+ }
50
+
51
+ // Build a PUBLIC KeyObject from PEM / base64 DER SPKI / public JWK. Refuses
52
+ // private key material. Returns null on any problem.
53
+ function toPublicKeyObject(input) {
54
+ if (!input) return null;
55
+ try {
56
+ if (typeof input === "object" && !Buffer.isBuffer(input)) {
57
+ if (isPrivateJwk(input)) return null; // never accept private material
58
+ return crypto.createPublicKey({ key: input, format: "jwk" });
59
+ }
60
+ const s = String(input).trim();
61
+ if (/BEGIN (EC |RSA |DSA |OPENSSH |PGP )?PRIVATE KEY/.test(s)) return null;
62
+ if (s.includes("BEGIN ")) return crypto.createPublicKey({ key: s, format: "pem" });
63
+ return crypto.createPublicKey({ key: Buffer.from(s, "base64"), format: "der", type: "spki" });
64
+ } catch (_) {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function keyRef(keyObject) {
70
+ try {
71
+ const spki = keyObject.export({ type: "spki", format: "der" });
72
+ const algo = keyObject.asymmetricKeyType || "key";
73
+ return `${algo}-spki-sha256:` + crypto.createHash("sha256").update(spki).digest("hex");
74
+ } catch (_) {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ // The declared alg must be consistent with the actual key type/curve.
80
+ function keyMatchesAlg(keyObject, alg) {
81
+ const spec = JWS_ALG[alg];
82
+ if (!spec || !keyObject) return false;
83
+ const t = keyObject.asymmetricKeyType;
84
+ if (spec.kind === "eddsa") return t === "ed25519";
85
+ if (spec.kind === "ecdsa") {
86
+ if (t !== "ec") return false;
87
+ const details = keyObject.asymmetricKeyDetails || {};
88
+ return details.namedCurve === spec.curve;
89
+ }
90
+ if (spec.kind === "rsa" || spec.kind === "rsapss") return t === "rsa" || t === "rsa-pss";
91
+ return false;
92
+ }
93
+
94
+ function verifyWithAlg(alg, signingInput, signature, keyObject) {
95
+ const spec = JWS_ALG[alg];
96
+ if (!spec || !keyObject) return false;
97
+ if (!keyMatchesAlg(keyObject, alg)) return false; // bind declared alg to the key
98
+ const data = Buffer.isBuffer(signingInput) ? signingInput : Buffer.from(String(signingInput), "utf8");
99
+ try {
100
+ if (spec.kind === "eddsa") return crypto.verify(null, data, keyObject, signature);
101
+ if (spec.kind === "ecdsa") return crypto.verify(spec.hash, data, { key: keyObject, dsaEncoding: "ieee-p1363" }, signature);
102
+ if (spec.kind === "rsa") return crypto.verify(spec.hash, data, keyObject, signature);
103
+ if (spec.kind === "rsapss") {
104
+ return crypto.verify(spec.hash, data, { key: keyObject, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST }, signature);
105
+ }
106
+ } catch (_) {
107
+ return false;
108
+ }
109
+ return false;
110
+ }
111
+
112
+ function trustResult(signatureValid, keyObject, keySource, expectedKeyRef, alg, extra) {
113
+ const kref = keyRef(keyObject);
114
+ const expected = typeof expectedKeyRef === "string" && expectedKeyRef.trim() ? expectedKeyRef.trim() : null;
115
+ const expectedMatch = expected ? kref === expected : null;
116
+ return {
117
+ ok: true,
118
+ signature_valid: signatureValid === true,
119
+ signing_key_ref: kref,
120
+ key_source: keySource,
121
+ expected_key_ref: expected,
122
+ // true only when the caller declared a trusted key fingerprint AND the signer
123
+ // matches it; null when no expectation was declared. Trust in signing_key_ref
124
+ // is otherwise the consumer's decision.
125
+ expected_key_match: expectedMatch,
126
+ trust_anchored: signatureValid === true && expectedMatch === true,
127
+ alg,
128
+ ...(extra || {})
129
+ };
130
+ }
131
+
132
+ // Verify a compact JWS. opts = { trusted_public_key?, expected_key_ref? }.
133
+ function verifyJws(jwsCompact, opts) {
134
+ const o = opts && typeof opts === "object" ? opts : {};
135
+ const parts = String(jwsCompact || "").split(".");
136
+ if (parts.length !== 3) return { ok: false, reason: "jws_malformed" };
137
+ const [protectedB64, payloadB64, signatureB64] = parts;
138
+ let header;
139
+ try {
140
+ header = JSON.parse(b64urlToBuffer(protectedB64).toString("utf8"));
141
+ } catch (_) {
142
+ return { ok: false, reason: "jws_header_invalid" };
143
+ }
144
+ const alg = header && typeof header.alg === "string" ? header.alg : null;
145
+ if (alg === "none") return { ok: false, reason: "jws_alg_none_rejected" };
146
+ if (!alg || !JWS_ALG[alg]) return { ok: false, reason: "jws_alg_unsupported", alg: alg || null };
147
+ // Refuse unknown critical extensions and non-standard signing-input variants.
148
+ if (Array.isArray(header.crit) && header.crit.length > 0) return { ok: false, reason: "jws_crit_unsupported" };
149
+ if (header.b64 === false) return { ok: false, reason: "jws_unencoded_payload_unsupported" };
150
+
151
+ const trustedKey = o.trusted_public_key ? toPublicKeyObject(o.trusted_public_key) : null;
152
+ const embeddedKey = !trustedKey && header.jwk ? toPublicKeyObject(header.jwk) : null;
153
+ const keyObject = trustedKey || embeddedKey;
154
+ if (!keyObject) return { ok: false, reason: "no_public_key" };
155
+
156
+ const valid = verifyWithAlg(alg, `${protectedB64}.${payloadB64}`, b64urlToBuffer(signatureB64), keyObject);
157
+ return trustResult(valid, keyObject, trustedKey ? "caller_supplied" : "embedded_self_asserted", o.expected_key_ref, alg, {
158
+ header,
159
+ payload_b64url: payloadB64
160
+ });
161
+ }
162
+
163
+ // Verify a detached signature over `material`. opts = { material, signature,
164
+ // signature_encoding?, public_key, algorithm, expected_key_ref? }.
165
+ function verifyDetachedSignature(opts) {
166
+ const o = opts && typeof opts === "object" ? opts : {};
167
+ const alg = typeof o.algorithm === "string" ? o.algorithm : null;
168
+ if (!alg || !JWS_ALG[alg]) return { ok: false, reason: "algorithm_unsupported", alg: alg || null };
169
+ const keyObject = toPublicKeyObject(o.public_key);
170
+ if (!keyObject) return { ok: false, reason: "no_public_key" };
171
+ if (o.material === undefined || o.material === null) return { ok: false, reason: "material_required" };
172
+ const material = Buffer.isBuffer(o.material) ? o.material : Buffer.from(String(o.material), "utf8");
173
+ let signature;
174
+ try {
175
+ const enc = o.signature_encoding === "hex" ? "hex" : o.signature_encoding === "base64url" ? null : "base64";
176
+ signature = enc ? Buffer.from(String(o.signature || ""), enc) : b64urlToBuffer(o.signature);
177
+ } catch (_) {
178
+ return { ok: false, reason: "signature_invalid" };
179
+ }
180
+ if (signature.length === 0) return { ok: false, reason: "signature_invalid" };
181
+ const valid = verifyWithAlg(alg, material, signature, keyObject);
182
+ return trustResult(valid, keyObject, "caller_supplied", o.expected_key_ref, alg);
183
+ }
184
+
185
+ function looksLikeCompactJws(s) {
186
+ return typeof s === "string" && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(s.trim());
187
+ }
188
+
189
+ module.exports = { verifyJws, verifyDetachedSignature, toPublicKeyObject, isPrivateJwk, looksLikeCompactJws };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xoonya",
3
- "version": "3.1.8",
3
+ "version": "3.1.9",
4
4
  "description": "Deterministic proof and review engine for receipts, handoffs, governance, and verification.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -76,6 +76,9 @@
76
76
  "verify:package": "node scripts/verify_all.js",
77
77
  "verify:conformance": "node scripts/verify_vectors.js",
78
78
  "verify:external-proof-import-gate": "node scripts/verify_external_proof_import_gate.js",
79
+ "verify:external-proof-signature": "node scripts/verify_external_proof_signature.js",
80
+ "verify:paid-execute": "node scripts/verify_paid_execute.js",
81
+ "verify:paywall-flag": "node scripts/verify_paywall_flag.js",
79
82
  "verify:integrations": "node scripts/verify_integrations.js",
80
83
  "verify:tooling": "node scripts/verify_tooling.js",
81
84
  "verify:reference-python": "node scripts/verify_reference_python.js",
@@ -92,7 +95,7 @@
92
95
  "verify:service": "node scripts/verify_service.js",
93
96
  "verify:forgery-resistance": "node scripts/verify_forgery_resistance.js",
94
97
  "verify:release-candidate": "node scripts/verify_release_candidate.js",
95
- "verify:all": "node scripts/verify_all.js && node scripts/verify_external_proof_import_gate.js && node scripts/verify_integrations.js && node scripts/verify_tooling.js && node scripts/verify_vectors.js && node scripts/verify_reference_python.js && node scripts/verify_reference_promotion_gate.js && node scripts/verify_reference_rust_scaffold.js && node scripts/verify_native_hybrid_design_gate.js && node scripts/verify_verification_services.js && node scripts/verify_trust_portability.js && node scripts/verify_control_plane_contracts.js && node scripts/verify_control_plane_routes.js && node scripts/verify_examples.js && node scripts/verify_service.js && node scripts/verify_forgery_resistance.js",
98
+ "verify:all": "node scripts/verify_all.js && node scripts/verify_external_proof_import_gate.js && node scripts/verify_integrations.js && node scripts/verify_tooling.js && node scripts/verify_vectors.js && node scripts/verify_reference_python.js && node scripts/verify_reference_promotion_gate.js && node scripts/verify_reference_rust_scaffold.js && node scripts/verify_native_hybrid_design_gate.js && node scripts/verify_verification_services.js && node scripts/verify_trust_portability.js && node scripts/verify_control_plane_contracts.js && node scripts/verify_control_plane_routes.js && node scripts/verify_examples.js && node scripts/verify_service.js && node scripts/verify_forgery_resistance.js && node scripts/verify_external_proof_signature.js && node scripts/verify_paid_execute.js && node scripts/verify_paywall_flag.js",
96
99
  "prepublishOnly": "npm run verify:release-candidate"
97
100
  }
98
101
  }