Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

HD Wallets

Overview

Brane provides full support for BIP-39/BIP-44 hierarchical deterministic (HD) wallets through the MnemonicWallet class. HD wallets allow you to:

  • Generate a single mnemonic phrase that controls unlimited addresses
  • Restore all accounts from a single backup phrase
  • Derive addresses deterministically using standard paths
  • Use passphrases for additional security layers

Generating a New Wallet

Create a new wallet with a randomly generated mnemonic phrase:

import sh.brane.core.crypto.hd.MnemonicWallet;
 
// Generate a 12-word phrase (default, 128 bits of entropy)
MnemonicWallet wallet = MnemonicWallet.generatePhrase();
 
// Generate a 24-word phrase (256 bits of entropy)
MnemonicWallet wallet24 = MnemonicWallet.generatePhrase(24);
 
// Get the phrase - store this securely!
String phrase = wallet.phrase();

Supported word counts: 12, 15, 18, 21, or 24 words. More words provide more entropy but are harder to back up manually.

Restoring from a Phrase

Restore a wallet from an existing mnemonic phrase:

// Restore without passphrase
MnemonicWallet wallet = MnemonicWallet.fromPhrase(
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
);
 
// Restore with passphrase (creates different keys from same phrase)
MnemonicWallet walletWithPass = MnemonicWallet.fromPhrase(
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
    "my-secret-passphrase"
);

Passphrases

A passphrase provides an additional security layer:

  • Different passphrases derive completely different keys from the same mnemonic
  • Enables plausible deniability (reveal a decoy passphrase if coerced)
  • A forgotten passphrase means permanent loss of access to those keys
// Same phrase, different passphrases = different addresses
MnemonicWallet wallet1 = MnemonicWallet.fromPhrase(phrase, "");
MnemonicWallet wallet2 = MnemonicWallet.fromPhrase(phrase, "secret");
 
wallet1.derive(0).address(); // 0x1234...
wallet2.derive(0).address(); // 0xABCD... (completely different!)

Validating Phrases

Check if a phrase is valid before attempting to restore:

boolean isValid = MnemonicWallet.isValidPhrase(phrase);
if (!isValid) {
    throw new IllegalArgumentException("Invalid mnemonic phrase");
}

Validation checks:

  • Word count (12, 15, 18, 21, or 24)
  • All words are in the BIP-39 English wordlist
  • Checksum is correct

Deriving Addresses

Simple Derivation

Derive signers using an address index (uses account 0):

MnemonicWallet wallet = MnemonicWallet.fromPhrase(phrase);
 
// Derive first 5 addresses: m/44'/60'/0'/0/0 through m/44'/60'/0'/0/4
Signer account0 = wallet.derive(0);
Signer account1 = wallet.derive(1);
Signer account2 = wallet.derive(2);
 
System.out.println(account0.address()); // First address
System.out.println(account1.address()); // Second address

Custom Derivation Paths

Use DerivationPath for full control over the BIP-44 path:

import sh.brane.core.crypto.hd.DerivationPath;
 
// m/44'/60'/0'/0/5 - Address index 5 of account 0
DerivationPath path1 = DerivationPath.of(5);
 
// m/44'/60'/1'/0/0 - First address of account 1
DerivationPath path2 = new DerivationPath(1, 0);
 
// m/44'/60'/2'/0/3 - Address index 3 of account 2
DerivationPath path3 = new DerivationPath(2, 3);
 
Signer signer = wallet.derive(path2);

Parsing Path Strings

Parse standard BIP-44 path strings:

// Parse from string format
DerivationPath path = DerivationPath.parse("m/44'/60'/0'/0/0");
 
// Get path string
String pathStr = path.toPath(); // "m/44'/60'/0'/0/0"

Using with Brane.Signer

Derived signers work seamlessly with Brane.Signer for transactions:

import sh.brane.rpc.Brane;
import sh.brane.core.types.Wei;
 
// Create wallet and derive signer
MnemonicWallet wallet = MnemonicWallet.fromPhrase(phrase);
Signer signer = wallet.derive(0);
 
