Skip to content

Report upstream: subduction_crypto Signer trait is infallible - platform-backed signers can only trap #18

Description

@lannbot

The finding

Observed in the subduction spike, phase 2a (spikes/subduction/, commit 42b6193; discussion on #8), and carried through the walking skeleton (#8, commit 3177cbd):

subduction_crypto::Signer is infallible:

// subduction_crypto/src/signer.rs (rev 2401102)
pub trait Signer<Async: FutureForm> {
    fn sign(&self, message: &[u8]) -> Async::Future<'_, Signature>;
    fn verifying_key(&self) -> VerifyingKey;
}

A platform-backed signer — WebCrypto crypto.subtle, an OS keystore, an HSM — fails at runtime in ordinary ways: the key handle was evicted, the keystore is temporarily unavailable, the platform call errors. With this trait shape the failure has nowhere to go but a panic. In a wasm component a panic is a trap that poisons the whole instance — and in the engine-composite topology the instance also holds the keyhive state, the sedimentree store, and the automerge docs, so a transient signing hiccup would kill everything.

Where it bites in our code: the WebcryptoSigner impls in spikes/subduction/guest/src/lib.rs and spikes/skeleton/guest/src/lib.rs carry

.expect("webcrypto signing failed (Signer trait is infallible)")

with a comment marking it as this recorded finding.

The contrast is upstream's own sibling: keyhive's AsyncSigner::try_sign_bytes_async returns Result<Signature, SigningError>, its docs name WebCrypto and KMS as intended backends, and keyhive_wasm's own WebCrypto signer uses that fallibility. The two projects share authorship and the platform-held-key posture; subduction_crypto just predates or missed it.

Where the trait is exercised

The subduction handshake (Signed::seal in handshake::initiate/respond) and anything else signing through subduction_crypto::Signer. (The subduction_keyhive bridge signs its protocol messages through keyhive's fallible signer, so it is not affected.)

Suggested fix shape

Make the trait fallible, mirroring keyhive:

pub trait Signer<Async: FutureForm> {
    type Error: core::error::Error;
    fn sign(&self, message: &[u8]) -> Async::Future<'_, Result<Signature, Self::Error>>;
    fn verifying_key(&self) -> VerifyingKey;
}

MemorySigner sets Error = Infallible. Call sites (handshake) already return rich error enums with transport variants, so threading a Signing(S::Error) variant through is mechanical. Breaking, but the workspace is pre-1.0 and marked unstable.

Draft upstream issue (for inkandswitch/subduction — do not file without explicit go)

Title: [api]: Signer::sign is infallible — platform-backed signers (WebCrypto, keystores) have no failure path

subduction_crypto::Signer::sign returns Signature directly. Signers whose key lives behind a platform handle — browser WebCrypto (crypto.subtle.sign over a non-extractable CryptoKey), OS keystores, HSMs — can fail at runtime (handle evicted, store unavailable, transient platform error), and with the current shape the only expressible outcome is a panic. In a wasm embedding that panic is a component trap, which destroys the whole instance over what may be a transient condition.

Keyhive's AsyncSigner models this correctly (Result<Signature, SigningError>, with WebCrypto/KMS named as intended backends, and keyhive_wasm's own WebCrypto signer relying on it), so the ecosystems diverge on the same design question: a Signer impl that holds keys the keyhive way cannot satisfy subduction's trait honestly.

Suggested: make the trait fallible (type Error: core::error::Error; + Result<Signature, Self::Error>), with MemorySigner::Error = Infallible; the handshake error enums already have room for a Signing variant. Happy to note our embedding as a concrete consumer: we back the signer with a non-extractable WebCrypto Ed25519 handle inside a wasm32-wasip2 component (https://github.com/polymorph-components/polymorph-apps/tree/main/spikes/subduction), currently via .expect(...).

Status

  • Upstream issue filed (needs explicit go; the draft above is ready to paste)
  • Upstream response / fix tracked
  • On fix: bump the subduction pin (keyhive+subduction as a pair), replace the expect calls in the spike signers with real error propagation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions