Skip to content

levinster82/sptools-py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

15 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Silent Payments Discovery & Sweep Tool

Version Python License

A modular, async, event-driven implementation of the BIP-352 Silent Payments specification for discovering and sweeping Bitcoin Silent Payment UTXOs.

TL;DR: Easily scan for and sweep Silent Payment UTXOs.

⚠️ Important: This tool was vibe coded with Claude. Testing has been completed for basic utility but not for every edge case.

It is highly recommended to import the signed transaction into Electrum wallet or Sparrow wallet and carefully review all transaction outputs before broadcasting.

Overview

A Python implementation inspired by and designed to work with sparrowwallet/frigate, a lightweight Electrum-style server optimized for Silent Payments discovery.

This tool provides:

  • Compatible with Frigate/Electrum protocols for efficient UTXO discovery and status checking
  • Full async/await support using asyncio for non-blocking I/O
  • Event-driven architecture with pub/sub pattern for reactive updates
  • Clean separation of concerns across 3 architectural layers (Core, Backend, Frontend)
  • Extensible design for adding new frontends (GUI, Web) and features
  • Comprehensive test suite with 103 unit tests covering BIP-352, BIP-340 (Schnorr), and BIP-341 (Taproot)
  • Modular codebase (~4,550 lines) organized into focused modules

Usage

First Time Setup

Note: Using a virtual environment is strongly recommended but not required. It isolates dependencies and prevents conflicts with other Python projects. If you choose not to use a venv, you can install dependencies directly with pip install -r requirements.txt and skip the activation/deactivation steps.

# 1. Clone the repository (if not already done)
git clone https://github.com/levinster82/sptools-py.git
cd sptools-py

# 2. Create a virtual environment
python3 -m venv venv

# 3. Activate the virtual environment
source venv/bin/activate  # Linux/Mac
# or
venv\Scripts\activate  # Windows

# You should see (venv) at the start of your command prompt

# 4. Install dependencies
pip install -r requirements.txt

# 5. Verify installation
python spspend.py --help

# 6. When finished, deactivate the virtual environment
deactivate

As a Command-Line Tool

# Interactive mode (will prompt for keys)
python spspend.py

# With arguments
python spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY -b 890000

# Export to JSON
python spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY --export utxos.json

# With Electrum server for status checking
python spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY \
    --electrum-server electrum.blockstream.info

Full Command-Line Options

usage: spspend.py [-h] [--host HOST] [--port PORT] [--plain-tcp]
                  [--no-verify-cert] [--timeout TIMEOUT]
                  [--electrum-server ELECTRUM_HOST]
                  [--electrum-port ELECTRUM_PORT]
                  [--scan-key SCAN_PRIVATE_KEY] [--spend-key SPEND_PUBLIC_KEY]
                  [--spend-privkey SPEND_PRIVATE_KEY] [--start START]
                  [--export EXPORT_FILE] [--quiet] [--ignore-spent]
                  [--network {mainnet,testnet,testnet4,signet,regtest}]
                  [--log-level {DEBUG,INFO,WARNING,ERROR}] [--version]

Silent Payments UTXO Discovery Tool - Find and display Silent Payment UTXOs

options:
  -h, --help            show this help message and exit
  --network, -n {mainnet,testnet,testnet4,signet,regtest}
                        Bitcoin network to use (default: mainnet)
  --log-level, -l {DEBUG,INFO,WARNING,ERROR}
                        Logging level (default: INFO)
  --version, -v         show program's version number and exit

connection options:
  --host, -H HOST       Frigate server host (default: 127.0.0.1)
  --port, -p PORT       Frigate server port (default: 57002 for SSL, 57001 for
                        TCP)
  --plain-tcp           Use plain TCP instead of SSL (default: SSL)
  --no-verify-cert      Disable SSL certificate verification (default:
                        enabled)
  --timeout TIMEOUT     Socket timeout in seconds (default: no timeout)

electrum server options (optional):
  --electrum-server ELECTRUM_HOST
                        Electrum server host for UTXO status checking (e.g.,
                        electrum.blockstream.info)
  --electrum-port ELECTRUM_PORT
                        Electrum server port (default: 50002 for SSL, 50001
                        for TCP)

