lib.rs (15476B)
1 #![forbid(unsafe_code)] 2 3 use core::fmt; 4 use k256::schnorr::signature::Signer; 5 use k256::schnorr::{Signature, SigningKey}; 6 use pocket_types::OwnedEvent as PocketOwnedEvent; 7 use tangle_crypto::{RelaySigner, compute_event_id}; 8 use tangle_groups::{ 9 CanonicalRelayUrl, GroupGeneratedEventBuilder, GroupLimitsConfig, GroupOutboxPayload, 10 GroupPolicyConfig, GroupRuntimeConfig, GroupRuntimeSettingsConfig, KIND_GROUP_CREATE_GROUP, 11 KIND_GROUP_DELETE_EVENT, KIND_GROUP_DELETE_GROUP, KIND_GROUP_EDIT_METADATA, 12 KIND_GROUP_JOIN_REQUEST, KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_PUT_USER, KIND_GROUP_REMOVE_USER, 13 RelaySecret, 14 }; 15 use tangle_protocol::{ 16 Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, 17 event_to_value, 18 }; 19 20 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 21 pub enum FixtureKey { 22 Relay, 23 Owner, 24 Admin, 25 Member, 26 Outsider, 27 } 28 29 impl FixtureKey { 30 pub fn public_key(self) -> PublicKeyHex { 31 let signing_key = self.signing_key(); 32 PublicKeyHex::new(&lower_hex(signing_key.verifying_key().to_bytes().as_ref())) 33 .expect("fixture public key is valid x-only lowercase hex") 34 } 35 36 fn signing_key(self) -> SigningKey { 37 match self { 38 Self::Relay => SigningKey::from_bytes(&[9_u8; 32]).expect("relay fixture key"), 39 Self::Owner => SigningKey::from_bytes(&[10_u8; 32]).expect("owner fixture key"), 40 Self::Admin => SigningKey::from_bytes(&[11_u8; 32]).expect("admin fixture key"), 41 Self::Member => SigningKey::from_bytes(&[12_u8; 32]).expect("member fixture key"), 42 Self::Outsider => SigningKey::from_bytes(&[13_u8; 32]).expect("outsider fixture key"), 43 } 44 } 45 } 46 47 impl fmt::Display for FixtureKey { 48 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 49 formatter.write_str(match self { 50 Self::Relay => "relay", 51 Self::Owner => "owner", 52 Self::Admin => "admin", 53 Self::Member => "member", 54 Self::Outsider => "outsider", 55 }) 56 } 57 } 58 59 pub fn build_fixture_event_from_parts( 60 fixture_key: FixtureKey, 61 created_at: u64, 62 kind: u64, 63 tags: Vec<Vec<String>>, 64 content: &str, 65 ) -> Result<Event, String> { 66 let unsigned = UnsignedEvent::new( 67 fixture_key.public_key(), 68 UnixTimestamp::new(created_at), 69 Kind::new(kind)?, 70 tags.into_iter() 71 .map(Tag::new) 72 .collect::<Result<Vec<_>, _>>()?, 73 content, 74 ); 75 sign_unsigned_event(fixture_key, unsigned) 76 } 77 78 pub const TANGLE_V2_RELAY_URL: &str = "wss://relay.radroots.test"; 79 pub const TANGLE_V2_RELAY_SECRET_HEX: &str = 80 "7777777777777777777777777777777777777777777777777777777777777777"; 81 82 pub fn tangle_v2_group_config( 83 owner: FixtureKey, 84 admins: &[FixtureKey], 85 ) -> Result<GroupRuntimeConfig, String> { 86 GroupRuntimeConfig::new( 87 true, 88 Some(CanonicalRelayUrl::new(TANGLE_V2_RELAY_URL).map_err(|error| error.to_string())?), 89 Some(RelaySecret::from_hex(TANGLE_V2_RELAY_SECRET_HEX).map_err(|error| error.to_string())?), 90 vec![owner.public_key()], 91 admins.iter().map(|admin| admin.public_key()).collect(), 92 GroupRuntimeSettingsConfig::new(GroupPolicyConfig::strict(), GroupLimitsConfig::default()) 93 .map_err(|error| error.to_string())?, 94 ) 95 .map_err(|error| error.to_string()) 96 } 97 98 pub fn tangle_v2_relay_signer() -> Result<RelaySigner, String> { 99 RelaySigner::from_secret_hex(TANGLE_V2_RELAY_SECRET_HEX).map_err(|error| error.to_string()) 100 } 101 102 pub fn tangle_v2_generated_pocket_event( 103 payload: &GroupOutboxPayload, 104 ) -> Result<PocketOwnedEvent, String> { 105 GroupGeneratedEventBuilder::new(tangle_v2_relay_signer()?) 106 .sign_payload_pocket(payload) 107 .map_err(|error| error.to_string()) 108 } 109 110 pub fn tangle_v2_event( 111 fixture_key: FixtureKey, 112 created_at: u64, 113 kind: u64, 114 tags: Vec<Tag>, 115 content: &str, 116 ) -> Result<Event, String> { 117 let unsigned = UnsignedEvent::new( 118 fixture_key.public_key(), 119 UnixTimestamp::new(created_at), 120 Kind::new(kind)?, 121 tags, 122 content, 123 ); 124 sign_unsigned_event(fixture_key, unsigned) 125 } 126 127 pub fn tangle_v2_auth_event( 128 fixture_key: FixtureKey, 129 challenge: &str, 130 created_at: u64, 131 ) -> Result<Event, String> { 132 tangle_v2_event( 133 fixture_key, 134 created_at, 135 22_242, 136 vec![ 137 tangle_v2_tag("relay", &[TANGLE_V2_RELAY_URL])?, 138 tangle_v2_tag("challenge", &[challenge])?, 139 ], 140 "", 141 ) 142 } 143 144 pub fn tangle_v2_group_create_event( 145 fixture_key: FixtureKey, 146 group_id: &str, 147 created_at: u64, 148 flags: &[&str], 149 ) -> Result<Event, String> { 150 let mut tags = vec![ 151 tangle_v2_group_tag(group_id)?, 152 tangle_v2_tag("name", &[group_id])?, 153 ]; 154 for flag in flags { 155 tags.push(tangle_v2_tag(flag, &[])?); 156 } 157 tangle_v2_event( 158 fixture_key, 159 created_at, 160 KIND_GROUP_CREATE_GROUP.into(), 161 tags, 162 "", 163 ) 164 } 165 166 pub fn tangle_v2_group_metadata_event( 167 fixture_key: FixtureKey, 168 group_id: &str, 169 name: &str, 170 created_at: u64, 171 flags: &[&str], 172 ) -> Result<Event, String> { 173 let mut tags = vec![ 174 tangle_v2_group_tag(group_id)?, 175 tangle_v2_tag("name", &[name])?, 176 ]; 177 for flag in flags { 178 tags.push(tangle_v2_tag(flag, &[])?); 179 } 180 tangle_v2_event( 181 fixture_key, 182 created_at, 183 KIND_GROUP_EDIT_METADATA.into(), 184 tags, 185 "", 186 ) 187 } 188 189 pub fn tangle_v2_put_user_event( 190 fixture_key: FixtureKey, 191 group_id: &str, 192 target: FixtureKey, 193 created_at: u64, 194 ) -> Result<Event, String> { 195 tangle_v2_event( 196 fixture_key, 197 created_at, 198 KIND_GROUP_PUT_USER.into(), 199 vec![ 200 tangle_v2_group_tag(group_id)?, 201 tangle_v2_pubkey_tag(target)?, 202 ], 203 "", 204 ) 205 } 206 207 pub fn tangle_v2_remove_user_event( 208 fixture_key: FixtureKey, 209 group_id: &str, 210 target: FixtureKey, 211 created_at: u64, 212 ) -> Result<Event, String> { 213 tangle_v2_event( 214 fixture_key, 215 created_at, 216 KIND_GROUP_REMOVE_USER.into(), 217 vec![ 218 tangle_v2_group_tag(group_id)?, 219 tangle_v2_pubkey_tag(target)?, 220 ], 221 "", 222 ) 223 } 224 225 pub fn tangle_v2_join_event( 226 fixture_key: FixtureKey, 227 group_id: &str, 228 created_at: u64, 229 ) -> Result<Event, String> { 230 tangle_v2_group_event( 231 fixture_key, 232 group_id, 233 created_at, 234 KIND_GROUP_JOIN_REQUEST.into(), 235 "", 236 ) 237 } 238 239 pub fn tangle_v2_leave_event( 240 fixture_key: FixtureKey, 241 group_id: &str, 242 created_at: u64, 243 ) -> Result<Event, String> { 244 tangle_v2_group_event( 245 fixture_key, 246 group_id, 247 created_at, 248 KIND_GROUP_LEAVE_REQUEST.into(), 249 "", 250 ) 251 } 252 253 pub fn tangle_v2_delete_event_event( 254 fixture_key: FixtureKey, 255 group_id: &str, 256 target: &Event, 257 created_at: u64, 258 ) -> Result<Event, String> { 259 tangle_v2_event( 260 fixture_key, 261 created_at, 262 KIND_GROUP_DELETE_EVENT.into(), 263 vec![ 264 tangle_v2_group_tag(group_id)?, 265 tangle_v2_event_tag(target.id())?, 266 ], 267 "", 268 ) 269 } 270 271 pub fn tangle_v2_delete_group_event( 272 fixture_key: FixtureKey, 273 group_id: &str, 274 created_at: u64, 275 ) -> Result<Event, String> { 276 tangle_v2_group_event( 277 fixture_key, 278 group_id, 279 created_at, 280 KIND_GROUP_DELETE_GROUP.into(), 281 "", 282 ) 283 } 284 285 pub fn tangle_v2_group_event( 286 fixture_key: FixtureKey, 287 group_id: &str, 288 created_at: u64, 289 kind: u64, 290 content: &str, 291 ) -> Result<Event, String> { 292 tangle_v2_event( 293 fixture_key, 294 created_at, 295 kind, 296 vec![tangle_v2_group_tag(group_id)?], 297 content, 298 ) 299 } 300 301 pub fn tangle_v2_group_tag(group_id: &str) -> Result<Tag, String> { 302 tangle_v2_tag("h", &[group_id]) 303 } 304 305 pub fn tangle_v2_address_group_tag(group_id: &str) -> Result<Tag, String> { 306 tangle_v2_tag("d", &[group_id]) 307 } 308 309 pub fn tangle_v2_pubkey_tag(fixture_key: FixtureKey) -> Result<Tag, String> { 310 let pubkey = fixture_key.public_key(); 311 tangle_v2_tag("p", &[pubkey.as_str()]) 312 } 313 314 pub fn tangle_v2_event_tag(event_id: &EventId) -> Result<Tag, String> { 315 tangle_v2_tag("e", &[event_id.as_str()]) 316 } 317 318 pub fn tangle_v2_tag(name: &str, values: &[&str]) -> Result<Tag, String> { 319 let mut parts = Vec::with_capacity(values.len() + 1); 320 parts.push(name.to_owned()); 321 parts.extend(values.iter().map(|value| (*value).to_owned())); 322 Tag::new(parts) 323 } 324 325 pub fn fixture_event_json(event: &Event) -> serde_json::Value { 326 event_to_value(event) 327 } 328 329 fn sign_unsigned_event(fixture_key: FixtureKey, unsigned: UnsignedEvent) -> Result<Event, String> { 330 let signing_key = fixture_key.signing_key(); 331 let event_id = compute_event_id(&unsigned); 332 let event_id_bytes = 333 fixed_hex_bytes(event_id.as_str(), 32, "event id").expect("computed event id decodes"); 334 let signature: Signature = signing_key.sign(&event_id_bytes); 335 let signature = 336 SignatureHex::new(&lower_hex(signature.to_bytes().as_ref())).expect("signature hex"); 337 Ok(Event::new(event_id, unsigned, signature)) 338 } 339 340 fn fixed_hex_bytes(value: &str, expected: usize, scalar: &str) -> Result<Vec<u8>, String> { 341 if value.len() != expected * 2 { 342 return Err(format!( 343 "{scalar} must decode to {expected} bytes, got {} hex characters", 344 value.len() 345 )); 346 } 347 let mut output = Vec::with_capacity(expected); 348 for chunk in value.as_bytes().chunks_exact(2) { 349 output.push((hex_value(chunk[0], scalar)? << 4) | hex_value(chunk[1], scalar)?); 350 } 351 Ok(output) 352 } 353 354 fn hex_value(value: u8, scalar: &str) -> Result<u8, String> { 355 match value { 356 b'0'..=b'9' => Ok(value - b'0'), 357 b'a'..=b'f' => Ok(value - b'a' + 10), 358 _ => Err(format!("{scalar} must be lowercase hex")), 359 } 360 } 361 362 fn lower_hex(bytes: &[u8]) -> String { 363 const HEX: &[u8; 16] = b"0123456789abcdef"; 364 let mut output = String::with_capacity(bytes.len() * 2); 365 for byte in bytes { 366 output.push(char::from(HEX[usize::from(byte >> 4)])); 367 output.push(char::from(HEX[usize::from(byte & 0x0f)])); 368 } 369 output 370 } 371 372 #[cfg(test)] 373 mod tests { 374 use super::{ 375 FixtureKey, build_fixture_event_from_parts, fixed_hex_bytes, fixture_event_json, 376 tangle_v2_auth_event, tangle_v2_generated_pocket_event, tangle_v2_group_config, 377 tangle_v2_group_create_event, tangle_v2_group_event, tangle_v2_group_metadata_event, 378 tangle_v2_join_event, tangle_v2_put_user_event, 379 }; 380 use tangle_crypto::{event_id_matches, verify_event_signature}; 381 use tangle_groups::{GroupOutboxPayload, KIND_GROUP_CREATE_GROUP, KIND_GROUP_METADATA}; 382 use tangle_protocol::UnixTimestamp; 383 384 #[test] 385 fn fixture_keys_have_stable_synthetic_public_keys() { 386 assert_eq!(FixtureKey::Relay.to_string(), "relay"); 387 assert_eq!(FixtureKey::Owner.to_string(), "owner"); 388 assert_eq!(FixtureKey::Admin.to_string(), "admin"); 389 assert_eq!(FixtureKey::Member.to_string(), "member"); 390 assert_eq!(FixtureKey::Outsider.to_string(), "outsider"); 391 assert_eq!( 392 FixtureKey::Owner.public_key().as_str(), 393 "f76a39d05686e34a4420897e359371836145dd3973e3982568b60f8433adde6e" 394 ); 395 assert_ne!( 396 FixtureKey::Owner.public_key(), 397 FixtureKey::Admin.public_key() 398 ); 399 } 400 401 #[test] 402 fn tangle_v2_builders_create_deterministic_signed_events() { 403 let first = 404 tangle_v2_group_create_event(FixtureKey::Owner, "Farm", 1_714_124_433, &["private"]) 405 .expect("first"); 406 let second = 407 tangle_v2_group_create_event(FixtureKey::Owner, "Farm", 1_714_124_433, &["private"]) 408 .expect("second"); 409 let auth = 410 tangle_v2_auth_event(FixtureKey::Owner, "challenge-001", 1_714_124_434).expect("auth"); 411 412 assert_eq!(first.id(), second.id()); 413 assert_eq!(verify_event_signature(&first), Ok(())); 414 assert_eq!(verify_event_signature(&auth), Ok(())); 415 assert!(event_id_matches(&first)); 416 assert_eq!(first.unsigned().kind().as_u32(), KIND_GROUP_CREATE_GROUP); 417 assert_eq!(auth.unsigned().kind().as_u32(), 22_242); 418 } 419 420 #[test] 421 fn tangle_v2_builders_cover_group_config_and_generated_events() { 422 let config = 423 tangle_v2_group_config(FixtureKey::Owner, &[FixtureKey::Admin]).expect("config"); 424 let metadata = tangle_v2_group_metadata_event( 425 FixtureKey::Owner, 426 "Farm", 427 "Market", 428 1_714_124_435, 429 &["hidden"], 430 ) 431 .expect("metadata"); 432 let put = 433 tangle_v2_put_user_event(FixtureKey::Admin, "Farm", FixtureKey::Member, 1_714_124_436) 434 .expect("put"); 435 let join = tangle_v2_join_event(FixtureKey::Member, "Farm", 1_714_124_437).expect("join"); 436 let normal = tangle_v2_group_event(FixtureKey::Member, "Farm", 1_714_124_438, 1, "harvest") 437 .expect("normal"); 438 let payload = GroupOutboxPayload::new( 439 KIND_GROUP_METADATA, 440 UnixTimestamp::new(1_714_124_439), 441 vec![vec!["d".to_owned(), "Farm".to_owned()]], 442 "", 443 ); 444 let generated = tangle_v2_generated_pocket_event(&payload).expect("generated"); 445 446 assert!(config.enabled()); 447 assert_eq!(config.owner_pubkeys(), &[FixtureKey::Owner.public_key()]); 448 assert_eq!(config.admin_pubkeys(), &[FixtureKey::Admin.public_key()]); 449 assert_eq!(verify_event_signature(&metadata), Ok(())); 450 assert_eq!(verify_event_signature(&put), Ok(())); 451 assert_eq!(verify_event_signature(&join), Ok(())); 452 assert_eq!(verify_event_signature(&normal), Ok(())); 453 generated.verify().expect("generated signature"); 454 assert_eq!(u32::from(generated.kind().as_u16()), KIND_GROUP_METADATA); 455 } 456 457 #[test] 458 fn fixture_json_uses_signed_event_shape() { 459 let event = 460 build_fixture_event_from_parts(FixtureKey::Member, 1_714_124_440, 1, Vec::new(), "hi") 461 .expect("event"); 462 let json = fixture_event_json(&event); 463 464 assert_eq!(verify_event_signature(&event), Ok(())); 465 assert_eq!(json["kind"], 1); 466 assert_eq!(json["content"], "hi"); 467 } 468 469 #[test] 470 fn fixture_builder_rejects_invalid_parts() { 471 let bad_tag = build_fixture_event_from_parts(FixtureKey::Owner, 1, 1, vec![Vec::new()], "") 472 .expect_err("tag"); 473 let bad_kind = 474 build_fixture_event_from_parts(FixtureKey::Owner, 1, 4_294_967_296, Vec::new(), "") 475 .expect_err("kind"); 476 477 assert_eq!(bad_tag, "tag must not be empty"); 478 assert_eq!(bad_kind, "kind must fit in u32, got 4294967296"); 479 } 480 481 #[test] 482 fn fixed_hex_bytes_validates_expected_width_and_lowercase() { 483 assert_eq!(fixed_hex_bytes("0a", 1, "value"), Ok(vec![10])); 484 assert_eq!( 485 fixed_hex_bytes("0a", 2, "value").expect_err("width"), 486 "value must decode to 2 bytes, got 2 hex characters" 487 ); 488 assert_eq!( 489 fixed_hex_bytes("0G", 1, "value").expect_err("case"), 490 "value must be lowercase hex" 491 ); 492 } 493 }