db/_archive/websocket/architecture.md
2025-06-27 12:11:04 +03:00

9.9 KiB

WebSocket Signing Server Architecture Plan

Based on my analysis of the existing Actix application structure, I've designed a comprehensive architecture for implementing a WebSocket server that handles signing operations. This server will integrate seamlessly with the existing hostbasket application.

1. Overview

The WebSocket Signing Server will:

  • Accept WebSocket connections from clients
  • Allow clients to identify themselves with a public key
  • Provide a send_to_sign() function that takes a public key and a message
  • Forward the message to the appropriate client for signing
  • Wait for a signed response (with a 1-minute timeout)
  • Verify the signature using the client's public key
  • Return the response message and signature

2. Component Architecture

graph TD
    A[Actix Web Server] --> B[WebSocket Manager]
    B --> C[Connection Registry]
    B --> D[Message Handler]
    D --> E[Signature Verifier]
    F[Client] <--> B
    G[Controllers] --> H[SigningService]
    H --> B

Key Components:

  1. WebSocket Manager

    • Handles WebSocket connections
    • Manages connection lifecycle
    • Routes messages to appropriate handlers
  2. Connection Registry

    • Maps public keys to active WebSocket connections
    • Handles connection tracking and cleanup
    • Provides lookup functionality
  3. Message Handler

    • Processes incoming messages
    • Implements the message protocol
    • Manages timeouts for responses
  4. Signature Verifier

    • Verifies signatures using public keys
    • Implements cryptographic operations
    • Ensures security of the signing process
  5. Signing Service

    • Provides a clean API for controllers to use
    • Abstracts WebSocket complexity from business logic
    • Handles error cases and timeouts

3. Directory Structure

src/
├── websocket/
│   ├── mod.rs                 # Module exports
│   ├── manager.rs             # WebSocket connection manager
│   ├── registry.rs            # Connection registry
│   ├── handler.rs             # Message handling logic
│   ├── protocol.rs            # Message protocol definitions
│   ├── crypto.rs              # Cryptographic operations
│   └── service.rs             # Service API for controllers
├── controllers/
│   └── [existing controllers]
│   └── websocket.rs           # WebSocket controller (if needed)
└── routes/
    └── mod.rs                 # Updated to include WebSocket routes

4. Data Flow

sequenceDiagram
    participant Client
    participant WebSocketManager
    participant Registry
    participant Controller
    participant SigningService
    
    Client->>WebSocketManager: Connect
    Client->>WebSocketManager: Introduce(public_key)
    WebSocketManager->>Registry: Register(connection, public_key)
    
    Controller->>SigningService: send_to_sign(public_key, message)
    SigningService->>Registry: Lookup(public_key)
    Registry-->>SigningService: connection
    SigningService->>WebSocketManager: Send message to connection
    WebSocketManager->>Client: Message to sign
    
    Client->>WebSocketManager: Signed response
    WebSocketManager->>SigningService: Forward response
    SigningService->>SigningService: Verify signature
    SigningService-->>Controller: Return verified response

5. Message Protocol

We'll define a simple JSON-based protocol for communication:

// Client introduction
{
  "type": "introduction",
  "public_key": "base64_encoded_public_key"
}

// Sign request
{
  "type": "sign_request",
  "message": "base64_encoded_message",
  "request_id": "unique_request_id"
}

// Sign response
{
  "type": "sign_response",
  "request_id": "unique_request_id",
  "message": "base64_encoded_message",
  "signature": "base64_encoded_signature"
}

6. Required Dependencies

We'll need to add the following dependencies to the project:

# WebSocket support
actix-web-actors = "4.2.0"

# Cryptography
ed25519-dalek = "2.0.0"  # For Ed25519 signatures
base64 = "0.21.0"        # For encoding/decoding
rand = "0.8.5"           # For generating random data

7. Implementation Details

7.1 WebSocket Manager

The WebSocket Manager will handle the lifecycle of WebSocket connections:

pub struct WebSocketManager {
    registry: Arc<RwLock<ConnectionRegistry>>,
}

impl Actor for WebSocketManager {
    type Context = ws::WebsocketContext<Self>;

    fn started(&mut self, ctx: &mut Self::Context) {
        // Handle connection start
    }

    fn stopped(&mut self, ctx: &mut Self::Context) {
        // Handle connection close and cleanup
    }
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketManager {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        // Handle different types of WebSocket messages
    }
}

7.2 Connection Registry

