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"
|
||||
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/colorhash"
|
||||
"github.com/status-im/status-go/protocol/identity/emojihash"
|
||||
"github.com/status-im/status-go/server"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/services/typeddata"
|
||||
|
@ -652,6 +654,14 @@ func Identicon(pk string) string {
|
|||
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 {
|
||||
m := extkeys.NewMnemonic()
|
||||
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/stickers"
|
||||
"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/wakuv2ext"
|
||||
"github.com/status-im/status-go/services/wallet"
|
||||
|
@ -118,7 +117,6 @@ type StatusNode struct {
|
|||
gifSrvc *gif.Service
|
||||
stickersSrvc *stickers.Service
|
||||
chatSrvc *chat.Service
|
||||
visualIdentitySrvc *visualIdentity.Service
|
||||
}
|
||||
|
||||
// New makes new instance of StatusNode.
|
||||
|
@ -429,7 +427,6 @@ func (n *StatusNode) stop() error {
|
|||
n.wakuV2ExtSrvc = nil
|
||||
n.ensSrvc = nil
|
||||
n.stickersSrvc = nil
|
||||
n.visualIdentitySrvc = nil
|
||||
n.publicMethods = make(map[string]bool)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -38,7 +38,6 @@ import (
|
|||
"github.com/status-im/status-go/services/status"
|
||||
"github.com/status-im/status-go/services/stickers"
|
||||
"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/wakuv2ext"
|
||||
"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.ChatService())
|
||||
|
||||
visualIdentitySrvc, err := b.visualIdentityService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
services = append(services, visualIdentitySrvc)
|
||||
|
||||
if config.WakuConfig.Enabled {
|
||||
wakuService, err := b.wakuService(&config.WakuConfig, &config.ClusterConfig)
|
||||
if err != nil {
|
||||
|
@ -395,21 +388,6 @@ func (b *StatusNode) gifService() *gif.Service {
|
|||
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 {
|
||||
if b.chatSrvc == nil {
|
||||
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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/status-im/status-go/protocol/identity"
|
||||
)
|
||||
|
||||
func setupTestAPI(t *testing.T) *API {
|
||||
api := NewAPI()
|
||||
|
||||
alphabet, err := LoadAlphabet()
|
||||
require.NoError(t, err)
|
||||
|
||||
api.emojisAlphabet = alphabet
|
||||
return api
|
||||
}
|
||||
|
||||
func TestEmojiHashOf(t *testing.T) {
|
||||
api := setupTestAPI(t)
|
||||
|
||||
func TestGenerateFor(t *testing.T) {
|
||||
checker := func(pubkey string, expected *[](string)) {
|
||||
emojihash, err := api.EmojiHashOf(pubkey)
|
||||
emojihash, err := GenerateFor(pubkey)
|
||||
require.NoError(t, err)
|
||||
if !reflect.DeepEqual(emojihash, *expected) {
|
||||
t.Fatalf("invalid emojihash %v != %v", emojihash, *expected)
|
||||
|
@ -35,34 +25,15 @@ func TestEmojiHashOf(t *testing.T) {
|
|||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀"})
|
||||
|
||||
checker("0x04000000000000000000000000000000000000000010000000000000000000000033600332D373318ECC2F212A30A5750D2EAC827B6A32B33D326CCF369B12B1BE",
|
||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (*api.emojisAlphabet)[1]})
|
||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (emojisAlphabet)[1]})
|
||||
|
||||
checker("0x040000000000000000000000000000000000000000200000000000000000000000353050BFE33B724E60A0C600FBA565A9B62217B1BD35BF9848F2AB847C598B30",
|
||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (*api.emojisAlphabet)[2]})
|
||||
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (emojisAlphabet)[2]})
|
||||
}
|
||||
|
||||
func TestColorHashOf(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)
|
||||
|
||||
func TestEmojiHashOfInvalidKey(t *testing.T) {
|
||||
checker := func(pubkey string) {
|
||||
_, err := api.EmojiHashOf(pubkey)
|
||||
require.Error(t, err)
|
||||
_, err = api.ColorHashOf(pubkey)
|
||||
_, err := GenerateFor(pubkey)
|
||||
require.Error(t, err)
|
||||
}
|
||||
checker("abc")
|
||||
|
@ -70,3 +41,30 @@ func TestHashesOfInvalidKey(t *testing.T) {
|
|||
checker("0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
|
||||
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