agentplane/testkit/stub_signer.rs
1//! A signer that is not cryptography.
2//!
3//! Exists so the store conformance battery can check that a backend **keeps**
4//! the signature it was given. That is a persistence question, not a
5//! cryptographic one, and pulling a real signature scheme into the battery would
6//! make every backend author install a crypto dependency to prove their `INSERT`
7//! has the right number of columns.
8//!
9//! Deliberately trivial and deliberately named. Nothing here is unforgeable —
10//! the "signature" is the hash with a fixed byte prepended — and a deployment
11//! that reached for this instead of a real [`Signer`](crate::core::Signer) would
12//! have records that look attested and prove nothing. It lives in `testkit`,
13//! which is off by default and documented as never belonging in a production
14//! build, for exactly that reason.
15
16use crate::core::{CheckpointSigner, Digest, KeyId, SignError, Signer, Verifier};
17
18/// Produces a deterministic, forgeable "signature".
19#[derive(Debug, Clone)]
20pub struct StubSigner {
21 key_id: KeyId,
22}
23
24impl StubSigner {
25 #[must_use]
26 pub fn new(key_id: impl Into<KeyId>) -> Self {
27 Self {
28 key_id: key_id.into(),
29 }
30 }
31}
32
33impl Default for StubSigner {
34 fn default() -> Self {
35 Self::new("testkit://stub")
36 }
37}
38
39impl Signer for StubSigner {
40 fn key_id(&self) -> KeyId {
41 self.key_id.clone()
42 }
43
44 fn sign(&self, hash: &Digest) -> Vec<u8> {
45 let mut out = Vec::with_capacity(33);
46 out.push(0xAB);
47 out.extend_from_slice(hash.as_bytes());
48 out
49 }
50}
51
52/// Also a checkpoint signer, and equally forgeable.
53///
54/// Signs the message rather than a digest of it, matching what a real witness
55/// key does, so a test reaching for this stub exercises production's shape.
56#[async_trait::async_trait]
57impl CheckpointSigner for StubSigner {
58 fn key_id(&self) -> KeyId {
59 self.key_id.clone()
60 }
61
62 async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, SignError> {
63 let mut out = Vec::with_capacity(33);
64 out.push(0xAB);
65 out.extend_from_slice(Digest::of(message).as_bytes());
66 Ok(out)
67 }
68}
69
70impl Verifier for StubSigner {
71 fn verify(&self, key_id: &str, hash: &Digest, signature: &[u8]) -> bool {
72 key_id == self.key_id && signature == Signer::sign(self, hash)
73 }
74}