receipt.rs (7517B)
1 //! Cryptographically verified exact-plan signing receipts. 2 3 use core::fmt; 4 use radroots_event::SignedEvent; 5 use radroots_event_codec::verify::{self, Nip01SignatureVerifier, RawEvent}; 6 use radroots_protocol::runtime::v1::OperationId; 7 8 use crate::{Error, SignRequest, SignerRequestId, SigningIntentId, error::Kind}; 9 10 /// Successful signer output with exact request and artifact provenance. 11 #[non_exhaustive] 12 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 13 #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 14 #[derive(Clone, PartialEq, Eq)] 15 pub struct SignReceipt { 16 operation_kind: OperationId, 17 intent_id: SigningIntentId, 18 signer_request_id: SignerRequestId, 19 signed_event: SignedEvent, 20 completed_at_unix_ms: u64, 21 } 22 23 impl fmt::Debug for SignReceipt { 24 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 25 formatter 26 .debug_struct("SignReceipt") 27 .field("operation_kind", &self.operation_kind) 28 .field("intent_id", &self.intent_id) 29 .field("signer_request_id", &self.signer_request_id) 30 .field("signed_event_id", &self.signed_event.id_str()) 31 .field("completed_at_unix_ms", &self.completed_at_unix_ms) 32 .finish() 33 } 34 } 35 36 impl SignReceipt { 37 /// Verifies exact plan fields, raw/wire coherence, event ID, signature, 38 /// deadline, cancellation, and request identity before success exists. 39 pub fn from_signed_event( 40 request: &SignRequest, 41 signed_event: SignedEvent, 42 completed_at_unix_ms: u64, 43 ) -> Result<Self, Error> { 44 request.ensure_active(completed_at_unix_ms)?; 45 verify_signed_event(&signed_event, request)?; 46 Ok(Self { 47 operation_kind: request.operation_kind(), 48 intent_id: request.intent_id(), 49 signer_request_id: request.signer_request_id(), 50 signed_event, 51 completed_at_unix_ms, 52 }) 53 } 54 55 #[must_use] 56 pub const fn operation_kind(&self) -> OperationId { 57 self.operation_kind 58 } 59 60 #[must_use] 61 pub const fn intent_id(&self) -> SigningIntentId { 62 self.intent_id 63 } 64 65 #[must_use] 66 pub const fn signer_request_id(&self) -> SignerRequestId { 67 self.signer_request_id 68 } 69 70 #[must_use] 71 pub const fn signed_event(&self) -> &SignedEvent { 72 &self.signed_event 73 } 74 75 #[must_use] 76 pub const fn completed_at_unix_ms(&self) -> u64 { 77 self.completed_at_unix_ms 78 } 79 } 80 81 fn verify_exact_plan(event: &SignedEvent, request: &SignRequest) -> Result<(), Error> { 82 if event.pubkey() != request.expected_author() 83 || event.created_at() != request.created_at() 84 || event.kind() != request.kind() 85 || event.tags_as_vec() != request.tags() 86 || event.content() != request.content() 87 || event.id() != request.expected_event_id() 88 { 89 return Err(Error::new(Kind::SignerOutputInvalid)); 90 } 91 // `SignedEvent` construction proves its retained raw JSON parses to this 92 // exact wire value; the checks above bind that wire to the request plan. 93 Ok(()) 94 } 95 96 /// Verified authored signature facts, independent of whether a caller is waiting. 97 /// 98 /// This is neither an active success receipt nor authority to sign, admit or 99 /// deliver an event. It cannot represent an expiring Blossom credential. 100 /// Persistence callers must use [`Self::revalidate`] with their retained request 101 /// and locally observed time before recording the evidence. 102 /// 103 /// Evidence cannot be deserialized without cryptographic verification: 104 /// ```compile_fail 105 /// fn decode<'a, T: serde::Deserialize<'a>>() {} 106 /// decode::<radroots_signing::AuthoredSignEvidence>(); 107 /// ``` 108 #[non_exhaustive] 109 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 110 #[derive(Clone, PartialEq, Eq)] 111 pub struct AuthoredSignEvidence { 112 operation_kind: OperationId, 113 intent_id: SigningIntentId, 114 signer_request_id: SignerRequestId, 115 signed_event: SignedEvent, 116 observed_at_unix_ms: u64, 117 } 118 119 impl AuthoredSignEvidence { 120 /// Verifies an already-created authored event against its originating request. 121 /// 122 /// Deadline and cancellation affect scheduling, not the truth of retained 123 /// signature facts. Observation time must be positive and is not proof of 124 /// when the signature was created. This constructor performs no signing. 125 pub fn from_signed_event( 126 request: &SignRequest, 127 signed_event: SignedEvent, 128 observed_at_unix_ms: u64, 129 ) -> Result<Self, Error> { 130 if request.authored_plan().is_none() || observed_at_unix_ms == 0 { 131 return Err(Error::new(Kind::InvalidArgument)); 132 } 133 verify_signed_event(&signed_event, request)?; 134 Ok(Self { 135 operation_kind: request.operation_kind(), 136 intent_id: request.intent_id(), 137 signer_request_id: request.signer_request_id(), 138 signed_event, 139 observed_at_unix_ms, 140 }) 141 } 142 143 /// Rebinds verified evidence to a caller's exact retained request and clock. 144 /// 145 /// A signature over the same event under a different operation or artifact 146 /// is not evidence for this request. The original raw event bytes survive. 147 pub fn revalidate( 148 &self, 149 request: &SignRequest, 150 observed_at_unix_ms: u64, 151 ) -> Result<Self, Error> { 152 verify_identity( 153 self.operation_kind, 154 self.intent_id, 155 self.signer_request_id, 156 request, 157 )?; 158 Self::from_signed_event(request, self.signed_event.clone(), observed_at_unix_ms) 159 } 160 161 #[must_use] 162 pub const fn operation_kind(&self) -> OperationId { 163 self.operation_kind 164 } 165 166 #[must_use] 167 pub const fn intent_id(&self) -> SigningIntentId { 168 self.intent_id 169 } 170 171 #[must_use] 172 pub const fn signer_request_id(&self) -> SignerRequestId { 173 self.signer_request_id 174 } 175 176 #[must_use] 177 pub const fn signed_event(&self) -> &SignedEvent { 178 &self.signed_event 179 } 180 181 #[must_use] 182 pub const fn observed_at_unix_ms(&self) -> u64 { 183 self.observed_at_unix_ms 184 } 185 } 186 187 impl fmt::Debug for AuthoredSignEvidence { 188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 189 formatter 190 .debug_struct("AuthoredSignEvidence") 191 .field("operation_kind", &self.operation_kind) 192 .field("intent_id", &self.intent_id) 193 .field("signer_request_id", &self.signer_request_id) 194 .field("signed_event_id", &self.signed_event.id_str()) 195 .field("observed_at_unix_ms", &self.observed_at_unix_ms) 196 .finish() 197 } 198 } 199 200 pub(crate) fn verify_identity( 201 operation_kind: OperationId, 202 intent_id: SigningIntentId, 203 signer_request_id: SignerRequestId, 204 request: &SignRequest, 205 ) -> Result<(), Error> { 206 if operation_kind != request.operation_kind() 207 || intent_id != request.intent_id() 208 || signer_request_id != request.signer_request_id() 209 { 210 return Err(Error::new(Kind::SignerOutputInvalid)); 211 } 212 Ok(()) 213 } 214 215 fn verify_signed_event(event: &SignedEvent, request: &SignRequest) -> Result<(), Error> { 216 verify_exact_plan(event, request)?; 217 let id_verified = verify::id(RawEvent::new(event.envelope().clone())) 218 .map_err(|_| Error::new(Kind::SignerOutputInvalid))?; 219 verify::signature(id_verified, &Nip01SignatureVerifier) 220 .map_err(|_| Error::new(Kind::SignerOutputInvalid))?; 221 Ok(()) 222 }