status-go/protocol/identity/emojihash/emojihash.go

95 lines
2.3 KiB
Go

package emojihash
import (
"bufio"
"bytes"
"errors"
"math/big"
"strings"
"github.com/status-im/status-go/protocol/identity"
"github.com/status-im/status-go/static"
)
const (
emojiAlphabetLen = 2757 // 20bytes of data described by 14 emojis requires at least 2757 length alphabet
emojiHashLen = 14
)
var emojisAlphabet []string
func GenerateFor(pubkey string) ([]string, error) {
if len(emojisAlphabet) == 0 {
alphabet, err := loadAlphabet()
if err != nil {
return nil, err
}
emojisAlphabet = *alphabet
}
compressedKey, err := identity.ToCompressedKey(pubkey)
if err != nil {
return nil, err
}
slices, err := identity.Slices(compressedKey)
if err != nil {
return nil, err
}
return toEmojiHash(new(big.Int).SetBytes(slices[1]), emojiHashLen, &emojisAlphabet)
}
func loadAlphabet() (*[]string, error) {
data, err := static.Asset("emojis.txt")
if err != nil {
return nil, err
}
alphabet := make([]string, 0, emojiAlphabetLen)
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
alphabet = append(alphabet, strings.Replace(scanner.Text(), "\n", "", -1))
}
// current alphabet contains more emojis than needed, just in case some emojis needs to be removed
// make sure only necessary part is loaded
if len(alphabet) > emojiAlphabetLen {
alphabet = alphabet[:emojiAlphabetLen]
}
return &alphabet, nil
}
func toEmojiHash(value *big.Int, hashLen int, alphabet *[]string) (hash []string, err error) {
valueBitLen := value.BitLen()
alphabetLen := new(big.Int).SetInt64(int64(len(*alphabet)))
indexes := identity.ToBigBase(value, alphabetLen.Uint64())
if hashLen == 0 {
hashLen = len(indexes)
} else if hashLen > len(indexes) {
prependLen := hashLen - len(indexes)
for i := 0; i < prependLen; i++ {
indexes = append([](uint64){0}, indexes...)
}
}
// alphabetLen^hashLen
possibleCombinations := new(big.Int).Exp(alphabetLen, new(big.Int).SetInt64(int64(hashLen)), nil)
// 2^valueBitLen
requiredCombinations := new(big.Int).Exp(new(big.Int).SetInt64(2), new(big.Int).SetInt64(int64(valueBitLen)), nil)
if possibleCombinations.Cmp(requiredCombinations) == -1 {
return nil, errors.New("alphabet or hash length is too short to encode given value")
}
for _, v := range indexes {
hash = append(hash, (*alphabet)[v])
}
return hash, nil
}