proof-aware-crypto-tooling-.../src/pacta/custodycard.py
mrwulf 60f0c09bc9 warden agent-native surfaces: MCP server, self-proving custody card, proof-of-posture
- walletmcp.py: stdlib stdio JSON-RPC MCP server; 7 outcome-first tools
  with strict schemas; errors are structured (code/missing/remediation),
  never prose; results carry evidence (structuredContent)
- custodycard.py: A2A-style card sharpened to self-proving - embeds each
  member's LTL inclusion proof + STH so a counterparty recomputes trust
  instead of believing it; proof-of-posture nonce challenge returns a
  firewalled, signed posture attestation with the full quorum trail
- cli.py:
- verified end-to-end on the live wallet: all 7 MCP tools, structured
  refusal on bad input, and a counterparty recomputing all 4 inclusion
  proofs + STH signatures from the card alone (no operator trust)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 23:35:47 +02:00

196 lines
7.8 KiB
Python

"""The custody card and the proof-of-posture challenge.
A2A publishes agent cards at ``/.well-known/agent-card.json`` and (since
v1.0) signs them. warden's card goes one step further: it is
**self-proving**. Alongside the description sits, for every quorum
member, the transparency-log inclusion proof binding the member's
attested source to a signed tree head. A counterparty does not have to
believe the card's adjectives - it can recompute the Merkle roots with
~40 lines of stdlib code and check the STH signature against the log key
it already pins.
The proof-of-posture (PoP) challenge is the live counterpart: send a
nonce, get back a signed posture attestation (capsule hash, ledger head,
latch state, incident count) whose signature just passed the wallet's
own outbound firewall - with the quorum trail attached. A heartbeat you
can audit instead of trust.
"""
from __future__ import annotations
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from .wallet import Refusal, Wallet
CARD_TYPE = "pacta.wallet.custody_card.v1"
POP_TYPE = "pacta.wallet.posture_attestation.v1"
WELL_KNOWN_PATH = ".well-known/custody-card.json"
def _now() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _canonical(document: Any) -> bytes:
return json.dumps(document, sort_keys=True, separators=(",", ":")).encode("utf-8")
def build_custody_card(wallet: Wallet, log_url: str = "https://ltl.zkdefi.org") -> dict[str, Any]:
"""The self-proving business card. Everything a counterparty agent needs
to evaluate this wallet is embedded or Merkle-bound; nothing requires
believing the operator."""
capsule = wallet.capsule()
posture = wallet.posture()
identities = sorted(p.name.removesuffix(".pub.pem") for p in wallet.keys_dir.glob("*.pub.pem"))
return {
"type": CARD_TYPE,
"name": "warden",
"description": (
"Verified-custody Ed25519 wallet. Inbound acceptance requires unanimity "
"of quorum members compiled from sources whose correctness certificates "
"are machine-checked in Lean 4 and replay-attested in a public RFC 9162 "
"transparency log. Outbound signatures pass the same quorum as a firewall "
"before release."
),
"protocol": {
"mcp": {
"transport": "stdio",
"command": "pacta wallet mcp --wallet <dir>",
"tools": [
"wallet_status",
"verify_inbound",
"request_signature",
"custody_card",
"posture_challenge",
"list_incidents",
"explain_refusal",
],
},
"refusal_codes": [
"EVIDENCE_REQUIRED", "POLICY_DENIED", "CUSTODY_LATCHED",
"EVIDENCE_STALE", "MALFORMED_INTENT", "SIGNER_UNAVAILABLE",
"FIREWALL_QUARANTINE", "PENDING_AIRGAP",
],
},
"identities": identities,
"quorum": {
"policy": capsule["policy"],
"members": [
{
"backend": m["backend"],
"component": m["component"],
"semantics": m["semantics"],
"entry_point": m["entry_point"],
"source_commit": m["source_commit"],
"binary_sha256": m["binary_sha256"],
"risk_tier": m["risk_tier"],
# The self-proving part: recompute, don't believe.
"transparency_evidence": m["evidence"],
}
for m in capsule["members"]
],
},
"signing": capsule["signing"],
"state": {
"latch": posture["latch"],
"ledger_head": posture["ledger"]["head"],
"ledger_entries": posture["ledger"]["entries"],
"incidents": posture["incidents"],
},
"evidence_endpoints": {
"log": log_url,
"log_docs": f"{log_url}/docs",
"verify_hint": (
"for each member: leaf_hash = SHA256(0x00 || canonical attestation leaf); "
"walk inclusion_proof to sth.root_hash; verify sth signature against the "
"log public key you pin. Stdlib only; see the log's verify.py."
),
},
"honesty": [
"verification path certificate-covered; signing path trusted base (attested artifact)",
"SHA-512 enters the theorems as an opaque oracle",
"wire parser outcomes are hypotheses; side channels and reproducible builds are R5, not claimed",
"ML-DSA (PQC) slot fail-closed: no proven implementation exists",
],
"generated_at": _now(),
}
def write_well_known(wallet: Wallet, out_dir: str | Path, log_url: str = "https://ltl.zkdefi.org") -> Path:
out = Path(out_dir) / WELL_KNOWN_PATH
out.parent.mkdir(parents=True, exist_ok=True)
card = build_custody_card(wallet, log_url)
out.write_text(json.dumps(card, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return out
def posture_challenge(
wallet: Wallet,
nonce: str,
key_name: str = "warden",
) -> dict[str, Any] | Refusal:
"""Answer a counterparty's nonce with a firewalled, signed posture.
The signature on the attestation is produced by the wallet's own
outbound path, so it carries the full quorum trail - the counterparty
sees not only *that* the wallet signed, but that four provably
-equivalent verifiers unanimously accepted the signature before it
left the building. If custody is latched, the challenge honestly
fails with a CUSTODY_LATCHED refusal instead of a heartbeat.
"""
if not isinstance(nonce, str) or not (8 <= len(nonce) <= 128):
raise ValueError("nonce must be a string of 8..128 characters")
posture = wallet.posture()
body = {
"type": POP_TYPE,
"nonce": nonce,
"posture": posture,
}
payload = _canonical(body)
intent = {
"purpose": f"proof-of-posture challenge response (nonce {nonce[:16]}...)",
"payload_sha256": hashlib.sha256(payload).hexdigest(),
}
result = wallet.request_signature(intent, payload, key_name=key_name)
if isinstance(result, Refusal):
return result
return {
"type": POP_TYPE,
"nonce": nonce,
"posture": posture,
"payload_hex": payload.hex(),
"signature_hex": result["signature_hex"],
"public_key_hex": result["public_key_hex"],
"identity": result["identity"],
"firewall": result["firewall"],
"issued_at": result["issued_at"],
}
def verify_posture_attestation(attestation: dict[str, Any], expected_nonce: str) -> tuple[bool, list[str]]:
"""Counterparty-side check, stdlib only: nonce echo, payload binding.
Signature verification is left to the counterparty's own verifier
(ideally its own quorum); this helper checks the structure so a
counterparty without crypto can still catch replay and splicing.
"""
problems: list[str] = []
if attestation.get("type") != POP_TYPE:
problems.append(f"unexpected type {attestation.get('type')}")
if attestation.get("nonce") != expected_nonce:
problems.append("nonce mismatch (replay?)")
try:
payload = bytes.fromhex(str(attestation.get("payload_hex", "")))
body = json.loads(payload)
except ValueError:
problems.append("payload_hex is not hex/JSON")
return False, problems
if body.get("nonce") != expected_nonce:
problems.append("signed payload does not bind the nonce")
if body.get("posture") != attestation.get("posture"):
problems.append("posture shown does not match posture signed")
return (not problems), problems