Context
HD Wallets (BIP 44) face a fundamental trade-off between Privacy (avoiding address reuse) and Safety (the Gap Limit problem).
- Privacy: Requires issuing a new address for every request ("Always New").
- Safety: Requires ensuring that the "Gap" (number of consecutive unused addresses) does not exceed the scanning window of a restored wallet (typically 20).
Currently, btcwallet defaults to the "Always New" strategy. If a user or application generates >20 addresses without receiving funds, a subsequent wallet restoration will fail to discover funds sent to the 21st address.
Detailed Problem Example
Consider a backend service using btcwallet to accept deposits, with the default gap limit of 20.
-
Initial State:
- Last Used Index (On-Chain): 100
- Next Issued Index: 101
- Gap: 0
-
High Volume Address Generation:
- The service generates 25 deposit addresses for new user signups.
- Each signup triggers a
NewAddress call (Indices 101 to 125).
- Crucially: None of these users deposit funds immediately. The addresses remain empty on-chain.
-
State After Generation:
- Last Used Index (On-Chain): 100
- Next Issued Index: 126
- Current Gap: 26 (Indices 101-126 are empty on-chain).
-
The "Lost" Funds Scenario:
This "Always New" strategy is brittle in automated environments. Services often generate addresses that aren't immediately used, easily exceeding the default gap limit.
Proposed Solutions
We propose a hybrid strategy that maintains privacy by default but enforces safety boundaries.
1. Track "Issued" vs "Used" Gap
Instead of blindly issuing new addresses, the wallet must track the gap explicitly.
- Used Index: The highest index found in the UTXO set (e.g., 100).
- Issued Index: The highest index found in the Addresses table (e.g., 125).
- Gap:
Issued Index - Used Index.
2. Configurable Gap Enforcement
When a new address is requested, check the Gap against a configured GapLimit (default 20, recommended 100+).
3. Increase Default Lookahead
To reduce friction, we should increase the default lookahead window in the scanner from 20 to a safer baseline (e.g., 100 or 1000).
- Pros: Drastically reduces the probability of accidental fund loss.
- Cons: Increases initial sync time and resource usage during rescans.
Implementation Strategy (Query-Based)
This logic relies on efficient queries rather than new schema columns:
- Issued Index: Query
MAX(address_index) from the addresses table (scoped by account/branch).
- Used Index: Query
MAX(address_index) from the utxos table (joined with addresses).
- Manager Logic:
- Calculates
Gap = MaxAddressIndex - MaxUtxoIndex.
- Enforces the policy before calling
NextAddressIndex.
This approach keeps the schema normalized and relies on the database's indexing for performance.
Context
HD Wallets (BIP 44) face a fundamental trade-off between Privacy (avoiding address reuse) and Safety (the Gap Limit problem).
Currently,
btcwalletdefaults to the "Always New" strategy. If a user or application generates >20 addresses without receiving funds, a subsequent wallet restoration will fail to discover funds sent to the 21st address.Detailed Problem Example
Consider a backend service using
btcwalletto accept deposits, with the default gap limit of 20.Initial State:
High Volume Address Generation:
NewAddresscall (Indices 101 to 125).State After Generation:
The "Lost" Funds Scenario:
This "Always New" strategy is brittle in automated environments. Services often generate addresses that aren't immediately used, easily exceeding the default gap limit.
Proposed Solutions
We propose a hybrid strategy that maintains privacy by default but enforces safety boundaries.
1. Track "Issued" vs "Used" Gap
Instead of blindly issuing new addresses, the wallet must track the gap explicitly.
Issued Index - Used Index.2. Configurable Gap Enforcement
When a new address is requested, check the Gap against a configured
GapLimit(default 20, recommended 100+).If Gap < Limit:
If Gap >= Limit:
The behavior should be configurable based on the user's preference for Safety vs. Privacy:
Option A (Strict Safety - Reuse): Return the oldest unused address (e.g., Index 101).
Option B (Strict Privacy - Error): Return an error (
ErrAddressGapExceeded).Option C (Lenient - Warning): Log a warning and issue the address anyway.
3. Increase Default Lookahead
To reduce friction, we should increase the default lookahead window in the scanner from 20 to a safer baseline (e.g., 100 or 1000).
Implementation Strategy (Query-Based)
This logic relies on efficient queries rather than new schema columns:
MAX(address_index)from theaddressestable (scoped by account/branch).MAX(address_index)from theutxostable (joined with addresses).Gap = MaxAddressIndex - MaxUtxoIndex.NextAddressIndex.This approach keeps the schema normalized and relies on the database's indexing for performance.