@7h3/protocol 0.1.2 → 0.5.0

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.
Files changed (276) hide show
  1. package/.dockerignore +19 -0
  2. package/.github/dependabot.yml +32 -0
  3. package/.github/workflows/ci.yml +31 -0
  4. package/.github/workflows/publish.yml +59 -0
  5. package/.github/workflows/scorecard.yml +37 -0
  6. package/7h3.example.yaml +125 -0
  7. package/CHANGELOG.md +152 -0
  8. package/CONTRIBUTING.md +82 -0
  9. package/Dockerfile +73 -0
  10. package/GOVERNANCE.md +62 -0
  11. package/README.md +1317 -6
  12. package/SECURITY.md +70 -0
  13. package/bench-results/replay-cache-full-1777891033256.json +10 -0
  14. package/bench-results/replay-cache-full-1777896317488.json +10 -0
  15. package/bench-results/replay-cache-full-1777900993184.json +10 -0
  16. package/bench-results/replay-cache-full-1777901019285.json +10 -0
  17. package/bench-results/replay-cache-quick-1777870170126.json +10 -0
  18. package/bench-results/signature-profiles-quick-1775875160079.json +85 -0
  19. package/bench-results/signature-profiles-quick-1775983539716.json +85 -0
  20. package/bench-results/signature-profiles-quick-1776237913190.json +85 -0
  21. package/bench-results/wire-codecs-full-1777891019803.json +93 -0
  22. package/bench-results/wire-codecs-full-1777896260964.json +93 -0
  23. package/bench-results/wire-codecs-full-1777901004247.json +93 -0
  24. package/bench-results/wire-codecs-quick-1775972879056.json +93 -0
  25. package/bench-results/wire-codecs-quick-1775983541111.json +93 -0
  26. package/bench-results/wire-codecs-quick-1776237914299.json +93 -0
  27. package/bench-results/wire-codecs-quick-1777841285236.json +93 -0
  28. package/bench-results/wire-codecs-quick-1777841321772.json +93 -0
  29. package/bench-results/wire-codecs-quick-1777841330408.json +93 -0
  30. package/bench-results/wire-codecs-quick-1777852886082.json +93 -0
  31. package/bench-results/wire-codecs-quick-1777852988773.json +93 -0
  32. package/bench-results/wire-codecs-quick-1777870188095.json +93 -0
  33. package/bench-results/wire-codecs-quick-1777870263918.json +93 -0
  34. package/bench-results/wire-codecs-quick-1777870455034.json +93 -0
  35. package/bench-results/wire-codecs-quick-1778816163081.json +93 -0
  36. package/bench-results/wire-codecs-quick-1778843936130.json +93 -0
  37. package/bin/7h3.ts +406 -0
  38. package/conformance/7h3_v0_1.json +77 -0
  39. package/conformance/7h3_v0_1_binary.json +20 -0
  40. package/conformance/aip_v0_1_binary.json +20 -0
  41. package/docker-compose.yaml +77 -0
  42. package/docs/ADOPTION_PLAN.md +120 -0
  43. package/docs/AGENTS.md +77 -0
  44. package/docs/AIP_RFC_v0.1.md +97 -0
  45. package/docs/AI_DECISION_CARD.md +122 -0
  46. package/docs/AI_RUNTIME_POLICY.json +126 -0
  47. package/docs/AI_RUNTIME_POLICY.yaml +110 -0
  48. package/docs/BACKPRESSURE_TUNING.md +65 -0
  49. package/docs/BENCHMARK_CLAIM_MATRIX.md +42 -0
  50. package/docs/BENCHMARK_REPORT_TEMPLATE.md +169 -0
  51. package/docs/BINARY_CODEC_BENCH.md +23 -0
  52. package/docs/CLEAN_CLONE_RUNBOOK.md +36 -0
  53. package/docs/CLOCK_SKEW_POLICY.md +30 -0
  54. package/docs/DISTRIBUTED_REPLAY.md +142 -0
  55. package/docs/FUZZ_CAMPAIGN.md +121 -0
  56. package/docs/GATEWAY.md +195 -0
  57. package/docs/KEY_MANAGEMENT_POLICY.md +53 -0
  58. package/docs/KEY_REVOCATION.md +69 -0
  59. package/docs/MCP_WRAPPER.md +159 -0
  60. package/docs/MIGRATION_GUIDE.md +40 -0
  61. package/docs/OPERATORS.md +184 -0
  62. package/docs/PERF_REGRESSION_POLICY.md +34 -0
  63. package/docs/PROJECT_EXAMINATION_2026-05-31.md +219 -0
  64. package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +135 -0
  65. package/docs/RELEASE_GATE.md +25 -0
  66. package/docs/RELEASE_NOTES_v0.1.0.md +54 -0
  67. package/docs/SECURITY_REVIEW_2026-06-05.md +165 -0
  68. package/docs/TELEMETRY.md +41 -0
  69. package/docs/THREAT_MODEL.md +89 -0
  70. package/docs/VERSIONING_POLICY.md +30 -0
  71. package/docs/assets/banner-github.png +0 -0
  72. package/docs/assets/banner.png +0 -0
  73. package/docs/assets/banner.svg +123 -0
  74. package/eslint.config.js +15 -0
  75. package/fuzz/ts/harness-decode.ts +136 -0
  76. package/fuzz/ts/harness-verify.ts +121 -0
  77. package/fuzz/ts/run.ts +35 -0
  78. package/mcp-server/README.md +38 -0
  79. package/mcp-server/package-lock.json +1187 -0
  80. package/mcp-server/package.json +35 -0
  81. package/mcp-server/src/index.ts +236 -0
  82. package/mcp-server/tsconfig.json +14 -0
  83. package/package.json +121 -13
  84. package/scripts/aip-framework-quickstart.ts +110 -0
  85. package/scripts/aip-mcp-gateway.ts +38 -0
  86. package/scripts/aip-mcp-wrap-demo.ts +72 -0
  87. package/scripts/aip-quickstart.ts +60 -0
  88. package/scripts/bench-diff.ts +118 -0
  89. package/scripts/bench-protocol-e2e.ts +937 -0
  90. package/scripts/bench-protocol-openloop.ts +1397 -0
  91. package/scripts/bench-replay-cache.ts +76 -0
  92. package/scripts/bench-signature-profiles.ts +180 -0
  93. package/scripts/bench-wire-codecs.ts +161 -0
  94. package/scripts/build-binary-conformance.ts +36 -0
  95. package/scripts/build-release-dashboard.ts +175 -0
  96. package/scripts/canary-rollout.ts +38 -0
  97. package/scripts/mcpGatewayCli.test.ts +116 -0
  98. package/scripts/prepare-aip-package.ts +88 -0
  99. package/scripts/regen-conformance-sigs.ts +18 -0
  100. package/scripts/release-gate.ts +19 -0
  101. package/scripts/validate-runtime-policy.ts +18 -0
  102. package/sdk/browser/index.test.ts +162 -0
  103. package/sdk/browser/index.ts +257 -0
  104. package/sdk/browser/package.json +13 -0
  105. package/sdk/go/cbor.go +551 -0
  106. package/sdk/go/cbor_test.go +232 -0
  107. package/sdk/go/encryption.go +280 -0
  108. package/sdk/go/encryption_test.go +318 -0
  109. package/sdk/go/go.mod +7 -0
  110. package/sdk/go/go.sum +4 -0
  111. package/sdk/go/http.go +135 -0
  112. package/sdk/go/protocol.go +324 -0
  113. package/sdk/go/protocol_test.go +334 -0
  114. package/sdk/go/replay.go +121 -0
  115. package/sdk/go/replay_test.go +149 -0
  116. package/sdk/go/webhook.go +136 -0
  117. package/sdk/pq/package-lock.json +1358 -0
  118. package/sdk/pq/package.json +42 -0
  119. package/sdk/pq/src/index.test.ts +143 -0
  120. package/sdk/pq/src/index.ts +166 -0
  121. package/sdk/pq/tsconfig.json +14 -0
  122. package/sdk/pq/vitest.config.ts +7 -0
  123. package/sdk/python/README.md +18 -0
  124. package/sdk/python/protocol_7h3/__init__.py +46 -0
  125. package/sdk/python/protocol_7h3/encryption.py +252 -0
  126. package/sdk/python/protocol_7h3/http.py +212 -0
  127. package/sdk/python/protocol_7h3/keys.py +149 -0
  128. package/sdk/python/protocol_7h3/pq.py +244 -0
  129. package/sdk/python/protocol_7h3/protocol.py +525 -0
  130. package/sdk/python/protocol_7h3/queue.py +118 -0
  131. package/sdk/python/protocol_7h3/replay.py +98 -0
  132. package/sdk/python/protocol_7h3/webhook.py +116 -0
  133. package/sdk/python/pyproject.toml +40 -0
  134. package/sdk/python/tests/test_conformance.py +110 -0
  135. package/sdk/python/tests/test_encryption.py +206 -0
  136. package/sdk/python/tests/test_http.py +305 -0
  137. package/sdk/python/tests/test_keys.py +417 -0
  138. package/sdk/python/tests/test_queue.py +120 -0
  139. package/sdk/python/tests/test_webhook.py +345 -0
  140. package/sdk/rust/Cargo.lock +371 -0
  141. package/sdk/rust/Cargo.toml +25 -0
  142. package/sdk/rust/README.md +31 -0
  143. package/sdk/rust/fuzz/Cargo.toml +29 -0
  144. package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +46 -0
  145. package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +11 -0
  146. package/sdk/rust/src/bin/aip_mcp_gateway.rs +59 -0
  147. package/sdk/rust/src/http.rs +145 -0
  148. package/sdk/rust/src/keys.rs +161 -0
  149. package/sdk/rust/src/lib.rs +688 -0
  150. package/sdk/rust/src/queue.rs +79 -0
  151. package/sdk/rust/src/webhook.rs +86 -0
  152. package/sdk/rust/tests/conformance.rs +148 -0
  153. package/sdk/rust/tests/gateway.rs +130 -0
  154. package/sdk/rust/tests/http_webhook_queue.rs +201 -0
  155. package/sdk/rust/tests/keys.rs +189 -0
  156. package/sdk/threshold/index.d.ts +68 -0
  157. package/sdk/threshold/index.d.ts.map +1 -0
  158. package/sdk/threshold/index.js +254 -0
  159. package/sdk/threshold/package-lock.json +1361 -0
  160. package/sdk/threshold/package.json +39 -0
  161. package/sdk/threshold/src/index.d.ts +68 -0
  162. package/sdk/threshold/src/index.d.ts.map +1 -0
  163. package/sdk/threshold/src/index.js +254 -0
  164. package/sdk/threshold/src/index.test.ts +238 -0
  165. package/sdk/threshold/src/index.ts +355 -0
  166. package/sdk/threshold/tsconfig.json +19 -0
  167. package/sdk/threshold/vitest.config.ts +12 -0
  168. package/src/agentAdapter.test.ts +48 -0
  169. package/src/agentAdapter.ts +56 -0
  170. package/src/auditLog.test.ts +145 -0
  171. package/src/auditLog.ts +147 -0
  172. package/src/capability.test.ts +504 -0
  173. package/src/capability.ts +380 -0
  174. package/src/cborCodec.test.ts +263 -0
  175. package/src/cborCodec.ts +339 -0
  176. package/src/conformance.test.ts +136 -0
  177. package/src/conformanceVectors.ts +99 -0
  178. package/src/encryption.test.ts +206 -0
  179. package/src/encryption.ts +245 -0
  180. package/src/envelopeCbor.ts +140 -0
  181. package/src/frameworkAdapters.test.ts +290 -0
  182. package/src/frameworkAdapters.ts +261 -0
  183. package/src/gateway.test.ts +343 -0
  184. package/src/gateway.ts +246 -0
  185. package/src/grpcBinding.test.ts +211 -0
  186. package/src/grpcBinding.ts +103 -0
  187. package/src/httpBinding.test.ts +376 -0
  188. package/src/httpBinding.ts +189 -0
  189. package/src/index.ts +39 -0
  190. package/src/keyInfra.test.ts +278 -0
  191. package/src/keyInfra.ts +228 -0
  192. package/src/keyRegistry.ts +59 -0
  193. package/src/keyRotation.test.ts +78 -0
  194. package/src/keyRotation.ts +72 -0
  195. package/src/mcpGateway.test.ts +129 -0
  196. package/src/mcpGateway.ts +250 -0
  197. package/src/mcpTransports.test.ts +92 -0
  198. package/src/mcpTransports.ts +169 -0
  199. package/src/mcpWrapper.test.ts +179 -0
  200. package/src/mcpWrapper.ts +206 -0
  201. package/src/otel.ts +136 -0
  202. package/src/policyEnforcer.test.ts +99 -0
  203. package/src/policyEnforcer.ts +169 -0
  204. package/src/policyTelemetryFeedback.test.ts +25 -0
  205. package/src/policyTelemetryFeedback.ts +38 -0
  206. package/src/protocol.bench.ts +37 -0
  207. package/{protocol.d.ts → src/protocol.d.ts} +2 -1
  208. package/src/protocol.d.ts.map +1 -0
  209. package/src/protocol.js +294 -0
  210. package/src/protocol.test.ts +155 -0
  211. package/src/protocol.ts +414 -0
  212. package/src/protocolAgent.test.ts +105 -0
  213. package/src/protocolAgent.ts +169 -0
  214. package/src/protocolBinary.test.ts +165 -0
  215. package/src/protocolBinary.ts +312 -0
  216. package/src/protocolCapabilities.ts +70 -0
  217. package/src/protocolFuzz.advanced.test.ts +235 -0
  218. package/src/protocolFuzz.test.ts +111 -0
  219. package/src/protocolNegative.test.ts +97 -0
  220. package/src/protocolReplay.test.ts +71 -0
  221. package/src/protocolReplay.ts +194 -0
  222. package/src/protocolTransport.test.ts +556 -0
  223. package/src/protocolTransport.ts +483 -0
  224. package/src/queueBinding.test.ts +130 -0
  225. package/src/queueBinding.ts +102 -0
  226. package/src/rateLimiter.test.ts +96 -0
  227. package/src/rateLimiter.ts +46 -0
  228. package/src/redisClient.ts +140 -0
  229. package/src/redisIntegration.test.ts +134 -0
  230. package/src/replayStores.test.ts +273 -0
  231. package/src/replayStores.ts +215 -0
  232. package/src/revocation.test.ts +98 -0
  233. package/src/revocation.ts +0 -0
  234. package/src/routePolicy.test.ts +87 -0
  235. package/src/routePolicy.ts +72 -0
  236. package/src/runtimePolicy.test.ts +49 -0
  237. package/src/runtimePolicy.ts +81 -0
  238. package/src/runtimePolicyManager.test.ts +29 -0
  239. package/src/runtimePolicyManager.ts +50 -0
  240. package/src/runtimePolicyPresets.ts +43 -0
  241. package/src/signedResponse.test.ts +111 -0
  242. package/src/signedResponse.ts +83 -0
  243. package/src/stream.test.ts +254 -0
  244. package/src/stream.ts +417 -0
  245. package/src/telemetry.test.ts +251 -0
  246. package/src/telemetry.ts +299 -0
  247. package/src/webhookBinding.test.ts +144 -0
  248. package/src/webhookBinding.ts +115 -0
  249. package/src/wsBinding.test.ts +221 -0
  250. package/src/wsBinding.ts +200 -0
  251. package/tsconfig.json +15 -0
  252. package/tsconfig.lib.json +23 -0
  253. package/vite.lib.config.ts +16 -0
  254. package/vitest.config.ts +11 -0
  255. package/agentAdapter.d.ts +0 -26
  256. package/conformanceVectors.d.ts +0 -20
  257. package/frameworkAdapters.d.ts +0 -72
  258. package/index.d.ts +0 -20
  259. package/index.js +0 -1702
  260. package/keyRotation.d.ts +0 -20
  261. package/mcpGateway.d.ts +0 -37
  262. package/mcpTransports.d.ts +0 -62
  263. package/mcpWrapper.d.ts +0 -83
  264. package/policyEnforcer.d.ts +0 -50
  265. package/policyTelemetryFeedback.d.ts +0 -11
  266. package/protocolAgent.d.ts +0 -58
  267. package/protocolBinary.d.ts +0 -8
  268. package/protocolCapabilities.d.ts +0 -24
  269. package/protocolReplay.d.ts +0 -35
  270. package/protocolTransport.d.ts +0 -73
  271. package/redisClient.d.ts +0 -49
  272. package/replayStores.d.ts +0 -32
  273. package/revocation.d.ts +0 -71
  274. package/runtimePolicy.d.ts +0 -24
  275. package/runtimePolicyManager.d.ts +0 -15
  276. package/runtimePolicyPresets.d.ts +0 -11
