Secure multi‑agent demo showcasing:
- RFC 9421 HTTP Message Signatures on outbound requests (A2A signer)
- DID resolution against an Ethereum Registry (on‑chain public keys)
- HPKE session bootstrap + payload encryption between Payment → External
- A tampering Gateway that demonstrates signature failure and HPKE integrity
This repo wires together in‑proc agents behind a Root, an External Payment service (with DID auth), a Gateway proxy (optional tamper), and a simple Client API.
- Root Agent
cmd/root/main.go(HTTP, default:18080) - In‑proc sub‑agents: Planning, MEDICAL, Payment (
agents/*) - External Payment server
cmd/payment/main.go(HTTP, default:19083) - Gateway reverse proxy
cmd/gateway/main.go(HTTP, default:5500) - Client API
cmd/client/main.go(HTTP, default:8086)
Libraries used:
- A2A signer/verifier:
github.com/sage-x-project/sage-a2a-go - Core crypto/DID/HPKE/session:
github.com/sage-x-project/sage
Notes on go.mod: this repo uses local replace directives to sibling checkouts of sage and sage-a2a-go. Adjust or remove these lines if you are not developing with local copies.
- Root:
18080 - Client API:
8086 - Gateway:
5500 - External Payment:
19083 - External Medical:
19082
All of these can be overridden via .env or flags (see scripts below).
- Go 1.24+ (toolchain declared in go.mod)
- An Ethereum dev node (Hardhat/Anvil) or any RPC with the SAGE Registry V4 deployed
- Basic tooling:
curl,jq(optional),cast(optional) for funding
Environment used by servers and middleware (with working defaults):
ETH_RPC_URL(defaulthttp://127.0.0.1:8545)SAGE_REGISTRY_ADDRESS(default0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512)SAGE_EXTERNAL_KEY(optional; hex without 0x; used for tx signing if needed)PAYMENT_JWK_FILE(path to secp256k1 JWK for Payment outbound signing)PAYMENT_JWK_FILEandPAYMENT_KEM_JWK_FILEfor the External Payment server
Demo keys are provided under keys/ and generated_agent_keys.json for convenience.
- Register agents (only once)
sh ./scripts/00_register_agents.sh \
--kem --merge \
--signing-keys ./generated_agent_keys.json \
--kem-keys ./keys/kem/generated_kem_keys.json \
--combined-out ./merged_agent_keys.json \
--agents "payment,planing,medical" \
--wait-seconds 60 \
--funding-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--try-activate- Merges signing+KEM keys, commits→registers, and tries activation after the delay.
- The
--funding-keyshown is the default Hardhat/Anvil dev key; replace if needed.
- Launch services (Gateway tamper by default)
./scripts/06_start_all.sh --tamper # tamper mode (default)
./scripts/06_start_all.sh --pass # pass-through (no tampering)--pass: Gateway forwards requests unchanged.- No flag: Gateway injects a small tamper string into JSON (or flips ciphertext when HPKE is on).
- Send a message
Using helper script (recommended):
# Single turn (signed)
./scripts/07_send_prompt.sh --sage on --prompt "iPhone 15 프로를 쿠팡에서 구매해서 서울 특별시 00길로 배송해줘"
# With HPKE (requires SAGE on)
./scripts/07_send_prompt.sh --sage on --hpke on --prompt "당뇨병 진단을 받았어. 혈당은 180이야. 식단 관리 방법 알려줘"
# Interactive multi-turn
./scripts/07_send_prompt.sh --sage on -iOr via curl to the Client API:
curl -sS -X POST http://localhost:8086/api/request \
-H 'Content-Type: application/json' \
-H 'X-SAGE-Enabled: true' \
--data-binary '{"prompt":"send 5 usdc to bob"}' | jqEndpoint
POST http://localhost:8086/api/request
Headers
Content-Type: application/json(required)X-SAGE-Enabled: true|false— enable/disable A2A signing (required for HPKE)X-HPKE-Enabled: true|false— request HPKE (requires SAGE=true)X-Conversation-IDorX-SAGE-Context-ID— optional; keeps conversation state across turnsX-Scenario: <name>— optional, for logging and demos
Body
{ "prompt": "..." }(optionally includeconversationIdtoo)
Rules
- If
X-HPKE-Enabled: truewhileX-SAGE-Enabled: false, the API returns400 Bad Request. - When HPKE is ON, the first request may perform a session handshake; subsequent requests carry ciphertext.
Examples
# SAGE ON (signed), HPKE OFF
curl -sS -X POST http://localhost:8086/api/request \
-H 'Content-Type: application/json' \
-H 'X-SAGE-Enabled: true' \
--data-binary '{"prompt":"buy an iPhone 15"}' | jq
# SAGE ON + HPKE ON
curl -sS -X POST http://localhost:8086/api/request \
-H 'Content-Type: application/json' \
-H 'X-SAGE-Enabled: true' \
-H 'X-HPKE-Enabled: true' \
--data-binary '{"prompt":"send 10 USDC to merchant"}' | jq
# Multi-turn with a fixed conversation id
CID="demo-$(date +%s)"
curl -sS -X POST http://localhost:8086/api/request \
-H 'Content-Type: application/json' \
-H "X-SAGE-Enabled: true" \
-H "X-Conversation-ID: $CID" -H "X-SAGE-Context-ID: $CID" \
--data-binary '{"prompt":"buy an iPhone 15"}' | jq
curl -sS -X POST http://localhost:8086/api/request \
-H 'Content-Type: application/json' \
-H "X-SAGE-Enabled: true" \
-H "X-Conversation-ID: $CID" -H "X-SAGE-Context-ID: $CID" \
--data-binary '{"prompt":"ship to Seoul, card, budget 1,500,000 KRW"}' | jqawait fetch("http://localhost:8086/api/request", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-SAGE-Enabled": "true",
// "X-HPKE-Enabled": "true", // optional; requires SAGE=true
// "X-Conversation-ID": "demo-123", // optional conversation id
},
body: JSON.stringify({ prompt: "send 10 USDC" }),
});- SAGE ON + Gateway Tamper: External Payment rejects mutated bodies (4xx) because DID middleware verifies RFC 9421 over the exact bytes. You should see an error bubble back to Root/Client.
- SAGE OFF + Gateway Tamper: Mutations pass through; you will see modified content reach External.
- HPKE ON: Payment encrypts payloads to External. The Gateway’s ciphertext bit‑flip breaks decryption; External returns an HPKE decrypt error. Plain responses are re‑encrypted back to the client.
- Root routing and health:
agents/root/agent.go - Client API facade:
api/api.go,cmd/client/main.go - A2A transport used by Payment:
protocol/a2a_transport.go - DID middleware wrapper:
internal/a2autil/middleware.go - Gateway reverse proxy (tamper):
gateway/gateway.go,cmd/gateway/main.go - External Payment (handshake + data mode):
cmd/payment/main.go - Payment HPKE client wiring:
agents/payment/hpke_wrap.go
- Check logs under
logs/*.log(launcher scripts write there) - Verify middleware env:
ETH_RPC_URL,SAGE_REGISTRY_ADDRESS - Kill stuck ports:
scripts/01_kill_ports.sh --force - Ensure keys exist:
keys/*.jwk,keys/kem/*.jwk,generated_agent_keys.json - If developing without local
sagerepos, remove/adjustreplacelines ingo.modand rungo mod tidy
- Demo keys are for local development only. Do not reuse in production.
- The Gateway demonstrates attacks; never deploy it in front of real systems.
- HPKE session/nonce/replay protection is handled by
sagesession manager. Keep processes single‑instance for predictable demos.