This specification defines a unified standard for Silent Payments indexing services, enabling wallet developers to implement efficient Silent Payments receive support through standardized APIs. The specification covers three service deployment models and their corresponding API requirements.
The goal of this specification is to provide a unified standard for Silent Payments wallet and service developers to implement receive support. The Electrum protocol provides a great model for how wallet-server communication works today, demonstrating efficient client-server interactions for lightweight Bitcoin wallets. This specification is not meant to replace the Electrum protocol but to extend it to support Silent Payments, leveraging its proven architecture to enhance privacy and efficiency.
Although it might appear that index servers are only necessary for light client support, reducing scanning operations for wallets improves the user experience, speeds up, and simplifies wallet receiving implementations. Minimizing the time it takes for users to confirm on-chain balance when opening a wallet aligns with existing expectations for user experience. Another benefit of having a common specification for tweak services is that developers can iterate on wallet or server improvements once, without requiring extensive coordination for upgrade compatibility. Additionally users and wallet developers will have the choice to migrate to the best service provider without requiring architectural changes.
blindbit, cake wallet and silentiumd have completed indexing server solutions that are being leveraged by wallet developers today. The current approaches successfully dervive tweaks and perform additional scan operations but there is no clear definition of what role each service provides to the wallet. A wallet team could not swap services without signficant changes to wallet design. Once this specification is solidified each of the current implementations will have a framework to align data formats and validate data integrity.
- blindbit-oracle
- dana-wallet
- BDK + kyoto
- cake-esplora
- cake wallet
A user can setup a bitcoin node and add an indexing service to same infrastructure, wallet registers scan private key and silent payment address + birthdate with service for most efficient initialization and UTXO management.
Some users may choose to forgo setting up bitcoin node infrastructure and choose to outsource tweaks for a range of blocks. Hosted services providing Block Processor and Block Filter Manager roles can be used anonymously if care is taken by the wallet developer.
There are three distinct service roles (steps) required to find silent payments. Either the wallet or service has to do the work. The following section details different service components and describes the key role it provides in scanning for payments. Even though the services can be combined into one Stack of capabilities all standard endpoints for a given service must be implemented. Additional endpoints can be offered but will not be captured in this specification. Each wallet implementation will have to choose which Stack to build based on the wallet / user objectives.
- requires access to full node
- Compute tweaks for all blocks
- Serve Tweaks and relevant TXOs for each block
- Examples
- blindbit-oracle, Electrs, ElectrumX, Esplora, Fullcrum, silentiumd
- requires access to full node or BP
- Serve CBF (BIP158) headers & filters
- Examples
- blindbit-oracle, kyoto (bdk-sp), silentiumd?
- requires access to full node or BP or BFM
- Wallet shares: scan_sk, scan_pk, spend_pk with service
- Permenant (Hosted) or Ephemeral (Session)
- Find owned UTXOs since last scan or for a range of blocks
- Examples
- sp-client::SpScanner, sp-poc (Kyoto + bdk-sp)
Service Roles | |||
---|---|---|---|
Feature | Block Processor | Block Filter Manager | SP Scanner |
Privacy | Yes | Yes | No |
Security (Trust) | N/A | N/A | Yes |
Dust | Possible | Possible | Possible |
Spent Tx Filtering | Possible | Possible | Possible |
Payment Notifications | No | Maybe | Yes |
Mempool Monitoring | No | Maybe | Yes |
Performance / UX (Offline catch up delay) | Slow - sync required | Depends on wallet implementation | Depends on wallet implementation |
Server Storage Requirements | Large | Small | Small |
Client Bandwidth (wallet <-> service) | Large/Moderate | Large/Moderate | Small |
Client Transaction Rate | < 2 per day | < 2 per day | unlimited |
Labels | No | No | Yes |
Stack implementations combine one or more service roles to create unique offerings for different privacy objectives. Each of these stacks would likely have different endpoint requirements.
- combines SP Scanner + Block Filter Manager + Block Processor
- Configured to support a single wallet or small group of wallets
- Wallet birthdate limits scanning requirements
- Register scan key pair + spend public key with server
- Trusted service
- Wallet can assume that any data received from the service does not need to be verified
- Wallet would receive a list of UTXOs
- User could be notified when payment is detected by server
- Technical User
- Willing to learn how to optimize for their situation (configure)
- Additional Services
- Wallet Recovery
- Fast - based on filtered UTXO set
- Mempool Monitoring
- Receive Notifications
- Wallet Recovery
- combines SP Scanner + Block Filter Manager + Block Processor
- Configured to respond to any wallet
- Must index entire chain from Taproot activation
- Wallet request UTXOs from service
- Service is trusted
- To preserve forward privacy a user should rotate wallet when leaving service
- Novice or Technical User
- Share scan key pair + spend public key with server during a session
- Relies on well organized UI/UX to educate user on "scanning concepts"
- Additional Services
- Wallet Recovery
- Wallet should maintain SP transaction archive
- Mempool Monitoring
- May be rate limited to a few checks per minute or max per day
- Labels
- Each additional label requires more compute to scan / transaction
- May be rate limited or require additional fee
- Wallet Recovery
- combines Block Filter Manager + Block Processor
- Configured to respond to any wallet
- Must index entire chain from Taproot activation
- Wallet request tweaks for a block or range of blocks, use block filters to identify transactions
- Service cannot be fully trusted
- Users should be able to verify received data against other sources or prove legitimacy independently
- Novice or Technical User
- Simple to understand workflow to configure and use (set it and forget)
- Relies on well organized UI/UX to educate user on "scanning concepts"
- Wallet Recovery
- Wallet should maintain SP transaction archive
- Services may rate limit bulk scanning
Silent Payment Stack | |||
---|---|---|---|
Feature | My Scanner (Personalized) | Remote Scanner (Ephemeral) | Tweak Server (Anonymous) |
Privacy | Full (register spend public key & scan private key) Self Hosted |
Privacy from all but Indexer Service (share spend public key & scan private key) for a session Hosted |
Mask interest in ScriptPubKeys and Transaction (Care should be taken when filtering for blocks) Hosted |
Skill (User) | Experienced / Technical | Novice / Moderate | Novice / Moderate |
Security (Trust) | Trusted (Self hosted) | Trusted (Paid/Free) | Untrusted (Free) Should download full block on filtered match (no simplified utxo) |
Wallet Requirements | Send / Spend SP Fetch Payments Manage UTXOs |
Send / Spend SP Remote Scan Manage UTXOs |
Send / Spend SP Scan For ScriptPubKeys Manage UTXOs |
Wallet Backup / Recovery | Label backup is necessary, recovering transactions should be quick | Most important Label backup + utxo backup would be best (restore could be rate limited) |
Most important Label backup + utxo backup would be best (restore could be rate limited) |
Dust | wallet preference | wallet preference | wallet preference |
Spent Tx Filtering (Cut Through) | wallet preference | wallet preference | Possible |
Payment Notifications | Optional | Optional | Not Feasible |
Mempool Monitoring | Optional | Optional | Not Feasible |
Performance / UX (Offline catch up delay) | Wallet can have same UX as traditional payment | Wallet will need to sync (catch up) to find all UTXOs - distributed scan. | Wallet will need to sync (catch up) to find all UTXOs |
Server Storage Requirements | Medium | Large | Large |
Client Bandwidth (wallet <-> service) | Low | Low | Moderate |
Client Transaction Rate | unlimited | < ~20 day | < 1 day |
Labels | Supported - register labels with service | Managed by wallet | Should labels be offered in wallet at all? |
API endpoints are grouped by Service Roles, thought has been given to include what each implementation should support "Required" vs "Optional".
Service Roles | ||||||||
---|---|---|---|---|---|---|---|---|
Endpoint | SP Scanner | Block Filter Manager | Block Processor | Description | blindbit-oracle | blindbit-scan | silentiumd | cake esplora |
/info | Required | Required | returns basic information about the indexing server instance | Y | Similar | Similar | ||
/block-hash/:blockheight | Required | Required | returns the block-hash for a certain block-height - used by wallet to detect reorg | Y | ||||
/tweaks/:blockheight?dustLimit&filterSpent | Required | returns tweak data; optional parameters filterSpent + dustLimit. | Y | Uses | Similar | Similar | ||
/filter/utxos/:blockheight | Required | returns a BIP 158 compliant filter of script pub keys which received funds | Y | Uses | ||||
/utxos/:blockheight | Optional? | UTXO data for that block (cut down to the essentials needed to spend) | Y | Uses | ||||
/filter/spent/:blockheight | Optional? | returns a filter for shortened spent outpoint hashes; optional validation of spent outpoints | Y | Uses | Similar | |||
/spent-index/:blockheight | Optional? | returns the spent outpoints index | Y | Uses | ||||
Proposed | ||||||||
/scan/:keys/:block_range/[:labels] | Y | scan a range of blocks given scan_sk + sp address; returns UTXOs or busy | ||||||
/scan-bulk/:keys/:block_range/[:labels] | Y | start async scan for UTXOs; returns a scan token and expected duration | ||||||
/scan-check/:token | Y | provide scan token to collect UTXOs or updated duration | ||||||
/scan-cancel/:token | Y | provide scan token to cancel bulk scan; wallet courtesy | ||||||
/scan-mempool/:keys/[:labels] | Y | subscribe to unconfirmed scanning given scan_sk + sp address | ||||||
Electrum | ||||||||
rpc blockchain.tweaks.subscribe | Y | Y | ||||||
rpc blockchain.scripthash.get_balance | Y | Y | ||||||
rpc blockchain.scripthash.get_history | Y | Y | ||||||
? | ||||||||
/register | Optional? | handles registering new wallets; returns unique wallet identifier | Y | |||||
/wallet/utxos?restore | Optional? | serves UTXOs since last check; restore all known UTXOs | Y | |||||
/labels | Optional? | add or return list of labels | Y |
{
"version": "1.0.0",
"network": "mainnet|testnet|regtest",
"block_height": 850000,
"services": ["block_processor", "block_filter_manager", "sp_scanner"],
"dust_limit": 546,
"filter_spent": "optional|N/A" # indicates if server supports optional filtering
}
{
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"status": 0|1
}
{
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"dust_limit": 546, # zero indicates disabled, requested limit is echoed for confirmation
"filter_spent": 0|1, # zero indicates off, one == on
"tweaks": [
"02abc123...",
"03def456...",
],
"checksum or count": 1234,
}
{
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"data": "hex_encoded_filter_bytes",
}
{
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"data": "hex_encoded_filter_bytes",
"checksum or data length": 12345,
}
Request:
{
"scan_pk": "02abc123...",
"scan_sk": "02abc123...",
"spend_pk": "03def456...",
"start_height": 800000,
"end_height": 800002,
"labels_index" : [1, 2, 3] # optional label index
}
Response (complete):
{
"status": "complete",
"utxos": [
{
"txid": "a1b2c3...",
"output_index": 0,
"value": 100000,
"output_pubkey": "03def456...", #script pub key
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"spent": false
}
]
}
Response (busy):
{
"status": "busy",
"message": "try bulk scan"
}
Request:
{
"scan_pk": "02abc123...",
"scan_sk": "02abc123...",
"spend_pk": "03def456...",
"start_height": 800000,
"end_height": 800102,
"labels_index" : [1, 3, 7] # optional label index
}
Response:
{
"duration": 100000,
"token": "02abc123..."
}
Request:
{
"token": "02abc123..."
}
Response (complete):
{
"status": "complete",
"utxos": [
{
"txid": "a1b2c3...",
"output_index": 0,
"value": 100000,
"output_pubkey": "03def456...", #script pub key
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"spent": false
},
{
"txid": "a1b2c3...",
"output_index": 3,
"value": 100000,
"output_pubkey": "03def456...", #script pub key
"height": 850001,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"spent": false
}
]
}
Response (busy):
{
"status": "busy",
"duration": 10000,
"token": "02abc123..."
}
Request:
{
"token": "02abc123..."
}
Response:
{
"status": "complete"
}
Request:
{
"scan_pk": "02abc123...",
"scan_sk": "02abc123...",
"spend_pk": "03def456...",
"labels_index" : [2, 3] # optional label index
}
Response (on update):
{
"status": "active",
"last_check": <timestamp>,
"next_check": <timestamp>,
"utxos": [
{
"txid": "a1b2c3...",
"output_index": 0,
"value": 100000,
"output_pubkey": "03def456...", #script pub key
"height": 850000,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"spent": false
}
]
}
Response (failure):
{
"status": "error",
"last_check": <timestamp>,
"message": "error string"
}
Request:
{
"scan_pk": "02abc123...",
"scan_sk": "02abc123...",
"spend_pk": "03def456...",
"birthday": 850000,
"label": "my_wallet"
}
Response:
{
"wallet_id": "uuid-string",
"registered": true,
"scan_from": 850000
}
{
"wallet_id": "uuid-string",
"last_scan_height": 850100,
"utxos": [
{
"txid": "a1b2c3...",
"output_index": 0,
"value": 100000,
"output_pubkey": "03def456...", #script pub key
"height": 850050,
"hash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
"spent": false
}
]
}
{
"error": {
"code": 400,
"message": "Invalid block height",
"details": "Block height must be between 0 and current tip"
}
}
(per block)
1. Fetch tweaks `/tweaks-index/:height`
2. Compute the possible scriptPubKeys for n = 0 and each label n+1
3. Fetch filter `/filter/utxos/:height` (BIP 158)
4. Match scriptPubKeys against filter
- If no match: go to 1. with height + 1
- Else: continue with 5.
5. Fetch block, verify owned UTXOs and add to wallet
- Go to 1. with height + 1
1. Wallet requests a block or range of blocks - provide sp address and scan private key `/scan/:keys/:range`
1.1. Service will respond with 1 of the following:
- utxos found for each block
- "busy - requests too large" - upon receiving this response wallet can try another service or request bulk scan
1.2. Bulk scan - wallet provide sp address and scan private key `/scan-bulk/:keys/:range`
- Service responds with estimated scan duration and bulk scan token
- Wallet records token and sets timer for duration
- on timeout wallet checks `/scan-check/:token`
2. Wallet subscribes to unconfirmed scanning `/scan-mempool/:keys`
2.1. Service will scan for utxos every # seconds
- For each UTXO found matching a key; stream message to wallet
3. Wallet maintain local UTXO state for spending
1. Register wallet sp address and scan private key with /register endpoint
2. Periodically query `/wallet/utxos` for updates
3. Handle notifications of unconfirmed transactions if supported
4. Maintain local UTXO state for spending
Reference implementations or specific test vectors for section 2 and 3. Section 1 is fully defined in BIP352 Spec
- Verify Tweak Derivation - BIP352 Test Vectors
- Block Processing
- Block Vectors
- expected tweaks for a given block
- Pruning
- Spent Tx Filtering (cut-through)
- Dust
- Block Vectors
- Service switching without data loss
- Reorg handling consistency
- Rate limiting behavior verification
- No authentication required for public endpoints
- Rate limiting applied per IP address
- No user registration or key storage
- Token or wallet ID based authentication
- Key storage should be ephemeral for the life of a session
- Use token for fetching scan results
- Anonymous services: Maximum 10 blocks / minute
- Scanner services: Depends on load and service offering
- Services should not log request patterns that could deanonymize users
- Tweak servers should implement request batching to reduce timing correlation
- Block hash verification required for reorg detection
- Tweak data should be verifiable against full node data
- Checksums recommended for bulk data transfers (How does a wallet know all tweaks were received for a given block request?)
- Pagination for large bulk results