package/README.md CHANGED
@@ -1,16 +1,1327 @@
1
- # @7h3/protocol
1
+ <div align="center">
2
+ <img src="./docs/assets/banner-github.png" alt="7h3 Protocol — Sign every message. Reject every replay." width="100%">
2
3
 
3
- 7h3 Protocol implements **AIP — the Aurelion Interaction Protocol** (wire version `aip/0.1`):
4
- deterministic, signed, replay-safe AI-to-AI message envelopes.
4
+ <br/><br/>
5
5
 
6
- Install:
6
+ [![npm](https://img.shields.io/npm/v/@7h3/protocol?style=flat-square&color=818cf8&logo=npm&logoColor=white&label=%407h3%2Fprotocol)](https://www.npmjs.com/package/@7h3/protocol)
7
+ [![npm browser](https://img.shields.io/npm/v/@7h3/protocol-browser?style=flat-square&color=6366f1&logo=npm&logoColor=white&label=%407h3%2Fprotocol-browser)](https://www.npmjs.com/package/@7h3/protocol-browser)
8
+ [![PyPI](https://img.shields.io/pypi/v/7h3-protocol?style=flat-square&color=818cf8&logo=python&logoColor=white)](https://pypi.org/project/7h3-protocol/)
9
+ [![Crates.io](https://img.shields.io/crates/v/protocol-7h3?style=flat-square&color=a5b4fc&logo=rust&logoColor=white)](https://crates.io/crates/protocol-7h3)
10
+ [![Tests](https://img.shields.io/badge/tests-278%20passing-4ade80?style=flat-square&logo=vitest&logoColor=white)](https://github.com/IceMasterT/7h3-protocol/tree/main/src)
11
+ [![Zero deps](https://img.shields.io/badge/runtime%20deps-0-a5b4fc?style=flat-square)](./package.json)
12
+ [![Wire](https://img.shields.io/badge/wire-7h3%2F0.1-818cf8?style=flat-square)](./docs/VERSIONING_POLICY.md)
13
+ [![License](https://img.shields.io/badge/license-MIT-94a3b8?style=flat-square)](./LICENSE)
14
+
15
+ <br/>
16
+
17
+ **Cryptographic signing and replay protection for AI agent messages. One envelope. Every transport.**
18
+
19
+ <br/>
20
+ </div>
21
+
22
+ ---
23
+
24
+ ## The Problem
25
+
26
+ AI agent systems are moving fast, and the protocols underpinning them were not built with message-level security in mind.
27
+
28
+ **MCP (Model Context Protocol)** is plain JSON-RPC 2.0. A message in flight has no signature. Any intermediary — a rogue proxy, a compromised queue consumer, a misconfigured load balancer — can alter tool call parameters or replay a previously captured request. The MCP handler on the other end has no way to know.
29
+
30
+ **A2A (Agent-to-Agent)** improves on this by signing Agent Cards, giving agents a verifiable identity at the domain level. But Agent Cards are static configuration, not per-message traffic. Once an agent is "trusted," every message it sends thereafter is implicitly trusted regardless of whether the specific message was tampered with in transit or is a replay from ten minutes ago.
31
+
32
+ **HTTP APIs** default to IP-based rate limiting. IP addresses are trivially spoofed or shared — a single compromised NAT or cloud egress IP can represent thousands of agents. And there is no standard replay prevention: the same valid signed request can often be submitted multiple times, triggering duplicate writes, payments, or tool executions. There is no tamper-evident audit trail baked into the infrastructure — logs can be deleted or altered after the fact.
33
+
34
+ The gap these protocols share is identical: they authenticate *agents* at the connection or identity level, but they do not authenticate *individual messages* at the content level. 7h3 Protocol fills that gap without replacing any existing protocol.
35
+
36
+ ---
37
+
38
+ ## What 7h3 Protocol Does
39
+
40
+ 7h3 Protocol wraps every message — regardless of transport — in a **signed envelope**. The envelope is compact, deterministic, and verifiable by any peer that holds the sender's public key (or shared secret).
41
+
42
+ The protocol provides four security primitives, all in one structure:
43
+
44
+ | Primitive | Mechanism |
45
+ |---|---|
46
+ | **Authentication** | Ed25519 or HMAC-SHA256 signature ties the message to a specific key |
47
+ | **Integrity** | Signature covers a canonical byte-identical serialization of the full envelope |
48
+ | **Replay prevention** | TTL expiry + unique nonce; a replayed envelope will fail `(messageId, nonce)` deduplication |
49
+ | **Non-repudiation** | Ed25519 asymmetric keys mean only the holder of the private key could have produced the signature |
50
+
51
+ The same envelope format works over HTTP, WebSocket, gRPC, message queues, and webhooks. One library. One wire format. Every transport.
52
+
53
+ ---
54
+
55
+ ## How It Works
56
+
57
+ ### Ed25519 Signing
58
+
59
+ Each sender generates an Ed25519 keypair. The private key signs messages; peers verify using the public key. Because Ed25519 is asymmetric, a peer that can verify your signatures cannot forge them — compromising one agent does not compromise the signing key of another.
60
+
61
+ For cases where key distribution is impractical, HMAC-SHA256 over a pre-shared secret is also supported, though it provides authentication without non-repudiation.
62
+
63
+ ### Canonical Serialization
64
+
65
+ Signatures only mean something if everyone signs the same bytes. JSON object key order is not guaranteed by the spec, so the same message can serialize differently on different platforms. 7h3 Protocol solves this with deterministic JSON canonicalization: keys are sorted alphabetically at every nesting level, optional absent fields are omitted entirely (not set to `null` or `""`), and the result is UTF-8 encoded with no trailing whitespace.
66
+
67
+ The canonical form is identical byte-for-byte across TypeScript, Python, Rust, and Go. The conformance test suite proves this with shared test vectors.
68
+
69
+ ### TTL and Nonce
70
+
71
+ Every envelope carries:
72
+ - `timestampMs` — when the message was created (Unix milliseconds)
73
+ - `ttlMs` — how long the message is valid
74
+ - `nonce` — a random value unique to this message
75
+
76
+ A receiver rejects the envelope if `now > timestampMs + ttlMs`. It then checks `(sender, messageId, nonce)` against a deduplication cache. A replayed envelope is rejected even if the signature is valid.
77
+
78
+ ### The Envelope Structure
79
+
80
+ ```
81
+ {"body":{"capability"?:"...","content":"...","correlationId"?:"...","intent":"..."},"header":{"messageId":"...","nonce":"...","recipient"?:"...","sender":"...","timestampMs":N,"ttlMs":N,"version":"7h3/0.1"}}
82
+ ```
83
+
84
+ Optional fields (`capability`, `correlationId`, `recipient`) are omitted when absent — not set to `null` or `""`. This is load-bearing for the canonical form: any variation breaks the signature.
85
+
86
+ ### Sequence Diagram
87
+
88
+ ```mermaid
89
+ sequenceDiagram
90
+ participant S as Sender Agent
91
+ participant C as Canonical Serializer
92
+ participant K as Ed25519 Private Key
93
+ participant T as Transport (HTTP/WS/gRPC/Queue/Webhook)
94
+ participant G as Gateway / Receiver
95
+ participant V as Verifier
96
+ participant U as Upstream Service
97
+
98
+ S->>C: createEnvelope(sender, body, ttlMs)
99
+ C->>C: Deterministic JSON canonicalization
100
+ C->>K: sign(canonicalPayload)
101
+ K-->>C: Ed25519 signature (base64url)
102
+ C-->>S: SignedEnvelope {header, body, signature}
103
+ S->>T: Transmit via transport
104
+ T->>G: Request with envelope in header/metadata/wrapper
105
+ G->>V: verifyEnvelope(envelope, publicKey)
106
+ V->>V: Check TTL not expired
107
+ V->>V: Check nonce not replayed
108
+ V->>V: Verify Ed25519 signature
109
+ V->>V: Check allowedSenders + rate limit
110
+ V-->>G: {ok: true, sender: "agent.alpha"}
111
+ G->>U: Forward + inject x-7h3-sender header
112
+ U-->>G: Response
113
+ G-->>S: Response (optionally signed with x-7h3-response)
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 🔒 Security Guarantees
119
+
120
+ | Attack | Defense |
121
+ |---|---|
122
+ | **Impersonation** | Ed25519 signature — only the private key holder can produce a valid signature; no private key means no forgeable message |
123
+ | **Replay attacks** | `(messageId, nonce)` deduplication cache + TTL expiry — a captured valid message cannot be resubmitted |
124
+ | **Tampering** | Signature covers the canonical serialization of the full envelope body; any modification breaks verification |
125
+ | **Unauthorized access** | Per-route `allowedSenders` policy — envelopes from unlisted senders are rejected before reaching upstream |
126
+ | **Response spoofing** | Signed responses with `x-7h3-response` header; `correlationId` binding ties the response to the specific request |
127
+ | **Rate abuse** | `SlidingWindowRateLimiter` keyed by verified sender identity, not IP — VPN and NAT sharing does not grant extra quota |
128
+ | **Audit trail manipulation** | `InMemoryAuditLog` entries are themselves Ed25519-signed and chained; tampering with any entry breaks the chain |
129
+
130
+ ---
131
+
132
+ ## 🤖 Works with Claude (MCP)
133
+
134
+ Claude's tool-calling mechanism is MCP (Model Context Protocol), which uses plain JSON-RPC 2.0. 7h3 Protocol hardens MCP traffic without requiring any changes to your MCP handler.
135
+
136
+ `wrapMcpServer` wraps an existing MCP handler and enforces:
137
+
138
+ - **Signature verification** — every inbound JSON-RPC request must carry a valid 7h3 envelope
139
+ - **Replay protection** — `InMemoryReplayCache` injected automatically
140
+ - **Recipient binding** — the server rejects envelopes not addressed to its own `selfAgentId`, defeating cross-server relay attacks
141
+ - **Sender binding** — the client accepts responses only from the declared `peerAgentId`, defeating response spoofing
142
+ - **Correlation binding** — `correlationId` in every response must match the request's `messageId`, defeating response substitution
143
+
144
+ ```mermaid
145
+ flowchart LR
146
+ CA[Claude Agent] -->|Signed JSON-RPC request| MW[7h3 MCP Wrapper]
147
+ MW -->|Verify signature\ncheck replay\nrecipient binding| MH[MCP Handler]
148
+ MH -->|Plain JSON-RPC response| MW
149
+ MW -->|Sign response\ncorrelation binding| CA
150
+ ```
151
+
152
+ **Server side:**
153
+
154
+ ```ts
155
+ import { wrapMcpServer, signEnvelopeEd25519 } from '@7h3/protocol'
156
+
157
+ const secureServer = wrapMcpServer(myMcpHandler, {
158
+ selfAgentId: 'my-mcp-server',
159
+ sign: (e) => signEnvelopeEd25519(e, serverPrivateKey, 'k1'),
160
+ })
161
+ ```
162
+
163
+ **Client side:**
164
+
165
+ ```ts
166
+ import { wrapMcpClient, signEnvelopeEd25519 } from '@7h3/protocol'
167
+
168
+ const { send } = wrapMcpClient({
169
+ selfAgentId: 'my-client',
170
+ peerAgentId: 'my-mcp-server',
171
+ sign: (e) => signEnvelopeEd25519(e, clientPrivateKey, 'k1'),
172
+ receive: {
173
+ signatureResolver: async ({ keyId }) => ({
174
+ alg: 'ED25519',
175
+ publicKey: serverPublicKey,
176
+ }),
177
+ },
178
+ })
179
+
180
+ const response = await send({ jsonrpc: '2.0', id: 1, method: 'tools/list' }, fetch)
181
+ ```
182
+
183
+ The MCP handler itself is unchanged. The wrapper handles all envelope logic at the boundary.
184
+
185
+ ---
186
+
187
+ ## ⚡ Transport Coverage
188
+
189
+ 7h3 Protocol covers all five common agent transports with the same signing primitives.
190
+
191
+ ### HTTP / REST
192
+
193
+ Signed envelopes travel in the `x-7h3-envelope` request header. The gateway verifies before forwarding; upstream receives `x-7h3-sender` and `x-7h3-verified: true`.
194
+
195
+ ```mermaid
196
+ flowchart LR
197
+ A[Client] -->|POST /api/action\nx-7h3-envelope: {...signed...}| B[API Gateway]
198
+ B -->|verify signature\nrate limit check| C{Policy match?}
199
+ C -->|✅ pass| D[Upstream Service\nx-7h3-sender: agent.alice\nx-7h3-verified: true]
200
+ C -->|❌ fail| E[401 / 403 / 429]
201
+ ```
202
+
203
+ ```ts
204
+ import { createEnvelope, signEnvelopeEd25519 } from '@7h3/protocol'
205
+
206
+ const envelope = await signEnvelopeEd25519(
207
+ createEnvelope({ sender: 'agent.alice', intent: 'write', content: JSON.stringify(payload) }),
208
+ privateKey, 'k1',
209
+ )
210
+
211
+ await fetch('https://api.example.com/action', {
212
+ method: 'POST',
213
+ headers: { 'x-7h3-envelope': JSON.stringify(envelope) },
214
+ body: JSON.stringify(payload),
215
+ })
216
+ ```
217
+
218
+ ### WebSocket
219
+
220
+ Each frame carries a signed JSON envelope with a monotonically increasing `sequenceNumber` to detect out-of-order or dropped frames.
221
+
222
+ ```mermaid
223
+ flowchart LR
224
+ A[Agent] -->|ws.send signed frame| B[WebSocket Server]
225
+ B -->|verify per-frame signature\nsequence check| C[Handler]
226
+ ```
227
+
228
+ ```ts
229
+ import { wrapWebSocket } from '@7h3/protocol'
230
+
231
+ const ws = new WebSocket('wss://agent.example.com/stream')
232
+
233
+ const secure = wrapWebSocket(ws, {
234
+ sender: 'agent.alpha',
235
+ sign: (e) => signEnvelopeEd25519(e, privateKey, 'k1'),
236
+ verify: (e) => verifyEnvelopeEd25519(e, peerPublicKey),
237
+ })
238
+
239
+ secure.send({ intent: 'UPDATE', content: 'delta-payload' })
240
+ secure.onMessage((verified) => console.log(verified.body))
241
+ ```
242
+
243
+ ### gRPC
244
+
245
+ Envelopes travel in the `7h3-envelope-bin` metadata key (binary-safe base64url). The interceptor verifies before the handler is invoked.
246
+
247
+ ```mermaid
248
+ flowchart LR
249
+ A[gRPC Client] -->|metadata: 7h3-envelope-bin| B[gRPC Interceptor]
250
+ B -->|verify| C[gRPC Handler]
251
+ ```
252
+
253
+ ```ts
254
+ import { withGrpcVerification } from '@7h3/protocol'
255
+
256
+ const server = new grpc.Server()
257
+ server.addService(MyService, withGrpcVerification(myServiceImpl, {
258
+ verify: (e) => verifyEnvelopeEd25519(e, clientPublicKey),
259
+ }))
260
+ ```
261
+
262
+ ### Message Queues (SQS, RabbitMQ, etc.)
263
+
264
+ Messages are wrapped in `{ envelope, payload }` JSON. The consumer verifies the envelope before processing the payload. Works with any queue that accepts JSON message bodies.
265
+
266
+ ```mermaid
267
+ flowchart LR
268
+ P[Producer] -->|{envelope, payload}| Q[Queue]
269
+ Q --> C[Consumer]
270
+ C -->|verify envelope\nthen process payload| H[Handler]
271
+ ```
272
+
273
+ ```ts
274
+ import { signQueueMessage, verifyQueueBatch } from '@7h3/protocol'
275
+
276
+ // Producer
277
+ const message = await signQueueMessage(
278
+ { intent: 'PROCESS_ORDER', content: JSON.stringify(order) },
279
+ { sender: 'order-service', sign: (e) => signEnvelopeEd25519(e, privateKey, 'k1') },
280
+ )
281
+ await sqs.sendMessage({ QueueUrl, MessageBody: JSON.stringify(message) }).promise()
282
+
283
+ // Consumer
284
+ const results = await verifyQueueBatch(messages, {
285
+ verify: (e) => verifyEnvelopeEd25519(e, producerPublicKey),
286
+ })
287
+ for (const { ok, payload, sender } of results) {
288
+ if (ok) await processOrder(payload)
289
+ }
290
+ ```
291
+
292
+ ### Webhooks
293
+
294
+ Webhook requests carry two headers: `x-7h3-sig` (Ed25519 signature of the body) and `x-7h3-ts` (Unix timestamp). The receiver verifies both before processing. Timestamp checking prevents replay of captured webhook payloads.
295
+
296
+ ```mermaid
297
+ flowchart LR
298
+ S[Sender] -->|POST /webhook\nx-7h3-sig: ...\nx-7h3-ts: ...| R[Receiver]
299
+ R -->|verify sig + ts freshness| H[Handler]
300
+ ```
301
+
302
+ ```ts
303
+ import { signWebhook, verifyWebhook, consumeWebhook } from '@7h3/protocol'
304
+
305
+ // Sender
306
+ const { headers } = await signWebhook(body, { privateKey, keyId: 'k1' })
307
+ await fetch('https://partner.example.com/webhook', {
308
+ method: 'POST',
309
+ headers: { 'content-type': 'application/json', ...headers },
310
+ body,
311
+ })
312
+
313
+ // Receiver (Express)
314
+ app.post('/webhook', async (req, res) => {
315
+ const result = await verifyWebhook(req.rawBody, req.headers, { publicKey })
316
+ if (!result.ok) return res.status(401).end()
317
+ await processWebhook(result.payload)
318
+ res.status(200).end()
319
+ })
320
+ ```
321
+
322
+ ---
323
+
324
+ ## 🚀 Installation
325
+
326
+ ### TypeScript / Node.js
7
327
 
8
328
  ```bash
9
329
  npm install @7h3/protocol
330
+ # or
331
+ yarn add @7h3/protocol
332
+ # or
333
+ pnpm add @7h3/protocol
334
+ ```
335
+
336
+ **Requirements:** Node.js ≥ 18, or any runtime with `globalThis.crypto` (Web Crypto API). Zero runtime dependencies.
337
+
338
+ ### Browser / Edge (Cloudflare Workers, Deno, Bun)
339
+
340
+ ```bash
341
+ npm install @7h3/protocol-browser
342
+ ```
343
+
344
+ Pure Web Crypto API. No Node.js built-ins. Works in browsers, Cloudflare Workers, Deno, and Bun out of the box. Zero dependencies.
345
+
346
+ ### Python
347
+
348
+ ```bash
349
+ pip install 7h3-protocol
350
+ # or
351
+ uv add 7h3-protocol
352
+ ```
353
+
354
+ **Requirements:** Python ≥ 3.9. Uses `cryptography` for Ed25519.
355
+
356
+ ### Rust
357
+
358
+ ```toml
359
+ [dependencies]
360
+ protocol-7h3 = "0.4"
361
+ ```
362
+
363
+ Or via cargo:
364
+
365
+ ```bash
366
+ cargo add protocol-7h3
367
+ ```
368
+
369
+ Zero external dependencies. Pure stdlib + `ed25519-dalek`.
370
+
371
+ ### Go
372
+
373
+ ```bash
374
+ go get github.com/IceMasterT/7h3-protocol/sdk/go
375
+ ```
376
+
377
+ Zero external dependencies. Pure stdlib (`crypto/ed25519`).
378
+
379
+ ### CLI (`7h3` binary)
380
+
381
+ ```bash
382
+ npm install -g @7h3/protocol
383
+ 7h3 --help
384
+ ```
385
+
386
+ ### Docker
387
+
388
+ ```bash
389
+ docker pull 7h3agency/gateway:latest
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Quick Start
395
+
396
+ The minimum to sign and verify a message.
397
+
398
+ ### TypeScript
399
+
400
+ ```ts
401
+ import {
402
+ generateEd25519KeypairBase64Url,
403
+ createEnvelope,
404
+ signEnvelopeEd25519,
405
+ verifyEnvelopeEd25519,
406
+ } from '@7h3/protocol'
407
+
408
+ const { privateKey, publicKey } = await generateEd25519KeypairBase64Url()
409
+
410
+ const envelope = await signEnvelopeEd25519(
411
+ createEnvelope({ sender: 'agent.alpha', intent: 'TASK', content: 'hello' }),
412
+ privateKey, 'k1',
413
+ )
414
+
415
+ const ok = await verifyEnvelopeEd25519(envelope, publicKey)
416
+ console.log(ok) // true
417
+ ```
418
+
419
+ ### Python
420
+
421
+ ```python
422
+ from protocol_7h3 import generate_keypair, create_envelope, sign_envelope, verify_envelope
423
+
424
+ private_key, public_key = generate_keypair()
425
+
426
+ envelope = sign_envelope(
427
+ create_envelope(sender="agent.alpha", intent="TASK", content="hello"),
428
+ private_key, key_id="k1",
429
+ )
430
+
431
+ ok = verify_envelope(envelope, public_key)
432
+ print(ok) # True
433
+ ```
434
+
435
+ ### Rust
436
+
437
+ ```rust
438
+ use protocol_7h3::{generate_keypair, create_envelope, sign_envelope, verify_envelope};
439
+
440
+ let (private_key, public_key) = generate_keypair();
441
+
442
+ let env = create_envelope("agent.alpha", "TASK", "hello", 30_000);
443
+ let signed = sign_envelope(&env, &private_key, "k1")?;
444
+
445
+ let ok = verify_envelope(&signed, &public_key)?;
446
+ assert!(ok);
447
+ ```
448
+
449
+ ### Go
450
+
451
+ ```go
452
+ import "github.com/IceMasterT/7h3-protocol/sdk/go/protocol7h3"
453
+
454
+ privateKey, publicKey, _ := protocol7h3.GenerateKeypair()
455
+
456
+ env := protocol7h3.CreateEnvelope("agent.alpha", "TASK", "hello", 30000)
457
+ signed, _ := protocol7h3.SignEnvelope(env, privateKey, "k1")
458
+
459
+ ok, _ := protocol7h3.VerifyEnvelope(signed, publicKey)
460
+ fmt.Println(ok) // true
461
+ ```
462
+
463
+ ---
464
+
465
+ ## HTTP Middleware
466
+
467
+ Drop-in middleware for common frameworks.
468
+
469
+ ### Express.js (TypeScript)
470
+
471
+ ```ts
472
+ import express from 'express'
473
+ import { createVerifyMiddleware } from '@7h3/protocol'
474
+
475
+ const app = express()
476
+
477
+ app.use(createVerifyMiddleware({
478
+ verify: (e) => verifyEnvelopeEd25519(e, agentPublicKey),
479
+ onFailure: (res, reason) => res.status(401).json({ error: reason }),
480
+ }))
481
+
482
+ app.post('/action', (req, res) => {
483
+ // req.headers['x-7h3-sender'] contains the verified sender identity
484
+ const sender = req.headers['x-7h3-sender']
485
+ res.json({ received: true, from: sender })
486
+ })
487
+ ```
488
+
489
+ ### Flask (Python)
490
+
491
+ ```python
492
+ from flask import Flask, request, jsonify
493
+ from protocol_7h3 import verify_middleware
494
+
495
+ app = Flask(__name__)
496
+
497
+ @app.before_request
498
+ @verify_middleware(public_key=AGENT_PUBLIC_KEY)
499
+ def require_signed():
500
+ pass
501
+
502
+ @app.route('/action', methods=['POST'])
503
+ def action():
504
+ sender = request.headers.get('x-7h3-sender')
505
+ return jsonify(received=True, sender=sender)
506
+ ```
507
+
508
+ ### Go net/http
509
+
510
+ ```go
511
+ import "github.com/IceMasterT/7h3-protocol/sdk/go/protocol7h3"
512
+
513
+ func verifyMiddleware(next http.Handler, pubKey []byte) http.Handler {
514
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
515
+ if err := protocol7h3.VerifyRequest(r, pubKey); err != nil {
516
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
517
+ return
518
+ }
519
+ next.ServeHTTP(w, r)
520
+ })
521
+ }
522
+
523
+ http.Handle("/action", verifyMiddleware(actionHandler, agentPublicKey))
524
+ ```
525
+
526
+ ---
527
+
528
+ ## 🛡️ API Gateway
529
+
530
+ The zero-code path: drop the 7h3 gateway in front of any existing service. No application changes required.
531
+
532
+ **Step 1: Generate keys**
533
+
534
+ ```bash
535
+ 7h3 keygen --output my-keys.json
536
+ ```
537
+
538
+ **Step 2: Configure `7h3.yaml`**
539
+
540
+ ```yaml
541
+ gateway:
542
+ upstream: http://my-api:3000
543
+ port: 8080
544
+
545
+ auth:
546
+ require: ed25519
547
+
548
+ routes:
549
+ - path: "/admin/**"
550
+ allowedSenders: ["agent.admin"]
551
+ require: ed25519
552
+
553
+ - path: "/api/**"
554
+ rateLimit:
555
+ windowMs: 60000
556
+ max: 100
557
+ ```
558
+
559
+ **Step 3: Run**
560
+
561
+ ```bash
562
+ # CLI
563
+ 7h3 gateway --upstream http://my-api:3000 --public-key MCowBQYDK2Vw...
564
+
565
+ # Docker
566
+ docker run -p 8080:8080 \
567
+ -e GATEWAY_PRIVATE_KEY=... \
568
+ 7h3agency/gateway:latest \
569
+ --upstream http://my-api:3000 --require ed25519
570
+
571
+ # Docker Compose
572
+ docker compose up
573
+ ```
574
+
575
+ ### Gateway Architecture
576
+
577
+ ```mermaid
578
+ flowchart TB
579
+ subgraph Clients
580
+ A1[AI Agent A]
581
+ A2[AI Agent B]
582
+ A3[Browser SDK]
583
+ end
584
+ subgraph "7h3 Gateway"
585
+ GW[Protocol7h3Gateway\nverify → rate-limit → policy]
586
+ AL[InMemoryAuditLog\nEd25519-signed entries]
587
+ end
588
+ subgraph "Your Services (unchanged)"
589
+ S1[API Service]
590
+ S2[Admin Service]
591
+ S3[Webhook Handler]
592
+ end
593
+ A1 -->|signed request| GW
594
+ A2 -->|signed request| GW
595
+ A3 -->|signed request| GW
596
+ GW -->|x-7h3-sender injected| S1
597
+ GW -->|policy: ed25519 + allowedSenders| S2
598
+ GW -.->|log every event| AL
599
+ S1 -.->|optional signed response| A1
600
+ ```
601
+
602
+ The gateway exposes three methods in code:
603
+
604
+ ```ts
605
+ import { createGateway } from '@7h3/protocol'
606
+
607
+ const gateway = createGateway(config)
608
+
609
+ // Verify an inbound envelope
610
+ const result = await gateway.verify(envelope)
611
+
612
+ // Handle a full HTTP request (verify + route + forward)
613
+ const response = await gateway.handle(request)
614
+ ```
615
+
616
+ ---
617
+
618
+ ## Per-Route Policies
619
+
620
+ Each route can enforce independent authentication requirements, sender allowlists, and rate limits.
621
+
622
+ ### TypeScript
623
+
624
+ ```ts
625
+ import { RoutePolicy } from '@7h3/protocol'
626
+
627
+ const policies: RoutePolicy[] = [
628
+ {
629
+ path: '/admin/**',
630
+ require: 'ed25519',
631
+ allowedSenders: ['agent.admin', 'agent.operator'],
632
+ rateLimit: { windowMs: 60_000, max: 20 },
633
+ },
634
+ {
635
+ path: '/api/write',
636
+ require: 'ed25519',
637
+ rateLimit: { windowMs: 60_000, max: 100 },
638
+ },
639
+ {
640
+ path: '/api/read/**',
641
+ require: 'hmac',
642
+ },
643
+ ]
644
+ ```
645
+
646
+ ### YAML
647
+
648
+ ```yaml
649
+ routes:
650
+ - path: "/admin/**"
651
+ require: ed25519
652
+ allowedSenders:
653
+ - agent.admin
654
+ - agent.operator
655
+ rateLimit:
656
+ windowMs: 60000
657
+ max: 20
658
+
659
+ - path: "/api/write"
660
+ require: ed25519
661
+ rateLimit:
662
+ windowMs: 60000
663
+ max: 100
664
+
665
+ - path: "/api/read/**"
666
+ require: hmac
10
667
  ```
11
668
 
12
- Import:
669
+ Path matching supports glob patterns: `**` (any path segment, recursive), `*` (single segment), `?` (single character).
670
+
671
+ ---
672
+
673
+ ## Rate Limiting
674
+
675
+ The `SlidingWindowRateLimiter` is keyed by **verified sender identity**, not IP address. This means:
676
+ - NAT, VPNs, and shared cloud egress IPs do not inflate any agent's quota
677
+ - Quota follows the cryptographic identity of the agent regardless of network topology
678
+ - Each window is computed per sender as a sliding window (not a fixed bucket), preventing burst exploitation at window boundaries
13
679
 
14
680
  ```ts
15
- import { createAipAgentAdapter, receiveEnvelope } from '@7h3/protocol'
681
+ import { SlidingWindowRateLimiter } from '@7h3/protocol'
682
+
683
+ const limiter = new SlidingWindowRateLimiter({
684
+ windowMs: 60_000, // 1 minute
685
+ max: 100, // 100 requests per window per sender
686
+ })
687
+
688
+ // After verifying the envelope:
689
+ const allowed = await limiter.check(verifiedSender)
690
+ if (!allowed) {
691
+ return new Response('Too Many Requests', { status: 429 })
692
+ }
693
+ ```
694
+
695
+ Rate limit state is per-instance. For distributed deployments, configure an external store (Redis adapter available) in `7h3.yaml`:
696
+
697
+ ```yaml
698
+ rateLimit:
699
+ store: redis
700
+ redisUrl: redis://localhost:6379
16
701
  ```
702
+
703
+ ---
704
+
705
+ ## Signed Responses
706
+
707
+ Responses can optionally carry a server signature in the `x-7h3-response` header. This gives clients cryptographic proof that the response came from the expected server and corresponds to their specific request.
708
+
709
+ ```ts
710
+ import { signResponse, verifyResponse } from '@7h3/protocol'
711
+
712
+ // Server: sign the response
713
+ const responseEnvelope = await signResponse(responseBody, {
714
+ sender: 'api-server',
715
+ correlationId: requestEnvelope.header.messageId,
716
+ sign: (e) => signEnvelopeEd25519(e, serverPrivateKey, 'k1'),
717
+ })
718
+
719
+ // Server sends: x-7h3-response: <JSON stringified responseEnvelope>
720
+
721
+ // Client: verify the response
722
+ const verified = await verifyResponse(responseEnvelope, {
723
+ expectedSender: 'api-server',
724
+ expectedCorrelationId: sentEnvelope.header.messageId,
725
+ verify: (e) => verifyEnvelopeEd25519(e, serverPublicKey),
726
+ })
727
+
728
+ if (!verified.ok) throw new Error(`Response rejected: ${verified.reason}`)
729
+ ```
730
+
731
+ Bidirectional trust matters when agents can be instructed by servers to take further actions. Without signed responses, a compromised intermediary can send arbitrary instructions on behalf of a trusted server.
732
+
733
+ ---
734
+
735
+ ## 📋 Tamper-Evident Audit Log
736
+
737
+ `InMemoryAuditLog` records every verification event. Each log entry is itself Ed25519-signed, and each entry's signature covers the hash of the previous entry. Deleting or modifying any entry breaks the chain — the `verify()` call surfaces exactly which entries were tampered with.
738
+
739
+ ```ts
740
+ import { createAuditLog } from '@7h3/protocol'
741
+
742
+ const auditLog = createAuditLog({
743
+ sign: (e) => signEnvelopeEd25519(e, auditPrivateKey, 'audit-k1'),
744
+ })
745
+
746
+ // Log an event
747
+ await auditLog.log({
748
+ event: 'VERIFY_OK',
749
+ sender: 'agent.alpha',
750
+ messageId: envelope.header.messageId,
751
+ route: '/api/action',
752
+ })
753
+
754
+ // Query events
755
+ const events = await auditLog.query({ sender: 'agent.alpha', since: Date.now() - 3600_000 })
756
+
757
+ // Verify chain integrity
758
+ const integrity = await auditLog.verify()
759
+ if (!integrity.ok) {
760
+ console.error('Audit log tampered at entry:', integrity.firstTamperedIndex)
761
+ }
762
+ ```
763
+
764
+ ```mermaid
765
+ flowchart LR
766
+ E1[Entry 1\n✅ valid sig] --> E2[Entry 2\n✅ valid sig] --> E3[Entry 3\n✅ valid sig]
767
+ E3 --> E4[Entry 4\n❌ TAMPERED\nsig broken]
768
+ style E4 fill:#ef4444,color:#fff
769
+ ```
770
+
771
+ Each entry contains: `event`, `sender`, `messageId`, `route`, `timestampMs`, `signature`, and `prevHash`. The chain is append-only during normal operation; `verify()` is a read-only integrity check that can be run at any time.
772
+
773
+ ---
774
+
775
+ ## 🔑 Key Management
776
+
777
+ ### Generate a Keypair
778
+
779
+ ```bash
780
+ # CLI — writes to stdout or --output file
781
+ 7h3 keygen
782
+ 7h3 keygen --output my-keys.json
783
+
784
+ # TypeScript
785
+ const { privateKey, publicKey } = await generateEd25519KeypairBase64Url()
786
+
787
+ # Python
788
+ private_key, public_key = generate_keypair()
789
+
790
+ # Rust
791
+ let (private_key, public_key) = generate_keypair();
792
+
793
+ # Go
794
+ privateKey, publicKey, _ := protocol7h3.GenerateKeypair()
795
+ ```
796
+
797
+ ### Key Rotation
798
+
799
+ `KeyRotationManager` manages multiple active key versions. Old keys remain valid for verification until explicitly revoked; only the current key is used for signing.
800
+
801
+ ```ts
802
+ import { KeyRotationManager } from '@7h3/protocol'
803
+
804
+ const manager = new KeyRotationManager()
805
+ await manager.addKey({ keyId: 'k2', privateKey: newPrivKey, publicKey: newPubKey })
806
+ await manager.setActive('k2')
807
+ // k1 still verifies existing messages; k2 signs new ones
808
+ ```
809
+
810
+ ### Revocation
811
+
812
+ ```ts
813
+ import { RevocationRegistry } from '@7h3/protocol'
814
+
815
+ const registry = new RevocationRegistry()
816
+ await registry.revoke('k1', { reason: 'key-compromise', revokedAt: Date.now() })
817
+
818
+ // Verification now rejects envelopes signed with k1
819
+ const result = await verifyEnvelopeEd25519(envelope, publicKey, { registry })
820
+ ```
821
+
822
+ ### Public Key Discovery
823
+
824
+ Serve your public keys at `/.well-known/7h3-keys` for automatic discovery by peers:
825
+
826
+ ```bash
827
+ # Serve via CLI
828
+ 7h3 keys serve --public-key MCowBQYDK2Vw...
829
+
830
+ # Or configure in 7h3.yaml
831
+ keyDiscovery:
832
+ enabled: true
833
+ keys:
834
+ - keyId: k1
835
+ publicKey: MCowBQYDK2Vw...
836
+ algorithm: ed25519
837
+ ```
838
+
839
+ Response format:
840
+
841
+ ```json
842
+ {
843
+ "version": "7h3/0.1",
844
+ "keys": [
845
+ { "keyId": "k1", "algorithm": "ed25519", "publicKey": "MCowBQYDK2Vw..." }
846
+ ]
847
+ }
848
+ ```
849
+
850
+ ---
851
+
852
+ ## WebSocket Usage
853
+
854
+ ```ts
855
+ import { wrapWebSocket, signEnvelopeEd25519, verifyEnvelopeEd25519 } from '@7h3/protocol'
856
+
857
+ const ws = new WebSocket('wss://agent.example.com/stream')
858
+
859
+ const secure = wrapWebSocket(ws, {
860
+ sender: 'agent.alpha',
861
+ sign: (envelope) => signEnvelopeEd25519(envelope, privateKey, 'k1'),
862
+ verify: (envelope) => verifyEnvelopeEd25519(envelope, peerPublicKey),
863
+ })
864
+
865
+ // Send a signed frame
866
+ await secure.send({ intent: 'UPDATE', content: JSON.stringify({ delta: 42 }) })
867
+
868
+ // Receive verified frames
869
+ secure.onMessage((verified) => {
870
+ console.log('from:', verified.header.sender)
871
+ console.log('body:', verified.body)
872
+ })
873
+
874
+ // Sequence numbers are managed automatically; out-of-order frames throw
875
+ secure.onSequenceError((err) => console.error('Frame sequence gap:', err))
876
+ ```
877
+
878
+ Each frame carries an auto-incremented `sequenceNumber` in the header. The receiver tracks the expected sequence and rejects frames that arrive out of order or with gaps.
879
+
880
+ ---
881
+
882
+ ## gRPC Usage
883
+
884
+ ```ts
885
+ import * as grpc from '@grpc/grpc-js'
886
+ import { withGrpcVerification, signEnvelopeEd25519 } from '@7h3/protocol'
887
+
888
+ // Server: wrap handler with signature verification
889
+ const server = new grpc.Server()
890
+ server.addService(
891
+ AgentService,
892
+ withGrpcVerification(agentServiceImpl, {
893
+ metadataKey: '7h3-envelope-bin',
894
+ verify: (envelope) => verifyEnvelopeEd25519(envelope, clientPublicKey),
895
+ }),
896
+ )
897
+
898
+ // Client: attach signed envelope to outbound metadata
899
+ const metadata = new grpc.Metadata()
900
+ const envelope = await signEnvelopeEd25519(
901
+ createEnvelope({ sender: 'client', intent: 'CALL', content: '' }),
902
+ privateKey, 'k1',
903
+ )
904
+ metadata.set('7h3-envelope-bin', Buffer.from(JSON.stringify(envelope)).toString('base64'))
905
+
906
+ const stub = new AgentServiceClient(address, grpc.credentials.createInsecure())
907
+ stub.someMethod(request, metadata, callback)
908
+ ```
909
+
910
+ ---
911
+
912
+ ## Queue Usage
913
+
914
+ ```ts
915
+ import { signQueueMessage, verifyQueueBatch } from '@7h3/protocol'
916
+ import { SQS } from 'aws-sdk'
917
+
918
+ const sqs = new SQS()
919
+
920
+ // Producer
921
+ async function enqueue(order: Order) {
922
+ const message = await signQueueMessage(
923
+ { intent: 'PROCESS_ORDER', content: JSON.stringify(order) },
924
+ {
925
+ sender: 'order-service',
926
+ sign: (e) => signEnvelopeEd25519(e, privateKey, 'k1'),
927
+ },
928
+ )
929
+ await sqs.sendMessage({
930
+ QueueUrl: QUEUE_URL,
931
+ MessageBody: JSON.stringify(message),
932
+ }).promise()
933
+ }
934
+
935
+ // Consumer
936
+ async function consume(sqsMessages: SQS.Message[]) {
937
+ const results = await verifyQueueBatch(
938
+ sqsMessages.map((m) => JSON.parse(m.Body!)),
939
+ { verify: (e) => verifyEnvelopeEd25519(e, producerPublicKey) },
940
+ )
941
+
942
+ for (const { ok, payload, sender, reason } of results) {
943
+ if (!ok) {
944
+ console.error('Rejected message from', sender, ':', reason)
945
+ continue
946
+ }
947
+ await processOrder(JSON.parse(payload.content))
948
+ }
949
+ }
950
+ ```
951
+
952
+ ---
953
+
954
+ ## Webhook Usage
955
+
956
+ ```ts
957
+ import { signWebhook, verifyWebhook } from '@7h3/protocol'
958
+ import express from 'express'
959
+
960
+ // Sender
961
+ async function sendWebhook(payload: object) {
962
+ const body = JSON.stringify(payload)
963
+ const { headers } = await signWebhook(body, {
964
+ privateKey,
965
+ keyId: 'k1',
966
+ })
967
+
968
+ await fetch('https://partner.example.com/webhook', {
969
+ method: 'POST',
970
+ headers: { 'content-type': 'application/json', ...headers },
971
+ body,
972
+ })
973
+ }
974
+
975
+ // Receiver (Express, with raw body access)
976
+ const app = express()
977
+ app.use(express.raw({ type: 'application/json' }))
978
+
979
+ app.post('/webhook', async (req, res) => {
980
+ const result = await verifyWebhook(req.body, req.headers as Record<string, string>, {
981
+ publicKey: senderPublicKey,
982
+ maxAgeMs: 30_000, // reject payloads older than 30 seconds
983
+ })
984
+
985
+ if (!result.ok) {
986
+ return res.status(401).json({ error: result.reason })
987
+ }
988
+
989
+ await processWebhookPayload(result.payload)
990
+ res.status(200).end()
991
+ })
992
+ ```
993
+
994
+ The `x-7h3-sig` header contains the Ed25519 signature of the raw request body. The `x-7h3-ts` header contains the Unix millisecond timestamp. Both must be present and valid. The timestamp check prevents replaying captured webhook payloads outside the `maxAgeMs` window.
995
+
996
+ ---
997
+
998
+ ## MCP Integration (Claude) — Full Example
999
+
1000
+ Complete setup for a hardened MCP server that Claude (or any MCP client) can call with signature verification on both sides.
1001
+
1002
+ ### Server (`my-mcp-server.ts`)
1003
+
1004
+ ```ts
1005
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
1006
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
1007
+ import {
1008
+ wrapMcpServer,
1009
+ signEnvelopeEd25519,
1010
+ verifyEnvelopeEd25519,
1011
+ generateEd25519KeypairBase64Url,
1012
+ } from '@7h3/protocol'
1013
+
1014
+ // Load or generate keys (in production, load from env/secrets)
1015
+ const { privateKey: serverPrivKey, publicKey: serverPubKey } = await generateEd25519KeypairBase64Url()
1016
+ const clientPubKey = process.env.CLIENT_PUBLIC_KEY!
1017
+
1018
+ const server = new Server({ name: 'my-mcp-server', version: '1.0.0' })
1019
+
1020
+ // Register tools normally
1021
+ server.setRequestHandler('tools/call', async (request) => {
1022
+ return { content: [{ type: 'text', text: `Executed: ${request.params.name}` }] }
1023
+ })
1024
+
1025
+ // Wrap with 7h3 verification — no other changes needed
1026
+ const secureServer = wrapMcpServer(server, {
1027
+ selfAgentId: 'my-mcp-server',
1028
+ sign: (e) => signEnvelopeEd25519(e, serverPrivKey, 'k1'),
1029
+ receive: {
1030
+ signatureResolver: async ({ keyId }) => ({
1031
+ alg: 'ED25519',
1032
+ publicKey: clientPubKey,
1033
+ }),
1034
+ },
1035
+ })
1036
+
1037
+ const transport = new StdioServerTransport()
1038
+ await secureServer.connect(transport)
1039
+ ```
1040
+
1041
+ ### Client (`my-mcp-client.ts`)
1042
+
1043
+ ```ts
1044
+ import {
1045
+ wrapMcpClient,
1046
+ signEnvelopeEd25519,
1047
+ verifyEnvelopeEd25519,
1048
+ generateEd25519KeypairBase64Url,
1049
+ } from '@7h3/protocol'
1050
+
1051
+ const { privateKey: clientPrivKey } = await generateEd25519KeypairBase64Url()
1052
+ const serverPubKey = process.env.SERVER_PUBLIC_KEY!
1053
+
1054
+ const { send } = wrapMcpClient({
1055
+ selfAgentId: 'my-client',
1056
+ peerAgentId: 'my-mcp-server',
1057
+ sign: (e) => signEnvelopeEd25519(e, clientPrivKey, 'k1'),
1058
+ receive: {
1059
+ signatureResolver: async () => ({ alg: 'ED25519', publicKey: serverPubKey }),
1060
+ },
1061
+ })
1062
+
1063
+ // Use exactly like a normal MCP fetch call — signing is transparent
1064
+ const tools = await send({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }, fetch)
1065
+ const result = await send({
1066
+ jsonrpc: '2.0', id: 2, method: 'tools/call',
1067
+ params: { name: 'my_tool', arguments: { input: 'hello' } },
1068
+ }, fetch)
1069
+ ```
1070
+
1071
+ The `send` function wraps the JSON-RPC request in a signed envelope, sends it, receives the signed response, verifies the response signature and correlation ID, and returns the unwrapped JSON-RPC result. The caller sees standard MCP semantics.
1072
+
1073
+ ---
1074
+
1075
+ ## Browser / Edge SDK
1076
+
1077
+ The `@7h3/protocol-browser` package uses the Web Crypto API exclusively. It has zero Node.js dependencies and works in browsers, Cloudflare Workers, Deno, and Bun.
1078
+
1079
+ ```ts
1080
+ import {
1081
+ generateKeypair,
1082
+ signRequest,
1083
+ verifyResponseHeader,
1084
+ } from '@7h3/protocol-browser'
1085
+
1086
+ // Generate a keypair (stored in the browser's crypto key store)
1087
+ const { privateKey, publicKey, publicKeyBase64 } = await generateKeypair()
1088
+
1089
+ // Sign an outbound request
1090
+ const { headers: signedHeaders } = await signRequest(
1091
+ { method: 'POST', url: 'https://api.example.com/action', body: payload },
1092
+ {
1093
+ sender: 'browser-agent',
1094
+ privateKey,
1095
+ keyId: 'k1',
1096
+ },
1097
+ )
1098
+
1099
+ const response = await fetch('https://api.example.com/action', {
1100
+ method: 'POST',
1101
+ headers: { 'content-type': 'application/json', ...signedHeaders },
1102
+ body: JSON.stringify(payload),
1103
+ })
1104
+
1105
+ // Verify the signed response
1106
+ const responseEnvelope = response.headers.get('x-7h3-response')
1107
+ if (responseEnvelope) {
1108
+ const verified = await verifyResponseHeader(responseEnvelope, {
1109
+ expectedSender: 'api-server',
1110
+ serverPublicKey: SERVER_PUBLIC_KEY,
1111
+ expectedCorrelationId: signedHeaders['x-7h3-message-id'],
1112
+ })
1113
+ if (!verified.ok) throw new Error('Response not from expected server')
1114
+ }
1115
+ ```
1116
+
1117
+ **Cloudflare Workers example:**
1118
+
1119
+ ```ts
1120
+ import { generateKeypair, signRequest } from '@7h3/protocol-browser'
1121
+
1122
+ export default {
1123
+ async fetch(request: Request, env: Env): Promise<Response> {
1124
+ const { privateKey } = await generateKeypair()
1125
+ const { headers } = await signRequest(
1126
+ { method: request.method, url: request.url, body: await request.text() },
1127
+ { sender: 'worker-agent', privateKey, keyId: 'k1' },
1128
+ )
1129
+ return fetch(env.UPSTREAM_URL, { headers: { ...Object.fromEntries(request.headers), ...headers } })
1130
+ },
1131
+ }
1132
+ ```
1133
+
1134
+ ---
1135
+
1136
+ ## Conformance and Cross-SDK Compatibility
1137
+
1138
+ All SDKs share a test vector file at `conformance/7h3_v0_1.json`. Each vector contains an envelope, the canonical serialization expected, and the expected signature (produced with a fixed test keypair). An SDK passes conformance when it can:
1139
+
1140
+ 1. Produce the identical canonical bytes from the input envelope
1141
+ 2. Verify the pre-generated signature from any other SDK
1142
+ 3. Produce a signature that any other SDK can verify
1143
+
1144
+ | SDK | Canonical form | Verify cross-SDK | Sign cross-SDK |
1145
+ |---|---|---|---|
1146
+ | TypeScript / Node.js | ✅ | ✅ | ✅ |
1147
+ | Browser / Edge | ✅ | ✅ | ✅ |
1148
+ | Python | ✅ | ✅ | ✅ |
1149
+ | Rust | ✅ | ✅ | ✅ |
1150
+ | Go | ✅ | ✅ | ✅ |
1151
+
1152
+ Run conformance tests:
1153
+
1154
+ ```bash
1155
+ # TypeScript
1156
+ npm run test:conformance
1157
+
1158
+ # Python
1159
+ pytest sdk/python/tests/test_conformance.py
1160
+
1161
+ # Rust
1162
+ cargo test conformance -- --nocapture
1163
+
1164
+ # Go
1165
+ go test ./sdk/go/... -run TestConformance
1166
+ ```
1167
+
1168
+ The canonical form rule is simple: serialize the `header` and `body` objects with keys sorted alphabetically at every level. Absent optional fields are excluded. Numbers are JSON numbers (not strings). The result is UTF-8 encoded with no trailing whitespace or newline.
1169
+
1170
+ ---
1171
+
1172
+ ## CLI Reference
1173
+
1174
+ ```bash
1175
+ npm install -g @7h3/protocol
1176
+ ```
1177
+
1178
+ | Command | Description |
1179
+ |---|---|
1180
+ | `7h3 keygen` | Generate an Ed25519 keypair, print to stdout |
1181
+ | `7h3 keygen --output <file>` | Write keypair to a JSON file |
1182
+ | `7h3 sign --private-key <k> --sender <id>` | Sign a message from stdin, print envelope |
1183
+ | `7h3 sign --private-key <k> --sender <id> --intent <intent>` | Sign with explicit intent |
1184
+ | `7h3 sign --private-key <k> --sender <id> --recipient <id>` | Sign with recipient binding |
1185
+ | `7h3 verify --public-key <k> --envelope <json>` | Verify an envelope's signature |
1186
+ | `7h3 verify --public-key <k> --envelope <json> --check-replay` | Verify and check replay cache |
1187
+ | `7h3 inspect --envelope <json>` | Pretty-print an envelope with decoded fields |
1188
+ | `7h3 gateway --upstream <url> --public-key <k>` | Start the HTTP proxy gateway |
1189
+ | `7h3 gateway --upstream <url> --public-key <k> --port <n>` | Gateway on a specific port |
1190
+ | `7h3 gateway --config 7h3.yaml` | Gateway with full config file |
1191
+ | `7h3 keys serve --public-key <k>` | Serve `/.well-known/7h3-keys` |
1192
+ | `7h3 keys serve --public-key <k> --port <n>` | Key server on a specific port |
1193
+ | `7h3 keys revoke --key-id <id>` | Add a key to the revocation list |
1194
+ | `7h3 --help` | Show all commands |
1195
+ | `7h3 <command> --help` | Show flags for a specific command |
1196
+
1197
+ **Common flags:**
1198
+
1199
+ | Flag | Description |
1200
+ |---|---|
1201
+ | `--private-key <base64url>` | Ed25519 private key (base64url encoded) |
1202
+ | `--public-key <base64url>` | Ed25519 public key (base64url encoded) |
1203
+ | `--key-id <id>` | Key identifier string (used in envelope header) |
1204
+ | `--sender <id>` | Sender identity string |
1205
+ | `--recipient <id>` | Recipient identity string (optional) |
1206
+ | `--intent <string>` | Intent label for the envelope body |
1207
+ | `--ttl <ms>` | TTL in milliseconds (default: 30000) |
1208
+ | `--output <file>` | Write output to file instead of stdout |
1209
+ | `--config <file>` | Load configuration from YAML file |
1210
+ | `--upstream <url>` | Upstream URL for gateway mode |
1211
+ | `--port <n>` | Port to listen on (default: 8080) |
1212
+ | `--require <alg>` | Required algorithm: `ed25519` or `hmac` |
1213
+
1214
+ ---
1215
+
1216
+ ## Docker Reference
1217
+
1218
+ ### Gateway Image
1219
+
1220
+ ```bash
1221
+ docker pull 7h3agency/gateway:latest
1222
+ ```
1223
+
1224
+ **Run:**
1225
+
1226
+ ```bash
1227
+ docker run -p 8080:8080 \
1228
+ -e GATEWAY_PRIVATE_KEY=<base64url-private-key> \
1229
+ -e GATEWAY_PUBLIC_KEY=<base64url-public-key> \
1230
+ 7h3agency/gateway:latest \
1231
+ --upstream http://my-api:3000 \
1232
+ --require ed25519
1233
+ ```
1234
+
1235
+ **Docker Compose (with config file):**
1236
+
1237
+ ```yaml
1238
+ services:
1239
+ gateway:
1240
+ image: 7h3agency/gateway:latest
1241
+ ports:
1242
+ - "8080:8080"
1243
+ environment:
1244
+ GATEWAY_PRIVATE_KEY: ${GATEWAY_PRIVATE_KEY}
1245
+ GATEWAY_PUBLIC_KEY: ${GATEWAY_PUBLIC_KEY}
1246
+ volumes:
1247
+ - ./7h3.yaml:/app/7h3.yaml:ro
1248
+ command: ["--config", "/app/7h3.yaml"]
1249
+ healthcheck:
1250
+ test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
1251
+ interval: 10s
1252
+ timeout: 5s
1253
+ retries: 3
1254
+ ```
1255
+
1256
+ **Environment variables:**
1257
+
1258
+ | Variable | Required | Description |
1259
+ |---|---|---|
1260
+ | `GATEWAY_PRIVATE_KEY` | Yes (if signing responses) | Base64url Ed25519 private key for signing gateway responses |
1261
+ | `GATEWAY_PUBLIC_KEY` | Yes | Base64url Ed25519 public key for incoming request verification |
1262
+ | `GATEWAY_UPSTREAM` | If no config file | Upstream service URL |
1263
+ | `GATEWAY_PORT` | No (default: 8080) | Port to listen on |
1264
+ | `GATEWAY_REQUIRE` | No (default: ed25519) | Required algorithm: `ed25519` or `hmac` |
1265
+ | `GATEWAY_LOG_LEVEL` | No (default: info) | Log level: `debug`, `info`, `warn`, `error` |
1266
+ | `GATEWAY_RATE_LIMIT_MAX` | No | Max requests per window per sender |
1267
+ | `GATEWAY_RATE_LIMIT_WINDOW_MS` | No | Rate limit window in milliseconds |
1268
+ | `REDIS_URL` | No | Redis URL for distributed rate limiting |
1269
+
1270
+ **Health check:**
1271
+
1272
+ ```bash
1273
+ curl http://localhost:8080/health
1274
+ # {"status":"ok","uptime":12345,"version":"0.4.0"}
1275
+ ```
1276
+
1277
+ **Metrics (Prometheus):**
1278
+
1279
+ ```bash
1280
+ curl http://localhost:8080/metrics
1281
+ ```
1282
+
1283
+ ---
1284
+
1285
+ ## Uninstall
1286
+
1287
+ ```bash
1288
+ # Node.js
1289
+ npm uninstall @7h3/protocol
1290
+ npm uninstall @7h3/protocol-browser
1291
+
1292
+ # Python
1293
+ pip uninstall 7h3-protocol
1294
+
1295
+ # Rust
1296
+ cargo remove protocol-7h3
1297
+
1298
+ # Go
1299
+ go mod edit -droprequire github.com/IceMasterT/7h3-protocol/sdk/go
1300
+ go mod tidy
1301
+ ```
1302
+
1303
+ **Note on deprecated packages:** The old package names `aip7h3` (PyPI) and `aip7h3` (crates.io) are tombstones — they re-export from the current packages and will not receive further updates. If you have them installed, uninstalling the tombstone and installing the current package is the correct migration path.
1304
+
1305
+ ```bash
1306
+ # Python migration
1307
+ pip uninstall aip7h3
1308
+ pip install 7h3-protocol
1309
+
1310
+ # Rust migration
1311
+ cargo remove aip7h3
1312
+ cargo add protocol-7h3
1313
+ ```
1314
+
1315
+ ---
1316
+
1317
+ ## Contributing
1318
+
1319
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, the test suite structure, how to add conformance vectors, and the pull request process.
1320
+
1321
+ The short version: open an issue before significant changes, run `npm test` (all 278 tests must pass), and ensure any new API is covered by conformance vectors if it affects the canonical form or signature behavior.
1322
+
1323
+ ---
1324
+
1325
+ ## License
1326
+
1327
+ MIT — see [LICENSE](./LICENSE).