use coracle_lib::events::{EventContent, OwnedEvent}; use coracle_lib::keys::SecretKey; use coracle_lib::pow::{get_leading_zero_bits, get_pow, mine_pow, mine_pow_batch}; use coracle_lib::tags::{Tag, Tags}; fn fixed_secret() -> SecretKey { let bytes: [u8; 32] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ]; SecretKey::from_hex(&hex::encode(bytes)).unwrap() } fn test_event() -> OwnedEvent { EventContent::new("hello nostr", Tags::new()) .kind(1) .stamp(1_700_000_000) .own(fixed_secret().public_key()) } // --- get_leading_zero_bits --- #[test] fn leading_zeros_empty_slice() { assert_eq!(get_leading_zero_bits(&[]), 0); } #[test] fn leading_zeros_all_zeros() { assert_eq!(get_leading_zero_bits(&[0, 0, 0, 0]), 32); } #[test] fn leading_zeros_first_byte_nonzero() { assert_eq!(get_leading_zero_bits(&[0x80, 0x00]), 0); assert_eq!(get_leading_zero_bits(&[0x01, 0x00]), 7); assert_eq!(get_leading_zero_bits(&[0x0F]), 4); } #[test] fn leading_zeros_with_zero_prefix() { assert_eq!(get_leading_zero_bits(&[0x00, 0x01]), 15); assert_eq!(get_leading_zero_bits(&[0x00, 0x00, 0x80]), 16); assert_eq!(get_leading_zero_bits(&[0x00, 0x00, 0x00, 0x01]), 31); } #[test] fn leading_zeros_known_hash() { // "000006d8..." → 0x00, 0x00, 0x06 → 16 + 5 = 21 let hash = hex::decode("000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358") .unwrap(); assert_eq!(get_leading_zero_bits(&hash), 21); } // --- get_pow (free function) --- #[test] fn get_pow_returns_min_of_hash_and_claim() { let event = test_event(); let hashed = mine_pow(&event, 8); // The free function should return at least 8 assert!(get_pow(&hashed.id, &hashed.tags) >= 8); } #[test] fn get_pow_zero_without_nonce_tag() { let hashed = test_event().hash(); assert_eq!(get_pow(&hashed.id, &hashed.tags), 0); } #[test] fn get_pow_capped_by_claimed_difficulty() { // Mine at difficulty 4 — even if hash has more leading zeros, // get_pow returns at most 4 let event = test_event(); let hashed = mine_pow(&event, 4); assert!(get_pow(&hashed.id, &hashed.tags) >= 4); // Actual leading zeros might exceed 4, but get_pow is min(leading, claimed) assert_eq!(get_pow(&hashed.id, &hashed.tags), 4); } // --- HashedEvent::get_pow method --- #[test] fn hashed_event_get_pow_method() { let event = test_event(); let hashed = mine_pow(&event, 8); assert!(hashed.get_pow() >= 8); } #[test] fn hashed_event_get_pow_zero_without_nonce() { let hashed = test_event().hash(); assert_eq!(hashed.get_pow(), 0); } // --- Event::get_pow method --- #[test] fn signed_event_get_pow_method() { let sk = fixed_secret(); let event = test_event(); let hashed = mine_pow(&event, 8); let sig = sk.sign(&hashed.id); let signed = hashed.sign(sig); assert!(signed.get_pow() >= 8); } // --- OwnedEvent::mine_pow method --- #[test] fn owned_event_mine_pow_method() { let event = test_event(); let result = event.mine_pow(8); assert!(result.get_pow() >= 8); } // --- OwnedEvent::mine_pow_batch method --- #[test] fn owned_event_mine_pow_batch_method() { let event = test_event(); let result = event.mine_pow_batch(1, 0, 1_000_000); assert!(result.is_some()); assert!(result.unwrap().get_pow() >= 1); } // --- mine_pow (free function) --- #[test] fn mine_pow_returns_valid_result() { let event = test_event(); let result = mine_pow(&event, 8); assert!(get_leading_zero_bits(&result.id) >= 8); let nonce_tag = result.tags.find("nonce").expect("should have nonce tag"); assert_eq!(nonce_tag.get(2), Some("8")); let _nonce: u64 = nonce_tag.value().parse().expect("nonce should be a number"); } #[test] fn mine_pow_preserves_event_fields() { let event = test_event(); let result = mine_pow(&event, 4); assert_eq!(result.content, event.content); assert_eq!(result.kind, event.kind); assert_eq!(result.created_at, event.created_at); assert_eq!(result.pubkey, event.pubkey); } #[test] fn mine_pow_preserves_existing_tags() { let mut event = test_event(); event.tags.0.push(Tag::new("t", ["nostr"])); let result = mine_pow(&event, 4); assert!(result.tags.find("t").is_some()); assert!(result.tags.find("nonce").is_some()); } #[test] fn mine_pow_does_not_mutate_input() { let event = test_event(); let tags_before = event.tags.clone(); let _result = mine_pow(&event, 4); assert_eq!(event.tags, tags_before); } // --- mine_pow_batch (free function) --- #[test] fn mine_pow_batch_returns_none_when_exhausted() { let event = test_event(); let result = mine_pow_batch(&event, 64, 0, 10); assert!(result.is_none()); } #[test] fn mine_pow_batch_finds_solution_in_range() { let event = test_event(); let result = mine_pow_batch(&event, 1, 0, 1_000_000); assert!(result.is_some()); assert!(get_leading_zero_bits(&result.unwrap().id) >= 1); } #[test] fn mine_pow_batch_respects_start_offset() { let event = test_event(); let a = mine_pow_batch(&event, 4, 0, 1_000_000); let b = mine_pow_batch(&event, 4, 500_000, 1_000_000); assert!(a.is_some()); assert!(b.is_some()); let nonce_a = a.unwrap().tags.find("nonce").unwrap().value().to_string(); let nonce_b = b.unwrap().tags.find("nonce").unwrap().value().to_string(); assert_ne!(nonce_a, nonce_b); } // --- Integration: mine then sign then verify --- #[test] fn mined_event_can_be_signed_and_verified() { let sk = fixed_secret(); let event = test_event(); let hashed = mine_pow(&event, 8); let sig = sk.sign(&hashed.id); let signed = hashed.sign(sig); assert!(signed.verify_id()); signed.verify().expect("mined and signed event should verify"); assert!(signed.get_pow() >= 8); }