v1.rs (6584B)
1 //! Frozen NIP-01 verification-v1 semantics. 2 3 #[cfg(not(feature = "std"))] 4 use alloc::string::String; 5 6 use core::fmt; 7 8 use radroots_event::contract::registry_v7::{ 9 ContractValidationError, EventContract, 10 validate_event_contract_registry_v7 as validate_radroots_event_contract_registry_v7, 11 }; 12 use radroots_event::envelope::EventEnvelope; 13 use radroots_event::wire::v1::compute_canonical_nip01_event_id_v1; 14 use secp256k1::{Message, Secp256k1, XOnlyPublicKey, schnorr::Signature}; 15 16 #[derive(Clone, Debug, PartialEq, Eq)] 17 pub struct RadrootsIdVerifiedEvent { 18 event: EventEnvelope, 19 } 20 21 impl RadrootsIdVerifiedEvent { 22 pub fn event(&self) -> &EventEnvelope { 23 &self.event 24 } 25 26 pub fn into_event(self) -> EventEnvelope { 27 self.event 28 } 29 } 30 31 #[derive(Clone, Debug, PartialEq, Eq)] 32 pub struct RadrootsSignatureVerifiedEvent { 33 event: EventEnvelope, 34 } 35 36 impl RadrootsSignatureVerifiedEvent { 37 pub fn event(&self) -> &EventEnvelope { 38 &self.event 39 } 40 41 pub fn into_event(self) -> EventEnvelope { 42 self.event 43 } 44 } 45 46 /// A NIP-01 verified event whose registry-selected contract shape is valid. 47 #[derive(Clone, Debug, PartialEq, Eq)] 48 pub struct RadrootsContractValidatedEvent { 49 verified_event: RadrootsSignatureVerifiedEvent, 50 contract: &'static EventContract, 51 } 52 53 impl RadrootsContractValidatedEvent { 54 pub fn verified_event(&self) -> &RadrootsSignatureVerifiedEvent { 55 &self.verified_event 56 } 57 58 pub fn event(&self) -> &EventEnvelope { 59 self.verified_event.event() 60 } 61 62 pub fn contract(&self) -> &'static EventContract { 63 self.contract 64 } 65 66 pub fn contract_id(&self) -> &'static str { 67 self.contract.id 68 } 69 70 pub fn into_verified_event(self) -> RadrootsSignatureVerifiedEvent { 71 self.verified_event 72 } 73 74 pub fn into_event(self) -> EventEnvelope { 75 self.verified_event.into_event() 76 } 77 } 78 79 #[non_exhaustive] 80 #[derive(Clone, Debug, PartialEq, Eq)] 81 pub enum RadrootsNip01VerificationError { 82 MalformedEnvelope, 83 KindOutOfRange { kind: u32 }, 84 IdMismatch { expected: String, actual: String }, 85 SignatureInvalid, 86 } 87 88 impl RadrootsNip01VerificationError { 89 pub const fn code(&self) -> &'static str { 90 match self { 91 Self::MalformedEnvelope => "malformed_envelope", 92 Self::KindOutOfRange { .. } => "kind_out_of_range", 93 Self::IdMismatch { .. } => "id_mismatch", 94 Self::SignatureInvalid => "signature_invalid", 95 } 96 } 97 } 98 99 impl fmt::Display for RadrootsNip01VerificationError { 100 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 101 match self { 102 Self::MalformedEnvelope => formatter.write_str("malformed NIP-01 event envelope"), 103 Self::KindOutOfRange { kind } => { 104 write!(formatter, "NIP-01 event kind {kind} exceeds {}", u16::MAX) 105 } 106 Self::IdMismatch { expected, actual } => { 107 write!( 108 formatter, 109 "NIP-01 event id mismatch: expected {expected}, got {actual}" 110 ) 111 } 112 Self::SignatureInvalid => formatter.write_str("invalid NIP-01 event signature"), 113 } 114 } 115 } 116 117 #[cfg(feature = "std")] 118 impl std::error::Error for RadrootsNip01VerificationError {} 119 120 pub fn verify_event_id( 121 event: EventEnvelope, 122 ) -> Result<RadrootsIdVerifiedEvent, RadrootsNip01VerificationError> { 123 verify_event_id_v1(event) 124 } 125 126 /// Verifies the canonical identifier with reconciliation-v1 semantics. 127 pub fn verify_event_id_v1( 128 event: EventEnvelope, 129 ) -> Result<RadrootsIdVerifiedEvent, RadrootsNip01VerificationError> { 130 u16::try_from(event.kind_u32()).map_err(|_| { 131 RadrootsNip01VerificationError::KindOutOfRange { 132 kind: event.kind_u32(), 133 } 134 })?; 135 let expected = compute_canonical_nip01_event_id_v1( 136 &event.author().to_hex(), 137 event.created_at_u64(), 138 event.kind_u32(), 139 &event.tags_as_vec(), 140 event.content(), 141 ) 142 .map_err(|_| RadrootsNip01VerificationError::MalformedEnvelope)?; 143 if event.id() != &expected { 144 return Err(RadrootsNip01VerificationError::IdMismatch { 145 expected: expected.to_hex(), 146 actual: event.id_hex(), 147 }); 148 } 149 Ok(RadrootsIdVerifiedEvent { event }) 150 } 151 152 pub fn verify_event_signature( 153 event: RadrootsIdVerifiedEvent, 154 ) -> Result<RadrootsSignatureVerifiedEvent, RadrootsNip01VerificationError> { 155 verify_event_signature_v1(event) 156 } 157 158 /// Verifies the Schnorr signature with reconciliation-v1 semantics. 159 pub fn verify_event_signature_v1( 160 event: RadrootsIdVerifiedEvent, 161 ) -> Result<RadrootsSignatureVerifiedEvent, RadrootsNip01VerificationError> { 162 let public_key = XOnlyPublicKey::from_slice(event.event.author().as_bytes()) 163 .map_err(|_| RadrootsNip01VerificationError::MalformedEnvelope)?; 164 let signature = Signature::from_slice(event.event.sig().as_bytes()) 165 .map_err(|_| RadrootsNip01VerificationError::MalformedEnvelope)?; 166 let message = Message::from_digest(*event.event.id().as_bytes()); 167 Secp256k1::verification_only() 168 .verify_schnorr(&signature, &message, &public_key) 169 .map_err(|_| RadrootsNip01VerificationError::SignatureInvalid)?; 170 Ok(RadrootsSignatureVerifiedEvent { event: event.event }) 171 } 172 173 /// Verifies the canonical NIP-01 identifier and Schnorr signature in order. 174 pub fn verify_nip01_event( 175 event: EventEnvelope, 176 ) -> Result<RadrootsSignatureVerifiedEvent, RadrootsNip01VerificationError> { 177 verify_nip01_event_v1(event) 178 } 179 180 /// Verifies a NIP-01 event with the behavior frozen for reconciliation v1. 181 pub fn verify_nip01_event_v1( 182 event: EventEnvelope, 183 ) -> Result<RadrootsSignatureVerifiedEvent, RadrootsNip01VerificationError> { 184 verify_event_signature_v1(verify_event_id_v1(event)?) 185 } 186 187 /// Applies full registry contract-shape validation to an already verified event. 188 pub fn validate_event_contract( 189 event: RadrootsSignatureVerifiedEvent, 190 ) -> Result<RadrootsContractValidatedEvent, ContractValidationError> { 191 validate_event_contract_registry_v7(event) 192 } 193 194 /// Applies the immutable registry-v7 contract-shape validation boundary. 195 pub fn validate_event_contract_registry_v7( 196 event: RadrootsSignatureVerifiedEvent, 197 ) -> Result<RadrootsContractValidatedEvent, ContractValidationError> { 198 let contract = validate_radroots_event_contract_registry_v7(event.event())?; 199 Ok(RadrootsContractValidatedEvent { 200 verified_event: event, 201 contract, 202 }) 203 } 204 205 #[cfg(test)] 206 mod tests;