add cryptographic primitives, single-use address model, and UTXO management strategies
This commit is contained in:
@@ -885,6 +885,265 @@ Jericho sacrifices the passive security of capital hostage-taking (Bitcoin's ASI
|
||||
|
||||
The work-bond identity mechanism ensures that validator creation always costs real resources—computation early, capital later—eliminating both Sybil attacks and privileged genesis distribution. Early and late participants face equivalent barriers in different currencies.
|
||||
|
||||
## Cryptographic Primitives and Address Model
|
||||
|
||||
### Signature Scheme
|
||||
|
||||
The protocol uses **secp256k1 Schnorr signatures** for all authentication:
|
||||
|
||||
```
|
||||
Key generation:
|
||||
private_key = random 256-bit integer mod n
|
||||
public_key = private_key × G (33 bytes compressed, or 32 bytes x-only)
|
||||
|
||||
Signature:
|
||||
sig = schnorr_sign(private_key, message)
|
||||
sig_size = 64 bytes (r: 32 bytes, s: 32 bytes)
|
||||
|
||||
Verification:
|
||||
valid = schnorr_verify(public_key, message, sig)
|
||||
```
|
||||
|
||||
**Schnorr advantages over ECDSA:**
|
||||
- Provable security under standard assumptions
|
||||
- Linear signature aggregation (n signatures → 1 signature for n-of-n multisig)
|
||||
- Batch verification (~2× faster for multiple signatures)
|
||||
- Simpler, non-malleable signatures
|
||||
|
||||
**Note on quantum resistance:** secp256k1 Schnorr signatures are not quantum-resistant. Shor's algorithm can solve the discrete logarithm problem, enabling key recovery from exposed public keys. The single-use address model (below) limits exposure, but does not provide full quantum security. Post-quantum migration may be considered before protocol ossification.
|
||||
|
||||
### Address Model
|
||||
|
||||
Addresses are SHA-256 hashes of public keys, not raw public keys:
|
||||
|
||||
```
|
||||
address = SHA256(public_key)
|
||||
|
||||
address_size = 32 bytes
|
||||
```
|
||||
|
||||
**Public key hiding:** The public key is not revealed until spending. This provides:
|
||||
- Partial quantum protection (public key unknown until spend)
|
||||
- Smaller on-chain footprint for unspent outputs
|
||||
- Address commitments possible before key generation
|
||||
|
||||
### Single-Use Address Enforcement
|
||||
|
||||
**The protocol rejects any transaction that spends to an address that has previously appeared as an output.**
|
||||
|
||||
```
|
||||
consensus_rule:
|
||||
for each output in transaction.outputs:
|
||||
if output.address ∈ historical_output_addresses:
|
||||
REJECT transaction as invalid
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
|
||||
| Problem | Single-Use Solution |
|
||||
|---------|---------------------|
|
||||
| Public key exposure | Key revealed only once, at spend time |
|
||||
| Address clustering | No repeated addresses to link |
|
||||
| Quantum harvest attacks | Exposed keys cannot receive future funds |
|
||||
| Change address correlation | Forced fresh addresses break heuristics |
|
||||
|
||||
**Implementation:**
|
||||
|
||||
Nodes maintain a bloom filter or set of all historical output addresses:
|
||||
|
||||
```
|
||||
historical_addresses: Set<Address>
|
||||
|
||||
on_new_block(block):
|
||||
for tx in block.transactions:
|
||||
for output in tx.outputs:
|
||||
if output.address in historical_addresses:
|
||||
reject_block("address reuse detected")
|
||||
historical_addresses.add(output.address)
|
||||
```
|
||||
|
||||
The storage overhead is ~32 bytes per historical output, but can be compressed using probabilistic structures with negligible false positive rate.
|
||||
|
||||
### Recommended UTXO Management
|
||||
|
||||
Wallets should implement automated UTXO management to minimize address linkage while maintaining efficient coin selection. The recommended strategy uses **interleaved change addresses** that distribute change outputs across the wallet's value range.
|
||||
|
||||
#### UTXO Set Structure
|
||||
|
||||
Wallets maintain UTXOs sorted by value:
|
||||
|
||||
```
|
||||
wallet_utxos = [
|
||||
{address: A1, value: 0.5},
|
||||
{address: A2, value: 2.3},
|
||||
{address: A3, value: 5.0},
|
||||
{address: A4, value: 12.7},
|
||||
{address: A5, value: 50.0},
|
||||
]
|
||||
```
|
||||
|
||||
#### Interleaved Change Strategy
|
||||
|
||||
When spending requires change, generate 2-3 change outputs that interleave with existing UTXOs by value:
|
||||
|
||||
```
|
||||
function create_change_outputs(change_amount, wallet_utxos) -> [output]:
|
||||
// Determine number of change outputs (2-3)
|
||||
num_changes = 2 + (random() % 2)
|
||||
|
||||
// Find value gaps in existing UTXO set
|
||||
gaps = find_value_gaps(wallet_utxos)
|
||||
|
||||
// Distribute change across gaps
|
||||
change_outputs = []
|
||||
remaining = change_amount
|
||||
|
||||
for i in 0..(num_changes - 1):
|
||||
// Target a value that falls within a gap
|
||||
target_value = select_gap_value(gaps[i % len(gaps)])
|
||||
|
||||
// Allocate portion of change (with randomization)
|
||||
if i == num_changes - 1:
|
||||
portion = remaining
|
||||
else:
|
||||
portion = remaining × (0.3 + random() × 0.4) // 30-70% of remaining
|
||||
|
||||
change_outputs.append({
|
||||
address: generate_fresh_address(),
|
||||
value: portion
|
||||
})
|
||||
remaining -= portion
|
||||
|
||||
return change_outputs
|
||||
|
||||
|
||||
function find_value_gaps(utxos) -> [gap]:
|
||||
gaps = []
|
||||
sorted_utxos = sort_by_value(utxos)
|
||||
|
||||
for i in 0..(len(sorted_utxos) - 1):
|
||||
gap = {
|
||||
lower: sorted_utxos[i].value,
|
||||
upper: sorted_utxos[i + 1].value,
|
||||
midpoint: (sorted_utxos[i].value + sorted_utxos[i + 1].value) / 2
|
||||
}
|
||||
gaps.append(gap)
|
||||
|
||||
// Add gaps at extremes
|
||||
if len(sorted_utxos) > 0:
|
||||
gaps.append({lower: 0, upper: sorted_utxos[0].value})
|
||||
gaps.append({lower: sorted_utxos[-1].value, upper: ∞})
|
||||
|
||||
return gaps
|
||||
```
|
||||
|
||||
#### Example: Interleaved Change
|
||||
|
||||
```
|
||||
Before transaction:
|
||||
UTXOs: [0.5, 2.3, 5.0, 12.7, 50.0]
|
||||
Gaps: [0-0.5, 0.5-2.3, 2.3-5.0, 5.0-12.7, 12.7-50.0, 50.0+]
|
||||
|
||||
Payment: 8.0 tokens from the 12.7 UTXO
|
||||
Change: 4.7 tokens to distribute
|
||||
|
||||
Interleaved change outputs (example):
|
||||
Change 1: 1.8 tokens (fills gap 0.5-2.3)
|
||||
Change 2: 2.9 tokens (fills gap 2.3-5.0)
|
||||
|
||||
After transaction:
|
||||
UTXOs: [0.5, 1.8, 2.3, 2.9, 5.0, 50.0]
|
||||
|
||||
Result: Change is distributed, not concentrated in one traceable output
|
||||
```
|
||||
|
||||
#### Privacy Properties
|
||||
|
||||
| Heuristic Attack | Mitigation |
|
||||
|------------------|------------|
|
||||
| Largest output is payment | Multiple change outputs obscure which is payment |
|
||||
| Change returns to same value range | Interleaving distributes across ranges |
|
||||
| Round number detection | Randomized splits avoid round numbers |
|
||||
| UTXO count fingerprinting | Gradual UTXO count changes, not dramatic |
|
||||
| Value clustering | Values spread across wallet's range |
|
||||
|
||||
#### Coin Selection Algorithm
|
||||
|
||||
When selecting inputs for a payment:
|
||||
|
||||
```
|
||||
function select_inputs(payment_amount, wallet_utxos) -> [utxo]:
|
||||
// Prefer single UTXO if possible (minimizes linkage)
|
||||
for utxo in wallet_utxos:
|
||||
if utxo.value >= payment_amount AND utxo.value < payment_amount × 1.5:
|
||||
return [utxo]
|
||||
|
||||
// Otherwise, select minimal set with preference for:
|
||||
// 1. UTXOs that will produce change fitting existing gaps
|
||||
// 2. Older UTXOs (reduce UTXO set age variance)
|
||||
// 3. UTXOs from different historical transactions (if metadata available)
|
||||
|
||||
selected = []
|
||||
total = 0
|
||||
|
||||
candidates = sort_by_selection_score(wallet_utxos, payment_amount)
|
||||
|
||||
for utxo in candidates:
|
||||
selected.append(utxo)
|
||||
total += utxo.value
|
||||
if total >= payment_amount + min_change:
|
||||
break
|
||||
|
||||
return selected
|
||||
|
||||
|
||||
function selection_score(utxo, payment_amount, wallet_utxos) -> float:
|
||||
// Score based on how well resulting change fits gaps
|
||||
potential_change = utxo.value - payment_amount
|
||||
gap_fit = score_gap_fit(potential_change, wallet_utxos)
|
||||
|
||||
// Prefer older UTXOs
|
||||
age_score = utxo.confirmation_height / current_height
|
||||
|
||||
// Combine scores
|
||||
return gap_fit × 0.6 + age_score × 0.4
|
||||
```
|
||||
|
||||
#### UTXO Consolidation
|
||||
|
||||
Periodically consolidate UTXOs during low-fee periods:
|
||||
|
||||
```
|
||||
function consolidation_candidates(wallet_utxos) -> [utxo]:
|
||||
// Identify dust or near-dust UTXOs
|
||||
dust_threshold = median(wallet_utxos.values) × 0.01
|
||||
|
||||
candidates = [u for u in wallet_utxos if u.value < dust_threshold]
|
||||
|
||||
// Only consolidate if it improves UTXO distribution
|
||||
if len(candidates) >= 3:
|
||||
return candidates
|
||||
else:
|
||||
return []
|
||||
|
||||
Consolidation creates a single new UTXO at a value that fills
|
||||
the largest gap in the remaining UTXO distribution.
|
||||
```
|
||||
|
||||
#### Wallet Implementation Requirements
|
||||
|
||||
Compliant wallets MUST:
|
||||
1. Generate fresh addresses for all outputs (enforced by consensus)
|
||||
2. Implement interleaved change (RECOMMENDED for privacy)
|
||||
3. Never expose public keys until spending
|
||||
4. Maintain UTXO metadata for intelligent selection
|
||||
|
||||
Compliant wallets SHOULD:
|
||||
1. Use 2-3 change outputs for transactions with significant change
|
||||
2. Randomize change value distribution (not exact splits)
|
||||
3. Consolidate dust during low-fee periods
|
||||
4. Avoid creating outputs smaller than median fee × 100
|
||||
|
||||
## Collusion Resistance and Protocol Ossification
|
||||
|
||||
The protocol is designed to resist capture by cartels seeking to modify consensus rules, inject arbitrary data, or expand scripting capabilities. These defenses promote eventual ossification—a stable end-state where the protocol becomes immutable.
|
||||
|
||||
Reference in New Issue
Block a user