feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
package alias
// For details: https://en.wikipedia.org/wiki/Linear-feedback_shift_register
type LSFR struct {
data uint64
poly uint64
}
func newLSFR(poly uint64, seed uint64) *LSFR {
return &LSFR{data: seed, poly: poly}
}
func (f *LSFR) next() uint64 {
var bit uint64
var i uint64
for i = 0; i < 64; i++ {
if f.poly&(1<<i) != 0 {
bit ^= (f.data >> i)
}
}
bit &= 0x01
f.data = (f.data << 1) | bit
return f.data
}

View File

@@ -0,0 +1,45 @@
package alias
import (
"crypto/ecdsa"
"encoding/hex"
"fmt"
"github.com/status-im/status-go/eth-node/crypto"
)
const poly uint64 = 0xB8
func generate(seed uint64) string {
generator := newLSFR(poly, seed)
adjective1Index := generator.next() % uint64(len(adjectives))
adjective2Index := generator.next() % uint64(len(adjectives))
animalIndex := generator.next() % uint64(len(animals))
adjective1 := adjectives[adjective1Index]
adjective2 := adjectives[adjective2Index]
animal := animals[animalIndex]
return fmt.Sprintf("%s %s %s", adjective1, adjective2, animal)
}
// GenerateFromPublicKey returns the 3 words name given an *ecdsa.PublicKey
func GenerateFromPublicKey(publicKey *ecdsa.PublicKey) string {
// Here we truncate the public key to the least significant 64 bits
return generate(uint64(publicKey.X.Int64()))
}
// GenerateFromPublicKeyString returns the 3 words name given a public key
// prefixed with 0x
func GenerateFromPublicKeyString(publicKeyString string) (string, error) {
publicKeyBytes, err := hex.DecodeString(publicKeyString[2:])
if err != nil {
return "", err
}
publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes)
if err != nil {
return "", err
}
return GenerateFromPublicKey(publicKey), nil
}

View File

@@ -0,0 +1,33 @@
package alias
import (
"strings"
)
func IsAdjective(val string) bool {
for _, v := range adjectives {
if v == val {
return true
}
}
return false
}
func IsAnimal(val string) bool {
for _, v := range animals {
if v == val {
return true
}
}
return false
}
func IsAlias(alias string) bool {
aliasParts := strings.Fields(alias)
if len(aliasParts) == 3 {
if IsAdjective(strings.Title(aliasParts[0])) && IsAdjective(strings.Title(aliasParts[1])) && IsAnimal(strings.Title(aliasParts[2])) {
return true
}
}
return false
}

View File

@@ -0,0 +1,74 @@
package colorhash
import (
"math/big"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/protocol/identity"
)
const (
colorHashSegmentMaxLen = 5
colorHashColorsCount = 32
)
var colorHashAlphabet [][]int
func GenerateFor(pubkey string) (hash multiaccounts.ColorHash, 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 multiaccounts.ColorHash) {
alphabetLen := len(*alphabet)
indexes := identity.ToBigBase(value, uint64(alphabetLen))
hash = make(multiaccounts.ColorHash, len(indexes))
for i, v := range indexes {
hash[i] = [2]int{}
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
}

View File

@@ -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
}

View File

@@ -0,0 +1,61 @@
package identicon
import (
"crypto/md5" // nolint: gosec
"image/color"
"github.com/lucasb-eyer/go-colorful"
)
const (
defaultSaturation = 0.5
defaultLightness = 0.7
)
type Identicon struct {
bitmap []byte
color color.Color
}
func generate(key string) Identicon {
hash := md5.Sum([]byte(key)) // nolint: gosec
return Identicon{
convertPatternToBinarySwitch(generatePatternFromHash(hash)),
getColorFromHash(hash),
}
}
func getColorFromHash(h [16]byte) color.Color {
// Take the last 3 relevant bytes, and convert to a float between [0..360]
sum := float64(h[13]) + float64(h[14]) + float64(h[15])
t := (sum / 765) * 360
return colorful.Hsl(t, defaultSaturation, defaultLightness)
}
func generatePatternFromHash(sum [16]byte) []byte {
p := make([]byte, 25)
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
jCount := j
if j > 2 {
jCount = 4 - j
}
p[5*i+j] = sum[3*i+jCount]
}
}
return p
}
func convertPatternToBinarySwitch(pattern []byte) []byte {
b := make([]byte, 25)
for i, v := range pattern {
if v%2 == 0 {
b[i] = 1
} else {
b[i] = 0
}
}
return b
}

