218 lines
5.8 KiB
Rust
218 lines
5.8 KiB
Rust
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);
|
|
}
|