key options:
  --scan-key, -s SCAN_PRIVATE_KEY
                        Scan private key (64 hex characters)
  --spend-key, -S SPEND_PUBLIC_KEY
                        Spend public key (66 hex characters)
  --spend-privkey, -P SPEND_PRIVATE_KEY
                        Spend private key (64 hex characters) - OPTIONAL: If
                        provided, will derive private keys for each UTXO

scanning options:
  --start, -b START     Start block height or timestamp
  --export, -e EXPORT_FILE
                        Export UTXOs to JSON file
  --quiet, -q           Disable progress output
  --ignore-spent        TESTING ONLY: Ignore spent status when offering to
                        sweep UTXOs

Examples:
  # Interactive mode
  spspend.py

  # Discover UTXOs with specific keys
  spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY

  # Start scanning from specific block
  spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY -b 890000

  # Export UTXOs to JSON file
  spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY --export utxos.json

  # Use Electrum server for UTXO status checking
  spspend.py -s SCAN_KEY -S SPEND_PUBLIC_KEY --electrum-server electrum.blockstream.info

As a Python Library

Note: This interface is currently untested and experimental.

Import and use programmatically:

import asyncio
from spspend_lib.backend.clients import SilentPaymentsClient
from spspend_lib.backend.scanner import SilentPaymentScanner
from spspend_lib.frontend.events import EventBus

async def scan_for_utxos():
    # Create client and event bus
    client = SilentPaymentsClient('localhost', 50001)
    event_bus = EventBus()

    # Set up event handlers
    async def on_utxo_found(event):
        print(f"Found UTXO: {event.data['utxo']}")

    event_bus.on(EventType.UTXO_FOUND, on_utxo_found)

    # Create scanner
    scanner = SilentPaymentScanner(
        client=client,
        scan_private_key="your_scan_key",
        spend_public_key="your_spend_key",
        network='mainnet',
        event_bus=event_bus
    )

    # Run scan
    async with client.connect():
        utxos = await scanner.scan()

    return utxos

# Run it
utxos = asyncio.run(scan_for_utxos())

Custom Frontend Example

Note: This interface is currently untested and experimental.

Create a custom UI by implementing FrontendInterface:

from spspend_lib.frontend.base import FrontendInterface
from spspend_lib.app import SilentPaymentApp

class WebFrontend(FrontendInterface):
    def __init__(self, websocket):
        self.ws = websocket

    async def show_scan_progress(self, progress: float, tx_count: int):
        await self.ws.send_json({
            'type': 'scan_progress',
            'progress': progress,
            'tx_count': tx_count
        })

    # Implement other abstract methods...

# Use it
app = SilentPaymentApp(
    frigate_client=client,
    electrum_client=None,
    frontend=WebFrontend(websocket),
    network='mainnet'
)

Architecture

The application is organized into three main layers:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Application Layer                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  spspend.py (Entry Point)                            β”‚  β”‚
β”‚  β”‚  - Argument parsing                                  β”‚  β”‚
β”‚  β”‚  - Client initialization                             β”‚  β”‚
β”‚  β”‚  - Async orchestration                               β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  app.py (SilentPaymentApp)                           β”‚  β”‚
β”‚  β”‚  - Workflow orchestration                            β”‚  β”‚
β”‚  β”‚  - Service coordination                              β”‚  β”‚
β”‚  β”‚  - Event bus management                              β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Frontend Layer                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  frontend/base.py (FrontendInterface)                β”‚  β”‚
β”‚  β”‚  - Abstract interface for UI                         β”‚  β”‚
β”‚  β”‚  - 30 abstract methods                               β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  frontend/cli.py (CLIFrontend)                       β”‚  β”‚
β”‚  β”‚  - Terminal-based UI                                 β”‚  β”‚
β”‚  β”‚  - Interactive prompts                               β”‚  β”‚
β”‚  β”‚  - Progress bars                                     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  frontend/events.py (EventBus)                       β”‚  β”‚
β”‚  β”‚  - Async pub/sub event system                        β”‚  β”‚
β”‚  β”‚  - 19 event types                                    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Backend Layer                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  backend/clients.py                                  β”‚  β”‚
β”‚  β”‚  - SilentPaymentsClient (Frigate protocol)           β”‚  β”‚
β”‚  β”‚  - ElectrumClient (Electrum protocol)                β”‚  β”‚
β”‚  β”‚  - Async TCP/SSL connections                         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  backend/scanner.py (SilentPaymentScanner)           β”‚  β”‚
β”‚  β”‚  - UTXO discovery workflow                           β”‚  β”‚
β”‚  β”‚  - Transaction processing                            β”‚  β”‚
β”‚  β”‚  - Event emission                                    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  backend/wallet.py (UTXOManager)                     β”‚  β”‚
β”‚  β”‚  - Spent status checking                             β”‚  β”‚
β”‚  β”‚  - UTXO filtering                                    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  backend/fee_estimator.py (FeeEstimator)             β”‚  β”‚
β”‚  β”‚  - Fee rate estimation                               β”‚  β”‚
β”‚  β”‚  - Transaction size calculation                      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       Core Layer                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  core/constants.py                                   β”‚  β”‚
β”‚  β”‚  - Network configurations                            β”‚  β”‚
β”‚  β”‚  - Port constants                                    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  core/models.py                                      β”‚  β”‚
β”‚  β”‚  - UTXO, TxEntry, ScanResult                         β”‚  β”‚
β”‚  β”‚  - TxOutput, TxSummary                               β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  core/crypto.py                                      β”‚  β”‚
β”‚  β”‚  - BIP-352 key derivation                            β”‚  β”‚
β”‚  β”‚  - Public key matching                               β”‚  β”‚
β”‚  β”‚  - Private key derivation                            β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  core/address.py                                     β”‚  β”‚
β”‚  β”‚  - Address derivation (P2TR, P2WPKH)                 β”‚  β”‚
β”‚  β”‚  - WIF encoding                                      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  core/transaction_builder.py                         β”‚  β”‚
β”‚  β”‚  - Transaction building                              β”‚  β”‚
β”‚  β”‚  - Schnorr signing (Taproot)                         β”‚  β”‚
β”‚  β”‚  - Transaction serialization                         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Directory Structure