View File

@@ -0,0 +1,73 @@
package identicon
import (
"bytes"
"encoding/base64"
"image"
"image/color"
"image/draw"
"image/png"
)
const (
Width = 50
Height = 50
)
func renderBase64(id Identicon) (string, error) {
img, err := render(id)
if err != nil {
return "", err
}
encodedString := base64.StdEncoding.EncodeToString(img)
image := "data:image/png;base64," + encodedString
return image, nil
}
func setBackgroundTransparent(img *image.RGBA) {
draw.Draw(img, img.Bounds(), &image.Uniform{C: color.Transparent}, image.Point{}, draw.Src)
}
func drawRect(rgba *image.RGBA, i int, c color.Color) {
sizeSquare := 6
maxRow := 5
r := image.Rect(
10+(i%maxRow)*sizeSquare,
10+(i/maxRow)*sizeSquare,
10+(i%maxRow)*sizeSquare+sizeSquare,
10+(i/maxRow)*sizeSquare+sizeSquare,
)
draw.Draw(rgba, r, &image.Uniform{C: c}, image.Point{}, draw.Src)
}
func render(id Identicon) ([]byte, error) {
img := image.NewRGBA(image.Rect(0, 0, Width, Height))
var buff bytes.Buffer
setBackgroundTransparent(img)
for i, v := range id.bitmap {
if v == 1 {
drawRect(img, i, id.color)
}
}
if err := png.Encode(&buff, img); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
// GenerateBase64 generates an identicon in base64 png format given a string
func GenerateBase64(id string) (string, error) {
i := generate(id)
return renderBase64(i)
}
func Generate(id string) ([]byte, error) {
i := generate(id)
return render(i)
}

View File

@@ -0,0 +1,91 @@
package ring
import (
"bytes"
"fmt"
"image"
"image/png"
"math"
"github.com/fogleman/gg"
"github.com/status-im/status-go/multiaccounts"
)
type Theme int
const (
LightTheme Theme = 1
DarkTheme Theme = 2
)
var (
lightThemeIdenticonRingColors = []string{
"#000000", "#726F6F", "#C4C4C4", "#E7E7E7", "#FFFFFF", "#00FF00",
"#009800", "#B8FFBB", "#FFC413", "#9F5947", "#FFFF00", "#A8AC00",
"#FFFFB0", "#FF5733", "#FF0000", "#9A0000", "#FF9D9D", "#FF0099",
"#C80078", "#FF00FF", "#900090", "#FFB0FF", "#9E00FF", "#0000FF",
"#000086", "#9B81FF", "#3FAEF9", "#9A6600", "#00FFFF", "#008694",
"#C2FFFF", "#00F0B6"}
darkThemeIdenticonRingColors = []string{
"#000000", "#726F6F", "#C4C4C4", "#E7E7E7", "#FFFFFF", "#00FF00",
"#009800", "#B8FFBB", "#FFC413", "#9F5947", "#FFFF00", "#A8AC00",
"#FFFFB0", "#FF5733", "#FF0000", "#9A0000", "#FF9D9D", "#FF0099",
"#C80078", "#FF00FF", "#900090", "#FFB0FF", "#9E00FF", "#0000FF",
"#000086", "#9B81FF", "#3FAEF9", "#9A6600", "#00FFFF", "#008694",
"#C2FFFF", "#00F0B6"}
)
type DrawRingParam struct {
Theme Theme `json:"theme"`
ColorHash multiaccounts.ColorHash `json:"colorHash"`
ImageBytes []byte `json:"imageBytes"`
Height int `json:"height"`
Width int `json:"width"`
RingWidth float64 `json:"ringWidth"`
}
func DrawRing(param *DrawRingParam) ([]byte, error) {
var colors []string
switch param.Theme {
case LightTheme:
colors = lightThemeIdenticonRingColors
case DarkTheme:
colors = darkThemeIdenticonRingColors
default:
return nil, fmt.Errorf("unknown theme")
}
dc := gg.NewContext(param.Width, param.Height)
img, _, err := image.Decode(bytes.NewReader(param.ImageBytes))
if err != nil {
return nil, err
}
dc.DrawImage(img, 0, 0)
radius := (float64(param.Height) - param.RingWidth) / 2
arcPos := 0.0
totalRingUnits := 0
for i := 0; i < len(param.ColorHash); i++ {
totalRingUnits += param.ColorHash[i][0]
}
unitRadLen := 2 * math.Pi / float64(totalRingUnits)
for i := 0; i < len(param.ColorHash); i++ {
dc.SetHexColor(colors[param.ColorHash[i][1]])
dc.DrawArc(float64(param.Width/2), float64(param.Height/2), radius, arcPos, arcPos+unitRadLen*float64(param.ColorHash[i][0]))
dc.SetLineWidth(param.RingWidth)
dc.SetLineCapButt()
dc.Stroke()
arcPos += unitRadLen * float64(param.ColorHash[i][0])
}
buf := new(bytes.Buffer)
err = png.Encode(buf, dc.Image())
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View File

@@ -0,0 +1,95 @@
package identity
import (
"encoding/json"
"github.com/status-im/status-go/protocol/protobuf"
)
// static links which need to be decorated by the UI clients
const (
TwitterID = "__twitter"
PersonalSiteID = "__personal_site"
GithubID = "__github"
YoutubeID = "__youtube"
DiscordID = "__discord"
TelegramID = "__telegram"
)
type SocialLink struct {
Text string `json:"text"`
URL string `json:"url"`
}
type SocialLinks []*SocialLink
type SocialLinksInfo struct {
Links []*SocialLink `json:"links"`
Removed bool `json:"removed"`
}
func NewSocialLinks(links []*protobuf.SocialLink) SocialLinks {
res := SocialLinks{}
for _, link := range links {
res = append(res, &SocialLink{Text: link.Text, URL: link.Url})
}
return res
}
func (s *SocialLink) ToProtobuf() *protobuf.SocialLink {
return &protobuf.SocialLink{
Text: s.Text,
Url: s.URL,
}
}
func (s *SocialLink) Equal(link *SocialLink) bool {
return s.Text == link.Text && s.URL == link.URL
}
func (s *SocialLinks) ToProtobuf() []*protobuf.SocialLink {
res := []*protobuf.SocialLink{}
for _, link := range *s {
res = append(res, link.ToProtobuf())
}
return res
}
func (s *SocialLinks) ToSyncProtobuf(clock uint64) *protobuf.SyncSocialLinks {
res := &protobuf.SyncSocialLinks{
Clock: clock,
}
for _, link := range *s {
res.SocialLinks = append(res.SocialLinks, link.ToProtobuf())
}
return res
}
// Equal means the same links at the same order
func (s *SocialLinks) Equal(links SocialLinks) bool {
if len(*s) != len(links) {
return false
}
for i := range *s {
if !(*s)[i].Equal(links[i]) {
return false
}
}
return true
}
func (s *SocialLinks) Contains(link *SocialLink) bool {
if len(*s) == 0 {
return false
}
for _, l := range *s {
if l.Equal(link) {
return true
}
}
return false
}
func (s *SocialLinks) Serialize() ([]byte, error) {
return json.Marshal(*s)
}

View File

@@ -0,0 +1,81 @@
package identity
import (
"errors"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
)
func ToColorID(pubkey string) (int64, error) {
const colorPalletLength = 12
pubkeyValue, ok := new(big.Int).SetString(pubkey, 0)
if !ok {
return 0, fmt.Errorf("invalid pubkey: %s", pubkey)
}
colorID := new(big.Int).Mod(pubkeyValue, new(big.Int).SetInt64(colorPalletLength-1)).Int64()
return colorID, nil
}
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
}