@adastracomputing/ink 0.1.0-alpha.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.
- package/CHANGELOG.md +63 -0
- package/CODE_OF_CONDUCT.md +42 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +133 -0
- package/SECURITY.md +57 -0
- package/docs/key-rotation-rule.md +108 -0
- package/docs/logo.svg +8 -0
- package/docs/maturity.md +81 -0
- package/docs/threat-model.md +150 -0
- package/package.json +72 -0
- package/specs/ink-agent-containment-and-governance-extension-spec.md +508 -0
- package/specs/ink-auditability.md +652 -0
- package/specs/ink-authorization-chain.md +242 -0
- package/specs/ink-compatibility-policy.md +263 -0
- package/specs/ink-compliance-checklist.md +309 -0
- package/specs/ink-containment-phase1-implementation-spec.md +593 -0
- package/specs/ink-introduction-receipts-extension.md +501 -0
- package/specs/ink-key-rotation-spec.md +535 -0
- package/src/crypto/ink.ts +902 -0
- package/src/crypto/keys.ts +211 -0
- package/src/crypto/multi-key-verify.ts +170 -0
- package/src/crypto/sign.ts +155 -0
- package/src/crypto/verify.ts +1 -0
- package/src/discovery/agent-card.ts +508 -0
- package/src/index.ts +59 -0
- package/src/ink/checkpoint.ts +75 -0
- package/src/ink/discovery-gating.ts +147 -0
- package/src/ink/handshake-budget.ts +413 -0
- package/src/ink/receipts.ts +114 -0
- package/src/ink/transport-auth.ts +96 -0
- package/src/middleware/ink-auth.ts +263 -0
- package/src/models/agent-card.ts +63 -0
- package/src/models/ink-audit.ts +205 -0
- package/src/models/ink-handshake.ts +123 -0
- package/src/models/intent.ts +201 -0
- package/src/models/key-entry.ts +52 -0
- package/src/models/profile.ts +31 -0
- package/test-vectors/README.md +129 -0
- package/test-vectors/encryption.json +90 -0
- package/test-vectors/handshake.json +482 -0
- package/test-vectors/jcs.json +30 -0
- package/test-vectors/key-rotation.json +101 -0
- package/test-vectors/keys.json +32 -0
- package/test-vectors/receipts-and-audit.json +142 -0
- package/test-vectors/replay.json +88 -0
- package/test-vectors/signing.json +61 -0
- package/test-vectors/witness.json +394 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "INK v0.1 handshake test vectors — challenge (Stage 2a), rejection (Stage 2b), resolution (Stage 3). All signatures use key material from keys.json.",
|
|
3
|
+
"vectors": [
|
|
4
|
+
{
|
|
5
|
+
"id": "challenge-valid",
|
|
6
|
+
"description": "Alice sends a valid challenge to Bob (Stage 2a)",
|
|
7
|
+
"input": {
|
|
8
|
+
"method": "POST",
|
|
9
|
+
"path": "/ink/v1/did:plc:bob456test/challenge",
|
|
10
|
+
"recipientDid": "did:plc:bob456test",
|
|
11
|
+
"body": {
|
|
12
|
+
"protocol": "ink/0.1",
|
|
13
|
+
"type": "network.tulpa.challenge",
|
|
14
|
+
"intentRef": "msg-intent-001",
|
|
15
|
+
"challengeType": "availability",
|
|
16
|
+
"nonce": "Y2hhbG5vbmNlMQ",
|
|
17
|
+
"timestamp": "2026-03-25T12:00:00Z"
|
|
18
|
+
},
|
|
19
|
+
"timestamp": "2026-03-25T12:00:00Z",
|
|
20
|
+
"signerPrivateKeyHex": "02362f546f26d9c252c9209d1218e3174654b04b5f08f2ee2b3fa7874111d086"
|
|
21
|
+
},
|
|
22
|
+
"expected": {
|
|
23
|
+
"canonicalBody": "{\"challengeType\":\"availability\",\"intentRef\":\"msg-intent-001\",\"nonce\":\"Y2hhbG5vbmNlMQ\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:00:00Z\",\"type\":\"network.tulpa.challenge\"}",
|
|
24
|
+
"signatureBase": "ink/0.1\nPOST\n/ink/v1/did:plc:bob456test/challenge\ndid:plc:bob456test\n{\"challengeType\":\"availability\",\"intentRef\":\"msg-intent-001\",\"nonce\":\"Y2hhbG5vbmNlMQ\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:00:00Z\",\"type\":\"network.tulpa.challenge\"}\n2026-03-25T12:00:00Z",
|
|
25
|
+
"signatureBase64url": "RpVtDeOoEegibsJdKJtaM6Iku6aaew7ckJ6PmtG__z4AnerYp85tcRSSSYbT2BPGKxqLXqVT6Kx9Kj2KkkC6Cw",
|
|
26
|
+
"accepted": true
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "challenge-invalid-path",
|
|
31
|
+
"description": "Challenge signature fails when verified against /rejection path",
|
|
32
|
+
"input": {
|
|
33
|
+
"method": "POST",
|
|
34
|
+
"path": "/ink/v1/did:plc:bob456test/rejection",
|
|
35
|
+
"recipientDid": "did:plc:bob456test",
|
|
36
|
+
"body": {
|
|
37
|
+
"protocol": "ink/0.1",
|
|
38
|
+
"type": "network.tulpa.challenge",
|
|
39
|
+
"intentRef": "msg-intent-001",
|
|
40
|
+
"challengeType": "availability",
|
|
41
|
+
"nonce": "Y2hhbG5vbmNlMQ",
|
|
42
|
+
"timestamp": "2026-03-25T12:00:00Z"
|
|
43
|
+
},
|
|
44
|
+
"timestamp": "2026-03-25T12:00:00Z",
|
|
45
|
+
"originalSignatureBase64url": "RpVtDeOoEegibsJdKJtaM6Iku6aaew7ckJ6PmtG__z4AnerYp85tcRSSSYbT2BPGKxqLXqVT6Kx9Kj2KkkC6Cw"
|
|
46
|
+
},
|
|
47
|
+
"expected": {
|
|
48
|
+
"accepted": false,
|
|
49
|
+
"reason": "Signature was computed over /challenge but verified against /rejection"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "challenge-invalid-recipient",
|
|
54
|
+
"description": "Challenge signature fails when verified with wrong recipient DID",
|
|
55
|
+
"input": {
|
|
56
|
+
"method": "POST",
|
|
57
|
+
"path": "/ink/v1/did:plc:bob456test/challenge",
|
|
58
|
+
"recipientDid": "did:plc:mallory999",
|
|
59
|
+
"body": {
|
|
60
|
+
"protocol": "ink/0.1",
|
|
61
|
+
"type": "network.tulpa.challenge",
|
|
62
|
+
"intentRef": "msg-intent-001",
|
|
63
|
+
"challengeType": "availability",
|
|
64
|
+
"nonce": "Y2hhbG5vbmNlMQ",
|
|
65
|
+
"timestamp": "2026-03-25T12:00:00Z"
|
|
66
|
+
},
|
|
67
|
+
"timestamp": "2026-03-25T12:00:00Z",
|
|
68
|
+
"originalSignatureBase64url": "RpVtDeOoEegibsJdKJtaM6Iku6aaew7ckJ6PmtG__z4AnerYp85tcRSSSYbT2BPGKxqLXqVT6Kx9Kj2KkkC6Cw"
|
|
69
|
+
},
|
|
70
|
+
"expected": {
|
|
71
|
+
"accepted": false,
|
|
72
|
+
"reason": "recipientDid in signature base does not match verifier"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "challenge-tampered-intentRef",
|
|
77
|
+
"description": "Challenge signature fails when intentRef is tampered",
|
|
78
|
+
"input": {
|
|
79
|
+
"method": "POST",
|
|
80
|
+
"path": "/ink/v1/did:plc:bob456test/challenge",
|
|
81
|
+
"recipientDid": "did:plc:bob456test",
|
|
82
|
+
"body": {
|
|
83
|
+
"protocol": "ink/0.1",
|
|
84
|
+
"type": "network.tulpa.challenge",
|
|
85
|
+
"intentRef": "msg-intent-TAMPERED",
|
|
86
|
+
"challengeType": "availability",
|
|
87
|
+
"nonce": "Y2hhbG5vbmNlMQ",
|
|
88
|
+
"timestamp": "2026-03-25T12:00:00Z"
|
|
89
|
+
},
|
|
90
|
+
"timestamp": "2026-03-25T12:00:00Z",
|
|
91
|
+
"originalSignatureBase64url": "RpVtDeOoEegibsJdKJtaM6Iku6aaew7ckJ6PmtG__z4AnerYp85tcRSSSYbT2BPGKxqLXqVT6Kx9Kj2KkkC6Cw"
|
|
92
|
+
},
|
|
93
|
+
"expected": {
|
|
94
|
+
"accepted": false,
|
|
95
|
+
"reason": "Body was modified after signing"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "challenge-timestamp-expired",
|
|
100
|
+
"description": "Challenge rejected — timestamp more than 5 minutes in the past",
|
|
101
|
+
"input": {
|
|
102
|
+
"messageTimestamp": "2026-03-25T11:50:00Z",
|
|
103
|
+
"receiverClock": "2026-03-25T12:00:00Z",
|
|
104
|
+
"nonce": "Y2hhbG5vbmNlMQ"
|
|
105
|
+
},
|
|
106
|
+
"expected": {
|
|
107
|
+
"accepted": false,
|
|
108
|
+
"errorCode": "expired_message"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "challenge-timestamp-future",
|
|
113
|
+
"description": "Challenge rejected — timestamp more than 30 seconds in the future",
|
|
114
|
+
"input": {
|
|
115
|
+
"messageTimestamp": "2026-03-25T12:01:00Z",
|
|
116
|
+
"receiverClock": "2026-03-25T12:00:00Z",
|
|
117
|
+
"nonce": "Y2hhbGZ1dHVyZQ"
|
|
118
|
+
},
|
|
119
|
+
"expected": {
|
|
120
|
+
"accepted": false,
|
|
121
|
+
"errorCode": "expired_message"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "challenge-duplicate-nonce",
|
|
126
|
+
"description": "Challenge rejected — nonce already seen",
|
|
127
|
+
"input": {
|
|
128
|
+
"messageTimestamp": "2026-03-25T12:00:00Z",
|
|
129
|
+
"receiverClock": "2026-03-25T12:00:00Z",
|
|
130
|
+
"nonce": "Y2hhbG5vbmNlMQ",
|
|
131
|
+
"previouslySeenNonces": [
|
|
132
|
+
"Y2hhbG5vbmNlMQ"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
"expected": {
|
|
136
|
+
"accepted": false,
|
|
137
|
+
"errorCode": "duplicate_nonce"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "rejection-valid",
|
|
142
|
+
"description": "Bob sends a valid rejection to Alice (Stage 2b)",
|
|
143
|
+
"input": {
|
|
144
|
+
"method": "POST",
|
|
145
|
+
"path": "/ink/v1/did:plc:alice123test/rejection",
|
|
146
|
+
"recipientDid": "did:plc:alice123test",
|
|
147
|
+
"body": {
|
|
148
|
+
"protocol": "ink/0.1",
|
|
149
|
+
"type": "network.tulpa.rejection",
|
|
150
|
+
"intentRef": "msg-intent-001",
|
|
151
|
+
"reason": "capacity",
|
|
152
|
+
"detail": "Too many pending coordination requests",
|
|
153
|
+
"nonce": "cmVqbm9uY2Ux",
|
|
154
|
+
"timestamp": "2026-03-25T12:01:00Z"
|
|
155
|
+
},
|
|
156
|
+
"timestamp": "2026-03-25T12:01:00Z",
|
|
157
|
+
"signerPrivateKeyHex": "4ea0adf03599751231c1c3b7fc5a6c9d2e2f2f97bcd7c885c0add2f47b9af181"
|
|
158
|
+
},
|
|
159
|
+
"expected": {
|
|
160
|
+
"canonicalBody": "{\"detail\":\"Too many pending coordination requests\",\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVqbm9uY2Ux\",\"protocol\":\"ink/0.1\",\"reason\":\"capacity\",\"timestamp\":\"2026-03-25T12:01:00Z\",\"type\":\"network.tulpa.rejection\"}",
|
|
161
|
+
"signatureBase": "ink/0.1\nPOST\n/ink/v1/did:plc:alice123test/rejection\ndid:plc:alice123test\n{\"detail\":\"Too many pending coordination requests\",\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVqbm9uY2Ux\",\"protocol\":\"ink/0.1\",\"reason\":\"capacity\",\"timestamp\":\"2026-03-25T12:01:00Z\",\"type\":\"network.tulpa.rejection\"}\n2026-03-25T12:01:00Z",
|
|
162
|
+
"signatureBase64url": "Dxye5S0lV3uUGck8ozOxF8kavH_ZTcxbdQm7i50hrVLewU1_wDvF3hMYrFHJsSmwnGThvCaM1SDLsaiEiksKBA",
|
|
163
|
+
"accepted": true
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": "rejection-invalid-path",
|
|
168
|
+
"description": "Rejection signature fails when verified against /challenge path",
|
|
169
|
+
"input": {
|
|
170
|
+
"method": "POST",
|
|
171
|
+
"path": "/ink/v1/did:plc:alice123test/challenge",
|
|
172
|
+
"recipientDid": "did:plc:alice123test",
|
|
173
|
+
"body": {
|
|
174
|
+
"protocol": "ink/0.1",
|
|
175
|
+
"type": "network.tulpa.rejection",
|
|
176
|
+
"intentRef": "msg-intent-001",
|
|
177
|
+
"reason": "capacity",
|
|
178
|
+
"detail": "Too many pending coordination requests",
|
|
179
|
+
"nonce": "cmVqbm9uY2Ux",
|
|
180
|
+
"timestamp": "2026-03-25T12:01:00Z"
|
|
181
|
+
},
|
|
182
|
+
"timestamp": "2026-03-25T12:01:00Z",
|
|
183
|
+
"originalSignatureBase64url": "Dxye5S0lV3uUGck8ozOxF8kavH_ZTcxbdQm7i50hrVLewU1_wDvF3hMYrFHJsSmwnGThvCaM1SDLsaiEiksKBA"
|
|
184
|
+
},
|
|
185
|
+
"expected": {
|
|
186
|
+
"accepted": false,
|
|
187
|
+
"reason": "Signature was computed over /rejection but verified against /challenge"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"id": "rejection-invalid-recipient",
|
|
192
|
+
"description": "Rejection signature fails when verified with wrong recipient DID",
|
|
193
|
+
"input": {
|
|
194
|
+
"method": "POST",
|
|
195
|
+
"path": "/ink/v1/did:plc:alice123test/rejection",
|
|
196
|
+
"recipientDid": "did:plc:mallory999",
|
|
197
|
+
"body": {
|
|
198
|
+
"protocol": "ink/0.1",
|
|
199
|
+
"type": "network.tulpa.rejection",
|
|
200
|
+
"intentRef": "msg-intent-001",
|
|
201
|
+
"reason": "capacity",
|
|
202
|
+
"detail": "Too many pending coordination requests",
|
|
203
|
+
"nonce": "cmVqbm9uY2Ux",
|
|
204
|
+
"timestamp": "2026-03-25T12:01:00Z"
|
|
205
|
+
},
|
|
206
|
+
"timestamp": "2026-03-25T12:01:00Z",
|
|
207
|
+
"originalSignatureBase64url": "Dxye5S0lV3uUGck8ozOxF8kavH_ZTcxbdQm7i50hrVLewU1_wDvF3hMYrFHJsSmwnGThvCaM1SDLsaiEiksKBA"
|
|
208
|
+
},
|
|
209
|
+
"expected": {
|
|
210
|
+
"accepted": false,
|
|
211
|
+
"reason": "recipientDid in signature base does not match verifier"
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"id": "rejection-tampered-reason",
|
|
216
|
+
"description": "Rejection signature fails when reason field is tampered",
|
|
217
|
+
"input": {
|
|
218
|
+
"method": "POST",
|
|
219
|
+
"path": "/ink/v1/did:plc:alice123test/rejection",
|
|
220
|
+
"recipientDid": "did:plc:alice123test",
|
|
221
|
+
"body": {
|
|
222
|
+
"protocol": "ink/0.1",
|
|
223
|
+
"type": "network.tulpa.rejection",
|
|
224
|
+
"intentRef": "msg-intent-001",
|
|
225
|
+
"reason": "expired",
|
|
226
|
+
"detail": "Too many pending coordination requests",
|
|
227
|
+
"nonce": "cmVqbm9uY2Ux",
|
|
228
|
+
"timestamp": "2026-03-25T12:01:00Z"
|
|
229
|
+
},
|
|
230
|
+
"timestamp": "2026-03-25T12:01:00Z",
|
|
231
|
+
"originalSignatureBase64url": "Dxye5S0lV3uUGck8ozOxF8kavH_ZTcxbdQm7i50hrVLewU1_wDvF3hMYrFHJsSmwnGThvCaM1SDLsaiEiksKBA"
|
|
232
|
+
},
|
|
233
|
+
"expected": {
|
|
234
|
+
"accepted": false,
|
|
235
|
+
"reason": "Body was modified after signing"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"id": "rejection-timestamp-expired",
|
|
240
|
+
"description": "Rejection rejected — timestamp expired",
|
|
241
|
+
"input": {
|
|
242
|
+
"messageTimestamp": "2026-03-25T11:50:00Z",
|
|
243
|
+
"receiverClock": "2026-03-25T12:00:00Z",
|
|
244
|
+
"nonce": "cmVqbm9uY2Ux"
|
|
245
|
+
},
|
|
246
|
+
"expected": {
|
|
247
|
+
"accepted": false,
|
|
248
|
+
"errorCode": "expired_message"
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"id": "rejection-duplicate-nonce",
|
|
253
|
+
"description": "Rejection rejected — duplicate nonce",
|
|
254
|
+
"input": {
|
|
255
|
+
"messageTimestamp": "2026-03-25T12:01:00Z",
|
|
256
|
+
"receiverClock": "2026-03-25T12:01:00Z",
|
|
257
|
+
"nonce": "cmVqbm9uY2Ux",
|
|
258
|
+
"previouslySeenNonces": [
|
|
259
|
+
"cmVqbm9uY2Ux"
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
"expected": {
|
|
263
|
+
"accepted": false,
|
|
264
|
+
"errorCode": "duplicate_nonce"
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"id": "resolution-accepted",
|
|
269
|
+
"description": "Bob sends a 'accepted' resolution to Alice (Stage 3)",
|
|
270
|
+
"input": {
|
|
271
|
+
"method": "POST",
|
|
272
|
+
"path": "/ink/v1/did:plc:alice123test/resolution",
|
|
273
|
+
"recipientDid": "did:plc:alice123test",
|
|
274
|
+
"body": {
|
|
275
|
+
"protocol": "ink/0.1",
|
|
276
|
+
"type": "network.tulpa.resolution",
|
|
277
|
+
"intentRef": "msg-intent-001",
|
|
278
|
+
"outcome": "accepted",
|
|
279
|
+
"nonce": "cmVzb2x1dGlvbi0YWNjZXB0ZWQ",
|
|
280
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
281
|
+
"details": {
|
|
282
|
+
"scheduledAt": "2026-04-01T15:00:00Z",
|
|
283
|
+
"duration": "30m"
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
287
|
+
"signerPrivateKeyHex": "4ea0adf03599751231c1c3b7fc5a6c9d2e2f2f97bcd7c885c0add2f47b9af181"
|
|
288
|
+
},
|
|
289
|
+
"expected": {
|
|
290
|
+
"canonicalBody": "{\"details\":{\"duration\":\"30m\",\"scheduledAt\":\"2026-04-01T15:00:00Z\"},\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0YWNjZXB0ZWQ\",\"outcome\":\"accepted\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}",
|
|
291
|
+
"signatureBase": "ink/0.1\nPOST\n/ink/v1/did:plc:alice123test/resolution\ndid:plc:alice123test\n{\"details\":{\"duration\":\"30m\",\"scheduledAt\":\"2026-04-01T15:00:00Z\"},\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0YWNjZXB0ZWQ\",\"outcome\":\"accepted\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}\n2026-03-25T12:05:00Z",
|
|
292
|
+
"signatureBase64url": "Gz7YiTPogpiTpTq0FttCq1tFtNA6bkeut7h-55nsu2xpZTd0vraF6hmmmyfTojtRzVfeMu7XOXIK4qIQJt7gDg",
|
|
293
|
+
"accepted": true
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"id": "resolution-declined",
|
|
298
|
+
"description": "Bob sends a 'declined' resolution to Alice (Stage 3)",
|
|
299
|
+
"input": {
|
|
300
|
+
"method": "POST",
|
|
301
|
+
"path": "/ink/v1/did:plc:alice123test/resolution",
|
|
302
|
+
"recipientDid": "did:plc:alice123test",
|
|
303
|
+
"body": {
|
|
304
|
+
"protocol": "ink/0.1",
|
|
305
|
+
"type": "network.tulpa.resolution",
|
|
306
|
+
"intentRef": "msg-intent-001",
|
|
307
|
+
"outcome": "declined",
|
|
308
|
+
"nonce": "cmVzb2x1dGlvbi0ZGVjbGluZWQ",
|
|
309
|
+
"timestamp": "2026-03-25T12:05:00Z"
|
|
310
|
+
},
|
|
311
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
312
|
+
"signerPrivateKeyHex": "4ea0adf03599751231c1c3b7fc5a6c9d2e2f2f97bcd7c885c0add2f47b9af181"
|
|
313
|
+
},
|
|
314
|
+
"expected": {
|
|
315
|
+
"canonicalBody": "{\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0ZGVjbGluZWQ\",\"outcome\":\"declined\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}",
|
|
316
|
+
"signatureBase": "ink/0.1\nPOST\n/ink/v1/did:plc:alice123test/resolution\ndid:plc:alice123test\n{\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0ZGVjbGluZWQ\",\"outcome\":\"declined\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}\n2026-03-25T12:05:00Z",
|
|
317
|
+
"signatureBase64url": "rlhcLWtcp2wXqxBVAbfyU9er-EBs2fUqZDfIrqOkwH9yuNgplawXP73IVQHFUmYa6fS1amlU2MXzgRPmYD73Dw",
|
|
318
|
+
"accepted": true
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"id": "resolution-escalated_to_human",
|
|
323
|
+
"description": "Bob sends a 'escalated_to_human' resolution to Alice (Stage 3)",
|
|
324
|
+
"input": {
|
|
325
|
+
"method": "POST",
|
|
326
|
+
"path": "/ink/v1/did:plc:alice123test/resolution",
|
|
327
|
+
"recipientDid": "did:plc:alice123test",
|
|
328
|
+
"body": {
|
|
329
|
+
"protocol": "ink/0.1",
|
|
330
|
+
"type": "network.tulpa.resolution",
|
|
331
|
+
"intentRef": "msg-intent-001",
|
|
332
|
+
"outcome": "escalated_to_human",
|
|
333
|
+
"nonce": "cmVzb2x1dGlvbi0ZXNjYWxhdGVkX3RvX2h1bWFu",
|
|
334
|
+
"timestamp": "2026-03-25T12:05:00Z"
|
|
335
|
+
},
|
|
336
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
337
|
+
"signerPrivateKeyHex": "4ea0adf03599751231c1c3b7fc5a6c9d2e2f2f97bcd7c885c0add2f47b9af181"
|
|
338
|
+
},
|
|
339
|
+
"expected": {
|
|
340
|
+
"canonicalBody": "{\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0ZXNjYWxhdGVkX3RvX2h1bWFu\",\"outcome\":\"escalated_to_human\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}",
|
|
341
|
+
"signatureBase": "ink/0.1\nPOST\n/ink/v1/did:plc:alice123test/resolution\ndid:plc:alice123test\n{\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0ZXNjYWxhdGVkX3RvX2h1bWFu\",\"outcome\":\"escalated_to_human\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}\n2026-03-25T12:05:00Z",
|
|
342
|
+
"signatureBase64url": "n0N6c0GCsDq3boAvM2jnN1Fyx4xTNy5HUrL8w5nLJ5NK_U-VXHVjIgLcNeb-eDvsXPHloizs7jIhiZfX6LzmBg",
|
|
343
|
+
"accepted": true
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"id": "resolution-expired",
|
|
348
|
+
"description": "Bob sends a 'expired' resolution to Alice (Stage 3)",
|
|
349
|
+
"input": {
|
|
350
|
+
"method": "POST",
|
|
351
|
+
"path": "/ink/v1/did:plc:alice123test/resolution",
|
|
352
|
+
"recipientDid": "did:plc:alice123test",
|
|
353
|
+
"body": {
|
|
354
|
+
"protocol": "ink/0.1",
|
|
355
|
+
"type": "network.tulpa.resolution",
|
|
356
|
+
"intentRef": "msg-intent-001",
|
|
357
|
+
"outcome": "expired",
|
|
358
|
+
"nonce": "cmVzb2x1dGlvbi0ZXhwaXJlZA",
|
|
359
|
+
"timestamp": "2026-03-25T12:05:00Z"
|
|
360
|
+
},
|
|
361
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
362
|
+
"signerPrivateKeyHex": "4ea0adf03599751231c1c3b7fc5a6c9d2e2f2f97bcd7c885c0add2f47b9af181"
|
|
363
|
+
},
|
|
364
|
+
"expected": {
|
|
365
|
+
"canonicalBody": "{\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0ZXhwaXJlZA\",\"outcome\":\"expired\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}",
|
|
366
|
+
"signatureBase": "ink/0.1\nPOST\n/ink/v1/did:plc:alice123test/resolution\ndid:plc:alice123test\n{\"intentRef\":\"msg-intent-001\",\"nonce\":\"cmVzb2x1dGlvbi0ZXhwaXJlZA\",\"outcome\":\"expired\",\"protocol\":\"ink/0.1\",\"timestamp\":\"2026-03-25T12:05:00Z\",\"type\":\"network.tulpa.resolution\"}\n2026-03-25T12:05:00Z",
|
|
367
|
+
"signatureBase64url": "eu9c-cNHGxsjRp1m4SW5NS0J5fHnYfaRt1akSWLwMIXIqrkTX0hrshEIFmQMUg5iiEad6h_p6rQuFT98ixavDg",
|
|
368
|
+
"accepted": true
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"id": "resolution-invalid-path",
|
|
373
|
+
"description": "Resolution signature fails when verified against /challenge path",
|
|
374
|
+
"input": {
|
|
375
|
+
"method": "POST",
|
|
376
|
+
"path": "/ink/v1/did:plc:alice123test/challenge",
|
|
377
|
+
"recipientDid": "did:plc:alice123test",
|
|
378
|
+
"body": {
|
|
379
|
+
"protocol": "ink/0.1",
|
|
380
|
+
"type": "network.tulpa.resolution",
|
|
381
|
+
"intentRef": "msg-intent-001",
|
|
382
|
+
"outcome": "accepted",
|
|
383
|
+
"details": {
|
|
384
|
+
"scheduledAt": "2026-04-01T15:00:00Z",
|
|
385
|
+
"duration": "30m"
|
|
386
|
+
},
|
|
387
|
+
"nonce": "cmVzb2x1dGlvbi0YWNjZXB0ZWQ",
|
|
388
|
+
"timestamp": "2026-03-25T12:05:00Z"
|
|
389
|
+
},
|
|
390
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
391
|
+
"originalSignatureBase64url": "Gz7YiTPogpiTpTq0FttCq1tFtNA6bkeut7h-55nsu2xpZTd0vraF6hmmmyfTojtRzVfeMu7XOXIK4qIQJt7gDg"
|
|
392
|
+
},
|
|
393
|
+
"expected": {
|
|
394
|
+
"accepted": false,
|
|
395
|
+
"reason": "Signature was computed over /resolution but verified against /challenge"
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"id": "resolution-invalid-recipient",
|
|
400
|
+
"description": "Resolution signature fails when verified with wrong recipient DID",
|
|
401
|
+
"input": {
|
|
402
|
+
"method": "POST",
|
|
403
|
+
"path": "/ink/v1/did:plc:alice123test/resolution",
|
|
404
|
+
"recipientDid": "did:plc:mallory999",
|
|
405
|
+
"body": {
|
|
406
|
+
"protocol": "ink/0.1",
|
|
407
|
+
"type": "network.tulpa.resolution",
|
|
408
|
+
"intentRef": "msg-intent-001",
|
|
409
|
+
"outcome": "accepted",
|
|
410
|
+
"details": {
|
|
411
|
+
"scheduledAt": "2026-04-01T15:00:00Z",
|
|
412
|
+
"duration": "30m"
|
|
413
|
+
},
|
|
414
|
+
"nonce": "cmVzb2x1dGlvbi0YWNjZXB0ZWQ",
|
|
415
|
+
"timestamp": "2026-03-25T12:05:00Z"
|
|
416
|
+
},
|
|
417
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
418
|
+
"originalSignatureBase64url": "Gz7YiTPogpiTpTq0FttCq1tFtNA6bkeut7h-55nsu2xpZTd0vraF6hmmmyfTojtRzVfeMu7XOXIK4qIQJt7gDg"
|
|
419
|
+
},
|
|
420
|
+
"expected": {
|
|
421
|
+
"accepted": false,
|
|
422
|
+
"reason": "recipientDid mismatch"
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"id": "resolution-tampered-outcome",
|
|
427
|
+
"description": "Resolution signature fails when outcome is tampered",
|
|
428
|
+
"input": {
|
|
429
|
+
"method": "POST",
|
|
430
|
+
"path": "/ink/v1/did:plc:alice123test/resolution",
|
|
431
|
+
"recipientDid": "did:plc:alice123test",
|
|
432
|
+
"body": {
|
|
433
|
+
"protocol": "ink/0.1",
|
|
434
|
+
"type": "network.tulpa.resolution",
|
|
435
|
+
"intentRef": "msg-intent-001",
|
|
436
|
+
"outcome": "declined",
|
|
437
|
+
"details": {
|
|
438
|
+
"scheduledAt": "2026-04-01T15:00:00Z",
|
|
439
|
+
"duration": "30m"
|
|
440
|
+
},
|
|
441
|
+
"nonce": "cmVzb2x1dGlvbi0YWNjZXB0ZWQ",
|
|
442
|
+
"timestamp": "2026-03-25T12:05:00Z"
|
|
443
|
+
},
|
|
444
|
+
"timestamp": "2026-03-25T12:05:00Z",
|
|
445
|
+
"originalSignatureBase64url": "Gz7YiTPogpiTpTq0FttCq1tFtNA6bkeut7h-55nsu2xpZTd0vraF6hmmmyfTojtRzVfeMu7XOXIK4qIQJt7gDg"
|
|
446
|
+
},
|
|
447
|
+
"expected": {
|
|
448
|
+
"accepted": false,
|
|
449
|
+
"reason": "Body was modified after signing"
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"id": "resolution-timestamp-expired",
|
|
454
|
+
"description": "Resolution rejected — timestamp expired",
|
|
455
|
+
"input": {
|
|
456
|
+
"messageTimestamp": "2026-03-25T11:50:00Z",
|
|
457
|
+
"receiverClock": "2026-03-25T12:00:00Z",
|
|
458
|
+
"nonce": "cmVzZXhwaXJlZA"
|
|
459
|
+
},
|
|
460
|
+
"expected": {
|
|
461
|
+
"accepted": false,
|
|
462
|
+
"errorCode": "expired_message"
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"id": "resolution-duplicate-nonce",
|
|
467
|
+
"description": "Resolution rejected — duplicate nonce",
|
|
468
|
+
"input": {
|
|
469
|
+
"messageTimestamp": "2026-03-25T12:05:00Z",
|
|
470
|
+
"receiverClock": "2026-03-25T12:05:00Z",
|
|
471
|
+
"nonce": "cmVzZHVwbm9uY2U",
|
|
472
|
+
"previouslySeenNonces": [
|
|
473
|
+
"cmVzZHVwbm9uY2U"
|
|
474
|
+
]
|
|
475
|
+
},
|
|
476
|
+
"expected": {
|
|
477
|
+
"accepted": false,
|
|
478
|
+
"errorCode": "duplicate_nonce"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "JCS (RFC 8785) canonicalization test cases for INK message signing",
|
|
3
|
+
"vectors": [
|
|
4
|
+
{
|
|
5
|
+
"description": "Intent message — keys sorted lexicographically, no whitespace",
|
|
6
|
+
"input": {
|
|
7
|
+
"protocol": "ink/0.1",
|
|
8
|
+
"type": "network.tulpa.intent",
|
|
9
|
+
"from": "did:plc:alice123test",
|
|
10
|
+
"to": "did:plc:bob456test",
|
|
11
|
+
"intentType": "scheduling",
|
|
12
|
+
"purpose": "Discuss partnership opportunity",
|
|
13
|
+
"urgency": "normal",
|
|
14
|
+
"expiresAt": "2026-03-25T00:00:00Z",
|
|
15
|
+
"nonce": "dGVzdG5vbmNlMTIzNDU2Nzg",
|
|
16
|
+
"timestamp": "2026-03-18T12:00:00Z"
|
|
17
|
+
},
|
|
18
|
+
"expectedCanonical": "{\"expiresAt\":\"2026-03-25T00:00:00Z\",\"from\":\"did:plc:alice123test\",\"intentType\":\"scheduling\",\"nonce\":\"dGVzdG5vbmNlMTIzNDU2Nzg\",\"protocol\":\"ink/0.1\",\"purpose\":\"Discuss partnership opportunity\",\"timestamp\":\"2026-03-18T12:00:00Z\",\"to\":\"did:plc:bob456test\",\"type\":\"network.tulpa.intent\",\"urgency\":\"normal\"}"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"description": "Empty details object",
|
|
22
|
+
"input": {
|
|
23
|
+
"protocol": "ink/0.1",
|
|
24
|
+
"details": {},
|
|
25
|
+
"type": "network.tulpa.resolution"
|
|
26
|
+
},
|
|
27
|
+
"expectedCanonical": "{\"details\":{},\"protocol\":\"ink/0.1\",\"type\":\"network.tulpa.resolution\"}"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "INK v0.1 key rotation test vectors — auth header keyId extension, rotated-key verification, revoked-key rejection and refresh-on-miss. All scenarios use runtime-generated keys (not static fixtures) and are exercised by test/ink-key-rotation.test.ts.",
|
|
3
|
+
"vectors": [
|
|
4
|
+
{
|
|
5
|
+
"id": "auth-header-with-keyid",
|
|
6
|
+
"description": "Authorization header includes optional keyId parameter after signature",
|
|
7
|
+
"format": "INK-Ed25519 <base64url(sig)> keyId=<keyId>",
|
|
8
|
+
"regex": "^INK-Ed25519\\s+(\\S+)(?:\\s+keyId=(\\S+))?$",
|
|
9
|
+
"examples": [
|
|
10
|
+
{
|
|
11
|
+
"header": "INK-Ed25519 abc123 keyId=sig-2026-03",
|
|
12
|
+
"signature": "abc123",
|
|
13
|
+
"keyId": "sig-2026-03"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"header": "INK-Ed25519 abc123",
|
|
17
|
+
"signature": "abc123",
|
|
18
|
+
"keyId": null,
|
|
19
|
+
"note": "Legacy format — no keyId"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "rotated-key-verification",
|
|
25
|
+
"description": "After rotation, new key signs messages. Verifier with [old=retired, new=active] keyset accepts.",
|
|
26
|
+
"scenario": {
|
|
27
|
+
"step1": "Agent generates keyV1, signs messages",
|
|
28
|
+
"step2": "Agent rotates: keyV1 retired, keyV2 active, keyV1 private wiped",
|
|
29
|
+
"step3": "Agent signs with keyV2, includes keyId=sig-v2 in auth header",
|
|
30
|
+
"step4": "Counterparty resolves keyset from Agent Card: [sig-v1=retired, sig-v2=active]",
|
|
31
|
+
"step5": "verifyInkSignatureWithKeys with hintKeyId=sig-v2 → verified=true, keyId=sig-v2"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "historical-message-verification",
|
|
36
|
+
"description": "Messages signed before rotation still verify against retired key in keyset",
|
|
37
|
+
"scenario": {
|
|
38
|
+
"step1": "Message signed with keyV1 at T=2026-03-20",
|
|
39
|
+
"step2": "Key rotated at T=2026-03-25: keyV1 retired, keyV2 active",
|
|
40
|
+
"step3": "Counterparty verifies old message with keyset [sig-v1=retired, sig-v2=active]",
|
|
41
|
+
"step4": "verifyInkSignatureWithKeys with hintKeyId=sig-v1 → verified=true, keyStatus=retired"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "revoked-key-rejection",
|
|
46
|
+
"description": "Messages signed with a revoked key are rejected even if cryptographically valid",
|
|
47
|
+
"scenario": {
|
|
48
|
+
"step1": "Agent has [sig-compromised=revoked, sig-current=active]",
|
|
49
|
+
"step2": "Attacker signs message with compromised key, sets keyId=sig-compromised",
|
|
50
|
+
"step3": "verifyInkSignatureWithKeys skips revoked keys",
|
|
51
|
+
"step4": "Result: verified=false"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "refresh-on-miss",
|
|
56
|
+
"description": "When all cached keys fail to verify, receiver refetches Agent Card and retries",
|
|
57
|
+
"scenario": {
|
|
58
|
+
"step1": "Receiver has stale cache with only keyV1",
|
|
59
|
+
"step2": "Sender rotated to keyV2 and signs message with it",
|
|
60
|
+
"step3": "Verification against stale keyset [sig-v1=active] → fails",
|
|
61
|
+
"step4": "Receiver fetches fresh Agent Card: [sig-v1=retired, sig-v2=active]",
|
|
62
|
+
"step5": "Retry verification → verified=true, keyId=sig-v2"
|
|
63
|
+
},
|
|
64
|
+
"constraints": {
|
|
65
|
+
"maxRetries": 1,
|
|
66
|
+
"note": "Only one refresh attempt per verification to prevent infinite loops"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "keyid-precedence",
|
|
71
|
+
"description": "When keyId appears in both auth header and message body (signingKeyId), the auth header takes precedence for verification hint",
|
|
72
|
+
"scenario": {
|
|
73
|
+
"headerKeyId": "sig-header",
|
|
74
|
+
"bodySigningKeyId": "sig-body",
|
|
75
|
+
"verificationHint": "sig-header",
|
|
76
|
+
"note": "Header is transport-layer; body is application-layer. Transport takes precedence for the verifier."
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "unknown-keyid-fallthrough",
|
|
81
|
+
"description": "When header keyId references a key not in the keyset, verifier falls through to normal iteration",
|
|
82
|
+
"scenario": {
|
|
83
|
+
"headerKeyId": "sig-unknown",
|
|
84
|
+
"keyset": [
|
|
85
|
+
{"keyId": "sig-actual", "status": "active"}
|
|
86
|
+
],
|
|
87
|
+
"result": "Hinted key not found → skip hint → iterate active keys → verified with sig-actual"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "audit-event-signing-key-tracking",
|
|
92
|
+
"description": "INK audit events record signingKeyId in data field so historical events can be verified against the correct key",
|
|
93
|
+
"schema": {
|
|
94
|
+
"data": {
|
|
95
|
+
"signingKeyId": "sig-v2",
|
|
96
|
+
"note": "Verifiers use this to select the correct key from the agent's historical keyset"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Fixed key material for INK v0.1 test vectors. NOT for production use.",
|
|
3
|
+
"alice": {
|
|
4
|
+
"did": "did:plc:alice123test",
|
|
5
|
+
"agentId": "agent-alice-01",
|
|
6
|
+
"signing": {
|
|
7
|
+
"publicKeyHex": "16d8c7758b7816f4540730fa5b01921bfa0c52459e435b8f970cd8ef3e548fba",
|
|
8
|
+
"privateKeyHex": "02362f546f26d9c252c9209d1218e3174654b04b5f08f2ee2b3fa7874111d086"
|
|
9
|
+
},
|
|
10
|
+
"encryption": {
|
|
11
|
+
"publicKeyHex": "3e24f178fa03cff75136b485611e53c37d628205b13bc53d7bd5415e3d1d7472",
|
|
12
|
+
"privateKeyHex": "b0691398c0e6254015c3d17c18268faf42bc4ae831ae249b7773437977e62f6c"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"bob": {
|
|
16
|
+
"did": "did:plc:bob456test",
|
|
17
|
+
"agentId": "agent-bob-01",
|
|
18
|
+
"signing": {
|
|
19
|
+
"publicKeyHex": "11dce4522822fffcb059f547b9e0b16bb8f47084fec275b46f813701273c014b",
|
|
20
|
+
"privateKeyHex": "4ea0adf03599751231c1c3b7fc5a6c9d2e2f2f97bcd7c885c0add2f47b9af181"
|
|
21
|
+
},
|
|
22
|
+
"encryption": {
|
|
23
|
+
"publicKeyHex": "613ca2aa2ff1102e440dd05ead7f05c11ea440d1109f3d058f9e5302cbf26f0b",
|
|
24
|
+
"privateKeyHex": "986e5e08b69232b6c888c838ada1669139c2993db417b482a780b86f067d8658"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"ephemeral": {
|
|
28
|
+
"note": "Ephemeral X25519 key pair for encryption test vector (INK §3.4.0 step 1)",
|
|
29
|
+
"publicKeyHex": "50bf0e81d5e4475282ed7739da2772594fb0d24816258dbceec766e580c5454b",
|
|
30
|
+
"privateKeyHex": "40fa01588b6f88871ad04a49fef0215fa00d79f2f5bb33cb3ee9742853601077"
|
|
31
|
+
}
|
|
32
|
+
}
|