secret/internal/bip85/README.md
2025-05-28 04:02:55 -07:00

4.0 KiB

BIP85 - Deterministic Entropy From BIP32 Keychains

This package implements BIP85, which allows for deterministic derivation of entropy from a BIP32 master key. This enables a single seed to generate multiple wallet keys, mnemonics, and random values in a fully deterministic way.

Overview

BIP85 enables a variety of use cases:

  • Generate multiple BIP39 mnemonic seeds from a single master key
  • Derive Bitcoin HD wallet seeds (WIF format)
  • Create extended private keys (XPRV)
  • Generate deterministic random values for dice rolls, hex values, and passwords

Usage Examples

Initialization

import (
    "fmt"
    "git.eeqj.de/sneak/secret/internal/bip85"
    "github.com/btcsuite/btcd/btcutil/hdkeychain"
)

// Parse an existing master key
masterKeyStr := "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb"
masterKey, err := bip85.ParseMasterKey(masterKeyStr)
if err != nil {
    panic(err)
}

Derive BIP39 Mnemonic

// Parameters:
// - language (0 = English, 1 = Japanese, 2 = Korean, 3 = Spanish, etc.)
// - number of words (12, 15, 18, 21, or 24)
// - index (allows multiple seeds of the same type)
entropy, err := bip85.DeriveBIP39Entropy(masterKey, 0, 12, 0)
if err != nil {
    panic(err)
}

// Use the entropy with github.com/tyler-smith/go-bip39
// to generate a mnemonic
mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
    panic(err)
}
fmt.Println("12-word BIP39 mnemonic:", mnemonic)

Derive HD-WIF Key

// Create a WIF format key for Bitcoin Core's hdseed
wif, err := bip85.DeriveWIFKey(masterKey, 0)
if err != nil {
    panic(err)
}
fmt.Println("WIF Key:", wif)

Derive XPRV

// Create an extended private key (XPRV)
xprv, err := bip85.DeriveXPRV(masterKey, 0)
if err != nil {
    panic(err)
}
fmt.Println("XPRV:", xprv.String())

Generate Hex Data

// Generate arbitrary hex data (16-64 bytes)
hex, err := bip85.DeriveHex(masterKey, 32, 0)
if err != nil {
    panic(err)
}
fmt.Println("32 bytes of hex:", hex)

Dice Rolls

// Generate dice rolls
// sides: number of sides on the die
// rolls: number of rolls to generate
// index: allows multiple sets of the same type
rolls, err := bip85.DeriveDiceRolls(masterKey, 6, 10, 0)
if err != nil {
    panic(err)
}
fmt.Print("10 rolls of a 6-sided die: ")
for _, roll := range rolls {
    fmt.Print(roll, " ")
}
fmt.Println()

DRNG (Deterministic Random Number Generator)

// First derive entropy
path := "m/83696968'/0'/0'"
entropy, err := bip85.DeriveBIP85Entropy(masterKey, path)
if err != nil {
    panic(err)
}

// Create a deterministic random number generator
drng := bip85.NewBIP85DRNG(entropy)

// Read arbitrary amount of random bytes
buffer := make([]byte, 32)
_, err = drng.Read(buffer)
if err != nil {
    panic(err)
}
fmt.Printf("Random bytes: %x\n", buffer)

BIP85 Paths

The derivation paths follow the format:

m/83696968'/{app}'/{parameters}

Where:

  • 83696968' is the BIP85 root path (BIP in ASCII)
  • {app}' is the application number:
    • 39' for BIP39 mnemonics
    • 2' for HD-WIF keys
    • 32' for XPRV
    • 128169' for HEX data
    • 707764' for Base64 passwords
    • 707785' for Base85 passwords
    • 89101' for dice rolls
    • 828365' for RSA keys
  • {parameters} are application-specific parameters

Test Vectors

This implementation passes all the test vectors from the BIP85 specification:

  • Basic test cases
  • BIP39 12, 18, and 24 word mnemonics
  • HD-WIF keys
  • XPRV
  • SHAKE256 DRNG output
  • Dice rolls

Run the tests with verbose output to see the test vectors and results:

go test -v git.eeqj.de/sneak/secret/internal/bip85

References