tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

lib.rs (18354B)


      1 #![forbid(unsafe_code)]
      2 
      3 use core::fmt;
      4 use std::sync::Arc;
      5 
      6 use k256::schnorr::signature::{Signer, Verifier};
      7 use k256::schnorr::{Signature, SigningKey, VerifyingKey};
      8 use pocket_types::{
      9     Kind as PocketKind, OwnedEvent as PocketOwnedEvent, Tags as PocketTags, Time as PocketTime,
     10 };
     11 use secp256k1::{Keypair, Secp256k1, SecretKey};
     12 use sha2::{Digest, Sha256};
     13 use tangle_protocol::{
     14     Event, EventId, PublicKeyHex, SignatureHex, UnsignedEvent, canonical_event_json,
     15 };
     16 use tokio::sync::Semaphore;
     17 
     18 pub fn compute_event_id(event: &UnsignedEvent) -> EventId {
     19     let event_id = compute_event_id_hex(event);
     20     EventId::new(&event_id).expect("sha256 emits 32-byte lowercase hex")
     21 }
     22 
     23 pub fn compute_event_id_hex(event: &UnsignedEvent) -> String {
     24     let canonical = canonical_event_json(event);
     25     let digest = Sha256::digest(canonical.as_bytes());
     26     lower_hex(&digest)
     27 }
     28 
     29 pub fn event_id_matches(event: &Event) -> bool {
     30     compute_event_id(event.unsigned()) == *event.id()
     31 }
     32 
     33 pub fn verify_event_id(event: &Event) -> Result<(), String> {
     34     let expected = compute_event_id(event.unsigned());
     35     if event.id() == &expected {
     36         Ok(())
     37     } else {
     38         Err(format!(
     39             "event id mismatch: expected {}, got {}",
     40             expected,
     41             event.id()
     42         ))
     43     }
     44 }
     45 
     46 pub fn verify_event_signature(event: &Event) -> Result<(), String> {
     47     verify_event_id(event)?;
     48     let event_id =
     49         validated_fixed_hex_bytes(event.id().as_str(), EventId::HEX_LENGTH / 2, "event id");
     50     let pubkey = fixed_hex_bytes(
     51         event.unsigned().pubkey().as_str(),
     52         EventId::HEX_LENGTH / 2,
     53         "public key",
     54     )
     55     .expect("validated public key scalar decodes");
     56     let signature = fixed_hex_bytes(event.sig().as_str(), 64, "signature")
     57         .expect("validated signature decodes");
     58     let verifying_key = VerifyingKey::from_bytes(&pubkey)
     59         .map_err(|_| "event public key is not a valid secp256k1 x-only key".to_owned())?;
     60     let signature = Signature::try_from(signature.as_slice())
     61         .map_err(|_| "event signature is not a valid schnorr signature".to_owned())?;
     62     verifying_key
     63         .verify(&event_id, &signature)
     64         .map_err(|_| "event signature verification failed".to_owned())
     65 }
     66 
     67 pub fn compute_event_id_hex_from_canonical_json(canonical: &str) -> String {
     68     let digest = Sha256::digest(canonical.as_bytes());
     69     lower_hex(&digest)
     70 }
     71 
     72 pub fn verify_event_signature_bytes(
     73     canonical: &str,
     74     event_id: &[u8; 32],
     75     pubkey: &[u8; 32],
     76     signature: &[u8; 64],
     77 ) -> Result<(), String> {
     78     let expected_id = Sha256::digest(canonical.as_bytes());
     79     let expected_id_bytes: &[u8] = expected_id.as_ref();
     80     if expected_id_bytes != event_id {
     81         return Err(format!(
     82             "event id mismatch: expected {}, got {}",
     83             lower_hex(&expected_id),
     84             lower_hex(event_id)
     85         ));
     86     }
     87     let verifying_key = VerifyingKey::from_bytes(pubkey)
     88         .map_err(|_| "event public key is not a valid secp256k1 x-only key".to_owned())?;
     89     let signature = Signature::try_from(signature.as_slice())
     90         .map_err(|_| "event signature is not a valid schnorr signature".to_owned())?;
     91     verifying_key
     92         .verify(event_id, &signature)
     93         .map_err(|_| "event signature verification failed".to_owned())
     94 }
     95 
     96 pub struct RelaySigner {
     97     signing_key: SigningKey,
     98     public_key: PublicKeyHex,
     99     secret_bytes: [u8; 32],
    100 }
    101 
    102 impl RelaySigner {
    103     pub fn from_secret_hex(secret: &str) -> Result<Self, String> {
    104         let bytes = fixed_hex_bytes(secret, 32, "relay secret")?;
    105         let bytes: [u8; 32] = bytes
    106             .try_into()
    107             .expect("validated relay secret length is 32 bytes");
    108         let signing_key = SigningKey::from_bytes(&bytes)
    109             .map_err(|_| "relay secret is not a valid secp256k1 signing key".to_owned())?;
    110         let public_key =
    111             PublicKeyHex::new(&lower_hex(signing_key.verifying_key().to_bytes().as_ref()))
    112                 .expect("signing key emits a valid x-only public key");
    113         Ok(Self {
    114             signing_key,
    115             public_key,
    116             secret_bytes: bytes,
    117         })
    118     }
    119 
    120     pub fn public_key(&self) -> &PublicKeyHex {
    121         &self.public_key
    122     }
    123 
    124     pub fn sign_unsigned_event(&self, unsigned: UnsignedEvent) -> Event {
    125         let event_id = compute_event_id(&unsigned);
    126         let event_id_bytes =
    127             fixed_hex_bytes(event_id.as_str(), 32, "event id").expect("event id is valid hex");
    128         let signature: Signature = self.signing_key.sign(&event_id_bytes);
    129         let signature = SignatureHex::new(&lower_hex(signature.to_bytes().as_ref()))
    130             .expect("schnorr signature emits valid hex");
    131         Event::new(event_id, unsigned, signature)
    132     }
    133 
    134     pub fn sign_pocket_event(
    135         &self,
    136         kind: PocketKind,
    137         tags: &PocketTags,
    138         created_at: PocketTime,
    139         content: &[u8],
    140     ) -> Result<PocketOwnedEvent, String> {
    141         let secp = Secp256k1::new();
    142         let secret_key = SecretKey::from_byte_array(self.secret_bytes)
    143             .map_err(|_| "relay secret is not a valid secp256k1 signing key".to_owned())?;
    144         let keypair = Keypair::from_secret_key(&secp, &secret_key);
    145         PocketOwnedEvent::sign_new(&keypair, kind, tags, created_at, content)
    146             .map_err(|error| format!("Pocket event signing failed: {error}"))
    147     }
    148 }
    149 
    150 impl fmt::Debug for RelaySigner {
    151     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    152         formatter
    153             .debug_struct("RelaySigner")
    154             .field("public_key", &self.public_key)
    155             .finish_non_exhaustive()
    156     }
    157 }
    158 
    159 #[derive(Clone, Debug)]
    160 pub struct VerificationService {
    161     semaphore: Arc<Semaphore>,
    162     max_concurrent: usize,
    163 }
    164 
    165 impl VerificationService {
    166     pub fn new(max_concurrent: usize) -> Result<Self, String> {
    167         if max_concurrent == 0 {
    168             return Err("verification concurrency limit must be greater than zero".to_owned());
    169         }
    170         Ok(Self {
    171             semaphore: Arc::new(Semaphore::new(max_concurrent)),
    172             max_concurrent,
    173         })
    174     }
    175 
    176     pub fn max_concurrent(&self) -> usize {
    177         self.max_concurrent
    178     }
    179 
    180     pub fn available_permits(&self) -> usize {
    181         self.semaphore.available_permits()
    182     }
    183 
    184     pub fn close(&self) {
    185         self.semaphore.close();
    186     }
    187 
    188     pub async fn verify_event(&self, event: &Event) -> Result<VerificationOutcome, String> {
    189         let permit = self
    190             .semaphore
    191             .clone()
    192             .acquire_owned()
    193             .await
    194             .map_err(|_| "verification service is closed".to_owned())?;
    195         let result = verify_event_signature(event);
    196         drop(permit);
    197         result.map(|_| VerificationOutcome {
    198             event_id: event.id().clone(),
    199         })
    200     }
    201 }
    202 
    203 #[derive(Debug, Clone, PartialEq, Eq)]
    204 pub struct VerificationOutcome {
    205     event_id: EventId,
    206 }
    207 
    208 impl VerificationOutcome {
    209     pub fn event_id(&self) -> &EventId {
    210         &self.event_id
    211     }
    212 }
    213 
    214 fn lower_hex(bytes: &[u8]) -> String {
    215     const HEX: &[u8; 16] = b"0123456789abcdef";
    216     let mut output = String::with_capacity(bytes.len() * 2);
    217     for byte in bytes {
    218         output.push(char::from(HEX[usize::from(byte >> 4)]));
    219         output.push(char::from(HEX[usize::from(byte & 0x0f)]));
    220     }
    221     output
    222 }
    223 
    224 fn fixed_hex_bytes(value: &str, expected: usize, scalar: &str) -> Result<Vec<u8>, String> {
    225     if value.len() != expected * 2 {
    226         return Err(format!(
    227             "{scalar} must decode to {expected} bytes, got {} hex characters",
    228             value.len()
    229         ));
    230     }
    231     let mut output = Vec::with_capacity(expected);
    232     for chunk in value.as_bytes().chunks_exact(2) {
    233         let high = hex_value(chunk[0], scalar)?;
    234         let low = hex_value(chunk[1], scalar)?;
    235         output.push((high << 4) | low);
    236     }
    237     Ok(output)
    238 }
    239 
    240 fn validated_fixed_hex_bytes(value: &str, expected: usize, scalar: &str) -> Vec<u8> {
    241     fixed_hex_bytes(value, expected, scalar).expect("validated hex scalar decodes")
    242 }
    243 
    244 fn hex_value(value: u8, scalar: &str) -> Result<u8, String> {
    245     match value {
    246         b'0'..=b'9' => Ok(value - b'0'),
    247         b'a'..=b'f' => Ok(value - b'a' + 10),
    248         _ => Err(format!("{scalar} must be lowercase hex")),
    249     }
    250 }
    251 
    252 #[cfg(test)]
    253 mod tests {
    254     use super::{
    255         RelaySigner, VerificationService, compute_event_id, compute_event_id_hex, event_id_matches,
    256         fixed_hex_bytes, lower_hex, verify_event_id, verify_event_signature,
    257     };
    258     use k256::schnorr::signature::Signer;
    259     use k256::schnorr::{Signature, SigningKey};
    260     use pocket_types::{Kind as PocketKind, OwnedTags as PocketOwnedTags, Time as PocketTime};
    261     use std::time::Duration;
    262     use tangle_protocol::{
    263         Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent,
    264     };
    265     use tokio::time::timeout;
    266 
    267     #[test]
    268     fn event_id_hashes_canonical_event_bytes() {
    269         let event = unsigned_event(Vec::new(), "");
    270 
    271         assert_eq!(
    272             compute_event_id_hex(&event),
    273             "da90287b43a114ad00f2a87854947df1251b9a0f148b1707b9241c73f11569ae"
    274         );
    275         assert_eq!(
    276             compute_event_id(&event).as_str(),
    277             "da90287b43a114ad00f2a87854947df1251b9a0f148b1707b9241c73f11569ae"
    278         );
    279     }
    280 
    281     #[test]
    282     fn event_id_verification_reports_match_and_mismatch() {
    283         let unsigned = unsigned_event(
    284             vec![Tag::from_parts("t", &["radroots"]).expect("tag")],
    285             "radroots cafe",
    286         );
    287         let event_id = compute_event_id(&unsigned);
    288         let event = Event::new(
    289             event_id,
    290             unsigned.clone(),
    291             SignatureHex::new(&"b".repeat(SignatureHex::HEX_LENGTH)).expect("sig"),
    292         );
    293         let wrong_event = Event::new(
    294             EventId::new(&"f".repeat(EventId::HEX_LENGTH)).expect("id"),
    295             unsigned,
    296             SignatureHex::new(&"b".repeat(SignatureHex::HEX_LENGTH)).expect("sig"),
    297         );
    298 
    299         assert!(event_id_matches(&event));
    300         assert_eq!(verify_event_id(&event), Ok(()));
    301         assert!(!event_id_matches(&wrong_event));
    302         assert_eq!(
    303             verify_event_id(&wrong_event).expect_err("mismatch"),
    304             format!(
    305                 "event id mismatch: expected {}, got {}",
    306                 compute_event_id(wrong_event.unsigned()),
    307                 wrong_event.id()
    308             )
    309         );
    310     }
    311 
    312     #[test]
    313     fn schnorr_verifier_accepts_deterministically_signed_event() {
    314         let event = signed_event();
    315 
    316         assert_eq!(verify_event_signature(&event), Ok(()));
    317     }
    318 
    319     #[test]
    320     fn relay_signer_derives_public_key_and_signs_canonical_events() {
    321         let secret = lower_hex(&[7_u8; 32]);
    322         let signer = RelaySigner::from_secret_hex(&secret).expect("signer");
    323         let unsigned = UnsignedEvent::new(
    324             signer.public_key().clone(),
    325             UnixTimestamp::new(1_714_124_433),
    326             Kind::new(1).expect("kind"),
    327             vec![Tag::from_parts("t", &["radroots"]).expect("tag")],
    328             "relay generated",
    329         );
    330 
    331         let event = signer.sign_unsigned_event(unsigned);
    332 
    333         assert_eq!(event.unsigned().pubkey(), signer.public_key());
    334         assert_eq!(verify_event_signature(&event), Ok(()));
    335         assert_eq!(
    336             format!("{signer:?}"),
    337             format!(
    338                 "RelaySigner {{ public_key: {:?}, .. }}",
    339                 signer.public_key()
    340             )
    341         );
    342         assert!(!format!("{signer:?}").contains(&secret));
    343     }
    344 
    345     #[test]
    346     fn relay_signer_signs_pocket_events() {
    347         let secret = "7".repeat(64);
    348         let signer = RelaySigner::from_secret_hex(&secret).expect("signer");
    349         let tags = PocketOwnedTags::new(&[["d", "Farm"]]).expect("tags");
    350         let event = signer
    351             .sign_pocket_event(
    352                 PocketKind::from_u16(39_000),
    353                 &tags,
    354                 PocketTime::from_u64(20),
    355                 b"",
    356             )
    357             .expect("event");
    358 
    359         event.verify().expect("verify");
    360         assert_eq!(event.pubkey().as_hex_string(), signer.public_key().as_str());
    361         assert_eq!(event.kind().as_u16(), 39_000);
    362         assert_eq!(
    363             event.id().as_hex_string(),
    364             "b107997a285780bc383ee5aadc0a0eefc46734914103d80f765a46543622782a"
    365         );
    366     }
    367 
    368     #[test]
    369     fn schnorr_verifier_rejects_bad_id_bad_pubkey_and_bad_signature() {
    370         let event = signed_event();
    371         let wrong_id = Event::new(
    372             EventId::new(&"f".repeat(EventId::HEX_LENGTH)).expect("id"),
    373             event.unsigned().clone(),
    374             event.sig().clone(),
    375         );
    376         let invalid_pubkey_unsigned = UnsignedEvent::new(
    377             PublicKeyHex::new(&"f".repeat(PublicKeyHex::HEX_LENGTH)).expect("pubkey"),
    378             event.unsigned().created_at(),
    379             event.unsigned().kind(),
    380             event.unsigned().tags().to_vec(),
    381             event.unsigned().content(),
    382         );
    383         let invalid_pubkey = Event::new(
    384             compute_event_id(&invalid_pubkey_unsigned),
    385             invalid_pubkey_unsigned,
    386             event.sig().clone(),
    387         );
    388         let invalid_signature = Event::new(
    389             compute_event_id(event.unsigned()),
    390             event.unsigned().clone(),
    391             SignatureHex::new(&"f".repeat(SignatureHex::HEX_LENGTH)).expect("sig"),
    392         );
    393         let wrong_message_unsigned = UnsignedEvent::new(
    394             event.unsigned().pubkey().clone(),
    395             event.unsigned().created_at(),
    396             event.unsigned().kind(),
    397             event.unsigned().tags().to_vec(),
    398             "different message",
    399         );
    400         let wrong_message_signature = Event::new(
    401             compute_event_id(&wrong_message_unsigned),
    402             wrong_message_unsigned,
    403             event.sig().clone(),
    404         );
    405 
    406         assert!(
    407             verify_event_signature(&wrong_id)
    408                 .expect_err("bad id")
    409                 .starts_with("event id mismatch")
    410         );
    411         assert_eq!(
    412             verify_event_signature(&invalid_pubkey).expect_err("bad pubkey"),
    413             "event public key is not a valid secp256k1 x-only key"
    414         );
    415         assert_eq!(
    416             verify_event_signature(&invalid_signature).expect_err("bad sig"),
    417             "event signature is not a valid schnorr signature"
    418         );
    419         assert_eq!(
    420             verify_event_signature(&wrong_message_signature).expect_err("wrong message"),
    421             "event signature verification failed"
    422         );
    423     }
    424 
    425     #[test]
    426     fn hex_decoder_rejects_bad_length_and_non_hex_input() {
    427         assert_eq!(
    428             fixed_hex_bytes("abc", 2, "sample").expect_err("length"),
    429             "sample must decode to 2 bytes, got 3 hex characters"
    430         );
    431         assert_eq!(
    432             fixed_hex_bytes("0G", 1, "sample").expect_err("hex"),
    433             "sample must be lowercase hex"
    434         );
    435         assert_eq!(
    436             fixed_hex_bytes("G0", 1, "sample").expect_err("hex"),
    437             "sample must be lowercase hex"
    438         );
    439     }
    440 
    441     #[tokio::test]
    442     async fn verification_service_accepts_valid_events_and_rejects_invalid_events() {
    443         let event = signed_event();
    444         let invalid = Event::new(
    445             EventId::new(&"f".repeat(EventId::HEX_LENGTH)).expect("id"),
    446             event.unsigned().clone(),
    447             event.sig().clone(),
    448         );
    449         let service = VerificationService::new(2).expect("service");
    450 
    451         let outcome = service.verify_event(&event).await.expect("verified");
    452 
    453         assert_eq!(service.max_concurrent(), 2);
    454         assert_eq!(service.available_permits(), 2);
    455         assert_eq!(outcome.event_id(), event.id());
    456         assert!(
    457             service
    458                 .verify_event(&invalid)
    459                 .await
    460                 .expect_err("invalid")
    461                 .starts_with("event id mismatch")
    462         );
    463     }
    464 
    465     #[tokio::test]
    466     async fn verification_service_enforces_limit_and_reports_closed_state() {
    467         let event = signed_event();
    468         let service = VerificationService::new(1).expect("service");
    469         let permit = service
    470             .semaphore
    471             .clone()
    472             .acquire_owned()
    473             .await
    474             .expect("permit");
    475 
    476         assert_eq!(service.available_permits(), 0);
    477         assert!(
    478             timeout(Duration::from_millis(20), service.verify_event(&event))
    479                 .await
    480                 .is_err()
    481         );
    482         drop(permit);
    483         assert!(
    484             timeout(Duration::from_secs(1), service.verify_event(&event))
    485                 .await
    486                 .expect("timeout")
    487                 .is_ok()
    488         );
    489         service.close();
    490         assert_eq!(
    491             service.verify_event(&event).await.expect_err("closed"),
    492             "verification service is closed"
    493         );
    494     }
    495 
    496     #[test]
    497     fn verification_service_rejects_zero_limit() {
    498         assert_eq!(
    499             VerificationService::new(0).expect_err("zero"),
    500             "verification concurrency limit must be greater than zero"
    501         );
    502     }
    503 
    504     fn unsigned_event(tags: Vec<Tag>, content: &str) -> UnsignedEvent {
    505         UnsignedEvent::new(
    506             PublicKeyHex::new(&"1".repeat(PublicKeyHex::HEX_LENGTH)).expect("pubkey"),
    507             UnixTimestamp::new(1_714_124_433),
    508             Kind::new(1).expect("kind"),
    509             tags,
    510             content,
    511         )
    512     }
    513 
    514     fn signed_event() -> Event {
    515         let signing_key = SigningKey::from_bytes(&[7_u8; 32]).expect("signing key");
    516         let public_key =
    517             PublicKeyHex::new(&lower_hex(signing_key.verifying_key().to_bytes().as_ref()))
    518                 .expect("pubkey");
    519         let unsigned = UnsignedEvent::new(
    520             public_key,
    521             UnixTimestamp::new(1_714_124_433),
    522             Kind::new(1).expect("kind"),
    523             vec![Tag::from_parts("t", &["radroots"]).expect("tag")],
    524             "radroots cafe",
    525         );
    526         let event_id = compute_event_id(&unsigned);
    527         let event_id_bytes = fixed_hex_bytes(event_id.as_str(), 32, "event id").expect("event id");
    528         let signature: Signature = signing_key.sign(&event_id_bytes);
    529         let signature = SignatureHex::new(&lower_hex(signature.to_bytes().as_ref())).expect("sig");
    530         Event::new(event_id, unsigned, signature)
    531     }
    532 }