lib

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

request.rs (13926B)


      1 //! Validated authored-plan signing requests.
      2 
      3 use core::{
      4     fmt,
      5     sync::atomic::{AtomicBool, Ordering},
      6 };
      7 use radroots_event::contract::event_contract;
      8 use radroots_event_codec::authoring::{AuthoredEventPlan, BlossomAuthorizationPlan, PlanDigest};
      9 use radroots_identity::PublicKey;
     10 use radroots_protocol::runtime::v1::OperationId;
     11 
     12 #[cfg(not(feature = "std"))]
     13 use alloc::sync::Arc;
     14 #[cfg(feature = "std")]
     15 use std::sync::Arc;
     16 
     17 use crate::{
     18     Actor, Error, SignerRequestId, SigningIntentId,
     19     authorization::{
     20         CurrentAuthoringAuthority, CurrentAuthoringDecision, CurrentRegistryAuthority,
     21         DeprecatedPlanPolicy, ManagedSigningPolicy,
     22     },
     23     error::Kind,
     24     status::SignProgress,
     25 };
     26 
     27 /// How a signer must interpret cancellation around remote publication.
     28 #[non_exhaustive]
     29 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     30 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
     31 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     32 pub enum CancellationPolicy {
     33     PreservePublishedRequest,
     34     LocalCooperative,
     35 }
     36 
     37 /// Runtime-local cooperative cancellation shared by caller and signer.
     38 #[derive(Clone, Debug, Default)]
     39 pub struct CancellationSignal(Arc<AtomicBool>);
     40 
     41 impl CancellationSignal {
     42     #[must_use]
     43     pub fn new() -> Self {
     44         Self::default()
     45     }
     46 
     47     pub fn cancel(&self) {
     48         self.0.store(true, Ordering::Release);
     49     }
     50 
     51     #[must_use]
     52     pub fn is_cancelled(&self) -> bool {
     53         self.0.load(Ordering::Acquire)
     54     }
     55 }
     56 
     57 /// Explicit millisecond deadline and authorization/cancellation policy.
     58 #[non_exhaustive]
     59 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
     60 #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
     61 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     62 pub struct SignPolicy {
     63     deadline_unix_ms: u64,
     64     cancellation: CancellationPolicy,
     65     deprecated_plan: DeprecatedPlanPolicy,
     66     managed_signing: ManagedSigningPolicy,
     67 }
     68 
     69 impl SignPolicy {
     70     pub const fn new(
     71         deadline_unix_ms: u64,
     72         cancellation: CancellationPolicy,
     73     ) -> Result<Self, Error> {
     74         if deadline_unix_ms == 0 {
     75             return Err(Error::new(Kind::InvalidArgument));
     76         }
     77         Ok(Self {
     78             deadline_unix_ms,
     79             cancellation,
     80             deprecated_plan: DeprecatedPlanPolicy::Deny,
     81             managed_signing: ManagedSigningPolicy::AnyValidatedSource,
     82         })
     83     }
     84 
     85     #[must_use]
     86     pub const fn allowing_deprecated(mut self) -> Self {
     87         self.deprecated_plan = DeprecatedPlanPolicy::Allow;
     88         self
     89     }
     90 
     91     #[must_use]
     92     pub const fn with_managed_signing_policy(mut self, policy: ManagedSigningPolicy) -> Self {
     93         self.managed_signing = policy;
     94         self
     95     }
     96 
     97     #[must_use]
     98     pub const fn deadline_unix_ms(self) -> u64 {
     99         self.deadline_unix_ms
    100     }
    101 
    102     #[must_use]
    103     pub const fn cancellation(self) -> CancellationPolicy {
    104         self.cancellation
    105     }
    106 
    107     #[must_use]
    108     pub const fn deprecated_plan(self) -> DeprecatedPlanPolicy {
    109         self.deprecated_plan
    110     }
    111 
    112     #[must_use]
    113     pub const fn managed_signing(self) -> ManagedSigningPolicy {
    114         self.managed_signing
    115     }
    116 }
    117 
    118 #[cfg(feature = "serde")]
    119 impl<'de> serde::Deserialize<'de> for SignPolicy {
    120     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    121     where
    122         D: serde::Deserializer<'de>,
    123     {
    124         #[derive(serde::Deserialize)]
    125         #[serde(deny_unknown_fields)]
    126         struct Repr {
    127             deadline_unix_ms: u64,
    128             cancellation: CancellationPolicy,
    129             deprecated_plan: DeprecatedPlanPolicy,
    130             managed_signing: ManagedSigningPolicy,
    131         }
    132 
    133         let value = Repr::deserialize(deserializer)?;
    134         let mut policy = Self::new(value.deadline_unix_ms, value.cancellation)
    135             .map_err(serde::de::Error::custom)?;
    136         policy.deprecated_plan = value.deprecated_plan;
    137         policy.managed_signing = value.managed_signing;
    138         Ok(policy)
    139     }
    140 }
    141 
    142 /// Runtime-local observer for signing progress.
    143 pub trait ProgressObserver: Send + Sync {
    144     fn on_progress(&self, progress: &SignProgress);
    145 }
    146 
    147 /// Domain-separated reason an exact Nostr event is being signed.
    148 #[non_exhaustive]
    149 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    150 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
    151 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    152 pub enum SigningPurpose {
    153     /// A registry-authorized event that may enter the durable relay pipeline.
    154     AuthoredEvent,
    155     /// A short-lived BUD-11 upload credential for an HTTP request only.
    156     BlossomUploadAuthorization,
    157 }
    158 
    159 #[derive(Clone)]
    160 enum SigningPlan {
    161     Authored(AuthoredEventPlan),
    162     BlossomUpload(BlossomAuthorizationPlan),
    163 }
    164 
    165 impl SigningPlan {
    166     const fn purpose(&self) -> SigningPurpose {
    167         match self {
    168             Self::Authored(_) => SigningPurpose::AuthoredEvent,
    169             Self::BlossomUpload(_) => SigningPurpose::BlossomUploadAuthorization,
    170         }
    171     }
    172 
    173     const fn author(&self) -> &PublicKey {
    174         match self {
    175             Self::Authored(plan) => plan.author(),
    176             Self::BlossomUpload(plan) => plan.author(),
    177         }
    178     }
    179 
    180     const fn created_at(&self) -> u64 {
    181         match self {
    182             Self::Authored(plan) => plan.created_at(),
    183             Self::BlossomUpload(plan) => plan.created_at(),
    184         }
    185     }
    186 
    187     const fn kind(&self) -> u32 {
    188         match self {
    189             Self::Authored(plan) => plan.body().kind(),
    190             Self::BlossomUpload(plan) => plan.kind(),
    191         }
    192     }
    193 
    194     fn tags(&self) -> &[alloc_or_std::Vec<alloc_or_std::String>] {
    195         match self {
    196             Self::Authored(plan) => plan.body().tags(),
    197             Self::BlossomUpload(plan) => plan.tags(),
    198         }
    199     }
    200 
    201     fn content(&self) -> &str {
    202         match self {
    203             Self::Authored(plan) => plan.body().content(),
    204             Self::BlossomUpload(plan) => plan.content(),
    205         }
    206     }
    207 
    208     const fn expected_event_id(&self) -> &radroots_event::EventId {
    209         match self {
    210             Self::Authored(plan) => plan.expected_event_id(),
    211             Self::BlossomUpload(plan) => plan.expected_event_id(),
    212         }
    213     }
    214 
    215     const fn digest(&self) -> PlanDigest {
    216         match self {
    217             Self::Authored(plan) => plan.digest(),
    218             Self::BlossomUpload(plan) => plan.digest(),
    219         }
    220     }
    221 }
    222 
    223 #[cfg(not(feature = "std"))]
    224 mod alloc_or_std {
    225     pub use alloc::{string::String, vec::Vec};
    226 }
    227 #[cfg(feature = "std")]
    228 mod alloc_or_std {
    229     pub use std::{string::String, vec::Vec};
    230 }
    231 
    232 /// One currently authorized exact plan and bounded signer invocation.
    233 #[derive(Clone)]
    234 pub struct SignRequest {
    235     operation_kind: OperationId,
    236     intent_id: SigningIntentId,
    237     signer_request_id: SignerRequestId,
    238     actor: Actor,
    239     plan: SigningPlan,
    240     authorization: Option<CurrentAuthoringDecision>,
    241     policy: SignPolicy,
    242     cancellation_signal: CancellationSignal,
    243     progress_observer: Option<Arc<dyn ProgressObserver>>,
    244 }
    245 
    246 impl SignRequest {
    247     pub fn new(
    248         operation_kind: OperationId,
    249         intent_id: SigningIntentId,
    250         actor: Actor,
    251         plan: AuthoredEventPlan,
    252         policy: SignPolicy,
    253     ) -> Result<Self, Error> {
    254         Self::new_with_authority(
    255             operation_kind,
    256             intent_id,
    257             actor,
    258             plan,
    259             policy,
    260             &CurrentRegistryAuthority,
    261         )
    262     }
    263 
    264     pub fn new_with_authority(
    265         operation_kind: OperationId,
    266         intent_id: SigningIntentId,
    267         actor: Actor,
    268         plan: AuthoredEventPlan,
    269         policy: SignPolicy,
    270         authority: &dyn CurrentAuthoringAuthority,
    271     ) -> Result<Self, Error> {
    272         let authorization = authority.evaluate(&plan);
    273         authorize(&actor, &plan, policy, authorization)?;
    274         let plan = SigningPlan::Authored(plan);
    275         let signer_request_id = SignerRequestId::derive(intent_id.artifact_id(), plan.digest());
    276         Ok(Self {
    277             operation_kind,
    278             intent_id,
    279             signer_request_id,
    280             actor,
    281             plan,
    282             authorization: Some(authorization),
    283             policy,
    284             cancellation_signal: CancellationSignal::new(),
    285             progress_observer: None,
    286         })
    287     }
    288 
    289     /// Creates a bounded HTTP-only BUD-11 upload authorization request.
    290     pub fn blossom_upload(
    291         operation_kind: OperationId,
    292         intent_id: SigningIntentId,
    293         actor: Actor,
    294         plan: BlossomAuthorizationPlan,
    295         policy: SignPolicy,
    296     ) -> Result<Self, Error> {
    297         if actor.public_key() != *plan.author() || !policy.managed_signing().permits(&actor) {
    298             return Err(Error::new(Kind::AuthorizationDenied));
    299         }
    300         let plan = SigningPlan::BlossomUpload(plan);
    301         let signer_request_id = SignerRequestId::derive(intent_id.artifact_id(), plan.digest());
    302         Ok(Self {
    303             operation_kind,
    304             intent_id,
    305             signer_request_id,
    306             actor,
    307             plan,
    308             authorization: None,
    309             policy,
    310             cancellation_signal: CancellationSignal::new(),
    311             progress_observer: None,
    312         })
    313     }
    314 
    315     #[must_use]
    316     pub fn with_cancellation_signal(mut self, signal: CancellationSignal) -> Self {
    317         self.cancellation_signal = signal;
    318         self
    319     }
    320 
    321     #[must_use]
    322     pub fn with_progress_observer(mut self, observer: Arc<dyn ProgressObserver>) -> Self {
    323         self.progress_observer = Some(observer);
    324         self
    325     }
    326 
    327     #[must_use]
    328     pub const fn operation_kind(&self) -> OperationId {
    329         self.operation_kind
    330     }
    331 
    332     #[must_use]
    333     pub const fn intent_id(&self) -> SigningIntentId {
    334         self.intent_id
    335     }
    336 
    337     #[must_use]
    338     pub const fn signer_request_id(&self) -> SignerRequestId {
    339         self.signer_request_id
    340     }
    341 
    342     #[must_use]
    343     pub const fn actor(&self) -> &Actor {
    344         &self.actor
    345     }
    346 
    347     #[must_use]
    348     pub const fn purpose(&self) -> SigningPurpose {
    349         self.plan.purpose()
    350     }
    351 
    352     /// Returns the registry-authored plan, if this is a relay-event request.
    353     #[must_use]
    354     pub const fn authored_plan(&self) -> Option<&AuthoredEventPlan> {
    355         match &self.plan {
    356             SigningPlan::Authored(plan) => Some(plan),
    357             SigningPlan::BlossomUpload(_) => None,
    358         }
    359     }
    360 
    361     /// Returns the HTTP-only upload-authorization plan, when applicable.
    362     #[must_use]
    363     pub const fn blossom_authorization_plan(&self) -> Option<&BlossomAuthorizationPlan> {
    364         match &self.plan {
    365             SigningPlan::Authored(_) => None,
    366             SigningPlan::BlossomUpload(plan) => Some(plan),
    367         }
    368     }
    369 
    370     #[must_use]
    371     pub const fn expected_author(&self) -> &PublicKey {
    372         self.plan.author()
    373     }
    374 
    375     #[must_use]
    376     pub const fn created_at(&self) -> u64 {
    377         self.plan.created_at()
    378     }
    379 
    380     #[must_use]
    381     pub const fn kind(&self) -> u32 {
    382         self.plan.kind()
    383     }
    384 
    385     #[must_use]
    386     pub fn tags(&self) -> &[alloc_or_std::Vec<alloc_or_std::String>] {
    387         self.plan.tags()
    388     }
    389 
    390     #[must_use]
    391     pub fn content(&self) -> &str {
    392         self.plan.content()
    393     }
    394 
    395     #[must_use]
    396     pub const fn expected_event_id(&self) -> &radroots_event::EventId {
    397         self.plan.expected_event_id()
    398     }
    399 
    400     #[must_use]
    401     pub const fn plan_digest(&self) -> PlanDigest {
    402         self.plan.digest()
    403     }
    404 
    405     #[must_use]
    406     pub const fn authorization(&self) -> Option<CurrentAuthoringDecision> {
    407         self.authorization
    408     }
    409 
    410     #[must_use]
    411     pub const fn policy(&self) -> SignPolicy {
    412         self.policy
    413     }
    414 
    415     #[must_use]
    416     pub const fn cancellation_signal(&self) -> &CancellationSignal {
    417         &self.cancellation_signal
    418     }
    419 
    420     pub fn ensure_active(&self, now_unix_ms: u64) -> Result<(), Error> {
    421         if self.cancellation_signal.is_cancelled() {
    422             return Err(Error::new(Kind::SignerCancelled));
    423         }
    424         if now_unix_ms >= self.policy.deadline_unix_ms {
    425             return Err(Error::new(Kind::DeadlineExceeded));
    426         }
    427         Ok(())
    428     }
    429 
    430     pub fn report_progress(&self, progress: &SignProgress) {
    431         if let Some(observer) = &self.progress_observer {
    432             observer.on_progress(progress);
    433         }
    434     }
    435 }
    436 
    437 fn authorize(
    438     actor: &Actor,
    439     plan: &AuthoredEventPlan,
    440     policy: SignPolicy,
    441     decision: CurrentAuthoringDecision,
    442 ) -> Result<(), Error> {
    443     match decision {
    444         CurrentAuthoringDecision::Allowed => {}
    445         CurrentAuthoringDecision::AllowedDeprecated { .. }
    446             if policy.deprecated_plan() == DeprecatedPlanPolicy::Allow => {}
    447         CurrentAuthoringDecision::AllowedDeprecated { .. }
    448         | CurrentAuthoringDecision::Blocked { .. }
    449         | CurrentAuthoringDecision::Revoked { .. } => {
    450             return Err(Error::new(Kind::AuthorizationDenied));
    451         }
    452     }
    453     let contract = event_contract(plan.body().contract().contract_id().as_str())
    454         .ok_or_else(|| Error::new(Kind::AuthorizationDenied))?;
    455     if !actor.satisfies(contract.required_author_role())
    456         || actor.public_key() != *plan.author()
    457         || !policy.managed_signing().permits(actor)
    458     {
    459         return Err(Error::new(Kind::AuthorizationDenied));
    460     }
    461     Ok(())
    462 }
    463 
    464 impl fmt::Debug for SignRequest {
    465     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    466         formatter
    467             .debug_struct("SignRequest")
    468             .field("operation_kind", &self.operation_kind)
    469             .field("intent_id", &self.intent_id)
    470             .field("signer_request_id", &self.signer_request_id)
    471             .field("actor", &self.actor)
    472             .field("purpose", &self.purpose())
    473             .field("plan", &"[redacted exact event plan]")
    474             .field("authorization", &self.authorization)
    475             .field("policy", &self.policy)
    476             .finish_non_exhaustive()
    477     }
    478 }