Skip to main content

CheckpointSigner

Trait CheckpointSigner 

Source
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§

Source

fn key_id(&self) -> KeyId

The identity this signer writes as.

Source

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§

Source§

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.

Source§

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.