This tutorial series aims to help you build foundational concepts for blockchain development. In Part 2, we'll:
- Create a simple cryptocurrency wallet
- Send signed transactions using our blockchain
- Build the core components of a working cryptocurrency
👉 Ready to dive deeper into blockchain development?
Prerequisites
- Completion of Part 1: Basic Blockchain Implementation
Dependencies:
- Bouncy Castle (for cryptography)
- GSON (for JSON processing)
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:
- Public Key: Your wallet address (shared to receive payments)
- Private Key: Used to sign transactions (keep this secret!)
- Elliptic Curve Cryptography provides strong security with smaller keys
2. Transaction Structure and Digital Signatures
Transactions contain:
- Sender/receiver public keys
- Transaction value
- Inputs (references to previous unspent outputs)
- Outputs (new ownership assignments)
- Cryptographic signature
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:
- Digital signatures are valid
- Inputs reference unspent outputs
- 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:
- Wallet generation with ECDSA keys
- Secure transaction signing/verification
- UTXO-based transaction model
- Basic cryptocurrency functionality
The complete code is available on GitHub. Try extending it with mining rewards, peer-to-peer networking, or smart contracts!