Machine Coding Problem

Crypto Wallet

maco60macoAllfintechsigningledger-immutability
Commonly Asked By:CoinbaseBinanceLedgerKraken

Requirements & System Scope

Functional Scope (In-Scope)

  • Local KeyPair Generation: Create secure cryptographic key pairs (ECDSA/Ed25519) on-device to enable non-custodial ownership.
  • UTXO Balance Derivation: Calculate wallet balances dynamically by scanning active sets of Unspent Transaction Outputs (UTXOs).
  • Local Transaction Signing: Sign transaction structures with user private keys; verify signatures via derived public keys.
  • Double-Spend Guards: Prevent double-spending by validating UTXO references against the active UTXO set inside the global ledger.
  • Change Address Mechanics: Dynamically calculate changes and route them back to a change address owned by the sender.

Explicit Boundaries (Out-of-Scope)

  • No Real-World P2P Blockchain Consensus Nodes: Bypasses live peer-to-peer block mining, mempool gossip propagation, or Proof-of-Work solvers.
  • No Fiat Banking Integration: Bypasses direct wire transfers, credit card processors, or fiat-to-crypto exchanges.

Class Diagram & Entity Relationships

Structural layout showing relationships between transactions, inputs, outputs, and the unspent transaction set:

Loading...
  • Polymorphic Transactions: Transaction links consumed inputs to newly created outputs.
  • Immutable Inputs: UTXO maps unspent transaction amounts to their specific owner addresses.

Design Patterns & SOLID Principles

  • Single Responsibility Principle (SRP): Isolating key derivation from transaction assembly (UTXO selection) and signature verification processes ensures a robust, testable structure.
  • Open-Closed Principle (OCP): Encapsulating signature verification under clear cryptographic verification algorithms allows the system to easily support multiple signature schemes (ECDSA, Ed25519) without modifying core transaction models.

Core Execution Workflows

Transaction Building & Broadcast Sequence

  1. Wallet initiates a payment: transferFunds(toAddress, amount).
  2. Scan the active UTXOSet to collect unspent outputs owned by the sender until their sum satisfies the target transfer amount.
  3. Create transaction outputs:
    1. Create an output for the recipient with the target transfer amount.
    2. Create a 'change' output returning any excess balance back to the sender's address.
  4. Generate unique transaction hash and sign it locally using the sender's private key.
  5. Verify signature integrity and broadcast to target validators.
  6. Upon confirmation, atomically remove consumed UTXOs from the active set and register the newly created outputs.

Concurrency & Thread Safety Strategy

When multiple threads construct or broadcast transactions simultaneously using shared wallets:

  • Thread-Safe UTXO Isolation: Use a read-write lock (`ReentrantReadWriteLock`) on the ledger to ensure atomic verification and transaction validation, preventing race conditions from double-spend transactions.
  • Stateless Sign Workflows: Make signature generation entirely stateless to allow safe parallel execution across thread pools.

Complete Clean Code Blueprint

Clean reference designs demonstrating UTXO-based balance calculations in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.security.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.stream.Collectors;

// Represents a unique Unspent Transaction Output
class UTXO {
    private final String txId;
    private final int outputIndex;
    private final double amount;
    private final String ownerAddress;

    public UTXO(String txId, int outputIndex, double amount, String ownerAddress) {
        this.txId = txId;
        this.outputIndex = outputIndex;
        this.amount = amount;
        this.ownerAddress = ownerAddress;
    }

    public String getTxId() { return txId; }
    public int getOutputIndex() { return outputIndex; }
    public double getAmount() { return amount; }
    public String getOwnerAddress() { return ownerAddress; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof UTXO)) return false;
        UTXO utxo = (UTXO) o;
        return outputIndex == utxo.outputIndex && txId.equals(utxo.txId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(txId, outputIndex);
    }

    @Override
    public String toString() {
        return String.format("UTXO[Tx: %s, Idx: %d, Amt: %.4f, Owner: %s]", 
            txId.substring(0, Math.min(txId.length(), 8)), outputIndex, amount, ownerAddress);
    }
}

// Transaction input pointing to a consumed UTXO
class TransactionInput {
    private final String txId;
    private final int outputIndex;
    private byte[] signature; // Proof of ownership of this UTXO

    public TransactionInput(String txId, int outputIndex) {
        this.txId = txId;
        this.outputIndex = outputIndex;
    }

    public String getTxId() { return txId; }
    public int getOutputIndex() { return outputIndex; }
    public byte[] getSignature() { return signature; }
    public void setSignature(byte[] signature) { this.signature = signature; }
}

// Transaction output creating a new UTXO
class TransactionOutput {
    private final String recipientAddress;
    private final double amount;

    public TransactionOutput(String recipientAddress, double amount) {
        this.recipientAddress = recipientAddress;
        this.amount = amount;
    }

    public String getRecipientAddress() { return recipientAddress; }
    public double getAmount() { return amount; }
}

// A full transaction containing inputs and outputs
class Transaction {
    private final String id;
    private final List<TransactionInput> inputs;
    private final List<TransactionOutput> outputs;
    private final double fee;

    public Transaction(List<TransactionInput> inputs, List<TransactionOutput> outputs, double fee) {
        this.inputs = Collections.unmodifiableList(inputs);
        this.outputs = Collections.unmodifiableList(outputs);
        this.fee = fee;
        this.id = calculateHash();
    }

    public String getId() { return id; }
    public List<TransactionInput> getInputs() { return inputs; }
    public List<TransactionOutput> getOutputs() { return outputs; }
    public double getFee() { return fee; }

    private String calculateHash() {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            StringBuilder sb = new StringBuilder();
            for (TransactionInput in : inputs) {
                sb.append(in.getTxId()).append(in.getOutputIndex());
            }
            for (TransactionOutput out : outputs) {
                sb.append(out.getRecipientAddress()).append(out.getAmount());
            }
            sb.append(fee);
            byte[] hash = digest.digest(sb.toString().getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception e) {
            throw new RuntimeException("SHA-256 error", e);
        }
    }

    public void sign(PrivateKey privateKey, Map<String, UTXO> utxoPool) throws Exception {
        Signature ecdsa = Signature.getInstance("SHA256withECDSA");
        ecdsa.initSign(privateKey);
        ecdsa.update(id.getBytes());
        byte[] signature = ecdsa.sign();

        for (TransactionInput in : inputs) {
            // Sign the transaction hash with the corresponding key
            in.setSignature(signature);
        }
    }

