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