seekia/internal/seedPhrase/seedPhrase.go

146 lines
3.7 KiB
Go

// seedPhrase provides functions to read/create seed phrases, and derive seed phrase hashes
package seedPhrase
// Seed Phrase is 15 words, separated by " "
// A seed phrase hash is a 32 bytes long blake3 hash of the seed phrase unicode bytes
import "seekia/resources/wordLists"
import "seekia/internal/cryptography/blake3"
import "crypto/rand"
import "math/big"
import "errors"
import "strings"
func VerifySeedPhrase(inputSeedPhrase string)bool{
seedPhraseCharacterCount := len(inputSeedPhrase)
if (seedPhraseCharacterCount < 44){
// There are 15 words, each word is at least 2 characters long
// 15 words * 2 characters each = 30 characters
// Each word is separated by a space
// 30 + 14 == 44 characters
return false
}
numberOfSpaces := 0
currentNumberOfCharacters := 0
finalIndex := seedPhraseCharacterCount - 1
for index, character := range inputSeedPhrase{
if (character == ' '){
if (currentNumberOfCharacters < 2){
// The seed phrase contains a word with less than 2 characters
return false
}
currentNumberOfCharacters = 0
numberOfSpaces += 1
} else {
currentNumberOfCharacters += 1
}
if (index == finalIndex && currentNumberOfCharacters < 2){
// The seed phrase ends with a word containing less than 2 characters
return false
}
}
if (numberOfSpaces != 14){
return false
}
return true
}
func ConvertSeedPhraseToSeedPhraseHash(inputSeedPhrase string)([32]byte, error){
isValid := VerifySeedPhrase(inputSeedPhrase)
if (isValid == false){
return [32]byte{}, errors.New("ConvertSeedPhraseToSeedPhraseHash called with invalid seed phrase.")
}
seedPhraseBytes := []byte(inputSeedPhrase)
seedPhraseHash, err := blake3.Get32ByteBlake3Hash(seedPhraseBytes)
if (err != nil) { return [32]byte{}, err }
return seedPhraseHash, nil
}
// This function is slower, only use it for generating a few seed phrases
// For many generations, use GetNewSeedPhraseFromWordList, which is faster
//Outputs:
// -string: New seed phrase
// -[32]byte: New seed phrase hash
// -error
func GetNewRandomSeedPhrase(languageName string)(string, [32]byte, error){
wordList, err := wordLists.GetWordListFromLanguage(languageName)
if (err != nil) { return "", [32]byte{}, err }
newSeedPhrase, newSeedPhraseHash, err := GetNewSeedPhraseFromWordList(wordList)
if (err != nil) { return "", [32]byte{}, err }
return newSeedPhrase, newSeedPhraseHash, nil
}
// wordList must be retrieved from resources/wordLists/wordLists.go
//Outputs:
// -string: Seed phrase
// -[32]byte: Seed phrase hash
// -error
func GetNewSeedPhraseFromWordList(wordList []string)(string, [32]byte, error){
lengthOfWordList := len(wordList)
if (lengthOfWordList < 2048) {
return "", [32]byte{}, errors.New("GetNewSeedPhraseFromWordList called with word list that is too short.")
}
upperLimitInt64 := int64(lengthOfWordList-1)
upperLimit := big.NewInt(upperLimitInt64)
// We use this to build the seed phrase
var seedPhraseBuilder strings.Builder
for i := 0; i < 15; i++ {
randomNumber, err := rand.Int(rand.Reader, upperLimit)
if (err != nil) { return "", [32]byte{}, err }
wordIndex := int(randomNumber.Int64())
randomWord := wordList[wordIndex]
_, err = seedPhraseBuilder.WriteString(randomWord)
if (err != nil) { return "", [32]byte{}, err }
if (i < 14){
// There is a space between every word, and no trailing space
_, err := seedPhraseBuilder.WriteString(" ")
if (err != nil) { return "", [32]byte{}, err }
}
}
newSeedPhrase := seedPhraseBuilder.String()
newSeedPhraseBytes := []byte(newSeedPhrase)
newSeedPhraseHash, err := blake3.Get32ByteBlake3Hash(newSeedPhraseBytes)
if (err != nil) { return "", [32]byte{}, err }
return newSeedPhrase, newSeedPhraseHash, nil
}