From 9f7087224561055f4214c903bee30a23c4f26190 Mon Sep 17 00:00:00 2001 From: karim-en Date: Thu, 12 Feb 2026 21:51:30 +0000 Subject: [PATCH 1/3] chore: claude and agents files for contract --- Makefile | 14 +++++++ contract/AGENTS.md | 1 + contract/CLAUDE.md | 101 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 contract/AGENTS.md create mode 100644 contract/CLAUDE.md diff --git a/Makefile b/Makefile index 43c97943..31c38aaa 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ FEATURES = bitcoin dogecoin litecoin zcash build: $(addprefix build-,$(FEATURES)) +build-local: $(addprefix build-local-,$(FEATURES)) + clippy: $(addprefix clippy-,$(FEATURES)) fmt: $(addprefix fmt-,$(FEATURES)) @@ -19,6 +21,18 @@ $(foreach feature,$(FEATURES), \ ) \ ) +ifeq ($(shell uname),Darwin) +LLVM_PATH = /opt/homebrew/opt/llvm@19/bin +LOCAL_ENV = PATH="$(LLVM_PATH):$(PATH)" CC=$(LLVM_PATH)/clang AR=$(LLVM_PATH)/llvm-ar +endif + +$(foreach feature,$(FEATURES), \ + $(eval build-local-$(feature): ; \ + $(LOCAL_ENV) cargo near build non-reproducible-wasm --no-default-features --features "$(feature)" --manifest-path $(NEAR_MANIFEST) && \ + mkdir -p res && mv ./contract/target/near/btc_light_client_contract.wasm ./res/$(feature).wasm \ + ) \ +) + $(foreach feature,$(FEATURES), \ $(eval clippy-$(feature): ; cargo clippy --no-default-features --features "$(feature)" --manifest-path $(NEAR_MANIFEST) -- $(LINT_OPTIONS)) \ ) diff --git a/contract/AGENTS.md b/contract/AGENTS.md new file mode 100644 index 00000000..ceb2b988 --- /dev/null +++ b/contract/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md diff --git a/contract/CLAUDE.md b/contract/CLAUDE.md new file mode 100644 index 00000000..47487246 --- /dev/null +++ b/contract/CLAUDE.md @@ -0,0 +1,101 @@ +# BTC Light Client Contract + +## Project Overview + +A Bitcoin SPV light client implemented as a NEAR Protocol smart contract. It verifies and stores block headers on-chain, enabling trustless verification of Bitcoin (and other UTXO chain) transactions without running a full node. Relayers can submit blocks and they are verified on-chain via proof-of-work validation, difficulty adjustment checks, and chain selection rules. + +**Supported chains** (compile-time feature flags, mutually exclusive): +- **Bitcoin** (`bitcoin`, default) — SHA-256d PoW, 2016-block difficulty adjustment +- **Litecoin** (`litecoin`) — Scrypt PoW, modified difficulty calc +- **Dogecoin** (`dogecoin`) — Scrypt + AuxPoW (merged mining), Digishield difficulty +- **Zcash** (`zcash`) — Equihash PoW, MTP-based averaging window retarget + +## Core Functions + +### Write Methods + +| Function | Description | +|----------|-------------| +| `init(args: InitArgs)` | Initialize contract with genesis block, network config, and GC threshold | +| `submit_blocks(headers: Vec)` | Submit block headers for on-chain PoW verification and storage. Handles main chain extension, fork tracking, and automatic reorgs | +| `run_mainchain_gc(batch_size: u64)` | Garbage collect oldest blocks beyond `gc_threshold` to manage storage costs | +| `migrate(network: Network)` | Contract state migration (e.g. V1 → V2 schema changes) | + +### View Methods + +| Function | Description | +|----------|-------------| +| `get_last_block_header() -> ExtendedHeader` | Current chain tip header with chain_work and height | +| `get_last_block_height() -> u64` | Current chain tip height | +| `get_block_hash_by_height(height: u64) -> Option` | Look up main chain block hash at a given height | +| `get_height_by_block_hash(blockhash: H256) -> Option` | Reverse lookup: block hash → height | +| `get_mainchain_size() -> u64` | Number of blocks currently stored on main chain | +| `get_last_n_blocks_hashes(skip, limit) -> Vec` | Paginated block hash query from tip | +| `verify_transaction_inclusion(args: ProofArgs) -> bool` | SPV proof: verify a tx is in a block via merkle proof | + +## Build & Test + +Requires: Rust 1.86.0, `cargo-near`, `wasm32-unknown-unknown` target. + +```bash +# Build all chain variants locally (non-reproducible) +make build-local + +# Run all tests (all features) +make test + +# Lint (strict: -D warnings -D clippy::pedantic) +make clippy + +# Format check +make fmt +``` + +## Architecture & Key Concepts + +### On-Chain State (`BtcLightClient` in `contract/src/lib.rs`) + +- **headers_pool**: `LookupMap` — all stored headers (main chain + forks) +- **mainchain_height_to_header** / **mainchain_header_to_height**: bidirectional main chain index +- **mainchain_tip_blockhash**: current chain tip +- **gc_threshold**: max stored blocks before garbage collection kicks in + +### Block Submission Flow + +1. Call `submit_blocks(headers)` with one or more block headers +2. For each header, the contract: + - Looks up the previous block in storage (rejects unattached blocks) + - **Verifies PoW**: block hash ≤ target derived from `bits` field + - **Checks difficulty adjustment**: validates `bits` matches the chain's retarget algorithm + - Computes accumulated `chain_work` +3. If the block extends the main chain tip → appended directly +4. If it's a fork → stored separately; if fork's `chain_work` > main chain → **automatic reorg** + +### Chain Reorganization + +When a fork accumulates more work than the main chain: +1. Walk both chains back to common ancestor +2. Demote old main chain blocks (remove from height index) +3. Promote fork blocks to main chain +4. Update tip pointer + +### Transaction Inclusion Verification + +`verify_transaction_inclusion(ProofArgs)` — SPV proof: given a tx hash, block hash, and merkle proof, verifies the transaction is in the block by recomputing the merkle root. + + +### Garbage Collection + +`run_mainchain_gc(batch_size)` removes the oldest blocks from storage when chain exceeds `gc_threshold`. Blocks older than the initial block are pruned. + +## Chain-Specific Implementation Details + +Each chain lives in its own module and is selected at compile time via feature flags: + +| Chain | Module | PoW Hash | Difficulty Algorithm | Special | +|-------|--------|----------|---------------------|---------| +| Bitcoin | `bitcoin.rs` | SHA-256d | Every 2016 blocks, 14-day target | — | +| Litecoin | `litecoin.rs` | Scrypt | Every 2016 blocks, 3.5-day target | Right-shift before multiply | +| Dogecoin | `dogecoin.rs` | Scrypt | Per-block Digishield (after 145k) | AuxPoW merged mining | +| Zcash | `zcash.rs` | Equihash (200,9) | 45-block averaging window, MTP | Blossom hardfork handling | + From 7a989ff4aaaee6d52b22e4d23b5fcab74a978dc0 Mon Sep 17 00:00:00 2001 From: karim-en Date: Thu, 12 Feb 2026 22:51:44 +0000 Subject: [PATCH 2/3] chore: update claude.md --- contract/CLAUDE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contract/CLAUDE.md b/contract/CLAUDE.md index 47487246..cb5b00b5 100644 --- a/contract/CLAUDE.md +++ b/contract/CLAUDE.md @@ -83,11 +83,17 @@ When a fork accumulates more work than the main chain: `verify_transaction_inclusion(ProofArgs)` — SPV proof: given a tx hash, block hash, and merkle proof, verifies the transaction is in the block by recomputing the merkle root. +**Important**: This function is vulnerable to the standard Bitcoin merkle tree second-preimage attack — it may return `true` for an internal node hash rather than a real transaction hash. Block headers do not contain the transaction count, so proof depth cannot be validated on-chain. Callers MUST validate that the `tx_id` corresponds to a valid transaction (e.g., by verifying raw transaction data) before trusting the inclusion proof. + ### Garbage Collection `run_mainchain_gc(batch_size)` removes the oldest blocks from storage when chain exceeds `gc_threshold`. Blocks older than the initial block are pruned. +## Important Build Flags + +The release profile sets `overflow-checks = true` in `Cargo.toml`. This ensures all arithmetic overflows/underflows panic safely instead of wrapping. Do not remove this flag — several view functions rely on it for safe behavior with untrusted inputs. + ## Chain-Specific Implementation Details Each chain lives in its own module and is selected at compile time via feature flags: From 4414efd4a0828935f409416d8ce8eb83ff31abf1 Mon Sep 17 00:00:00 2001 From: karim-en Date: Thu, 9 Apr 2026 13:02:20 +0100 Subject: [PATCH 3/3] Review fixes --- contract/CLAUDE.md | 50 ++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/contract/CLAUDE.md b/contract/CLAUDE.md index cb5b00b5..8ef954d2 100644 --- a/contract/CLAUDE.md +++ b/contract/CLAUDE.md @@ -2,44 +2,22 @@ ## Project Overview -A Bitcoin SPV light client implemented as a NEAR Protocol smart contract. It verifies and stores block headers on-chain, enabling trustless verification of Bitcoin (and other UTXO chain) transactions without running a full node. Relayers can submit blocks and they are verified on-chain via proof-of-work validation, difficulty adjustment checks, and chain selection rules. - -**Supported chains** (compile-time feature flags, mutually exclusive): -- **Bitcoin** (`bitcoin`, default) — SHA-256d PoW, 2016-block difficulty adjustment -- **Litecoin** (`litecoin`) — Scrypt PoW, modified difficulty calc -- **Dogecoin** (`dogecoin`) — Scrypt + AuxPoW (merged mining), Digishield difficulty -- **Zcash** (`zcash`) — Equihash PoW, MTP-based averaging window retarget - -## Core Functions - -### Write Methods - -| Function | Description | -|----------|-------------| -| `init(args: InitArgs)` | Initialize contract with genesis block, network config, and GC threshold | -| `submit_blocks(headers: Vec)` | Submit block headers for on-chain PoW verification and storage. Handles main chain extension, fork tracking, and automatic reorgs | -| `run_mainchain_gc(batch_size: u64)` | Garbage collect oldest blocks beyond `gc_threshold` to manage storage costs | -| `migrate(network: Network)` | Contract state migration (e.g. V1 → V2 schema changes) | - -### View Methods - -| Function | Description | -|----------|-------------| -| `get_last_block_header() -> ExtendedHeader` | Current chain tip header with chain_work and height | -| `get_last_block_height() -> u64` | Current chain tip height | -| `get_block_hash_by_height(height: u64) -> Option` | Look up main chain block hash at a given height | -| `get_height_by_block_hash(blockhash: H256) -> Option` | Reverse lookup: block hash → height | -| `get_mainchain_size() -> u64` | Number of blocks currently stored on main chain | -| `get_last_n_blocks_hashes(skip, limit) -> Vec` | Paginated block hash query from tip | -| `verify_transaction_inclusion(args: ProofArgs) -> bool` | SPV proof: verify a tx is in a block via merkle proof | +A Bitcoin/Litecoin/Dogecoin/Zcash SPV light client implemented as a NEAR Protocol smart contract. It verifies and stores block headers on-chain, enabling trustless verification of Bitcoin (and other UTXO chain) transactions without running a full node. Relayers can submit blocks and they are verified on-chain via proof-of-work validation, difficulty adjustment checks, and chain selection rules. ## Build & Test Requires: Rust 1.86.0, `cargo-near`, `wasm32-unknown-unknown` target. ```bash +# Build specific chain variant (reproducible via cargo-near) +make build-bitcoin # default, feature: bitcoin +make build-litecoin # feature: litecoin +make build-dogecoin # feature: dogecoin +make build-zcash # feature: zcash + # Build all chain variants locally (non-reproducible) -make build-local +make build-local # all variants +make build-local-bitcoin # single variant # Run all tests (all features) make test @@ -58,7 +36,7 @@ make fmt - **headers_pool**: `LookupMap` — all stored headers (main chain + forks) - **mainchain_height_to_header** / **mainchain_header_to_height**: bidirectional main chain index - **mainchain_tip_blockhash**: current chain tip -- **gc_threshold**: max stored blocks before garbage collection kicks in +- **gc_threshold**: max number of mainchain blocks to keep in storage. When the mainchain grows beyond this, the oldest mainchain blocks are pruned. GC runs automatically during `submit_blocks()` (with `batch_size` = number of submitted headers) and can also be triggered manually via `run_mainchain_gc(batch_size)`. Only mainchain blocks are deleted; fork/sidechain blocks are not affected ### Block Submission Flow @@ -79,6 +57,8 @@ When a fork accumulates more work than the main chain: 3. Promote fork blocks to main chain 4. Update tip pointer +**Caveat**: If mainchain blocks near the fork point have been garbage collected, reorg will fail — the contract panics with `PrevBlockNotFound` when it cannot walk the chain back to the common ancestor. This means GC depth must be set conservatively relative to expected fork lengths + ### Transaction Inclusion Verification `verify_transaction_inclusion(ProofArgs)` — SPV proof: given a tx hash, block hash, and merkle proof, verifies the transaction is in the block by recomputing the merkle root. @@ -88,7 +68,7 @@ When a fork accumulates more work than the main chain: ### Garbage Collection -`run_mainchain_gc(batch_size)` removes the oldest blocks from storage when chain exceeds `gc_threshold`. Blocks older than the initial block are pruned. +`run_mainchain_gc(batch_size)` removes the oldest mainchain blocks from storage when the mainchain exceeds `gc_threshold`. Only mainchain blocks are pruned; fork/sidechain blocks remain. The `batch_size` parameter limits how many blocks are removed per call to bound gas usage. ## Important Build Flags @@ -96,12 +76,12 @@ The release profile sets `overflow-checks = true` in `Cargo.toml`. This ensures ## Chain-Specific Implementation Details -Each chain lives in its own module and is selected at compile time via feature flags: +Each chain lives in its own module and is selected at compile time via mutually exclusive feature flags (`--no-default-features --features ""`): | Chain | Module | PoW Hash | Difficulty Algorithm | Special | |-------|--------|----------|---------------------|---------| | Bitcoin | `bitcoin.rs` | SHA-256d | Every 2016 blocks, 14-day target | — | | Litecoin | `litecoin.rs` | Scrypt | Every 2016 blocks, 3.5-day target | Right-shift before multiply | | Dogecoin | `dogecoin.rs` | Scrypt | Per-block Digishield (after 145k) | AuxPoW merged mining | -| Zcash | `zcash.rs` | Equihash (200,9) | 45-block averaging window, MTP | Blossom hardfork handling | +| Zcash | `zcash.rs` | Equihash (200,9) | 17-block averaging window, MTP | Blossom hardfork handling |