Skip to content

Latest commit

 

History

History
374 lines (282 loc) · 12.4 KB

File metadata and controls

374 lines (282 loc) · 12.4 KB

BIP32/BIP39 Utilities for NUT-13

Minimal Java library for BIP32 hierarchical deterministic key derivation and BIP39 mnemonic handling, specifically designed for NUT-13 (Cashu ecash) implementation.

Features

  • Full BIP39 Support: Generate, validate, and convert mnemonic phrases to seeds
  • Multi-Language Wordlists: English, French, Spanish, and Japanese support
  • BIP32 Derivation: Hierarchical deterministic key derivation
  • NUT-13 Specific: Purpose-built utilities for Cashu ecash secret derivation
  • Well-tested: 100+ tests with known BIP39/BIP32 test vectors
  • Minimal Dependencies: Uses only bitcoinj-core for cryptographic operations

Requirements

  • Java 21 or higher
  • Maven 3.6+

Installation

Build the project:

mvn clean install

Add to your pom.xml:

<dependency>
    <groupId>xyz.tcheeric</groupId>
    <artifactId>bip-utils</artifactId>
    <version>2.0.0</version>
</dependency>

Usage

BIP39 - Mnemonic Generation and Validation

import xyz.tcheeric.bips.bip39.Bip39;
import xyz.tcheeric.bips.bip39.WordList;

// Generate a random 12-word mnemonic (English)
String mnemonic = Bip39.generateMnemonic(12);
// Example: "witch collapse practice feed shame open despair creek road again ice least"

// Generate with different word counts (12, 15, 18, 21, or 24)
String mnemonic24 = Bip39.generateMnemonic(24);

// Generate in different languages
String frenchMnemonic = Bip39.generateMnemonic(12, WordList.french());
String spanishMnemonic = Bip39.generateMnemonic(12, WordList.spanish());
String japaneseMnemonic = Bip39.generateMnemonic(12, WordList.japanese());

// Validate a mnemonic
boolean isValid = Bip39.isValidMnemonic(mnemonic);

// Get detailed validation results
MnemonicValidator.ValidationResult result = Bip39.validateMnemonic(mnemonic);
if (!result.isValid()) {
    System.out.println("Error: " + result.getErrorMessage());
}

// Convert mnemonic to seed
byte[] seed = Bip39.mnemonicToSeed(mnemonic, ""); // No passphrase
byte[] seedWithPassphrase = Bip39.mnemonicToSeed(mnemonic, "my passphrase");

// Generate mnemonic from custom entropy
byte[] entropy = new byte[16]; // 16 bytes = 128 bits = 12 words
// ... fill entropy with secure random bytes
String customMnemonic = Bip39.generateMnemonic(entropy);

BIP32 - Hierarchical Deterministic Key Derivation

import xyz.tcheeric.bips.bip32.Bip32;
import xyz.tcheeric.bips.bip39.Bip39;
import org.bitcoinj.crypto.DeterministicKey;

// Generate mnemonic and derive master key
String mnemonic = Bip39.generateMnemonic(12);
DeterministicKey masterKey = Bip39.mnemonicToMasterKey(mnemonic, "");

// Or do it step by step
byte[] seed = Bip39.mnemonicToSeed(mnemonic, "");
DeterministicKey masterKey = Bip32.deriveMasterKey(seed);

// Derive child key using BIP32 path
DeterministicKey childKey = Bip32.deriveKey(masterKey, "m/44'/0'/0'/0/0");

// Get private key bytes
byte[] privateKey = Bip32.getPrivateKeyBytes(childKey);

// Get public key bytes (compressed)
byte[] publicKey = Bip32.getPublicKeyBytes(childKey, true);

// Get chain code
byte[] chainCode = Bip32.getChainCode(childKey);

NUT-13 Specific Derivation

NUT-13 defines a specific derivation path for Cashu ecash tokens:

Path Format: m/129372'/0'/{keyset_id_int}'/{counter}'/{0|1}

Where:

  • 129372' = Purpose (UTF-8 encoding of 🥜 peanut emoji)
  • 0' = Coin type (always 0, independent of ecash unit)
  • keyset_id_int' = Keyset ID converted to integer via modulo 2^31-1
  • counter' = Increments per successful mint (starts at 0)
  • 0 = Derive secret
  • 1 = Derive blinding factor (r)
