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 +7 -6
- package/lib/external_proof_verify.js +189 -0
- package/package.json +5 -2
- package/proof_sdk.js +6692 -6561
- package/scripts/verify_external_proof_signature.js +128 -0
- package/scripts/verify_paid_execute.js +133 -0
- package/scripts/verify_paywall_flag.js +86 -0
- package/server.js +4430 -4223
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies xoonya's external-proof signature verification (lib/external_proof_verify.js
|
|
4
|
+
// + proof_sdk integration) under the HONEST trust model: it reports signature_valid
|
|
5
|
+
// (a fact) + signing_key_ref, and only reports trust_anchored when the caller declared
|
|
6
|
+
// an expected_key_ref that matches the signer. Covers JWS (EdDSA/ES256/RS256), the
|
|
7
|
+
// alg<->key binding, alg:none/crit/malformed rejection, private-key refusal, detached
|
|
8
|
+
// signatures, import-run promotion, and byte-for-byte backward compatibility.
|
|
9
|
+
|
|
10
|
+
const crypto = require("crypto");
|
|
11
|
+
const assert = require("assert");
|
|
12
|
+
const { verifyJws, verifyDetachedSignature } = require("../lib/external_proof_verify");
|
|
13
|
+
|
|
14
|
+
const b64url = (buf) => Buffer.from(buf).toString("base64url");
|
|
15
|
+
|
|
16
|
+
function makeJws(alg, payloadObj, signFn, extraHeader) {
|
|
17
|
+
const header = { alg, ...(extraHeader || {}) };
|
|
18
|
+
const protectedB64 = b64url(Buffer.from(JSON.stringify(header), "utf8"));
|
|
19
|
+
const payloadB64 = b64url(Buffer.from(JSON.stringify(payloadObj), "utf8"));
|
|
20
|
+
const signingInput = `${protectedB64}.${payloadB64}`;
|
|
21
|
+
return `${signingInput}.${b64url(signFn(Buffer.from(signingInput, "utf8")))}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function main() {
|
|
25
|
+
const ed = crypto.generateKeyPairSync("ed25519");
|
|
26
|
+
const edPubPem = ed.publicKey.export({ type: "spki", format: "pem" });
|
|
27
|
+
const edJws = makeJws("EdDSA", { sub: "agent-1" }, (d) => crypto.sign(null, d, ed.privateKey));
|
|
28
|
+
|
|
29
|
+
// 1. EdDSA JWS: signature_valid is a fact; trust requires a declared key ref.
|
|
30
|
+
let r = verifyJws(edJws, { trusted_public_key: edPubPem });
|
|
31
|
+
assert.strictEqual(r.ok, true);
|
|
32
|
+
assert.strictEqual(r.signature_valid, true, "EdDSA signature valid");
|
|
33
|
+
assert.ok(r.signing_key_ref && r.signing_key_ref.startsWith("ed25519-"));
|
|
34
|
+
assert.strictEqual(r.trust_anchored, false, "no expected_key_ref -> not trust-anchored");
|
|
35
|
+
const edRef = r.signing_key_ref;
|
|
36
|
+
assert.strictEqual(verifyJws(edJws, { trusted_public_key: edPubPem, expected_key_ref: edRef }).trust_anchored, true, "matching expected_key_ref -> anchored");
|
|
37
|
+
assert.strictEqual(verifyJws(edJws, { trusted_public_key: edPubPem, expected_key_ref: "ed25519-spki-sha256:deadbeef" }).trust_anchored, false, "wrong expected_key_ref -> not anchored");
|
|
38
|
+
|
|
39
|
+
// wrong key / tamper -> signature invalid
|
|
40
|
+
const ed2 = crypto.generateKeyPairSync("ed25519");
|
|
41
|
+
assert.strictEqual(verifyJws(edJws, { trusted_public_key: ed2.publicKey.export({ type: "spki", format: "pem" }) }).signature_valid, false);
|
|
42
|
+
|
|
43
|
+
// 2. Embedded self-asserted key: signature internally valid but NOT caller-supplied.
|
|
44
|
+
const edJwk = ed.publicKey.export({ format: "jwk" });
|
|
45
|
+
const edJwsEmbedded = makeJws("EdDSA", { sub: "agent-1" }, (d) => crypto.sign(null, d, ed.privateKey), { jwk: edJwk });
|
|
46
|
+
r = verifyJws(edJwsEmbedded);
|
|
47
|
+
assert.strictEqual(r.signature_valid, true, "embedded-key signature internally valid");
|
|
48
|
+
assert.strictEqual(r.key_source, "embedded_self_asserted");
|
|
49
|
+
assert.strictEqual(r.trust_anchored, false, "self-asserted key is never trust-anchored");
|
|
50
|
+
|
|
51
|
+
// 3. ES256 works; alg<->key binding rejects an ES256K header over a P-256 key.
|
|
52
|
+
const ec = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
53
|
+
const ecPubPem = ec.publicKey.export({ type: "spki", format: "pem" });
|
|
54
|
+
const es256Jws = makeJws("ES256", { sub: "a" }, (d) => crypto.sign("sha256", d, { key: ec.privateKey, dsaEncoding: "ieee-p1363" }));
|
|
55
|
+
assert.strictEqual(verifyJws(es256Jws, { trusted_public_key: ecPubPem }).signature_valid, true, "ES256 valid");
|
|
56
|
+
const es256kMisdeclared = makeJws("ES256K", { sub: "a" }, (d) => crypto.sign("sha256", d, { key: ec.privateKey, dsaEncoding: "ieee-p1363" }));
|
|
57
|
+
assert.strictEqual(verifyJws(es256kMisdeclared, { trusted_public_key: ecPubPem }).signature_valid, false, "alg<->key mismatch (ES256K over P-256) rejected");
|
|
58
|
+
|
|
59
|
+
// 4. RS256
|
|
60
|
+
const rsa = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
61
|
+
const rsaJws = makeJws("RS256", { sub: "a" }, (d) => crypto.sign("sha256", d, rsa.privateKey));
|
|
62
|
+
assert.strictEqual(verifyJws(rsaJws, { trusted_public_key: rsa.publicKey.export({ type: "spki", format: "pem" }) }).signature_valid, true, "RS256 valid");
|
|
63
|
+
|
|
64
|
+
// 5. none / crit / malformed / private-key refusal.
|
|
65
|
+
assert.strictEqual(verifyJws(makeJws("none", { x: 1 }, () => Buffer.alloc(0)), { trusted_public_key: edPubPem }).reason, "jws_alg_none_rejected");
|
|
66
|
+
const critJws = makeJws("EdDSA", { sub: "a" }, (d) => crypto.sign(null, d, ed.privateKey), { crit: ["exp"] });
|
|
67
|
+
assert.strictEqual(verifyJws(critJws, { trusted_public_key: edPubPem }).reason, "jws_crit_unsupported");
|
|
68
|
+
assert.strictEqual(verifyJws("only.two").reason, "jws_malformed");
|
|
69
|
+
assert.strictEqual(verifyJws(edJws, { trusted_public_key: ed.privateKey.export({ type: "pkcs8", format: "pem" }) }).reason, "no_public_key", "PEM private key refused as trust key");
|
|
70
|
+
assert.strictEqual(verifyJws(edJws, { trusted_public_key: ed.privateKey.export({ format: "jwk" }) }).reason, "no_public_key", "private JWK refused");
|
|
71
|
+
|
|
72
|
+
// 6. Detached signature.
|
|
73
|
+
const material = "x402-receipt:0xabc";
|
|
74
|
+
const detSig = crypto.sign(null, Buffer.from(material), ed.privateKey).toString("base64");
|
|
75
|
+
r = verifyDetachedSignature({ material, signature: detSig, signature_encoding: "base64", public_key: edPubPem, algorithm: "EdDSA", expected_key_ref: edRef });
|
|
76
|
+
assert.strictEqual(r.signature_valid, true);
|
|
77
|
+
assert.strictEqual(r.trust_anchored, true, "detached with matching expected_key_ref -> anchored");
|
|
78
|
+
assert.strictEqual(verifyDetachedSignature({ material: "tampered", signature: detSig, signature_encoding: "base64", public_key: edPubPem, algorithm: "EdDSA" }).signature_valid, false);
|
|
79
|
+
|
|
80
|
+
// 7. proof_sdk integration.
|
|
81
|
+
const proofSdk = require("../proof_sdk");
|
|
82
|
+
// no verification input -> structural, NO external_signature_* fields (byte-for-byte).
|
|
83
|
+
const run0 = proofSdk.runExternalProofImport({ method: "jwt-assertion-v1", token: edJws, issuer_ref: "iss" });
|
|
84
|
+
assert.strictEqual(run0.external_signature_valid, undefined, "no verification -> no top-level field");
|
|
85
|
+
assert.strictEqual(run0.proof_bundle.attestations[0].external_signature_valid, undefined, "no verification -> no attestation field");
|
|
86
|
+
assert.ok(String(run0.proof_bundle.attestations[0].verification_boundary).includes("external_truth_out_of_scope"));
|
|
87
|
+
const run0Keys = Object.keys(run0.proof_bundle.attestations[0]).length;
|
|
88
|
+
|
|
89
|
+
// A stray TOP-LEVEL signature/key field must NOT activate verification (namespace only).
|
|
90
|
+
const run0b = proofSdk.runExternalProofImport({ method: "jwt-assertion-v1", token: edJws, issuer_ref: "iss", signature: "ZZZZ", public_key: edPubPem });
|
|
91
|
+
assert.strictEqual(run0b.external_signature_valid, undefined, "top-level fields do not activate verification");
|
|
92
|
+
assert.strictEqual(Object.keys(run0b.proof_bundle.attestations[0]).length, run0Keys, "attestation shape unchanged by stray top-level fields");
|
|
93
|
+
|
|
94
|
+
// trusted key but no expected_key_ref -> signature_valid, NOT anchored; result echo is whitelisted.
|
|
95
|
+
const run1 = proofSdk.runExternalProofImport({ method: "jwt-assertion-v1", token: edJws, issuer_ref: "iss", signature_verification: { trusted_public_key: edPubPem } });
|
|
96
|
+
assert.strictEqual(run1.external_signature_valid, true, "imported JWS signature verified");
|
|
97
|
+
assert.strictEqual(run1.external_signature_trust_anchored, false);
|
|
98
|
+
assert.ok(String(run1.proof_bundle.attestations[0].verification_boundary).includes("consumer_decision"));
|
|
99
|
+
assert.strictEqual(run1.signature_verification.header, undefined, "result echo does not leak JWS header");
|
|
100
|
+
assert.strictEqual(run1.signature_verification.payload_b64url, undefined, "result echo does not leak payload");
|
|
101
|
+
|
|
102
|
+
// declared expected_key_ref matching the signer -> trust-anchored.
|
|
103
|
+
const run2 = proofSdk.runExternalProofImport({ method: "jwt-assertion-v1", token: edJws, issuer_ref: "iss", signature_verification: { trusted_public_key: edPubPem, expected_key_ref: edRef } });
|
|
104
|
+
assert.strictEqual(run2.external_signature_trust_anchored, true, "declared+matching key -> anchored");
|
|
105
|
+
assert.ok(String(run2.proof_bundle.attestations[0].verification_boundary).includes("cryptographically_verified_against_declared_key_ref"));
|
|
106
|
+
|
|
107
|
+
// Requested-but-INVALID signature hard-fails the import (ok:false).
|
|
108
|
+
const run3 = proofSdk.runExternalProofImport({ method: "jwt-assertion-v1", token: edJws, issuer_ref: "iss", signature_verification: { trusted_public_key: ed2.publicKey.export({ type: "spki", format: "pem" }) } });
|
|
109
|
+
assert.strictEqual(run3.ok, false, "invalid requested signature hard-fails");
|
|
110
|
+
assert.strictEqual(run3.reason, "external_signature_invalid");
|
|
111
|
+
// ...unless the caller opts out.
|
|
112
|
+
const run3b = proofSdk.runExternalProofImport({ method: "jwt-assertion-v1", token: edJws, issuer_ref: "iss", signature_verification: { trusted_public_key: ed2.publicKey.export({ type: "spki", format: "pem" }), require_valid_signature: false } });
|
|
113
|
+
assert.strictEqual(run3b.ok, true, "opt-out records the import despite invalid signature");
|
|
114
|
+
assert.strictEqual(run3b.external_signature_valid, false);
|
|
115
|
+
|
|
116
|
+
// Object material + detached signature request -> unsupported (does not mislead), hard-fails by default.
|
|
117
|
+
const run4 = proofSdk.runExternalProofImport({ method: "signed-envelope-v1", proof_envelope: { claim: "x" }, issuer_ref: "iss", signature_verification: { trusted_public_key: edPubPem, signature: "AA", algorithm: "EdDSA" } });
|
|
118
|
+
assert.strictEqual(run4.ok, false);
|
|
119
|
+
assert.strictEqual(run4.reason, "unsupported_import_signature_verification");
|
|
120
|
+
|
|
121
|
+
// standalone function
|
|
122
|
+
assert.strictEqual(proofSdk.verifyExternalProofSignature({ jws: edJws, trusted_public_key: edPubPem }).signature_valid, true);
|
|
123
|
+
assert.strictEqual(proofSdk.verifyExternalProofSignature({}).reason, "no_signature_verification_input");
|
|
124
|
+
|
|
125
|
+
console.log("verify_external_proof_signature: all checks passed");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main();
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies the bearer-authed /execute backend that exposes xoonya's real proof
|
|
4
|
+
// verification pay-per-call through the xytara x402 spine (xytara settles, then calls
|
|
5
|
+
// this as an external execution target). Covers: fail-closed when the bearer env is
|
|
6
|
+
// unset (503), 401 on missing/wrong bearer, 422 on unknown tool / unprocessable input /
|
|
7
|
+
// private-key material, 200 with the MCP-executor-compatible output contract for a real
|
|
8
|
+
// EdDSA JWS verification, and that existing free routes are unchanged.
|
|
9
|
+
|
|
10
|
+
const assert = require("assert");
|
|
11
|
+
const crypto = require("crypto");
|
|
12
|
+
const http = require("http");
|
|
13
|
+
|
|
14
|
+
const b64url = (buf) => Buffer.from(buf).toString("base64url");
|
|
15
|
+
function makeJws(alg, payloadObj, signFn) {
|
|
16
|
+
const protectedB64 = b64url(Buffer.from(JSON.stringify({ alg }), "utf8"));
|
|
17
|
+
const payloadB64 = b64url(Buffer.from(JSON.stringify(payloadObj), "utf8"));
|
|
18
|
+
const signingInput = `${protectedB64}.${payloadB64}`;
|
|
19
|
+
return `${signingInput}.${b64url(signFn(Buffer.from(signingInput, "utf8")))}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function post(baseUrl, path, body, headers = {}) {
|
|
23
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "content-type": "application/json", ...headers },
|
|
26
|
+
body: JSON.stringify(body)
|
|
27
|
+
});
|
|
28
|
+
let json = null;
|
|
29
|
+
try { json = await res.json(); } catch (_) { json = null; }
|
|
30
|
+
return { status: res.status, json };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function executeBody(toolName, args, taskRef) {
|
|
34
|
+
return { command: "xoonya proof verify", task_ref: taskRef || "adapter.xoonya.invoke", tasks: [], body: { tool_name: toolName, arguments: args }, account_id: "acct_test", quote: {}, authorization_context: null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
const prev = {
|
|
39
|
+
bearer: process.env.XOONYA_EXECUTOR_BEARER_TOKEN,
|
|
40
|
+
pkm: process.env.XOONYA_ACCEPT_PRIVATE_KEY_MATERIAL
|
|
41
|
+
};
|
|
42
|
+
delete process.env.XOONYA_EXECUTOR_BEARER_TOKEN;
|
|
43
|
+
|
|
44
|
+
delete require.cache[require.resolve("../server.js")];
|
|
45
|
+
const { createServer } = require("../server.js");
|
|
46
|
+
const server = createServer();
|
|
47
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
48
|
+
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
49
|
+
const TOKEN = "test-executor-bearer-token";
|
|
50
|
+
const authHdr = { authorization: `Bearer ${TOKEN}` };
|
|
51
|
+
|
|
52
|
+
// Build a real EdDSA JWS + its public key for a genuine verification.
|
|
53
|
+
const ed = crypto.generateKeyPairSync("ed25519");
|
|
54
|
+
const edPubPem = ed.publicKey.export({ type: "spki", format: "pem" });
|
|
55
|
+
const edJws = makeJws("EdDSA", { sub: "agent-1" }, (d) => crypto.sign(null, d, ed.privateKey));
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// 1. Fail-closed: bearer env unset -> 503, never runs unauthenticated.
|
|
59
|
+
let r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", {}), authHdr);
|
|
60
|
+
assert.strictEqual(r.status, 503, "unset bearer env must 503");
|
|
61
|
+
assert.strictEqual(r.json.reason, "executor_not_configured", "503 reason");
|
|
62
|
+
|
|
63
|
+
process.env.XOONYA_EXECUTOR_BEARER_TOKEN = TOKEN;
|
|
64
|
+
|
|
65
|
+
// 2. Missing / wrong bearer -> 401.
|
|
66
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", {}));
|
|
67
|
+
assert.strictEqual(r.status, 401, "missing bearer must 401");
|
|
68
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", {}), { authorization: "Bearer wrong" });
|
|
69
|
+
assert.strictEqual(r.status, 401, "wrong bearer must 401");
|
|
70
|
+
|
|
71
|
+
// 3. Unknown tool -> 422 (don't-charge class).
|
|
72
|
+
r = await post(baseUrl, "/execute", executeBody("proof.nope", {}), authHdr);
|
|
73
|
+
assert.strictEqual(r.status, 422, "unknown tool must 422");
|
|
74
|
+
assert.strictEqual(r.json.reason, "unknown_tool", "unknown_tool reason");
|
|
75
|
+
assert.ok(Array.isArray(r.json.enabled_tools) && r.json.enabled_tools.includes("proof.verify-signature"), "enabled_tools listed");
|
|
76
|
+
|
|
77
|
+
// 4. Real EdDSA JWS verification -> 200 with the MCP-executor output contract.
|
|
78
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", { signature_verification: { jws: edJws, trusted_public_key: edPubPem } }), authHdr);
|
|
79
|
+
assert.strictEqual(r.status, 200, "valid verification must 200");
|
|
80
|
+
assert.strictEqual(r.json.ok, true, "200 ok");
|
|
81
|
+
assert.strictEqual(r.json.status, "completed", "status completed");
|
|
82
|
+
assert.strictEqual(r.json.provider_status, "completed", "provider_status completed");
|
|
83
|
+
assert.ok(r.json.output_payload && r.json.output_payload.signature_valid === true, "real signature verified valid in output_payload");
|
|
84
|
+
assert.ok(r.json.result && r.json.result.output && r.json.result.output.signature_valid === true, "nested result.output present (xytara lifts this)");
|
|
85
|
+
assert.strictEqual(r.json.output_payload.trust_anchored, false, "no expected_key_ref -> not trust-anchored (honest model preserved)");
|
|
86
|
+
|
|
87
|
+
// 4b. A NEGATIVE verdict (wrong key) is NOT billed: money-safety rule charges only on
|
|
88
|
+
// a positively-successful verification. 422, not charged, verdict surfaced.
|
|
89
|
+
const ed2 = crypto.generateKeyPairSync("ed25519");
|
|
90
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", { signature_verification: { jws: edJws, trusted_public_key: ed2.publicKey.export({ type: "spki", format: "pem" }) } }), authHdr);
|
|
91
|
+
assert.strictEqual(r.status, 422, "a ran-but-invalid verdict must not be charged (422)");
|
|
92
|
+
assert.ok(r.json.verdict && r.json.verdict.signature_valid === false, "negative verdict surfaced in 422 body");
|
|
93
|
+
|
|
94
|
+
// 5. Unprocessable input (no signature_verification) -> 422, not charged.
|
|
95
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", {}), authHdr);
|
|
96
|
+
assert.strictEqual(r.status, 422, "no sig input must 422");
|
|
97
|
+
assert.strictEqual(r.json.reason, "no_signature_verification_input", "unprocessable reason surfaced");
|
|
98
|
+
|
|
99
|
+
// 5b. The other three tools report unprocessable/negative via {valid:false} with NO
|
|
100
|
+
// `ok` field — empty args to each must be 422 (the structural-reject bug fix), so
|
|
101
|
+
// a paid caller is never charged for sending nothing verifiable.
|
|
102
|
+
for (const tool of ["proof.verify-bundle", "proof.verify-envelope", "proof.verify-receipt"]) {
|
|
103
|
+
r = await post(baseUrl, "/execute", executeBody(tool, {}), authHdr);
|
|
104
|
+
assert.strictEqual(r.status, 422, `${tool} with empty args must 422 (not a billable 200)`);
|
|
105
|
+
assert.strictEqual(r.json.ok, false, `${tool} empty-args must report ok:false`);
|
|
106
|
+
}
|
|
107
|
+
// A malformed receipt hex is likewise a structural reject -> 422, not a billed 200.
|
|
108
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-receipt", { opreturn_hex: "zz" }), authHdr);
|
|
109
|
+
assert.strictEqual(r.status, 422, "non-hex receipt must 422");
|
|
110
|
+
assert.strictEqual(r.json.reason, "non_hex", "non_hex reason surfaced");
|
|
111
|
+
|
|
112
|
+
// 6. Private-key material refused -> 422 (only when the deployment rejects it).
|
|
113
|
+
process.env.XOONYA_ACCEPT_PRIVATE_KEY_MATERIAL = "false";
|
|
114
|
+
r = await post(baseUrl, "/execute", executeBody("proof.verify-signature", { signature_verification: { private_key: "-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----" } }), authHdr);
|
|
115
|
+
assert.strictEqual(r.status, 422, "private key material must 422");
|
|
116
|
+
assert.strictEqual(r.json.reason, "private_key_material_not_accepted", "pkm reason");
|
|
117
|
+
|
|
118
|
+
// 7. Non-breaking: an existing free route still answers unchanged.
|
|
119
|
+
const health = await fetch(`${baseUrl}/health`);
|
|
120
|
+
assert.strictEqual(health.status, 200, "free /health route unchanged");
|
|
121
|
+
|
|
122
|
+
console.log("xoonya verify:paid-execute passed");
|
|
123
|
+
} finally {
|
|
124
|
+
await new Promise((resolve) => server.close(resolve));
|
|
125
|
+
if (prev.bearer === undefined) delete process.env.XOONYA_EXECUTOR_BEARER_TOKEN; else process.env.XOONYA_EXECUTOR_BEARER_TOKEN = prev.bearer;
|
|
126
|
+
if (prev.pkm === undefined) delete process.env.XOONYA_ACCEPT_PRIVATE_KEY_MATERIAL; else process.env.XOONYA_ACCEPT_PRIVATE_KEY_MATERIAL = prev.pkm;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main().catch((error) => {
|
|
131
|
+
console.error(error && error.message ? error.message : error);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies the optional paywall flag (XOONYA_REQUIRE_PAID_VERIFICATION) for the FREE
|
|
4
|
+
// direct verification routes. Default OFF => routes stay free (non-breaking). ON => the
|
|
5
|
+
// five crypto-verify routes that have a metered paid equivalent return 402 pointing to
|
|
6
|
+
// the paid x402 lane, while /execute, /health and descriptor GETs stay free.
|
|
7
|
+
|
|
8
|
+
const assert = require("assert");
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
const http = require("http");
|
|
11
|
+
|
|
12
|
+
const b64url = (buf) => Buffer.from(buf).toString("base64url");
|
|
13
|
+
function makeJws(payloadObj, privateKey) {
|
|
14
|
+
const protectedB64 = b64url(Buffer.from(JSON.stringify({ alg: "EdDSA" }), "utf8"));
|
|
15
|
+
const payloadB64 = b64url(Buffer.from(JSON.stringify(payloadObj), "utf8"));
|
|
16
|
+
const signingInput = `${protectedB64}.${payloadB64}`;
|
|
17
|
+
return `${signingInput}.${b64url(crypto.sign(null, Buffer.from(signingInput, "utf8"), privateKey))}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function post(baseUrl, path, body) {
|
|
21
|
+
const res = await fetch(`${baseUrl}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
22
|
+
let json = null; try { json = await res.json(); } catch (_) { json = null; }
|
|
23
|
+
return { status: res.status, json };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const GATED = [
|
|
27
|
+
["/v1/proof-center/import-adapter/verify-signature", "proof.verify-signature"],
|
|
28
|
+
["/v1/proof-bundles/verify", "proof.verify-bundle"],
|
|
29
|
+
["/v1/proofs/verify", "proof.verify-envelope"],
|
|
30
|
+
["/validate", "proof.verify-receipt"],
|
|
31
|
+
["/v1/receipts/verify", "proof.verify-receipt"]
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
const prevFlag = process.env.XOONYA_REQUIRE_PAID_VERIFICATION;
|
|
36
|
+
const prevBearer = process.env.XOONYA_EXECUTOR_BEARER_TOKEN;
|
|
37
|
+
delete process.env.XOONYA_REQUIRE_PAID_VERIFICATION;
|
|
38
|
+
process.env.XOONYA_EXECUTOR_BEARER_TOKEN = "paywall-test-bearer";
|
|
39
|
+
|
|
40
|
+
delete require.cache[require.resolve("../server.js")];
|
|
41
|
+
const { createServer } = require("../server.js");
|
|
42
|
+
const server = createServer();
|
|
43
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
44
|
+
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
45
|
+
|
|
46
|
+
const ed = crypto.generateKeyPairSync("ed25519");
|
|
47
|
+
const edPubPem = ed.publicKey.export({ type: "spki", format: "pem" });
|
|
48
|
+
const jws = makeJws({ sub: "a" }, ed.privateKey);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Flag OFF (default): free direct verification works unchanged (non-breaking).
|
|
52
|
+
let r = await post(baseUrl, "/v1/proof-center/import-adapter/verify-signature", { signature_verification: { jws, trusted_public_key: edPubPem } });
|
|
53
|
+
assert.strictEqual(r.status, 200, "flag off: verify-signature free (200)");
|
|
54
|
+
assert.strictEqual(r.json.signature_valid, true, "flag off: real verdict returned free");
|
|
55
|
+
|
|
56
|
+
// Flag ON: every gated route returns 402 pointing to the paid lane.
|
|
57
|
+
process.env.XOONYA_REQUIRE_PAID_VERIFICATION = "true";
|
|
58
|
+
for (const [path, tool] of GATED) {
|
|
59
|
+
r = await post(baseUrl, path, {});
|
|
60
|
+
assert.strictEqual(r.status, 402, `flag on: ${path} must 402`);
|
|
61
|
+
assert.strictEqual(r.json.reason, "direct_verification_paywalled", `flag on: ${path} paywall reason`);
|
|
62
|
+
assert.ok(r.json.paid_lane && r.json.paid_lane.task_ref === "adapter.xoonya.invoke", `flag on: ${path} points to paid lane`);
|
|
63
|
+
assert.strictEqual(r.json.paid_lane.tool_name, tool, `flag on: ${path} names the paid tool`);
|
|
64
|
+
}
|
|
65
|
+
// And the paywalled route no longer performs the free verification (402, not 200).
|
|
66
|
+
r = await post(baseUrl, "/v1/proof-center/import-adapter/verify-signature", { signature_verification: { jws, trusted_public_key: edPubPem } });
|
|
67
|
+
assert.strictEqual(r.status, 402, "flag on: a valid request is still paywalled (no free bypass)");
|
|
68
|
+
|
|
69
|
+
// Flag ON must NOT paywall /execute (the paid backend) or /health (free).
|
|
70
|
+
const exec = await fetch(`${baseUrl}/execute`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
71
|
+
assert.notStrictEqual(exec.status, 402, "/execute must not be paywalled (it IS the paid path; 401 without bearer here)");
|
|
72
|
+
const health = await fetch(`${baseUrl}/health`);
|
|
73
|
+
assert.strictEqual(health.status, 200, "/health stays free");
|
|
74
|
+
|
|
75
|
+
console.log("xoonya verify:paywall-flag passed");
|
|
76
|
+
} finally {
|
|
77
|
+
await new Promise((resolve) => server.close(resolve));
|
|
78
|
+
if (prevFlag === undefined) delete process.env.XOONYA_REQUIRE_PAID_VERIFICATION; else process.env.XOONYA_REQUIRE_PAID_VERIFICATION = prevFlag;
|
|
79
|
+
if (prevBearer === undefined) delete process.env.XOONYA_EXECUTOR_BEARER_TOKEN; else process.env.XOONYA_EXECUTOR_BEARER_TOKEN = prevBearer;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch((error) => {
|
|
84
|
+
console.error(error && error.message ? error.message : error);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
});
|