draft_signing.rs (4143B)
1 #![forbid(unsafe_code)] 2 3 use crate::error::RadrootsNostrError; 4 use crate::event_convert::radroots_event_from_nostr; 5 use crate::events::radroots_nostr_build_event; 6 use crate::types::{RadrootsNostrKeys, RadrootsNostrTimestamp}; 7 use nostr::JsonUtil; 8 use radroots_events::draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent}; 9 10 pub fn radroots_nostr_sign_frozen_draft( 11 keys: &RadrootsNostrKeys, 12 draft: &RadrootsFrozenEventDraft, 13 ) -> Result<RadrootsSignedNostrEvent, RadrootsNostrError> { 14 let actual_pubkey = keys.public_key().to_hex(); 15 if actual_pubkey != draft.expected_pubkey { 16 return Err(RadrootsNostrError::FrozenDraftPubkeyMismatch { 17 expected_pubkey: draft.expected_pubkey.clone(), 18 actual_pubkey, 19 }); 20 } 21 22 let event = radroots_nostr_build_event(draft.kind, draft.content.clone(), draft.tags.clone())? 23 .custom_created_at(RadrootsNostrTimestamp::from_secs(u64::from( 24 draft.created_at, 25 ))) 26 .sign_with_keys(keys)?; 27 let actual_event_id = event.id.to_hex(); 28 if actual_event_id != draft.expected_event_id { 29 return Err(RadrootsNostrError::FrozenDraftEventIdMismatch { 30 expected_event_id: draft.expected_event_id.clone(), 31 actual_event_id, 32 }); 33 } 34 35 let raw_json = event.as_json(); 36 RadrootsSignedNostrEvent::from_event(radroots_event_from_nostr(&event), raw_json) 37 .map_err(Into::into) 38 } 39 40 #[cfg(test)] 41 mod tests { 42 use super::radroots_nostr_sign_frozen_draft; 43 use crate::error::RadrootsNostrError; 44 use crate::test_fixtures::{FIXTURE_ALICE, FIXTURE_BOB}; 45 use crate::types::{RadrootsNostrKeys, RadrootsNostrSecretKey}; 46 use nostr::JsonUtil; 47 use radroots_events::draft::RadrootsFrozenEventDraft; 48 use radroots_events::kinds::KIND_POST; 49 50 fn fixture_keys(secret_key_hex: &str) -> RadrootsNostrKeys { 51 let secret_key = RadrootsNostrSecretKey::from_hex(secret_key_hex).expect("secret key"); 52 RadrootsNostrKeys::new(secret_key) 53 } 54 55 fn post_draft(expected_pubkey: &str) -> RadrootsFrozenEventDraft { 56 RadrootsFrozenEventDraft::new( 57 "radroots.social.post.v1", 58 KIND_POST, 59 1_700_000_000, 60 vec![vec!["t".to_owned(), "soil".to_owned()]], 61 "hello", 62 expected_pubkey, 63 ) 64 .expect("draft") 65 } 66 67 #[test] 68 fn sign_frozen_draft_uses_fixed_created_at_and_expected_id() { 69 let keys = fixture_keys(FIXTURE_ALICE.secret_key_hex); 70 let draft = post_draft(FIXTURE_ALICE.public_key_hex); 71 let signed = radroots_nostr_sign_frozen_draft(&keys, &draft).expect("signed event"); 72 73 assert_eq!(signed.id, draft.expected_event_id); 74 assert_eq!(signed.pubkey, draft.expected_pubkey); 75 assert_eq!(signed.created_at, draft.created_at); 76 assert_eq!(signed.kind, draft.kind); 77 assert_eq!(signed.tags, draft.tags); 78 assert_eq!(signed.content, draft.content); 79 80 let raw_event = crate::types::RadrootsNostrEvent::from_json(signed.raw_json.as_str()) 81 .expect("raw json"); 82 assert_eq!(raw_event.id.to_hex(), signed.id); 83 assert_eq!(raw_event.created_at.as_secs(), u64::from(draft.created_at)); 84 } 85 86 #[test] 87 fn sign_frozen_draft_rejects_wrong_signer() { 88 let keys = fixture_keys(FIXTURE_BOB.secret_key_hex); 89 let draft = post_draft(FIXTURE_ALICE.public_key_hex); 90 let error = radroots_nostr_sign_frozen_draft(&keys, &draft).expect_err("wrong signer"); 91 92 assert!(matches!( 93 error, 94 RadrootsNostrError::FrozenDraftPubkeyMismatch { .. } 95 )); 96 } 97 98 #[test] 99 fn sign_frozen_draft_rejects_event_id_mismatch() { 100 let keys = fixture_keys(FIXTURE_ALICE.secret_key_hex); 101 let mut draft = post_draft(FIXTURE_ALICE.public_key_hex); 102 draft.expected_event_id = "f".repeat(64); 103 let error = radroots_nostr_sign_frozen_draft(&keys, &draft).expect_err("id mismatch"); 104 105 assert!(matches!( 106 error, 107 RadrootsNostrError::FrozenDraftEventIdMismatch { .. } 108 )); 109 } 110 }