@muretai/agent-entry 1.3.0 → 1.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.
@@ -0,0 +1,637 @@
1
+ {
2
+ "note": "Golden wire vectors for an Agent Entry implementation. Reproduce every `canonical`, `did`, `signingPayload` and `bindingPayload` field BYTE-FOR-BYTE, and REFUSE every case under `reject`. A drift does not throw: it silently makes your signatures unverifiable by everyone else, and an implementation that refuses nothing passes every positive vector in this file. Header and URL values inside a signed vector are OPAQUE TEST DATA covered by that vector's signature: reproduce them verbatim. Substituting your own host there does not make the case yours, it makes it fail.",
3
+ "protocolVersion": "0.2",
4
+ "canonicalSpec": "python json.dumps(sort_keys=True, separators=(',',':'), ensure_ascii=False).encode('utf-8')",
5
+ "timestampNote": "Timestamps in signed payloads are INTEGER epoch seconds — this is the CONTRACT, not a convenience of these vectors. It used to be the latter: core minted floats and the vectors side-stepped them, which meant a client could pass every vector here and still be unable to verify a real message from a Python node. `crypto.signing_payload` does not cast, so the type on the wire IS the type in the signed bytes, and a float there is bytes no other language can reproduce (Python's shortest-round-trip repr). Since 2026-07-17 core mints ints (shared/protocol.Message, cardpub publishers). Clients: send ints; keep VERIFYING whatever type arrives — an older node still sends floats and its signature is over those exact bytes. Never coerce inside a payload builder.",
6
+ "canonical": [
7
+ {
8
+ "name": "sorted-keys",
9
+ "payload": {
10
+ "b": 1,
11
+ "a": "x"
12
+ },
13
+ "why": "sort_keys=True: insertion order must not survive into the bytes",
14
+ "canonical": "{\"a\":\"x\",\"b\":1}"
15
+ },
16
+ {
17
+ "name": "no-whitespace",
18
+ "payload": {
19
+ "a": 1,
20
+ "b": 2
21
+ },
22
+ "why": "separators=(',',':'): a default json.dumps would add spaces here",
23
+ "canonical": "{\"a\":1,\"b\":2}"
24
+ },
25
+ {
26
+ "name": "non-ascii-literal",
27
+ "payload": {
28
+ "text": "群れたい"
29
+ },
30
+ "why": "ensure_ascii=False: non-ASCII stays literal UTF-8, NOT \\uXXXX. This is the one most clients get wrong, and message text is routinely non-ASCII",
31
+ "canonical": "{\"text\":\"群れたい\"}"
32
+ },
33
+ {
34
+ "name": "escape-shorthands",
35
+ "payload": {
36
+ "k": "\"\\\b\f\n\r\t"
37
+ },
38
+ "why": "Python's ESCAPE_DCT: exactly these seven shorthands",
39
+ "canonical": "{\"k\":\"\\\"\\\\\\b\\f\\n\\r\\t\"}"
40
+ },
41
+ {
42
+ "name": "control-chars",
43
+ "payload": {
44
+ "k": "\u0000\u0001\u001f"
45
+ },
46
+ "why": "other <0x20 become lowercase \\u00xx",
47
+ "canonical": "{\"k\":\"\\u0000\\u0001\\u001f\"}"
48
+ },
49
+ {
50
+ "name": "slash-and-del-unescaped",
51
+ "payload": {
52
+ "k": "a/b"
53
+ },
54
+ "why": "Python escapes NEITHER '/' nor DEL — many JSON writers escape both",
55
+ "canonical": "{\"k\":\"a/b\"}"
56
+ },
57
+ {
58
+ "name": "null-value",
59
+ "payload": {
60
+ "contextId": null
61
+ },
62
+ "why": "None -> null (contextId is genuinely nullable on the wire)",
63
+ "canonical": "{\"contextId\":null}"
64
+ },
65
+ {
66
+ "name": "empty-string",
67
+ "payload": {
68
+ "text": ""
69
+ },
70
+ "why": "empty string is not null",
71
+ "canonical": "{\"text\":\"\"}"
72
+ },
73
+ {
74
+ "name": "surrogate-pair",
75
+ "payload": {
76
+ "text": "🐦"
77
+ },
78
+ "why": "an astral char is one code point in Python but two UTF-16 units elsewhere",
79
+ "canonical": "{\"text\":\"🐦\"}"
80
+ },
81
+ {
82
+ "name": "combining-marks",
83
+ "payload": {
84
+ "text": "が"
85
+ },
86
+ "why": "NOT normalized: canonical JSON must never NFC/NFD the input",
87
+ "canonical": "{\"text\":\"が\"}"
88
+ },
89
+ {
90
+ "name": "key-ordering-unicode",
91
+ "payload": {
92
+ "z": 1,
93
+ "a": 2,
94
+ "群": 3,
95
+ "A": 4
96
+ },
97
+ "why": "keys sort by CODE POINT (Python str order), not by UTF-16 unit or locale",
98
+ "canonical": "{\"A\":4,\"a\":2,\"z\":1,\"群\":3}"
99
+ },
100
+ {
101
+ "name": "negative-and-zero",
102
+ "payload": {
103
+ "a": 0,
104
+ "b": -1
105
+ },
106
+ "why": "integers render bare, no + or leading zeros",
107
+ "canonical": "{\"a\":0,\"b\":-1}"
108
+ },
109
+ {
110
+ "name": "large-int-within-double",
111
+ "payload": {
112
+ "a": 9007199254740991
113
+ },
114
+ "why": "2**53-1, the largest integer a JavaScript Number holds exactly. Signed integers must stay inside +/-(2**53-1) — see numberHazards/int-beyond-2exp53",
115
+ "canonical": "{\"a\":9007199254740991}"
116
+ },
117
+ {
118
+ "name": "fractional-float-agrees",
119
+ "payload": {
120
+ "a": 0.1,
121
+ "b": 1785937682.845164
122
+ },
123
+ "why": "the CONTRAST case for numberHazards: ordinary FRACTIONAL floats do agree byte-for-byte across languages. That is exactly why float timestamps looked fine for a year — they are reproducible until one lands on a whole second",
124
+ "canonical": "{\"a\":0.1,\"b\":1785937682.845164}"
125
+ }
126
+ ],
127
+ "numberHazards": [
128
+ {
129
+ "name": "integral-float",
130
+ "payload": {
131
+ "a": 1.0
132
+ },
133
+ "pythonCanonical": "{\"a\":1.0}",
134
+ "javascriptWouldWrite": "{\"a\":1}",
135
+ "signMustNotEmit": true,
136
+ "verifyNeedsPythonRepr": true,
137
+ "why": "Python writes 1.0, JavaScript writes 1 — and JSON.parse('1.0') is irrecoverably 1, so a client cannot even reconstruct what was signed. `credentialSubject.trustLevel` was this value on every introduction minted by `operator_cli.introduce` and a Room's `/introduce`; fixed 2026-08-07 by minting integer basis points (`trustLevelBp`)."
138
+ },
139
+ {
140
+ "name": "integral-float-zero",
141
+ "payload": {
142
+ "a": 0.0
143
+ },
144
+ "pythonCanonical": "{\"a\":0.0}",
145
+ "javascriptWouldWrite": "{\"a\":0}",
146
+ "signMustNotEmit": true,
147
+ "verifyNeedsPythonRepr": true,
148
+ "why": "`keystate.notBefore` defaulted to 0.0 and was therefore unverifiable outside Python 100% of the time; fixed 2026-08-07."
149
+ },
150
+ {
151
+ "name": "integral-float-negative",
152
+ "payload": {
153
+ "a": -1.0
154
+ },
155
+ "pythonCanonical": "{\"a\":-1.0}",
156
+ "javascriptWouldWrite": "{\"a\":-1}",
157
+ "signMustNotEmit": true,
158
+ "verifyNeedsPythonRepr": true,
159
+ "why": "the sign does not save it"
160
+ },
161
+ {
162
+ "name": "negative-zero",
163
+ "payload": {
164
+ "a": -0.0
165
+ },
166
+ "pythonCanonical": "{\"a\":-0.0}",
167
+ "javascriptWouldWrite": "{\"a\":0}",
168
+ "signMustNotEmit": true,
169
+ "verifyNeedsPythonRepr": true,
170
+ "why": "Python keeps the sign, JavaScript does not. Matrix bans -0 from its canonical JSON outright, for this reason."
171
+ },
172
+ {
173
+ "name": "exponent-small",
174
+ "payload": {
175
+ "a": 1e-07
176
+ },
177
+ "pythonCanonical": "{\"a\":1e-07}",
178
+ "javascriptWouldWrite": "{\"a\":1e-7}",
179
+ "signMustNotEmit": true,
180
+ "verifyNeedsPythonRepr": true,
181
+ "why": "Python zero-pads and always signs the exponent (1e-07); JavaScript does neither"
182
+ },
183
+ {
184
+ "name": "exponent-threshold",
185
+ "payload": {
186
+ "a": 1e+16
187
+ },
188
+ "pythonCanonical": "{\"a\":1e+16}",
189
+ "javascriptWouldWrite": "{\"a\":10000000000000000}",
190
+ "signMustNotEmit": true,
191
+ "verifyNeedsPythonRepr": true,
192
+ "why": "Python switches to exponent notation here (1e+16); JavaScript does not switch until 1e21. The thresholds differ, so agreement is a coin flip."
193
+ },
194
+ {
195
+ "name": "int-beyond-2exp53",
196
+ "payload": {
197
+ "a": 9007199254740993
198
+ },
199
+ "pythonCanonical": "{\"a\":9007199254740993}",
200
+ "javascriptWouldWrite": "{\"a\":9007199254740992}",
201
+ "signMustNotEmit": true,
202
+ "verifyNeedsPythonRepr": true,
203
+ "why": "NOT a formatting mismatch — SILENT DATA CORRUPTION. Python has arbitrary-precision integers; a JavaScript Number rounds. Keep signed integers inside +/-(2**53-1), the bound Matrix and RFC 8785 both set."
204
+ },
205
+ {
206
+ "name": "nested-float",
207
+ "payload": {
208
+ "credentialSubject": {
209
+ "trustLevel": 1.0
210
+ }
211
+ },
212
+ "pythonCanonical": "{\"credentialSubject\":{\"trustLevel\":1.0}}",
213
+ "javascriptWouldWrite": "{\"credentialSubject\":{\"trustLevel\":1}}",
214
+ "signMustNotEmit": true,
215
+ "verifyNeedsPythonRepr": true,
216
+ "why": "the real shape of the bug: two levels down inside an introduction credential, where nothing at the top level looked wrong"
217
+ }
218
+ ],
219
+ "numberHazardNote": "These are NOT in `canonical`, and that distinction is the point. Every case here is a value whose canonical bytes DIFFER between Python and JavaScript, so requiring a client to reproduce them would be requiring it to re-implement CPython's float repr — the opposite of the contract. `signMustNotEmit` means exactly what it says: a signer that emits one of these produces bytes only Python can verify. `verifyNeedsPythonRepr` means a client CAN still meet a lesser duty — recognise the shape and report honestly that it cannot verify that artifact, rather than reporting a bad signature. This section exists because `trustLevel: 1.0` shipped for a year while all twelve original `canonical` cases stayed green: none of them contained a float, so nothing caught it, and the failure surfaced as a signature error pointing at the client's Ed25519 code. Same rule as timestampNote, stated for every number: signed payloads carry INTEGERS inside +/-(2**53-1). Never coerce inside a payload builder; never coerce on the verify path either.",
220
+ "did": [
221
+ {
222
+ "curve": "ed25519",
223
+ "multicodec": "ed01",
224
+ "publicHex": "0000000000000000000000000000000000000000000000000000000000000000",
225
+ "did": "did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP"
226
+ },
227
+ {
228
+ "curve": "ed25519",
229
+ "multicodec": "ed01",
230
+ "publicHex": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
231
+ "did": "did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e"
232
+ },
233
+ {
234
+ "curve": "ed25519",
235
+ "multicodec": "ed01",
236
+ "publicHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
237
+ "did": "did:key:z6MkeTGwHmLmuCmgg4ABYhzWVh6ZX7hTwWt8gguAretUfc9c"
238
+ },
239
+ {
240
+ "curve": "ed25519",
241
+ "multicodec": "ed01",
242
+ "publicHex": "094119b513c9c595718eaba5a497a8e0ea1dd19bd5e043dad8b5f1ee9a893b64",
243
+ "did": "did:key:z6Mkf5PHmUf14MDpPoHgRVVNDEFSZ62ecAHhjSJGicWcjrno"
244
+ },
245
+ {
246
+ "curve": "p256",
247
+ "multicodec": "8024",
248
+ "publicHex": "020000000000000000000000000000000000000000000000000000000000000000",
249
+ "did": "did:key:zDnaeQRy3dcKsKa1zmKtVKsTy3m2HYoQnFnfKuxD6HfSTQgYf"
250
+ },
251
+ {
252
+ "curve": "p256",
253
+ "multicodec": "8024",
254
+ "publicHex": "02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
255
+ "did": "did:key:zDnaehfHR8Q5U7ckmLQfuZ3eGEypooJ46zzjRQ1AR9asDvdnv"
256
+ },
257
+ {
258
+ "curve": "p256",
259
+ "multicodec": "8024",
260
+ "publicHex": "02000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
261
+ "did": "did:key:zDnaeQRywL8RCtEJDKtCyC1VdhMZrFLqPnRJ6udCLK3MvA4ut"
262
+ },
263
+ {
264
+ "curve": "p256",
265
+ "multicodec": "8024",
266
+ "publicHex": "030000000000000000000000000000000000000000000000000000000000000000",
267
+ "did": "did:key:zDnaehfHR8Q5U7ckmLQfuZ3eGEypooJ46zzjRQ1AR9asDvdnw"
268
+ },
269
+ {
270
+ "curve": "p256",
271
+ "multicodec": "8024",
272
+ "publicHex": "03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
273
+ "did": "did:key:zDnaeztbndBq4ufVXuVTKnDpZSCdL3nhRkCoWt47k1WHzSb3C"
274
+ },
275
+ {
276
+ "curve": "p256",
277
+ "multicodec": "8024",
278
+ "publicHex": "03000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
279
+ "did": "did:key:zDnaehfJJpvAogH2ytxzPRBfvtaNNVqUiXdNCPg9fAxngg2AA"
280
+ }
281
+ ],
282
+ "envelope": [
283
+ {
284
+ "name": "plain",
285
+ "from": "did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP",
286
+ "to": "did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e",
287
+ "messageId": "m1",
288
+ "contextId": "c1",
289
+ "timestamp": 1752451200,
290
+ "text": "hi",
291
+ "signingPayload": "{\"contextId\":\"c1\",\"from\":\"did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP\",\"messageId\":\"m1\",\"text\":\"hi\",\"timestamp\":1752451200,\"to\":\"did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e\"}"
292
+ },
293
+ {
294
+ "name": "null-context",
295
+ "from": "did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP",
296
+ "to": "did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e",
297
+ "messageId": "m1",
298
+ "contextId": null,
299
+ "timestamp": 1752451200,
300
+ "text": "hi",
301
+ "signingPayload": "{\"contextId\":null,\"from\":\"did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP\",\"messageId\":\"m1\",\"text\":\"hi\",\"timestamp\":1752451200,\"to\":\"did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e\"}"
302
+ },
303
+ {
304
+ "name": "non-ascii-text",
305
+ "from": "did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP",
306
+ "to": "did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e",
307
+ "messageId": "m-群",
308
+ "contextId": "c1",
309
+ "timestamp": 1752451200,
310
+ "text": "群れたい — hello",
311
+ "signingPayload": "{\"contextId\":\"c1\",\"from\":\"did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP\",\"messageId\":\"m-群\",\"text\":\"群れたい — hello\",\"timestamp\":1752451200,\"to\":\"did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e\"}"
312
+ },
313
+ {
314
+ "name": "empty-text",
315
+ "from": "did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP",
316
+ "to": "did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e",
317
+ "messageId": "m1",
318
+ "contextId": "c1",
319
+ "timestamp": 0,
320
+ "text": "",
321
+ "signingPayload": "{\"contextId\":\"c1\",\"from\":\"did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP\",\"messageId\":\"m1\",\"text\":\"\",\"timestamp\":0,\"to\":\"did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e\"}"
322
+ }
323
+ ],
324
+ "bindingV2": {
325
+ "checkNow": 1784273741,
326
+ "cases": [
327
+ {
328
+ "name": "no-expiry",
329
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
330
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
331
+ "ownerSeed": "1717171717171717171717171717171717171717171717171717171717171717",
332
+ "deviceSeed": "1818181818181818181818181818181818181818181818181818181818181818",
333
+ "ts": 1784273681,
334
+ "validUntil": 0,
335
+ "bindingPayload": "{\"deviceDid\":\"did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU\",\"rootDid\":\"did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q\",\"ts\":1784273681,\"typ\":\"muretai/devicebinding/2\",\"validUntil\":0}",
336
+ "binding": {
337
+ "typ": "muretai/devicebinding/2",
338
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
339
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
340
+ "ts": 1784273681,
341
+ "validUntil": 0,
342
+ "sig": "9uaChaM0gQoIgiZqV/k8FDBZXHiaJPvtZbijhoQWogbCVb0ylI+hMNESnQ4bSsreEbE+d50kaBv+12E8UxBCDw==",
343
+ "deviceSig": "OowWKRk3TNZr58K+/Yd+y/iA5siZPN4IF/0lQYFqMcWy8/EHaodbt8tWx+K883uTI55xh1rKpwd5pFuJ8CSvBQ=="
344
+ }
345
+ },
346
+ {
347
+ "name": "bounded",
348
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
349
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
350
+ "ownerSeed": "1717171717171717171717171717171717171717171717171717171717171717",
351
+ "deviceSeed": "1818181818181818181818181818181818181818181818181818181818181818",
352
+ "ts": 1784273681,
353
+ "validUntil": 1815809681,
354
+ "bindingPayload": "{\"deviceDid\":\"did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU\",\"rootDid\":\"did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q\",\"ts\":1784273681,\"typ\":\"muretai/devicebinding/2\",\"validUntil\":1815809681}",
355
+ "binding": {
356
+ "typ": "muretai/devicebinding/2",
357
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
358
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
359
+ "ts": 1784273681,
360
+ "validUntil": 1815809681,
361
+ "sig": "5alfC8XRi6brgqjfzzbq9Jam8aXyA08PgQ3vopL5r+N0BPr+AXl6xR+vGtzc5ZlszVVBle4P/rzplq+2e+SVAA==",
362
+ "deviceSig": "BZDaDqB0kQPNsry0Qfn9JSlLdX5Z+EkWAsPS9027/j/YHdpsKZeT4EDVffPfYwaCSP75NaIrxv5yiC1Nb1utCg=="
363
+ }
364
+ }
365
+ ],
366
+ "reject": [
367
+ {
368
+ "name": "float-ts",
369
+ "category": "float-ts",
370
+ "mustReject": true,
371
+ "input": {
372
+ "typ": "muretai/devicebinding/2",
373
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
374
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
375
+ "ts": 1784273681.0,
376
+ "validUntil": 0,
377
+ "sig": "Z5kh7m9BZs3QGw7W51fx7S7/Nit0uCo6qsdKdjnt2agsl4PEl0lwfbmoCoO/+mMc+x0XDWSMu/16F9/ftdd9DA==",
378
+ "deviceSig": "LbqqmEBJzge+cJgXTdIWYmAE0PUGurrNeE/Hy6F7NkaqNiPDnbySWEl42eU+lrm6861t2COBUxhgrHErI39mBA=="
379
+ },
380
+ "note": "ts is 1784273681.0 and both signatures are GENUINE over those float bytes. Reject on the type, before any crypto: a float repr is bytes only Python reproduces, so accepting it forks the contract (numberHazards/integral-float is this same value)."
381
+ },
382
+ {
383
+ "name": "missing-deviceSig",
384
+ "category": "missing-deviceSig",
385
+ "mustReject": true,
386
+ "input": {
387
+ "typ": "muretai/devicebinding/2",
388
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
389
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
390
+ "ts": 1784273681,
391
+ "validUntil": 0,
392
+ "sig": "9uaChaM0gQoIgiZqV/k8FDBZXHiaJPvtZbijhoQWogbCVb0ylI+hMNESnQ4bSsreEbE+d50kaBv+12E8UxBCDw=="
393
+ },
394
+ "note": "the owner signature is valid and the device countersignature is absent — without it a foreign owner can claim someone else's device, which is the v1 gap v2 exists to close. Never fall back to owner-only."
395
+ },
396
+ {
397
+ "name": "wrong-typ",
398
+ "category": "wrong-typ",
399
+ "mustReject": true,
400
+ "input": {
401
+ "typ": "muretai/devicebinding/1",
402
+ "rootDid": "did:key:z6Mkhow21JWUi49xXiMVnHL18JjZf5dYMSmSwPoHfWU4VN5q",
403
+ "deviceDid": "did:key:z6Mkk4Li7FrksouqV6ehQnbKh8R5kEkkqtmpQtohPvE3fnKU",
404
+ "ts": 1784273681,
405
+ "validUntil": 0,
406
+ "sig": "rQVkNoTQbOrxOCeAeIV2aQBT2EIZWvDB3rWeW7BbPVRCZjmiqYa77GlLAMcG+Od2BIwhT48xsiHbDXjHAWaEAQ==",
407
+ "deviceSig": "zJGq14Oifmq08Z6LcWVx4Re0H1v+FT6EuaE2vQ8Ntf4wC8mW9IEu+LLCnzcy8b1dbtw30r9MYYLxMAQOIQl4DA=="
408
+ },
409
+ "note": "typ says muretai/devicebinding/1 and both signatures are genuine over those bytes. `typ` is INSIDE the signed payload precisely so another artifact type can never be replayed as an account binding — match it exactly, before the signatures."
410
+ }
411
+ ]
412
+ },
413
+ "domainLinkage": [
414
+ {
415
+ "name": "plain",
416
+ "did": "did:key:z6MktojHN9D8obak7C9wjpTzCRrdE5zC6cxt5ANUFnQskgbs",
417
+ "domain": "example.com",
418
+ "nbf": 1754870400,
419
+ "exp": 1762646400,
420
+ "signingInput": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzI3o2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NDY0MDAsImlzcyI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwibmJmIjoxNzU0ODcwNDAwLCJzdWIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdG9qSE45RDhvYmFrN0M5d2pwVHpDUnJkRTV6QzZjeHQ1QU5VRm5Rc2tnYnMiLCJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyNS0xMS0wOVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI1LTA4LTExVDAwOjAwOjAwWiIsImlzc3VlciI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl19fQ",
421
+ "token": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzI3o2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NDY0MDAsImlzcyI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwibmJmIjoxNzU0ODcwNDAwLCJzdWIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdG9qSE45RDhvYmFrN0M5d2pwVHpDUnJkRTV6QzZjeHQ1QU5VRm5Rc2tnYnMiLCJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyNS0xMS0wOVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI1LTA4LTExVDAwOjAwOjAwWiIsImlzc3VlciI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl19fQ.PC86WXKk1O_0asYAWi81QrUDp2aRdUbqjAmINiSHCZmZIVNOffrkqSxPnYVFvVZf87_g_EKhC2b2F8dr5z0JDA"
422
+ },
423
+ {
424
+ "name": "subdomain",
425
+ "did": "did:key:z6MktojHN9D8obak7C9wjpTzCRrdE5zC6cxt5ANUFnQskgbs",
426
+ "domain": "agents.example.com",
427
+ "nbf": 1754870400,
428
+ "exp": 1762646400,
429
+ "signingInput": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzI3o2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NDY0MDAsImlzcyI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwibmJmIjoxNzU0ODcwNDAwLCJzdWIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdG9qSE45RDhvYmFrN0M5d2pwVHpDUnJkRTV6QzZjeHQ1QU5VRm5Rc2tnYnMiLCJvcmlnaW4iOiJodHRwczovL2FnZW50cy5leGFtcGxlLmNvbSJ9LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjUtMTEtMDlUMDA6MDA6MDBaIiwiaXNzdWFuY2VEYXRlIjoiMjAyNS0wOC0xMVQwMDowMDowMFoiLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJEb21haW5MaW5rYWdlQ3JlZGVudGlhbCJdfX0",
430
+ "token": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzI3o2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NDY0MDAsImlzcyI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwibmJmIjoxNzU0ODcwNDAwLCJzdWIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdG9qSE45RDhvYmFrN0M5d2pwVHpDUnJkRTV6QzZjeHQ1QU5VRm5Rc2tnYnMiLCJvcmlnaW4iOiJodHRwczovL2FnZW50cy5leGFtcGxlLmNvbSJ9LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjUtMTEtMDlUMDA6MDA6MDBaIiwiaXNzdWFuY2VEYXRlIjoiMjAyNS0wOC0xMVQwMDowMDowMFoiLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJEb21haW5MaW5rYWdlQ3JlZGVudGlhbCJdfX0.USRlay6FLlmoBpEwywyZtocxsgwzr8PR5uGkJbpnSL4UkqqVVx66QZoSjn3vdf3aJiSQvBXdIPV-WT_uwFk3BQ"
431
+ },
432
+ {
433
+ "name": "loopback-port",
434
+ "did": "did:key:z6MktojHN9D8obak7C9wjpTzCRrdE5zC6cxt5ANUFnQskgbs",
435
+ "domain": "127.0.0.1:8443",
436
+ "nbf": 1754870400,
437
+ "exp": 1762646400,
438
+ "signingInput": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzI3o2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NDY0MDAsImlzcyI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwibmJmIjoxNzU0ODcwNDAwLCJzdWIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdG9qSE45RDhvYmFrN0M5d2pwVHpDUnJkRTV6QzZjeHQ1QU5VRm5Rc2tnYnMiLCJvcmlnaW4iOiJodHRwczovLzEyNy4wLjAuMTo4NDQzIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyNS0xMS0wOVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI1LTA4LTExVDAwOjAwOjAwWiIsImlzc3VlciI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl19fQ",
439
+ "token": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzI3o2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NDY0MDAsImlzcyI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwibmJmIjoxNzU0ODcwNDAwLCJzdWIiOiJkaWQ6a2V5Ono2TWt0b2pITjlEOG9iYWs3Qzl3anBUekNScmRFNXpDNmN4dDVBTlVGblFza2dicyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdG9qSE45RDhvYmFrN0M5d2pwVHpDUnJkRTV6QzZjeHQ1QU5VRm5Rc2tnYnMiLCJvcmlnaW4iOiJodHRwczovLzEyNy4wLjAuMTo4NDQzIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyNS0xMS0wOVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI1LTA4LTExVDAwOjAwOjAwWiIsImlzc3VlciI6ImRpZDprZXk6ejZNa3RvakhOOUQ4b2JhazdDOXdqcFR6Q1JyZEU1ekM2Y3h0NUFOVUZuUXNrZ2JzIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl19fQ.k_XOyNvWVUGtS88CBYl7IYUptxnM3A3hGlhRymmQxDSaItKxvhOnj2NT1F5dZauqoUQdVMhexVpzxTEIUVwXCQ"
440
+ }
441
+ ],
442
+ "domainLinkageNote": "The DIF Domain Linkage Credential (compact JWS, shared/domainbind.py). The signature covers the TEXT of the first two segments — b64url(header).b64url(payload) — never the object they decode to, so `signingInput` is the byte string to reproduce and re-serializing a decoded payload is NOT how you verify. Header is {alg:\"EdDSA\",typ:\"JWT\",kid:\"<did>#<multibase>\"}; base64url is UNPADDED and a padded or standard-alphabet spelling is refused rather than repaired. `nbf`/`exp` are INTEGER epoch seconds and are the values a verifier compares; the ISO strings inside `vc` are their human-facing echo. `exp` is MANDATORY — a domain is leased, not owned, so a credential with no end date is indefinite authority over a name the issuer may no longer hold. iss == sub == vc.credentialSubject.id, all three the identical string, or refuse. Origins are compared canonicalized on BOTH sides (shared/neturl.origin), and a non-default port is part of the origin. DIRECTION: this proves only that the holder of `did` CLAIMS `domain`; it becomes a proof once the document is fetched from that domain's /.well-known/did-configuration.json and the DID's card names the domain back.",
443
+ "webBotAuth": {
444
+ "note": "The `keys` entries are the SAME public keys as the `did` section. `keyid` is the RFC 7638 thumbprint of the JWK, and `thumbprintInput` is the exact text hashed to produce it. `alg` is lowercase \"ed25519\" (RFC 9421), NOT JOSE's \"EdDSA\" — domainLinkage uses the other spelling for the same curve. A signature base is LF-joined with NO trailing newline. The request path covers @authority (so it cannot be replayed at another origin) and signature-agent (so the site can look us up); the covered signature-agent value and the Signature-Agent header are the same sf-string, quotes included. `tag` separates the two directions and is inside the signed params. READ THE ROLES BEFORE COMPARING HEX: `seedHex` is a PRIVATE seed and `did` is the key DERIVED from it, while the identical-looking hex at keys[2].publicHex is that same byte string used as a PUBLIC key — a different DID, by construction.",
445
+ "seedHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
446
+ "did": "did:key:z6MkehRgf7yJbgaGfYsdoAsKdBPE3dj2CYhowQdcjqSJgvVd",
447
+ "keys": [
448
+ {
449
+ "publicHex": "0000000000000000000000000000000000000000000000000000000000000000",
450
+ "did": "did:key:z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP",
451
+ "x": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
452
+ "thumbprintInput": "{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}",
453
+ "thumbprint": "ogRZbCR5KTrPFCAfuYmCMwj0w7Yuk3Lr6YWQWfpkbf0"
454
+ },
455
+ {
456
+ "publicHex": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
457
+ "did": "did:key:z6MkwgaR63138bEEgad7uk993KMX54vBA6KTB4sFhCPnSB2e",
458
+ "x": "__________________________________________8",
459
+ "thumbprintInput": "{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"__________________________________________8\"}",
460
+ "thumbprint": "5rFvj451YsRwcWuFV5946TD4InhZSbOza0q8jWz8X90"
461
+ },
462
+ {
463
+ "publicHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
464
+ "did": "did:key:z6MkeTGwHmLmuCmgg4ABYhzWVh6ZX7hTwWt8gguAretUfc9c",
465
+ "x": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8",
466
+ "thumbprintInput": "{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8\"}",
467
+ "thumbprint": "P7IdLIpiTZiFaIoOSqbX3JrSyps3hvZ4Y2SieP96XIY"
468
+ },
469
+ {
470
+ "publicHex": "094119b513c9c595718eaba5a497a8e0ea1dd19bd5e043dad8b5f1ee9a893b64",
471
+ "did": "did:key:z6Mkf5PHmUf14MDpPoHgRVVNDEFSZ62ecAHhjSJGicWcjrno",
472
+ "x": "CUEZtRPJxZVxjqulpJeo4Ood0ZvV4EPa2LXx7pqJO2Q",
473
+ "thumbprintInput": "{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"CUEZtRPJxZVxjqulpJeo4Ood0ZvV4EPa2LXx7pqJO2Q\"}",
474
+ "thumbprint": "XS885FlMDO9EHzHe7ghOBrxMG-EzyAtWUPON3O8yQgQ"
475
+ }
476
+ ],
477
+ "request": {
478
+ "authority": "example.com",
479
+ "created": 1754870400,
480
+ "expires": 1754870700,
481
+ "keyid": "1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y",
482
+ "alg": "ed25519",
483
+ "tag": "web-bot-auth",
484
+ "components": [
485
+ "@authority",
486
+ "signature-agent"
487
+ ],
488
+ "signatureAgentUrl": "https://muretai.net/z6MkehRgf7yJbgaGfYsdoAsKdBPE3dj2CYhowQdcjqSJgvVd",
489
+ "signatureAgent": "\"https://muretai.net/z6MkehRgf7yJbgaGfYsdoAsKdBPE3dj2CYhowQdcjqSJgvVd\"",
490
+ "signatureParams": "(\"@authority\" \"signature-agent\");created=1754870400;expires=1754870700;keyid=\"1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y\";alg=\"ed25519\";tag=\"web-bot-auth\"",
491
+ "signatureBase": "\"@authority\": example.com\n\"signature-agent\": \"https://muretai.net/z6MkehRgf7yJbgaGfYsdoAsKdBPE3dj2CYhowQdcjqSJgvVd\"\n\"@signature-params\": (\"@authority\" \"signature-agent\");created=1754870400;expires=1754870700;keyid=\"1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y\";alg=\"ed25519\";tag=\"web-bot-auth\"",
492
+ "signatureInput": "sig1=(\"@authority\" \"signature-agent\");created=1754870400;expires=1754870700;keyid=\"1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y\";alg=\"ed25519\";tag=\"web-bot-auth\"",
493
+ "signature": "sig1=:4AUlLYDVmcMdzrlmW7PMafuKmfM1E1kkt94bnr+AeQuVl7siZl0+PK+cPeZ33lqQFEUk14ldvTxqNlzTC07UDA==:"
494
+ },
495
+ "directory": {
496
+ "authority": "example.com",
497
+ "created": 1754870400,
498
+ "expires": 1754877600,
499
+ "keyid": "1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y",
500
+ "alg": "ed25519",
501
+ "tag": "http-message-signatures-directory",
502
+ "components": [
503
+ "@authority"
504
+ ],
505
+ "path": "/.well-known/http-message-signatures-directory",
506
+ "contentType": "application/http-message-signatures-directory+json",
507
+ "body": "{\"keys\":[{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"A6EHv_POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg\"}]}",
508
+ "signatureParams": "(\"@authority\");created=1754870400;expires=1754877600;keyid=\"1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y\";alg=\"ed25519\";tag=\"http-message-signatures-directory\"",
509
+ "signatureBase": "\"@authority\": example.com\n\"@signature-params\": (\"@authority\");created=1754870400;expires=1754877600;keyid=\"1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y\";alg=\"ed25519\";tag=\"http-message-signatures-directory\"",
510
+ "signatureInput": "sig1=(\"@authority\");created=1754870400;expires=1754877600;keyid=\"1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y\";alg=\"ed25519\";tag=\"http-message-signatures-directory\"",
511
+ "signature": "sig1=:xn3Gf4Niw6pEOIKZD9NC85a+AGbY6S7oBeBn77Li4vVm7TeFJHpeWqvws8n4JCIqIN1MXPjZAbOQmloCJd/3CQ==:"
512
+ }
513
+ },
514
+ "cardpub": [
515
+ {
516
+ "name": "relay-only",
517
+ "card": {
518
+ "did": "did:key:z6MkpFkurpyZgyna5SAfLpvdzp7W6cvdc1fn9YECrwv3AMbF",
519
+ "name": "Node",
520
+ "url": "",
521
+ "relay": "https://relay.example",
522
+ "enc_pub": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
523
+ },
524
+ "ts": 1784273681,
525
+ "envelopePayload": "{\"card\":{\"did\":\"did:key:z6MkpFkurpyZgyna5SAfLpvdzp7W6cvdc1fn9YECrwv3AMbF\",\"enc_pub\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"name\":\"Node\",\"relay\":\"https://relay.example\",\"url\":\"\"},\"ts\":1784273681,\"typ\":\"agentcard\",\"v\":1}",
526
+ "sig": "Ryrhh/k3UvsVHF4kR1gLOhinZMtKNWcQuvMHSWZ/UVLU34XJ0NmSXh3QyUiqpXuQdoU09F8DLgkbXEEPcJn0AA=="
527
+ },
528
+ {
529
+ "name": "with-url",
530
+ "card": {
531
+ "did": "did:key:z6MkpFkurpyZgyna5SAfLpvdzp7W6cvdc1fn9YECrwv3AMbF",
532
+ "name": "Node",
533
+ "url": "http://127.0.0.1:8001/"
534
+ },
535
+ "ts": 0,
536
+ "envelopePayload": "{\"card\":{\"did\":\"did:key:z6MkpFkurpyZgyna5SAfLpvdzp7W6cvdc1fn9YECrwv3AMbF\",\"name\":\"Node\",\"url\":\"http://127.0.0.1:8001/\"},\"ts\":0,\"typ\":\"agentcard\",\"v\":1}",
537
+ "sig": "7NNkIvHLZ8CgjlHXsTaGRYfmmKhKLpdaRGw0dX3ztbrY05wyaWBN+5RlM4vLPxeggSMb+VxYIQ0pgfcONK7zDQ=="
538
+ }
539
+ ],
540
+ "epochNote": "NOT bytes, so not pinnable here — and the thing a client is most likely to get wrong. An `epoch` identifies a LISTENER, not a poll: sortable (creation-time+pid), STABLE for that listener's life, greater-wins, older gets HTTP 409. Minting a fresh epoch per poll makes a client supersede its own long-poll and drop mail. Specified in docs/SPECIFICATION.md §relay.",
541
+ "reject": {
542
+ "message": [
543
+ {
544
+ "name": "from-not-signer",
545
+ "category": "from-not-signer",
546
+ "mustReject": true,
547
+ "input": {
548
+ "from": "did:key:z6MkpgbpjhTBQSzhqCnLNNSnz42fSfBak34gYxoE2vqjKPSJ",
549
+ "to": "did:key:z6MkknhhwAGsRrcTTthPSsBbQtrNX6CMqt4poro5jdP6uCue",
550
+ "messageId": "m1",
551
+ "contextId": "c1",
552
+ "timestamp": 1784273681,
553
+ "text": "pay the invoice",
554
+ "sig": "E/lo60PN4uJdh7nt2BYj3BZU1rFmsTv+/1WK8iuEJ/zmjn2W6X+ycD+Knvs8mGvJjaJ1b/fHNk+hfUHKtX/1CA=="
555
+ },
556
+ "note": "the signature is valid, but by a DIFFERENT key than `from` names. Derive the verifying key FROM `from` (with did:key the DID IS the key) and check against it — do not trust `from` as a label, and do not verify against a key from any other field."
557
+ },
558
+ {
559
+ "name": "bad-signature",
560
+ "category": "bad-signature",
561
+ "mustReject": true,
562
+ "input": {
563
+ "from": "did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7",
564
+ "to": "did:key:z6MkknhhwAGsRrcTTthPSsBbQtrNX6CMqt4poro5jdP6uCue",
565
+ "messageId": "m1",
566
+ "contextId": "c1",
567
+ "timestamp": 1784273681,
568
+ "text": "pay the invoice",
569
+ "sig": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
570
+ },
571
+ "note": "garbage signature bytes over an otherwise valid envelope — must not be displayed as signed."
572
+ },
573
+ {
574
+ "name": "tampered-text",
575
+ "category": "tampered-text",
576
+ "mustReject": true,
577
+ "input": {
578
+ "from": "did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7",
579
+ "to": "did:key:z6MkknhhwAGsRrcTTthPSsBbQtrNX6CMqt4poro5jdP6uCue",
580
+ "messageId": "m1",
581
+ "contextId": "c1",
582
+ "timestamp": 1784273681,
583
+ "text": "pay the ATTACKER",
584
+ "sig": "E/lo60PN4uJdh7nt2BYj3BZU1rFmsTv+/1WK8iuEJ/zmjn2W6X+ycD+Knvs8mGvJjaJ1b/fHNk+hfUHKtX/1CA=="
585
+ },
586
+ "note": "signed for one text, delivered with another. Recompute the canonical signing payload from the received fields — never trust a `sig` field without recomputing what it covers."
587
+ },
588
+ {
589
+ "name": "tampered-timestamp",
590
+ "category": "tampered-timestamp",
591
+ "mustReject": true,
592
+ "input": {
593
+ "from": "did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7",
594
+ "to": "did:key:z6MkknhhwAGsRrcTTthPSsBbQtrNX6CMqt4poro5jdP6uCue",
595
+ "messageId": "m1",
596
+ "contextId": "c1",
597
+ "timestamp": 1784273999,
598
+ "text": "pay the invoice",
599
+ "sig": "E/lo60PN4uJdh7nt2BYj3BZU1rFmsTv+/1WK8iuEJ/zmjn2W6X+ycD+Knvs8mGvJjaJ1b/fHNk+hfUHKtX/1CA=="
600
+ },
601
+ "note": "signed ts changed in transit (also proves timestamp is inside the signed payload)."
602
+ },
603
+ {
604
+ "name": "missing-sig",
605
+ "category": "missing-sig",
606
+ "mustReject": true,
607
+ "input": {
608
+ "from": "did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7",
609
+ "to": "did:key:z6MkknhhwAGsRrcTTthPSsBbQtrNX6CMqt4poro5jdP6uCue",
610
+ "messageId": "m1",
611
+ "contextId": "c1",
612
+ "timestamp": 1784273681,
613
+ "text": "pay the invoice",
614
+ "sig": null
615
+ },
616
+ "note": "no signature at all — an unsigned message must never be shown as from a DID. `agent/inbox.verify` raises before any brain/log/display."
617
+ },
618
+ {
619
+ "name": "wrong-recipient",
620
+ "category": "wrong-recipient",
621
+ "mustReject": true,
622
+ "input": {
623
+ "from": "did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7",
624
+ "to": "did:key:z6Mkm6LsYZNUxC249DMDsHx5rZgMSuP3VNrqsCSk3nYZiGjP",
625
+ "messageId": "m1",
626
+ "contextId": "c1",
627
+ "timestamp": 1784273681,
628
+ "text": "pay the invoice",
629
+ "sig": "mbWMD6FBZevg6rxqAFKIbyxVNVXlL3hXvq+D+dhAFLAvCCLLhc15GQbWSPykZ7YtQzqJzAvigUCg5Ux7reCAAA=="
630
+ },
631
+ "recipientDid": "did:key:z6MkknhhwAGsRrcTTthPSsBbQtrNX6CMqt4poro5jdP6uCue",
632
+ "note": "validly signed, but `to` is not us. The signature verifies for the real recipient; a client must still refuse a message not addressed to it."
633
+ }
634
+ ]
635
+ },
636
+ "rejectNote": "Each case MUST be rejected by your receiver (`mustReject`). `category` is language-neutral guidance, NOT core's -32xxx — your own error taxonomy is yours. message: verified with the key DERIVED FROM `from` (crypto.verify_envelope); invite: shared/invite.verify_invite, judged at the case's `checkNow`; claim: signature MUST verify AND a one-time nonce THIS device issued must be consumed before any trust is written. ONE case cannot be a static vector: `claim-spent-nonce` (a replayed claim whose nonce was already consumed) needs receiver state, so it is a REQUIRED client unit test, not pinned here — the same boundary as cryptobox's random-nonce seal."
637
+ }