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