1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
//! Who wrote this history.
//!
//! # A hash chain answers the wrong question
//!
//! `hash = H(prev ‖ bytes)` proves a run's records are *internally consistent*:
//! nothing was edited, reordered, or removed within the run, by anyone who
//! cannot recompute every subsequent hash. It says nothing whatsoever about
//! **authorship**. Whoever can run SHA-256 owns the history, and the party
//! holding the store can always run SHA-256.
//!
//! That is a real limitation and not a theoretical one: the deployer is the
//! party an auditor is being asked to trust, and a chain they can regenerate
//! end-to-end is not evidence against them. A signature is.
//!
//! # What is signed, and why that is enough
//!
//! The record's **chain hash**, which already covers `prev_hash ‖ canonical
//! bytes`. Because the hash chains, one signature over record *n* transitively
//! commits to every record before it — so a forged prefix invalidates every
//! later signature, not just its own.
//!
//! Signing the hash rather than the body also avoids a circularity that is easy
//! to walk into: put the signature *inside* the body and the hash covers the
//! signature, which covers the hash.
//!
//! # This crate ships a seam, not a key manager
//!
//! Same reasoning as the policy engine and the tracing exporter. Production
//! signing identity belongs to the deployment's workload identity system —
//! SPIFFE SVIDs, which is what [`identity`](crate::core::identity) already
//! assumes and what the state of the art does. A crate that invented its own key
//! distribution would be wrong for every deployment that already has one.
//!
//! What the crate owns is the shape: a signature is attached where the chain is
//! sealed, verified where the chain is verified, and carries the **key id** so a
//! verifier can say *which* workload wrote a record rather than merely that
//! somebody with a key did.
//!
//! # What this still does not buy
//!
//! Signing binds authorship. It does not bind *existence*: an operator who
//! controls the signing identity can produce a perfectly signed alternative
//! history, and nothing here detects a whole run being deleted or two different
//! histories being shown to two auditors. Those need an anchor outside the
//! producing party — a witness-cosigned checkpoint — and that is deliberately a
//! separate mechanism rather than a bigger signature.
use Debug;
use ;
use crateDigest;
/// Names the key that produced a signature.
///
/// A SPIFFE ID in a deployment that has one (`spiffe://example.org/plane/a`),
/// or any stable string. Carried on every record because "somebody with a valid
/// key wrote this" is a much weaker statement than "this workload wrote this",
/// and the second is what an audit is asking for.
pub type KeyId = String;
/// A signature over a record's chain hash.
/// Signs a record's chain hash.
///
/// Implementations must be cheap enough to run on every append — this is on the
/// write path of every journaled effect — and must not perform I/O in `sign`. A
/// signer that calls out to a KMS per record turns the journal's write path into
/// a network dependency, which is the same mistake as a policy engine that can
/// fail open. Fetch and cache the credential elsewhere; sign locally.
/// Bind a signature to what it is *about*, not just to the bytes it covers.
///
/// [`Signer::sign`] takes a bare 32-byte digest, so a signature over a manifest
/// and a signature over a record's chain hash are structurally identical: the
/// same key, the same algorithm, the same input shape. Nothing in either says
/// which question it was answering. That is the classic cross-protocol
/// confusion, and the defence is to hash a domain label in alongside the payload
/// so the two can never be mistaken for one another.
///
/// The label is separated from the payload by a `0x00` byte, so no domain can be
/// a prefix of another with the boundary landing inside the payload — the same
/// reason canonical encodings length-prefix their fields.
///
/// **Universal for every signature this crate defines**, which it was not when
/// it was written: manifests used it and record attestations and provenance
/// seals signed their digest directly. The caveat recorded here at the time said
/// the argument for leaving them — that confusing two would need a preimage —
/// "stops holding when somebody adds a surface where the signer's input is more
/// attacker-shaped". Provenance sealing was then added and is exactly that
/// surface: its payload carries a caller-chosen `target` and an arguments
/// digest, and the sealed block travels to third-party tool servers and peers.
/// So the three are separated rather than argued about.
///
/// The **one** signature deliberately not routed through here is the checkpoint
/// cosignature. Its input is a C2SP `signed-note` body, which is an
/// interoperable artifact: what a signature over it must cover is the format's
/// business and not this crate's, so adding a label of ours would be this crate
/// unilaterally redefining somebody else's wire. Separation there comes from the
/// note's own structure — an origin line, a size and a root hash — rather than
/// from a prefix, and the encoding is pinned by its own tests.
/// The domain a manifest signature is made under.
pub const DOMAIN_MANIFEST: &str = "io.github.hupe1980.agentplane/manifest/v1";
/// The domain a journal record's attestation is made under.
///
/// Answers *this key appended this record to this chain*. Distinct from
/// [`DOMAIN_PROVENANCE`] because the two are signed by the **same** workload key
/// on the same plane, and an attestation lifted from one to the other would say
/// something nobody attested to.
pub const DOMAIN_RECORD: &str = "io.github.hupe1980.agentplane/record/v1";
/// The domain a provenance seal is made under.
///
/// Answers *this plane made this call, for this run, with these arguments*. The
/// most exposed of the three: the sealed block is handed to tool servers and A2A
/// peers, so its verifier is often somebody else's code and its payload contains
/// values a caller chose.
pub const DOMAIN_PROVENANCE: &str = "io.github.hupe1980.agentplane/provenance/v1";
/// Signs the rare, high-value things: checkpoints and cosignatures.
///
/// A deliberate second trait, and the split is about **granularity**, not taste.
/// [`Signer`] runs on the write path of every journaled effect, so it must not
/// perform I/O — a network round trip per record would make the journal
/// unavailable whenever a KMS is. That constraint is right there and wrong here.
///
/// A checkpoint is signed once per seal; a witness cosignature once per
/// observation. At that rate a network call costs nothing, and the key involved
/// is the most valuable in the system: a witness key is the trust anchor, so
/// keeping it in the memory of the process whose history it vouches for
/// concedes the property it exists to provide. Dedicated witness hardware is
/// where this role is going in the wider ecosystem, and a trait that forbids I/O
/// cannot reach it.
///
/// Fallible, unlike [`Signer`]. A local key cannot fail to sign; a KMS can be
/// throttled, unreachable, or have revoked the key. Returning `Vec<u8>`
/// infallibly would force every remote implementation to panic or to fabricate
/// a signature, and a fabricated signature is worse than an outage.
///
/// Any [`Signer`] is usable here, so a deployment holding a local key writes no
/// adapter.
/// Why a signature could not be produced.
///
/// Its own error rather than a string, because the two cases call for different
/// operator responses: a service that is merely unreachable will work again, and
/// a key that has been revoked or denied never will.
/// Every local signer is a checkpoint signer.
///
/// So the common deployment — one Ed25519 key held in process — needs no
/// adapter, and only somebody actually reaching for a KMS writes code.
/// Checks a signature against the key that claims to have made it.
///
/// Deliberately separate from [`Signer`]: an auditor verifies without being able
/// to sign, and that asymmetry is the entire point of using signatures rather
/// than a MAC. A verifier that could also sign would be a shared secret with
/// extra steps.
/// Why a chain's attestations were not acceptable.