# SeedPay Documentation import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; This specification is **v0.3 draft** — research software, not for production use. SeedPay is a payment channel protocol for **streaming delivery** — when you're paying as data arrives, chunk by chunk, and you need cryptographic proof it was actually delivered. The first application is BitTorrent: seeders earn USDC for sharing files, leechers pay fractions of a cent per megabyte. But the primitive is general — it applies to any scenario where delivery takes time and can fail partway (decentralized AI inference, CDN, etc). Quick Links [#quick-links] Protocol at a Glance [#protocol-at-a-glance] | Property | Value | | --------------------- | ----------------------------------------------------- | | **Version** | v0.3 (Pre-Alpha) | | **Primitive** | Unidirectional payment channels with fair exchange | | **First Application** | Pay-per-megabyte BitTorrent file sharing | | **Payment** | USDC via streaming off-chain checks | | **Pricing** | $0.0001-$0.001 per MB | | **Privacy** | ECDH ephemeral session keys (unlinkable sessions) | | **Chain** | Solana (initial), extensible to EVM chains | | **On-chain cost** | 2 transactions per session (\~$0.003 total) | | **Trust model** | Neither party can cheat — cryptographic fair exchange | How It Works [#how-it-works] 1. **Handshake** — Peers advertise SeedPay support via BEP 10 extended handshake 2. **Channel Setup** — ECDH key exchange derives ephemeral Session UUID, Leecher deposits USDC into on-chain escrow 3. **Verification** — Seeder independently verifies the payment channel on-chain before serving data 4. **Data Transfer** — Standard BitTorrent piece exchange with streaming signed payment checks 5. **Settlement** — Seeder submits the highest check to collect; remainder refunded to Leecher --- # Data Transfer import { Callout } from 'fumadocs-ui/components/callout'; After a payment session is established via [verification](/docs/core-protocol/verification), the Seeder begins serving pieces to the Leecher while tracking payment checks. This phase reuses the normal BitTorrent `request`/`piece`/`cancel` messages and adds only local accounting. Mapping Pieces to Cost [#mapping-pieces-to-cost] SeedPay doesn't modify BitTorrent wire messages for data transfer. Instead, the Seeder observes each `request` message and converts the byte length into monetary cost: ``` cost = bytes / (1024 × 1024) × price_per_mb ``` The Seeder maintains per payment session: | Field | Description | | ------------------- | ----------------------------------------- | | `channel_id` | Payment channel identifier | | `channel_deposit` | Total amount locked in escrow | | `last_check_nonce` | Highest nonce from valid payment checks | | `last_check_amount` | Highest amount authorized by valid checks | | `bytes_downloaded` | Cumulative bytes served | | `price_per_mb` | Agreed rate from handshake | Tracking on successful `piece` send is RECOMMENDED for accuracy. Serving Requests [#serving-requests] For each `request(index, begin, length)` from the Leecher, the Seeder: 1. **Looks up** the payment session for this connection 2. **Computes** the cost of serving this block 3. **Computes** cumulative cost: `(bytes_downloaded + length) / (1024 × 1024) × price_per_mb` 4. **Checks** whether `last_check_amount` covers the `cumulative_cost` If Payment is Sufficient [#if-payment-is-sufficient] The Seeder increments `bytes_downloaded` and sends the `piece` message as normal. If Payment is Insufficient [#if-payment-is-insufficient] The Seeder MAY send a `payment_check_required` message: ```json { "type": "payment_check_required", "required_amount": 0.005, "current_check_amount": 0.003, "estimated_remaining_mb": 20.0 } ``` Before choking, the Seeder SHOULD wait a short grace period (e.g. 5 seconds) to allow in-flight payment checks to arrive. This prevents unnecessary choke/unchoke cycles. After the grace period, if no valid check has been received, the Seeder chokes the Leecher until a new payment check arrives. Payment Check Processing [#payment-check-processing] When the Seeder receives a `payment_check` during data transfer: 1. **Verify** the signature using the Leecher's public key from channel state 2. **Verify** nonce is greater than `last_check_nonce` (prevents replay) 3. **Verify** amount is greater than or equal to `last_check_amount` (monotonically increasing) 4. **Verify** amount does not exceed `channel_deposit` If all validations pass: * Update `last_check_nonce` and `last_check_amount` * Unchoke the Leecher if previously choked * Continue serving pieces If validation fails: * Reject the check and keep the Leecher choked * MAY send a `payment_check_rejected` error (see [Message Types](/docs/reference/message-types)) Session Lifetime [#session-lifetime] A Seeder SHOULD treat a session as expired if: * The channel timeout has been reached * The connection is closed or idle for too long * The ephemeral keys have been deleted When a session ends, the Seeder SHOULD: 1. Submit the highest valid payment check to [close the channel cooperatively](/docs/core-protocol/payment-channels#cooperative-close-normal) 2. Discard session state (channel tracking, usage counters) 3. Delete the ephemeral secret key (ensures forward secrecy) The Seeder MAY continue to serve as a free seeder (if local policy allows) or close the connection entirely. --- # Handshake Phase import { Callout } from 'fumadocs-ui/components/callout'; The handshake phase extends the existing BitTorrent peer wire handshake (BEP 3) and extension protocol (BEP 10) to advertise SeedPay support and payment terms — without breaking compatibility with non-SeedPay clients. Standard BitTorrent Handshake [#standard-bittorrent-handshake] When two peers connect over TCP, they first perform the normal BitTorrent handshake as defined in BEP 3: * The protocol string `"BitTorrent protocol"` * An 8-byte `reserved` field used for feature flags * The torrent's `info hash` * Each peer's 20-byte `peer_id` SeedPay doesn't modify this message. Instead, it relies on the extension protocol flag in the `reserved` field. If both peers set the BEP 10 "extended messaging" bit, they proceed with the extended handshake. Extended Handshake (BEP 10) [#extended-handshake-bep-10] Immediately after the standard handshake, peers exchange an extended handshake (BitTorrent message ID `20`, extended ID `0`). The payload is a bencoded dictionary that advertises supported extensions via the `m` map. SeedPay defines a new extension named `"seedpay"`. A **Seeder** that supports paid seeding MUST include an entry in the `m` dictionary along with payment capabilities: ```json title="Seeder Extended Handshake" { "m": { "seedpay": 5, "ut_metadata": 2 }, "v": "SeedPayClient 0.3", "seedpay": { "wallet": "DYw8jCN...", "price_per_mb": 0.0001, "min_prepayment": 0.01, "chain": "solana" } } ``` The `seedpay` dictionary contains: | Field | Description | | ---------------- | ------------------------------------------------------- | | `wallet` | Seeder's on-chain wallet address for receiving payments | | `price_per_mb` | Quoted price in USDC per megabyte | | `min_prepayment` | Minimum amount required to start a paid session | | `chain` | Settlement chain identifier (e.g. `"solana"`) | The numeric value `5` is the local extension ID for SeedPay messages on this connection. It is implementation-defined and only needs to be consistent for this peer pair. A **Leecher** that supports SeedPay also includes `seedpay` in its `m` map: ```json title="Leecher Extended Handshake" { "m": { "seedpay": 3 }, "v": "SeedPayClient 0.3" } ``` Capability Detection [#capability-detection] After both extended handshakes are exchanged, each peer: 1. Checks whether the remote `m` contains `"seedpay"` 2. Records the remote extension ID for `"seedpay"` 3. Parses the remote `seedpay` object (if present) to learn the counterparty's wallet and pricing From the Leecher's perspective, this answers: | Question | Answer | | ------------------------------- | ------------------------------------------------------------------ | | Does this peer support SeedPay? | Yes, if `"seedpay"` is present in remote `m` map | | What are the payment terms? | `seedpay.price_per_mb`, `seedpay.wallet`, `seedpay.min_prepayment` | Based on this, the Leecher classifies the peer as: * **Free-only** (no `seedpay` entry) — standard BitTorrent behavior * **Paid seeder** (SeedPay with pricing) — can open a payment channel If either side's extended handshake does not include `"seedpay"` in `m`, the connection continues as a normal BitTorrent session without payments. Only after handshake completes does the protocol move to the [Payment Channel Setup](/docs/core-protocol/payment-channels). --- # Protocol Overview import { Cards, Card } from 'fumadocs-ui/components/card'; import { Steps, Step } from 'fumadocs-ui/components/steps'; The SeedPay payment flow consists of four sequential phases: Handshake [#handshake] Peers exchange BEP 10 extended handshakes to advertise SeedPay support and payment terms (wallet, price, chain). Non-SeedPay clients are unaffected. Payment Channel Setup [#payment-channel-setup] The Leecher and Seeder perform an ECDH key exchange to derive an ephemeral Session UUID. The Leecher then opens a unidirectional payment channel by depositing USDC into an on-chain escrow. Verification [#verification] The Seeder independently verifies the payment channel on-chain — checking the deposit amount, session binding, and freshness. Only after verification does the Seeder unchoke the Leecher. Data Transfer [#data-transfer] Standard BitTorrent piece requests proceed. The Leecher sends signed off-chain payment checks as data is downloaded. The Seeder tracks cumulative cost and pauses if payment checks fall behind. Key Properties [#key-properties] | Property | How | | --------------------- | ---------------------------------------------------------- | | **Micropayments** | As low as $0.0001/MB via streaming payment checks | | **Low on-chain cost** | Only 2 transactions per session (open + close channel) | | **Privacy** | ECDH session keys — no peer\_id or IP on-chain | | **Fair exchange** | Seeder only serves data when payment checks are sufficient | | **Forward secrecy** | Ephemeral keys deleted after session | Deep Dive [#deep-dive] --- # Payment Channels import { Callout } from 'fumadocs-ui/components/callout'; import { Steps, Step } from 'fumadocs-ui/components/steps'; Once the [handshake](/docs/core-protocol/handshake) is complete and the Leecher has discovered a paid Seeder, the next step is to establish a cryptographically-bound payment channel. Payment channels enable **streaming micropayments**: the Leecher deposits funds into an escrow account, then signs off-chain payment checks as data is downloaded. The Seeder can submit the final check to claim funds, or the Leecher can close the channel after a timeout period. Ephemeral Key Exchange (ECDH) [#ephemeral-key-exchange-ecdh] Before creating the payment transaction, both peers establish a shared secret using Elliptic Curve Diffie-Hellman (ECDH). This happens inside the existing MSE (Message Stream Encryption) tunnel. Generate Ephemeral Keypairs [#generate-ephemeral-keypairs] Both parties independently generate fresh Curve25519 keypairs: ``` secret_key = random_32_bytes() public_key = secret_key × G ``` Where `G` is the standard generator point on Curve25519. Exchange Public Keys [#exchange-public-keys] Peers exchange ephemeral public keys via a SeedPay extension message over the encrypted MSE tunnel: ```json { "type": "ecdh_init", "ephemeral_pk": "<32-byte-public-key-hex>" } ``` Compute Shared Secret [#compute-shared-secret] Both parties arrive at the same shared secret: ``` Leecher: shared_secret = leecher_secret_key × seeder_public_key Seeder: shared_secret = seeder_secret_key × leecher_public_key ``` Derive Session UUID [#derive-session-uuid] ``` Session_UUID = HKDF-Expand( key: shared_secret, info: "seedpay-v1-session", length: 32 bytes ) ``` The `Session_UUID` is now known only to these two peers and cryptographically binds this payment to this specific TCP connection. Security Properties [#security-properties] | Property | Guarantee | | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | **Forward Secrecy** | Ephemeral keys are deleted after the session — past sessions cannot be decrypted even if wallets are compromised | | **MITM Resistance** | An attacker cannot forge the Session\_UUID without knowing one of the private keys | | **Unlinkability** | Blockchain observers see only `Hash(Session_UUID)` — they cannot reverse it or link it to peer activity | | **Replay Protection** | Each session has a unique UUID; old payment proofs cannot be reused | Why "seedpay-v1-session"? [#why-seedpay-v1-session] The context string provides **domain separation** (distinct from other derived keys), **version compatibility** (future versions use `"seedpay-v2-session"`), and **protocol isolation** (prevents key reuse across protocols). Pricing and Channel Setup [#pricing-and-channel-setup] From the extended handshake, the Leecher learns the Seeder's terms: * `wallet`: on-chain wallet address * `price_per_mb`: quoted price in USDC per MB * `min_prepayment`: minimum amount to open a channel * `chain`: settlement chain (e.g. `"solana"`) The Leecher may also apply local policy such as maximum price per MB, maximum total spend, or minimum acceptable timeout. If terms are acceptable, the Leecher computes an initial deposit large enough to cover 50–200 MB of data (but at least `min_prepayment`). Opening a Payment Channel [#opening-a-payment-channel] The Leecher opens a unidirectional payment channel by depositing funds into an escrow account controlled by a smart contract. Channel ID [#channel-id] ``` channel_id = SHA-256( leecher_wallet_address || seeder_wallet_address || timestamp || nonce ) ``` The `channel_id` is deterministically derivable from on-chain data so the smart contract can create and lookup channels without requiring the Session\_UUID. Channel State [#channel-state] ``` channel_state = { leecher: seeder: escrow: deposited: channel_id: created_at: timeout: last_nonce: 0 status: "Open" } ``` Privacy-Preserving Memo [#privacy-preserving-memo] The channel opening transaction includes a memo with only the opaque session identifier: ```json { "protocol": "seedpay", "version": "1.0", "session_hash": "a3f5c8d9e2b1...", "nonce": 1702700000000 } ``` Where `session_hash` = `hex(SHA-256(Session_UUID))`. No `peer_id` or IP address appears on-chain. Channel Opening Notification [#channel-opening-notification] After the transaction is confirmed, the Leecher notifies the Seeder: ```json { "type": "channel_opened", "tx_signature": "", "channel_id": "", "amount": 0.01, "timestamp": 1702700000000 } ``` The Seeder MUST NOT rely on the `amount` field or any client-provided data. All validation is done against on-chain state. The Seeder proceeds to [Verification](/docs/core-protocol/verification) before serving any data. Streaming Off-Chain Payments [#streaming-off-chain-payments] Once the channel is [verified](/docs/core-protocol/verification), the Leecher streams micropayments by signing off-chain payment checks. Payment Check Structure [#payment-check-structure] ```json { "channel_id": "", "amount": 0.005, "nonce": 1, "signature": "" } ``` * `amount`: **cumulative** amount authorized (not per-check) * `nonce`: monotonically increasing sequence number * `signature`: Ed25519 signature over `channel_id || amount || nonce` Signing Payment Checks [#signing-payment-checks] The signature MUST be computed over a hash of the message to ensure consistent length and prevent length extension attacks: ``` // Structured binary encoding (recommended) payment_check_data = { channel_id: [u8; 32], amount: u64, nonce: u64 } message = serialize(payment_check_data) message_hash = SHA-256(message) signature = ed25519_sign(leecher_private_key, message_hash) ``` Recommended Frequency [#recommended-frequency] | Download Size | Frequency | | ------------------ | ------------------------- | | Small (\<100MB) | Every 10MB or 40 pieces | | Medium (100MB–1GB) | Every 50MB or 200 pieces | | Large (>1GB) | Every 100MB or 400 pieces | Leechers SHOULD send checks proactively rather than waiting for `payment_check_required` messages. Payment Check Message [#payment-check-message] ```json { "type": "payment_check", "channel_id": "", "amount": 0.005, "nonce": 1, "signature": "" } ``` Closing a Payment Channel [#closing-a-payment-channel] Cooperative Close (Normal) [#cooperative-close-normal] The Seeder submits the highest valid payment check to the blockchain: ``` close_channel( channel_id: amount: nonce: signature: ) ``` The smart contract transfers `amount` to the Seeder, refunds the remainder to the Leecher, and marks the channel as closed. Timeout Close (Force-Close) [#timeout-close-force-close] If the Seeder disappears, the Leecher can force-close after the timeout period: ``` timeout_close(channel_id: ) ``` The smart contract refunds the entire deposit to the Leecher. Timeout Periods [#timeout-periods] | Scenario | Timeout | | --------------------------- | ------------------------- | | Quick downloads (\< 1 hour) | 3,600 seconds (1 hour) | | Standard downloads | 86,400 seconds (24 hours) | | Long-running channels | 604,800 seconds (7 days) | The timeout MUST be at least 3,600 seconds to allow for normal session completion. Channel Closing Notification [#channel-closing-notification] ```json { "type": "channel_closed", "channel_id": "", "tx_signature": "", "final_amount": 0.005, "reason": "cooperative" } ``` --- # Verification Phase import { Callout } from 'fumadocs-ui/components/callout'; In the verification phase, the Seeder independently checks the on-chain payment channel before opening a paid session. The Seeder treats the **blockchain as the source of truth** and MUST NOT rely solely on the `channel_opened` message contents. On-Chain Channel Lookup [#on-chain-channel-lookup] Upon receiving a `channel_opened` message, the Seeder performs a read-only lookup using the provided `tx_signature`. The Seeder MUST: 1. Fetch the transaction by `tx_signature` from a trusted RPC endpoint 2. Reject if the transaction cannot be found or hasn't reached at least **confirmed** status 3. Parse the transaction to locate: * The channel creation instruction * The escrow account holding deposited funds * The deposited amount * The channel state (leecher, seeder, timeout, etc.) * Any memo instruction attached to the transaction If any of these steps fail, the Seeder MUST treat the channel as invalid. Validation Rules [#validation-rules] A payment channel is considered **valid** only if ALL of the following checks pass: 1. Channel State Check [#1-channel-state-check] * The channel MUST exist on-chain and be in an `"Open"` state * The Seeder's wallet address in the channel state MUST equal the Seeder's configured wallet 2. Deposit Amount Check [#2-deposit-amount-check] * The deposited amount MUST be ≥ the Seeder's `min_prepayment` * The Seeder MAY also enforce a maximum deposit policy 3. Token Check [#3-token-check] * The escrow MUST hold the expected token (e.g. USDC) on the expected chain 4. Session Binding Check (Privacy-Preserving) [#4-session-binding-check-privacy-preserving] The memo MUST match the SeedPay format: ```json { "protocol": "seedpay", "version": "1.0", "session_hash": "", "nonce": 1702700000000 } ``` The Seeder computes `expected_hash = SHA-256(Session_UUID)` using the Session\_UUID derived during [ECDH key exchange](/docs/core-protocol/payment-channels#ephemeral-key-exchange-ecdh). The `session_hash` in the memo MUST equal `expected_hash`. This proves the channel is bound to **this specific connection** without revealing peer\_id or IP information. 5. Freshness / Replay Protection [#5-freshness--replay-protection] * The channel opening MUST be recent: `nonce` or block time within 5–10 minutes * The Seeder MUST maintain a set of already-used channel identifiers and reject duplicates 6. Error-Free Execution [#6-error-free-execution] * The transaction metadata MUST indicate success — any failed or reverted transaction is invalid Seeder Response [#seeder-response] On Success [#on-success] The Seeder: 1. Creates a **payment session** with initial state: | Field | Value | | ------------------- | --------------------------- | | `channel_id` | Verified channel identifier | | `channel_deposit` | Verified deposited amount | | `last_check_nonce` | 0 | | `last_check_amount` | 0 | | `bytes_downloaded` | 0 | | `price_per_mb` | Value from handshake | | `channel_timeout` | Timeout from channel state | 2. Sends a `channel_confirmed` message: ```json { "type": "channel_confirmed", "confirmed": true, "channel_id": "", "deposit": 0.01, "price_per_mb": 0.0001, "timeout": 1702703600000 } ``` 3. **Unchokes** the Leecher on the BitTorrent wire, allowing piece requests. On Failure [#on-failure] The Seeder sends a `channel_rejected` message and keeps the Leecher choked: ```json { "type": "channel_rejected", "confirmed": false, "reason": "session_mismatch" } ``` Possible rejection reasons: | Reason | Description | | ----------------------- | -------------------------------------------- | | `tx_not_found` | Transaction not found on-chain | | `tx_failed` | Transaction failed or reverted | | `wrong_seeder` | Seeder wallet doesn't match | | `insufficient_deposit` | Below `min_prepayment` | | `session_mismatch` | Session hash doesn't match ECDH-derived UUID | | `replayed_channel` | Channel ID already used | | `expired` | Channel opening too old | | `invalid_channel_state` | Channel not in Open state | The Seeder MAY allow the Leecher to retry with a new channel opening, or MAY close the connection according to local policy. Once verified, the protocol transitions to [Data Transfer](/docs/core-protocol/data-transfer). --- # Design Principles Principles [#principles] SeedPay is designed around five core principles: 1. **Backward compatible with BitTorrent** — SeedPay extends the existing wire protocol via BEP 10 (Extension Protocol). Non-SeedPay clients continue to work without modification. 2. **Opt-in** — No forced payments. Users choose whether to participate in the paid tier or use free BitTorrent as usual. 3. **Blockchain-agnostic** — Solana is the initial target chain, but the protocol is designed to be extensible to Ethereum, L2s, and other chains. 4. **Minimal trust required** — Payment verification is done on-chain. Seeders verify payments independently via blockchain state — they never trust client-reported data. 5. **Privacy-preserving** — On-chain payments cannot be linked to download activity. Ephemeral session keys (ECDH-based) replace raw PeerIDs in on-chain memos. Participation Model [#participation-model] Users can participate in both directions: * **Earn**: Seed files and receive payments from leechers * **Spend**: Pay seeders to download files quickly This creates a **circular economy**: users earn USDC by seeding, then spend it on downloads, all within the same payment system. ``` User A: Seeds 10GB → Earns 0.1 USDC User A: Downloads 5GB → Spends 0.05 USDC Result: User A has 0.05 USDC remaining (net earner) ``` Getting Started as a User [#getting-started-as-a-user] Users can enter the economy in multiple ways: 1. **Add funds** (buy USDC) for immediate download access 2. **Seed popular content** to earn USDC from other users 3. **Offset costs** by seeding and earning while downloading 4. **Become a net earner** by seeding more than downloading Protocol Version [#protocol-version] This documentation covers **SeedPay v0.3**, which introduces: * Ephemeral Session Keys (ECDH-based) for privacy * Unidirectional payment channels with streaming micropayments * Simplified protocol focused on crypto-native users with direct payments * Ratio credits system dropped in favor of simpler implementation --- # What is SeedPay? import { Callout } from 'fumadocs-ui/components/callout'; import { Cards, Card } from 'fumadocs-ui/components/card'; This specification is **v0.3 draft**. This is research software — do not use in production. SeedPay is an open payment protocol that enables BitTorrent seeders to earn cryptocurrency for sharing files, while leechers pay seeders directly with stablecoins (e.g. USDC) for faster downloads and guaranteed availability. By extending the BitTorrent Wire Protocol with payment handshakes and blockchain-verified payment channels, SeedPay solves the **free-rider problem** without requiring centralized infrastructure or breaking compatibility with existing clients. The Problem [#the-problem] BitTorrent's success depends on users seeding (uploading) files after downloading them. However, rational actors have no incentive to continue seeding once their download completes — leading to the "free-rider problem." * Popular torrents thrive (many seeders) * Long-tail content dies (no seeders after initial interest) * Download speeds degrade as seeder/leecher ratio drops * The network relies on altruism, which doesn't scale The Solution [#the-solution] SeedPay provides **direct economic incentives** for seeding through micropayments: * **Micropayments**: Leechers pay seeders in stablecoins (e.g. USDC) — typical pricing: $0.0001–$0.001/MB * **Payment Channels**: Streaming micropayments with minimal on-chain costs * **Privacy**: Ephemeral session keys ensure blockchain observers cannot link wallet addresses to download activity * **Backward Compatible**: Standard BitTorrent continues to work — payments are opt-in Who is SeedPay For? [#who-is-seedpay-for] **Target users:** Crypto-native users who value speed, availability, and supporting content creators. Non-crypto users can continue using standard BitTorrent for free. Roles [#roles] | Role | Description | | ----------- | ------------------------------------------------------------- | | **Leecher** | Downloads files, pays seeders with cryptocurrency (e.g. USDC) | | **Seeder** | Uploads files, earns cryptocurrency from leechers | Participation Tiers [#participation-tiers] * **Free Tier**: Standard BitTorrent continues to work (free seeders, tit-for-tat) * **Paid Tier**: SeedPay-enabled clients can pay for faster speeds and guaranteed availability Why Not Existing Solutions? [#why-not-existing-solutions] | Solution | Limitation | | -------------------------- | ------------------------------------------------------------- | | **Tit-for-tat** | Only works during active sessions; no post-download incentive | | **Private trackers** | Centralized, invite-only, high barrier to entry | | **BitTorrent Token (BTT)** | Centralized ledger, proprietary, limited adoption | Next Steps [#next-steps] --- # Blockchain Integration import { Callout } from 'fumadocs-ui/components/callout'; A working Solana implementation of these requirements exists at [seedpay-solana](https://github.com/seedpay-protocol/seedpay-solana). See the [Solana PoC](/docs/implementation/solana-poc) page for details and architectural deviations from this spec. Smart Contract Requirements [#smart-contract-requirements] The payment channel MUST be implemented as a smart contract on the target blockchain. The contract MUST provide three functions: open_channel [#open_channel] Creates a new payment channel. * **Parameters:** `leecher`, `seeder`, `deposit_amount`, `timeout_period`, `channel_id`, `memo` * **Actions:** * Transfer tokens from Leecher to escrow account * Create channel state record * Store channel metadata * **Returns:** Transaction signature close_channel [#close_channel] Closes channel with the final payment check (cooperative close). * **Parameters:** `channel_id`, `amount`, `nonce`, `signature` * **Actions:** * Verify payment check signature * Verify nonce exceeds `last_nonce` * Transfer `amount` to Seeder * Refund `deposited - amount` to Leecher * Mark channel as closed * **Returns:** Transaction signature timeout_close [#timeout_close] Closes channel after timeout (force-close by Leecher). * **Parameters:** `channel_id` * **Actions:** * Verify `current_time` exceeds `channel.timeout` * Refund entire deposit to Leecher * Mark channel as timed out * **Returns:** Transaction signature Account / State Structure [#account--state-structure] The contract MUST use deterministic account addressing (e.g. Program Derived Addresses on Solana, CREATE2 on Ethereum) to ensure channel accounts can be computed from parameters. Transaction Construction [#transaction-construction] ``` transaction = { instructions: [ token_transfer(leecher -> escrow, amount), create_channel_state( leecher: leecher_wallet, seeder: seeder_wallet, escrow: escrow_account, deposited: amount, channel_id: channel_id, created_at: current_timestamp, timeout: current_timestamp + timeout_period, last_nonce: 0, status: "Open" ), attach_memo({ protocol: "seedpay", version: "1.0", session_hash: session_hash, nonce: nonce }) ], signers: [leecher_private_key] } ``` Token Program Requirements [#token-program-requirements] The blockchain MUST support: * Token transfers (fungible tokens, e.g. USDC) * Escrow accounts controlled by smart contracts * Memo/note attachment to transactions | Chain | Token Standard | | ------------ | ---------------------------------- | | Solana | SPL Token program | | Ethereum/EVM | ERC-20 tokens | | Other chains | Equivalent fungible token standard | RPC Provider Requirements [#rpc-provider-requirements] For production Seeders, use dedicated RPC providers — not public free-tier endpoints. Minimum Requirements [#minimum-requirements] | Metric | Requirement | | ----------------- | ----------------------------------- | | Read operations | ≥ 10 requests/second | | Write operations | ≥ 5 transactions/second | | Confirmation time | \< 5 seconds for "confirmed" status | RPC Operations [#rpc-operations] | Operation | RPC Calls | When | | ---------------------------- | --------- | ------------------------------ | | Channel opening verification | 1 | Per channel | | Channel closing | 1 | Per close | | Payment check validation | 0 | Off-chain during data transfer | Implement retry logic with exponential backoff and cache channel state to reduce RPC dependency. Confirmation Requirements [#confirmation-requirements] | Event | Minimum | Recommended | | --------------- | --------- | ----------- | | Channel opening | Confirmed | Finalized | | Channel closing | Confirmed | Finalized | * **Confirmed**: Transaction included in block, may be reverted * **Finalized**: Transaction cannot be reverted (chain-specific finality) --- # Cryptographic Requirements Elliptic Curve — Curve25519 (x25519) [#elliptic-curve--curve25519-x25519] SeedPay uses Curve25519 for ECDH key agreement: * Well-established, audited implementations available * Fast key generation and ECDH computation * Widely supported: libsodium, NaCl, OpenSSL All ephemeral keypairs for the [ECDH key exchange](/docs/core-protocol/payment-channels#ephemeral-key-exchange-ecdh) MUST use Curve25519. Key Derivation — HKDF-SHA256 [#key-derivation--hkdf-sha256] The Session UUID is derived from the ECDH shared secret using HKDF: ``` Session_UUID = HKDF-Expand( key: shared_secret, info: "seedpay-v1-session", length: 32 bytes ) ``` | Parameter | Value | | -------------- | ---------------------- | | Hash function | SHA-256 | | Context string | `"seedpay-v1-session"` | | Output length | 32 bytes | The context string provides domain separation, version compatibility, and protocol isolation. Hashing — SHA-256 [#hashing--sha-256] SHA-256 is used for: * Computing `session_hash` from Session\_UUID: `session_hash = hex(SHA-256(Session_UUID))` * Hashing payment check messages before signing * Computing `channel_id` from wallet addresses, timestamp, and nonce Payment Check Signatures — Ed25519 [#payment-check-signatures--ed25519] Payment checks are signed using Ed25519: ``` // Structured binary encoding (recommended) payment_check_data = { channel_id: [u8; 32], amount: u64, nonce: u64 } message = serialize(payment_check_data) message_hash = SHA-256(message) signature = ed25519_sign(leecher_private_key, message_hash) ``` The signature MUST be computed over a **hash** of the message (not raw message) to ensure consistent signature length and prevent length extension attacks. An alternative delimited string encoding is also acceptable: ``` message = channel_id + ":" + amount.to_string() + ":" + nonce.to_string() message_hash = SHA-256(message) signature = ed25519_sign(leecher_private_key, message_hash) ``` Random Number Generation [#random-number-generation] * MUST use cryptographically secure RNG (e.g. `/dev/urandom`, `crypto.getRandomValues()`) * Ephemeral secret keys MUST be truly random (no deterministic derivation) * Nonces for channel opening SHOULD use timestamp-based values for freshness --- # Error Handling import { Callout } from 'fumadocs-ui/components/callout'; Connection Drops Mid-Session [#connection-drops-mid-session] Leecher Behavior [#leecher-behavior] * **Before sending final payment check:** * Reconnect and continue with same channel (if still open) * Or force-close channel after timeout to recover unspent deposit * **After sending payment check:** * Seeder may have already received check and can close channel * Monitor channel state to verify final settlement Seeder Behavior [#seeder-behavior] * **Before receiving final payment check:** * Attempt to close channel with highest received check * If no valid check received, Leecher will timeout close * **After receiving payment check:** * Close channel immediately with received check * Do not wait for reconnection RPC Failures During Channel Verification [#rpc-failures-during-channel-verification] Retry Strategy [#retry-strategy] * Exponential backoff: 1s, 2s, 4s, 8s, 16s * Maximum retries: 5 attempts * After max retries: Reject channel, keep Leecher choked Fallback [#fallback] * Maintain list of trusted RPC providers * Switch to backup RPC provider if primary fails * Cache recent channel verifications to reduce RPC dependency Invalid Payment Check Handling [#invalid-payment-check-handling] | Failure | Action | | ---------------------- | ---------------------------------------------- | | Invalid signature | Reject check, send error, keep choked | | Stale nonce | Reject check, send expected nonce, keep choked | | Amount exceeds deposit | Reject check, send error, keep choked | | Amount not increasing | Reject check, send error, keep choked | Error message format: ```json { "type": "payment_check_rejected", "channel_id": "", "reason": "stale_nonce", "expected_nonce": 5, "received_nonce": 3 } ``` Channel Timeout Edge Cases [#channel-timeout-edge-cases] When the channel timeout approaches while download is still in progress: Leecher [#leecher] * Monitor timeout (e.g. check every 10 minutes) * If \< 1 hour remaining and download incomplete: * **Option 1:** Open new channel and continue * **Option 2:** Extend timeout (if contract supports it) * **Option 3:** Force-close and recover funds Seeder [#seeder] * Monitor timeout for active sessions * If approaching: send warning, request new channel * If no response: close channel with highest check ```json { "type": "channel_timeout_warning", "channel_id": "", "timeout_in_seconds": 3600, "recommended_action": "open_new_channel" } ``` Transaction Failures [#transaction-failures] Channel Opening Failure [#channel-opening-failure] * Retry with new nonce (avoid replay) * Check network conditions, increase gas/fee if applicable * Maximum retries: 3 attempts Channel Closing Failure [#channel-closing-failure] * **Cooperative close:** Retry with same payment check * **Timeout close:** Retry with same channel\_id * If repeated failures: wait and retry later (likely network congestion) Recovery Procedures [#recovery-procedures] Leecher Recovery [#leecher-recovery] On startup, scan for open channels associated with the wallet. For each open channel: 1. Check if timeout has passed 2. If timed out → Force-close to recover funds 3. If not timed out → Check if download can continue or should be abandoned Seeder Recovery [#seeder-recovery] On startup, scan for open channels where this wallet is the seeder. For each open channel: 1. Check if valid payment checks were received 2. If valid checks exist → Close channel to claim funds 3. If no valid checks → Channel will timeout, no action needed --- # Implementation Overview import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; SeedPay V0.3 **requires** MSE (Message Stream Encryption / BEP 52) for ECDH key exchange. Fallback to unencrypted mode is NOT supported for payment sessions. This section covers the practical requirements for implementing a SeedPay-compatible client, including cryptographic primitives, blockchain integration, and error handling. Key Requirements [#key-requirements] | Area | Requirement | | ------------------ | ------------------------------------------------------- | | **Encryption** | MSE (BEP 52) required for all payment sessions | | **Elliptic Curve** | Curve25519 (x25519) for ECDH key exchange | | **Key Derivation** | HKDF-SHA256 with context `"seedpay-v1-session"` | | **Hashing** | SHA-256 for session\_hash and payment check signatures | | **Blockchain** | Smart contract with open/close/timeout\_close functions | | **Token** | SPL Token (Solana), ERC-20 (Ethereum/EVM) | Performance Overhead [#performance-overhead] SeedPay adds minimal overhead to the BitTorrent protocol: | Operation | Cost | | ------------------------ | -------------------------------- | | ECDH key generation | \~0.1ms | | Key exchange | 2 round-trips (within handshake) | | HKDF derivation | \<0.01ms | | Payment check validation | Off-chain (no RPC calls) | | Channel open/close | 1 RPC call each | Deep Dive [#deep-dive] --- # Solana Proof of Concept import { Callout } from 'fumadocs-ui/components/callout'; import { Cards, Card } from 'fumadocs-ui/components/card'; This is a **proof-of-concept** implementation. It is not audited and should not be used in production. The [seedpay-solana](https://github.com/seedpay-protocol/seedpay-solana) repository contains a working Solana implementation of the SeedPay payment channel protocol, built with **Anchor 0.32** and a companion **TypeScript SDK**. | | | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **Program ID** | [`7DwPMoGzTjRUroE47VPEEJn4FBSypAA5dbeMn3ocVdsS`](https://explorer.solana.com/address/7DwPMoGzTjRUroE47VPEEJn4FBSypAA5dbeMn3ocVdsS?cluster=devnet) | | **Network** | Solana Devnet | | **Status** | Deployed, executable | Repository Structure [#repository-structure] ``` seedpay-solana/ ├── programs/seedpay/ # Anchor smart contract (Rust, ~614 lines) ├── packages/sdk/ # TypeScript SDK (ECDH, payment checks, client) ├── packages/demo/ # End-to-end demo application ├── tests/ # Integration tests (anchor-bankrun) └── docs/ # Architecture docs and deviation log ``` Smart Contract [#smart-contract] The on-chain program implements three instructions that manage the full lifecycle of a payment channel: open_channel [#open_channel] Creates a new payment channel with USDC escrow. * Leecher deposits tokens into a PDA-controlled escrow account * Channel state is stored in a PDA derived from `[b"channel", leecher, seeder, channel_id]` * Validates deposit amount > 0 and timeout between 1 hour and 7 days close_channel [#close_channel] Cooperative close — Seeder claims earned funds. * Verifies the Leecher's Ed25519 signature over `channel_id || amount || nonce` using Solana's native Ed25519 program * Transfers claimed amount to Seeder, refunds remainder to Leecher * Enforces monotonically increasing nonces for replay protection timeout_close [#timeout_close] Force-close — Leecher recovers funds after timeout. * Requires `current_time > channel.timeout` * Refunds entire deposit to Leecher * Only callable by the original depositor Channel State [#channel-state] ```rust pub struct ChannelState { pub leecher: Pubkey, pub seeder: Pubkey, pub deposited: u64, pub channel_id: [u8; 32], pub created_at: i64, pub timeout: i64, pub last_nonce: u64, pub status: ChannelStatus, // Open | Closed | TimedOut pub bump: u8, } ``` TypeScript SDK [#typescript-sdk] The SDK provides two modules for client-side cryptography: ECDH Module [#ecdh-module] Handles ephemeral session key exchange between peers: * X25519 key pair generation * Shared secret computation * Session UUID derivation via HKDF-SHA256 with context `"seedpay-v1-session"` * Channel ID derivation: `SHA-256(Session_UUID)` Payment Check Module [#payment-check-module] Handles off-chain micropayment authorization: * Constructs payment check messages: `channel_id (32B) || amount (8B LE) || nonce (8B LE)` * Signs with Ed25519 (tweetnacl) * Verifies signatures locally before submission Architectural Deviations from Spec [#architectural-deviations-from-spec] During PoC development, three design decisions diverged from the v0.3 protocol specification: 1. Escrow Address — Derived Instead of Stored [#1-escrow-address--derived-instead-of-stored] The spec originally stored the escrow address in channel state. The PoC derives it from PDA seeds instead. * Saves 32 bytes of rent per channel * Anchor validates the derived address automatically via account constraints * No information lost — the escrow PDA is deterministic from the channel state address 2. PDA Seeds — channel_id Replaces Nonce [#2-pda-seeds--channel_id-replaces-nonce] Original seeds: `["seedpay", "channel", leecher, seeder, nonce(u64)]` PoC seeds: `[b"channel", leecher, seeder, channel_id([u8; 32])]` * Dropped `"seedpay"` prefix — the program ID already namespaces * Replaced 64-bit nonce with 256-bit `channel_id` — no global counter needed * Collision probability is negligible with 256 bits of entropy 3. channel_id = Session Hash (Memo Program Dropped) [#3-channel_id--session-hash-memo-program-dropped] This is the most significant change. The spec used two separate concepts: * `channel_id` derived from `SHA-256(leecher || seeder || timestamp || nonce)` — no connection to ECDH * Session binding via `session_hash = SHA-256(Session_UUID)` in a Memo instruction The PoC unifies them: **`channel_id = SHA-256(Session_UUID)`** — the channel identifier IS the session binding. * Removes dependency on Solana's Memo program entirely * Simpler program and client logic * **Chain-agnostic**: memo is Solana-specific; embedding session binding in account derivation works on any chain (Ethereum CREATE2, Sui object IDs, etc.) * Same security: SHA-256 preimage resistance still prevents linking session\_hash to download activity * Same verification: Seeder computes `SHA-256(Session_UUID)` locally and uses it to derive the PDA — if it matches, the session is bound These deviations are candidates for adoption into the main protocol specification. See [CONTRIBUTING.md](https://github.com/seedpay-protocol/seedpay/blob/main/CONTRIBUTING.md) to provide feedback. Tests [#tests] Integration tests use `anchor-bankrun` for local execution without requiring a Solana validator: * **Happy path**: ECDH key exchange → open channel → 3 progressive payment checks → close with highest nonce → verify balances * **Timeout path**: Open channel → warp time past timeout → timeout close → verify full refund * **Payment check crypto**: Signing, verification, and replay protection * **Balance conservation**: Verify total tokens are conserved across open/close operations Running Locally [#running-locally] ```bash git clone https://github.com/seedpay-protocol/seedpay-solana.git cd seedpay-solana pnpm install anchor build anchor test ``` Tech Stack [#tech-stack] | Layer | Technology | | ---------------------- | ----------------------------- | | Smart Contract | Rust + Anchor 0.32 | | Token Standard | SPL Token (USDC) | | Signature Verification | Solana Ed25519 native program | | SDK | TypeScript | | ECDH | @noble/curves (X25519) | | Key Derivation | @noble/hashes (HKDF-SHA256) | | Payment Signatures | tweetnacl (Ed25519) | | Testing | anchor-bankrun | --- # Future Extensions import { Callout } from 'fumadocs-ui/components/callout'; Multi-Chain Support [#multi-chain-support] The ECDH-based session binding is chain-agnostic and can be implemented on: * **Ethereum** — Use EIP-3009 for meta-transactions * **Base, Arbitrum, Optimism** — L2s with low fees * Other EVM-compatible chains Multi-chain support is planned for a future version. V1 targets Solana as the initial chain. Advanced Payment Models [#advanced-payment-models] Current: Unidirectional Channels (V1) [#current-unidirectional-channels-v1] SeedPay V1 uses unidirectional channels (Leecher → Seeder only) because: * Seeders earn, Leechers pay (one direction of value flow) * Simpler state management (no need for both parties to sign updates) * Lower risk (only Leecher's deposit is at risk) * Sufficient for the core use case of paid file downloads Future: Bidirectional Payment Channels [#future-bidirectional-payment-channels] Bidirectional channels would allow both parties to send payments, enabling: * Refunds or disputes * Two-way value exchange * More complex payment flows These require more complex state management with both parties signing updates. Future: Probabilistic Payments [#future-probabilistic-payments] Lottery-style micropayments for extreme scalability: * Trade certainty for reduced transaction count * Useful for very high-frequency, low-value transactions Future: Multi-Hop Payment Routing [#future-multi-hop-payment-routing] Allow payments through intermediate nodes (similar to Lightning Network): * Enables payment channel networks * Requires routing protocol and liquidity management Ratio Credits (V2 Consideration) [#ratio-credits-v2-consideration] Ratio credits are **deferred to V2**. V1 focuses on direct payments via payment channels. V2 MAY add ratio credits if user research indicates: * Strong demand for non-monetary credits * Need for better privacy (credits vs direct payments) * Anti-Sybil mechanisms that credits can provide * Demand from non-crypto users Design Challenges [#design-challenges] * Preventing Sybil attacks (users farming credits with fake seeders) * Ensuring credits have real value (tied to actual bandwidth provision) * Cross-torrent credit portability * On-chain vs off-chain credit ledger * Credit verification mechanisms Reputation Systems [#reputation-systems] Planned reputation features for future versions: * Seeder reputation based on successful channel closes * Leecher trust scores * Rate limiting for channel creation per wallet * Proof-of-bandwidth mechanisms --- # Message Types All SeedPay messages are sent over the BEP 10 extension channel using the `"seedpay"` extension ID negotiated during the [handshake](/docs/core-protocol/handshake). Messages are JSON-encoded. ECDH Key Exchange [#ecdh-key-exchange] ecdh_init [#ecdh_init] Sent by both peers during the [ECDH key exchange](/docs/core-protocol/payment-channels#ephemeral-key-exchange-ecdh). ```json { "type": "ecdh_init", "ephemeral_pk": "<32-byte-public-key-hex>" } ``` | Field | Type | Description | | -------------- | ---------- | ----------------------------- | | `type` | string | Always `"ecdh_init"` | | `ephemeral_pk` | hex string | 32-byte Curve25519 public key | Payment Channel Lifecycle [#payment-channel-lifecycle] channel_opened [#channel_opened] Sent by the Leecher after the channel opening transaction is confirmed on-chain. ```json { "type": "channel_opened", "tx_signature": "", "channel_id": "", "amount": 0.01, "timestamp": 1702700000000 } ``` | Field | Type | Description | | -------------- | ------ | ----------------------------------------------- | | `tx_signature` | string | Blockchain transaction signature | | `channel_id` | string | Channel identifier (reference only) | | `amount` | number | Deposited amount (UI/logging only, not trusted) | | `timestamp` | number | Unix milliseconds when notification was created | channel_confirmed [#channel_confirmed] Sent by the Seeder after successful [on-chain verification](/docs/core-protocol/verification). ```json { "type": "channel_confirmed", "confirmed": true, "channel_id": "", "deposit": 0.01, "price_per_mb": 0.0001, "timeout": 1702703600000 } ``` | Field | Type | Description | | -------------- | ------- | ----------------------------------- | | `confirmed` | boolean | Always `true` | | `channel_id` | string | Verified channel identifier | | `deposit` | number | Verified deposit amount | | `price_per_mb` | number | Agreed price per MB | | `timeout` | number | Channel timeout (Unix milliseconds) | channel_rejected [#channel_rejected] Sent by the Seeder when channel verification fails. ```json { "type": "channel_rejected", "confirmed": false, "reason": "session_mismatch" } ``` | Field | Type | Description | | ----------- | ------- | ---------------------------------- | | `confirmed` | boolean | Always `false` | | `reason` | string | One of the rejection reasons below | **Rejection reasons:** | Reason | Description | | ----------------------- | -------------------------------------------- | | `tx_not_found` | Transaction not found on-chain | | `tx_failed` | Transaction failed or reverted | | `wrong_seeder` | Seeder wallet doesn't match | | `insufficient_deposit` | Below `min_prepayment` | | `session_mismatch` | Session hash doesn't match ECDH-derived UUID | | `replayed_channel` | Channel ID already used | | `expired` | Channel opening too old | | `invalid_channel_state` | Channel not in Open state | channel_closed [#channel_closed] Sent by the closing party after a channel is closed. ```json { "type": "channel_closed", "channel_id": "", "tx_signature": "", "final_amount": 0.005, "reason": "cooperative" } ``` | Field | Type | Description | | -------------- | ------ | ------------------------------ | | `channel_id` | string | Channel identifier | | `tx_signature` | string | Closing transaction signature | | `final_amount` | number | Final settled amount | | `reason` | string | `"cooperative"` or `"timeout"` | Payment Checks [#payment-checks] payment_check [#payment_check] Sent by the Leecher during [data transfer](/docs/core-protocol/data-transfer) to authorize cumulative payment. ```json { "type": "payment_check", "channel_id": "", "amount": 0.005, "nonce": 1, "signature": "" } ``` | Field | Type | Description | | ------------ | ------- | ---------------------------------------- | | `channel_id` | string | Channel identifier | | `amount` | number | Cumulative amount authorized | | `nonce` | integer | Monotonically increasing sequence number | | `signature` | string | Base64-encoded Ed25519 signature | payment_check_required [#payment_check_required] Sent by the Seeder when the Leecher's payment checks fall behind cumulative cost. ```json { "type": "payment_check_required", "required_amount": 0.005, "current_check_amount": 0.003, "estimated_remaining_mb": 20.0 } ``` | Field | Type | Description | | ------------------------ | ------ | ------------------------------ | | `required_amount` | number | Amount needed to continue | | `current_check_amount` | number | Highest check amount received | | `estimated_remaining_mb` | number | Estimated remaining data in MB | payment_check_rejected [#payment_check_rejected] Sent by the Seeder when a payment check fails validation. ```json { "type": "payment_check_rejected", "channel_id": "", "reason": "stale_nonce", "expected_nonce": 5, "received_nonce": 3 } ``` | Field | Type | Description | | ---------------- | ------- | ------------------------------------------------------------------------------------------------ | | `channel_id` | string | Channel identifier | | `reason` | string | `"invalid_signature"`, `"stale_nonce"`, `"amount_exceeds_deposit"`, or `"amount_not_increasing"` | | `expected_nonce` | integer | Expected nonce value (if applicable) | | `received_nonce` | integer | Received nonce value (if applicable) | Timeout Warning [#timeout-warning] channel_timeout_warning [#channel_timeout_warning] Sent by the Seeder when the channel timeout approaches. ```json { "type": "channel_timeout_warning", "channel_id": "", "timeout_in_seconds": 3600, "recommended_action": "open_new_channel" } ``` | Field | Type | Description | | -------------------- | ------- | ------------------------------------------ | | `channel_id` | string | Channel identifier | | `timeout_in_seconds` | integer | Seconds remaining until timeout | | `recommended_action` | string | `"open_new_channel"` or `"extend_timeout"` | --- # References BitTorrent Protocol Specifications [#bittorrent-protocol-specifications] * **BEP 3** — [The BitTorrent Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html) Standard handshake, peer wire protocol, piece exchange * **BEP 10** — [Extension Protocol](https://www.bittorrent.org/beps/bep_0010.html) Extended handshake mechanism used by SeedPay * **BEP 52** — [Message Stream Encryption](https://www.bittorrent.org/beps/bep_0052.html) Required by SeedPay for ECDH key exchange Cryptographic Standards [#cryptographic-standards] * **RFC 7748** — [Elliptic Curves for Security (Curve25519)](https://tools.ietf.org/html/rfc7748) Curve25519 / x25519 used for ECDH key agreement * **RFC 5869** — [HKDF (HMAC-based Key Derivation Function)](https://tools.ietf.org/html/rfc5869) Used to derive Session UUID from shared secret * **NIST FIPS 180-4** — [Secure Hash Standard (SHA-256)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) Used for session\_hash, channel\_id, and payment check signatures Related Work [#related-work] * **Coinbase x402 Protocol** — [github.com/coinbase/x402](https://github.com/coinbase/x402) HTTP-native micropayments protocol * **Lightning Network** — [lightning.network](https://lightning.network/) Bitcoin payment channels and multi-hop routing * **BitTorrent Token (BTT)** — [bittorrent.com/token/btt](https://www.bittorrent.com/token/btt/) Previous attempt at incentivized BitTorrent (centralized, proprietary) Changelog [#changelog] | Version | Date | Changes | | ------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | v0.3 | 2026-01-04 | Dropped ratio credits. Simplified protocol focused on crypto-native users. Direct payments via payment channels only. | | v0.2 | 2025-12-21 | ECDH ephemeral session keys for privacy. Removed peer\_id from on-chain memos. Unidirectional payment channels with streaming micropayments. | | v0.1 | 2025-12-15 | Initial draft with peer\_id-based memo binding (deprecated due to privacy concerns). | --- # Economic Attacks Maximum Loss Per Session [#maximum-loss-per-session] The payment channel design limits maximum loss for both parties: | Party | Maximum Loss | When | Mitigation | | ----------- | -------------------------------- | -------------------- | ------------------------------------ | | **Leecher** | Channel deposit (e.g. 0.01 USDC) | Seeder sends no data | Force-close after timeout to recover | | **Seeder** | $0 | Leecher stops paying | Stop serving data | Griefing Attacks [#griefing-attacks] Leecher Opens Channel But Never Downloads [#leecher-opens-channel-but-never-downloads] 1. Leecher opens payment channel (locks 0.01 USDC) 2. Leecher never requests any pieces 3. Seeder's capital is not at risk, but channel occupies contract state **Mitigations:** * Channels can be force-closed by Leecher after timeout * Seeder can set `min_prepayment` high enough to deter spam * Smart contract can charge a small creation fee Leecher Requests Data But Sends No Payment Checks [#leecher-requests-data-but-sends-no-payment-checks] 1. Leecher opens valid channel 2. Requests pieces but never sends payment checks 3. Seeder serves 1–2 pieces before realizing no payment is coming **Mitigations:** * Seeder requires first payment check before sending ANY data * Or sends 1 piece, then waits for payment check before continuing * **Maximum loss: \~$0.000025** (cost of 1 piece) Seeder Sends Corrupted Data [#seeder-sends-corrupted-data] 1. Seeder sends corrupted piece 2. Leecher detects corruption via BitTorrent hash verification 3. Leecher stops sending payment checks **Result:** Leecher loses payment for the corrupted piece only (\~$0.000025). Built-in BitTorrent hash verification protects the Leecher. Sybil Attacks [#sybil-attacks] Malicious Seeder Creates Many Identities [#malicious-seeder-creates-many-identities] * Attacker creates 100 Seeder wallets * Advertises content but sends garbage data * Each Seeder scams 1 Leecher per day **Analysis:** Cost to Leecher per scam = 1 piece = $0.000025. Attacker gain = $0.0025/day for 100 seeders. **Not economically rational.** Leecher Opens Many Channels, Never Closes [#leecher-opens-many-channels-never-closes] * Leecher opens 1000 channels with different Seeders * Deposits $0.01 each = $10 total locked * Leecher disappears **Impact:** Leecher loses $10 (funds locked until timeout). Seeders lose $0. Channels auto-close after timeout. Max timeout of 7 days limits lock duration. Front-Running Attacks [#front-running-attacks] Seeder Front-Runs Channel Close [#seeder-front-runs-channel-close] 1. Leecher broadcasts `timeout_close()` transaction 2. Seeder sees it in mempool 3. Seeder front-runs with `close_channel()` using an old, low payment check **Analysis:** The Seeder cannot front-run with a lower amount than the Leecher already signed. The contract enforces monotonically increasing amounts. Even if the Seeder front-runs, they can only claim what the Leecher authorized. **Result: Attack ineffective.** The protocol is front-run resistant. Replay Attacks [#replay-attacks] Seeder Reuses Old Payment Check [#seeder-reuses-old-payment-check] 1. Leecher signs check #5 (amount: 0.005) 2. Later, Leecher signs check #10 (amount: 0.01) 3. Seeder tries to close with check #5 (to keep more funds) **Mitigation:** Smart contract tracks `last_nonce` and only accepts checks with a nonce greater than `last_nonce`. Seeder Reuses Check Across Sessions [#seeder-reuses-check-across-sessions] 1. Session A: Leecher signs check for channel A 2. Session B: Seeder tries to use same check for channel B **Mitigation:** Payment check includes `channel_id` in the signed message. Check is only valid for the specific channel. --- # Security Overview import { Cards, Card } from 'fumadocs-ui/components/card'; SeedPay's security model is built on three pillars: **privacy** (unlinkable on-chain payments), **payment verification** (blockchain as source of truth), and **economic attack resistance** (bounded losses, front-run resistance). Threat Model [#threat-model] SeedPay considers the following adversaries: | Adversary | Capabilities | Mitigations | | ------------------------ | ------------------------------------------ | ---------------------------------------------------- | | **Blockchain observer** | Can see all on-chain transactions | ECDH session keys — no peer\_id or IP on-chain | | **Network eavesdropper** | Can observe TCP connections | MSE tunnel encrypts all SeedPay messages | | **Malicious seeder** | May send corrupted data or refuse to serve | BitTorrent hash verification, bounded loss per piece | | **Malicious leecher** | May refuse to pay after receiving data | Seeder tracks payment checks before serving | | **Sybil attacker** | Creates many fake identities | Economic cost of channel opening deters spam | Key Guarantees [#key-guarantees] * **No peer\_id on-chain** — blockchain observers cannot link wallets to swarm activity * **Session unlinkability** — different sessions produce different Session\_UUIDs * **Forward secrecy** — ephemeral keys are deleted after sessions * **Bounded loss** — maximum loss per session is bounded by channel deposit (leecher) or cost of 1 piece (seeder) * **Front-run resistance** — smart contract enforces monotonically increasing amounts Deep Dive [#deep-dive] --- # Payment Verification Security Properties [#security-properties] * Seeders MUST verify payments on-chain (blockchain is the source of truth) * ECDH binding prevents payment proof replay across different connections * Nonce freshness prevents replay of old payments * Transaction signature tracking prevents double-spending Attack Mitigations [#attack-mitigations] Fake Payment Proof [#fake-payment-proof] **Attack:** Leecher sends a fabricated `channel_opened` message with a fake transaction signature. **Mitigation:** The Seeder fetches the transaction independently from the blockchain. It ignores the Leecher-provided `amount` field entirely. All validation is done against on-chain state. Replay Attack [#replay-attack] **Attack:** Leecher tries to reuse a transaction signature from a previous session. **Mitigation:** The Seeder checks nonce freshness (channel opening must be within 5–10 minutes) and maintains a set of consumed transaction signatures. Any previously-used channel is rejected. Man-in-the-Middle Attack [#man-in-the-middle-attack] **Attack:** An attacker intercepts the connection and tries to redirect payments. **Mitigation:** The ECDH key exchange ensures only the two peers with correct ephemeral keys can derive the Session\_UUID. The session hash in the memo binds the payment channel to this specific connection. An attacker cannot forge the `Session_UUID` without knowing one of the private keys. Peer Authentication [#peer-authentication] Payment channels require real cryptocurrency deposits, which makes Sybil attacks economically unfeasible. Each channel opening costs transaction fees, limiting spam. Current V1 Approach [#current-v1-approach] | Mechanism | Protection | | ---------------- | --------------------------------------------------- | | Real deposits | Sybil deterrence — attacker must deposit real funds | | Transaction fees | Spam prevention — each channel costs gas | | Session binding | MITM prevention — ECDH ensures correct counterparty | | Nonce tracking | Replay prevention — each session is unique | Future Considerations [#future-considerations] * Reputation systems for seeders (based on successful channel closes) * Rate limiting for channel creation per wallet * Proof-of-bandwidth mechanisms if needed --- # Privacy Model import { Callout } from 'fumadocs-ui/components/callout'; V0.3 introduces ephemeral session keys to ensure **payment privacy** — blockchain observers cannot link wallet addresses to download activity. Privacy Guarantees [#privacy-guarantees] 1. Unlinkability (Blockchain to Swarm) [#1-unlinkability-blockchain-to-swarm] Blockchain observers see: ``` wallet_A → wallet_B, memo: { session_hash: "0xabc..." } ``` They **cannot** determine: * Which torrent is being downloaded * Which peer\_id is involved * Which IP address is associated The `session_hash` is `SHA-256(Session_UUID)` — preimage resistance of SHA-256 prevents reversing it. 2. Unlinkability (Session to Session) [#2-unlinkability-session-to-session] * Each TCP connection uses **fresh ephemeral keys** * Different sessions produce different Session\_UUIDs * Blockchain observers cannot link multiple payments from the same user across sessions 3. Forward Secrecy [#3-forward-secrecy] * Ephemeral keys are **deleted after the session ends** * Compromising a wallet after the fact cannot decrypt past sessions * Past download history remains private What is NOT Private [#what-is-not-private] Not all metadata is hidden: | Visible To | Information | | ---------------------- | ------------------------------------------------------------- | | Blockchain observers | The fact that `wallet_A` paid `wallet_B` (amounts and timing) | | Swarm participants | Seeder wallet addresses (visible in handshake) | | ISP / network observer | Connection metadata (IP addresses, timing, volume) | Privacy Best Practices [#privacy-best-practices] * Use **Tor or VPN** for IP address privacy * Use **burner wallets** funded via mixers for maximum anonymity * Avoid reusing the same Seeder/Leecher wallet combination if privacy is critical