Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
vendor/github.com/ethereum/go-ethereum/vendor
node_modules/
tags
build/

#*
.#*
Expand Down
3 changes: 3 additions & 0 deletions node/get_status_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ 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"
Expand Down Expand Up @@ -117,6 +118,7 @@ type StatusNode struct {
gifSrvc *gif.Service
stickersSrvc *stickers.Service
chatSrvc *chat.Service
visualIdentitySrvc *visualIdentity.Service
}

// New makes new instance of StatusNode.
Expand Down Expand Up @@ -427,6 +429,7 @@ 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
Expand Down
22 changes: 22 additions & 0 deletions node/status_node_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ 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"
Expand Down Expand Up @@ -78,6 +79,12 @@ 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 {
Expand Down Expand Up @@ -388,6 +395,21 @@ 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)
Expand Down
92 changes: 92 additions & 0 deletions services/visual-identity/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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]")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we can change this to .Debug or .Trace (if it exists), since it might pollute the logs too much


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)
}
72 changes: 72 additions & 0 deletions services/visual-identity/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package visualidentity

import (
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

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)

checker := func(pubkey string, expected *[](string)) {
emojihash, err := api.EmojiHashOf(pubkey)
require.NoError(t, err)
if !reflect.DeepEqual(emojihash, *expected) {
t.Fatalf("invalid emojihash %v != %v", emojihash, *expected)
}
}

checker("0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8",
&[](string){"👦🏽", "🦹🏻", "👶🏿", "🛁", "🌁", "🙌🏻", "🙇🏽‍♂️", "🙌🏾", "🤥", "🐛", "👩🏽‍🔧", "🔧", "⚙️", "🧒🏽"})

checker("0x0400000000000000000000000000000000000000000000000000000000000000014218F20AE6C646B363DB68605822FB14264CA8D2587FDD6FBC750D587E76A7EE",
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀"})

checker("0x04000000000000000000000000000000000000000010000000000000000000000033600332D373318ECC2F212A30A5750D2EAC827B6A32B33D326CCF369B12B1BE",
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (*api.emojisAlphabet)[1]})

checker("0x040000000000000000000000000000000000000000200000000000000000000000353050BFE33B724E60A0C600FBA565A9B62217B1BD35BF9848F2AB847C598B30",
&[](string){"😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", "😀", (*api.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)

checker := func(pubkey string) {
_, err := api.EmojiHashOf(pubkey)
require.Error(t, err)
_, err = api.ColorHashOf(pubkey)
require.Error(t, err)
}
checker("abc")
checker("0x01")
checker("0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
checker("0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8")
}
53 changes: 53 additions & 0 deletions services/visual-identity/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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
}
Loading