Skip to main content

defuse_crypto/
signer.rs

1use std::{
2    fmt::{Debug, Display},
3    sync::{Arc, OnceLock},
4};
5
6use impl_tools::autoimpl;
7
8use crate::{Curve, RecoverableCurve};
9
10/// A signer capable of producing signatures for a specific [`Curve`].
11#[trait_variant::make(Send)]
12#[autoimpl(for<T: trait + ?Sized> &T, &mut T, Box<T>, Arc<T>)]
13pub trait Signer<C: Curve>: Sync {
14    /// An error that can occur during [signing](Self::sign).
15    type Error: Debug + Display;
16
17    /// Public key of the signer
18    fn public_key(&self) -> C::PublicKey;
19
20    /// Sign a given message and return a signature.
21    ///
22    /// NOTE: implementations MAY require `msg` to be prehash (i.e. output
23    /// of cryptographic hash function) of a fixed length and return
24    /// an error otherwise. Check corresponding docs before using.
25    async fn sign(&self, msg: &[u8]) -> Result<C::Signature, Self::Error>;
26
27    #[inline]
28    fn cache_public_key(self) -> CachePublicKey<C, Self>
29    where
30        Self: Sized,
31    {
32        CachePublicKey::new(self)
33    }
34}
35
36/// A [`Signer`] that can produce [recoverable](RecoverableCurve::recover)
37/// signatures.
38#[trait_variant::make(Send)]
39#[autoimpl(for<T: trait + ?Sized> &T, &mut T, Box<T>, Arc<T>)]
40pub trait RecoverableSigner<C: RecoverableCurve>: Signer<C> {
41    /// Sign a given message and return a signature along with recovery id.
42    ///
43    /// NOTE: implementations MAY require `msg` to be prehash (i.e. output
44    /// of cryptographic hash function) of a fixed length and return
45    /// an error otherwise. Check corresponding docs before using.
46    async fn sign_recoverable(
47        &self,
48        msg: &[u8],
49    ) -> Result<(C::Signature, C::RecoveryId), Self::Error>;
50}
51
52/// TODO: docs
53#[autoimpl(Deref using self.signer)]
54#[autoimpl(Debug, Clone, PartialEq, Eq where C::PublicKey: trait, S: trait)]
55pub struct CachePublicKey<C: Curve, S> {
56    public_key: OnceLock<C::PublicKey>,
57    signer: S,
58}
59
60impl<C: Curve, S> CachePublicKey<C, S> {
61    #[inline]
62    const fn new(signer: S) -> Self {
63        Self {
64            public_key: OnceLock::new(),
65            signer,
66        }
67    }
68}
69
70impl<C, S> Signer<C> for CachePublicKey<C, S>
71where
72    C: Curve,
73    C::PublicKey: Clone + Send + Sync,
74    S: Signer<C>,
75{
76    type Error = S::Error;
77
78    #[inline]
79    fn public_key(&self) -> C::PublicKey {
80        self.public_key
81            .get_or_init(|| self.signer.public_key())
82            .clone()
83    }
84
85    async fn sign(&self, msg: &[u8]) -> Result<C::Signature, Self::Error> {
86        self.signer.sign(msg).await
87    }
88}
89
90impl<C, S> RecoverableSigner<C> for CachePublicKey<C, S>
91where
92    C: RecoverableCurve,
93    C::PublicKey: Clone + Send + Sync,
94    S: RecoverableSigner<C>,
95{
96    async fn sign_recoverable(
97        &self,
98        msg: &[u8],
99    ) -> Result<(<C>::Signature, <C as RecoverableCurve>::RecoveryId), Self::Error> {
100        self.signer.sign_recoverable(msg).await
101    }
102}
103
104/// Test helpers
105#[cfg(test)]
106#[allow(dead_code, clippy::redundant_pub_crate)]
107pub(crate) mod tests {
108    use std::fmt::Debug;
109
110    use super::*;
111
112    pub async fn test_sign_verify<C: Curve, S: Signer<C>>(signer: S, msg: impl AsRef<[u8]>) {
113        let msg = msg.as_ref();
114        let signature = signer.sign(msg).await.unwrap();
115        assert!(
116            C::verify(&signer.public_key(), msg, &signature),
117            "signer produced invalid signature"
118        );
119    }
120
121    pub async fn test_sign_recover<C, S>(signer: S, msg: impl AsRef<[u8]>)
122    where
123        C: RecoverableCurve<PublicKey: PartialEq + Debug>,
124        S: RecoverableSigner<C>,
125    {
126        let msg = msg.as_ref();
127        let (signature, recovery_id) = signer.sign_recoverable(msg).await.unwrap();
128
129        assert_eq!(
130            C::recover(msg, &signature, recovery_id),
131            Some(signer.public_key()),
132            "can't recover signer's public key"
133        );
134    }
135}