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 }