Skip to content

Signature Timestamp Expiry Validation #48

@AnkanMisra

Description

@AnkanMisra

Problem

Security vulnerability: Signatures never expire. An attacker who intercepts a valid payment signature can replay it hours or days later to drain funds or abuse the service.

Current Flow (Vulnerable)

sequenceDiagram
    participant A as Attacker
    participant V as Victim
    participant G as Gateway
    
    V->>G: Valid request (signature)
    A->>A: Intercept signature
    Note over A: Wait 1 hour...
    A->>G: Replay same signature
    G-->>A: Works! (Bug)
Loading

Why This Matters

  1. Man-in-the-middle attacks - Attacker intercepts network traffic
  2. Stolen credentials - If signature is logged or leaked
  3. Session hijacking - Reuse old valid signatures

Solution

Add a timestamp field to the EIP-712 payment message and reject signatures older than a configurable window (default: 5 minutes).

Protected Flow

sequenceDiagram
    participant A as Attacker
    participant G as Gateway
    
    A->>G: Replay old signature
    Note over G: Check timestamp
    Note over G: age = now - timestamp
    Note over G: age > 5 min 
    G-->>A: 400 E007 Signature Expired
Loading

Technical Details

Modified EIP-712 Structure

The payment context must include a timestamp field. This is a breaking change - all clients must update.

// Before
struct PaymentContext {
    recipient: Address,
    amount: String,
    token: String,
    nonce: String,
    chain_id: u64,
}

// After
struct PaymentContext {
    recipient: Address,
    amount: String,
    token: String,
    nonce: String,
    chain_id: u64,
    timestamp: u64,  // Unix timestamp in seconds
}

Validation Logic

flowchart TD
    A[Signature + Timestamp] --> B{timestamp > now + 60s?}
    B -->|Yes| C["E008: Future timestamp"]
    B -->|No| D{age > max_window?}
    D -->|Yes| E["E007: Signature expired"]
    D -->|No| F["Valid"]
    
    style C fill:#f96
    style E fill:#f96
    style F fill:#6f6
Loading

Edge Cases to Handle

Scenario Expected Behavior
Signature from 2 minutes ago Valid
Signature from 4 minutes ago Valid (within 5 min window)
Signature from 10 minutes ago Reject with E007
Signature with future timestamp (+2 min) Reject with E008
Missing timestamp field Reject with E009
Client clock 30 seconds ahead Allow (60s grace period)

Implementation

Rust Verifier

use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug)]
enum VerifyError {
    SignatureExpired { age_seconds: u64, max_seconds: u64 },
    FutureTimestamp { timestamp: u64, now: u64 },
    MissingTimestamp,
}

fn validate_timestamp(timestamp: u64, window_seconds: u64) -> Result<(), VerifyError> {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("Time went backwards")
        .as_secs();
    
    // Reject future timestamps (with 60s grace for clock skew)
    if timestamp > now + 60 {
        return Err(VerifyError::FutureTimestamp { timestamp, now });
    }
    
    // Reject expired signatures
    let age = now.saturating_sub(timestamp);
    if age > window_seconds {
        return Err(VerifyError::SignatureExpired { 
            age_seconds: age, 
            max_seconds: window_seconds 
        });
    }
    
    Ok(())
}

Go Gateway Changes

The gateway must include timestamp when generating the payment context:

// In handleSummarize
paymentContext := PaymentContext{
    Recipient: getRecipientAddress(),
    Token:     "USDC",
    Amount:    getPaymentAmount(),
    Nonce:     uuid.New().String(),
    ChainID:   getChainID(),
    Timestamp: uint64(time.Now().Unix()),  // Add this
}

Acceptance Criteria

  • Add timestamp field to EIP-712 PaymentContext
  • Validate timestamp in Rust verifier
  • Configurable expiry window via SIGNATURE_EXPIRY_SECONDS
  • Return E007 for expired signatures
  • Return E008 for future timestamps
  • Update Go gateway to include timestamp
  • Add tests for all edge cases (expired, future, boundary)
  • Document breaking change in CHANGELOG
  • Update TypeScript client SDK (if exists)

Environment Variables

# Signature expiry window in seconds (default: 300 = 5 minutes)
SIGNATURE_EXPIRY_SECONDS=300

# Grace period for clock skew (default: 60 seconds)
SIGNATURE_CLOCK_SKEW_SECONDS=60

Testing

cd verifier && cargo test

# Manual testing scenarios:
# 1. Fresh signature (now) -> Should pass
# 2. Signature from 3 min ago -> Should pass
# 3. Signature from 10 min ago -> Should fail E007
# 4. Signature with timestamp +5 min -> Should fail E008

Migration Guide

This is a breaking change. Existing clients must update:

  1. Update client SDK to include timestamp in payment context
  2. Regenerate signatures with new EIP-712 domain
  3. Deploy updated verifier before clients (with fallback period)

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions