Machine Coding Problem

Crypto Wallet

maco60macoAllfintechsigningledger-immutability
Commonly Asked By:CoinbaseBinanceLedgerKraken

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.

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");
    }
}