pub trait CheckpointSigner:
Send
+ Sync
+ Debug {
// Required methods
fn key_id(&self) -> KeyId;
fn sign<'life0, 'life1, 'async_trait>(
&'life0 self,
message: &'life1 [u8],
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, SignError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
}Expand description
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.
Required Methods§
Sourcefn sign<'life0, 'life1, 'async_trait>(
&'life0 self,
message: &'life1 [u8],
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, SignError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn sign<'life0, 'life1, 'async_trait>(
&'life0 self,
message: &'life1 [u8],
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, SignError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Sign the message, or say why not.
Bytes, not a Digest, and the difference is interoperability rather
than taste. A checkpoint’s signature travels as a C2SP signed-note
line, and that format specifies a signature over the note text: pure
Ed25519, not Ed25519 over a pre-hash. Signing the hash instead produces
sixty-four bytes of the right algorithm under the right key that verify
against no witness, no auditor and no signed-note implementation.
CardSigner carries the same reasoning for
JWS — “signing its hash instead would verify perfectly here and nowhere
else”.
§Errors
If the signing service refuses or cannot be reached.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementors§
impl CheckpointSigner for Ed25519Signer
Signing a checkpoint is signing bytes, for the same reason a card is.
C2SP signed-note specifies pure Ed25519 over the note text. Signing
SHA-256(note) instead — which is what a digest-shaped signer does —
produces 64 bytes that verify against nothing any witness, auditor or
signed-note tool will compute.
impl CheckpointSigner for StubSigner
Also a checkpoint signer, and equally forgeable.
Signs the message rather than a digest of it, matching what a real witness key does, so a test reaching for this stub exercises production’s shape.