decred.org/dcrwallet/v3@v3.1.0/walletseed/seed.go (about)

     1  // Copyright (c) 2016 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package walletseed
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"encoding/hex"
    11  	"strings"
    12  
    13  	"decred.org/dcrwallet/v3/errors"
    14  	"decred.org/dcrwallet/v3/pgpwordlist"
    15  	"github.com/decred/dcrd/hdkeychain/v3"
    16  )
    17  
    18  // GenerateRandomSeed returns a new seed created from a cryptographically-secure
    19  // random source.  If the seed size is unacceptable,
    20  // hdkeychain.ErrInvalidSeedLen is returned.
    21  func GenerateRandomSeed(size uint) ([]byte, error) {
    22  	const op errors.Op = "walletseed.GenerateRandomSeed"
    23  	if size >= uint(^uint8(0)) {
    24  		return nil, errors.E(op, errors.Invalid, hdkeychain.ErrInvalidSeedLen)
    25  	}
    26  	if size < hdkeychain.MinSeedBytes || size > hdkeychain.MaxSeedBytes {
    27  		return nil, errors.E(op, errors.Invalid, hdkeychain.ErrInvalidSeedLen)
    28  	}
    29  	seed, err := hdkeychain.GenerateSeed(uint8(size))
    30  	if err != nil {
    31  		return nil, errors.E(op, err)
    32  	}
    33  	return seed, nil
    34  }
    35  
    36  // checksumByte returns the checksum byte used at the end of the seed mnemonic
    37  // encoding.  The "checksum" is the first byte of the double SHA256.
    38  func checksumByte(data []byte) byte {
    39  	intermediateHash := sha256.Sum256(data)
    40  	return sha256.Sum256(intermediateHash[:])[0]
    41  }
    42  
    43  // EncodeMnemonicSlice encodes a seed as a mnemonic word list.
    44  func EncodeMnemonicSlice(seed []byte) []string {
    45  	words := make([]string, len(seed)+1) // Extra word for checksumByte
    46  	for i, b := range seed {
    47  		words[i] = pgpwordlist.ByteToMnemonic(b, i)
    48  	}
    49  	checksum := checksumByte(seed)
    50  	words[len(words)-1] = pgpwordlist.ByteToMnemonic(checksum, len(seed))
    51  	return words
    52  }
    53  
    54  // EncodeMnemonic encodes a seed as a mnemonic word list separated by spaces.
    55  func EncodeMnemonic(seed []byte) string {
    56  	var buf bytes.Buffer
    57  	for i, b := range seed {
    58  		if i != 0 {
    59  			buf.WriteRune(' ')
    60  		}
    61  		buf.WriteString(pgpwordlist.ByteToMnemonic(b, i))
    62  	}
    63  	checksum := checksumByte(seed)
    64  	buf.WriteRune(' ')
    65  	buf.WriteString(pgpwordlist.ByteToMnemonic(checksum, len(seed)))
    66  	return buf.String()
    67  }
    68  
    69  // DecodeUserInput decodes a seed in either hexadecimal or mnemonic word list
    70  // encoding back into its binary form.
    71  func DecodeUserInput(input string) ([]byte, error) {
    72  	const op errors.Op = "walletseed.DecodeUserInput"
    73  	words := strings.Split(strings.TrimSpace(input), " ")
    74  	var seed []byte
    75  	switch {
    76  	case len(words) == 1:
    77  		// Assume hex
    78  		var err error
    79  		seed, err = hex.DecodeString(words[0])
    80  		if err != nil {
    81  			return nil, errors.E(op, errors.Encoding, err)
    82  		}
    83  	case len(words) > 1:
    84  		// Assume mnemonic with encoded checksum byte
    85  		decoded, err := pgpwordlist.DecodeMnemonics(words)
    86  		if err != nil {
    87  			return nil, errors.E(op, errors.Encoding, err)
    88  		}
    89  		if len(decoded) < 2 { // need data (0) and checksum (1) to check checksum
    90  			break
    91  		}
    92  		if checksumByte(decoded[:len(decoded)-1]) != decoded[len(decoded)-1] {
    93  			return nil, errors.E(op, errors.Encoding, "checksum mismatch")
    94  		}
    95  		seed = decoded[:len(decoded)-1]
    96  	}
    97  
    98  	if len(seed) < hdkeychain.MinSeedBytes || len(seed) > hdkeychain.MaxSeedBytes {
    99  		return nil, errors.E(op, errors.Encoding, hdkeychain.ErrInvalidSeedLen)
   100  	}
   101  	return seed, nil
   102  }