@7h3/protocol 0.4.0 → 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 (63) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +1169 -175
  3. package/bin/7h3.ts +22 -1
  4. package/docs/assets/banner-github.png +0 -0
  5. package/docs/assets/banner.svg +123 -0
  6. package/package.json +55 -13
  7. package/sdk/browser/package.json +1 -1
  8. package/sdk/go/cbor.go +551 -0
  9. package/sdk/go/cbor_test.go +232 -0
  10. package/sdk/go/encryption.go +280 -0
  11. package/sdk/go/encryption_test.go +318 -0
  12. package/sdk/go/go.mod +5 -1
  13. package/sdk/go/go.sum +4 -0
  14. package/sdk/go/replay.go +121 -0
  15. package/sdk/go/replay_test.go +149 -0
  16. package/sdk/pq/package-lock.json +1358 -0
  17. package/sdk/pq/package.json +42 -0
  18. package/sdk/pq/src/index.test.ts +143 -0
  19. package/sdk/pq/src/index.ts +166 -0
  20. package/sdk/pq/tsconfig.json +14 -0
  21. package/sdk/pq/vitest.config.ts +7 -0
  22. package/sdk/python/protocol_7h3/encryption.py +252 -0
  23. package/sdk/python/protocol_7h3/pq.py +244 -0
  24. package/sdk/python/protocol_7h3/replay.py +98 -0
  25. package/sdk/python/pyproject.toml +1 -1
  26. package/sdk/python/tests/test_encryption.py +206 -0
  27. package/sdk/rust/Cargo.lock +1 -1
  28. package/sdk/rust/Cargo.toml +1 -1
  29. package/sdk/threshold/index.d.ts +68 -0
  30. package/sdk/threshold/index.d.ts.map +1 -0
  31. package/sdk/threshold/index.js +254 -0
  32. package/sdk/threshold/package-lock.json +1361 -0
  33. package/sdk/threshold/package.json +39 -0
  34. package/sdk/threshold/src/index.d.ts +68 -0
  35. package/sdk/threshold/src/index.d.ts.map +1 -0
  36. package/sdk/threshold/src/index.js +254 -0
  37. package/sdk/threshold/src/index.test.ts +238 -0
  38. package/sdk/threshold/src/index.ts +355 -0
  39. package/sdk/threshold/tsconfig.json +19 -0
  40. package/sdk/threshold/vitest.config.ts +12 -0
  41. package/src/capability.test.ts +504 -0
  42. package/src/capability.ts +380 -0
  43. package/src/cborCodec.test.ts +263 -0
  44. package/src/cborCodec.ts +339 -0
  45. package/src/encryption.test.ts +206 -0
  46. package/src/encryption.ts +245 -0
  47. package/src/envelopeCbor.ts +140 -0
  48. package/src/gateway.ts +75 -0
  49. package/src/httpBinding.ts +37 -11
  50. package/src/index.ts +7 -0
  51. package/src/otel.ts +136 -0
  52. package/src/protocol.d.ts +67 -0
  53. package/src/protocol.d.ts.map +1 -0
  54. package/src/protocol.js +294 -0
  55. package/src/protocol.ts +1 -0
  56. package/src/replayStores.test.ts +133 -1
  57. package/src/replayStores.ts +136 -3
  58. package/src/stream.test.ts +254 -0
  59. package/src/stream.ts +417 -0
  60. package/src/telemetry.test.ts +251 -0
  61. package/src/telemetry.ts +299 -0
  62. package/src/wsBinding.ts +100 -0
  63. package/vitest.config.ts +11 -0
package/README.md CHANGED
@@ -1,333 +1,1327 @@
1
1
  <div align="center">
2
- <img src="./docs/assets/banner.png" alt="@7h3/protocolAIP: Sign every message. Reject every replay." width="100%">
2
+ <img src="./docs/assets/banner-github.png" alt="7h3 Protocol — Sign every message. Reject every replay." width="100%">
3
3
 
4
4
  <br/><br/>
5
5
 