import org.bitcoinj.crypto.DeterministicKey;
import xyz.tcheeric.bips.bip32.Bip32;
import xyz.tcheeric.bips.bip32.nut.Nut13Derivation;

String mnemonic = "your twelve word mnemonic phrase goes here for wallet recovery";
String keysetId = "009a1f293253e41e"; // Hex keyset ID from mint
int counter = 0; // Increment for each mint operation

Nut13Derivation.Nut13DerivationParams params = Nut13Derivation.Nut13DerivationParams.builder()
    .mnemonicPhrase(mnemonic)
    .passphrase("") // passphrase (empty if none)
    .keysetIdHex(keysetId)
    .counter(counter)
    .build();

// Derive secret
byte[] secret = Nut13Derivation.deriveSecretFromMnemonic(params);

// Derive blinding factor
byte[] blindingFactor = Nut13Derivation.deriveBlindingFactorFromMnemonic(params);

// Or derive both at once
byte[] seed = Bip32.mnemonicToSeed(mnemonic, "");
DeterministicKey masterKey = Bip32.deriveMasterKey(seed);
Nut13Derivation.SecretAndBlindingFactor pair =
    Nut13Derivation.deriveSecretAndBlindingFactor(masterKey, keysetId, counter);

System.out.println("Secret: " + pair.getSecretHex());
System.out.println("Blinding Factor: " + pair.getBlindingFactorHex());

Complete Workflow Example

import xyz.tcheeric.bips.*;
import org.bitcoinj.crypto.DeterministicKey;

public class CashuWallet {
    private final DeterministicKey masterKey;

    public CashuWallet(String mnemonic, String passphrase) {
        byte[] seed = Bip32.mnemonicToSeed(mnemonic, passphrase);
        this.masterKey = Bip32.deriveMasterKey(seed);
    }

    // Derive secrets for a mint operation
    public TokenSecrets mintToken(String keysetId, int counter) {
        byte[] secret = Nut13Derivation.deriveSecret(masterKey, keysetId, counter);
        byte[] blindingFactor = Nut13Derivation.deriveBlindingFactor(masterKey, keysetId, counter);

        return new TokenSecrets(secret, blindingFactor);
    }

    // Recover previously minted tokens
    public TokenSecrets recoverToken(String keysetId, int counter) {
        // Same derivation process - deterministic recovery
        return mintToken(keysetId, counter);
    }

    static class TokenSecrets {
        final byte[] secret;
        final byte[] blindingFactor;

        TokenSecrets(byte[] secret, byte[] blindingFactor) {
            this.secret = secret;
            this.blindingFactor = blindingFactor;
        }
    }
}

// Usage
CashuWallet wallet = new CashuWallet(
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
    ""
);

// First mint
TokenSecrets mint0 = wallet.mintToken("009a1f293253e41e", 0);

// Second mint (increment counter)
TokenSecrets mint1 = wallet.mintToken("009a1f293253e41e", 1);

// Recover from mnemonic after wallet loss
CashuWallet recoveredWallet = new CashuWallet(
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
    ""
);
TokenSecrets recovered = recoveredWallet.recoverToken("009a1f293253e41e", 0);
// recovered.secret will match mint0.secret

Keyset ID Conversion

// Convert keyset hex ID to integer for derivation
String keysetIdHex = "009a1f293253e41e";
int keysetIdInt = Nut13Derivation.keysetIdToInt(keysetIdHex);
// Result: 242349537

// Build the full derivation paths
String secretPath = Nut13Derivation.buildSecretDerivationPath(keysetIdInt, 0);
String blindingPath = Nut13Derivation.buildBlindingFactorDerivationPath(keysetIdInt, 0);
// secretPath: "m/129372'/0'/242349537'/0'/0"
// blindingPath: "m/129372'/0'/242349537'/0'/1"

Utility Methods

import xyz.tcheeric.bips.util.HexUtils;
import xyz.tcheeric.bips.util.CryptoUtils;

// Hex conversion (recommended: use utility classes)
byte[] bytes = HexUtils.fromHex("deadbeef");
String hex = HexUtils.toHex(bytes);

// Deprecated alternatives (still work but use HexUtils internally)
byte[] bytesOld = Bip32.hexToBytes("deadbeef");
String hexOld = Bip32.bytesToHex(bytes);

// SHA-256 hashing (recommended: use utility classes)
byte[] hash = CryptoUtils.sha256("hello world".getBytes());

// Deprecated alternative (still works but uses CryptoUtils internally)
byte[] hashOld = Bip32.sha256("hello world".getBytes());

// Parse BIP32 path
List<ChildNumber> path = Bip32.parsePath("m/44'/0'/0'/0/0");

Testing

Run all tests:

mvn -q verify

The suite covers:

  • BIP39 mnemonic generation and validation across supported wordlists and reference vectors
  • BIP32 derivation, path parsing, and key material extraction
  • NUT-13 deterministic derivation, counter management, and recovery scenarios

Project Structure

src/
├── main/
│   ├── java/xyz/tcheeric/bips/
│   │   ├── bip32/
│   │   │   ├── Bip32.java           # BIP32 utilities
│   │   │   └── nut/
│   │   │       └── Nut13Derivation.java  # NUT-13 Cashu derivation
│   │   └── bip39/
│   │       ├── Bip39.java           # High-level BIP39 API
│   │       ├── MnemonicGenerator.java
│   │       ├── MnemonicValidator.java
│   │       ├── SeedCalculator.java
│   │       └── WordList.java        # Multi-language wordlists
│   └── resources/bip39-wordlists/
│       ├── english.txt
│       ├── french.txt
│       ├── spanish.txt
│       └── japanese.txt
└── test/java/xyz/tcheeric/bips/
    ├── bip32/
    │   ├── Bip32Test.java
    │   └── nut/
    │       └── Nut13DerivationTest.java
    └── bip39/
        ├── Bip39Test.java
        ├── MnemonicGeneratorTest.java
        ├── MnemonicValidatorTest.java
        └── WordListTest.java

Dependencies

  • bitcoinj-core (0.17): BIP32/BIP39 cryptographic operations
  • slf4j-api (2.0.9): Logging API
  • jsr305 (3.0.2, provided): Null safety annotations
  • JUnit 5 (5.10.0, test): Testing framework
  • AssertJ (3.24.2, test): Fluent assertions

Documentation

Structure

docs/
├── README.md
├── tutorials/
│   └── getting-started.md
├── how-to/
│   └── implement-nut13.md
├── reference/
│   └── api-overview.md
└── explanation/
    ├── nut13-protocol.md
    └── security.md

The documentation suite follows the Diátaxis framework. Start at docs/README.md for an index of all resources.

Available Guides

Recent Updates

Documentation now reflects the refactored package structure:

  • All references to Bip32Utils were renamed to Bip32
  • NUT-13 imports now use xyz.tcheeric.bips.bip32.nut.Nut13Derivation
  • Project diagrams and examples match the current package layout
  • Code snippets were re-verified against the latest release

Expansion Roadmap

Upcoming documentation work items:

  • Tutorials: Understanding BIP39 Mnemonics, Understanding BIP32 Derivation
  • How-To Guides: Generate Multi-Language Mnemonics, Validate Mnemonic Phrases, Derive BIP32 Keys
  • Reference: Detailed BIP39, BIP32, and NUT-13 API references, Supported Languages, Error Codes
  • Explanations: Why BIP39 Matters, How BIP32 Works, Design Decisions

Maintenance

Keep the documentation current by updating API references alongside code changes, expanding how-to guides for new features, reviewing security guidance for new threats, and verifying code snippets whenever the library evolves.

Security Considerations

  1. Mnemonic Storage: Never store mnemonics in plain text. Use secure storage mechanisms.
  2. Private Keys: Private keys should be cleared from memory after use when possible.
  3. Passphrase: BIP39 passphrase adds additional security layer. Use strong passphrases.
  4. Counter Management: Maintain accurate counter state to avoid key reuse.
  5. Production Use: Always use cryptographically secure random number generators for mnemonic generation.

NUT-13 Specification

For complete NUT-13 specification, see: https://github.com/cashubtc/nuts/blob/main/13.md

License

This project is derived from bitcoin-utils and maintains compatibility with the original codebase.

References