# Plan: Proof of Work ## Topic Summary Full implementation of NIP-13 proof-of-work: validation and generation. The mining function receives an `OwnedEvent` reference with a target difficulty and hashes until the difficulty is achieved, returning a `HashedEvent`. The API is batch-based and pure — no threading, no async, no platform-specific code. Callers control the loop and can parallelize or cancel by managing batch ranges externally. ## Chapter Outline 1. **Introduction** — What PoW means in nostr: leading zero bits on the SHA-256 event id. Why it exists (spam resistance, relay filtering, signal of effort). Reference NIP-13. 2. **Module setup** — Add `pub mod pow;` to `coracle-lib/src/lib.rs`. Module imports. 3. **Counting leading zero bits** — `get_leading_zero_bits(hash: &[u8]) -> u8`. Pure utility that iterates bytes: full zero byte = +8 bits, partial byte uses `leading_zeros()`. This is the foundation everything else builds on. 4. **The nonce tag** — NIP-13 specifies a `["nonce", "", ""]` tag. Explain that the nonce is part of the event content that gets hashed — changing it changes the id. Show how to add/read the tag using the existing `Tags` type. 5. **Validation** — `check_pow(event: &HashedEvent, difficulty: u8) -> bool`. Checks that the event id has at least `difficulty` leading zero bits AND that the nonce tag's claimed difficulty is at least `difficulty`. Both checks are needed: the id check proves the work was done, the tag check proves the miner intended at least this difficulty. 6. **Mining** — `mine_pow(event: &OwnedEvent, difficulty: u8, start: Option, count: Option) -> Option`. Clones the event's tags, appends/updates a nonce tag, hashes, checks difficulty. Iterates nonces from `start` (default 0) for up to `count` attempts (default unbounded). Returns `Some(HashedEvent)` on success, `None` if the batch is exhausted. 7. **Usage patterns** — Brief prose (not tangled code) showing: - Simple: `mine_pow(&event, 20, None, None)` — blocks until done - Batched: loop calling with `Some(cursor)` and `Some(100_000)` - Parallel: N threads/workers with non-overlapping ranges - WASM: worker runs batches, main thread stops sending to cancel 8. **What's next** — Transition to filters chapter. ## API Design ```rust /// Count the leading zero bits in a byte slice. pub fn get_leading_zero_bits(hash: &[u8]) -> u8 /// Check that an event meets a minimum proof-of-work difficulty. /// Returns true if the event id has at least `difficulty` leading zero bits /// AND the nonce tag claims at least `difficulty`. pub fn check_pow(event: &HashedEvent, difficulty: u8) -> bool /// Mine proof-of-work for an event. /// /// Tries nonces starting from `start` (default 0) for up to `count` attempts /// (default unbounded). Returns `Some(HashedEvent)` with the nonce tag included /// if a valid nonce is found, `None` if the batch is exhausted. pub fn mine_pow( event: &OwnedEvent, difficulty: u8, start: Option, count: Option, ) -> Option ``` ## Code Organization - **Crate**: `coracle-lib` - **Module**: `coracle-lib/src/pow.rs` - **Exports**: `get_leading_zero_bits`, `check_pow`, `mine_pow` - **Dependencies on existing code**: `OwnedEvent`, `HashedEvent` from `events`, `Tags` from `tags` - Add `pub mod pow;` to `coracle-lib/src/lib.rs` ## Dependencies No new external crates. SHA-256 (`sha2`) and hex encoding are already available from the events chapter. Leading zero bit counting uses only `u8::leading_zeros()` from std. ## Narrative Notes - **Why both id check and tag check**: An event with 25 leading zeros but a nonce tag claiming difficulty 10 only committed to 10 bits of work — the extra zeros are luck. Relays that require difficulty 20 should reject it. Explain this clearly. - **Why batch-based**: The chapter should explain the design choice. A blocking `loop until found` function can't be cancelled and can't be parallelized. By exposing start/count, the library stays pure and platform-agnostic while giving callers full control. Show how this maps to threads (native) and workers (WASM). - **Nonce tag placement**: The tag must be part of the serialized event before hashing. The mining loop adds the tag, hashes, checks, and either returns or tries the next nonce. Work on a clone of the input's tags — don't mutate the original. - **Difficulty semantics**: Difficulty is measured in leading zero *bits*, not bytes or hex characters. Difficulty 20 means the first 20 bits of the 256-bit hash are zero. This allows fine-grained difficulty that isn't locked to 4-bit (hex) or 8-bit (byte) boundaries. ## Design Decisions 1. **Batch-based API over blocking loop**: Enables cancellation and parallelization without platform-specific code. Callers who want simple blocking behavior pass `None` for both start and count. Research showed rust-nostr uses a tight blocking loop with optional multi-threading via feature flags; welshman uses Web Workers. Our approach avoids both by pushing the concurrency concern to the caller. 2. **No prefix generation**: `get_prefixes_for_difficulty()` (seen in rust-nostr and nostr-tools) converts bit difficulty to hex prefixes for relay querying. Deferred — it's orthogonal to mining/validation and can be added later if needed. 3. **Nonce as u64**: rust-nostr uses u128, but u64 gives 1.8×10¹⁹ attempts — more than enough for any practical difficulty. Keeps the tag string shorter and matches JS number limits for WASM interop. 4. **Clone, don't mutate**: `mine_pow` takes `&OwnedEvent` and clones internally. The caller keeps their original event unchanged. This matches the functional style of the event pipeline (each step returns a new type). 5. **check_pow validates both id and tag**: Following NIP-13 semantics. The id check proves the work; the tag check proves intent. Both are needed for correct validation. ## Open Questions None — scope is well-defined and all design decisions are resolved.