lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

signer.rs (5900B)


      1 #![forbid(unsafe_code)]
      2 
      3 use crate::{RadrootsAuthorityError, RadrootsSignerError};
      4 #[cfg(test)]
      5 use radroots_events::draft::RadrootsSignedNostrEventParts;
      6 use radroots_events::draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent};
      7 use radroots_events::ids::RadrootsPublicKey;
      8 
      9 #[derive(Clone, Debug, PartialEq, Eq)]
     10 pub struct RadrootsSignerIdentity {
     11     pub pubkey: RadrootsPublicKey,
     12 }
     13 
     14 impl RadrootsSignerIdentity {
     15     pub fn new(pubkey: impl AsRef<str>) -> Result<Self, RadrootsAuthorityError> {
     16         let pubkey = RadrootsPublicKey::parse(pubkey.as_ref())
     17             .map_err(|_| RadrootsAuthorityError::InvalidSignerPubkey)?;
     18         Ok(Self { pubkey })
     19     }
     20 
     21     pub fn pubkey(&self) -> &RadrootsPublicKey {
     22         &self.pubkey
     23     }
     24 }
     25 
     26 pub trait RadrootsEventSigner {
     27     fn pubkey(&self) -> &RadrootsPublicKey;
     28 
     29     fn sign_frozen_draft(
     30         &self,
     31         draft: &RadrootsFrozenEventDraft,
     32     ) -> Result<RadrootsSignedNostrEvent, RadrootsSignerError>;
     33 }
     34 
     35 #[cfg(test)]
     36 mod tests {
     37     use super::*;
     38     use radroots_events::kinds::KIND_POST;
     39 
     40     fn hex_64(character: char) -> String {
     41         std::iter::repeat_n(character, 64).collect()
     42     }
     43 
     44     fn hex_128(character: char) -> String {
     45         std::iter::repeat_n(character, 128).collect()
     46     }
     47 
     48     fn draft_for(pubkey: &str) -> RadrootsFrozenEventDraft {
     49         RadrootsFrozenEventDraft::new(
     50             "radroots.social.post.v1",
     51             KIND_POST,
     52             1_700_000_000,
     53             vec![vec!["t".to_owned(), "soil".to_owned()]],
     54             "hello",
     55             pubkey,
     56         )
     57         .expect("draft")
     58     }
     59 
     60     struct MockSigner {
     61         pubkey: RadrootsPublicKey,
     62         failure: Option<RadrootsSignerError>,
     63     }
     64 
     65     impl MockSigner {
     66         fn new(pubkey: &str) -> Self {
     67             Self {
     68                 pubkey: RadrootsPublicKey::parse(pubkey).expect("pubkey"),
     69                 failure: None,
     70             }
     71         }
     72 
     73         fn failing(pubkey: &str, failure: RadrootsSignerError) -> Self {
     74             Self {
     75                 pubkey: RadrootsPublicKey::parse(pubkey).expect("pubkey"),
     76                 failure: Some(failure),
     77             }
     78         }
     79     }
     80 
     81     impl RadrootsEventSigner for MockSigner {
     82         fn pubkey(&self) -> &RadrootsPublicKey {
     83             &self.pubkey
     84         }
     85 
     86         fn sign_frozen_draft(
     87             &self,
     88             draft: &RadrootsFrozenEventDraft,
     89         ) -> Result<RadrootsSignedNostrEvent, RadrootsSignerError> {
     90             if let Some(failure) = &self.failure {
     91                 return Err(match failure {
     92                     RadrootsSignerError::Unavailable => RadrootsSignerError::Unavailable,
     93                     RadrootsSignerError::Rejected => RadrootsSignerError::Rejected,
     94                     RadrootsSignerError::SigningFailed { message } => {
     95                         RadrootsSignerError::SigningFailed {
     96                             message: message.clone(),
     97                         }
     98                     }
     99                 });
    100             }
    101             RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts {
    102                 id: draft.expected_event_id.to_string(),
    103                 pubkey: self.pubkey.to_string(),
    104                 created_at: draft.created_at,
    105                 kind: draft.kind,
    106                 tags: draft.tags.clone(),
    107                 content: draft.content.clone(),
    108                 sig: hex_128('f'),
    109                 raw_json: "{}".to_owned(),
    110             })
    111             .map_err(|error| RadrootsSignerError::SigningFailed {
    112                 message: error.to_string(),
    113             })
    114         }
    115     }
    116 
    117     #[test]
    118     fn mock_signer_reports_public_key() {
    119         let pubkey = hex_64('a');
    120         let signer = MockSigner::new(pubkey.as_str());
    121 
    122         assert_eq!(signer.pubkey().as_str(), pubkey);
    123     }
    124 
    125     #[test]
    126     fn signer_identity_validates_public_key() {
    127         let pubkey = hex_64('a');
    128         let identity = RadrootsSignerIdentity::new(pubkey.as_str()).expect("identity");
    129         assert_eq!(identity.pubkey().as_str(), pubkey);
    130 
    131         assert!(matches!(
    132             RadrootsSignerIdentity::new("bad-pubkey"),
    133             Err(RadrootsAuthorityError::InvalidSignerPubkey)
    134         ));
    135     }
    136 
    137     #[test]
    138     fn mock_signer_returns_signed_frozen_draft() {
    139         let pubkey = hex_64('a');
    140         let signer = MockSigner::new(pubkey.as_str());
    141         let draft = draft_for(pubkey.as_str());
    142 
    143         let signed = signer.sign_frozen_draft(&draft).expect("signed");
    144 
    145         assert_eq!(signed.id, draft.expected_event_id);
    146         assert_eq!(signed.pubkey, pubkey);
    147         assert_eq!(signed.kind, KIND_POST);
    148     }
    149 
    150     #[test]
    151     fn mock_signer_propagates_signing_errors() {
    152         let pubkey = hex_64('a');
    153         let draft = draft_for(pubkey.as_str());
    154 
    155         for failure in [
    156             RadrootsSignerError::Unavailable,
    157             RadrootsSignerError::Rejected,
    158             RadrootsSignerError::SigningFailed {
    159                 message: "deterministic failure".to_owned(),
    160             },
    161         ] {
    162             let signer = MockSigner::failing(pubkey.as_str(), failure);
    163             let err = signer.sign_frozen_draft(&draft).expect_err("failure");
    164 
    165             match err {
    166                 RadrootsSignerError::Unavailable => {}
    167                 RadrootsSignerError::Rejected => {}
    168                 RadrootsSignerError::SigningFailed { message } => {
    169                     assert_eq!(message, "deterministic failure");
    170                 }
    171             }
    172         }
    173     }
    174 
    175     #[test]
    176     fn mock_signer_maps_invalid_signed_event_parts() {
    177         let pubkey = hex_64('a');
    178         let signer = MockSigner::new(pubkey.as_str());
    179         let mut draft = draft_for(pubkey.as_str());
    180         draft.expected_event_id = "bad-id".to_string();
    181 
    182         let err = signer.sign_frozen_draft(&draft).expect_err("failure");
    183 
    184         assert!(matches!(err, RadrootsSignerError::SigningFailed { .. }));
    185     }
    186 }