Cross-Chain Interoperability and Bridges

Building cross-chain bridges and implementing interoperability protocols between different blockchains.

Advanced⏱️ 55 min📚 Prerequisites: 2

Cross-Chain Interoperability and Bridges

Cross-chain solutions enable communication and value transfer between different blockchains.

The Interoperability Problem

Blockchains are isolated - they can't natively communicate with each other. This creates:

  • Liquidity Fragmentation: Assets locked on different chains
  • User Friction: Multiple wallets, different experiences
  • Limited Composability: Can't combine features across chains

Bridge Types

1. Lock and Mint

  • Lock assets on source chain
  • Mint wrapped tokens on destination chain
  • Burn wrapped tokens to unlock originals

2. Burn and Mint

  • Burn tokens on source chain
  • Mint equivalent on destination chain
  • Reverse process for return

3. Atomic Swaps

  • Direct peer-to-peer swaps
  • No intermediary needed
  • Uses hash time-locked contracts (HTLC)

4. Relays

  • Validators monitor both chains
  • Relay state and messages
  • More complex but more flexible

Bridge Architecture

RUST
struct Bridge {
    source_chain: String,
    target_chain: String,
    validators: Vec<String>,
    deposits: HashMap<String, DepositInfo>,
    withdrawals: HashMap<String, WithdrawalInfo>,
}

struct DepositInfo {
    amount: u64,
    recipient: String,
    source_tx_hash: String,
    status: BridgeStatus,
}

enum BridgeStatus {
    Pending,
    Confirmed,
    Processed,
    Failed,
}

impl Bridge {
    fn deposit(&mut self, amount: u64, recipient: &str, tx_hash: &str) -> String {
        let deposit_id = generate_id();
        
        self.deposits.insert(deposit_id.clone(), DepositInfo {
            amount,
            recipient: recipient.to_string(),
            source_tx_hash: tx_hash.to_string(),
            status: BridgeStatus::Pending,
        });
        
        deposit_id
    }
    
    fn confirm_deposit(&mut self, deposit_id: &str, validator: &str) -> Result<(), String> {
        if !self.validators.contains(&validator.to_string()) {
            return Err(String::from("Invalid validator"));
        }
        
        let deposit = self.deposits.get_mut(deposit_id)
            .ok_or(String::from("Deposit not found"))?;
        
        // In real implementation: verify validator signatures
        deposit.status = BridgeStatus::Confirmed;
        
        Ok(())
    }
    
    fn process_withdrawal(&mut self, deposit_id: &str) -> Result<WithdrawalInfo, String> {
        let deposit = self.deposits.get(deposit_id)
            .ok_or(String::from("Deposit not found"))?;
        
        if deposit.status != BridgeStatus::Confirmed {
            return Err(String::from("Deposit not confirmed"));
        }
        
        // Check validator consensus (simplified)
        // In real implementation: require 2/3+ validator signatures
        
        let withdrawal = WithdrawalInfo {
            amount: deposit.amount,
            recipient: deposit.recipient.clone(),
            target_tx_hash: String::new(), // Will be set when processed
        };
        
        self.withdrawals.insert(deposit_id.to_string(), withdrawal.clone());
        
        Ok(withdrawal)
    }
}

Hash Time-Locked Contracts (HTLC)

HTLCs enable atomic swaps without trusted intermediaries.

RUST
struct HTLC {
    hash_lock: Vec<u8>, // Hash of secret
    time_lock: u64, // Expiration time
    sender: String,
    receiver: String,
    amount: u64,
    secret: Option<Vec<u8>>, // Revealed when claimed
}

impl HTLC {
    fn new(hash_lock: Vec<u8>, time_lock: u64, sender: &str, receiver: &str, amount: u64) -> Self {
        HTLC {
            hash_lock,
            time_lock,
            sender: sender.to_string(),
            receiver: receiver.to_string(),
            amount,
            secret: None,
        }
    }
    
