lib

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

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;