Skip to content

Latest commit

 

History

History
149 lines (106 loc) · 5.3 KB

File metadata and controls

149 lines (106 loc) · 5.3 KB

NEAR BTC/Zcash Bridge

Bridge between Bitcoin/Zcash and NEAR Protocol. Users deposit BTC/ZEC to receive nBTC/nZEC (NEP-141 token) and withdraw nBTC/nZEC to receive BTC/ZEC back.

Trust Model:

  • BTC → NEAR (deposit): Trustless verification via BTC Light Client (Merkle proof validation)
  • NEAR → BTC (withdraw): Requires trust in NEAR validator set for Chain Signatures (MPC)

Build / Test / Lint

# Build for development (non-reproducible)
make build-local-bitcoin    # Bitcoin bridge
make build-local-zcash      # Zcash bridge

# Run tests
make test

# Format and clippy
cargo fmt --all             # Format all code
make clippy-bitcoin         # Clippy for Bitcoin
make clippy-zcash           # Clippy for Zcash

Key Architecture

Contracts: contracts/nbtc/ (NEP-141 token), contracts/satoshi-bridge/ (main bridge), contracts/mock-* (testing)

External Dependencies: BTC Light Client (Merkle proof verification), Chain Signatures (MPC signing)

Bridge Flows

Deposit (BTC → nBTC)

1. User sends BTC to deposit address (derived from DepositMsg hash)
2. Relayer: bridge.verify_deposit(tx_proof)
3. Bridge verifies with Light Client → calls nbtc.mint(user, amount)
4. UTXO added to bridge's available set

Withdraw (nBTC → BTC)

1. User: nbtc.ft_transfer(bridge, amount, WithdrawMsg)
   → Tokens TRANSFERRED to bridge (not burned yet!)
2. nBTC: bridge.ft_on_transfer(user, amount, msg) → Bridge returns 0 (keeps tokens)
3. Bridge creates BTC tx, Chain Signatures signs
4. Tx broadcast to Bitcoin network
5. Relayer: bridge.verify_withdraw(tx_proof)
6. Bridge verifies → calls nbtc.burn(user, amount, relayer, fee)
   → Burns from bridge balance (tokens already there!)

Security Invariants

Token Flow (NEP-141)

  • Withdraw tokens already transferred: By the time burn() is called, tokens are in bridge balance via ft_transfer
  • burn_account_id is for events only: Actual burn happens from bridge balance, not from burn_account_id
  • ft_on_transfer return value: 0 = keep all tokens, amount = refund amount
  • Only burn after BTC tx is verified on-chain

Arithmetic Safety

  • overflow-checks = true: All overflow panics in release mode (fail-safe)
  • Use checked_mul(), checked_add() for explicit error handling
  • Prefer panic over silent

State Management

  • Mutate state (mark UTXO used, update balances) BEFORE cross-contract calls
  • Create and emit events AFTER all state mutations complete
  • Cross-contract calls are NOT atomic: Each callback is a separate transaction - must manually rollback state in callback if external call fails

Zcash-Specific

  • Mutual exclusion: actual_received_amounts.len() == 1 ensures EITHER transparent OR Orchard output, never both
  • OVK required: All Orchard bundles must provide valid Outgoing Viewing Key for decryption
  • Address restrictions: Transparent addresses CANNOT accept Orchard bundles (panics)
  • Bridge transparency: Full transaction tracking required, privacy is NOT a design goal
  • Branch IDs hardcoded: Network upgrades require contract redeployment anyway

Critical Patterns

NEAR decorators: #[private] for callbacks, #[access_control_any(roles(...))] for admin functions, #[pause(except(roles(...)))] for pausable functions, assert_one_yocto() to prevent batching

Security checks: Always use require!(condition, "message") for validation, checked_* arithmetic for money operations, emit events AFTER state changes


Safe Functions (Omni Bridge Integration)

The bridge provides "safe" versions of deposit/mint functions primarily used by Omni Bridge:

verify_deposit vs safe_verify_deposit

verify_deposit (standard):

  • Normal deposit flow with fees
  • Charges deposit bridge fee
  • Pays for user's token storage
  • Requires safe_deposit: None in DepositMsg
  • Does NOT revert on mint failures (uses lost & found)

safe_verify_deposit (integration):

  • Primarily used by Omni Bridge
  • NO fees charged
  • User must attach NEAR for storage (via #[payable])
  • Reverts entire transaction if mint fails (no lost & found)
  • Requires safe_deposit: Some(SafeDepositMsg) in DepositMsg
  • post_actions must be None (not supported in safe mode)
  • Safer for integrations - atomic success/failure

mint vs safe_mint (nbtc contract)

mint (standard):

  • Mints tokens unconditionally
  • If account not registered → panics or creates account

safe_mint (integration):

  • Checks if account is registered first
  • If NOT registered → returns U128(0) instead of panicking
  • Used by safe_verify_deposit to detect failures

Design Decisions (Non-Issues)

These patterns are intentional. Do not flag or "fix" them:

  • DAO powers are by design: Governance functions with DAO role are necessary, not a vulnerability
  • Expiry height gap: Buffer for transaction processing delays (Zcash)
  • No validation for self-serialized data: Format guaranteed by construction - only validate external inputs
  • Public API vs private callbacks: If parameter cannot be passed through public API, no vulnerability exists

Git Workflow

Main branch: omni-main (use for PRs)

Before committing: Run cargo test, cargo fmt, cargo clippy. Only commit if user explicitly requests.


Version: 2.1 Last Updated: 2026-02-16