// Connect to chain
Brane.Signer client = Brane.connect("http://127.0.0.1:8545", signer);
 
// Send transaction
var receipt = client.sendTransactionAndWait(TxBuilder.eip1559()
    .to(recipient)
    .value(Wei.fromEther(new java.math.BigDecimal("0.1")))
    .build());

Security Considerations

Phrase Storage

  • Store phrases offline (paper, metal backup, hardware wallet)
  • Never store in plain text files, environment variables, or version control
  • Consider splitting with Shamir's Secret Sharing for high-value wallets

Lifecycle Management

MnemonicWallet implements Destroyable for explicit cleanup:

MnemonicWallet wallet = MnemonicWallet.fromPhrase(phrase);
try {
    Signer signer = wallet.derive(0);
    // Use signer...
} finally {
    wallet.destroy();
}
 
// After destroy(), derive() throws IllegalStateException
wallet.derive(1); // throws!

String Immutability Caveat

The destroy() method zeros the master private key bytes and chain code, but the phrase String remains in memory until garbage collected.

Derived Signers Are Independent

Derived Signer instances are independent of the wallet:

MnemonicWallet wallet = MnemonicWallet.fromPhrase(phrase);
Signer signer0 = wallet.derive(0);
Signer signer1 = wallet.derive(1);
 
// Destroy wallet - signers still work
wallet.destroy();
 
signer0.address();     // Still works
signer0.signMessage(); // Still works
 
// But can't derive new signers
wallet.derive(2);      // throws IllegalStateException

This allows you to derive needed signers early, then destroy the wallet to minimize exposure.

Signer Cleanup

Derived signers are PrivateKeySigner instances that also support Destroyable:

Signer signer = wallet.derive(0);
PrivateKeySigner pks = (PrivateKeySigner) signer;
 
// When done with this signer
pks.destroy();
 
pks.signMessage(msg); // throws IllegalStateException

Complete Example

import sh.brane.core.crypto.Signer;
import sh.brane.core.crypto.hd.DerivationPath;
import sh.brane.core.crypto.hd.MnemonicWallet;
import sh.brane.rpc.Brane;
 
public class HdWalletExample {
    public static void main(String[] args) {
        // Generate new wallet (in production, restore from secure storage)
        MnemonicWallet wallet = MnemonicWallet.generatePhrase();
 
        try {
            // IMPORTANT: Save this phrase securely!
            System.out.println("Backup phrase: " + wallet.phrase());
 
            // Derive multiple accounts
            Signer mainAccount = wallet.derive(0);
            Signer savingsAccount = wallet.derive(1);
            Signer tradingAccount = wallet.derive(new DerivationPath(2, 0));
 
            System.out.println("Main account:    " + mainAccount.address());
            System.out.println("Savings account: " + savingsAccount.address());
            System.out.println("Trading account: " + tradingAccount.address());
 
            // Connect and transact
            Brane.Signer client = Brane.connect("http://127.0.0.1:8545", mainAccount);
            System.out.println("Balance: " + client.getBalance(mainAccount.address()));
 
        } finally {
            wallet.destroy();
        }
    }
}

API Reference

MnemonicWallet

MethodDescription
generatePhrase()Generate wallet with 12-word phrase
generatePhrase(int wordCount)Generate wallet with specified word count
fromPhrase(String phrase)Restore wallet from phrase
fromPhrase(String phrase, String passphrase)Restore with passphrase
isValidPhrase(String phrase)Validate a mnemonic phrase
derive(int addressIndex)Derive signer at index (account 0)
derive(DerivationPath path)Derive signer at custom path
phrase()Get the mnemonic phrase
destroy()Zero sensitive key material
isDestroyed()Check if destroyed

DerivationPath

MethodDescription
of(int addressIndex)Create path with account 0
new DerivationPath(int account, int addressIndex)Create path with custom account
parse(String path)Parse path string (e.g., "m/44'/60'/0'/0/0")
toPath()Get path as string
account()Get account index
addressIndex()Get address index