fix: move visual-identity service to protocol/identity
It is required to be called before RPC server is running on client side
This commit is contained in:
parent
7ef8bc68c8
commit
d0f4a94f75
|
@ -26,6 +26,8 @@ import (
|
||||||
"github.com/status-im/status-go/profiling"
|
"github.com/status-im/status-go/profiling"
|
||||||
protocol "github.com/status-im/status-go/protocol"
|
protocol "github.com/status-im/status-go/protocol"
|
||||||
"github.com/status-im/status-go/protocol/identity/alias"
|
"github.com/status-im/status-go/protocol/identity/alias"
|
||||||
|
"github.com/status-im/status-go/protocol/identity/colorhash"
|
||||||
|
"github.com/status-im/status-go/protocol/identity/emojihash"
|
||||||
"github.com/status-im/status-go/server"
|
"github.com/status-im/status-go/server"
|
||||||
"github.com/status-im/status-go/services/personal"
|
"github.com/status-im/status-go/services/personal"
|
||||||
"github.com/status-im/status-go/services/typeddata"
|
"github.com/status-im/status-go/services/typeddata"
|
||||||
|
@ -652,6 +654,14 @@ func Identicon(pk string) string {
|
||||||
return identicon
|
return identicon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EmojiHash(pk string) string {
|
||||||
|
return prepareJSONResponse(emojihash.GenerateFor(pk))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColorHash(pk string) string {
|
||||||
|
return prepareJSONResponse(colorhash.GenerateFor(pk))
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateMnemonic(mnemonic string) string {
|
func ValidateMnemonic(mnemonic string) string {
|
||||||
m := extkeys.NewMnemonic()
|
m := extkeys.NewMnemonic()
|
||||||
err := m.ValidateMnemonic(mnemonic, extkeys.Language(0))
|
err := m.ValidateMnemonic(mnemonic, extkeys.Language(0))
|
||||||
|
|
|
@ -45,7 +45,6 @@ import (
|
||||||
"github.com/status-im/status-go/services/status"
|
"github.com/status-im/status-go/services/status"
|
||||||
"github.com/status-im/status-go/services/stickers"
|
"github.com/status-im/status-go/services/stickers"
|
||||||
"github.com/status-im/status-go/services/subscriptions"
|
"github.com/status-im/status-go/services/subscriptions"
|
||||||
visualIdentity "github.com/status-im/status-go/services/visual-identity"
|
|
||||||
"github.com/status-im/status-go/services/wakuext"
|
"github.com/status-im/status-go/services/wakuext"
|
||||||
"github.com/status-im/status-go/services/wakuv2ext"
|
"github.com/status-im/status-go/services/wakuv2ext"
|
||||||
"github.com/status-im/status-go/services/wallet"
|
"github.com/status-im/status-go/services/wallet"
|
||||||
|
@ -118,7 +117,6 @@ type StatusNode struct {
|
||||||
gifSrvc *gif.Service
|
gifSrvc *gif.Service
|
||||||
stickersSrvc *stickers.Service
|
stickersSrvc *stickers.Service
|
||||||
chatSrvc *chat.Service
|
chatSrvc *chat.Service
|
||||||
visualIdentitySrvc *visualIdentity.Service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New makes new instance of StatusNode.
|
// New makes new instance of StatusNode.
|
||||||
|
@ -429,7 +427,6 @@ func (n *StatusNode) stop() error {
|
||||||
n.wakuV2ExtSrvc = nil
|
n.wakuV2ExtSrvc = nil
|
||||||
n.ensSrvc = nil
|
n.ensSrvc = nil
|
||||||
n.stickersSrvc = nil
|
n.stickersSrvc = nil
|
||||||
n.visualIdentitySrvc = nil
|
|
||||||
n.publicMethods = make(map[string]bool)
|
n.publicMethods = make(map[string]bool)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -38,7 +38,6 @@ import (
|
||||||
"github.com/status-im/status-go/services/status"
|
"github.com/status-im/status-go/services/status"
|
||||||
"github.com/status-im/status-go/services/stickers"
|
"github.com/status-im/status-go/services/stickers"
|
||||||
"github.com/status-im/status-go/services/subscriptions"
|
"github.com/status-im/status-go/services/subscriptions"
|
||||||
visualIdentity "github.com/status-im/status-go/services/visual-identity"
|
|
||||||
"github.com/status-im/status-go/services/wakuext"
|
"github.com/status-im/status-go/services/wakuext"
|
||||||
"github.com/status-im/status-go/services/wakuv2ext"
|
"github.com/status-im/status-go/services/wakuv2ext"
|
||||||
"github.com/status-im/status-go/services/wallet"
|
"github.com/status-im/status-go/services/wallet"
|
||||||
|
@ -79,12 +78,6 @@ func (b *StatusNode) initServices(config *params.NodeConfig) error {
|
||||||
services = append(services, b.gifService())
|
services = append(services, b.gifService())
|
||||||
services = append(services, b.ChatService())
|
services = append(services, b.ChatService())
|
||||||
|
|
||||||
visualIdentitySrvc, err := b.visualIdentityService()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
services = append(services, visualIdentitySrvc)
|
|
||||||
|
|
||||||
if config.WakuConfig.Enabled {
|
if config.WakuConfig.Enabled {
|
||||||
wakuService, err := b.wakuService(&config.WakuConfig, &config.ClusterConfig)
|
wakuService, err := b.wakuService(&config.WakuConfig, &config.ClusterConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -395,21 +388,6 @@ func (b *StatusNode) gifService() *gif.Service {
|
||||||
return b.gifSrvc
|
return b.gifSrvc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *StatusNode) visualIdentityService() (*visualIdentity.Service, error) {
|
|
||||||
if b.visualIdentitySrvc != nil {
|
|
||||||
return b.visualIdentitySrvc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
srvc := visualIdentity.NewService()
|
|
||||||
err := srvc.Init()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.visualIdentitySrvc = srvc
|
|
||||||
|
|
||||||
return b.visualIdentitySrvc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *StatusNode) ChatService() *chat.Service {
|
func (b *StatusNode) ChatService() *chat.Service {
|
||||||
if b.chatSrvc == nil {
|
if b.chatSrvc == nil {
|
||||||
b.chatSrvc = chat.NewService(b.appDB)
|
b.chatSrvc = chat.NewService(b.appDB)
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package colorhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/protocol/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
colorHashSegmentMaxLen = 5
|
||||||
|
colorHashColorsCount = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
var colorHashAlphabet [][]int
|
||||||
|
|
||||||
|
func GenerateFor(pubkey string) (hash [][]int, err error) {
|
||||||
|
if len(colorHashAlphabet) == 0 {
|
||||||
|
colorHashAlphabet = makeColorHashAlphabet(colorHashSegmentMaxLen, colorHashColorsCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
compressedKey, err := identity.ToCompressedKey(pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
slices, err := identity.Slices(compressedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return toColorHash(new(big.Int).SetBytes(slices[2]), &colorHashAlphabet, colorHashColorsCount), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// [[1 0] [1 1] [1 2] ... [units, colors-1]]
|
||||||
|
// [3 12] => 3 units length, 12 color index
|
||||||
|
func makeColorHashAlphabet(units, colors int) (res [][]int) {
|
||||||
|
res = make([][]int, units*colors)
|
||||||
|
idx := 0
|
||||||
|
for i := 0; i < units; i++ {
|
||||||
|
for j := 0; j < colors; j++ {
|
||||||
|
res[idx] = make([]int, 2)
|
||||||
|
res[idx][0] = i + 1
|
||||||
|
res[idx][1] = j
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func toColorHash(value *big.Int, alphabet *[][]int, colorsCount int) (hash [][]int) {
|
||||||
|
alphabetLen := len(*alphabet)
|
||||||
|
indexes := identity.ToBigBase(value, uint64(alphabetLen))
|
||||||
|
hash = make([][](int), len(indexes))
|
||||||
|
for i, v := range indexes {
|
||||||
|
hash[i] = make([](int), 2)
|
||||||
|
hash[i][0] = (*alphabet)[v][0]
|
||||||
|
hash[i][1] = (*alphabet)[v][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// colors can't repeat themselves
|
||||||
|
// this makes color hash not fully collision resistant
|
||||||
|
prevColorIdx := hash[0][1]
|
||||||
|
hashLen := len(hash)
|
||||||
|
for i := 1; i < hashLen; i++ {
|
||||||
|
colorIdx := hash[i][1]
|
||||||
|
if colorIdx == prevColorIdx {
|
||||||
|
hash[i][1] = (colorIdx + 1) % colorsCount
|
||||||
|
}
|
||||||
|
prevColorIdx = hash[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package colorhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/protocol/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateFor(t *testing.T) {
|
||||||
|
checker := func(pubkey string, expected *[][](int)) {
|
||||||
|
colorhash, err := GenerateFor(pubkey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !reflect.DeepEqual(colorhash, *expected) {
|
||||||
|
t.Fatalf("invalid emojihash %v != %v", colorhash, *expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker("0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8",
|
||||||
|
&[][]int{{3, 30}, {2, 10}, {5, 5}, {3, 14}, {5, 4}, {4, 19}, {3, 16}, {4, 0}, {5, 28}, {4, 13}, {4, 15}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorHashOfInvalidKey(t *testing.T) {
|
||||||
|
checker := func(pubkey string) {
|
||||||
|
_, err := GenerateFor(pubkey)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
checker("abc")
|
||||||
|
checker("0x01")
|
||||||
|
checker("0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
||||||
|
checker("0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorHash(t *testing.T) {
|
||||||
|
alphabet := makeColorHashAlphabet(4, 4)
|
||||||
|
|
||||||
|
checker := func(valueStr string, expected *[][](int)) {
|
||||||
|
value := identity.ToBigInt(t, valueStr)
|
||||||
|
res := toColorHash(value, &alphabet, 4)
|
||||||
|
if !reflect.DeepEqual(res, *expected) {
|
||||||
|
t.Fatalf("invalid colorhash conversion %v != %v", res, *expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker("0x0", &[][]int{{1, 0}})
|
||||||
|
checker("0x1", &[][]int{{1, 1}})
|
||||||
|
checker("0x4", &[][]int{{2, 0}})
|
||||||
|
checker("0xF", &[][]int{{4, 3}})
|
||||||
|
|
||||||
|
// oops, collision
|
||||||
|
checker("0xFF", &[][]int{{4, 3}, {4, 0}})
|
||||||
|
checker("0xFC", &[][]int{{4, 3}, {4, 0}})
|
||||||
|
|
||||||
|
checker("0xFFFF", &[][]int{{4, 3}, {4, 0}, {4, 3}, {4, 0}})
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,27 +1,17 @@
|
||||||
package visualidentity
|
package emojihash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/protocol/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestAPI(t *testing.T) *API {
|
func TestGenerateFor(t *testing.T) {
|
||||||
api := NewAPI()
|
|
||||||
|
|
||||||
alphabet, err := LoadAlphabet()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
api.emojisAlphabet = alphabet
|
|
||||||
return api
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmojiHashOf(t *testing.T) {
|
|
||||||
api := setupTestAPI(t)
|
|
||||||
|
|
||||||
checker := func(pubkey string, expected *[](string)) {
|
checker := func(pubkey string, expected *[](string)) {
|
||||||
emojihash, err := api.EmojiHashOf(pubkey)
|
emojihash, err := GenerateFor(pubkey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if !reflect.DeepEqual(emojihash, *expected) {
|
if !reflect.DeepEqual(emojihash, *expected) {
|
||||||
t.Fatalf("invalid emojihash %v != %v", emojihash, *expected)
|
t.Fatalf("invalid emojihash %v != %v", emojihash, *expected)
|
||||||
|
@ -35,34 +25,15 @@ func TestEmojiHashOf(t *testing.T) {
|
||||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀"})
|
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀"})
|
||||||
|
|
||||||
checker("0x04000000000000000000000000000000000000000010000000000000000000000033600332D373318ECC2F212A30A5750D2EAC827B6A32B33D326CCF369B12B1BE",
|
checker("0x04000000000000000000000000000000000000000010000000000000000000000033600332D373318ECC2F212A30A5750D2EAC827B6A32B33D326CCF369B12B1BE",
|
||||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (*api.emojisAlphabet)[1]})
|
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (emojisAlphabet)[1]})
|
||||||
|
|
||||||
checker("0x040000000000000000000000000000000000000000200000000000000000000000353050BFE33B724E60A0C600FBA565A9B62217B1BD35BF9848F2AB847C598B30",
|
checker("0x040000000000000000000000000000000000000000200000000000000000000000353050BFE33B724E60A0C600FBA565A9B62217B1BD35BF9848F2AB847C598B30",
|
||||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (*api.emojisAlphabet)[2]})
|
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (emojisAlphabet)[2]})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorHashOf(t *testing.T) {
|
func TestEmojiHashOfInvalidKey(t *testing.T) {
|
||||||
api := NewAPI()
|
|
||||||
|
|
||||||
checker := func(pubkey string, expected *[][](int)) {
|
|
||||||
colorhash, err := api.ColorHashOf(pubkey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
if !reflect.DeepEqual(colorhash, *expected) {
|
|
||||||
t.Fatalf("invalid emojihash %v != %v", colorhash, *expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checker("0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8",
|
|
||||||
&[][]int{{3, 30}, {2, 10}, {5, 5}, {3, 14}, {5, 4}, {4, 19}, {3, 16}, {4, 0}, {5, 28}, {4, 13}, {4, 15}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashesOfInvalidKey(t *testing.T) {
|
|
||||||
api := setupTestAPI(t)
|
|
||||||
|
|
||||||
checker := func(pubkey string) {
|
checker := func(pubkey string) {
|
||||||
_, err := api.EmojiHashOf(pubkey)
|
_, err := GenerateFor(pubkey)
|
||||||
require.Error(t, err)
|
|
||||||
_, err = api.ColorHashOf(pubkey)
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
checker("abc")
|
checker("abc")
|
||||||
|
@ -70,3 +41,30 @@ func TestHashesOfInvalidKey(t *testing.T) {
|
||||||
checker("0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
checker("0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
||||||
checker("0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
checker("0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToEmojiHash(t *testing.T) {
|
||||||
|
alphabet := [](string){"😇", "🤐", "🥵", "🙊", "🤌"}
|
||||||
|
|
||||||
|
checker := func(valueStr string, hashLen int, expected *[](string)) {
|
||||||
|
value := identity.ToBigInt(t, valueStr)
|
||||||
|
res, err := toEmojiHash(value, hashLen, &alphabet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !reflect.DeepEqual(res, *expected) {
|
||||||
|
t.Fatalf("invalid emojihash conversion %v != %v", res, *expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker("777", 5, &[](string){"🤐", "🤐", "🤐", "😇", "🥵"})
|
||||||
|
checker("777", 0, &[](string){"🤐", "🤐", "🤐", "😇", "🥵"})
|
||||||
|
checker("777", 10, &[](string){"😇", "😇", "😇", "😇", "😇", "🤐", "🤐", "🤐", "😇", "🥵"})
|
||||||
|
|
||||||
|
// 20bytes of data described by 14 emojis requires at least 2757 length alphabet
|
||||||
|
alphabet = make([](string), 2757)
|
||||||
|
val := identity.ToBigInt(t, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") // 20 bytes
|
||||||
|
_, err := toEmojiHash(val, 14, &alphabet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
alphabet = make([](string), 2757-1)
|
||||||
|
_, err = toEmojiHash(val, 14, &alphabet)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package identity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToBigBase(value *big.Int, base uint64) (res [](uint64)) {
|
||||||
|
toBigBaseImpl(value, base, &res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBigBaseImpl(value *big.Int, base uint64, res *[](uint64)) {
|
||||||
|
bigBase := new(big.Int).SetUint64(base)
|
||||||
|
quotient := new(big.Int).Div(value, bigBase)
|
||||||
|
if quotient.Cmp(new(big.Int).SetUint64(0)) != 0 {
|
||||||
|
toBigBaseImpl(quotient, base, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
*res = append(*res, new(big.Int).Mod(value, bigBase).Uint64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// compressedPubKey = |1.5 bytes chars cutoff|20 bytes emoji hash|10 bytes color hash|1.5 bytes chars cutoff|
|
||||||
|
func Slices(compressedPubkey []byte) (res [4][]byte, err error) {
|
||||||
|
if len(compressedPubkey) != 33 {
|
||||||
|
return res, errors.New("incorrect compressed pubkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
getSlice := func(low, high int, and string, rsh uint) []byte {
|
||||||
|
sliceValue := new(big.Int).SetBytes(compressedPubkey[low:high])
|
||||||
|
andValue, _ := new(big.Int).SetString(and, 0)
|
||||||
|
andRes := new(big.Int).And(sliceValue, andValue)
|
||||||
|
return new(big.Int).Rsh(andRes, rsh).Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
res[0] = getSlice(0, 2, "0xFFF0", 4)
|
||||||
|
res[1] = getSlice(1, 22, "0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", 4)
|
||||||
|
res[2] = getSlice(21, 32, "0x0FFFFFFFFFFFFFFFFFFFF0", 4)
|
||||||
|
res[3] = getSlice(31, 33, "0x0FFF", 0)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToCompressedKey(pubkey string) ([]byte, error) {
|
||||||
|
pubkeyValue, ok := new(big.Int).SetString(pubkey, 0)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid pubkey: %s", pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y := secp256k1.S256().Unmarshal(pubkeyValue.Bytes())
|
||||||
|
if x == nil || !secp256k1.S256().IsOnCurve(x, y) {
|
||||||
|
return nil, fmt.Errorf("invalid pubkey: %s", pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secp256k1.CompressPubkey(x, y), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToBigInt(t *testing.T, str string) *big.Int {
|
||||||
|
res, ok := new(big.Int).SetString(str, 0)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("invalid conversion to int from %s", str)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package identity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToBigBase(t *testing.T) {
|
||||||
|
checker := func(value *big.Int, base uint64, expected *[](uint64)) {
|
||||||
|
res := ToBigBase(value, base)
|
||||||
|
if !reflect.DeepEqual(res, *expected) {
|
||||||
|
t.Fatalf("invalid big base conversion %v != %v", res, *expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lengthChecker := func(value *big.Int, base, expectedLength uint64) {
|
||||||
|
res := ToBigBase(value, base)
|
||||||
|
if len(res) != int(expectedLength) {
|
||||||
|
t.Fatalf("invalid big base conversion %d != %d", len(res), expectedLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker(new(big.Int).SetUint64(15), 16, &[](uint64){15})
|
||||||
|
checker(new(big.Int).SetUint64(495), 16, &[](uint64){1, 14, 15})
|
||||||
|
checker(new(big.Int).SetUint64(495), 30, &[](uint64){16, 15})
|
||||||
|
checker(new(big.Int).SetUint64(495), 1024, &[](uint64){495})
|
||||||
|
checker(new(big.Int).SetUint64(2048), 1024, &[](uint64){2, 0})
|
||||||
|
|
||||||
|
base := uint64(math.Pow(2, 7*4))
|
||||||
|
checker(ToBigInt(t, "0xFFFFFFFFFFFFFF"), base, &[](uint64){base - 1, base - 1})
|
||||||
|
|
||||||
|
val := ToBigInt(t, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
|
||||||
|
lengthChecker(val, 2757, 14)
|
||||||
|
lengthChecker(val, 2756, 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlices(t *testing.T) {
|
||||||
|
checker := func(compressedKey, charsCutoffA, emojiHash, colorHash, charsCutoffB string) {
|
||||||
|
slices, err := Slices(ToBigInt(t, compressedKey).Bytes())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sliceChecker := func(idx int, value *big.Int) {
|
||||||
|
if !reflect.DeepEqual(slices[idx], value.Bytes()) {
|
||||||
|
t.Fatalf("invalid slice (%d) %v != %v", idx, slices[idx], value.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceChecker(0, ToBigInt(t, charsCutoffA))
|
||||||
|
sliceChecker(1, ToBigInt(t, emojiHash))
|
||||||
|
sliceChecker(2, ToBigInt(t, colorHash))
|
||||||
|
sliceChecker(3, ToBigInt(t, charsCutoffB))
|
||||||
|
}
|
||||||
|
|
||||||
|
checker("0x03086138b210f21d41c757ae8a5d2a4cb29c1350f7389517608378ebd9efcf4a55", "0x030", "0x86138b210f21d41c757ae8a5d2a4cb29c1350f73", "0x89517608378ebd9efcf4", "0xa55")
|
||||||
|
checker("0x020000000000000000000000000000000000000000100000000000000000000000", "0x020", "0x0000000000000000000000000000000000000001", "0x00000000000000000000", "0x000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlicesInvalid(t *testing.T) {
|
||||||
|
_, err := Slices(ToBigInt(t, "0x01").Bytes())
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
package visualidentity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"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
|
|
||||||
colorHashSegmentMaxLen = 5
|
|
||||||
colorHashColorsCount = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewAPI() *API {
|
|
||||||
colorHashAlphabet := MakeColorHashAlphabet(colorHashSegmentMaxLen, colorHashColorsCount)
|
|
||||||
return &API{
|
|
||||||
emojisAlphabet: &[]string{},
|
|
||||||
colorHashAlphabet: &colorHashAlphabet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type API struct {
|
|
||||||
emojisAlphabet *[]string
|
|
||||||
colorHashAlphabet *[][]int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) EmojiHashOf(pubkey string) (hash []string, err error) {
|
|
||||||
log.Info("[VisualIdentityAPI::EmojiHashOf]")
|
|
||||||
|
|
||||||
slices, err := slices(pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ToEmojiHash(new(big.Int).SetBytes(slices[1]), emojiHashLen, api.emojisAlphabet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) ColorHashOf(pubkey string) (hash [][]int, err error) {
|
|
||||||
log.Info("[VisualIdentityAPI::ColorHashOf]")
|
|
||||||
|
|
||||||
slices, err := slices(pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ToColorHash(new(big.Int).SetBytes(slices[2]), api.colorHashAlphabet, colorHashColorsCount), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 slices(pubkey string) (res [4][]byte, err error) {
|
|
||||||
pubkeyValue, ok := new(big.Int).SetString(pubkey, 0)
|
|
||||||
if !ok {
|
|
||||||
return res, fmt.Errorf("invalid pubkey: %s", pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y := secp256k1.S256().Unmarshal(pubkeyValue.Bytes())
|
|
||||||
if x == nil || !secp256k1.S256().IsOnCurve(x, y) {
|
|
||||||
return res, fmt.Errorf("invalid pubkey: %s", pubkey)
|
|
||||||
}
|
|
||||||
compressedKey := secp256k1.CompressPubkey(x, y)
|
|
||||||
|
|
||||||
return Slices(compressedKey)
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package visualidentity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents out own implementation of Identity Visual Representation.
|
|
||||||
type Service struct {
|
|
||||||
api *API
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new Service.
|
|
||||||
func NewService() *Service {
|
|
||||||
return &Service{
|
|
||||||
api: NewAPI(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Init() error {
|
|
||||||
alphabet, err := LoadAlphabet()
|
|
||||||
if err == nil {
|
|
||||||
s.api.emojisAlphabet = alphabet
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protocols returns a new protocols list. In this case, there are none.
|
|
||||||
func (s *Service) Protocols() []p2p.Protocol {
|
|
||||||
return []p2p.Protocol{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIs returns a list of new APIs.
|
|
||||||
func (s *Service) APIs() []rpc.API {
|
|
||||||
return []rpc.API{
|
|
||||||
{
|
|
||||||
Namespace: "visualIdentity",
|
|
||||||
Version: "0.1.0",
|
|
||||||
Service: s.api,
|
|
||||||
Public: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start is run when a service is started.
|
|
||||||
func (s *Service) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop is run when a service is stopped.
|
|
||||||
func (s *Service) Stop() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
package visualidentity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToBigBase(value *big.Int, base uint64) (res [](uint64)) {
|
|
||||||
toBigBaseImpl(value, base, &res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func toBigBaseImpl(value *big.Int, base uint64, res *[](uint64)) {
|
|
||||||
bigBase := new(big.Int).SetUint64(base)
|
|
||||||
quotient := new(big.Int).Div(value, bigBase)
|
|
||||||
if quotient.Cmp(new(big.Int).SetUint64(0)) != 0 {
|
|
||||||
toBigBaseImpl(quotient, base, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
*res = append(*res, new(big.Int).Mod(value, bigBase).Uint64())
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// compressedPubKey = |1.5 bytes chars cutoff|20 bytes emoji hash|10 bytes color hash|1.5 bytes chars cutoff|
|
|
||||||
func Slices(compressedPubkey []byte) (res [4][]byte, err error) {
|
|
||||||
if len(compressedPubkey) != 33 {
|
|
||||||
return res, errors.New("incorrect compressed pubkey")
|
|
||||||
}
|
|
||||||
|
|
||||||
getSlice := func(low, high int, and string, rsh uint) []byte {
|
|
||||||
sliceValue := new(big.Int).SetBytes(compressedPubkey[low:high])
|
|
||||||
andValue, _ := new(big.Int).SetString(and, 0)
|
|
||||||
andRes := new(big.Int).And(sliceValue, andValue)
|
|
||||||
return new(big.Int).Rsh(andRes, rsh).Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
res[0] = getSlice(0, 2, "0xFFF0", 4)
|
|
||||||
res[1] = getSlice(1, 22, "0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", 4)
|
|
||||||
res[2] = getSlice(21, 32, "0x0FFFFFFFFFFFFFFFFFFFF0", 4)
|
|
||||||
res[3] = getSlice(31, 33, "0x0FFF", 0)
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// [[1 0] [1 1] [1 2] ... [units, colors-1]]
|
|
||||||
// [3 12] => 3 units length, 12 color index
|
|
||||||
func MakeColorHashAlphabet(units, colors int) (res [][]int) {
|
|
||||||
res = make([][]int, units*colors)
|
|
||||||
idx := 0
|
|
||||||
for i := 0; i < units; i++ {
|
|
||||||
for j := 0; j < colors; j++ {
|
|
||||||
res[idx] = make([]int, 2)
|
|
||||||
res[idx][0] = i + 1
|
|
||||||
res[idx][1] = j
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToColorHash(value *big.Int, alphabet *[][]int, colorsCount int) (hash [][]int) {
|
|
||||||
alphabetLen := len(*alphabet)
|
|
||||||
indexes := ToBigBase(value, uint64(alphabetLen))
|
|
||||||
hash = make([][](int), len(indexes))
|
|
||||||
for i, v := range indexes {
|
|
||||||
hash[i] = make([](int), 2)
|
|
||||||
hash[i][0] = (*alphabet)[v][0]
|
|
||||||
hash[i][1] = (*alphabet)[v][1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// colors can't repeat themselves
|
|
||||||
// this makes color hash not fully collision resistant
|
|
||||||
prevColorIdx := hash[0][1]
|
|
||||||
hashLen := len(hash)
|
|
||||||
for i := 1; i < hashLen; i++ {
|
|
||||||
colorIdx := hash[i][1]
|
|
||||||
if colorIdx == prevColorIdx {
|
|
||||||
hash[i][1] = (colorIdx + 1) % colorsCount
|
|
||||||
}
|
|
||||||
prevColorIdx = hash[i][1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
package visualidentity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestToBigBase(t *testing.T) {
|
|
||||||
checker := func(value *big.Int, base uint64, expected *[](uint64)) {
|
|
||||||
res := ToBigBase(value, base)
|
|
||||||
if !reflect.DeepEqual(res, *expected) {
|
|
||||||
t.Fatalf("invalid big base conversion %v != %v", res, *expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lengthChecker := func(value *big.Int, base, expectedLength uint64) {
|
|
||||||
res := ToBigBase(value, base)
|
|
||||||
if len(res) != int(expectedLength) {
|
|
||||||
t.Fatalf("invalid big base conversion %d != %d", len(res), expectedLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checker(new(big.Int).SetUint64(15), 16, &[](uint64){15})
|
|
||||||
checker(new(big.Int).SetUint64(495), 16, &[](uint64){1, 14, 15})
|
|
||||||
checker(new(big.Int).SetUint64(495), 30, &[](uint64){16, 15})
|
|
||||||
checker(new(big.Int).SetUint64(495), 1024, &[](uint64){495})
|
|
||||||
checker(new(big.Int).SetUint64(2048), 1024, &[](uint64){2, 0})
|
|
||||||
|
|
||||||
base := uint64(math.Pow(2, 7*4))
|
|
||||||
checker(toBigInt(t, "0xFFFFFFFFFFFFFF"), base, &[](uint64){base - 1, base - 1})
|
|
||||||
|
|
||||||
val := toBigInt(t, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
|
|
||||||
lengthChecker(val, 2757, 14)
|
|
||||||
lengthChecker(val, 2756, 15)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToEmojiHash(t *testing.T) {
|
|
||||||
alphabet := [](string){"😇", "🤐", "🥵", "🙊", "🤌"}
|
|
||||||
|
|
||||||
checker := func(valueStr string, hashLen int, expected *[](string)) {
|
|
||||||
value := toBigInt(t, valueStr)
|
|
||||||
res, err := ToEmojiHash(value, hashLen, &alphabet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
if !reflect.DeepEqual(res, *expected) {
|
|
||||||
t.Fatalf("invalid emojihash conversion %v != %v", res, *expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checker("777", 5, &[](string){"🤐", "🤐", "🤐", "😇", "🥵"})
|
|
||||||
checker("777", 0, &[](string){"🤐", "🤐", "🤐", "😇", "🥵"})
|
|
||||||
checker("777", 10, &[](string){"😇", "😇", "😇", "😇", "😇", "🤐", "🤐", "🤐", "😇", "🥵"})
|
|
||||||
|
|
||||||
// 20bytes of data described by 14 emojis requires at least 2757 length alphabet
|
|
||||||
alphabet = make([](string), 2757)
|
|
||||||
val := toBigInt(t, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") // 20 bytes
|
|
||||||
_, err := ToEmojiHash(val, 14, &alphabet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
alphabet = make([](string), 2757-1)
|
|
||||||
_, err = ToEmojiHash(val, 14, &alphabet)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSlices(t *testing.T) {
|
|
||||||
checker := func(compressedKey, charsCutoffA, emojiHash, colorHash, charsCutoffB string) {
|
|
||||||
slices, err := Slices(toBigInt(t, compressedKey).Bytes())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
sliceChecker := func(idx int, value *big.Int) {
|
|
||||||
if !reflect.DeepEqual(slices[idx], value.Bytes()) {
|
|
||||||
t.Fatalf("invalid slice (%d) %v != %v", idx, slices[idx], value.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sliceChecker(0, toBigInt(t, charsCutoffA))
|
|
||||||
sliceChecker(1, toBigInt(t, emojiHash))
|
|
||||||
sliceChecker(2, toBigInt(t, colorHash))
|
|
||||||
sliceChecker(3, toBigInt(t, charsCutoffB))
|
|
||||||
}
|
|
||||||
|
|
||||||
checker("0x03086138b210f21d41c757ae8a5d2a4cb29c1350f7389517608378ebd9efcf4a55", "0x030", "0x86138b210f21d41c757ae8a5d2a4cb29c1350f73", "0x89517608378ebd9efcf4", "0xa55")
|
|
||||||
checker("0x020000000000000000000000000000000000000000100000000000000000000000", "0x020", "0x0000000000000000000000000000000000000001", "0x00000000000000000000", "0x000")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSlicesInvalid(t *testing.T) {
|
|
||||||
_, err := Slices(toBigInt(t, "0x01").Bytes())
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorHash(t *testing.T) {
|
|
||||||
alphabet := MakeColorHashAlphabet(4, 4)
|
|
||||||
|
|
||||||
checker := func(valueStr string, expected *[][](int)) {
|
|
||||||
value := toBigInt(t, valueStr)
|
|
||||||
res := ToColorHash(value, &alphabet, 4)
|
|
||||||
if !reflect.DeepEqual(res, *expected) {
|
|
||||||
t.Fatalf("invalid colorhash conversion %v != %v", res, *expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checker("0x0", &[][]int{{1, 0}})
|
|
||||||
checker("0x1", &[][]int{{1, 1}})
|
|
||||||
checker("0x4", &[][]int{{2, 0}})
|
|
||||||
checker("0xF", &[][]int{{4, 3}})
|
|
||||||
|
|
||||||
// oops, collision
|
|
||||||
checker("0xFF", &[][]int{{4, 3}, {4, 0}})
|
|
||||||
checker("0xFC", &[][]int{{4, 3}, {4, 0}})
|
|
||||||
|
|
||||||
checker("0xFFFF", &[][]int{{4, 3}, {4, 0}, {4, 3}, {4, 0}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func toBigInt(t *testing.T, str string) *big.Int {
|
|
||||||
res, ok := new(big.Int).SetString(str, 0)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("invalid conversion to int from %s", str)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
Loading…
Reference in New Issue