# agehd - Deterministic Age Identities from BIP85 The `agehd` package derives deterministic X25519 age identities using BIP85 entropy derivation and a deterministic random number generator (DRNG). This package only supports proper BIP85 sources: BIP39 mnemonics and extended private keys (xprv). ## Features - **Deterministic key generation**: Same input always produces the same age identity - **BIP85 compliance**: Uses the BIP85 standard for entropy derivation - **Multiple key support**: Generate multiple keys from the same source using different indices - **Two BIP85 input methods**: Support for BIP39 mnemonics and extended private keys (xprv) - **Vendor/application scoped**: Uses vendor-specific derivation paths to avoid conflicts ## Derivation Path The package uses the following BIP85 derivation path: ``` m/83696968'/592366788'/733482323'/n' ``` Where: - `83696968'` is the BIP85 root path ("bip" in ASCII) - `592366788'` is the vendor ID (sha256("berlin.sneak") & 0x7fffffff) - `733482323'` is the application ID (sha256("secret") & 0x7fffffff) - `n'` is the sequential index (0, 1, 2, ...) ## Usage ### From BIP39 Mnemonic ```go package main import ( "fmt" "log" "git.eeqj.de/sneak/secret/pkg/agehd" ) func main() { mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" // Derive the first identity (index 0) identity, err := agehd.DeriveIdentity(mnemonic, 0) if err != nil { log.Fatal(err) } fmt.Printf("Secret key: %s\n", identity.String()) fmt.Printf("Public key: %s\n", identity.Recipient().String()) } ``` ### From Extended Private Key (XPRV) ```go package main import ( "fmt" "log" "git.eeqj.de/sneak/secret/pkg/agehd" ) func main() { xprv := "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb" // Derive the first identity (index 0) from the xprv identity, err := agehd.DeriveIdentityFromXPRV(xprv, 0) if err != nil { log.Fatal(err) } fmt.Printf("Secret key: %s\n", identity.String()) fmt.Printf("Public key: %s\n", identity.Recipient().String()) } ``` ### Multiple Identities ```go package main import ( "fmt" "log" "git.eeqj.de/sneak/secret/pkg/agehd" ) func main() { mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" // Derive multiple identities with different indices for i := uint32(0); i < 3; i++ { identity, err := agehd.DeriveIdentity(mnemonic, i) if err != nil { log.Fatal(err) } fmt.Printf("Identity %d: %s\n", i, identity.Recipient().String()) } } ``` ### From Raw Entropy ```go package main import ( "fmt" "log" "git.eeqj.de/sneak/secret/pkg/agehd" ) func main() { mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" // First derive entropy using BIP85 entropy, err := agehd.DeriveEntropy(mnemonic, 0) if err != nil { log.Fatal(err) } // Then create identity from entropy identity, err := agehd.IdentityFromEntropy(entropy) if err != nil { log.Fatal(err) } fmt.Printf("Secret key: %s\n", identity.String()) fmt.Printf("Public key: %s\n", identity.Recipient().String()) } ``` ## API Reference ### Functions #### `DeriveIdentity(mnemonic string, n uint32) (*age.X25519Identity, error)` Derives a deterministic age identity from a BIP39 mnemonic and index. - `mnemonic`: A valid BIP39 mnemonic phrase - `n`: The derivation index (0, 1, 2, ...) - Returns: An age X25519 identity or an error #### `DeriveIdentityFromXPRV(xprv string, n uint32) (*age.X25519Identity, error)` Derives a deterministic age identity from an extended private key (xprv) and index. - `xprv`: A valid extended private key in xprv format - `n`: The derivation index (0, 1, 2, ...) - Returns: An age X25519 identity or an error #### `DeriveEntropy(mnemonic string, n uint32) ([]byte, error)` Derives 32 bytes of entropy from a BIP39 mnemonic and index using BIP85. - `mnemonic`: A valid BIP39 mnemonic phrase - `n`: The derivation index - Returns: 32 bytes of entropy or an error #### `DeriveEntropyFromXPRV(xprv string, n uint32) ([]byte, error)` Derives 32 bytes of entropy from an extended private key (xprv) and index using BIP85. - `xprv`: A valid extended private key in xprv format - `n`: The derivation index - Returns: 32 bytes of entropy or an error #### `IdentityFromEntropy(ent []byte) (*age.X25519Identity, error)` Converts 32 bytes of entropy into an age X25519 identity. - `ent`: Exactly 32 bytes of entropy - Returns: An age X25519 identity or an error ## Implementation Details 1. **BIP85 Entropy Derivation**: The package uses the BIP85 standard to derive 64 bytes of entropy from the input source 2. **DRNG**: A BIP85 DRNG (Deterministic Random Number Generator) using SHAKE256 is seeded with the 64-byte entropy 3. **Key Generation**: 32 bytes are read from the DRNG to generate the age private key 4. **RFC-7748 Clamping**: The private key is clamped according to RFC-7748 for X25519 5. **Bech32 Encoding**: The key is encoded using Bech32 with the "age-secret-key-" prefix ## Security Considerations - The same mnemonic/xprv and index will always produce the same identity - Different indices produce cryptographically independent identities - The vendor/application scoping prevents conflicts with other BIP85 applications - The DRNG ensures high-quality randomness for key generation - Private keys are properly clamped for X25519 usage - Only accepts proper BIP85 sources (mnemonics and xprv keys), not arbitrary passphrases ## Testing Run the tests with: ```bash go test -v ./internal/agehd ```