    fn claim(&mut self, secret: Vec<u8>) -> Result<(), String> {
        // Verify secret matches hash
        if hash(&secret) != self.hash_lock {
            return Err(String::from("Invalid secret"));
        }
        
        self.secret = Some(secret);
        Ok(())
    }
    
    fn refund(&self, current_time: u64) -> Result<(), String> {
        if current_time < self.time_lock {
            return Err(String::from("Time lock not expired"));
        }
        
        if self.secret.is_some() {
            return Err(String::from("Already claimed"));
        }
        
        Ok(())
    }
}

Security Considerations

Validator Risks

  • Collusion: Validators could steal funds
  • Single Point of Failure: If validators go offline
  • Solution: Use many validators, require high threshold

Replay Attacks

  • Problem: Same transaction executed on both chains
  • Solution: Nonces, chain-specific identifiers

Double Spending

  • Problem: Spending same asset on both chains
  • Solution: Lock mechanism, proper state tracking

Real-World Examples

  • Polygon Bridge: Lock and mint between Ethereum and Polygon
  • Wormhole: Multi-chain bridge with validators
  • Chainlink CCIP: Cross-chain messaging protocol
  • Cosmos IBC: Inter-Blockchain Communication protocol

Best Practices

  • Multi-Signature: Require multiple validator signatures
  • Time Locks: Allow users to challenge suspicious transactions
  • Rate Limiting: Prevent large-scale attacks
  • Monitoring: Alert on unusual activity
  • Upgradability: Ability to fix bugs and add features

Code Examples

Cross-Chain Bridge

Basic bridge implementation for asset transfer

RUST
use std::collections::HashMap;

enum BridgeStatus {
    Pending,
    Confirmed,
    Processed,
}

struct Deposit {
    amount: u64,
    recipient: String,
    source_tx: String,
    status: BridgeStatus,
    confirmations: usize,
}

struct Bridge {
    deposits: HashMap<String, Deposit>,
    validators: Vec<String>,
    required_confirmations: usize,
}

impl Bridge {
    fn new(validators: Vec<String>) -> Self {
        let required = (validators.len() * 2) / 3 + 1; // 2/3+1
        Bridge {
            deposits: HashMap::new(),
            validators,
            required_confirmations: required,
        }
    }
    
    fn deposit(&mut self, deposit_id: String, amount: u64, recipient: String, source_tx: String) {
        self.deposits.insert(deposit_id, Deposit {
            amount,
            recipient,
            source_tx,
            status: BridgeStatus::Pending,
            confirmations: 0,
        });
    }
    
    fn confirm(&mut self, deposit_id: &str, validator: &str) -> Result<bool, String> {
        if !self.validators.contains(&validator.to_string()) {
            return Err(String::from("Invalid validator"));
        }
        
        let deposit = self.deposits.get_mut(deposit_id)
            .ok_or(String::from("Deposit not found"))?;
        
        deposit.confirmations += 1;
        
        if deposit.confirmations >= self.required_confirmations {
            deposit.status = BridgeStatus::Confirmed;
            Ok(true) // Ready to process
        } else {
            Ok(false) // Need more confirmations
        }
    }
    
    fn process_withdrawal(&mut self, deposit_id: &str) -> Result<u64, String> {
        let deposit = self.deposits.get_mut(deposit_id)
            .ok_or(String::from("Deposit not found"))?;
        
        match deposit.status {
            BridgeStatus::Confirmed => {
                deposit.status = BridgeStatus::Processed;
                Ok(deposit.amount)
            }
            _ => Err(String::from("Deposit not confirmed")),
        }
    }
}

