mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-04 20:03:40 +00:00
194 lines
7.9 KiB
Python
194 lines
7.9 KiB
Python
|
|
"""The agent-native MCP surface: JSON-RPC plumbing + structured errors.
|
||
|
|
|
||
|
|
Protocol plumbing and read-only tools are tested with a hand-sealed
|
||
|
|
wallet (no live quorum needed). The crypto tools (verify_inbound,
|
||
|
|
request_signature, posture_challenge) are covered by the live end-to-end
|
||
|
|
test when the real quorum + dogfood signer are present.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import base64
|
||
|
|
import hashlib
|
||
|
|
import json
|
||
|
|
import stat
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from pacta.quorum import binary_path
|
||
|
|
from pacta.signing import generate_ed25519_keypair
|
||
|
|
from pacta.wallet import Wallet
|
||
|
|
from pacta.walletmcp import TOOLS, WalletMCP
|
||
|
|
|
||
|
|
|
||
|
|
def _sha256(data: bytes) -> str:
|
||
|
|
return hashlib.sha256(data).hexdigest()
|
||
|
|
|
||
|
|
|
||
|
|
def _fake_member(path: Path, verdict: str) -> None:
|
||
|
|
code = {"accept": 0, "reject": 1}[verdict]
|
||
|
|
out = {"accept": "OK", "reject": "INVALID"}[verdict]
|
||
|
|
path.write_text(f"#!/bin/sh\necho {out}\nexit {code}\n")
|
||
|
|
path.chmod(path.stat().st_mode | stat.S_IEXEC)
|
||
|
|
|
||
|
|
|
||
|
|
def _seal_wallet(tmp_path: Path, verdicts: dict, state_dir: Path) -> Wallet:
|
||
|
|
state_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
members = []
|
||
|
|
for name, verdict in verdicts.items():
|
||
|
|
binary = binary_path(name, state_dir)
|
||
|
|
_fake_member(binary, verdict)
|
||
|
|
members.append({
|
||
|
|
"backend": name, "component": f"{name}-ed25519-verified", "semantics": "test",
|
||
|
|
"entry_point": "test", "source_commit": "de" * 20, "repo_commit": "ca" * 20,
|
||
|
|
"binary_sha256": _sha256(binary.read_bytes()), "backend_cfg": "test", "risk_tier": "R4",
|
||
|
|
"evidence": {"leaf_hash": "00", "leaf_index": 0, "tree_size": 1, "inclusion_proof": [],
|
||
|
|
"sth": {"timestamp": "2099-01-01T00:00:00Z"}},
|
||
|
|
})
|
||
|
|
wallet = Wallet(tmp_path / "w")
|
||
|
|
for sub in (wallet.keys_dir, wallet.incidents_dir, wallet.receipts_dir,
|
||
|
|
wallet.quarantine_dir, wallet.airgap_dir / "outbox", wallet.airgap_dir / "inbox"):
|
||
|
|
sub.mkdir(parents=True, exist_ok=True)
|
||
|
|
capsule = {"type": "pacta.wallet.custody_capsule.v1", "created_at": "2026-07-06T00:00:00Z",
|
||
|
|
"members": members,
|
||
|
|
"policy": {"require_unanimity": True, "min_members": 2, "require_tier": "R4",
|
||
|
|
"freshness_max_age_days": 0},
|
||
|
|
"signing": {"backend": "test"}, "problems_at_init": []}
|
||
|
|
wallet.capsule_path.write_text(json.dumps(capsule, indent=2, sort_keys=True) + "\n")
|
||
|
|
wallet._append_ledger("genesis", {"capsule_sha256": "x"})
|
||
|
|
generate_ed25519_keypair(wallet.keys_dir / "warden.key.pem", wallet.keys_dir / "warden.pub.pem")
|
||
|
|
wallet._test_state_dir = state_dir # type: ignore[attr-defined]
|
||
|
|
return wallet
|
||
|
|
|
||
|
|
|
||
|
|
def _mcp(tmp_path):
|
||
|
|
wallet = _seal_wallet(tmp_path, {"a": "accept", "b": "accept"}, tmp_path / "state")
|
||
|
|
srv = WalletMCP(wallet.dir)
|
||
|
|
return srv, wallet
|
||
|
|
|
||
|
|
|
||
|
|
def _call(srv, method, params=None, msg_id=1):
|
||
|
|
return srv.handle({"jsonrpc": "2.0", "id": msg_id, "method": method, "params": params or {}})
|
||
|
|
|
||
|
|
|
||
|
|
def test_initialize_advertises_server(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
res = _call(srv, "initialize")["result"]
|
||
|
|
assert res["serverInfo"]["name"] == "warden"
|
||
|
|
assert "protocolVersion" in res
|
||
|
|
|
||
|
|
|
||
|
|
def test_initialized_notification_has_no_response(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
assert srv.handle({"jsonrpc": "2.0", "method": "notifications/initialized"}) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_tools_list_matches_declared(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
names = {t["name"] for t in _call(srv, "tools/list")["result"]["tools"]}
|
||
|
|
assert names == {t["name"] for t in TOOLS}
|
||
|
|
assert "request_signature" in names
|
||
|
|
|
||
|
|
|
||
|
|
def test_every_tool_has_a_strict_schema():
|
||
|
|
for tool in TOOLS:
|
||
|
|
schema = tool["inputSchema"]
|
||
|
|
assert schema["type"] == "object"
|
||
|
|
assert schema.get("additionalProperties") is False, tool["name"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_unknown_tool_is_jsonrpc_error(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
res = _call(srv, "tools/call", {"name": "nope", "arguments": {}})
|
||
|
|
assert res["error"]["code"] == -32602
|
||
|
|
|
||
|
|
|
||
|
|
def test_wallet_status_tool(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
res = _call(srv, "tools/call", {"name": "wallet_status"})["result"]
|
||
|
|
assert not res["isError"]
|
||
|
|
assert res["structuredContent"]["type"] == "pacta.wallet.posture.v1"
|
||
|
|
|
||
|
|
|
||
|
|
def test_custody_card_is_self_proving_shape(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
card = _call(srv, "tools/call", {"name": "custody_card"})["result"]["structuredContent"]
|
||
|
|
assert card["type"] == "pacta.wallet.custody_card.v1"
|
||
|
|
for member in card["quorum"]["members"]:
|
||
|
|
assert "transparency_evidence" in member
|
||
|
|
|
||
|
|
|
||
|
|
def test_structured_error_on_bad_base64(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
res = _call(srv, "tools/call", {
|
||
|
|
"name": "verify_inbound",
|
||
|
|
"arguments": {"payload_b64": "x", "signature_b64": "!!!", "public_key_b64": "!!!"},
|
||
|
|
})["result"]
|
||
|
|
assert res["isError"]
|
||
|
|
body = res["structuredContent"]
|
||
|
|
assert set(body) >= {"code", "reason", "missing", "remediation"}
|
||
|
|
assert body["code"] == "MALFORMED_INTENT"
|
||
|
|
|
||
|
|
|
||
|
|
def test_explain_refusal_when_none(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
res = _call(srv, "tools/call", {"name": "explain_refusal"})["result"]
|
||
|
|
assert res["isError"]
|
||
|
|
assert res["structuredContent"]["code"] == "EVIDENCE_REQUIRED"
|
||
|
|
|
||
|
|
|
||
|
|
def test_server_never_crashes_on_garbage(tmp_path):
|
||
|
|
srv, _ = _mcp(tmp_path)
|
||
|
|
# missing method
|
||
|
|
res = _call(srv, "does/not/exist")
|
||
|
|
assert res["error"]["code"] == -32601
|
||
|
|
|
||
|
|
|
||
|
|
def test_request_signature_and_verify_roundtrip_live(tmp_path):
|
||
|
|
if not all(binary_path(b).exists() for b in ("dalek", "anza")):
|
||
|
|
pytest.skip("real quorum not built")
|
||
|
|
from pacta.dogfood import locate_verifier
|
||
|
|
|
||
|
|
if locate_verifier() is None:
|
||
|
|
pytest.skip("dogfood signer not built")
|
||
|
|
# seal against the REAL default-location binaries
|
||
|
|
real_hashes = {b: _sha256(binary_path(b).read_bytes()) for b in ("dalek", "anza", "risc0", "betrusted")
|
||
|
|
if binary_path(b).exists()}
|
||
|
|
wallet = Wallet(tmp_path / "w")
|
||
|
|
for sub in (wallet.keys_dir, wallet.incidents_dir, wallet.receipts_dir,
|
||
|
|
wallet.quarantine_dir, wallet.airgap_dir / "outbox", wallet.airgap_dir / "inbox"):
|
||
|
|
sub.mkdir(parents=True, exist_ok=True)
|
||
|
|
members = [{
|
||
|
|
"backend": b, "component": f"{b}-ed25519-verified", "semantics": "t", "entry_point": "t",
|
||
|
|
"source_commit": "x", "repo_commit": "y", "binary_sha256": h, "backend_cfg": "t",
|
||
|
|
"risk_tier": "R4",
|
||
|
|
"evidence": {"leaf_hash": "00", "leaf_index": 0, "tree_size": 1, "inclusion_proof": [],
|
||
|
|
"sth": {"timestamp": "2099-01-01T00:00:00Z"}},
|
||
|
|
} for b, h in real_hashes.items()]
|
||
|
|
capsule = {"type": "pacta.wallet.custody_capsule.v1", "created_at": "2026-07-06T00:00:00Z",
|
||
|
|
"members": members,
|
||
|
|
"policy": {"require_unanimity": True, "min_members": 2, "require_tier": "R4",
|
||
|
|
"freshness_max_age_days": 0},
|
||
|
|
"signing": {"backend": "test"}, "problems_at_init": []}
|
||
|
|
wallet.capsule_path.write_text(json.dumps(capsule, indent=2, sort_keys=True) + "\n")
|
||
|
|
wallet._append_ledger("genesis", {"capsule_sha256": "x"})
|
||
|
|
generate_ed25519_keypair(wallet.keys_dir / "warden.key.pem", wallet.keys_dir / "warden.pub.pem")
|
||
|
|
|
||
|
|
srv = WalletMCP(wallet.dir)
|
||
|
|
payload = b"live mcp roundtrip"
|
||
|
|
res = _call(srv, "tools/call", {
|
||
|
|
"name": "request_signature",
|
||
|
|
"arguments": {"payload_b64": base64.b64encode(payload).decode(), "purpose": "test"},
|
||
|
|
})["result"]
|
||
|
|
assert not res["isError"], res["structuredContent"]
|
||
|
|
sig = bytes.fromhex(res["structuredContent"]["signature_hex"])
|
||
|
|
pub = bytes.fromhex(res["structuredContent"]["public_key_hex"])
|
||
|
|
vi = _call(srv, "tools/call", {
|
||
|
|
"name": "verify_inbound",
|
||
|
|
"arguments": {
|
||
|
|
"payload_b64": base64.b64encode(payload).decode(),
|
||
|
|
"signature_b64": base64.b64encode(sig).decode(),
|
||
|
|
"public_key_b64": base64.b64encode(pub).decode(),
|
||
|
|
},
|
||
|
|
})["result"]
|
||
|
|
assert vi["structuredContent"]["classification"] == "unanimous-accept"
|