6
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 mcp](https://img.shields.io/npm/v/@7h3/protocol-mcp?style=flat-square&color=6366f1&logo=npm&logoColor=white&label=%407h3%2Fprotocol-mcp)](https://www.npmjs.com/package/@7h3/protocol-mcp)
8
- [![PyPI](https://img.shields.io/pypi/v/aip7h3?style=flat-square&color=818cf8&logo=python&logoColor=white)](https://pypi.org/project/aip7h3/)
9
- [![Crates.io](https://img.shields.io/crates/v/aip7h3?style=flat-square&color=a5b4fc&logo=rust&logoColor=white)](https://crates.io/crates/aip7h3)
10
- [![Tests](https://img.shields.io/badge/tests-131%20passing-4ade80?style=flat-square&logo=vitest&logoColor=white)](https://github.com/IceMasterT/7h3-protocol-aip/tree/main/src)
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
11
  [![Zero deps](https://img.shields.io/badge/runtime%20deps-0-a5b4fc?style=flat-square)](./package.json)
12
- [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white)](./tsconfig.json)
12
+ [![Wire](https://img.shields.io/badge/wire-7h3%2F0.1-818cf8?style=flat-square)](./docs/VERSIONING_POLICY.md)
13
13
  [![License](https://img.shields.io/badge/license-MIT-94a3b8?style=flat-square)](./LICENSE)
14
- [![Wire](https://img.shields.io/badge/wire-aip%2F0.1-818cf8?style=flat-square)](./docs/VERSIONING_POLICY.md)
15
14
 
16
15
  <br/>
17
16
 
18
- **The signing-and-replay layer your agent protocol forgot.**
17
+ **Cryptographic signing and replay protection for AI agent messages. One envelope. Every transport.**
19
18
 
20
19
  <br/>
21
20
  </div>
22
21
 
23
22
  ---
24
23
 
25
- ## Why it exists
24
+ ## The Problem
26
25
 
27
- The dominant agent protocols ship messages unsigned:
26
+ AI agent systems are moving fast, and the protocols underpinning them were not built with message-level security in mind.
28
27
 
29
- - **MCP** uses plain JSON-RPC 2.0 no signatures, no replay protection. Parameters can be altered in transit; valid messages can be replayed indefinitely.
30
- - **A2A** signs *Agent Cards* for domain identity — but not the per-message task traffic.
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.
31
29
 
32
- AIP fills exactly that gap. It is a hardening envelope you put *around* MCP or A2A traffic not a competitor to them. Every message gets signed, TTL-bounded, and replay-checked before it reaches your handler.
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.
33
31
 
34
- Use it when agents trigger real side effects (writes, payments, tool calls) and you need tamper-evidence, replay-safety, and auditability.
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
35
 
36
36
  ---
37
37
 
38
- ## Guarantees
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).
39
41
 
40
- | Property | Mechanism |
42
+ The protocol provides four security primitives, all in one structure:
43
+
44
+ | Primitive | Mechanism |
41
45
  |---|---|
42
- | **Authentic** | HMAC-SHA256 (HS256) or Ed25519 over a canonical payload — real WebCrypto, no hand-rolled crypto |
43
- | **Deterministic** | Fixed-key-order canonicalization; signatures match byte-for-byte across TS / Python / Rust |
44
- | **Replay-resistant** | `(sender, messageId, nonce)` uniqueness window + TTL / clock-skew enforcement |
45
- | **Polyglot** | Shared conformance fixture set (`conformance/aip_v0_1.json`) proves parity across all three runtimes |
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.
46
52
 
47
53
  ---
48
54
 
49
- ## Install
55
+ ## How It Works
50
56
 
51
- ```bash
52
- npm install @7h3/protocol
53
- ```
57
+ ### Ed25519 Signing
54
58
 
55
- Python and Rust SDKs live under `sdk/python` (`from aip7h3 import …`) and `sdk/rust` (`use aip7h3::…`).
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.
56
60
 
57
- ### MCP server (for Claude Code / Claude Desktop)
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.
58
62
 
59
- The companion `@7h3/protocol-mcp` package installs five tools into your AI assistant for generating secrets, keypairs, and boilerplate.
63
+ ### Canonical Serialization
60
64
 
61
- ```bash
62
- # Claude Code
63
- claude mcp add aip -- npx @7h3/protocol-mcp
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"}}
64
82
  ```
65
83
 
66
- ```json
67
- // Claude Desktop — claude_desktop_config.json
68
- {
69
- "mcpServers": {
70
- "aip": { "command": "npx", "args": ["@7h3/protocol-mcp"] }
71
- }
72
- }
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)
73
114
  ```
74
115
 
75
- | Tool | What it does |
116
+ ---
117
+
118
+ ## 🔒 Security Guarantees
119
+
120
+ | Attack | Defense |
76
121
  |---|---|
77
- | `aip_generate_secret` | 32-byte HMAC secret `AIP_SECRET` |
78
- | `aip_generate_keypair` | Ed25519 keypair env vars |
79
- | `aip_wrap_mcp_server` | Ready-to-paste boilerplate for your MCP server |
80
- | `aip_sign` | Sign a test envelope (debugging / fixture generation) |
81
- | `aip_verify` | Verify an envelope's signature and shape |
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 |
82
129
 
83
130
  ---
84
131
 
85
- ## Quick start
132
+ ## 🤖 Works with Claude (MCP)
86
133
 
87
- ### HMAC (shared secret simplest path)
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:**
88
153
 
89
154
  ```ts
90
- import {
91
- createEnvelope, signEnvelopeHmac, verifyEnvelopeHmac, validateEnvelope,
92
- } from '@7h3/protocol'
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
93
188
 
94
- const secret = 'shared-secret'
95
- const envelope = await signEnvelopeHmac(
96
- createEnvelope({ sender: 'planner', recipient: 'worker', intent: 'TASK', content: 'do-the-thing' }),
97
- secret,
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',
98
209
  )
99
210
 
100
- const diagnostics = validateEnvelope(envelope) // shape / TTL / version checks
101
- const ok = await verifyEnvelopeHmac(envelope, secret) // tamper + auth check
102
- // replay-check downstream via your transport's replay cache
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
+ })
103
320
  ```
104
321
 
105
- ### Ed25519 (asymmetric — production recommendation)
322
+ ---
323
+
324
+ ## 🚀 Installation
106
325
 
107
- > **Why Ed25519?** HMAC is a shared secret — any peer that can verify can also forge. Ed25519 is asymmetric: you sign with a private key, peers verify with your public key only. Compromising a peer does not compromise your signing key.
326
+ ### TypeScript / Node.js
327
+
328
+ ```bash
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
108
399
 
109
400
  ```ts
110
401
  import {
111
- generateEd25519KeypairBase64Url, createEnvelope,
112
- signEnvelopeEd25519, verifyEnvelopeEd25519,
402
+ generateEd25519KeypairBase64Url,
403
+ createEnvelope,
404
+ signEnvelopeEd25519,
405
+ verifyEnvelopeEd25519,
113
406
  } from '@7h3/protocol'
114
407
 
115
408
  const { privateKey, publicKey } = await generateEd25519KeypairBase64Url()
409
+
116
410
  const envelope = await signEnvelopeEd25519(
117
- createEnvelope({ sender: 'planner', recipient: 'worker', intent: 'TASK', content: 'do-the-thing' }),
411
+ createEnvelope({ sender: 'agent.alpha', intent: 'TASK', content: 'hello' }),
118
412
  privateKey, 'k1',
119
413
  )
414
+
120
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
121
461
  ```
122
462
 
123
463
  ---
124
464
 
125
- ## MCP hardening wrapper
465
+ ## HTTP Middleware
466
+
467
+ Drop-in middleware for common frameworks.
126
468
 
127
- Wrap any existing MCP server or client — handler signature does not change:
469
+ ### Express.js (TypeScript)
128
470
 
129
471
  ```ts
130
- import { wrapMcpServer, wrapMcpClient, signEnvelopeEd25519 } from '@7h3/protocol'
472
+ import express from 'express'
473
+ import { createVerifyMiddleware } from '@7h3/protocol'
131
474
 
132
- // Server side
133
- const secureServer = wrapMcpServer(myMcpHandler, {
134
- selfAgentId: 'my-server',
135
- sign: (e) => signEnvelopeEd25519(e, serverPrivateKey, 'k1'),
136
- })
475
+ const app = express()
137
476
 
138
- // Client side
139
- const { send } = wrapMcpClient({
140
- selfAgentId: 'my-client',
141
- peerAgentId: 'my-server',
142
- sign: (e) => signEnvelopeEd25519(e, clientPrivateKey, 'k1'),
143
- receive: { signatureResolver: async ({ keyId }) => ({ alg: 'ED25519', publicKey: serverPublicKey }) },
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 })
144
486
  })
145
- const response = await send({ jsonrpc: '2.0', id: 1, method: 'tools/list' }, fetch)
146
487
  ```
147
488
 
148
- The wrapper enforces four bindings beyond signature verification:
489
+ ### Flask (Python)
149
490
 
150
- | Binding | Attack defeated |
151
- |---|---|
152
- | **Recipient** | Server rejects envelopes not addressed to `selfAgentId` — cross-server relay |
153
- | **Sender** | Client accepts responses only from `peerAgentId` — response spoofing |
154
- | **Correlation** | Client enforces `correlationId === request messageId` — response substitution |
155
- | **Replay** | `InMemoryReplayCache` injected by default — replay of prior requests |
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
+ ```
156
507
 
157
- Demo: `npm run aip:mcp:wrap`
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
+ ```
158
525
 
159
526
  ---
160
527
 
161
- ## Wire formats
528
+ ## 🛡️ API Gateway
162
529
 
163
- Three formats choose by context:
530
+ The zero-code path: drop the 7h3 gateway in front of any existing service. No application changes required.
164
531
 
165
- | Format | Use case |
166
- |---|---|
167
- | `json` | Human-readable, debug-friendly |
168
- | `compact` | Minified JSON — smaller over HTTP |
169
- | `binary` | MessagePack (magic `AIPB`) — highest throughput, lowest parse overhead |
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:
170
603
 
171
604
  ```ts
172
- import { encodeEnvelope, decodeEnvelope } from '@7h3/protocol'
605
+ import { createGateway } from '@7h3/protocol'
606
+
607
+ const gateway = createGateway(config)
173
608
 
174
- const wire = encodeEnvelope(envelope, 'binary') // Uint8Array
175
- const back = decodeEnvelope(wire) // ProtocolEnvelope
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)
176
614
  ```
177
615
 
178
616
  ---
179
617
 
180
- ## Distributed replay store (Redis)
618
+ ## Per-Route Policies
619
+
620
+ Each route can enforce independent authentication requirements, sender allowlists, and rate limits.
181
621
 
182
- Production deployments need a shared replay store across agent instances:
622
+ ### TypeScript
183
623
 
184
624
  ```ts
185
- import { createRedisReplayStore, DistributedReplayCache } from '@7h3/protocol'
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
667
+ ```
668
+
669
+ Path matching supports glob patterns: `**` (any path segment, recursive), `*` (single segment), `?` (single character).
186
670
 
187
- const store = createRedisReplayStore(redisClient, {
188
- errorBehavior: 'fallback', // degrade to local store on Redis outage — never silent
189
- onDegraded: (err) => telemetry.error('replay-store-degraded', err),
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
679
+
680
+ ```ts
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
190
686
  })
191
- const replayCache = new DistributedReplayCache(store)
192
687
 
193
- // Pass replayCache into receiveEnvelope or wrapMcpServer/wrapMcpClient
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
+ }
194
693
  ```
195
694
 
196
- - Atomic `SET NX PX` reserve no double-processing under concurrent writes
197
- - `reserveMany` batch pipeline — low overhead for high-volume handlers
198
- - `errorBehavior: 'fallback' | 'reject' | 'allow'` — operator controls degradation posture
199
- - See `docs/DISTRIBUTED_REPLAY.md`
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
701
+ ```
200
702
 
201
703
  ---
202
704
 
203
- ## Fleet-wide key revocation
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.
204
708
 
205
709
  ```ts
206
- import { createRedisRevocationStore, withRevocationCheck } from '@7h3/protocol'
710
+ import { signResponse, verifyResponse } from '@7h3/protocol'
207
711
 
208
- const revocationStore = createRedisRevocationStore(redisClient) // fail-closed default
209
- const secureResolver = withRevocationCheck(mySignatureResolver, revocationStore)
210
- // Revoked key → resolver returns undefined → verification fails
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}`)
211
729
  ```
212
730
 
213
- - Cached reads (5 s TTL by default) low overhead on the verify hot path
214
- - Fail-closed default: Redis outage → reject, not allow
215
- - See `docs/KEY_REVOCATION.md`
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.
216
732
 
217
733
  ---
218
734
 
219
- ## Transport adapters
735
+ ## 📋 Tamper-Evident Audit Log
220
736
 
221
- Zero new runtime dependencies only Node built-ins and global `fetch`:
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.
222
738
 
223
739
  ```ts
224
- import { serveMcpOverStdio, createStdioMcpClient } from '@7h3/protocol'
225
- import { createHttpMcpHandler, createHttpMcpClient } from '@7h3/protocol'
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
+ }
226
762
  ```
227
763
 
228
- | Adapter | Notes |
229
- |---|---|
230
- | `serveMcpOverStdio` + `createStdioMcpClient` | Newline-delimited; in-order sequential chain prevents response interleaving |
231
- | `createHttpMcpHandler` + `createHttpMcpClient` | `node:http` handler + `fetch` client; supports `binary` wire format |
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
+ ```
232
909
 
233
910
  ---
234
911
 
235
- ## Framework adapters
912
+ ## Queue Usage
236
913
 
237
- LangChain, LlamaIndex, and JSON-RPC bridge adapters wrap `AipAgentAdapter` to translate between AIP envelopes and framework-native message types:
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
238
955
 
239
956
  ```ts
240
- import { LangChainAipAdapter, LlamaIndexAipAdapter, JsonRpcBridge } from '@7h3/protocol'
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
+ })
241
992
  ```
242
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
+
243
1073
  ---
244
1074
 
245
- ## Policy and telemetry
1075
+ ## Browser / Edge SDK
246
1076
 
247
- Runtime policy controls transport behavior, retry, rate limits, and safety invariants loaded from `AI_RUNTIME_POLICY.yaml` or inline:
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.
248
1078
 
249
1079
  ```ts
250
- import { loadRuntimePolicy, validateRuntimePolicy, PolicyEnforcer } from '@7h3/protocol'
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
+ })
251
1104
 
252
- const policy = await loadRuntimePolicy({ path: './AI_RUNTIME_POLICY.yaml' })
253
- const enforcer = new PolicyEnforcer(policy)
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
+ }
254
1115
  ```
255
1116
 
256
- See `docs/TELEMETRY.md`, `docs/AI_DECISION_CARD.md`, `docs/OPERATORS.md`.
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
+ ```
257
1133
 
258
1134
  ---
259
1135
 
260
- ## Polyglot parity
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:
261
1139
 
262
- All three SDKs are driven by the same conformance fixture set at `conformance/aip_v0_1.json`. Signatures verified against known vectors in all three runtimes:
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:
263
1153
 
264
1154
  ```bash
265
- npm test # TypeScript (131 tests / 23 files)
266
- npm run conformance:python # Python unittest
267
- npm run conformance:rust # Rust cargo test (7 tests)
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
268
1166
  ```
269
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
+
270
1170
  ---
271
1171
 
272
- ## Status
1172
+ ## CLI Reference
273
1173
 
274
- **Version: 0.1.2** · Wire protocol: `aip/0.1`
1174
+ ```bash
1175
+ npm install -g @7h3/protocol
1176
+ ```
275
1177
 
276
- | What | Status |
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 |
277
1200
  |---|---|
278
- | Core test suite | 131 tests / 23 files — all green |
279
- | Cryptography | Real WebCrypto — HMAC-SHA256 + Ed25519 (no hand-rolled crypto) |
280
- | Deterministic canonicalization | Fixed key order; byte-identical across runtimes |
281
- | TS/Python/Rust parity | Shared conformance fixtures; all pass |
282
- | Distributed replay (Redis) | Atomic `SET NX PX`; batch pipeline; graceful degradation |
283
- | Fleet-wide revocation (Redis) | Fail-closed; cached reads; stale-serve during outage |
284
- | MCP hardening wrapper | 4 bindings; all independently tested |
285
- | Property-based fuzz tests | 8 properties via fast-check (wire decoder, canonicalization, replay) |
286
- | Live-Redis integration test | Auto-skips if no server present — no false passes |
287
- | Formal fuzz campaign | TypeScript mutation harnesses run clean (50k/20k rounds, 0 crashes); Rust targets built and run (4.9M iterations, no panics in `canonicalize_envelope`/`decode_envelope`) — see [`docs/FUZZ_CAMPAIGN.md`](./docs/FUZZ_CAMPAIGN.md) |
288
- | Independent security audit | ⚠️ Not yet performed by an external reviewer — internal AI-assisted review completed 2026-06-05, 2 bugs found and fixed (see [`docs/SECURITY_REVIEW_2026-06-05.md`](./docs/SECURITY_REVIEW_2026-06-05.md)); cryptographic primitives are standard WebCrypto; parsing/replay/canonicalization logic remains unaudited by a qualified third party |
289
- | Python Ed25519 | Pure-Python fallback — no external packages required; tries `cryptography` `PyNaCl` → pure Python in order |
290
- | Rust crates.io publish | ✅ Metadata complete; `cargo publish --dry-run` passes — publish with `cargo publish` when ready |
291
- | Redis HA | ✅ Sentinel, Cluster, and Upstash adapter patterns documented in [`docs/DISTRIBUTED_REPLAY.md`](./docs/DISTRIBUTED_REPLAY.md) |
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` |
292
1213
 
293
1214
  ---
294
1215
 
295
- ## Docs
1216
+ ## Docker Reference
296
1217
 
297
- | Document | Contents |
298
- |---|---|
299
- | [`docs/THREAT_MODEL.md`](./docs/THREAT_MODEL.md) | Full threat coverage matrix |
300
- | [`docs/MCP_WRAPPER.md`](./docs/MCP_WRAPPER.md) | MCP wrapper usage, transport examples, HMAC vs Ed25519 comparison |
301
- | [`docs/DISTRIBUTED_REPLAY.md`](./docs/DISTRIBUTED_REPLAY.md) | Redis store setup, `errorBehavior` table, ops guidance |
302
- | [`docs/KEY_REVOCATION.md`](./docs/KEY_REVOCATION.md) | Revocation store setup, cache TTL tuning |
303
- | [`docs/KEY_MANAGEMENT_POLICY.md`](./docs/KEY_MANAGEMENT_POLICY.md) | Key lifecycle and rotation policy |
304
- | [`docs/CLOCK_SKEW_POLICY.md`](./docs/CLOCK_SKEW_POLICY.md) | Clock sync requirements |
305
- | [`docs/VERSIONING_POLICY.md`](./docs/VERSIONING_POLICY.md) | Wire freeze guarantees, semver policy |
306
- | [`docs/MIGRATION_GUIDE.md`](./docs/MIGRATION_GUIDE.md) | Breaking-change upgrade paths |
307
- | [`docs/OPERATORS.md`](./docs/OPERATORS.md) | Deployment and operations reference |
308
- | [`docs/TELEMETRY.md`](./docs/TELEMETRY.md) | Telemetry hooks and observability |
309
- | [`docs/SECURITY_REVIEW_2026-06-05.md`](./docs/SECURITY_REVIEW_2026-06-05.md) | AI-assisted internal security review — findings, fixes, positive findings |
310
- | [`docs/PROJECT_EXAMINATION_2026-05-31.md`](./docs/PROJECT_EXAMINATION_2026-05-31.md) | Independent examination — verified vs asserted |
311
- | [`CHANGELOG.md`](./CHANGELOG.md) | Full version history |
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
+ ```
312
1282
 
313
1283
  ---
314
1284
 
315
- ## Security
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
316
1297
 
317
- Report vulnerabilities via the coordinated disclosure process in [`SECURITY.md`](./SECURITY.md). **Do not open a public issue for security findings.** 48-hour acknowledgement SLA; 14-day critical patch SLA.
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
318
1309
 
319
- The cryptographic primitives are standard (WebCrypto Ed25519 / HMAC-SHA256). The envelope parsing, canonicalization, and replay-cache logic have not been formally audited. No independent security audit has been performed. Treat accordingly in high-stakes deployments.
1310
+ # Rust migration
1311
+ cargo remove aip7h3
1312
+ cargo add protocol-7h3
1313
+ ```
320
1314
 
321
1315
  ---
322
1316
 
323
1317
  ## Contributing
324
1318
 
325
- See [`CONTRIBUTING.md`](./CONTRIBUTING.md) test commands, wire-freeze policy, conformance fixture update requirement, and PR workflow.
1319
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, the test suite structure, how to add conformance vectors, and the pull request process.
326
1320
 
327
- ## Governance
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.
328
1322
 
329
- See [`GOVERNANCE.md`](./GOVERNANCE.md) — single-maintainer stage, decision process, co-maintainership path (LF Minimum Viable Governance style).
1323
+ ---
330
1324
 
331
1325
  ## License
332
1326
 
333
- MIT
1327
+ MIT — see [LICENSE](./LICENSE).