sptools-py/
β”œβ”€β”€ spspend.py                    # Entry point (257 lines)
β”œβ”€β”€ requirements.txt              # Python dependencies
β”œβ”€β”€ spspend_lib/                  # Main library package (~4,550 lines)
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ app.py                    # Application orchestrator
β”‚   β”œβ”€β”€ utils.py                  # Utility functions
β”‚   β”œβ”€β”€ core/                     # Pure business logic (no I/O)
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ constants.py          # Network configs, ports, constants
β”‚   β”‚   β”œβ”€β”€ models.py             # Data models (UTXO, TxEntry, etc.)
β”‚   β”‚   β”œβ”€β”€ crypto.py             # BIP-352 cryptographic operations
β”‚   β”‚   β”œβ”€β”€ address.py            # Address derivation, WIF encoding
β”‚   β”‚   └── transaction_builder.py # Transaction building & signing
β”‚   β”œβ”€β”€ backend/                  # Services with I/O operations
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ clients.py            # Async network clients (Frigate, Electrum)
β”‚   β”‚   β”œβ”€β”€ scanner.py            # Silent Payment scanner
β”‚   β”‚   β”œβ”€β”€ wallet.py             # UTXO management
β”‚   β”‚   └── fee_estimator.py     # Fee estimation service
β”‚   └── frontend/                 # UI abstraction layer
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ base.py               # Abstract frontend interface (30 methods)
β”‚       β”œβ”€β”€ cli.py                # CLI implementation
β”‚       └── events.py             # Event bus (pub/sub, 19 event types)
└── tests/                        # Test suite (103 tests)
    β”œβ”€β”€ fixtures/                 # Test data (BIP-352 test vectors)
    β”‚   └── bip352_test_vectors.json
    β”œβ”€β”€ test_core/                # Pure function tests
    β”‚   β”œβ”€β”€ test_crypto.py        # BIP-352 crypto operations
    β”‚   β”œβ”€β”€ test_address.py       # Address derivation tests
    β”‚   β”œβ”€β”€ test_models.py        # Dataclass serialization tests
    β”‚   β”œβ”€β”€ test_schnorr_bip340.py # Schnorr signature tests (BIP-340)
    β”‚   β”œβ”€β”€ test_taproot_bip341.py # Taproot tests (BIP-341)
    β”‚   └── test_transaction_builder.py # Transaction building tests
    β”œβ”€β”€ test_backend/             # Service tests with mocks
    β”‚   └── test_scanner.py       # Scanner tests with mocked clients
    └── test_frontend/            # Frontend tests (empty - pending)

Key Features

1. Async/Await Throughout

All I/O operations use Python's asyncio for non-blocking execution:

async with frigate_client.connect():
    async with electrum_client.connect():
        app = SilentPaymentApp(...)
        await app.run(...)

2. Event-Driven Architecture

Components communicate through an async event bus:

# Scanner emits events
await self.event_bus.emit(EventType.UTXO_FOUND, {
    'utxo': utxo,
    'total_found': len(self.discovered_utxos)
})

# Frontend listens and reacts
self.event_bus.on(EventType.UTXO_FOUND, self._on_utxo_found)

3. Pluggable Frontend

Abstract interface allows multiple UI implementations:

class CustomGUIFrontend(FrontendInterface):
    def show_scan_progress(self, progress: float, tx_count: int):
        self.progress_bar.set_value(progress * 100)

    def prompt_for_keys(self) -> Tuple[str, str, Optional[str], Optional[int]]:
        return self.key_input_dialog.get_values()

4. Pure Core Functions

Core layer has no I/O or side effects:

# Pure function - deterministic, no I/O
def derive_output_pubkey(
    spend_pubkey: str,
    tweak_key: str,
    scan_privkey: str,
    k: int = 0
) -> Tuple[Tuple[int, int], str]:
    # BIP-352 derivation logic...
    return (output_pubkey, t_k_hex)

Running Tests

The project includes a comprehensive test suite with 103 tests covering:

  • BIP-352 Silent Payments operations
  • BIP-340 Schnorr signatures
  • BIP-341 Taproot operations
  • Address derivation and encoding
  • Transaction building and signing
  • Scanner functionality with mocked clients

Note: Python 3.13+ required. Activate the virtual environment first.

# Activate virtual environment
source venv/bin/activate  # Linux/Mac

# Run all tests
python -m unittest discover tests

# Run specific test module
python -m unittest tests.test_core.test_crypto

# Run with verbose output
python -m unittest discover tests -v

# Run only core tests
python -m unittest discover tests/test_core -v

# Alternative: using pytest
pytest tests/ -v

Development

Adding a New Feature

  1. Core Logic: Add pure functions to core/ modules
  2. Backend Service: Create async service in backend/
  3. Event Types: Add new events to frontend/events.py
  4. Frontend Methods: Add UI methods to frontend/base.py
  5. CLI Implementation: Implement in frontend/cli.py
  6. Orchestration: Wire up in app.py
  7. Tests: Add unit tests for all components

Code Style

  • Type Hints: All public functions use type hints
  • Docstrings: Google-style docstrings for all modules and public functions
  • Pure Functions: Core layer functions have no side effects
  • Async First: All I/O operations use async/await
  • Event-Driven: Components communicate via events, not direct calls

Performance

The modular architecture provides:

  • Non-blocking I/O: Async operations don't block the event loop
  • Concurrent Scanning: Multiple transactions can be processed concurrently
  • Efficient Event Bus: O(1) event emission and handler registration
  • Lazy Loading: Modules loaded only when needed

Dependencies

Python Version: 3.13+ (tested with Python 3.13.7)

Core dependencies:

  • asyncio (built-in): Async I/O framework
  • coincurve (21.0.0): ECDSA/Schnorr cryptography
  • embit (0.8.0): Bitcoin primitives, Bech32 encoding
  • gmpy2 (2.2.1): Fast modular arithmetic
  • base58 (2.1.1): Base58 encoding for WIF
  • pytest (optional): Alternative test runner

Installation:

# Activate virtual environment
source venv/bin/activate

# Install all dependencies
pip install -r requirements.txt

Architecture Benefits

Separation of Concerns

  • Core: Pure business logic, easily testable
  • Backend: I/O and external service interaction
  • Frontend: UI presentation logic
  • App: Workflow orchestration

Testability

  • Core functions are pure and deterministic
  • Backend services can be mocked
  • Frontend can be swapped for testing
  • Event bus enables integration tests

Extensibility

  • New frontends (GUI, Web) without touching core
  • New backend services without changing UI
  • New features through event handlers
  • Custom workflows by composing services

Maintainability

  • Small, focused modules (~200 lines each)
  • Clear dependencies between layers
  • Self-documenting code with type hints
  • Comprehensive test coverage

License

GPL-3.0 License - See LICENSE file for details.

Copyright (c) 2025 levinster82

Credits

Implements the BIP-352 Silent Payments specification for Bitcoin. Built with a clean, modular architecture designed for extensibility and maintainability.

About

A simple python tool for discovery and sweeping Silent Payment UTXO's

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages