Creating Your First Blockchain with Java Part 2 — Transactions

·

This tutorial series aims to help you build foundational concepts for blockchain development. In Part 2, we'll:

👉 Ready to dive deeper into blockchain development?

Prerequisites

1. Building a Cryptocurrency Wallet

A wallet stores cryptographic keys that control coin ownership:

import java.security.*;
public class Wallet {
    private PrivateKey privateKey;
    private PublicKey publicKey;

    public Wallet() {
        generateKeyPair();
    }

    private void generateKeyPair() {
        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
            keyGen.initialize(ecSpec, random);
            KeyPair keyPair = keyGen.generateKeyPair();
            privateKey = keyPair.getPrivate();
            publicKey = keyPair.getPublic();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // Getters omitted for brevity
}

Key Concepts:

2. Transaction Structure and Digital Signatures

Transactions contain:

public class Transaction {
    public String transactionId;
    public PublicKey sender;
    public PublicKey recipient;
    public float value;
    public byte[] signature;
    
    public ArrayList<TransactionInput> inputs = new ArrayList<>();
    public ArrayList<TransactionOutput> outputs = new ArrayList<>();
    
    // Methods will be added below
}

Signature Generation

public void generateSignature(PrivateKey privateKey) {
    String data = StringUtil.getStringFromKey(sender) 
               + StringUtil.getStringFromKey(recipient) 
               + Float.toString(value);
    signature = StringUtil.applyECDSASig(privateKey, data);
}

public boolean verifySignature() {
    String data = StringUtil.getStringFromKey(sender) 
               + StringUtil.getStringFromKey(recipient) 
               + Float.toString(value);
    return StringUtil.verifyECDSASig(sender, data, signature);
}

3. Transaction Inputs and Outputs

TransactionOutput Class

public class TransactionOutput {
    public String id;
    public PublicKey recipient;
    public float value;
    public String parentTransactionId;
    
    public TransactionOutput(PublicKey recipient, float value, String parentTransactionId) {
        this.recipient = recipient;
        this.value = value;
        this.parentTransactionId = parentTransactionId;
        this.id = StringUtil.applySha256(
                StringUtil.getStringFromKey(recipient) +
                Float.toString(value) +
                parentTransactionId
        );
    }
    
    public boolean isMine(PublicKey publicKey) {
        return (publicKey == recipient);
    }
}

TransactionInput Class

public class TransactionInput {
    public String transactionOutputId;
    public TransactionOutput UTXO;
    
    public TransactionInput(String transactionOutputId) {
        this.transactionOutputId = transactionOutputId;
    }
}

4. Processing Transactions

The core logic for validating and executing transactions:

public boolean processTransaction() {
    if(!verifySignature()) {
        System.out.println("Transaction signature failed to verify");
        return false;
    }
    
    // Gather transaction inputs
    for(TransactionInput i : inputs) {
        i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId);
    }
    
    // Check if transaction is valid
    if(getInputsValue() < NoobChain.minimumTransaction) {
        System.out.println("Transaction inputs too small");
        return false;
    }
    
    // Generate transaction outputs
    float leftOver = getInputsValue() - value;
    transactionId = calculateHash();
    outputs.add(new TransactionOutput(this.recipient, value, transactionId));
    outputs.add(new TransactionOutput(this.sender, leftOver, transactionId));
    
    // Add outputs to UTXO list
    for(TransactionOutput o : outputs) {
        NoobChain.UTXOs.put(o.id, o);
    }
    
    // Remove transaction inputs from UTXO list
    for(TransactionInput i : inputs) {
        if(i.UTXO != null) NoobChain.UTXOs.remove(i.UTXO.id);
    }
    
    return true;
}

5. Integrating Transactions into Blocks

Replace block's string data with transaction lists:

public class Block {
    public String hash;
    public String previousHash;
    public String merkleRoot;
    public ArrayList<Transaction> transactions = new ArrayList<>();
    private long timeStamp;
    private int nonce;
    
    // Updated constructor
    public Block(String previousHash) {
        this.previousHash = previousHash;
        this.timeStamp = new Date().getTime();
        this.hash = calculateHash();
    }
    
    public String calculateHash() {
        return StringUtil.applySha256(
                previousHash +
                Long.toString(timeStamp) +
                Integer.toString(nonce) +
                merkleRoot
        );
    }
}

👉 Want to see the complete implementation?

6. Testing the Implementation

public class NoobChain {
    public static HashMap<String, TransactionOutput> UTXOs = new HashMap<>();
    public static float minimumTransaction = 0.1f;
    
    public static void main(String[] args) {
        // Setup Bouncy Castle security provider
        Security.addProvider(new BouncyCastleProvider());
        
        // Create wallets
        Wallet walletA = new Wallet();
        Wallet walletB = new Wallet();
        
        // Genesis transaction
        Transaction genesisTransaction = new Transaction(
                walletA.publicKey, 
                walletA.publicKey, 
                100f, 
                null
        );
        genesisTransaction.generateSignature(walletA.privateKey);
        genesisTransaction.transactionId = "0";
        genesisTransaction.outputs.add(new TransactionOutput(
                genesisTransaction.recipient, 
                genesisTransaction.value, 
                genesisTransaction.transactionId
        ));
        UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
        
        System.out.println("Creating and mining Genesis block...");
        Block genesis = new Block("0");
        genesis.addTransaction(genesisTransaction);
        addBlock(genesis);
        
        // Test transactions
        Block block1 = new Block(genesis.hash);
        System.out.println("\nWalletA's balance: " + walletA.getBalance());
        System.out.println("WalletA is attempting to send 40 coins to WalletB...");
        block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f));
        addBlock(block1);
        
        System.out.println("WalletA's balance: " + walletA.getBalance());
        System.out.println("WalletB's balance: " + walletB.getBalance());
    }
    
    public static void addBlock(Block newBlock) {
        // Mining and chain addition logic
    }
}

FAQs

Q: How are cryptocurrency wallets secured?

A: Wallets use asymmetric cryptography (public/private key pairs). The private key signs transactions, while the public key verifies signatures.

Q: What prevents double-spending in blockchain?

A: The UTXO system ensures each transaction output can only be spent once. The network rejects transactions referencing already-spent outputs.

Q: How are transactions verified?

A: Miners/nodes verify:

  1. Digital signatures are valid
  2. Inputs reference unspent outputs
  3. Sum of inputs ≥ sum of outputs

Q: Why use elliptic curve cryptography?

A: ECC provides equivalent security to RSA with smaller key sizes (256-bit ECC ≈ 3072-bit RSA), making blockchain operations more efficient.

Conclusion

You've now built a functional blockchain with:

The complete code is available on GitHub. Try extending it with mining rewards, peer-to-peer networking, or smart contracts!

👉 Explore more blockchain development resources