The Connection Registry will maintain a mapping of public keys to active connections:

pub struct ConnectionRegistry {
    connections: HashMap<String, Addr<WebSocketManager>>,
}

impl ConnectionRegistry {
    pub fn register(&mut self, public_key: String, addr: Addr<WebSocketManager>) {
        self.connections.insert(public_key, addr);
    }

    pub fn unregister(&mut self, public_key: &str) {
        self.connections.remove(public_key);
    }

    pub fn get(&self, public_key: &str) -> Option<&Addr<WebSocketManager>> {
        self.connections.get(public_key)
    }
}

7.3 Signing Service

The Signing Service will provide a clean API for controllers:

pub struct SigningService {
    registry: Arc<RwLock<ConnectionRegistry>>,
}

impl SigningService {
    pub async fn send_to_sign(&self, public_key: &str, message: &[u8]) 
        -> Result<(Vec<u8>, Vec<u8>), SigningError> {
        
        // 1. Find the connection for the public key
        let connection = self.registry.read().await.get(public_key).cloned();
        
        if let Some(conn) = connection {
            // 2. Generate a unique request ID
            let request_id = Uuid::new_v4().to_string();
            
            // 3. Create a response channel
            let (tx, rx) = oneshot::channel();
            
            // 4. Register the response channel
            self.pending_requests.write().await.insert(request_id.clone(), tx);
            
            // 5. Send the message to the client
            let sign_request = SignRequest {
                request_id: request_id.clone(),
                message: message.to_vec(),
            };
            
            conn.do_send(sign_request);
            
            // 6. Wait for the response with a timeout
            match tokio::time::timeout(Duration::from_secs(60), rx).await {
                Ok(Ok(response)) => {
                    // 7. Verify the signature
                    if self.verify_signature(&response.signature, message, public_key) {
                        Ok((response.message, response.signature))
                    } else {
                        Err(SigningError::InvalidSignature)
                    }
                },
                Ok(Err(_)) => Err(SigningError::ChannelClosed),
                Err(_) => Err(SigningError::Timeout),
            }
        } else {
            Err(SigningError::ConnectionNotFound)
        }
    }
}

8. Error Handling

We'll define a comprehensive error type for the signing service:

#[derive(Debug, thiserror::Error)]
pub enum SigningError {
    #[error("Connection not found for the provided public key")]
    ConnectionNotFound,
    
    #[error("Timeout waiting for signature")]
    Timeout,
    
    #[error("Invalid signature")]
    InvalidSignature,
    
    #[error("Channel closed unexpectedly")]
    ChannelClosed,
    
    #[error("WebSocket error: {0}")]
    WebSocketError(#[from] ws::ProtocolError),
    
    #[error("Serialization error: {0}")]
    SerializationError(#[from] serde_json::Error),
}

9. Security Considerations

  1. Public Key Validation: Validate public keys upon connection to ensure they are properly formatted
  2. Message Authentication: Consider adding a nonce or timestamp to prevent replay attacks
  3. Rate Limiting: Implement rate limiting to prevent DoS attacks
  4. Connection Timeouts: Automatically close inactive connections
  5. Error Logging: Log errors but avoid exposing sensitive information
  6. Input Validation: Validate all inputs to prevent injection attacks

10. Testing Strategy

  1. Unit Tests: Test individual components in isolation
  2. Integration Tests: Test the interaction between components
  3. End-to-End Tests: Test the complete flow from client connection to signature verification
  4. Load Tests: Test the system under high load to ensure stability
  5. Security Tests: Test for common security vulnerabilities

11. Integration with Existing Controllers

Controllers can use the SigningService through dependency injection:

pub struct SomeController {
    signing_service: Arc<SigningService>,
}

impl SomeController {
    pub async fn some_action(&self, public_key: String, message: Vec<u8>) -> HttpResponse {
        match self.signing_service.send_to_sign(&public_key, &message).await {
            Ok((response, signature)) => {
                HttpResponse::Ok().json(json!({
                    "response": base64::encode(response),
                    "signature": base64::encode(signature),
                }))
            },
            Err(e) => {
                HttpResponse::InternalServerError().json(json!({
                    "error": e.to_string(),
                }))
            }
        }
    }
}

12. Deployment Considerations

  1. Scalability: The WebSocket server should be designed to scale horizontally
  2. Monitoring: Implement metrics for connection count, message throughput, and error rates
  3. Logging: Log important events for debugging and auditing
  4. Documentation: Document the API and protocol for client implementers