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.
It is highly recommended to import the signed transaction into Electrum wallet or Sparrow wallet and carefully review all transaction outputs before broadcasting.
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
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# 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.infousage: 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
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())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'
)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 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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)
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(...)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)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()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)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- Core Logic: Add pure functions to
core/modules - Backend Service: Create async service in
backend/ - Event Types: Add new events to
frontend/events.py - Frontend Methods: Add UI methods to
frontend/base.py - CLI Implementation: Implement in
frontend/cli.py - Orchestration: Wire up in
app.py - Tests: Add unit tests for all components
- 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
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
Python Version: 3.13+ (tested with Python 3.13.7)
Core dependencies:
asyncio(built-in): Async I/O frameworkcoincurve(21.0.0): ECDSA/Schnorr cryptographyembit(0.8.0): Bitcoin primitives, Bech32 encodinggmpy2(2.2.1): Fast modular arithmeticbase58(2.1.1): Base58 encoding for WIFpytest(optional): Alternative test runner
Installation:
# Activate virtual environment
source venv/bin/activate
# Install all dependencies
pip install -r requirements.txt- Core: Pure business logic, easily testable
- Backend: I/O and external service interaction
- Frontend: UI presentation logic
- App: Workflow orchestration
- Core functions are pure and deterministic
- Backend services can be mocked
- Frontend can be swapped for testing
- Event bus enables integration tests
- New frontends (GUI, Web) without touching core
- New backend services without changing UI
- New features through event handlers
- Custom workflows by composing services
- Small, focused modules (~200 lines each)
- Clear dependencies between layers
- Self-documenting code with type hints
- Comprehensive test coverage
GPL-3.0 License - See LICENSE file for details.
Copyright (c) 2025 levinster82
Implements the BIP-352 Silent Payments specification for Bitcoin. Built with a clean, modular architecture designed for extensibility and maintainability.