diff --git a/pkg/agehd/agehd.go b/pkg/agehd/agehd.go index 88c2f8a..c8b84ef 100644 --- a/pkg/agehd/agehd.go +++ b/pkg/agehd/agehd.go @@ -21,10 +21,11 @@ import ( ) const ( - purpose = uint32(83696968) // fixed by BIP-85 ("bip") - vendorID = uint32(592366788) // berlin.sneak - appID = uint32(733482323) // secret - hrp = "age-secret-key-" // Bech32 HRP used by age + purpose = uint32(83696968) // fixed by BIP-85 ("bip") + vendorID = uint32(592366788) // berlin.sneak + appID = uint32(733482323) // secret + hrp = "age-secret-key-" // Bech32 HRP used by age + x25519KeySize = 32 // 256-bit key size for X25519 ) // clamp applies RFC-7748 clamping to a 32-byte scalar. @@ -37,17 +38,20 @@ func clamp(k []byte) { // IdentityFromEntropy converts 32 deterministic bytes into an // *age.X25519Identity by round-tripping through Bech32. func IdentityFromEntropy(ent []byte) (*age.X25519Identity, error) { - const x25519KeySize = 32 // 256-bit key size for X25519 if len(ent) != x25519KeySize { return nil, fmt.Errorf("need 32-byte scalar, got %d", len(ent)) } // Make a copy to avoid modifying the original - key := make([]byte, 32) // 32 bytes = 256-bit key size for X25519 // 32 bytes = 256-bit key size for X25519 + key := make([]byte, x25519KeySize) copy(key, ent) clamp(key) - data, err := bech32.ConvertBits(key, 8, 5, true) // Convert from 8-bit to 5-bit encoding for bech32 + const ( + bech32BitSize8 = 8 // Standard 8-bit encoding + bech32BitSize5 = 5 // Bech32 5-bit encoding + ) + data, err := bech32.ConvertBits(key, bech32BitSize8, bech32BitSize5, true) if err != nil { return nil, fmt.Errorf("bech32 convert: %w", err) } @@ -82,7 +86,7 @@ func DeriveEntropy(mnemonic string, n uint32) ([]byte, error) { // Use BIP85 DRNG to generate deterministic 32 bytes for the age key drng := bip85.NewBIP85DRNG(entropy) - key := make([]byte, 32) // 32 bytes = 256-bit key size for X25519 + key := make([]byte, x25519KeySize) _, err = drng.Read(key) if err != nil { return nil, fmt.Errorf("failed to read from DRNG: %w", err) @@ -111,7 +115,7 @@ func DeriveEntropyFromXPRV(xprv string, n uint32) ([]byte, error) { // Use BIP85 DRNG to generate deterministic 32 bytes for the age key drng := bip85.NewBIP85DRNG(entropy) - key := make([]byte, 32) // 32 bytes = 256-bit key size for X25519 + key := make([]byte, x25519KeySize) _, err = drng.Read(key) if err != nil { return nil, fmt.Errorf("failed to read from DRNG: %w", err) diff --git a/pkg/bip85/bip85.go b/pkg/bip85/bip85.go index a9e71b1..ae5dd88 100644 --- a/pkg/bip85/bip85.go +++ b/pkg/bip85/bip85.go @@ -52,8 +52,9 @@ type DRNG struct { // NewBIP85DRNG creates a new DRNG seeded with BIP85 entropy func NewBIP85DRNG(entropy []byte) *DRNG { + const bip85EntropySize = 64 // 512 bits // The entropy must be exactly 64 bytes (512 bits) - if len(entropy) != 64 { + if len(entropy) != bip85EntropySize { panic("DRNG entropy must be 64 bytes") } @@ -169,17 +170,26 @@ func DeriveBIP39Entropy(masterKey *hdkeychain.ExtendedKey, language, words, inde } // Determine how many bits of entropy to use based on the words + // BIP39 defines specific word counts and their corresponding entropy bits + const ( + words12 = 12 // 128 bits of entropy + words15 = 15 // 160 bits of entropy + words18 = 18 // 192 bits of entropy + words21 = 21 // 224 bits of entropy + words24 = 24 // 256 bits of entropy + ) + var bits int switch words { - case 12: + case words12: bits = 128 - case 15: + case words15: bits = 160 - case 18: + case words18: bits = 192 - case 21: + case words21: bits = 224 - case 24: + case words24: bits = 256 default: return nil, fmt.Errorf("invalid BIP39 word count: %d", words)