bip39.mx raw

   1  // Package bip39 implements BIP-39 mnemonic encoding, validation, and seed derivation.
   2  // https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
   3  //
   4  // Currently supports 12-word mnemonics (128-bit entropy + 4-bit checksum).
   5  // Wordlist: English only (Wordlist.English).
   6  package bip39
   7  
   8  import (
   9  	"crypto/pbkdf2"
  10  	"crypto/sha256"
  11  	"crypto/sha512"
  12  )
  13  
  14  // EntropyToMnemonic converts 16 bytes (128 bits) of entropy into a 12-word
  15  // English BIP-39 mnemonic. Returns "" on bad input length.
  16  func EntropyToMnemonic(entropy []byte) string {
  17  	if len(entropy) != 16 {
  18  		return ""
  19  	}
  20  	hash := sha256.Sum(entropy)
  21  	all := []byte{:17}
  22  	copy(all, entropy)
  23  	all[16] = hash[0]
  24  	eng := English()
  25  	buf := []byte{:0}
  26  	for i := 0; i < 12; i++ {
  27  		bitPos := i * 11
  28  		byteIdx := bitPos / 8
  29  		bitOff := uint(bitPos % 8)
  30  		var val uint32
  31  		val = uint32(all[byteIdx]) << 16
  32  		if byteIdx+1 < 17 {
  33  			val |= uint32(all[byteIdx+1]) << 8
  34  		}
  35  		if byteIdx+2 < 17 {
  36  			val |= uint32(all[byteIdx+2])
  37  		}
  38  		idx := (val << bitOff >> 13) & 0x7FF
  39  		if i > 0 {
  40  			buf = append(buf, ' ')
  41  		}
  42  		buf = append(buf, eng[idx]...)
  43  	}
  44  	return string(buf)
  45  }
  46  
  47  // ValidateMnemonic returns true if phrase is a valid 12-word BIP-39 English
  48  // mnemonic with a correct checksum.
  49  func ValidateMnemonic(phrase string) bool {
  50  	if phrase == "" {
  51  		return false
  52  	}
  53  	var words [13]string
  54  	n := 0
  55  	start := 0
  56  	for i := 0; i <= len(phrase); i++ {
  57  		if i == len(phrase) || phrase[i] == ' ' {
  58  			if i > start {
  59  				if n >= 13 {
  60  					return false
  61  				}
  62  				words[n] = phrase[start:i]
  63  				n++
  64  			}
  65  			start = i + 1
  66  		}
  67  	}
  68  	if n != 12 {
  69  		return false
  70  	}
  71  	eng := English()
  72  	var indices [12]uint32
  73  	for w := 0; w < 12; w++ {
  74  		found := false
  75  		for i := 0; i < 2048; i++ {
  76  			if eng[i] == words[w] {
  77  				indices[w] = uint32(i)
  78  				found = true
  79  				break
  80  			}
  81  		}
  82  		if !found {
  83  			return false
  84  		}
  85  	}
  86  	// Reconstruct 132-bit stream into all[17]: 128 entropy + 4 checksum.
  87  	var all [17]byte
  88  	for w := 0; w < 12; w++ {
  89  		idx := indices[w]
  90  		for bit := 0; bit < 11; bit++ {
  91  			if (idx>>uint(10-bit))&1 != 0 {
  92  				streamPos := w*11 + bit
  93  				all[streamPos/8] |= 1 << uint(7-(streamPos%8))
  94  			}
  95  		}
  96  	}
  97  	entropy := all[:16]
  98  	cs := all[16] >> 4
  99  	hash := sha256.Sum(entropy)
 100  	return (hash[0] >> 4) == cs
 101  }
 102  
 103  // MnemonicToSeed derives a 64-byte seed from a mnemonic + optional passphrase
 104  // per BIP-39 ("mnemonic" || passphrase as salt, 2048 PBKDF2-HMAC-SHA512 rounds).
 105  // Returns nil on PBKDF2 failure (should never happen with valid params).
 106  func MnemonicToSeed(phrase, passphrase string) []byte {
 107  	salt := []byte{:0:8+len(passphrase)}
 108  	salt = append(salt, 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c')
 109  	salt = append(salt, passphrase...)
 110  	dk, err := pbkdf2.Key(sha512.New, []byte(phrase), salt, 2048, 64)
 111  	if err != nil {
 112  		return nil
 113  	}
 114  	return dk
 115  }
 116