lib

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

evidence.rs (9635B)


      1 use super::*;
      2 use radroots_signing::{AuthoredSignEvidence, Signer, SignerStatus, signer::BoxFuture};
      3 use std::{
      4     sync::atomic::{AtomicUsize, Ordering},
      5     task::{Context, Poll, Waker},
      6 };
      7 
      8 fn ready<T>(mut future: BoxFuture<'_, T>) -> T {
      9     match future
     10         .as_mut()
     11         .poll(&mut Context::from_waker(Waker::noop()))
     12     {
     13         Poll::Ready(value) => value,
     14         Poll::Pending => panic!("fixture must complete without an executor"),
     15     }
     16 }
     17 
     18 #[test]
     19 fn late_and_cancelled_evidence_preserves_exact_bytes_without_active_success() {
     20     let request = request();
     21     let event = signed_event();
     22     let evidence = AuthoredSignEvidence::from_signed_event(&request, event.clone(), DEADLINE_MS)
     23         .expect("late signature remains a fact");
     24     assert_eq!(evidence.operation_kind(), request.operation_kind());
     25     assert_eq!(evidence.intent_id(), request.intent_id());
     26     assert_eq!(evidence.signer_request_id(), request.signer_request_id());
     27     assert_eq!(evidence.observed_at_unix_ms(), DEADLINE_MS);
     28     assert_eq!(evidence.signed_event(), &event);
     29     assert_eq!(
     30         SignReceipt::from_signed_event(&request, event.clone(), DEADLINE_MS)
     31             .unwrap_err()
     32             .kind(),
     33         Kind::DeadlineExceeded
     34     );
     35     request.cancellation_signal().cancel();
     36     let later = evidence
     37         .revalidate(&request, DEADLINE_MS + 1)
     38         .expect("retain after cancellation");
     39     assert_eq!(later.signed_event(), &event);
     40     assert_eq!(later.observed_at_unix_ms(), DEADLINE_MS + 1);
     41     assert_eq!(
     42         SignReceipt::from_signed_event(&request, event.clone(), DEADLINE_MS - 1)
     43             .unwrap_err()
     44             .kind(),
     45         Kind::SignerCancelled
     46     );
     47     assert_eq!(
     48         AuthoredSignEvidence::from_signed_event(&request, event, 0)
     49             .unwrap_err()
     50             .kind(),
     51         Kind::InvalidArgument
     52     );
     53     assert_eq!(
     54         later.revalidate(&request, 0).unwrap_err().kind(),
     55         Kind::InvalidArgument
     56     );
     57     let debug = format!("{later:?}");
     58     assert!(debug.contains(later.signed_event().id_str()));
     59     assert!(!debug.contains("exact signing plan"));
     60     assert!(!debug.contains(&later.signed_event().wire().sig));
     61     assert_eq!(later, later.clone());
     62     #[cfg(feature = "serde")]
     63     {
     64         let json = serde_json::to_value(&later).expect("serialize evidence");
     65         assert_eq!(json["observed_at_unix_ms"], DEADLINE_MS + 1);
     66         assert!(json.get("signed_event").is_some());
     67     }
     68 }
     69 
     70 #[test]
     71 fn evidence_rejects_wrong_intent_even_for_the_identical_signed_event() {
     72     let expected = request();
     73     let evidence =
     74         AuthoredSignEvidence::from_signed_event(&expected, signed_event(), DEADLINE_MS).unwrap();
     75     for identity in [intent(3, 2), intent(1, 3)] {
     76         let other = SignRequest::new(
     77             OperationId::SyncPush,
     78             identity,
     79             actor(ActorSource::ExplicitPublicKey, [AuthorRole::Any]),
     80             plan(),
     81             policy(),
     82         )
     83         .unwrap();
     84         assert_eq!(other.expected_event_id(), expected.expected_event_id());
     85         assert_eq!(
     86             evidence.revalidate(&other, DEADLINE_MS).unwrap_err().kind(),
     87             Kind::SignerOutputInvalid
     88         );
     89     }
     90     let other_kind = SignRequest::new(
     91         OperationId::SyncPull,
     92         intent(1, 2),
     93         actor(ActorSource::ExplicitPublicKey, [AuthorRole::Any]),
     94         plan(),
     95         policy(),
     96     )
     97     .unwrap();
     98     assert_eq!(
     99         evidence
    100             .revalidate(&other_kind, DEADLINE_MS)
    101             .unwrap_err()
    102             .kind(),
    103         Kind::SignerOutputInvalid
    104     );
    105     let other_plan = AuthoredEventPlan::from_generic(
    106         GenericEventDraft::new(
    107             "radroots.social.geochat.v1",
    108             20_000,
    109             CREATED_AT,
    110             Vec::new(),
    111             "different plan",
    112             public_key().to_hex(),
    113         )
    114         .unwrap(),
    115     )
    116     .unwrap();
    117     let other_request = SignRequest::new(
    118         OperationId::SyncPush,
    119         intent(1, 2),
    120         actor(ActorSource::ExplicitPublicKey, [AuthorRole::Any]),
    121         other_plan,
    122         policy(),
    123     )
    124     .unwrap();
    125     assert_eq!(
    126         evidence
    127             .revalidate(&other_request, DEADLINE_MS)
    128             .unwrap_err()
    129             .kind(),
    130         Kind::SignerOutputInvalid
    131     );
    132 }
    133 
    134 #[test]
    135 fn late_evidence_keeps_cryptographic_and_exact_plan_checks() {
    136     let request = request();
    137     let other_keys = Keys::new(SecretKey::from_hex(&"11".repeat(32)).unwrap());
    138     for event in [
    139         signed_event_with(
    140             &other_keys,
    141             20_000,
    142             "exact signing plan",
    143             CREATED_AT,
    144             Vec::new(),
    145         ),
    146         signed_event_with(
    147             &keys(),
    148             20_000,
    149             "exact signing plan",
    150             CREATED_AT + 1,
    151             Vec::new(),
    152         ),
    153         signed_event_with(
    154             &keys(),
    155             20_001,
    156             "exact signing plan",
    157             CREATED_AT,
    158             Vec::new(),
    159         ),
    160         signed_event_with(
    161             &keys(),
    162             20_000,
    163             "exact signing plan",
    164             CREATED_AT,
    165             vec![nostr::Tag::parse(["t", "wrong"]).unwrap()],
    166         ),
    167         signed_event_with(&keys(), 20_000, "wrong content", CREATED_AT, Vec::new()),
    168     ] {
    169         assert_eq!(
    170             AuthoredSignEvidence::from_signed_event(&request, event, DEADLINE_MS + 1)
    171                 .unwrap_err()
    172                 .kind(),
    173             Kind::SignerOutputInvalid
    174         );
    175     }
    176     let invalid = invalid_signature_event();
    177     assert_eq!(
    178         AuthoredSignEvidence::from_signed_event(&request, invalid, DEADLINE_MS + 1)
    179             .unwrap_err()
    180             .kind(),
    181         Kind::SignerOutputInvalid
    182     );
    183 }
    184 
    185 struct LegacySigner {
    186     receipt_request: SignRequest,
    187     calls: AtomicUsize,
    188     fail: bool,
    189 }
    190 impl Signer for LegacySigner {
    191     fn status(&self) -> BoxFuture<'_, Result<SignerStatus, Error>> {
    192         Box::pin(async { Ok(SignerStatus::unavailable()) })
    193     }
    194     fn sign(&self, _request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, Error>> {
    195         self.calls.fetch_add(1, Ordering::SeqCst);
    196         Box::pin(async {
    197             if self.fail {
    198                 return Err(Error::new(Kind::SignerUnavailable));
    199             }
    200             SignReceipt::from_signed_event(&self.receipt_request, signed_event(), DEADLINE_MS - 1)
    201         })
    202     }
    203 }
    204 
    205 #[test]
    206 fn default_hook_preserves_legacy_adapters_and_rejects_wrong_receipt_binding() {
    207     let signer = LegacySigner {
    208         receipt_request: request(),
    209         calls: AtomicUsize::new(0),
    210         fail: false,
    211     };
    212     let object: &dyn Signer = &signer;
    213     let result = ready(object.sign_authored_evidence(request())).unwrap();
    214     assert_eq!(result.intent_id(), request().intent_id());
    215     assert_eq!(result.observed_at_unix_ms(), DEADLINE_MS - 1);
    216     assert_eq!(signer.calls.load(Ordering::SeqCst), 1);
    217     let wrong = SignRequest::new(
    218         OperationId::SyncPush,
    219         intent(4, 2),
    220         actor(ActorSource::ExplicitPublicKey, [AuthorRole::Any]),
    221         plan(),
    222         policy(),
    223     )
    224     .unwrap();
    225     assert_eq!(
    226         ready(object.sign_authored_evidence(wrong))
    227             .unwrap_err()
    228             .kind(),
    229         Kind::SignerOutputInvalid
    230     );
    231     let unavailable = LegacySigner {
    232         receipt_request: request(),
    233         calls: AtomicUsize::new(0),
    234         fail: true,
    235     };
    236     assert_eq!(
    237         ready(unavailable.sign_authored_evidence(request()))
    238             .unwrap_err()
    239             .kind(),
    240         Kind::SignerUnavailable
    241     );
    242 }
    243 
    244 #[test]
    245 fn blossom_credentials_cannot_use_authored_evidence_or_invoke_default_signer() {
    246     let request = blossom_request();
    247     let event = signed_event_with(
    248         &keys(),
    249         24_242,
    250         request.content(),
    251         request.created_at(),
    252         request
    253             .tags()
    254             .iter()
    255             .cloned()
    256             .map(nostr::Tag::parse)
    257             .collect::<Result<Vec<_>, _>>()
    258             .unwrap(),
    259     );
    260     assert!(SignReceipt::from_signed_event(&request, event.clone(), DEADLINE_MS - 1).is_ok());
    261     for time in [DEADLINE_MS - 1, DEADLINE_MS, DEADLINE_MS + 1] {
    262         assert_eq!(
    263             AuthoredSignEvidence::from_signed_event(&request, event.clone(), time)
    264                 .unwrap_err()
    265                 .kind(),
    266             Kind::InvalidArgument
    267         );
    268     }
    269     assert_eq!(
    270         SignReceipt::from_signed_event(&request, event.clone(), DEADLINE_MS)
    271             .unwrap_err()
    272             .kind(),
    273         Kind::DeadlineExceeded
    274     );
    275     request.cancellation_signal().cancel();
    276     assert_eq!(
    277         SignReceipt::from_signed_event(&request, event, DEADLINE_MS - 1)
    278             .unwrap_err()
    279             .kind(),
    280         Kind::SignerCancelled
    281     );
    282     let signer = LegacySigner {
    283         receipt_request: super::request(),
    284         calls: AtomicUsize::new(0),
    285         fail: false,
    286     };
    287     assert_eq!(
    288         ready(signer.sign_authored_evidence(request))
    289             .unwrap_err()
    290             .kind(),
    291         Kind::InvalidArgument
    292     );
    293     assert_eq!(signer.calls.load(Ordering::SeqCst), 0);
    294 }
    295 
    296 #[test]
    297 fn default_hook_does_not_invoke_an_adapter_for_already_cancelled_work() {
    298     let request = request();
    299     request.cancellation_signal().cancel();
    300     let signer = LegacySigner {
    301         receipt_request: super::request(),
    302         calls: AtomicUsize::new(0),
    303         fail: false,
    304     };
    305     assert_eq!(
    306         ready(signer.sign_authored_evidence(request))
    307             .unwrap_err()
    308             .kind(),
    309         Kind::SignerCancelled
    310     );
    311     assert_eq!(signer.calls.load(Ordering::SeqCst), 0);
    312 }