github.com/status-im/status-go@v1.1.0/protocol/identity/emojihash/emojihash.go (about)

     1  package emojihash
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"math/big"
     8  	"strings"
     9  
    10  	"github.com/status-im/status-go/protocol/identity"
    11  	"github.com/status-im/status-go/static"
    12  )
    13  
    14  const (
    15  	emojiAlphabetLen = 2757 // 20bytes of data described by 14 emojis requires at least 2757 length alphabet
    16  	emojiHashLen     = 14
    17  )
    18  
    19  var emojisAlphabet []string
    20  
    21  func GenerateFor(pubkey string) ([]string, error) {
    22  	if len(emojisAlphabet) == 0 {
    23  		alphabet, err := loadAlphabet()
    24  		if err != nil {
    25  			return nil, err
    26  		}
    27  		emojisAlphabet = *alphabet
    28  	}
    29  
    30  	compressedKey, err := identity.ToCompressedKey(pubkey)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	slices, err := identity.Slices(compressedKey)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	return toEmojiHash(new(big.Int).SetBytes(slices[1]), emojiHashLen, &emojisAlphabet)
    41  }
    42  
    43  func loadAlphabet() (*[]string, error) {
    44  	data, err := static.Asset("emojis.txt")
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	alphabet := make([]string, 0, emojiAlphabetLen)
    50  
    51  	scanner := bufio.NewScanner(bytes.NewReader(data))
    52  	for scanner.Scan() {
    53  		alphabet = append(alphabet, strings.Replace(scanner.Text(), "\n", "", -1))
    54  	}
    55  
    56  	// current alphabet contains more emojis than needed, just in case some emojis needs to be removed
    57  	// make sure only necessary part is loaded
    58  	if len(alphabet) > emojiAlphabetLen {
    59  		alphabet = alphabet[:emojiAlphabetLen]
    60  	}
    61  
    62  	return &alphabet, nil
    63  }
    64  
    65  func toEmojiHash(value *big.Int, hashLen int, alphabet *[]string) (hash []string, err error) {
    66  	valueBitLen := value.BitLen()
    67  	alphabetLen := new(big.Int).SetInt64(int64(len(*alphabet)))
    68  
    69  	indexes := identity.ToBigBase(value, alphabetLen.Uint64())
    70  	if hashLen == 0 {
    71  		hashLen = len(indexes)
    72  	} else if hashLen > len(indexes) {
    73  		prependLen := hashLen - len(indexes)
    74  		for i := 0; i < prependLen; i++ {
    75  			indexes = append([](uint64){0}, indexes...)
    76  		}
    77  	}
    78  
    79  	// alphabetLen^hashLen
    80  	possibleCombinations := new(big.Int).Exp(alphabetLen, new(big.Int).SetInt64(int64(hashLen)), nil)
    81  
    82  	// 2^valueBitLen
    83  	requiredCombinations := new(big.Int).Exp(new(big.Int).SetInt64(2), new(big.Int).SetInt64(int64(valueBitLen)), nil)
    84  
    85  	if possibleCombinations.Cmp(requiredCombinations) == -1 {
    86  		return nil, errors.New("alphabet or hash length is too short to encode given value")
    87  	}
    88  
    89  	for _, v := range indexes {
    90  		hash = append(hash, (*alphabet)[v])
    91  	}
    92  
    93  	return hash, nil
    94  }