    public boolean verify(Map<String, PublicKey> addressKeys, Map<String, UTXO> utxoPool) {
        try {
            for (TransactionInput in : inputs) {
                String key = in.getTxId() + ":" + in.getOutputIndex();
                UTXO sourceUTXO = utxoPool.get(key);
                if (sourceUTXO == null) return false;

                PublicKey pubKey = addressKeys.get(sourceUTXO.getOwnerAddress());
                if (pubKey == null) return false;

                Signature ecdsa = Signature.getInstance("SHA256withECDSA");
                ecdsa.initVerify(pubKey);
                ecdsa.update(id.getBytes());
                if (!ecdsa.verify(in.getSignature())) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

// Immutable Block-like structure holding transaction list
class Block {
    private final String hash;
    private final String previousHash;
    private final List<Transaction> transactions;
    private final long timestamp;

    public Block(List<Transaction> transactions, String previousHash) {
        this.transactions = Collections.unmodifiableList(transactions);
        this.previousHash = previousHash;
        this.timestamp = System.currentTimeMillis();
        this.hash = calculateHash();
    }

    public String getHash() { return hash; }
    public String getPreviousHash() { return previousHash; }
    public List<Transaction> getTransactions() { return transactions; }

    private String calculateHash() {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            String data = previousHash + timestamp + transactions.stream()
                .map(Transaction::getId).collect(Collectors.joining());
            byte[] hashBytes = digest.digest(data.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

// Global Ledger managing immutable blockchain database and thread-safe UTXO mapping
class BlockchainLedger {
    private final List<Block> chain = new CopyOnWriteArrayList<>();
    private final Map<String, UTXO> utxoPool = new ConcurrentHashMap<>();
    private final Map<String, PublicKey> addressKeys = new ConcurrentHashMap<>();
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public BlockchainLedger() {
        // Create genesis block hash
        chain.add(new Block(new ArrayList<>(), "0"));
    }

    public void registerPublicKey(String address, PublicKey pubKey) {
        addressKeys.put(address, pubKey);
    }

    public void mintInitialUTXO(String address, double amount) {
        lock.writeLock().lock();
        try {
            String txId = "genesis-" + UUID.randomUUID().toString().substring(0, 8);
            UTXO utxo = new UTXO(txId, 0, amount, address);
            utxoPool.put(txId + ":0", utxo);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public double getBalance(String address) {
        lock.readLock().lock();
        try {
            return utxoPool.values().stream()
                .filter(u -> u.getOwnerAddress().equals(address))
                .mapToDouble(UTXO::getAmount)
                .sum();
        } finally {
            lock.readLock().unlock();
        }
    }

    public List<UTXO> getUnspentOutputsForAddress(String address) {
        lock.readLock().lock();
        try {
            return utxoPool.values().stream()
                .filter(u -> u.getOwnerAddress().equals(address))
                .collect(Collectors.toList());
        } finally {
            lock.readLock().unlock();
        }
    }

    public boolean submitTransaction(Transaction tx) {
        lock.writeLock().lock();
        try {
            // Verify signature
            if (!tx.verify(addressKeys, utxoPool)) {
                System.out.println("[REJECTED] Transaction signature verification failed: " + tx.getId());
                return false;
            }

            // Verify inputs exist in active UTXO set (Double-Spend prevention)
            double inputSum = 0;
            for (TransactionInput in : tx.getInputs()) {
                String key = in.getTxId() + ":" + in.getOutputIndex();
                UTXO utxo = utxoPool.get(key);
                if (utxo == null) {
                    System.out.println("[REJECTED] Double-Spend or Invalid UTXO input: " + key);
                    return false;
                }
                inputSum += utxo.getAmount();
            }

            // Verify outputs amount + fee matches inputs sum
            double outputSum = tx.getOutputs().stream().mapToDouble(TransactionOutput::getAmount).sum();
            if (Math.abs(inputSum - (outputSum + tx.getFee())) > 1e-9) {
                System.out.println("[REJECTED] Balance mismatch. Input: " + inputSum + ", Output+Fee: " + (outputSum + tx.getFee()));
                return false;
            }

            // Atomically update UTXO pool
            for (TransactionInput in : tx.getInputs()) {
                utxoPool.remove(in.getTxId() + ":" + in.getOutputIndex());
            }
            for (int i = 0; i < tx.getOutputs().size(); i++) {
                TransactionOutput out = tx.getOutputs().get(i);
                UTXO newUTXO = new UTXO(tx.getId(), i, out.getAmount(), out.getRecipientAddress());
                utxoPool.put(tx.getId() + ":" + i, newUTXO);
            }

            // Append to chain
            String prevHash = chain.get(chain.size() - 1).getHash();
            chain.add(new Block(Collections.singletonList(tx), prevHash));
            System.out.println("[SUCCESS] Transaction added to ledger: " + tx.getId() + " | Block Depth: " + chain.size());
            return true;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// Client Wallet managing multi-address and handling ECDSA credentials
class Wallet {
    private final String name;
    private final Map<String, KeyPair> keys = new ConcurrentHashMap<>();
    private final BlockchainLedger ledger;

    public Wallet(String name, BlockchainLedger ledger) {
        this.name = name;
        this.ledger = ledger;
    }

    public String generateNewAddress() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
        keyGen.initialize(256);
        KeyPair pair = keyGen.generateKeyPair();
        
        // Use simple hex hash of public key as readable address
        byte[] pubBytes = pair.getPublic().getEncoded();
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        byte[] hash = sha.digest(pubBytes);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10; i++) { // Limit length for visibility
            sb.append(String.format("%02x", hash[i]));
        }
        String address = name.toLowerCase() + "-" + sb.toString();
        keys.put(address, pair);
        ledger.registerPublicKey(address, pair.getPublic());
        return address;
    }

    public double getBalance() {
        double balance = 0;
        for (String addr : keys.keySet()) {
            balance += ledger.getBalance(addr);
        }
        return balance;
    }

    public Transaction createTransaction(String toAddress, double amount, double fee) throws Exception {
        if (keys.isEmpty()) throw new IllegalStateException("Wallet has no addresses generated.");

        // Aggregate all UTXOs owned by this wallet
        List<UTXO> allUTXOs = new ArrayList<>();
        for (String addr : keys.keySet()) {
            allUTXOs.addAll(ledger.getUnspentOutputsForAddress(addr));
        }

        // Selection: First-Fit Strategy to reach amount + fee
        double target = amount + fee;
        double accumulated = 0;
        List<UTXO> selectedUTXOs = new ArrayList<>();
        for (UTXO utxo : allUTXOs) {
            selectedUTXOs.add(utxo);
            accumulated += utxo.getAmount();
            if (accumulated >= target) break;
        }

        if (accumulated < target) {
            throw new IllegalArgumentException(name + " has insufficient balance! Needed: " + target + ", Available: " + accumulated);
        }

        // Build Inputs
        List<TransactionInput> inputs = new ArrayList<>();
        for (UTXO utxo : selectedUTXOs) {
            inputs.add(new TransactionInput(utxo.getTxId(), utxo.getOutputIndex()));
        }

        // Build Outputs
        List<TransactionOutput> outputs = new ArrayList<>();
        outputs.add(new TransactionOutput(toAddress, amount));

        // Change output returning back to the first address of the sender wallet
        double change = accumulated - target;
        String changeAddress = keys.keySet().iterator().next();
        if (change > 0) {
            outputs.add(new TransactionOutput(changeAddress, change));
        }

        Transaction tx = new Transaction(inputs, outputs, fee);

        // Sign inputs with respective private keys
        for (int i = 0; i < selectedUTXOs.size(); i++) {
            UTXO sourceUTXO = selectedUTXOs.get(i);
            PrivateKey privKey = keys.get(sourceUTXO.getOwnerAddress()).getPrivate();
            tx.sign(privKey, null);
        }

        return tx;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("=== INITIALIZING CRYPTO LEDGER AND WALLETS ===");
        BlockchainLedger ledger = new BlockchainLedger();

        Wallet alice = new Wallet("Alice", ledger);
        Wallet bob = new Wallet("Bob", ledger);
        Wallet charlie = new Wallet("Charlie", ledger);

        String aliceAddr1 = alice.generateNewAddress();
        String aliceAddr2 = alice.generateNewAddress();
        String bobAddr = bob.generateNewAddress();
        String charlieAddr = charlie.generateNewAddress();

        // Seed Alice with initial funds via genesis mint
        ledger.mintInitialUTXO(aliceAddr1, 10.0);
        ledger.mintInitialUTXO(aliceAddr2, 15.0);

        System.out.println("Initial Alice Balance: " + alice.getBalance() + " BTC");
        System.out.println("Initial Bob Balance: " + bob.getBalance() + " BTC");

        System.out.println("\n=== EXECUTE VALID TRANSACTION ===");
        // Alice sends 18.0 BTC to Bob (fee 0.5)
        Transaction tx1 = alice.createTransaction(bobAddr, 18.0, 0.5);
        boolean success1 = ledger.submitTransaction(tx1);
        System.out.println("Alice Balance after Tx1: " + alice.getBalance() + " BTC");
        System.out.println("Bob Balance after Tx1: " + bob.getBalance() + " BTC");

        System.out.println("\n=== PREVENT DOUBLE-SPEND ===");
        // Try to submit Alice's transaction AGAIN (double spend inputs)
        boolean successDouble = ledger.submitTransaction(tx1);
        System.out.println("Double spend attempt successful? " + successDouble);

        System.out.println("\n=== CONCURRENT TRANSFERS SIMULATION ===");
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // Bob tries to send to Charlie and Alice at the same time
        Runnable task1 = () -> {
            try {
                Transaction tx = bob.createTransaction(charlieAddr, 5.0, 0.2);
                ledger.submitTransaction(tx);
            } catch (Exception e) {
                System.out.println("Task 1 error: " + e.getMessage());
            }
        };

        Runnable task2 = () -> {
            try {
                Transaction tx = bob.createTransaction(aliceAddr1, 12.0, 0.2);
                ledger.submitTransaction(tx);
            } catch (Exception e) {
                System.out.println("Task 2 error: " + e.getMessage());
            }
        };

        executor.submit(task1);
        executor.submit(task2);

        executor.shutdown();
        executor.awaitTermination(3, TimeUnit.SECONDS);

        System.out.println("\n=== FINAL LEDGER METRICS ===");
        System.out.println("Alice Final balance: " + alice.getBalance() + " BTC");
        System.out.println("Bob Final balance: " + bob.getBalance() + " BTC");
        System.out.println("Charlie Final balance: " + charlie.getBalance() + " BTC");
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...