diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ea49508..00ecb50 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -153,7 +153,9 @@ "Bash(git check-ignore:*)", "Bash(git commit:*)", "WebFetch(domain:www.npmjs.com)", - "Bash(git stash:*)" + "Bash(git stash:*)", + "WebFetch(domain:arxiv.org)", + "WebFetch(domain:hal.science)" ], "deny": [], "ask": [] diff --git a/.claude/skills/distributed-systems/SKILL.md b/.claude/skills/distributed-systems/SKILL.md new file mode 100644 index 0000000..c5af8b3 --- /dev/null +++ b/.claude/skills/distributed-systems/SKILL.md @@ -0,0 +1,1115 @@ +--- +name: distributed-systems +description: This skill should be used when designing or implementing distributed systems, understanding consensus protocols (Paxos, Raft, PBFT, Nakamoto, PnyxDB), analyzing CAP theorem trade-offs, implementing logical clocks (Lamport, Vector, ITC), or building fault-tolerant architectures. Provides comprehensive knowledge of consensus algorithms, Byzantine fault tolerance, adversarial oracle protocols, replication strategies, causality tracking, and distributed system design principles. +--- + +# Distributed Systems + +This skill provides deep knowledge of distributed systems design, consensus protocols, fault tolerance, and the fundamental trade-offs in building reliable distributed architectures. + +## When to Use This Skill + +- Designing distributed databases or storage systems +- Implementing consensus protocols (Raft, Paxos, PBFT, Nakamoto, PnyxDB) +- Analyzing system trade-offs using CAP theorem +- Building fault-tolerant or Byzantine fault-tolerant systems +- Understanding replication and consistency models +- Implementing causality tracking with logical clocks +- Building blockchain consensus mechanisms +- Designing decentralized oracle systems +- Understanding adversarial attack vectors in distributed systems + +## CAP Theorem + +### The Fundamental Trade-off + +The CAP theorem, introduced by Eric Brewer in 2000, states that a distributed data store cannot simultaneously provide more than two of: + +1. **Consistency (C)**: Every read receives the most recent write or an error +2. **Availability (A)**: Every request receives a non-error response (without guarantee of most recent data) +3. **Partition Tolerance (P)**: System continues operating despite network partitions + +### Why P is Non-Negotiable + +In any distributed system over a network: +- Network partitions **will** occur (cable cuts, router failures, congestion) +- A system that isn't partition-tolerant isn't truly distributed +- The real choice is between **CP** and **AP** during partitions + +### System Classifications + +#### CP Systems (Consistency + Partition Tolerance) + +**Behavior during partition**: Refuses some requests to maintain consistency. + +**Examples**: +- MongoDB (with majority write concern) +- HBase +- Zookeeper +- etcd + +**Use when**: +- Correctness is paramount (financial systems) +- Stale reads are unacceptable +- Brief unavailability is tolerable + +#### AP Systems (Availability + Partition Tolerance) + +**Behavior during partition**: Continues serving requests, may return stale data. + +**Examples**: +- Cassandra +- DynamoDB +- CouchDB +- Riak + +**Use when**: +- High availability is critical +- Eventual consistency is acceptable +- Shopping carts, social media feeds + +#### CA Systems + +**Theoretical only**: Cannot exist in distributed systems because partitions are inevitable. + +Single-node databases are technically CA but aren't distributed. + +### PACELC Extension + +PACELC extends CAP to address normal operation: + +> If there is a **P**artition, choose between **A**vailability and **C**onsistency. +> **E**lse (normal operation), choose between **L**atency and **C**onsistency. + +| System | P: A or C | E: L or C | +|--------|-----------|-----------| +| DynamoDB | A | L | +| Cassandra | A | L | +| MongoDB | C | C | +| PNUTS | C | L | + +## Consistency Models + +### Strong Consistency + +Every read returns the most recent write. Achieved through: +- Single leader with synchronous replication +- Consensus protocols (Paxos, Raft) + +**Trade-off**: Higher latency, lower availability during failures. + +### Eventual Consistency + +If no new updates, all replicas eventually converge to the same state. + +**Variants**: +- **Causal consistency**: Preserves causally related operations order +- **Read-your-writes**: Clients see their own writes +- **Monotonic reads**: Never see older data after seeing newer +- **Session consistency**: Consistency within a session + +### Linearizability + +Operations appear instantaneous at some point between invocation and response. + +**Provides**: +- Single-object operations appear atomic +- Real-time ordering guarantees +- Foundation for distributed locks, leader election + +### Serializability + +Transactions appear to execute in some serial order. + +**Note**: Linearizability ≠ Serializability +- Linearizability: Single-operation recency guarantee +- Serializability: Multi-operation isolation guarantee + +## Consensus Protocols + +### The Consensus Problem + +Getting distributed nodes to agree on a single value despite failures. + +**Requirements**: +1. **Agreement**: All correct nodes decide on the same value +2. **Validity**: Decided value was proposed by some node +3. **Termination**: All correct nodes eventually decide + +### Paxos + +Developed by Leslie Lamport (1989/1998), foundational consensus algorithm. + +#### Roles + +- **Proposers**: Propose values +- **Acceptors**: Vote on proposals +- **Learners**: Learn decided values + +#### Basic Protocol (Single-Decree) + +**Phase 1a: Prepare** +``` +Proposer → Acceptors: PREPARE(n) + - n is unique proposal number +``` + +**Phase 1b: Promise** +``` +Acceptor → Proposer: PROMISE(n, accepted_proposal) + - If n > highest_seen: promise to ignore lower proposals + - Return previously accepted proposal if any +``` + +**Phase 2a: Accept** +``` +Proposer → Acceptors: ACCEPT(n, v) + - v = value from highest accepted proposal, or proposer's own value +``` + +**Phase 2b: Accepted** +``` +Acceptor → Learners: ACCEPTED(n, v) + - If n >= highest_promised: accept the proposal +``` + +**Decision**: Value is decided when majority of acceptors accept it. + +#### Multi-Paxos + +Optimization for sequences of values: +- Elect stable leader +- Skip Phase 1 for subsequent proposals +- Significantly reduces message complexity + +#### Strengths and Weaknesses + +**Strengths**: +- Proven correct +- Tolerates f failures with 2f+1 nodes +- Foundation for many systems + +**Weaknesses**: +- Complex to implement correctly +- No specified leader election +- Performance requires Multi-Paxos optimizations + +### Raft + +Designed by Diego Ongaro and John Ousterhout (2013) for understandability. + +#### Key Design Principles + +1. **Decomposition**: Separates leader election, log replication, safety +2. **State reduction**: Minimizes states to consider +3. **Strong leader**: All writes through leader + +#### Server States + +- **Leader**: Handles all client requests, replicates log +- **Follower**: Passive, responds to leader and candidates +- **Candidate**: Trying to become leader + +#### Leader Election + +``` +1. Follower times out (no heartbeat from leader) +2. Becomes Candidate, increments term, votes for self +3. Requests votes from other servers +4. Wins with majority votes → becomes Leader +5. Loses (another leader) → becomes Follower +6. Timeout → starts new election +``` + +**Safety**: Only candidates with up-to-date logs can win. + +#### Log Replication + +``` +1. Client sends command to Leader +2. Leader appends to local log +3. Leader sends AppendEntries to Followers +4. On majority acknowledgment: entry is committed +5. Leader applies to state machine, responds to client +6. Followers apply committed entries +``` + +#### Log Matching Property + +If two logs contain entry with same index and term: +- Entries are identical +- All preceding entries are identical + +#### Term + +Logical clock that increases with each election: +- Detects stale leaders +- Resolves conflicts +- Included in all messages + +#### Comparison with Paxos + +| Aspect | Paxos | Raft | +|--------|-------|------| +| Understandability | Complex | Designed for clarity | +| Leader | Optional (Multi-Paxos) | Required | +| Log gaps | Allowed | Not allowed | +| Membership changes | Complex | Joint consensus | +| Implementations | Many variants | Consistent | + +### PBFT (Practical Byzantine Fault Tolerance) + +Developed by Castro and Liskov (1999) for Byzantine faults. + +#### Byzantine Faults + +Nodes can behave arbitrarily: +- Crash +- Send incorrect messages +- Collude maliciously +- Act inconsistently to different nodes + +#### Fault Tolerance + +Tolerates f Byzantine faults with **3f+1** nodes. + +**Why 3f+1?** +- Need 2f+1 honest responses +- f Byzantine nodes might lie +- Need f more to distinguish honest majority + +#### Protocol Phases + +**Normal Operation** (leader is honest): + +``` +1. REQUEST: Client → Primary (leader) +2. PRE-PREPARE: Primary → All replicas + - Primary assigns sequence number +3. PREPARE: Each replica → All replicas + - Validates pre-prepare +4. COMMIT: Each replica → All replicas + - After receiving 2f+1 prepares +5. REPLY: Each replica → Client + - After receiving 2f+1 commits +``` + +**Client waits for f+1 matching replies**. + +#### View Change + +When primary appears faulty: +1. Replicas timeout waiting for primary +2. Broadcast VIEW-CHANGE with prepared certificates +3. New primary collects 2f+1 view-changes +4. Broadcasts NEW-VIEW with proof +5. System resumes with new primary + +#### Message Complexity + +- **Normal case**: O(n²) messages per request +- **View change**: O(n³) messages + +**Scalability challenge**: Quadratic messaging limits cluster size. + +#### Optimizations + +- **Speculative execution**: Execute before commit +- **Batching**: Group multiple requests +- **Signatures**: Use MACs instead of digital signatures +- **Threshold signatures**: Reduce signature overhead + +### Modern BFT Variants + +#### HotStuff (2019) + +- Linear message complexity O(n) +- Used in LibraBFT (Diem), other blockchains +- Three-phase protocol with threshold signatures + +#### Tendermint + +- Blockchain-focused BFT +- Integrated with Cosmos SDK +- Immediate finality + +#### QBFT (Quorum BFT) + +- Enterprise-focused (ConsenSys/JPMorgan) +- Enhanced IBFT for Ethereum-based systems + +### Nakamoto Consensus + +The consensus mechanism powering Bitcoin, introduced by Satoshi Nakamoto (2008). + +#### Core Innovation + +Combines three elements: +1. **Proof-of-Work (PoW)**: Cryptographic puzzle for block creation +2. **Longest Chain Rule**: Fork resolution by accumulated work +3. **Probabilistic Finality**: Security increases with confirmations + +#### How It Works + +``` +1. Transactions broadcast to network +2. Miners collect transactions into blocks +3. Miners race to solve PoW puzzle: + - Find nonce such that Hash(block_header) < target + - Difficulty adjusts to maintain ~10 min block time +4. First miner to solve broadcasts block +5. Other nodes verify and append to longest chain +6. Miner receives block reward + transaction fees +``` + +#### Longest Chain Rule + +When forks occur: +``` +Chain A: [genesis] → [1] → [2] → [3] +Chain B: [genesis] → [1] → [2'] → [3'] → [4'] + +Nodes follow Chain B (more accumulated work) +Chain A blocks become "orphaned" +``` + +**Note**: Actually "most accumulated work" not "most blocks"—a chain with fewer but harder blocks wins. + +#### Security Model + +**Honest Majority Assumption**: Protocol secure if honest mining power > 50%. + +Formal analysis (Ren 2019): +``` +Safe if: g²α > β + +Where: + α = honest mining rate + β = adversarial mining rate + g = growth rate accounting for network delay + Δ = maximum network delay +``` + +**Implications**: +- Larger block interval → more security margin +- Higher network delay → need more honest majority +- 10-minute block time provides safety margin for global network + +#### Probabilistic Finality + +No instant finality—deeper blocks are exponentially harder to reverse: + +| Confirmations | Attack Probability (30% attacker) | +|---------------|-----------------------------------| +| 1 | ~50% | +| 3 | ~12% | +| 6 | ~0.2% | +| 12 | ~0.003% | + +**Convention**: 6 confirmations (~1 hour) considered "final" for Bitcoin. + +#### Attacks + +**51% Attack**: Attacker with majority hashrate can: +- Double-spend transactions +- Prevent confirmations +- NOT: steal funds, change consensus rules, create invalid transactions + +**Selfish Mining**: Strategic block withholding to waste honest miners' work. +- Profitable with < 50% hashrate under certain conditions +- Mitigated by network propagation improvements + +**Long-Range Attacks**: Not applicable to PoW (unlike PoS). + +#### Trade-offs vs Traditional BFT + +| Aspect | Nakamoto | Classical BFT | +|--------|----------|---------------| +| Finality | Probabilistic | Immediate | +| Throughput | Low (~7 TPS) | Higher | +| Participants | Permissionless | Permissioned | +| Energy | High (PoW) | Low | +| Fault tolerance | 50% hashrate | 33% nodes | +| Scalability | Global | Limited nodes | + +### PnyxDB: Leaderless Democratic BFT + +Developed by Bonniot, Neumann, and Taïani (2019) for consortia applications. + +#### Key Innovation: Conditional Endorsements + +Unlike leader-based BFT, PnyxDB uses **leaderless quorums** with conditional endorsements: +- Endorsements track conflicts between transactions +- If transactions commute (no conflicting operations), quorums built independently +- Non-commuting transactions handled via Byzantine Veto Procedure (BVP) + +#### Transaction Lifecycle + +``` +1. Client broadcasts transaction to endorsers +2. Endorsers evaluate against application-defined policies +3. If no conflicts: endorser sends acknowledgment +4. If conflicts detected: conditional endorsement specifying + which transactions must NOT be committed for this to be valid +5. Transaction commits when quorum of valid endorsements collected +6. BVP resolves conflicting transactions +``` + +#### Byzantine Veto Procedure (BVP) + +Ensures termination with conflicting transactions: +- Transactions have deadlines +- Conflicting endorsements trigger resolution loop +- Protocol guarantees exit when deadline passes +- At most f Byzantine nodes tolerated with n endorsers + +#### Application-Level Voting + +Unique feature: nodes can endorse or reject transactions based on **application-defined policies** without compromising consistency. + +Use cases: +- Consortium governance decisions +- Policy-based access control +- Democratic decision making + +#### Performance + +Compared to BFT-SMaRt and Tendermint: +- **11x faster** commit latencies +- **< 5 seconds** in worldwide geo-distributed deployment +- Tested with **180 nodes** + +#### Implementation + +- Written in Go (requires Go 1.11+) +- Uses gossip broadcast for message propagation +- Web-of-trust node authentication +- Scales to hundreds/thousands of nodes + +## Replication Strategies + +### Single-Leader Replication + +``` +Clients → Leader → Followers +``` + +**Pros**: Simple, strong consistency possible +**Cons**: Leader bottleneck, failover complexity + +#### Synchronous vs Asynchronous + +| Type | Durability | Latency | Availability | +|------|------------|---------|--------------| +| Synchronous | Guaranteed | High | Lower | +| Asynchronous | At-risk | Low | Higher | +| Semi-synchronous | Balanced | Medium | Medium | + +### Multi-Leader Replication + +Multiple nodes accept writes, replicate to each other. + +**Use cases**: +- Multi-datacenter deployment +- Clients with offline operation + +**Challenges**: +- Write conflicts +- Conflict resolution complexity + +#### Conflict Resolution + +- **Last-write-wins (LWW)**: Timestamp-based, may lose data +- **Application-specific**: Custom merge logic +- **CRDTs**: Mathematically guaranteed convergence + +### Leaderless Replication + +Any node can accept reads and writes. + +**Examples**: Dynamo, Cassandra, Riak + +#### Quorum Reads/Writes + +``` +n = total replicas +w = write quorum (nodes that must acknowledge write) +r = read quorum (nodes that must respond to read) + +For strong consistency: w + r > n +``` + +**Common configurations**: +- n=3, w=2, r=2: Tolerates 1 failure +- n=5, w=3, r=3: Tolerates 2 failures + +#### Sloppy Quorums and Hinted Handoff + +During partitions: +- Write to available nodes (even if not home replicas) +- "Hints" stored for unavailable nodes +- Hints replayed when nodes recover + +## Failure Modes + +### Crash Failures + +Node stops responding. Simplest failure model. + +**Detection**: Heartbeats, timeouts +**Tolerance**: 2f+1 nodes for f failures (Paxos, Raft) + +### Byzantine Failures + +Arbitrary behavior including malicious. + +**Detection**: Difficult without redundancy +**Tolerance**: 3f+1 nodes for f failures (PBFT) + +### Network Partitions + +Nodes can't communicate with some other nodes. + +**Impact**: Forces CP vs AP choice +**Recovery**: Reconciliation after partition heals + +### Split Brain + +Multiple nodes believe they are leader. + +**Prevention**: +- Fencing (STONITH: Shoot The Other Node In The Head) +- Quorum-based leader election +- Lease-based leadership + +## Design Patterns + +### State Machine Replication + +Replicate deterministic state machine across nodes: +1. All replicas start in same state +2. Apply same commands in same order +3. All reach same final state + +**Requires**: Total order broadcast (consensus) + +### Chain Replication + +``` +Head → Node2 → Node3 → ... → Tail +``` + +- Writes enter at head, propagate down chain +- Reads served by tail (strongly consistent) +- Simple, high throughput + +### Primary-Backup + +Primary handles all operations, synchronously replicates to backups. + +**Failover**: Backup promoted to primary on failure. + +### Quorum Systems + +Intersecting sets ensure consistency: +- Any read quorum intersects any write quorum +- Guarantees reads see latest write + +## Balancing Trade-offs + +### Identifying Critical Requirements + +1. **Correctness requirements** + - Is data loss acceptable? + - Can operations be reordered? + - Are conflicts resolvable? + +2. **Availability requirements** + - What's acceptable downtime? + - Geographic distribution needs? + - Partition recovery strategy? + +3. **Performance requirements** + - Latency targets? + - Throughput needs? + - Consistency cost tolerance? + +### Vulnerability Mitigation by Protocol + +#### Paxos/Raft (Crash Fault Tolerant) + +**Vulnerabilities**: +- Leader failure causes brief unavailability +- Split-brain without proper fencing +- Slow follower impacts commit latency (sync replication) + +**Mitigations**: +- Fast leader election (pre-voting) +- Quorum-based fencing +- Flexible quorum configurations +- Learner nodes for read scaling + +#### PBFT (Byzantine Fault Tolerant) + +**Vulnerabilities**: +- O(n²) messages limit scalability +- View change is expensive +- Requires 3f+1 nodes (more infrastructure) + +**Mitigations**: +- Batching and pipelining +- Optimistic execution (HotStuff) +- Threshold signatures +- Hierarchical consensus for scaling + +### Choosing the Right Protocol + +| Scenario | Recommended | Rationale | +|----------|-------------|-----------| +| Internal infrastructure | Raft | Simple, well-understood | +| High consistency needs | Raft/Paxos | Proven correctness | +| Public/untrusted network | PBFT variant | Byzantine tolerance | +| Blockchain | HotStuff/Tendermint | Linear complexity BFT | +| Eventually consistent | Dynamo-style | High availability | +| Global distribution | Multi-leader + CRDTs | Partition tolerance | + +## Implementation Considerations + +### Timeouts + +- **Heartbeat interval**: 100-300ms typical +- **Election timeout**: 10x heartbeat (avoid split votes) +- **Request timeout**: Application-dependent + +### Persistence + +What must be persisted before acknowledgment: +- **Raft**: Current term, voted-for, log entries +- **PBFT**: View number, prepared/committed certificates + +### Membership Changes + +Dynamic cluster membership: +- **Raft**: Joint consensus (old + new config) +- **Paxos**: α-reconfiguration +- **PBFT**: View change with new configuration + +### Testing + +- **Jepsen**: Distributed systems testing framework +- **Chaos engineering**: Intentional failure injection +- **Formal verification**: TLA+, Coq proofs + +## Adversarial Oracle Protocols + +Oracles bridge on-chain smart contracts with off-chain data, but introduce trust assumptions into trustless systems. + +### The Oracle Problem + +**Definition**: The security, authenticity, and trust conflict between third-party oracles and the trustless execution of smart contracts. + +**Core Challenge**: Blockchains cannot verify correctness of external data. Oracles become: +- Single points of failure +- Targets for manipulation +- Trust assumptions in "trustless" systems + +### Attack Vectors + +#### Price Oracle Manipulation + +**Flash Loan Attacks**: +``` +1. Borrow large amount via flash loan (no collateral) +2. Manipulate price on DEX (large trade) +3. Oracle reads manipulated price +4. Smart contract executes with wrong price +5. Profit from arbitrage/liquidation +6. Repay flash loan in same transaction +``` + +**Notable Example**: Harvest Finance ($30M+ loss, 2020) + +#### Data Source Attacks + +- **Compromised API**: Single data source manipulation +- **Front-running**: Oracle updates exploited before on-chain +- **Liveness attacks**: Preventing oracle updates +- **Bribery**: Incentivizing oracle operators to lie + +#### Economic Attacks + +**Cost of Corruption Analysis**: +``` +If oracle controls value V: + - Attack profit: V + - Attack cost: oracle stake + reputation + - Rational to attack if: profit > cost +``` + +**Implication**: Oracles must have stake > value they secure. + +### Decentralized Oracle Networks (DONs) + +#### Chainlink Model + +**Multi-layer Security**: +``` +1. Multiple independent data sources +2. Multiple independent node operators +3. Aggregation (median, weighted average) +4. Reputation system +5. Cryptoeconomic incentives (staking) +``` + +**Data Aggregation**: +``` +Nodes: [Oracle₁: $100, Oracle₂: $101, Oracle₃: $150, Oracle₄: $100] +Median: $100.50 +Outlier (Oracle₃) has minimal impact +``` + +#### Reputation and Staking + +``` +Node reputation based on: + - Historical accuracy + - Response time + - Uptime + - Stake amount + +Job assignment weighted by reputation +Slashing for misbehavior +``` + +### Oracle Design Patterns + +#### Time-Weighted Average Price (TWAP) + +Resist single-block manipulation: +``` +TWAP = Σ(price_i × duration_i) / total_duration + +Example over 1 hour: + - 30 min at $100: 30 × 100 = 3000 + - 20 min at $101: 20 × 101 = 2020 + - 10 min at $150 (manipulation): 10 × 150 = 1500 + TWAP = 6520 / 60 = $108.67 (vs $150 spot) +``` + +#### Commit-Reveal Schemes + +Prevent front-running oracle updates: +``` +Phase 1 (Commit): + - Oracle commits: hash(price || salt) + - Cannot be read by others + +Phase 2 (Reveal): + - Oracle reveals: price, salt + - Contract verifies hash matches + - All oracles reveal simultaneously +``` + +#### Schelling Points + +Game-theoretic oracle coordination: +``` +1. Multiple oracles submit answers +2. Consensus answer determined +3. Oracles matching consensus rewarded +4. Outliers penalized + +Assumption: Honest answer is "obvious" Schelling point +``` + +### Trusted Execution Environments (TEEs) + +Hardware-based oracle security: +``` +TEE (Intel SGX, ARM TrustZone): + - Isolated execution environment + - Code attestation + - Protected memory + - External data fetching inside enclave +``` + +**Benefits**: +- Verifiable computation +- Protected from host machine +- Cryptographic proofs of execution + +**Limitations**: +- Hardware trust assumption +- Side-channel attacks possible +- Intel SGX vulnerabilities discovered + +### Oracle Types by Data Source + +| Type | Source | Trust Model | Use Case | +|------|--------|-------------|----------| +| Price feeds | Exchanges | Multiple sources | DeFi | +| Randomness | VRF/DRAND | Cryptographic | Gaming, NFTs | +| Event outcomes | Manual report | Reputation | Prediction markets | +| Cross-chain | Other blockchains | Bridge security | Interoperability | +| Computation | Off-chain compute | Verifiable | Complex logic | + +### Defense Mechanisms + +1. **Diversification**: Multiple independent oracles +2. **Economic security**: Stake > protected value +3. **Time delays**: Allow dispute periods +4. **Circuit breakers**: Pause on anomalous data +5. **TWAP**: Resist flash manipulation +6. **Commit-reveal**: Prevent front-running +7. **Reputation**: Long-term incentives + +### Hybrid Approaches + +**Optimistic Oracles**: +``` +1. Oracle posts answer + bond +2. Dispute window (e.g., 2 hours) +3. If disputed: escalate to arbitration +4. If not disputed: answer accepted +5. Incorrect oracle loses bond +``` + +**Examples**: UMA Protocol, Optimistic Oracle + +## Causality and Logical Clocks + +Physical clocks cannot reliably order events in distributed systems due to clock drift and synchronization issues. Logical clocks provide ordering based on causality. + +### The Happened-Before Relation + +Defined by Leslie Lamport (1978): + +Event a **happened-before** event b (a → b) if: +1. a and b are in the same process, and a comes before b +2. a is a send event and b is the corresponding receive +3. There exists c such that a → c and c → b (transitivity) + +If neither a → b nor b → a, events are **concurrent** (a || b). + +### Lamport Clocks + +Simple scalar timestamps providing partial ordering. + +**Rules**: +``` +1. Each process maintains counter C +2. Before each event: C = C + 1 +3. Send message m with timestamp C +4. On receive: C = max(C, message_timestamp) + 1 +``` + +**Properties**: +- If a → b, then C(a) < C(b) +- **Limitation**: C(a) < C(b) does NOT imply a → b +- Cannot detect concurrent events + +**Use cases**: +- Total ordering with tie-breaker (process ID) +- Distributed snapshots +- Simple event ordering + +### Vector Clocks + +Array of counters, one per process. Captures full causality. + +**Structure** (for n processes): +``` +VC[1..n] where VC[i] is process i's logical time +``` + +**Rules** (at process i): +``` +1. Before each event: VC[i] = VC[i] + 1 +2. Send message with full vector VC +3. On receive from j: + for k in 1..n: + VC[k] = max(VC[k], received_VC[k]) + VC[i] = VC[i] + 1 +``` + +**Comparison** (for vectors V1 and V2): +``` +V1 = V2 iff ∀i: V1[i] = V2[i] +V1 ≤ V2 iff ∀i: V1[i] ≤ V2[i] +V1 < V2 iff V1 ≤ V2 and V1 ≠ V2 +V1 || V2 iff NOT(V1 ≤ V2) and NOT(V2 ≤ V1) # concurrent +``` + +**Properties**: +- a → b iff VC(a) < VC(b) +- a || b iff VC(a) || VC(b) +- **Full causality detection** + +**Trade-off**: O(n) space per event, where n = number of processes. + +### Interval Tree Clocks (ITC) + +Developed by Almeida, Baquero, and Fonte (2008) for dynamic systems. + +**Problem with Vector Clocks**: +- Static: size fixed to max number of processes +- ID retirement requires global coordination +- Unsuitable for high-churn systems (P2P) + +**ITC Solution**: +- Binary tree structure for ID space +- Dynamic ID allocation and deallocation +- Localized fork/join operations + +**Core Operations**: + +``` +fork(id): Split ID into two children + - Parent retains left half + - New process gets right half + +join(id1, id2): Merge two IDs + - Combine ID trees + - Localized operation, no global coordination + +event(id, stamp): Increment logical clock +peek(id, stamp): Read without increment +``` + +**ID Space Representation**: +``` + 1 # Full ID space + / \ + 0 1 # After one fork + / \ + 0 1 # After another fork (left child) +``` + +**Stamp (Clock) Representation**: +- Tree structure mirrors ID space +- Each node has base value + optional children +- Efficient representation of sparse vectors + +**Example**: +``` +Initial: id=(1), stamp=0 +Fork: id1=(1,0), stamp1=0 + id2=(0,1), stamp2=0 +Event at id1: stamp1=(0,(1,0)) +Join id1+id2: id=(1), stamp=max of both +``` + +**Advantages over Vector Clocks**: +- Constant-size representation possible +- Dynamic membership without global state +- Efficient ID garbage collection +- Causality preserved across reconfigurations + +**Use cases**: +- Peer-to-peer systems +- Mobile/ad-hoc networks +- Systems with frequent node join/leave + +### Version Vectors + +Specialization of vector clocks for tracking data versions. + +**Difference from Vector Clocks**: +- Vector clocks: track all events +- Version vectors: track data updates only + +**Usage in Dynamo-style systems**: +``` +Client reads with version vector V1 +Client writes with version vector V2 +Server compares: + - If V1 < current: stale read, conflict possible + - If V1 = current: safe update + - If V1 || current: concurrent writes, need resolution +``` + +### Hybrid Logical Clocks (HLC) + +Combines physical and logical time. + +**Structure**: +``` +HLC = (physical_time, logical_counter) +``` + +**Rules**: +``` +1. On local/send event: + pt = physical_clock() + if pt > l: + l = pt + c = 0 + else: + c = c + 1 + return (l, c) + +2. On receive with timestamp (l', c'): + pt = physical_clock() + if pt > l and pt > l': + l = pt + c = 0 + elif l' > l: + l = l' + c = c' + 1 + elif l > l': + c = c + 1 + else: # l = l' + c = max(c, c') + 1 + return (l, c) +``` + +**Properties**: +- Bounded drift from physical time +- Captures causality like Lamport clocks +- Timestamps comparable to wall-clock time +- Used in CockroachDB, Google Spanner + +### Comparison of Logical Clocks + +| Clock Type | Space | Causality | Concurrency | Dynamic | +|------------|-------|-----------|-------------|---------| +| Lamport | O(1) | Partial | No | Yes | +| Vector | O(n) | Full | Yes | No | +| ITC | O(log n)* | Full | Yes | Yes | +| HLC | O(1) | Partial | No | Yes | + +*ITC space varies based on tree structure + +### Practical Applications + +**Conflict Detection** (Vector Clocks): +``` +if V1 < V2: + # v1 is ancestor of v2, no conflict +elif V1 > V2: + # v2 is ancestor of v1, no conflict +else: # V1 || V2 + # Concurrent updates, need conflict resolution +``` + +**Causal Broadcast**: +``` +Deliver message m with VC only when: +1. VC[sender] = local_VC[sender] + 1 (next expected from sender) +2. ∀j ≠ sender: VC[j] ≤ local_VC[j] (all causal deps satisfied) +``` + +**Snapshot Algorithms**: +``` +Consistent cut: set of events S where + if e ∈ S and f → e, then f ∈ S +Vector clocks make this efficiently verifiable +``` + +## References + +For detailed protocol specifications and proofs, see: +- `references/consensus-protocols.md` - Detailed protocol descriptions +- `references/consistency-models.md` - Formal consistency definitions +- `references/failure-scenarios.md` - Failure mode analysis +- `references/logical-clocks.md` - Clock algorithms and implementations diff --git a/.claude/skills/distributed-systems/references/consensus-protocols.md b/.claude/skills/distributed-systems/references/consensus-protocols.md new file mode 100644 index 0000000..a3bfb07 --- /dev/null +++ b/.claude/skills/distributed-systems/references/consensus-protocols.md @@ -0,0 +1,610 @@ +# Consensus Protocols - Detailed Reference + +Complete specifications and implementation details for major consensus protocols. + +## Paxos Complete Specification + +### Proposal Numbers + +Proposal numbers must be: +- **Unique**: No two proposers use the same number +- **Totally ordered**: Any two can be compared + +**Implementation**: `(round_number, proposer_id)` where proposer_id breaks ties. + +### Single-Decree Paxos State + +**Proposer state**: +``` +proposal_number: int +value: any +``` + +**Acceptor state (persistent)**: +``` +highest_promised: int # Highest proposal number promised +accepted_proposal: int # Number of accepted proposal (0 if none) +accepted_value: any # Value of accepted proposal (null if none) +``` + +### Message Format + +**Prepare** (Phase 1a): +``` +{ + type: "PREPARE", + proposal_number: n +} +``` + +**Promise** (Phase 1b): +``` +{ + type: "PROMISE", + proposal_number: n, + accepted_proposal: m, # null if nothing accepted + accepted_value: v # null if nothing accepted +} +``` + +**Accept** (Phase 2a): +``` +{ + type: "ACCEPT", + proposal_number: n, + value: v +} +``` + +**Accepted** (Phase 2b): +``` +{ + type: "ACCEPTED", + proposal_number: n, + value: v +} +``` + +### Proposer Algorithm + +``` +function propose(value): + n = generate_proposal_number() + + # Phase 1: Prepare + promises = [] + for acceptor in acceptors: + send PREPARE(n) to acceptor + + wait until |promises| > |acceptors|/2 or timeout + + if timeout: + return FAILED + + # Choose value + highest = max(promises, key=p.accepted_proposal) + if highest.accepted_value is not null: + value = highest.accepted_value + + # Phase 2: Accept + accepts = [] + for acceptor in acceptors: + send ACCEPT(n, value) to acceptor + + wait until |accepts| > |acceptors|/2 or timeout + + if timeout: + return FAILED + + return SUCCESS(value) +``` + +### Acceptor Algorithm + +``` +on receive PREPARE(n): + if n > highest_promised: + highest_promised = n + persist(highest_promised) + reply PROMISE(n, accepted_proposal, accepted_value) + else: + # Optionally reply NACK(highest_promised) + ignore or reject + +on receive ACCEPT(n, v): + if n >= highest_promised: + highest_promised = n + accepted_proposal = n + accepted_value = v + persist(highest_promised, accepted_proposal, accepted_value) + reply ACCEPTED(n, v) + else: + ignore or reject +``` + +### Multi-Paxos Optimization + +**Stable leader**: +``` +# Leader election (using Paxos or other method) +leader = elect_leader() + +# Leader's Phase 1 for all future instances +leader sends PREPARE(n) for instance range [i, ∞) + +# For each command: +function propose_as_leader(value, instance): + # Skip Phase 1 if already leader + for acceptor in acceptors: + send ACCEPT(n, value, instance) to acceptor + wait for majority ACCEPTED + return SUCCESS +``` + +### Paxos Safety Proof Sketch + +**Invariant**: If a value v is chosen for instance i, no other value can be chosen. + +**Proof**: +1. Value chosen → accepted by majority with proposal n +2. Any higher proposal n' must contact majority +3. Majorities intersect → at least one acceptor has accepted v +4. New proposer adopts v (or higher already-accepted value) +5. By induction, all future proposals use v + +## Raft Complete Specification + +### State + +**All servers (persistent)**: +``` +currentTerm: int # Latest term seen +votedFor: ServerId # Candidate voted for in current term (null if none) +log[]: LogEntry # Log entries +``` + +**All servers (volatile)**: +``` +commitIndex: int # Highest log index known to be committed +lastApplied: int # Highest log index applied to state machine +``` + +**Leader (volatile, reinitialized after election)**: +``` +nextIndex[]: int # For each server, next log index to send +matchIndex[]: int # For each server, highest log index replicated +``` + +**LogEntry**: +``` +{ + term: int, + command: any +} +``` + +### RequestVote RPC + +**Request**: +``` +{ + term: int, # Candidate's term + candidateId: ServerId, # Candidate requesting vote + lastLogIndex: int, # Index of candidate's last log entry + lastLogTerm: int # Term of candidate's last log entry +} +``` + +**Response**: +``` +{ + term: int, # currentTerm, for candidate to update itself + voteGranted: bool # True if candidate received vote +} +``` + +**Receiver implementation**: +``` +on receive RequestVote(term, candidateId, lastLogIndex, lastLogTerm): + if term < currentTerm: + return {term: currentTerm, voteGranted: false} + + if term > currentTerm: + currentTerm = term + votedFor = null + convert to follower + + # Check if candidate's log is at least as up-to-date as ours + ourLastTerm = log[len(log)-1].term if log else 0 + ourLastIndex = len(log) - 1 + + logOK = (lastLogTerm > ourLastTerm) or + (lastLogTerm == ourLastTerm and lastLogIndex >= ourLastIndex) + + if (votedFor is null or votedFor == candidateId) and logOK: + votedFor = candidateId + persist(currentTerm, votedFor) + reset election timer + return {term: currentTerm, voteGranted: true} + + return {term: currentTerm, voteGranted: false} +``` + +### AppendEntries RPC + +**Request**: +``` +{ + term: int, # Leader's term + leaderId: ServerId, # For follower to redirect clients + prevLogIndex: int, # Index of log entry preceding new ones + prevLogTerm: int, # Term of prevLogIndex entry + entries[]: LogEntry, # Log entries to store (empty for heartbeat) + leaderCommit: int # Leader's commitIndex +} +``` + +**Response**: +``` +{ + term: int, # currentTerm, for leader to update itself + success: bool # True if follower had matching prevLog entry +} +``` + +**Receiver implementation**: +``` +on receive AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries, leaderCommit): + if term < currentTerm: + return {term: currentTerm, success: false} + + reset election timer + + if term > currentTerm: + currentTerm = term + votedFor = null + + convert to follower + + # Check log consistency + if prevLogIndex >= len(log) or + (prevLogIndex >= 0 and log[prevLogIndex].term != prevLogTerm): + return {term: currentTerm, success: false} + + # Append new entries (handling conflicts) + for i, entry in enumerate(entries): + index = prevLogIndex + 1 + i + if index < len(log): + if log[index].term != entry.term: + # Delete conflicting entry and all following + log = log[:index] + log.append(entry) + else: + log.append(entry) + + persist(currentTerm, votedFor, log) + + # Update commit index + if leaderCommit > commitIndex: + commitIndex = min(leaderCommit, len(log) - 1) + + return {term: currentTerm, success: true} +``` + +### Leader Behavior + +``` +on becoming leader: + for each server: + nextIndex[server] = len(log) + matchIndex[server] = 0 + + start sending heartbeats + +on receiving client command: + append entry to local log + persist log + send AppendEntries to all followers + +on receiving AppendEntries response from server: + if response.success: + matchIndex[server] = prevLogIndex + len(entries) + nextIndex[server] = matchIndex[server] + 1 + + # Update commit index + for N from commitIndex+1 to len(log)-1: + if log[N].term == currentTerm and + |{s : matchIndex[s] >= N}| > |servers|/2: + commitIndex = N + else: + nextIndex[server] = max(1, nextIndex[server] - 1) + retry AppendEntries with lower prevLogIndex + +on commitIndex update: + while lastApplied < commitIndex: + lastApplied++ + apply log[lastApplied].command to state machine +``` + +### Election Timeout + +``` +on election timeout (follower or candidate): + currentTerm++ + convert to candidate + votedFor = self + persist(currentTerm, votedFor) + reset election timer + votes = 1 # Vote for self + + for each server except self: + send RequestVote(currentTerm, self, lastLogIndex, lastLogTerm) + + wait for responses or timeout: + if received votes > |servers|/2: + become leader + if received AppendEntries from valid leader: + become follower + if timeout: + start new election +``` + +## PBFT Complete Specification + +### Message Types + +**REQUEST**: +``` +{ + type: "REQUEST", + operation: o, # Operation to execute + timestamp: t, # Client timestamp (for reply matching) + client: c # Client identifier +} +``` + +**PRE-PREPARE**: +``` +{ + type: "PRE-PREPARE", + view: v, # Current view number + sequence: n, # Sequence number + digest: d, # Hash of request + request: m # The request message +} +signature(primary) +``` + +**PREPARE**: +``` +{ + type: "PREPARE", + view: v, + sequence: n, + digest: d, + replica: i # Sending replica +} +signature(replica_i) +``` + +**COMMIT**: +``` +{ + type: "COMMIT", + view: v, + sequence: n, + digest: d, + replica: i +} +signature(replica_i) +``` + +**REPLY**: +``` +{ + type: "REPLY", + view: v, + timestamp: t, + client: c, + replica: i, + result: r # Execution result +} +signature(replica_i) +``` + +### Replica State + +``` +view: int # Current view +sequence: int # Last assigned sequence number (primary) +log[]: {request, prepares, commits, state} # Log of requests +prepared_certificates: {} # Prepared certificates (2f+1 prepares) +committed_certificates: {} # Committed certificates (2f+1 commits) +h: int # Low water mark +H: int # High water mark (h + L) +``` + +### Normal Operation Protocol + +**Primary (replica p = v mod n)**: +``` +on receive REQUEST(m) from client: + if not primary for current view: + forward to primary + return + + n = assign_sequence_number() + d = hash(m) + + broadcast PRE-PREPARE(v, n, d, m) to all replicas + add to log +``` + +**All replicas**: +``` +on receive PRE-PREPARE(v, n, d, m) from primary: + if v != current_view: + ignore + if already accepted pre-prepare for (v, n) with different digest: + ignore + if not in_view_as_backup(v): + ignore + if not h < n <= H: + ignore # Outside sequence window + + # Valid pre-prepare + add to log + broadcast PREPARE(v, n, d, i) to all replicas + +on receive PREPARE(v, n, d, j) from replica j: + if v != current_view: + ignore + + add to log[n].prepares + + if |log[n].prepares| >= 2f and not already_prepared(v, n, d): + # Prepared certificate complete + mark as prepared + broadcast COMMIT(v, n, d, i) to all replicas + +on receive COMMIT(v, n, d, j) from replica j: + if v != current_view: + ignore + + add to log[n].commits + + if |log[n].commits| >= 2f + 1 and prepared(v, n, d): + # Committed certificate complete + if all entries < n are committed: + execute(m) + send REPLY(v, t, c, i, result) to client +``` + +### View Change Protocol + +**Timeout trigger**: +``` +on request timeout (no progress): + view_change_timeout++ + broadcast VIEW-CHANGE(v+1, n, C, P, i) + + where: + n = last stable checkpoint sequence number + C = checkpoint certificate (2f+1 checkpoint messages) + P = set of prepared certificates for messages after n +``` + +**VIEW-CHANGE**: +``` +{ + type: "VIEW-CHANGE", + view: v, # New view number + sequence: n, # Checkpoint sequence + checkpoints: C, # Checkpoint certificate + prepared: P, # Set of prepared certificates + replica: i +} +signature(replica_i) +``` + +**New primary (p' = v mod n)**: +``` +on receive 2f VIEW-CHANGE for view v: + V = set of valid view-change messages + + # Compute O: set of requests to re-propose + O = {} + for seq in max_checkpoint_seq(V) to max_seq(V): + if exists prepared certificate for seq in V: + O[seq] = request from certificate + else: + O[seq] = null-request # No-op + + broadcast NEW-VIEW(v, V, O) + + # Re-run protocol for requests in O + for seq, request in O: + if request != null: + send PRE-PREPARE(v, seq, hash(request), request) +``` + +**NEW-VIEW**: +``` +{ + type: "NEW-VIEW", + view: v, + view_changes: V, # 2f+1 view-change messages + pre_prepares: O # Set of pre-prepare messages +} +signature(primary) +``` + +### Checkpointing + +Periodic stable checkpoints to garbage collect logs: + +``` +every K requests: + state_hash = hash(state_machine_state) + broadcast CHECKPOINT(n, state_hash, i) + +on receive 2f+1 CHECKPOINT for (n, d): + if all digests match: + create stable checkpoint + h = n # Move low water mark + garbage_collect(entries < n) +``` + +## HotStuff Protocol + +Linear complexity BFT using threshold signatures. + +### Key Innovation + +- **Three-phase**: prepare → pre-commit → commit → decide +- **Pipelining**: Next proposal starts before current finishes +- **Threshold signatures**: O(n) total messages instead of O(n²) + +### Message Flow + +``` +Phase 1 (Prepare): + Leader: broadcast PREPARE(v, node) + Replicas: sign and send partial signature to leader + Leader: aggregate into prepare certificate QC + +Phase 2 (Pre-commit): + Leader: broadcast PRE-COMMIT(v, QC_prepare) + Replicas: sign and send partial signature + Leader: aggregate into pre-commit certificate + +Phase 3 (Commit): + Leader: broadcast COMMIT(v, QC_precommit) + Replicas: sign and send partial signature + Leader: aggregate into commit certificate + +Phase 4 (Decide): + Leader: broadcast DECIDE(v, QC_commit) + Replicas: execute and commit +``` + +### Pipelining + +``` +Block k: [prepare] [pre-commit] [commit] [decide] +Block k+1: [prepare] [pre-commit] [commit] [decide] +Block k+2: [prepare] [pre-commit] [commit] [decide] +``` + +Each phase of block k+1 piggybacks on messages for block k. + +## Protocol Comparison Matrix + +| Feature | Paxos | Raft | PBFT | HotStuff | +|---------|-------|------|------|----------| +| Fault model | Crash | Crash | Byzantine | Byzantine | +| Fault tolerance | f with 2f+1 | f with 2f+1 | f with 3f+1 | f with 3f+1 | +| Message complexity | O(n) | O(n) | O(n²) | O(n) | +| Leader required | No (helps) | Yes | Yes | Yes | +| Phases | 2 | 2 | 3 | 3 | +| View change | Complex | Simple | Complex | Simple | diff --git a/.claude/skills/distributed-systems/references/logical-clocks.md b/.claude/skills/distributed-systems/references/logical-clocks.md new file mode 100644 index 0000000..c8b09fa --- /dev/null +++ b/.claude/skills/distributed-systems/references/logical-clocks.md @@ -0,0 +1,610 @@ +# Logical Clocks - Implementation Reference + +Detailed implementations and algorithms for causality tracking. + +## Lamport Clock Implementation + +### Data Structure + +```go +type LamportClock struct { + counter uint64 + mu sync.Mutex +} + +func NewLamportClock() *LamportClock { + return &LamportClock{counter: 0} +} +``` + +### Operations + +```go +// Tick increments clock for local event +func (c *LamportClock) Tick() uint64 { + c.mu.Lock() + defer c.mu.Unlock() + c.counter++ + return c.counter +} + +// Send returns timestamp for outgoing message +func (c *LamportClock) Send() uint64 { + return c.Tick() +} + +// Receive updates clock based on incoming message timestamp +func (c *LamportClock) Receive(msgTime uint64) uint64 { + c.mu.Lock() + defer c.mu.Unlock() + + if msgTime > c.counter { + c.counter = msgTime + } + c.counter++ + return c.counter +} + +// Time returns current clock value without incrementing +func (c *LamportClock) Time() uint64 { + c.mu.Lock() + defer c.mu.Unlock() + return c.counter +} +``` + +### Usage Example + +```go +// Process A +clockA := NewLamportClock() +e1 := clockA.Tick() // Event 1: time=1 +msgTime := clockA.Send() // Send: time=2 + +// Process B +clockB := NewLamportClock() +e2 := clockB.Tick() // Event 2: time=1 +e3 := clockB.Receive(msgTime) // Receive: time=3 (max(1,2)+1) +``` + +## Vector Clock Implementation + +### Data Structure + +```go +type VectorClock struct { + clocks map[string]uint64 // processID -> logical time + self string // this process's ID + mu sync.RWMutex +} + +func NewVectorClock(processID string, allProcesses []string) *VectorClock { + clocks := make(map[string]uint64) + for _, p := range allProcesses { + clocks[p] = 0 + } + return &VectorClock{ + clocks: clocks, + self: processID, + } +} +``` + +### Operations + +```go +// Tick increments own clock +func (vc *VectorClock) Tick() map[string]uint64 { + vc.mu.Lock() + defer vc.mu.Unlock() + + vc.clocks[vc.self]++ + return vc.copy() +} + +// Send returns copy of vector for message +func (vc *VectorClock) Send() map[string]uint64 { + return vc.Tick() +} + +// Receive merges incoming vector and increments +func (vc *VectorClock) Receive(incoming map[string]uint64) map[string]uint64 { + vc.mu.Lock() + defer vc.mu.Unlock() + + // Merge: take max of each component + for pid, time := range incoming { + if time > vc.clocks[pid] { + vc.clocks[pid] = time + } + } + + // Increment own clock + vc.clocks[vc.self]++ + return vc.copy() +} + +// copy returns a copy of the vector +func (vc *VectorClock) copy() map[string]uint64 { + result := make(map[string]uint64) + for k, v := range vc.clocks { + result[k] = v + } + return result +} +``` + +### Comparison Functions + +```go +// Compare returns ordering relationship between two vectors +type Ordering int + +const ( + Equal Ordering = iota // V1 == V2 + HappenedBefore // V1 < V2 + HappenedAfter // V1 > V2 + Concurrent // V1 || V2 +) + +func Compare(v1, v2 map[string]uint64) Ordering { + less := false + greater := false + + // Get all keys + allKeys := make(map[string]bool) + for k := range v1 { + allKeys[k] = true + } + for k := range v2 { + allKeys[k] = true + } + + for k := range allKeys { + t1 := v1[k] // 0 if not present + t2 := v2[k] + + if t1 < t2 { + less = true + } + if t1 > t2 { + greater = true + } + } + + if !less && !greater { + return Equal + } + if less && !greater { + return HappenedBefore + } + if greater && !less { + return HappenedAfter + } + return Concurrent +} + +// IsConcurrent checks if two events are concurrent +func IsConcurrent(v1, v2 map[string]uint64) bool { + return Compare(v1, v2) == Concurrent +} + +// HappenedBefore checks if v1 -> v2 (v1 causally precedes v2) +func HappenedBefore(v1, v2 map[string]uint64) bool { + return Compare(v1, v2) == HappenedBefore +} +``` + +## Interval Tree Clock Implementation + +### Data Structures + +```go +// ID represents the identity tree +type ID struct { + IsLeaf bool + Value int // 0 or 1 for leaves + Left *ID // nil for leaves + Right *ID +} + +// Stamp represents the event tree +type Stamp struct { + Base int + Left *Stamp // nil for leaf stamps + Right *Stamp +} + +// ITC combines ID and Stamp +type ITC struct { + ID *ID + Stamp *Stamp +} +``` + +### ID Operations + +```go +// NewSeedID creates initial full ID (1) +func NewSeedID() *ID { + return &ID{IsLeaf: true, Value: 1} +} + +// Fork splits an ID into two +func (id *ID) Fork() (*ID, *ID) { + if id.IsLeaf { + if id.Value == 0 { + // Cannot fork zero ID + return &ID{IsLeaf: true, Value: 0}, + &ID{IsLeaf: true, Value: 0} + } + // Split full ID into left and right halves + return &ID{ + IsLeaf: false, + Left: &ID{IsLeaf: true, Value: 1}, + Right: &ID{IsLeaf: true, Value: 0}, + }, + &ID{ + IsLeaf: false, + Left: &ID{IsLeaf: true, Value: 0}, + Right: &ID{IsLeaf: true, Value: 1}, + } + } + + // Fork from non-leaf: give half to each + if id.Left.IsLeaf && id.Left.Value == 0 { + // Left is zero, fork right + newRight1, newRight2 := id.Right.Fork() + return &ID{IsLeaf: false, Left: id.Left, Right: newRight1}, + &ID{IsLeaf: false, Left: &ID{IsLeaf: true, Value: 0}, Right: newRight2} + } + if id.Right.IsLeaf && id.Right.Value == 0 { + // Right is zero, fork left + newLeft1, newLeft2 := id.Left.Fork() + return &ID{IsLeaf: false, Left: newLeft1, Right: id.Right}, + &ID{IsLeaf: false, Left: newLeft2, Right: &ID{IsLeaf: true, Value: 0}} + } + + // Both have IDs, split + return &ID{IsLeaf: false, Left: id.Left, Right: &ID{IsLeaf: true, Value: 0}}, + &ID{IsLeaf: false, Left: &ID{IsLeaf: true, Value: 0}, Right: id.Right} +} + +// Join merges two IDs +func Join(id1, id2 *ID) *ID { + if id1.IsLeaf && id1.Value == 0 { + return id2 + } + if id2.IsLeaf && id2.Value == 0 { + return id1 + } + if id1.IsLeaf && id2.IsLeaf && id1.Value == 1 && id2.Value == 1 { + return &ID{IsLeaf: true, Value: 1} + } + + // Normalize to non-leaf + left1 := id1.Left + right1 := id1.Right + left2 := id2.Left + right2 := id2.Right + + if id1.IsLeaf { + left1 = id1 + right1 = id1 + } + if id2.IsLeaf { + left2 = id2 + right2 = id2 + } + + newLeft := Join(left1, left2) + newRight := Join(right1, right2) + + return normalize(&ID{IsLeaf: false, Left: newLeft, Right: newRight}) +} + +func normalize(id *ID) *ID { + if !id.IsLeaf { + if id.Left.IsLeaf && id.Right.IsLeaf && + id.Left.Value == id.Right.Value { + return &ID{IsLeaf: true, Value: id.Left.Value} + } + } + return id +} +``` + +### Stamp Operations + +```go +// NewStamp creates initial stamp (0) +func NewStamp() *Stamp { + return &Stamp{Base: 0} +} + +// Event increments the stamp for the given ID +func Event(id *ID, stamp *Stamp) *Stamp { + if id.IsLeaf { + if id.Value == 1 { + return &Stamp{Base: stamp.Base + 1} + } + return stamp // Cannot increment with zero ID + } + + // Non-leaf ID: fill where we have ID + if id.Left.IsLeaf && id.Left.Value == 1 { + // Have left ID, increment left + newLeft := Event(&ID{IsLeaf: true, Value: 1}, getLeft(stamp)) + return normalizeStamp(&Stamp{ + Base: stamp.Base, + Left: newLeft, + Right: getRight(stamp), + }) + } + if id.Right.IsLeaf && id.Right.Value == 1 { + newRight := Event(&ID{IsLeaf: true, Value: 1}, getRight(stamp)) + return normalizeStamp(&Stamp{ + Base: stamp.Base, + Left: getLeft(stamp), + Right: newRight, + }) + } + + // Both non-zero, choose lower side + leftMax := maxStamp(getLeft(stamp)) + rightMax := maxStamp(getRight(stamp)) + + if leftMax <= rightMax { + return normalizeStamp(&Stamp{ + Base: stamp.Base, + Left: Event(id.Left, getLeft(stamp)), + Right: getRight(stamp), + }) + } + return normalizeStamp(&Stamp{ + Base: stamp.Base, + Left: getLeft(stamp), + Right: Event(id.Right, getRight(stamp)), + }) +} + +func getLeft(s *Stamp) *Stamp { + if s.Left == nil { + return &Stamp{Base: 0} + } + return s.Left +} + +func getRight(s *Stamp) *Stamp { + if s.Right == nil { + return &Stamp{Base: 0} + } + return s.Right +} + +func maxStamp(s *Stamp) int { + if s.Left == nil && s.Right == nil { + return s.Base + } + left := 0 + right := 0 + if s.Left != nil { + left = maxStamp(s.Left) + } + if s.Right != nil { + right = maxStamp(s.Right) + } + max := left + if right > max { + max = right + } + return s.Base + max +} + +// JoinStamps merges two stamps +func JoinStamps(s1, s2 *Stamp) *Stamp { + // Take max at each level + base := s1.Base + if s2.Base > base { + base = s2.Base + } + + // Adjust for base difference + adj1 := s1.Base + adj2 := s2.Base + + return normalizeStamp(&Stamp{ + Base: base, + Left: joinStampsRecursive(s1.Left, s2.Left, adj1-base, adj2-base), + Right: joinStampsRecursive(s1.Right, s2.Right, adj1-base, adj2-base), + }) +} + +func normalizeStamp(s *Stamp) *Stamp { + if s.Left == nil && s.Right == nil { + return s + } + if s.Left != nil && s.Right != nil { + if s.Left.Base > 0 && s.Right.Base > 0 { + min := s.Left.Base + if s.Right.Base < min { + min = s.Right.Base + } + return &Stamp{ + Base: s.Base + min, + Left: &Stamp{Base: s.Left.Base - min, Left: s.Left.Left, Right: s.Left.Right}, + Right: &Stamp{Base: s.Right.Base - min, Left: s.Right.Left, Right: s.Right.Right}, + } + } + } + return s +} +``` + +## Hybrid Logical Clock Implementation + +```go +type HLC struct { + l int64 // logical component (physical time) + c int64 // counter + mu sync.Mutex +} + +func NewHLC() *HLC { + return &HLC{l: 0, c: 0} +} + +type HLCTimestamp struct { + L int64 + C int64 +} + +func (hlc *HLC) physicalTime() int64 { + return time.Now().UnixNano() +} + +// Now returns current HLC timestamp for local/send event +func (hlc *HLC) Now() HLCTimestamp { + hlc.mu.Lock() + defer hlc.mu.Unlock() + + pt := hlc.physicalTime() + + if pt > hlc.l { + hlc.l = pt + hlc.c = 0 + } else { + hlc.c++ + } + + return HLCTimestamp{L: hlc.l, C: hlc.c} +} + +// Update updates HLC based on received timestamp +func (hlc *HLC) Update(received HLCTimestamp) HLCTimestamp { + hlc.mu.Lock() + defer hlc.mu.Unlock() + + pt := hlc.physicalTime() + + if pt > hlc.l && pt > received.L { + hlc.l = pt + hlc.c = 0 + } else if received.L > hlc.l { + hlc.l = received.L + hlc.c = received.C + 1 + } else if hlc.l > received.L { + hlc.c++ + } else { // hlc.l == received.L + if received.C > hlc.c { + hlc.c = received.C + 1 + } else { + hlc.c++ + } + } + + return HLCTimestamp{L: hlc.l, C: hlc.c} +} + +// Compare compares two HLC timestamps +func (t1 HLCTimestamp) Compare(t2 HLCTimestamp) int { + if t1.L < t2.L { + return -1 + } + if t1.L > t2.L { + return 1 + } + if t1.C < t2.C { + return -1 + } + if t1.C > t2.C { + return 1 + } + return 0 +} +``` + +## Causal Broadcast Implementation + +```go +type CausalBroadcast struct { + vc *VectorClock + pending []PendingMessage + deliver func(Message) + mu sync.Mutex +} + +type PendingMessage struct { + Msg Message + Timestamp map[string]uint64 +} + +func NewCausalBroadcast(processID string, processes []string, deliver func(Message)) *CausalBroadcast { + return &CausalBroadcast{ + vc: NewVectorClock(processID, processes), + pending: make([]PendingMessage, 0), + deliver: deliver, + } +} + +// Broadcast sends a message to all processes +func (cb *CausalBroadcast) Broadcast(msg Message) map[string]uint64 { + cb.mu.Lock() + defer cb.mu.Unlock() + + timestamp := cb.vc.Send() + // Actual network broadcast would happen here + return timestamp +} + +// Receive handles an incoming message +func (cb *CausalBroadcast) Receive(msg Message, sender string, timestamp map[string]uint64) { + cb.mu.Lock() + defer cb.mu.Unlock() + + // Add to pending + cb.pending = append(cb.pending, PendingMessage{Msg: msg, Timestamp: timestamp}) + + // Try to deliver pending messages + cb.tryDeliver() +} + +func (cb *CausalBroadcast) tryDeliver() { + changed := true + for changed { + changed = false + + for i, pending := range cb.pending { + if cb.canDeliver(pending.Timestamp) { + // Deliver message + cb.vc.Receive(pending.Timestamp) + cb.deliver(pending.Msg) + + // Remove from pending + cb.pending = append(cb.pending[:i], cb.pending[i+1:]...) + changed = true + break + } + } + } +} + +func (cb *CausalBroadcast) canDeliver(msgVC map[string]uint64) bool { + currentVC := cb.vc.clocks + + for pid, msgTime := range msgVC { + if pid == cb.vc.self { + // Must be next expected from sender + if msgTime != currentVC[pid]+1 { + return false + } + } else { + // All other dependencies must be satisfied + if msgTime > currentVC[pid] { + return false + } + } + } + return true +} +``` diff --git a/.claude/skills/elliptic-curves/SKILL.md b/.claude/skills/elliptic-curves/SKILL.md new file mode 100644 index 0000000..82bab00 --- /dev/null +++ b/.claude/skills/elliptic-curves/SKILL.md @@ -0,0 +1,369 @@ +--- +name: elliptic-curves +description: This skill should be used when working with elliptic curve cryptography, implementing or debugging secp256k1 operations, understanding modular arithmetic and finite fields, or implementing signature schemes like ECDSA and Schnorr. Provides comprehensive knowledge of group theory foundations, curve mathematics, point multiplication algorithms, and cryptographic optimizations. +--- + +# Elliptic Curve Cryptography + +This skill provides deep knowledge of elliptic curve cryptography (ECC), with particular focus on the secp256k1 curve used in Bitcoin and Nostr, including the mathematical foundations and implementation considerations. + +## When to Use This Skill + +- Implementing or debugging elliptic curve operations +- Working with secp256k1, ECDSA, or Schnorr signatures +- Understanding modular arithmetic and finite field operations +- Optimizing cryptographic code for performance +- Analyzing security properties of curve-based cryptography + +## Mathematical Foundations + +### Groups in Cryptography + +A **group** is a set G with a binary operation (often denoted · or +) satisfying: + +1. **Closure**: For all a, b ∈ G, the result a · b is also in G +2. **Associativity**: (a · b) · c = a · (b · c) +3. **Identity**: There exists e ∈ G such that e · a = a · e = a +4. **Inverse**: For each a ∈ G, there exists a⁻¹ such that a · a⁻¹ = e + +A **cyclic group** is generated by repeatedly applying the operation to a single element (the generator). The **order** of a group is the number of elements. + +**Why groups matter in cryptography**: The discrete logarithm problem—given g and gⁿ, find n—is computationally hard in certain groups, forming the security basis for ECC. + +### Modular Arithmetic + +Modular arithmetic constrains calculations to a finite range [0, p-1] for some modulus p: + +``` +a ≡ b (mod p) means p divides (a - b) + +Operations: +- Addition: (a + b) mod p +- Subtraction: (a - b + p) mod p +- Multiplication: (a × b) mod p +- Inverse: a⁻¹ where (a × a⁻¹) ≡ 1 (mod p) +``` + +**Computing modular inverse**: +- **Fermat's Little Theorem**: If p is prime, a⁻¹ ≡ a^(p-2) (mod p) +- **Extended Euclidean Algorithm**: More efficient for general cases +- **SafeGCD Algorithm**: Constant-time, used in libsecp256k1 + +### Finite Fields (Galois Fields) + +A **finite field** GF(p) or 𝔽ₚ is a field with a finite number of elements where: +- p must be prime (or a prime power for extension fields) +- All arithmetic operations are defined and produce elements within the field +- Every non-zero element has a multiplicative inverse + +For cryptographic curves like secp256k1, the field is 𝔽ₚ where p is a 256-bit prime. + +**Key property**: The non-zero elements of a finite field form a cyclic group under multiplication. + +## Elliptic Curves + +### The Curve Equation + +An elliptic curve over a finite field 𝔽ₚ is defined by the Weierstrass equation: + +``` +y² = x³ + ax + b (mod p) +``` + +The curve must satisfy the non-singularity condition: 4a³ + 27b² ≠ 0 + +### Points on the Curve + +A point P = (x, y) is on the curve if it satisfies the equation. The set of all points, plus a special "point at infinity" O (the identity element), forms an abelian group. + +### Point Operations + +**Point Addition (P + Q where P ≠ Q)**: +``` +λ = (y₂ - y₁) / (x₂ - x₁) (mod p) +x₃ = λ² - x₁ - x₂ (mod p) +y₃ = λ(x₁ - x₃) - y₁ (mod p) +``` + +**Point Doubling (P + P = 2P)**: +``` +λ = (3x₁² + a) / (2y₁) (mod p) +x₃ = λ² - 2x₁ (mod p) +y₃ = λ(x₁ - x₃) - y₁ (mod p) +``` + +**Point at Infinity**: Acts as the identity element; P + O = P for all P. + +**Point Negation**: -P = (x, -y) = (x, p - y) + +## The secp256k1 Curve + +### Parameters + +secp256k1 is defined by SECG (Standards for Efficient Cryptography Group): + +``` +Curve equation: y² = x³ + 7 (a = 0, b = 7) + +Prime modulus p: + 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F + = 2²⁵⁶ - 2³² - 977 + +Group order n: + 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141 + +Generator point G: + Gx = 0x79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798 + Gy = 0x483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8 + +Cofactor h = 1 +``` + +### Why secp256k1? + +1. **Koblitz curve**: a = 0 enables faster computation (no ax term) +2. **Special prime**: p = 2²⁵⁶ - 2³² - 977 allows efficient modular reduction +3. **Deterministic construction**: Not randomly generated, reducing backdoor concerns +4. **~30% faster** than random curves when fully optimized + +### Efficient Modular Reduction + +The special form of p enables fast reduction without general division: + +``` +For p = 2²⁵⁶ - 2³² - 977: +To reduce a 512-bit number c = c_high × 2²⁵⁶ + c_low: + c ≡ c_low + c_high × 2³² + c_high × 977 (mod p) +``` + +## Point Multiplication Algorithms + +Scalar multiplication kP (computing P + P + ... + P, k times) is the core operation. + +### Double-and-Add (Binary Method) + +``` +Input: k (scalar), P (point) +Output: kP + +R = O (point at infinity) +for i from bit_length(k)-1 down to 0: + R = 2R # Point doubling + if bit i of k is 1: + R = R + P # Point addition +return R +``` + +**Complexity**: O(log k) point operations +**Vulnerability**: Timing side-channels (different branches for 0/1 bits) + +### Montgomery Ladder + +Constant-time algorithm that performs the same operations regardless of bit values: + +``` +Input: k (scalar), P (point) +Output: kP + +R0 = O +R1 = P +for i from bit_length(k)-1 down to 0: + if bit i of k is 0: + R1 = R0 + R1 + R0 = 2R0 + else: + R0 = R0 + R1 + R1 = 2R1 +return R0 +``` + +**Advantage**: Resistant to simple power analysis and timing attacks. + +### Window Methods (w-NAF) + +Precompute small multiples of P, then process w bits at a time: + +``` +w-NAF representation reduces additions by ~1/3 compared to binary +Precomputation table: [P, 3P, 5P, 7P, ...] for w=4 +``` + +### Endomorphism Optimization (GLV Method) + +secp256k1 has an efficiently computable endomorphism φ where: +``` +φ(x, y) = (βx, y) where β³ ≡ 1 (mod p) +φ(P) = λP where λ³ ≡ 1 (mod n) +``` + +This allows splitting scalar k into k₁ + k₂λ with smaller k₁, k₂, reducing operations by ~33-50%. + +### Multi-Scalar Multiplication (Strauss-Shamir) + +For computing k₁P₁ + k₂P₂ (common in signature verification): + +``` +Process both scalars simultaneously, combining operations +Reduces work compared to separate multiplications +``` + +## Coordinate Systems + +### Affine Coordinates + +Standard (x, y) representation. Requires modular inversion for each operation. + +### Projective Coordinates + +Represent (X:Y:Z) where x = X/Z, y = Y/Z: +- Avoids inversions during intermediate computations +- Only one inversion at the end to convert back to affine + +### Jacobian Coordinates + +Represent (X:Y:Z) where x = X/Z², y = Y/Z³: +- Fastest for point doubling +- Used extensively in libsecp256k1 + +### López-Dahab Coordinates + +For curves over GF(2ⁿ), optimized for binary field arithmetic. + +## Signature Schemes + +### ECDSA (Elliptic Curve Digital Signature Algorithm) + +**Key Generation**: +``` +Private key: d (random integer in [1, n-1]) +Public key: Q = dG +``` + +**Signing message m**: +``` +1. Hash: e = H(m) truncated to curve order bit length +2. Random: k ∈ [1, n-1] +3. Compute: (x, y) = kG +4. Calculate: r = x mod n (if r = 0, restart with new k) +5. Calculate: s = k⁻¹(e + rd) mod n (if s = 0, restart) +6. Signature: (r, s) +``` + +**Verification of signature (r, s) on message m**: +``` +1. Check: r, s ∈ [1, n-1] +2. Hash: e = H(m) +3. Compute: w = s⁻¹ mod n +4. Compute: u₁ = ew mod n, u₂ = rw mod n +5. Compute: (x, y) = u₁G + u₂Q +6. Valid if: r ≡ x (mod n) +``` + +**Security considerations**: +- k MUST be unique per signature (reuse leaks private key) +- Use RFC 6979 for deterministic k derivation + +### Schnorr Signatures (BIP-340) + +Simpler, more efficient, with provable security. + +**Signing message m**: +``` +1. Random: k ∈ [1, n-1] +2. Compute: R = kG +3. Challenge: e = H(R || Q || m) +4. Response: s = k + ed mod n +5. Signature: (R, s) or (r_x, s) where r_x is x-coordinate of R +``` + +**Verification**: +``` +1. Compute: e = H(R || Q || m) +2. Check: sG = R + eQ +``` + +**Advantages over ECDSA**: +- Linear: enables signature aggregation (MuSig) +- Simpler verification (no modular inverse) +- Batch verification support +- Provably secure in Random Oracle Model + +## Implementation Considerations + +### Constant-Time Operations + +To prevent timing attacks: +- Avoid branches dependent on secret data +- Use constant-time comparison functions +- Mask operations to hide data-dependent timing + +```go +// BAD: Timing leak +if secretBit == 1 { + doOperation() +} + +// GOOD: Constant-time conditional +result = conditionalSelect(secretBit, value1, value0) +``` + +### Memory Safety + +- Zeroize sensitive data after use +- Avoid leaving secrets in registers or cache +- Use secure memory allocation when available + +### Side-Channel Protections + +- **Timing attacks**: Use constant-time algorithms +- **Power analysis**: Montgomery ladder, point blinding +- **Cache attacks**: Avoid table lookups indexed by secrets + +### Random Number Generation + +- Use cryptographically secure RNG for k in ECDSA +- Consider deterministic k (RFC 6979) for reproducibility +- Validate output is in valid range [1, n-1] + +## libsecp256k1 Optimizations + +The Bitcoin Core library includes: + +1. **Field arithmetic**: 5×52-bit limbs for 64-bit platforms +2. **Scalar arithmetic**: 4×64-bit representation +3. **Endomorphism**: GLV decomposition enabled by default +4. **Batch inversion**: Amortizes expensive inversions +5. **SafeGCD**: Constant-time modular inverse +6. **Precomputed tables**: For generator point multiplications + +## Security Properties + +### Discrete Logarithm Problem (DLP) + +Given P and Q = kP, finding k is computationally infeasible. + +**Best known attacks**: +- Generic: Baby-step Giant-step, Pollard's rho: O(√n) operations +- For secp256k1: ~2¹²⁸ operations (128-bit security) + +### Curve Security Criteria + +- Large prime order subgroup +- Cofactor 1 (no small subgroup attacks) +- Resistant to MOV attack (embedding degree) +- Not anomalous (n ≠ p) + +## Common Pitfalls + +1. **k reuse in ECDSA**: Immediately leaks private key +2. **Weak random k**: Partially leaks key over multiple signatures +3. **Invalid curve points**: Validate points are on curve +4. **Small subgroup attacks**: Check point order (cofactor = 1 helps) +5. **Timing leaks**: Non-constant-time scalar multiplication + +## References + +For detailed implementations, see: +- `references/secp256k1-parameters.md` - Full curve parameters +- `references/algorithms.md` - Detailed algorithm pseudocode +- `references/security.md` - Security analysis and attack vectors diff --git a/.claude/skills/elliptic-curves/references/algorithms.md b/.claude/skills/elliptic-curves/references/algorithms.md new file mode 100644 index 0000000..63ec1dd --- /dev/null +++ b/.claude/skills/elliptic-curves/references/algorithms.md @@ -0,0 +1,513 @@ +# Elliptic Curve Algorithms + +Detailed pseudocode for core elliptic curve operations. + +## Field Arithmetic + +### Modular Addition + +``` +function mod_add(a, b, p): + result = a + b + if result >= p: + result = result - p + return result +``` + +### Modular Subtraction + +``` +function mod_sub(a, b, p): + if a >= b: + return a - b + else: + return p - b + a +``` + +### Modular Multiplication + +For general case: +``` +function mod_mul(a, b, p): + return (a * b) mod p +``` + +For secp256k1 optimized (Barrett reduction): +``` +function mod_mul_secp256k1(a, b): + # Compute full 512-bit product + product = a * b + + # Split into high and low 256-bit parts + low = product & ((1 << 256) - 1) + high = product >> 256 + + # Reduce: result ≡ low + high * (2³² + 977) (mod p) + result = low + high * (1 << 32) + high * 977 + + # May need additional reduction + while result >= p: + result = result - p + + return result +``` + +### Modular Inverse + +**Extended Euclidean Algorithm**: +``` +function mod_inverse(a, p): + if a == 0: + error "No inverse exists for 0" + + old_r, r = p, a + old_s, s = 0, 1 + + while r != 0: + quotient = old_r / r + old_r, r = r, old_r - quotient * r + old_s, s = s, old_s - quotient * s + + if old_r != 1: + error "No inverse exists" + + if old_s < 0: + old_s = old_s + p + + return old_s +``` + +**Fermat's Little Theorem** (for prime p): +``` +function mod_inverse_fermat(a, p): + return mod_exp(a, p - 2, p) +``` + +### Modular Exponentiation (Square-and-Multiply) + +``` +function mod_exp(base, exp, p): + result = 1 + base = base mod p + + while exp > 0: + if exp & 1: # exp is odd + result = (result * base) mod p + exp = exp >> 1 + base = (base * base) mod p + + return result +``` + +### Modular Square Root (Tonelli-Shanks) + +For secp256k1 where p ≡ 3 (mod 4): +``` +function mod_sqrt(a, p): + # For p ≡ 3 (mod 4), sqrt(a) = a^((p+1)/4) + return mod_exp(a, (p + 1) / 4, p) +``` + +## Point Operations + +### Point Validation + +``` +function is_on_curve(P, a, b, p): + if P is infinity: + return true + + x, y = P + left = (y * y) mod p + right = (x * x * x + a * x + b) mod p + + return left == right +``` + +### Point Addition (Affine Coordinates) + +``` +function point_add(P, Q, a, p): + if P is infinity: + return Q + if Q is infinity: + return P + + x1, y1 = P + x2, y2 = Q + + if x1 == x2: + if y1 == mod_neg(y2, p): # P = -Q + return infinity + else: # P == Q + return point_double(P, a, p) + + # λ = (y2 - y1) / (x2 - x1) + numerator = mod_sub(y2, y1, p) + denominator = mod_sub(x2, x1, p) + λ = mod_mul(numerator, mod_inverse(denominator, p), p) + + # x3 = λ² - x1 - x2 + x3 = mod_sub(mod_sub(mod_mul(λ, λ, p), x1, p), x2, p) + + # y3 = λ(x1 - x3) - y1 + y3 = mod_sub(mod_mul(λ, mod_sub(x1, x3, p), p), y1, p) + + return (x3, y3) +``` + +### Point Doubling (Affine Coordinates) + +``` +function point_double(P, a, p): + if P is infinity: + return infinity + + x, y = P + + if y == 0: + return infinity + + # λ = (3x² + a) / (2y) + numerator = mod_add(mod_mul(3, mod_mul(x, x, p), p), a, p) + denominator = mod_mul(2, y, p) + λ = mod_mul(numerator, mod_inverse(denominator, p), p) + + # x3 = λ² - 2x + x3 = mod_sub(mod_mul(λ, λ, p), mod_mul(2, x, p), p) + + # y3 = λ(x - x3) - y + y3 = mod_sub(mod_mul(λ, mod_sub(x, x3, p), p), y, p) + + return (x3, y3) +``` + +### Point Negation + +``` +function point_negate(P, p): + if P is infinity: + return infinity + + x, y = P + return (x, p - y) +``` + +## Scalar Multiplication + +### Double-and-Add (Left-to-Right) + +``` +function scalar_mult_double_add(k, P, a, p): + if k == 0 or P is infinity: + return infinity + + if k < 0: + k = -k + P = point_negate(P, p) + + R = infinity + bits = binary_representation(k) # MSB first + + for bit in bits: + R = point_double(R, a, p) + if bit == 1: + R = point_add(R, P, a, p) + + return R +``` + +### Montgomery Ladder (Constant-Time) + +``` +function scalar_mult_montgomery(k, P, a, p): + R0 = infinity + R1 = P + + bits = binary_representation(k) # MSB first + + for bit in bits: + if bit == 0: + R1 = point_add(R0, R1, a, p) + R0 = point_double(R0, a, p) + else: + R0 = point_add(R0, R1, a, p) + R1 = point_double(R1, a, p) + + return R0 +``` + +### w-NAF Scalar Multiplication + +``` +function compute_wNAF(k, w): + # Convert scalar to width-w Non-Adjacent Form + naf = [] + + while k > 0: + if k & 1: # k is odd + # Get w-bit window + digit = k mod (1 << w) + if digit >= (1 << (w-1)): + digit = digit - (1 << w) + naf.append(digit) + k = k - digit + else: + naf.append(0) + k = k >> 1 + + return naf + +function scalar_mult_wNAF(k, P, w, a, p): + # Precompute odd multiples: [P, 3P, 5P, ..., (2^(w-1)-1)P] + precomp = [P] + P2 = point_double(P, a, p) + for i in range(1, 1 << (w-1)): + precomp.append(point_add(precomp[-1], P2, a, p)) + + # Convert k to w-NAF + naf = compute_wNAF(k, w) + + # Compute scalar multiplication + R = infinity + for i in range(len(naf) - 1, -1, -1): + R = point_double(R, a, p) + digit = naf[i] + if digit > 0: + R = point_add(R, precomp[(digit - 1) / 2], a, p) + elif digit < 0: + R = point_add(R, point_negate(precomp[(-digit - 1) / 2], p), a, p) + + return R +``` + +### Shamir's Trick (Multi-Scalar) + +For computing k₁P + k₂Q efficiently: + +``` +function multi_scalar_mult(k1, P, k2, Q, a, p): + # Precompute P + Q + PQ = point_add(P, Q, a, p) + + # Get binary representations (same length, padded) + bits1 = binary_representation(k1) + bits2 = binary_representation(k2) + max_len = max(len(bits1), len(bits2)) + bits1 = pad_left(bits1, max_len) + bits2 = pad_left(bits2, max_len) + + R = infinity + + for i in range(max_len): + R = point_double(R, a, p) + + b1, b2 = bits1[i], bits2[i] + + if b1 == 1 and b2 == 1: + R = point_add(R, PQ, a, p) + elif b1 == 1: + R = point_add(R, P, a, p) + elif b2 == 1: + R = point_add(R, Q, a, p) + + return R +``` + +## Jacobian Coordinates + +More efficient for repeated operations. + +### Conversion + +``` +# Affine to Jacobian +function affine_to_jacobian(P): + if P is infinity: + return (1, 1, 0) # Jacobian infinity + x, y = P + return (x, y, 1) + +# Jacobian to Affine +function jacobian_to_affine(P, p): + X, Y, Z = P + if Z == 0: + return infinity + + Z_inv = mod_inverse(Z, p) + Z_inv2 = mod_mul(Z_inv, Z_inv, p) + Z_inv3 = mod_mul(Z_inv2, Z_inv, p) + + x = mod_mul(X, Z_inv2, p) + y = mod_mul(Y, Z_inv3, p) + + return (x, y) +``` + +### Point Doubling (Jacobian) + +For curve y² = x³ + 7 (a = 0): + +``` +function jacobian_double(P, p): + X, Y, Z = P + + if Y == 0: + return (1, 1, 0) # infinity + + # For a = 0: M = 3*X² + S = mod_mul(4, mod_mul(X, mod_mul(Y, Y, p), p), p) + M = mod_mul(3, mod_mul(X, X, p), p) + + X3 = mod_sub(mod_mul(M, M, p), mod_mul(2, S, p), p) + Y3 = mod_sub(mod_mul(M, mod_sub(S, X3, p), p), + mod_mul(8, mod_mul(Y, Y, mod_mul(Y, Y, p), p), p), p) + Z3 = mod_mul(2, mod_mul(Y, Z, p), p) + + return (X3, Y3, Z3) +``` + +### Point Addition (Jacobian + Affine) + +Mixed addition is faster when one point is in affine: + +``` +function jacobian_add_affine(P, Q, p): + # P in Jacobian (X1, Y1, Z1), Q in affine (x2, y2) + X1, Y1, Z1 = P + x2, y2 = Q + + if Z1 == 0: + return affine_to_jacobian(Q) + + Z1Z1 = mod_mul(Z1, Z1, p) + U2 = mod_mul(x2, Z1Z1, p) + S2 = mod_mul(y2, mod_mul(Z1, Z1Z1, p), p) + + H = mod_sub(U2, X1, p) + HH = mod_mul(H, H, p) + I = mod_mul(4, HH, p) + J = mod_mul(H, I, p) + r = mod_mul(2, mod_sub(S2, Y1, p), p) + V = mod_mul(X1, I, p) + + X3 = mod_sub(mod_sub(mod_mul(r, r, p), J, p), mod_mul(2, V, p), p) + Y3 = mod_sub(mod_mul(r, mod_sub(V, X3, p), p), mod_mul(2, mod_mul(Y1, J, p), p), p) + Z3 = mod_mul(mod_sub(mod_mul(mod_add(Z1, H, p), mod_add(Z1, H, p), p), + mod_add(Z1Z1, HH, p), p), 1, p) + + return (X3, Y3, Z3) +``` + +## GLV Endomorphism (secp256k1) + +### Scalar Decomposition + +``` +# Constants for secp256k1 +LAMBDA = 0x5363AD4CC05C30E0A5261C028812645A122E22EA20816678DF02967C1B23BD72 +BETA = 0x7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE + +# Decomposition coefficients +A1 = 0x3086D221A7D46BCDE86C90E49284EB15 +B1 = 0x114CA50F7A8E2F3F657C1108D9D44CFD8 +A2 = 0xE4437ED6010E88286F547FA90ABFE4C3 +B2 = A1 + +function glv_decompose(k, n): + # Compute c1 = round(b2 * k / n) + # Compute c2 = round(-b1 * k / n) + c1 = (B2 * k + n // 2) // n + c2 = (-B1 * k + n // 2) // n + + # k1 = k - c1*A1 - c2*A2 + # k2 = -c1*B1 - c2*B2 + k1 = k - c1 * A1 - c2 * A2 + k2 = -c1 * B1 - c2 * B2 + + return (k1, k2) + +function glv_scalar_mult(k, P, p, n): + k1, k2 = glv_decompose(k, n) + + # Compute endomorphism: φ(P) = (β*x, y) + x, y = P + phi_P = (mod_mul(BETA, x, p), y) + + # Use Shamir's trick: k1*P + k2*φ(P) + return multi_scalar_mult(k1, P, k2, phi_P, 0, p) +``` + +## Batch Inversion + +Amortize expensive inversions over multiple points: + +``` +function batch_invert(values, p): + n = len(values) + if n == 0: + return [] + + # Compute cumulative products + products = [values[0]] + for i in range(1, n): + products.append(mod_mul(products[-1], values[i], p)) + + # Invert the final product + inv = mod_inverse(products[-1], p) + + # Compute individual inverses + inverses = [0] * n + for i in range(n - 1, 0, -1): + inverses[i] = mod_mul(inv, products[i - 1], p) + inv = mod_mul(inv, values[i], p) + inverses[0] = inv + + return inverses +``` + +## Key Generation + +``` +function generate_keypair(G, n, p): + # Generate random private key + d = random_integer(1, n - 1) + + # Compute public key + Q = scalar_mult(d, G) + + return (d, Q) +``` + +## Point Compression/Decompression + +``` +function compress_point(P, p): + if P is infinity: + return bytes([0x00]) + + x, y = P + prefix = 0x02 if (y % 2 == 0) else 0x03 + return bytes([prefix]) + x.to_bytes(32, 'big') + +function decompress_point(compressed, a, b, p): + prefix = compressed[0] + + if prefix == 0x00: + return infinity + + x = int.from_bytes(compressed[1:], 'big') + + # Compute y² = x³ + ax + b + y_squared = mod_add(mod_add(mod_mul(x, mod_mul(x, x, p), p), + mod_mul(a, x, p), p), b, p) + + # Compute y = sqrt(y²) + y = mod_sqrt(y_squared, p) + + # Select correct y based on prefix + if (prefix == 0x02) != (y % 2 == 0): + y = p - y + + return (x, y) +``` \ No newline at end of file diff --git a/.claude/skills/elliptic-curves/references/secp256k1-parameters.md b/.claude/skills/elliptic-curves/references/secp256k1-parameters.md new file mode 100644 index 0000000..a8ed056 --- /dev/null +++ b/.claude/skills/elliptic-curves/references/secp256k1-parameters.md @@ -0,0 +1,194 @@ +# secp256k1 Complete Parameters + +## Curve Definition + +**Name**: secp256k1 (Standards for Efficient Cryptography, prime field, 256-bit, Koblitz curve #1) + +**Equation**: y² = x³ + 7 (mod p) + +This is the short Weierstrass form with coefficients a = 0, b = 7. + +## Field Parameters + +### Prime Modulus p + +``` +Decimal: +115792089237316195423570985008687907853269984665640564039457584007908834671663 + +Hexadecimal: +0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F + +Binary representation: +2²⁵⁶ - 2³² - 2⁹ - 2⁸ - 2⁷ - 2⁶ - 2⁴ - 1 += 2²⁵⁶ - 2³² - 977 +``` + +**Special form benefits**: +- Efficient modular reduction using: c mod p = c_low + c_high × (2³² + 977) +- Near-Mersenne prime enables fast arithmetic + +### Group Order n + +``` +Decimal: +115792089237316195423570985008687907852837564279074904382605163141518161494337 + +Hexadecimal: +0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +``` + +The number of points on the curve, including the point at infinity. + +### Cofactor h + +``` +h = 1 +``` + +Cofactor 1 means the group order n equals the curve order, simplifying security analysis and eliminating small subgroup attacks. + +## Generator Point G + +### Compressed Form + +``` +02 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 +``` + +The 02 prefix indicates the y-coordinate is even. + +### Uncompressed Form + +``` +04 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 + 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 +``` + +### Individual Coordinates + +**Gx**: +``` +Decimal: +55066263022277343669578718895168534326250603453777594175500187360389116729240 + +Hexadecimal: +0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 +``` + +**Gy**: +``` +Decimal: +32670510020758816978083085130507043184471273380659243275938904335757337482424 + +Hexadecimal: +0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 +``` + +## Endomorphism Parameters + +secp256k1 has an efficiently computable endomorphism φ: (x, y) → (βx, y). + +### β (Beta) + +``` +Hexadecimal: +0x7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE + +Property: β³ ≡ 1 (mod p) +``` + +### λ (Lambda) + +``` +Hexadecimal: +0x5363AD4CC05C30E0A5261C028812645A122E22EA20816678DF02967C1B23BD72 + +Property: λ³ ≡ 1 (mod n) +Relationship: φ(P) = λP for all points P +``` + +### GLV Decomposition Constants + +For splitting scalar k into k₁ + k₂λ: + +``` +a₁ = 0x3086D221A7D46BCDE86C90E49284EB15 +b₁ = -0xE4437ED6010E88286F547FA90ABFE4C3 +a₂ = 0x114CA50F7A8E2F3F657C1108D9D44CFD8 +b₂ = a₁ +``` + +## Derived Constants + +### Field Characteristics + +``` +(p + 1) / 4 = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFF0C +Used for computing modular square roots via Tonelli-Shanks shortcut +``` + +### Order Characteristics + +``` +(n - 1) / 2 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 +Used in low-S normalization for ECDSA signatures +``` + +## Validation Formulas + +### Point on Curve Check + +For point (x, y), verify: +``` +y² ≡ x³ + 7 (mod p) +``` + +### Generator Verification + +Verify G is on curve: +``` +Gy² mod p = 0x9C47D08FFB10D4B8 ... (truncated for display) +Gx³ + 7 mod p = same value +``` + +### Order Verification + +Verify nG = O (point at infinity): +``` +Computing n × G should yield the identity element +``` + +## Bit Lengths + +| Parameter | Bits | Bytes | +|-----------|------|-------| +| p (prime) | 256 | 32 | +| n (order) | 256 | 32 | +| Private key | 256 | 32 | +| Public key (compressed) | 257 | 33 | +| Public key (uncompressed) | 513 | 65 | +| ECDSA signature | 512 | 64 | +| Schnorr signature | 512 | 64 | + +## Security Level + +- **Equivalent symmetric key strength**: 128 bits +- **Best known attack complexity**: ~2¹²⁸ operations (Pollard's rho) +- **Safe until**: Quantum computers with ~1500+ logical qubits + +## ASN.1 OID + +``` +1.3.132.0.10 +iso(1) identified-organization(3) certicom(132) curve(0) secp256k1(10) +``` + +## Comparison with Other Curves + +| Curve | Field Size | Security | Speed | Use Case | +|-------|------------|----------|-------|----------| +| secp256k1 | 256-bit | 128-bit | Fast (Koblitz) | Bitcoin, Nostr | +| secp256r1 (P-256) | 256-bit | 128-bit | Moderate | TLS, general | +| Curve25519 | 255-bit | ~128-bit | Very fast | Modern crypto | +| secp384r1 (P-384) | 384-bit | 192-bit | Slower | High security | diff --git a/.claude/skills/elliptic-curves/references/security.md b/.claude/skills/elliptic-curves/references/security.md new file mode 100644 index 0000000..8c241bf --- /dev/null +++ b/.claude/skills/elliptic-curves/references/security.md @@ -0,0 +1,291 @@ +# Elliptic Curve Security Analysis + +Security properties, attack vectors, and mitigations for elliptic curve cryptography. + +## The Discrete Logarithm Problem (ECDLP) + +### Definition + +Given points P and Q = kP on an elliptic curve, find the scalar k. + +**Security assumption**: For properly chosen curves, this problem is computationally infeasible. + +### Best Known Attacks + +#### Generic Attacks (Work on Any Group) + +| Attack | Complexity | Notes | +|--------|------------|-------| +| Baby-step Giant-step | O(√n) space and time | Requires √n storage | +| Pollard's rho | O(√n) time, O(1) space | Practical for large groups | +| Pollard's lambda | O(√n) | When k is in known range | +| Pohlig-Hellman | O(√p) where p is largest prime factor | Exploits factorization of n | + +For secp256k1 (n ≈ 2²⁵⁶): +- Generic attack complexity: ~2¹²⁸ operations +- Equivalent to 128-bit symmetric security + +#### Curve-Specific Attacks + +| Attack | Applicable When | Mitigation | +|--------|-----------------|------------| +| MOV/FR reduction | Low embedding degree | Use curves with high embedding degree | +| Anomalous curve attack | n = p | Ensure n ≠ p | +| GHS attack | Extension field curves | Use prime field curves | + +**secp256k1 is immune to all known curve-specific attacks**. + +## Side-Channel Attacks + +### Timing Attacks + +**Vulnerability**: Execution time varies based on secret data. + +**Examples**: +- Conditional branches on secret bits +- Early exit conditions +- Variable-time modular operations + +**Mitigations**: +- Constant-time algorithms (Montgomery ladder) +- Fixed execution paths +- Dummy operations to equalize timing + +### Power Analysis + +**Simple Power Analysis (SPA)**: Single trace reveals operations. +- Double-and-add visible as different power signatures +- Mitigation: Montgomery ladder (uniform operations) + +**Differential Power Analysis (DPA)**: Statistical analysis of many traces. +- Mitigation: Point blinding, scalar blinding + +### Cache Attacks + +**FLUSH+RELOAD Attack**: +``` +1. Attacker flushes cache line containing lookup table +2. Victim performs table lookup based on secret +3. Attacker measures reload time to determine which entry was accessed +``` + +**Mitigations**: +- Avoid secret-dependent table lookups +- Use constant-time table access patterns +- Scatter tables to prevent cache line sharing + +### Electromagnetic (EM) Attacks + +Similar to power analysis but captures electromagnetic emissions. + +**Mitigations**: +- Shielding +- Same algorithmic protections as power analysis + +## Implementation Vulnerabilities + +### k-Reuse in ECDSA + +**The Sony PS3 Hack (2010)**: + +If the same k is used for two signatures (r₁, s₁) and (r₂, s₂) on messages m₁ and m₂: + +``` +s₁ = k⁻¹(e₁ + rd) mod n +s₂ = k⁻¹(e₂ + rd) mod n + +Since k is the same: +s₁ - s₂ = k⁻¹(e₁ - e₂) mod n +k = (e₁ - e₂)(s₁ - s₂)⁻¹ mod n + +Once k is known: +d = (s₁k - e₁)r⁻¹ mod n +``` + +**Mitigation**: Use deterministic k (RFC 6979). + +### Weak Random k + +Even with unique k values, if the RNG is biased: +- Lattice-based attacks can recover private key +- Only ~1% bias in k can be exploitable with enough signatures + +**Mitigations**: +- Use cryptographically secure RNG +- Use deterministic k (RFC 6979) +- Verify k is in valid range [1, n-1] + +### Invalid Curve Attacks + +**Attack**: Attacker provides point not on the curve. +- Point may be on a weaker curve +- Operations may leak information + +**Mitigation**: Always validate points are on curve: +``` +Verify: y² ≡ x³ + ax + b (mod p) +``` + +### Small Subgroup Attacks + +**Attack**: If cofactor h > 1, points of small order exist. +- Attacker sends point of small order +- Response reveals private key mod (small order) + +**Mitigation**: +- Use curves with cofactor 1 (secp256k1 has h = 1) +- Multiply received points by cofactor +- Validate point order + +### Fault Attacks + +**Attack**: Induce computational errors (voltage glitches, radiation). +- Corrupted intermediate values may leak information +- Differential fault analysis can recover keys + +**Mitigations**: +- Redundant computations with comparison +- Verify final results +- Hardware protections + +## Signature Malleability + +### ECDSA Malleability + +Given valid signature (r, s), signature (r, n - s) is also valid for the same message. + +**Impact**: Transaction ID malleability (historical Bitcoin issue) + +**Mitigation**: Enforce low-S normalization: +``` +if s > n/2: + s = n - s +``` + +### Schnorr Non-Malleability + +BIP-340 Schnorr signatures are non-malleable by design: +- Use x-only public keys +- Deterministic nonce derivation + +## Quantum Threats + +### Shor's Algorithm + +**Threat**: Polynomial-time discrete log on quantum computers. +- Requires ~1500-2000 logical qubits for secp256k1 +- Current quantum computers: <100 noisy qubits + +**Timeline**: Estimated 10-20+ years for cryptographically relevant quantum computers. + +### Migration Strategy + +1. **Monitor**: Track quantum computing progress +2. **Prepare**: Develop post-quantum alternatives +3. **Hybrid**: Use classical + post-quantum in transition +4. **Migrate**: Full transition when necessary + +### Post-Quantum Alternatives + +- Lattice-based signatures (CRYSTALS-Dilithium) +- Hash-based signatures (SPHINCS+) +- Code-based cryptography + +## Best Practices + +### Key Generation + +``` +DO: +- Use cryptographically secure RNG +- Validate private key is in [1, n-1] +- Verify public key is on curve +- Verify public key is not point at infinity + +DON'T: +- Use predictable seeds +- Use truncated random values +- Skip validation +``` + +### Signature Generation + +``` +DO: +- Use RFC 6979 for deterministic k +- Validate all inputs +- Use constant-time operations +- Clear sensitive memory after use + +DON'T: +- Reuse k values +- Use weak/biased RNG +- Skip low-S normalization (ECDSA) +``` + +### Signature Verification + +``` +DO: +- Validate r, s are in [1, n-1] +- Validate public key is on curve +- Validate public key is not infinity +- Use batch verification when possible + +DON'T: +- Skip any validation steps +- Accept malformed signatures +``` + +### Public Key Handling + +``` +DO: +- Validate received points are on curve +- Check point is not infinity +- Prefer compressed format for storage + +DON'T: +- Accept unvalidated points +- Skip curve membership check +``` + +## Security Checklist + +### Implementation Review + +- [ ] All scalar multiplications are constant-time +- [ ] No secret-dependent branches +- [ ] No secret-indexed table lookups +- [ ] Memory is zeroized after use +- [ ] Random k uses CSPRNG or RFC 6979 +- [ ] All received points are validated +- [ ] Private keys are in valid range +- [ ] Signatures use low-S normalization + +### Operational Security + +- [ ] Private keys stored securely (HSM, secure enclave) +- [ ] Key derivation uses proper KDF +- [ ] Backups are encrypted +- [ ] Key rotation policy exists +- [ ] Audit logging enabled +- [ ] Incident response plan exists + +## Security Levels Comparison + +| Curve | Bits | Symmetric Equivalent | RSA Equivalent | +|-------|------|---------------------|----------------| +| secp192r1 | 192 | 96 | 1536 | +| secp224r1 | 224 | 112 | 2048 | +| secp256k1 | 256 | 128 | 3072 | +| secp384r1 | 384 | 192 | 7680 | +| secp521r1 | 521 | 256 | 15360 | + +## References + +- NIST SP 800-57: Recommendation for Key Management +- SEC 1: Elliptic Curve Cryptography +- RFC 6979: Deterministic Usage of DSA and ECDSA +- BIP-340: Schnorr Signatures for secp256k1 +- SafeCurves: Choosing Safe Curves for Elliptic-Curve Cryptography