Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions arbnode/espresso_batcher_addr_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,17 +261,17 @@ func (b *BatcherAddrMonitor) Store() error {
return fmt.Errorf("failed to encode events: %w", err)
}

err = b.db.AuthWriteInitAddresses(newBatch, b.initAddresses)
err = authdb.WriteInitAddresses(newBatch, b.initAddresses)
if err != nil {
return fmt.Errorf("failed to put init addresses: %w", err)
}

err = b.db.AuthWriteEvents(newBatch, eventsBytes)
err = authdb.WriteEvents(newBatch, eventsBytes)
if err != nil {
return fmt.Errorf("failed to put events: %w", err)
}

err = b.db.AuthWriteLastProcessedHeight(newBatch, b.lastProcessedParentHeight)
err = authdb.WriteLastProcessedHeight(newBatch, b.lastProcessedParentHeight)
if err != nil {
return fmt.Errorf("failed to put last processed height: %w", err)
}
Expand All @@ -281,21 +281,21 @@ func (b *BatcherAddrMonitor) Store() error {

func (b *BatcherAddrMonitor) Restore() error {

initAddresses, err := b.db.AuthReadInitAddresses()
initAddresses, err := authdb.ReadInitAddresses(&b.db)
if err != nil {
return fmt.Errorf("failed to get init addresses: %w, init addresses: %v", err, initAddresses)
}
if initAddresses != nil {
b.initAddresses = initAddresses
}

lastProcessedHeight, err := b.db.AuthReadLastProcessedHeight()
lastProcessedHeight, err := authdb.ReadLastProcessedHeight(&b.db)
if err != nil {
return fmt.Errorf("failed to get last processed height: %w", err)
}
b.lastProcessedParentHeight = lastProcessedHeight

eventsBytes, err := b.db.AuthReadEvents()
eventsBytes, err := authdb.ReadEvents(&b.db)
if err != nil {
return fmt.Errorf("failed to get events: %w", err)
}
Expand Down Expand Up @@ -421,7 +421,7 @@ func (b *BatcherAddrMonitor) Process(ctx context.Context) error {
if len(events) == 0 {
// If no events are found, we still need to update the last processed height
batch := b.db.NewBatch()
err = b.db.AuthWriteLastProcessedHeight(batch, newHeight)
err = authdb.WriteLastProcessedHeight(batch, newHeight)
if err != nil {
return fmt.Errorf("failed to store last processed height: %w", err)
}
Expand Down
8 changes: 4 additions & 4 deletions arbnode/espresso_caff_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func NewEspressoCaffNode(
fromBlock := configFetcher().FromBlock

if !configFetcher().Dangerous.IgnoreDatabaseFromBlock {
fromBlock, err = db.AuthReadFromBlock()
fromBlock, err = authdb.ReadFromBlock(&db)
if err != nil {
return nil, fmt.Errorf("failed to read l1 block from db: %w", err)
}
Expand Down Expand Up @@ -336,15 +336,15 @@ func (n *EspressoCaffNode) createBlock(ctx context.Context) (returnValue bool) {
batch := n.db.NewBatch()

// Store hotshot block num with auth tag
if err := n.db.AuthWriteNextHotshotBlockNum(batch, hotshotBlockNumber); err != nil {
if err := authdb.WriteNextHotshotBlockNum(batch, hotshotBlockNumber); err != nil {
log.Error("Failed to store NextHotshotBlockNum and its auth tag: %w", err)
return false
}

// Store from block with signature if snapshot signer is configured
// fromBlock will only be stored when we process a delayed message
if fromBlock != 0 {
if err := n.db.AuthWriteFromBlock(batch, fromBlock); err != nil {
if err := authdb.WriteFromBlock(batch, fromBlock); err != nil {
log.Error("failed to store delayedMessageFetcherFromBlock and its auth tag", "err", err)
return false
}
Expand Down Expand Up @@ -414,7 +414,7 @@ func (n *EspressoCaffNode) Start(ctx context.Context) error {
var nextHotshotBlock uint64

if !n.configFetcher().Dangerous.IgnoreDatabaseHotshotBlock {
nextHotshotBlock, err = n.db.AuthReadNextHotshotBlockNum()
nextHotshotBlock, err = authdb.ReadNextHotshotBlockNum(&n.db)
if err != nil {
return fmt.Errorf("failed to read next hotshot block: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions espresso/authdb/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ tmp/

# tmp ancient store for test
authdbancient
testancient
144 changes: 144 additions & 0 deletions espresso/authdb/DEV.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Authenticated Storage (AuthDB) Architecture

This document explains the authenticated storage mechanism designed to ensure data integrity for security-critical operations in the Espresso-Nitro integration.

## Core Security Model

**AuthDB** provides cryptographic integrity guarantees for all database operations using HMAC (Hash-based Message Authentication Code). Every stored value is protected by an authentication tag that prevents tampering and detects corruption.

### Key Principle
All security-critical data passes through AuthDB, which enforces that:
1. **Write operations** automatically generate and store HMAC tags alongside data
2. **Read operations** verify HMAC tags before returning data
3. **Invalid/tampered data** causes read operations to fail with authentication errors

## Architecture Overview

```
Application Layer
AuthDB (wrapper)
Underlying Database (Geth's ethdb)
```

### Core Components

- **`AuthDB`** - Main wrapper implementing `ethdb.Database` interface
- **`AuthBatch`** - Authenticated batch operations for atomic writes
- **`AuthIterator`** - Iterator that skips internal tag entries and validates data
- **`AuthAncientWriteOp`** - Handles freezer (ancient) data with authentication

## Authentication Schemes

### 1. Key-Value Store Authentication
**Location:** `Put()`, `Get()`, `Has()` methods

**Tag Generation:**
```go
tag = HMAC(key || value)
```

**Storage Pattern:**
- Data: `key → value`
- Tag: `key-tag → HMAC(key || value)`

**Critical Security Properties:**
- Tags stored separately from data prevent value substitution attacks
- Key inclusion in HMAC prevents key confusion attacks
- Constant-time comparison (`hmac.Equal`) prevents timing attacks

### 2. Ancient Store Authentication
**Location:** `Ancient()`, `AncientRange()`, `ModifyAncients()` methods

Ancient data uses two different authentication approaches based on data type:

**Hash Table (Raw bytes):**
```go
stored_data = original_data || HMAC(kind || number || original_data)
```

**Structured Data (Headers/Bodies/Receipts):**
```go
auth_item = AncientItemWithTag{
item: original_item,
tag: HMAC(kind || number || RLP(original_item))
}
stored_data = RLP(auth_item)
```

## Security-Critical Functions

### Enforcement Functions (`operations.go`)
```go
func enforceAuthenticatedWriter(db ethdb.KeyValueWriter) error
func enforceAuthenticatedReader(db ethdb.KeyValueReader) error
```

**Purpose:** Compile-time + runtime guarantee that only `AuthDB`/`AuthBatch` instances are used for security-critical operations.

**Audit Focus:** Verify all security-sensitive database operations in the codebase use these enforcement functions.

### Domain-Specific Operations
Functions like `WriteNextHotshotBlockNum`, `ReadInitAddresses` enforce authenticated storage for:
- Hotshot consensus state tracking
- Batcher address monitoring
- Event processing checkpoints

**Audit Focus:** Confirm these operations cannot be bypassed with direct database access.

## Attack Resistance

### Tampering Detection
- **Bit-flip attacks:** Any corruption in stored data fails HMAC verification
- **Substitution attacks:** Cannot replace values due to key-specific tags
- **Rollback attacks:** Sequence numbers in ancient data prevent replay

### Authentication Bypasses
- **Type confusion:** `enforceAuthenticated*` functions prevent use of raw database
- **Tag forgery:** HMAC requires secret key unknown to attackers
- **Timing attacks:** Constant-time comparison prevents tag extraction

## Key Security Boundaries

### 1. HMAC Key Management
**Location:** `NewAuthDB(db ethdb.Database, mac hash.Hash)`

**Security Requirement:** The `mac` parameter must be initialized with a cryptographically secure key. This key represents the root of trust for all authentication.

**Audit Focus:** Verify HMAC key derivation and lifecycle management in calling code.

### 2. Authentication Bypass Prevention
**Critical Code Paths:**
- All `Write*` and `Read*` functions in `operations.go` call enforcement functions
- Iterator skips tag entries to prevent information disclosure
- Batch operations maintain authentication invariants

**Audit Focus:** Confirm no code paths exist that bypass AuthDB for security-critical data.

### 3. Ancient Data Integrity
**Critical Invariant:** Ancient data authentication handles both metadata (kind, number) and content in HMAC computation.

**Audit Focus:** Verify freezer data cannot be modified without triggering authentication failures.

## Testing Verification

### Authentication Test Coverage
- **`authdb_test.go`:** Full database suite compliance testing
- **`operations_test.go`:** Security enforcement verification

**Key Test:** `TestSecurityEnforcement` confirms raw database access fails with clear error messages.

### Audit Recommendations
1. **Static Analysis:** Verify no direct `ethdb.Database` usage for security-critical data
2. **Integration Testing:** Confirm authentication failures halt system operation
3. **Key Management Review:** Audit HMAC key generation and rotation procedures
4. **Performance Impact:** Measure authentication overhead in production scenarios

## Non-Security Operations

Several database operations remain unauthenticated by design:
- **Metadata operations:** `Ancients()`, `Tail()`, `AncientSize()` (not security-sensitive)
- **Maintenance operations:** `Sync()`, `Compact()`, `Close()` (operational, not data integrity)

**Rationale:** These operations don't affect data integrity and authentication would add unnecessary overhead.
136 changes: 0 additions & 136 deletions espresso/authdb/accessors_chain.go

This file was deleted.

Loading
Loading