fn main() {
    let validators = vec![
        String::from("validator1"),
        String::from("validator2"),
        String::from("validator3"),
        String::from("validator4"),
    ];
    
    let mut bridge = Bridge::new(validators);
    
    // User deposits on source chain
    bridge.deposit(
        String::from("deposit1"),
        1000,
        String::from("0xAlice"),
        String::from("0xtx123"),
    );
    
    // Validators confirm
    bridge.confirm("deposit1", "validator1").unwrap();
    bridge.confirm("deposit1", "validator2").unwrap();
    bridge.confirm("deposit1", "validator3").unwrap();
    
    // Process withdrawal on target chain
    match bridge.process_withdrawal("deposit1") {
        Ok(amount) => {
            println!("Withdrawal processed: {} tokens to 0xAlice", amount);
        }
        Err(e) => println!("Error: {}", e),
    }
}

Explanation:

Bridges lock assets on the source chain and mint equivalent assets on the target chain. Validators monitor both chains and confirm deposits. Once enough validators confirm (2/3+), the withdrawal can be processed. This enables cross-chain asset transfers.

HTLC Atomic Swap

Hash Time-Locked Contract for atomic swaps

RUST
use std::collections::HashMap;

fn hash(data: &[u8]) -> Vec<u8> {
    // Simplified hash function
    // In real implementation: use SHA256 or similar
    data.iter().map(|b| b.wrapping_add(1)).collect()
}

struct HTLC {
    hash_lock: Vec<u8>,
    time_lock: u64,
    sender: String,
    receiver: String,
    amount: u64,
    secret: Option<Vec<u8>>,
    claimed: bool,
}

impl HTLC {
    fn new(
        secret_preimage: Vec<u8>,
        time_lock: u64,
        sender: &str,
        receiver: &str,
        amount: u64,
    ) -> Self {
        let hash_lock = hash(&secret_preimage);
        
        HTLC {
            hash_lock,
            time_lock,
            sender: sender.to_string(),
            receiver: receiver.to_string(),
            amount,
            secret: None,
            claimed: false,
        }
    }
    
    fn claim(&mut self, secret: Vec<u8>, current_time: u64) -> Result<(), String> {
        if self.claimed {
            return Err(String::from("Already claimed"));
        }
        
        if current_time >= self.time_lock {
            return Err(String::from("Time lock expired"));
        }
        
        if hash(&secret) != self.hash_lock {
            return Err(String::from("Invalid secret"));
        }
        
        self.secret = Some(secret);
        self.claimed = true;
        Ok(())
    }
    
    fn refund(&self, current_time: u64) -> Result<(), String> {
        if self.claimed {
            return Err(String::from("Already claimed"));
        }
        
        if current_time < self.time_lock {
            return Err(String::from("Time lock not expired"));
        }
        
        Ok(())
    }
}

fn main() {
    // Alice wants to swap with Bob
    let secret = vec![1, 2, 3, 4, 5];
    
    // Alice creates HTLC on chain A
    let mut htlc_a = HTLC::new(
        secret.clone(),
        1000, // Expires at block 1000
        "alice",
        "bob",
        100,
    );
    
    println!("HTLC created on chain A");
    
    // Bob sees the hash, creates HTLC on chain B
    // Bob reveals secret to claim on chain A
    match htlc_a.claim(secret, 500) {
        Ok(_) => {
            println!("Bob claimed HTLC on chain A");
            println!("Secret revealed: {:?}", htlc_a.secret);
            println!("Alice can now use secret to claim on chain B");
        }
        Err(e) => println!("Claim failed: {}", e),
    }
}

Explanation:

HTLCs enable atomic swaps: Alice locks funds with a hash, Bob locks funds on another chain, Bob claims Alice's funds revealing the secret, then Alice uses the same secret to claim Bob's funds. If either party doesn't cooperate, funds can be refunded after the time lock expires.

Exercises

Simple Bridge

Create a simple bridge deposit and withdrawal system!

Medium

Starter Code:

RUST
use std::collections::HashMap;

struct Bridge {
    deposits: HashMap<String, u64>,
}

fn main() {
    // Create bridge
    // Deposit assets
    // Process withdrawal
}