# Building Signet: My Journey Creating an Ethereum Wallet in Rust 🦀 > *What happens when you stop reading about cryptography and start implementing it* > I knew *what* cryptocurrency wallets did and do. I'd been building DeFi UIs and dApps for years, integrating wallet connections, handling transactions, managing user balances and state management for wallets and balances. > But I wanted to know *how*. I'm always curious to know how? > But I'd never actually implemented ECDSA signing. Never derived a key from a mnemonic. Never RLP-encoded a transaction. I'd just called `web3.signTransaction()` and trusted the magic. As I started looking toward protocol engineering, I realized this was a problem. You can't build the infrastructure layer if you don't understand the primitives. So I made a deal with myself: **No more abstractions. Build it yourself.** > Three months later, I have Signet, a fully-functional Ethereum wallet written in Rust, implementing every cryptographic primitive and protocol standard myself. Turns out, the **"magic"** is just really clever math. And implementing it taught me more than a year of reading docs ever could. > Here's how it went. 🚀 ## 🤔 What Even Is a Wallet? Before we dive in, let's get super clear on what a cryptocurrency wallet actually is. ### The Simple Explanation Imagine you have a **magic diary** with two special pages: 1. **The Secret Page** (Private Key) - This page has a magic spell that only YOU know. With this spell, you can sign your name on anything and prove it's really you. 2. **The Public Page** (Public Key/Address) - This is like your home address that you give to friends so they can send you mail. Anyone can see it, but they can't use it to take your stuff. **That's a wallet!** It's just a secure way to: - Store your secret spell (private key) - Generate your public address - Sign transactions to prove "yes, I really want to send this money" ### In Technical Terms A cryptocurrency wallet is: - A **private key** (256 random bits) - A **public key** (derived from the private key using elliptic curve cryptography) - An **address** (the last 20 bytes of the public key's hash) - Tools to **sign transactions** and messages No coins actually "live" in your wallet - they live on the blockchain. Your wallet just proves you own them! --- ## Why I Built Signet I had three big goals: ### 1. **Learn Protocol Engineering** I wanted to transition from building DeFi frontends to understanding the **deep infrastructure** layer. How do blockchains actually work at the protocol level? ### 2. **Understand the Standards** Crypto is full of mysterious acronyms: EIP-191, BIP39, BIP32, secp256k1. I wanted to implement these standards myself instead of just using libraries blindly. ### 3. **Build Something Real** Reading docs is boring. I learn best by **building things that actually work**. I wanted a wallet I could actually use (on testnet, at least!). --- ## What I Built Signet is a **complete Ethereum wallet** with three major features: ### Phase 1: Core Wallet Functions - Generate random private keys - Create Ethereum addresses - Sign messages (like "I agree to these terms") - Save keys securely with password encryption **Think of this as:** The basic "magic diary" with your secret spell and public address. ### Phase 2: HD Wallets (Seed Phrases) - Generate those 12-word recovery phrases you've seen - Create multiple accounts from one phrase - Follow the BIP39 and BIP32 standards **Think of this as:** Instead of remembering 10 different secret spells, you remember ONE master spell that can create all the others! ### Phase 3: Transactions - Build Ethereum transactions - Sign them with your private key - Support both old and new transaction types (Legacy & EIP-1559) **Think of this as:** Actually using your magic spell to move money around! --- ## The Building Journey Let me walk you through how I built this, step by step. ### Step 1: Understanding Cryptography First, I needed to understand **elliptic curve cryptography**. This is the "magic" that lets you: - Generate a public key from a private key (easy) - But NOT go backwards from public to private (impossible!) I used the `k256` crate which implements `secp256k1` - the specific curve Ethereum uses. ```rust // Generate a random private key (32 bytes) let private_key = SigningKey::random(&mut OsRng); // Derive the public key (this is the math magic!) let public_key = private_key.verifying_key(); ``` **What I learned:** Elliptic curves are like **one-way doors**. Easy to go through, impossible to go back. That's what makes crypto secure! ### Step 2: Creating Addresses Ethereum addresses are just: 1. Take the public key 2. Hash it with Keccak256 3. Take the last 20 bytes 4. Done! ```rust let public_key_bytes = public_key.to_encoded_point(false); let hash = keccak256(&public_key_bytes[1..]); // Skip first byte let address = &hash[12..]; // Last 20 bytes = address! ``` **What I learned:** Those long addresses like `0x742d35...` aren't random! They're mathematically derived from your public key. ### Step 3: Signing Messages Signing is how you prove "yes, I really sent this." It's like a digital signature that can't be forged. The tricky part? Ethereum has a special format called **EIP-191** that wraps your message: ```rust let wrapped = format!( "\x19Ethereum Signed Message:\n{}{}", message.len(), message ); ``` his prevents someone from tricking you into signing a transaction when you think you're just signing a login! ### Step 4: Seed Phrases (The Hard Part!) This is where things got interesting. Those 12-word phrases you see? Here's how they work: 1. **Generate random data** (128-256 bits) 2. **Convert to words** using a standard wordlist (BIP39) 3. **Turn words into a seed** using a password-stretching function 4. **Derive infinite keys** from that seed (BIP32) ```rust // 12 words = 128 bits of entropy let mut entropy = vec![0u8; 16]; rand::thread_rng().fill_bytes(&mut entropy); // Convert to mnemonic let mnemonic = Mnemonic::from_entropy(&entropy)?; // "scissors piece stage security whale actor..." println!("{}", mnemonic); ``` **What I learned:** Those 12 words represent your entire wallet! They can generate billions of addresses. That's why losing them means losing everything. ### Step 5: Transactions Finally, I needed to build actual Ethereum transactions. These are just structured data that gets: 1. **RLP encoded** (a special Ethereum format) 2. **Hashed** with Keccak256 3. **Signed** with your private key 4. **Broadcast** to the network ```rust let tx = LegacyTransaction { nonce: 0, gas_price: 21_000_000_000, // 21 gwei gas_limit: 21_000, to: Some(recipient_address), value: 1_000_000_000_000_000_000, // 1 ETH data: vec![], chain_id: 1, // Mainnet }; let signed = tx.sign(&private_key)?; ``` ## The Bugs (Where Real Learning Happens!) Building this wasn't smooth sailing. Here are the big bugs I hit and how I solved them: ### Bug #1: The bip39 API Mystery **The Problem:** ```rust // My code: let mnemonic = Mnemonic::generate(MnemonicType::Words12, Language::English); // Compiler: error: no function named `generate` found! ``` I was following tutorials that used an older version of the `bip39` crate. The API completely changed in v2.2.0! **The Solution:** Instead of using built-in types, I had to: 1. Generate random entropy myself 2. Feed it to `Mnemonic::from_entropy()` ```rust // Generate 16 bytes of randomness let mut entropy = vec![0u8; 16]; rand::thread_rng().fill_bytes(&mut entropy); // Convert to mnemonic let mnemonic = Mnemonic::from_entropy(&entropy)?; ``` **What I learned:** Crypto libraries change their APIs. Always check the docs for YOUR version, not random tutorials! ### Bug #2: The Private Key is Private! **The Problem:** ```rust let private_key_bytes = xpriv.key(); // Get private key // Compiler: error: field `key` is private! ``` The BIP32 library intentionally makes private keys hard to access (for security). But I needed them to sign transactions! **The Solution:** I had to decode the extended key format manually: ```rust // Extended keys are base58 encoded let xpriv_str = derived.to_string(); let decoded = bs58::decode(&xpriv_str).into_vec()?; // Extract bytes 46-78 (the actual private key) let private_key_bytes = &decoded[46..78]; ``` **What I learned:** Sometimes you need to understand the **wire format** of cryptographic data structures. Extended keys follow the BIP32 specification exactly. ### Bug #3: Invalid Signature Recovery **The Problem:** ```rust // Sign a message let signature = wallet.sign_message(b"Hello")?; // Verify it assert!(wallet.verify_signature(b"Hello", &signature)?); // Result: Error: InvalidSignature ``` My signatures weren't verifying! The issue was with the **recovery ID** (the "v" value that helps recover the public key). **The Solution:** Instead of manually calculating the recovery ID, I used k256's built-in function: ```rust // OLD (broken): let signature = signing_key.sign(message_hash); let recovery_id = calculate_manually(...); // Error-prone! // NEW (works): let (signature, recovery_id) = signing_key .sign_prehash_recoverable(&message_hash)?; ``` **What I learned:** Don't reinvent crypto primitives! Use the library's built-in functions when they exist. ### Bug #4: The 39-Character Address 🤦‍♂️ **The Problem:** ```rust let address = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"; // 39 chars Error: InvalidAddress("Address must be 40 hex characters") ``` I had typo'd a test address. Ethereum addresses must be EXACTLY 40 hex characters (20 bytes). **The Solution:** ```rust // Fixed - added the missing '0' let address = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0"; // 40 chars ``` **What I learned:** Ethereum addresses are precisely 20 bytes. No more, no less. Also, write tests that use GENERATED addresses, not hard-coded ones! --- ## What I Actually Learned Beyond just the code, here's what this project taught me: ### 1. **Cryptography is Not Magic** It's just math! Intimidating math, but learnable math. Elliptic curves, hash functions, digital signatures, they're all based on simple principles applied cleverly. ### 2. **Standards Matter** EIP-155, EIP-191, BIP39, BIP32 - these aren't just boring documents. They're carefully designed specifications that make everything work together. Implementing them taught me WHY each decision was made. ### 3. **Security is Hard** So many ways to mess up: - Using bad randomness - Leaking private keys in logs - Not validating inputs - Timing attacks on comparisons Writing crypto code makes you paranoid. That's good! ### 4. **Rust is Perfect for This** Rust's type system caught SO many bugs: - Can't accidentally use a private key as a public key - Can't forget to handle errors - Memory safety guaranteed If I'd done this in JavaScript, I'd still be debugging. ### 5. **Documentation is Your Friend** The Ethereum Yellow Paper, BIP specifications, RFCs - these are treasure troves. Reading the actual spec is often clearer than blog posts. --- ## The Bigger Picture Building Signet wasn't just about making a wallet. It was about understanding the **foundation** of blockchain technology: - **How do wallets prove ownership?** → Digital signatures - **How do seed phrases work?** → Deterministic key derivation - **How do transactions get structured?** → RLP encoding + signing - **How does Ethereum prevent replay attacks?** → Chain ID in signatures Now when I work on DeFi protocols at Own Protocol, I understand what's happening at every layer. I'm not just calling `web3.eth.sendTransaction()` - I know exactly what that function does under the hood! --- ## What's Next? Signet is complete, but my learning journey isn't! Here's what I'm building next: ### Short Term - **RPC Client** - Connect to actual Ethereum nodes - **EIP-712** - Sign structured data (used by DeFi protocols) - **Hardware Wallet Support** - Integrate with Ledger/Trezor ### Long Term - **P2P Networking** - Build a simple P2P node using libp2p - **Consensus Mechanisms** - Implement a toy blockchain with PoW - **Mini EVM** - Build a simple virtual machine for smart contracts The goal? Understand blockchain **from the ground up** - from cryptography all the way to consensus. --- ## Check It Out! ### Repository **GitHub:** [Signet - Ethereum Wallet in Rust](https://github.com/dicethedev/signet) ```bash # Clone and try it yourself! git clone https://github.com/dicethedev/signet cd signet # Run examples cargo run --example basic_usage # Try the CLI cargo run -- new # Generate a wallet cargo run -- hd # Create HD wallet with seed phrase ``` ### Resources That Helped Me **Ethereum Fundamentals:** - [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf) - The technical spec - [Mastering Ethereum](https://github.com/ethereumbook/ethereumbook) - Andreas Antonopoulos' book - [ethereum.org Developer Docs](https://ethereum.org/en/developers/docs/) - Official docs **Cryptography:** - [Practical Cryptography for Developers](https://cryptobook.nakov.com/) - Great intro - [Understanding Cryptography](http://www.crypto-textbook.com/) - Deeper dive **Rust for Blockchain:** - [The Rust Book](https://doc.rust-lang.org/book/) - Learn Rust - [Rust Blockchain Dev](https://rustinblockchain.org/) - Community resources **Standards (Read These!):** - [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) - Mnemonic phrases - [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) - HD wallets - [EIP-155](https://eips.ethereum.org/EIPS/eip-155) - Replay protection - [EIP-191](https://eips.ethereum.org/EIPS/eip-191) - Signed messages - [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) - New transaction type ### Tech Stack - **Language:** Rust 🦀 - **Crypto:** k256 (secp256k1), sha3, sha2 - **HD Wallets:** bip39, coins-bip32 - **Encoding:** hex, bs58, rlp - **CLI:** clap --- ## Final Thoughts Three months ago, wallets were black boxes to me. Now I can: - Generate keys from scratch - Understand how seed phrases work - Sign transactions manually - Read and understand EIPs But more importantly, I learned that **blockchain isn't magic**. It's just: - Really good cryptography - Clever data structures - Carefully designed protocols - A lot of attention to security If you're interested in blockchain protocol engineering, **start building**! Don't just read about it. Pick something that interests you and implement it from scratch. You'll learn 10x more than reading docs. And if you get stuck? That's where the real learning happens! Every bug is a lesson. Every compiler error is teaching you something. --- ## Acknowledgments Huge thanks to: - The Rust community for amazing crates - The Ethereum Foundation for detailed specs - Everyone who's written about blockchain internals --- ## Let's Connect! Built something similar? Have questions? Want to chat about protocol engineering? - **Twitter:** [@dicethedev](https://twitter.com/dicethedev) - **GitHub:** [dicethedev](https://github.com/dicethedev) Drop me a message - I'd love to hear about your blockchain learning journey! --- ## License MIT License - Feel free to use this for learning!