Build Your Own Bitcoin Wallet: A Developer’s Guide to Secure Cryptocurrency Tools

·

Creating a secure, functional cryptocurrency wallet is a pivotal skill for developers entering the blockchain space. This comprehensive guide walks you through building a fully encrypted Bitcoin wallet using TypeScript and essential cryptographic libraries like BitcoinJS, ecpair, tiny-secp256k1, and CryptoJS. You'll learn how to generate keys, manage wallet data securely, store encrypted information, and maintain transaction history—all while prioritizing security at every step.

Whether you're a beginner exploring blockchain development or an experienced coder diving into decentralized systems, this tutorial provides practical insights into wallet architecture, encryption best practices, and local data persistence.


Core Components of a Bitcoin Wallet

A cryptocurrency wallet acts as the bridge between users and the blockchain. It enables key operations such as:

For most users, wallets are the primary interface for interacting with Bitcoin. As a developer, understanding how to build one ensures you grasp fundamental cryptographic principles and security considerations critical in decentralized applications.

Key Libraries Used

To streamline development, we use several well-maintained JavaScript/TypeScript libraries:

👉 Discover powerful tools to test your blockchain applications securely.


Project Setup and Dependencies

Start by creating a new directory:

mkdir Bitcoin_Cli_Wallet && cd Bitcoin_Cli_Wallet
npm init -y

Install TypeScript and execution tools:

npm install -D typescript ts-node

Add required dependencies:

npm install bitcoinjs-lib ecpair tiny-secp256k1 crypto-js dotenv

Create a tsconfig.json file to configure TypeScript compilation, then set up the following folder structure:

Bitcoin_Cli_Wallet/
├── wallets/
├── index.ts
└── .env

The wallets/ folder will store encrypted wallet files and history.


Environment Configuration

Create a .env file in the root directory:

ENCRYPTION_KEY=MySuperSecretKey
ALGORITHM=aes-256-cbc
🔐 Never commit .env files to version control. These contain sensitive data.

Load them in your code using dotenv.config().


Importing Dependencies

In index.ts, import all required modules:

import * as bitcoin from 'bitcoinjs-lib';
import { ECPairFactory, ECPairInterface } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import * as fs from 'fs';
import * as crypto from 'crypto-js';
import * as dotenv from 'dotenv';

dotenv.config();

Initialize the EC pair factory with secp256k1 support:

const ECPair = ECPairFactory(ecc);

Define network types for flexibility:

const TESTNET = bitcoin.networks.testnet;
const REGTEST = bitcoin.networks.regtest;
const BITCOIN = bitcoin.networks.bitcoin;

Wallet Creation Logic

We define a createWallet function that takes three parameters:

function createWallet(walletname: string, network: string, walletType: string) {
  let history = fetchWalletHistory();
  let keyPair = decodeNetwork(network);
  let walletData = decodeWalletType(walletType, keyPair, network);
  let address = walletData.address;

  // Update history
  history.totalWallets += 1;
  history.allWallets.push(walletname);
  history.allAddresses.push(address);

  // Save wallet and updated history
  writeWalletDataToFile(walletname, { keyPair, walletData, walletType });
  writeWalletHistoryToFile(history);

  console.log("Wallet created successfully. Address:", address);
}

This modular approach separates concerns: key generation, type decoding, and secure storage.


Decoding Network and Wallet Types

Selecting the Correct Blockchain Network

function decodeNetwork(network: string): ECPairInterface {
  switch (network) {
    case 'testnet': return ECPair.makeRandom({ network: TESTNET });
    case 'bitcoin': return ECPair.makeRandom({ network: BITCOIN });
    case 'regtest': return ECPair.makeRandom({ network: REGTEST });
    default: throw new Error('Invalid network type');
  }
}

Supporting Multiple Address Formats

Bitcoin supports various output scripts. Our wallet handles common ones:

function decodeWalletType(type: string, keyPair: ECPairInterface, network: string) {
  const net = network === 'testnet' ? TESTNET : BITCOIN;

  switch (type) {
    case 'p2pkh': return bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: net });
    case 'p2wpkh': return bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: net });
    case 'p2sh': return bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }), network: net });
    default: throw new Error('Unsupported wallet type');
  }
}

This extensible design allows future support for multisig or taproot (p2tr) wallets.


Secure Data Storage with Encryption

All wallet data must be encrypted before being written to disk.

Encrypting Wallet Data

function encryptData(data: string): string {
  const cipher = crypto.AES.encrypt(data, process.env.ENCRYPTION_KEY!);
  return cipher.toString();
}

Decrypting Stored Files

function decryptData(cipherText: string): string {
  const bytes = crypto.AES.decrypt(cipherText, process.env.ENCRYPTION_KEY!);
  return bytes.toString(crypto.enc.Utf8);
}

Files are saved as .json in the wallets/ directory with AES-256-CBC encryption.


Managing Wallet History

Track all created wallets with a central history.json file:

function writeWalletHistoryToFile(history: History): void {
  const encrypted = encryptData(JSON.stringify(history));
  fs.writeFileSync('./wallets/history.json', encrypted, 'utf8');
}

function fetchWalletHistory(): History {
  const path = './wallets/history.json';
  if (!fs.existsSync(path)) {
    const init: History = { totalWallets: 0, allWallets: [], allAddresses: [] };
    writeWalletHistoryToFile(init);
    return init;
  }

  const data = fs.readFileSync(path, 'utf8');
  const decrypted = decryptData(data);
  return JSON.parse(decrypted);
}

This ensures persistence across sessions and supports recovery workflows.


Frequently Asked Questions

Q: Can I use this wallet on mainnet?
A: Yes—but only after thorough testing on testnet or regtest. Always ensure your encryption is robust before handling real funds.

Q: How secure is AES-256 encryption for wallet storage?
A: AES-256 is industry-standard and considered highly secure when implemented correctly. The strength depends on your ENCRYPTION_KEY. Use a long, random secret—not a simple phrase.

Q: What happens if I lose my private key or encryption password?
A: There is no recovery mechanism in this CLI tool. Like all self-custodial wallets, you are your own bank. Losing keys means permanent loss of access.

Q: Is this wallet compatible with hardware wallets?
A: Not directly. This implementation is software-based. However, the same cryptographic principles apply to hardware integrations.

Q: Can I extend this to support Ethereum or other blockchains?
A: While the current code is Bitcoin-specific, similar patterns apply. Different chains use different libraries (e.g., ethers.js for Ethereum), but key management and encryption concepts remain consistent.

👉 Explore secure environments to test multi-chain applications.


Testing the Implementation

Add test code at the bottom of index.ts:

console.log("Starting wallet creation tests...");
createWallet("myWallet", "regtest", "p2pkh");
createWallet("BobWallet", "regtest", "p2pkh");
createWallet("AliceWallet", "regtest", "p2pkh");

listAllAddresses();

Compile and run:

npx tsc index.ts
node index.js

You should see success messages and a list of generated addresses.


Final Thoughts and Next Steps

This minimal CLI wallet demonstrates core concepts:

Future enhancements could include:

While this version doesn’t handle spending yet, it lays the foundation for a full-featured Bitcoin client.

👉 Accelerate your blockchain development with advanced tools and APIs.


Core Keywords:
Bitcoin wallet development, TypeScript crypto wallet, encrypted wallet storage, BitcoinJS tutorial, blockchain security, CLI wallet creation, secp256k1 cryptography