diff --git a/Makefile b/Makefile index 8dd2d19b8..9b1361023 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ setup-build: lint-install release-install gomobile-install ##@other Prepare proj setup: setup-build setup-dev tidy ##@other Prepare project for development and building generate: ##@other Regenerate assets and other auto-generated stuff - go generate ./static ./static/encryption_migrations ./static/mailserver_db_migrations ./t + go generate ./static ./static/chat_db_migrations ./static/mailserver_db_migrations ./t $(shell cd ./services/shhext/chat && exec protoc --go_out=. ./*.proto) prepare-release: clean-release diff --git a/mailserver/migrations/bindata.go b/mailserver/migrations/bindata.go index e19ee203b..042ba8619 100644 --- a/mailserver/migrations/bindata.go +++ b/mailserver/migrations/bindata.go @@ -1,15 +1,15 @@ -// Code generated by go-bindata. DO NOT EDIT. +// Code generated by go-bindata. // sources: -// 1557732988_initialize_db.down.sql (72B) -// 1557732988_initialize_db.up.sql (234B) -// static.go (178B) +// 1557732988_initialize_db.down.sql +// 1557732988_initialize_db.up.sql +// static.go +// DO NOT EDIT! package migrations import ( "bytes" "compress/gzip" - "crypto/sha256" "fmt" "io" "io/ioutil" @@ -22,7 +22,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } var buf bytes.Buffer @@ -30,7 +30,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } if clErr != nil { return nil, err @@ -40,9 +40,8 @@ func bindataRead(data []byte, name string) ([]byte, error) { } type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte + bytes []byte + info os.FileInfo } type bindataFileInfo struct { @@ -86,8 +85,8 @@ func _1557732988_initialize_dbDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1557732988_initialize_db.down.sql", size: 72, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x77, 0x40, 0x78, 0xb7, 0x71, 0x3c, 0x20, 0x3b, 0xc9, 0xb, 0x2f, 0x49, 0xe4, 0xff, 0x1c, 0x84, 0x54, 0xa1, 0x30, 0xe3, 0x90, 0xf8, 0x73, 0xda, 0xb0, 0x2a, 0xea, 0x8e, 0xf1, 0x82, 0xe7, 0xd2}} + info := bindataFileInfo{name: "1557732988_initialize_db.down.sql", size: 72, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -106,8 +105,8 @@ func _1557732988_initialize_dbUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1557732988_initialize_db.up.sql", size: 234, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0xa, 0x31, 0xf, 0x94, 0xe, 0xd7, 0xd6, 0xaa, 0x22, 0xd6, 0x6c, 0x7a, 0xbc, 0xad, 0x6a, 0xed, 0x2e, 0x7a, 0xf0, 0x24, 0x81, 0x87, 0x14, 0xe, 0x1c, 0x8a, 0xf1, 0x45, 0xaf, 0x9e, 0x85}} + info := bindataFileInfo{name: "1557732988_initialize_db.up.sql", size: 234, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -126,8 +125,8 @@ func staticGo() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static.go", size: 178, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xab, 0x8a, 0xf4, 0x27, 0x24, 0x9d, 0x2a, 0x1, 0x7b, 0x54, 0xea, 0xae, 0x4a, 0x35, 0x40, 0x92, 0xb5, 0xf9, 0xb3, 0x54, 0x3e, 0x3a, 0x1a, 0x2b, 0xae, 0xfb, 0x9e, 0x82, 0xeb, 0x4c, 0xf, 0x6}} + info := bindataFileInfo{name: "static.go", size: 178, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -135,8 +134,8 @@ func staticGo() (*asset, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) @@ -146,12 +145,6 @@ func Asset(name string) ([]byte, error) { return nil, fmt.Errorf("Asset %s not found", name) } -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - // MustAsset is like Asset but panics when Asset would return an error. // It simplifies safe initialization of global variables. func MustAsset(name string) []byte { @@ -163,18 +156,12 @@ func MustAsset(name string) []byte { return a } -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - // AssetInfo loads and returns the asset info for the given name. // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) @@ -184,33 +171,6 @@ func AssetInfo(name string) (os.FileInfo, error) { return nil, fmt.Errorf("AssetInfo %s not found", name) } -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - // AssetNames returns the names of the assets. func AssetNames() []string { names := make([]string, 0, len(_bindata)) @@ -223,9 +183,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "1557732988_initialize_db.down.sql": _1557732988_initialize_dbDownSql, - "1557732988_initialize_db.up.sql": _1557732988_initialize_dbUpSql, - "static.go": staticGo, } @@ -238,15 +196,15 @@ var _bindata = map[string]func() (*asset, error){ // img/ // a.png // b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error // AssetDir("") will return []string{"data"}. func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { @@ -268,14 +226,13 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "1557732988_initialize_db.down.sql": &bintree{_1557732988_initialize_dbDownSql, map[string]*bintree{}}, - "1557732988_initialize_db.up.sql": &bintree{_1557732988_initialize_dbUpSql, map[string]*bintree{}}, - "static.go": &bintree{staticGo, map[string]*bintree{}}, + "1557732988_initialize_db.up.sql": &bintree{_1557732988_initialize_dbUpSql, map[string]*bintree{}}, + "static.go": &bintree{staticGo, map[string]*bintree{}}, }} -// RestoreAsset restores an asset under the given directory. +// RestoreAsset restores an asset under the given directory func RestoreAsset(dir, name string) error { data, err := Asset(name) if err != nil { @@ -293,10 +250,14 @@ func RestoreAsset(dir, name string) error { if err != nil { return err } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil } -// RestoreAssets restores an asset under the given directory recursively. +// RestoreAssets restores an asset under the given directory recursively func RestoreAssets(dir, name string) error { children, err := AssetDir(name) // File @@ -314,6 +275,7 @@ func RestoreAssets(dir, name string) error { } func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/params/config.go b/params/config.go index 7703f9407..ce0991be4 100644 --- a/params/config.go +++ b/params/config.go @@ -368,6 +368,8 @@ type WalletConfig struct { // ShhextConfig defines options used by shhext service. type ShhextConfig struct { + // AsymKeyID the key id of the selected account + AsymKeyID string PFSEnabled bool // BackupDisabledDataDir is the file system folder the node should use for any data storage needs that it doesn't want backed up. BackupDisabledDataDir string diff --git a/services/shhext/api.go b/services/shhext/api.go index f845b8771..e9d312951 100644 --- a/services/shhext/api.go +++ b/services/shhext/api.go @@ -14,8 +14,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/golang/protobuf/proto" "github.com/status-im/status-go/db" "github.com/status-im/status-go/mailserver" "github.com/status-im/status-go/services/shhext/chat" @@ -485,8 +487,15 @@ func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg chat.SendPublic return nil, err } + // marshal for sending to wire + marshaledMessage, err := proto.Marshal(protocolMessage) + if err != nil { + api.log.Error("encryption-service", "error marshaling message", err) + return nil, err + } + // Enrich with transport layer info - whisperMessage := chat.PublicMessageToWhisper(msg, protocolMessage) + whisperMessage := chat.PublicMessageToWhisper(msg, marshaledMessage) whisperMessage.SymKeyID = symKeyID // And dispatch @@ -510,20 +519,46 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect } // This is transport layer-agnostic - var protocolMessage []byte + var protocolMessage *chat.ProtocolMessage + // The negotiated secret + var topic []byte + api.log.Info("BUILDING MESSAGE") if msg.DH { - protocolMessage, err = api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload) + protocolMessage, topic, err = api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload) } else { - protocolMessage, err = api.service.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload) + protocolMessage, topic, err = api.service.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload) } + api.log.Info("BUILT MESSAGE", "topic", topic) + if err != nil { return nil, err } + // marshal for sending to wire + marshaledMessage, err := proto.Marshal(protocolMessage) + if err != nil { + api.log.Error("encryption-service", "error marshaling message", err) + return nil, err + } + + // TODO: Refactor this as it's not quite the right abstraction anymore + whisperMessage := chat.DirectMessageToWhisper(msg, marshaledMessage, topic) // Enrich with transport layer info - whisperMessage := chat.DirectMessageToWhisper(msg, protocolMessage) + if topic != nil { + api.log.Info("GETTING SYM KEY", "symkey", api.service.GetNegotiatedChat(publicKey)) + + chat := api.service.GetNegotiatedChat(publicKey) + + if chat != nil { + whisperMessage.SymKeyID = chat.SymKeyID + whisperMessage.Topic = whisper.BytesToTopic(chat.Topic) + whisperMessage.PublicKey = nil + } + } + + api.log.Info("WHISPER MESSAGE", "message", whisperMessage) // And dispatch return api.Post(ctx, whisperMessage) @@ -637,40 +672,6 @@ func (api *PublicAPI) CompleteRequest(parent context.Context, hex string) (err e return err } -// DEPRECATED: use SendDirectMessage with DH flag -// SendPairingMessage sends a 1:1 chat message to our own devices to initiate a pairing session -func (api *PublicAPI) SendPairingMessage(ctx context.Context, msg chat.SendDirectMessageRPC) ([]hexutil.Bytes, error) { - if !api.service.pfsEnabled { - return nil, ErrPFSNotEnabled - } - // To be completely agnostic from whisper we should not be using whisper to store the key - privateKey, err := api.service.w.GetPrivateKey(msg.Sig) - if err != nil { - return nil, err - } - - msg.PubKey = crypto.FromECDSAPub(&privateKey.PublicKey) - - protocolMessage, err := api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload) - if err != nil { - return nil, err - } - - var response []hexutil.Bytes - - // Enrich with transport layer info - whisperMessage := chat.DirectMessageToWhisper(msg, protocolMessage) - - // And dispatch - hash, err := api.Post(ctx, whisperMessage) - if err != nil { - return nil, err - } - response = append(response, hash) - - return response, nil -} - func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) error { msg := dedupMessage.Message @@ -689,7 +690,15 @@ func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) e return err } - response, err := api.service.protocol.HandleMessage(privateKey, publicKey, msg.Payload, dedupMessage.DedupID) + // Unmarshal message + protocolMessage := &chat.ProtocolMessage{} + + if err := proto.Unmarshal(msg.Payload, protocolMessage); err != nil { + api.log.Debug("Not a protocol message", "err", err) + return nil + } + + response, err := api.service.protocol.HandleMessage(privateKey, publicKey, protocolMessage, dedupMessage.DedupID) switch err { case nil: @@ -703,13 +712,9 @@ func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) e handler := EnvelopeSignalHandler{} handler.DecryptMessageFailed(keyString) } - case chat.ErrNotProtocolMessage: - // Not using encryption, pass directly to the layer below - api.log.Debug("Not a protocol message", "err", err) default: // Log and pass to the client, even if failed to decrypt api.log.Error("Failed handling message with error", "err", err) - } return nil diff --git a/services/shhext/chat/chat.proto b/services/shhext/chat/chat.proto deleted file mode 100644 index 9f70a325c..000000000 --- a/services/shhext/chat/chat.proto +++ /dev/null @@ -1,50 +0,0 @@ -syntax = "proto3"; - -package chat; - -// What is sent through the wire -message ChatMessagePayload { - // Message content - string content = 1; - // MIME type - string content_type = 2; - // Message type - string message_type = 3; - // Sender's clock value for message ordering - double clock_value = 4; -} - -// ContactUpdatePayload is sent when a user updates its profile -message ContactUpdatePayload { - // Contact display name - string name = 1; - // Contact profile image, using the data URI scheme (e.g. "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAA...") - string profile_image = 2; - // Contact address - string address = 3; - // Contact Firebase Cloud Messaging token - string fcm_token = 4; -} - -// Incoming RPC messages -message OneToOneRPC { - string src = 1; - string dst = 2; - bytes payload = 3; - //ChatMessagePayload payload = 3; -} - -message ContactUpdateRPC { - string src = 1; - string dst = 2; - ContactUpdatePayload payload = 3; -} - -// Incoming messages -message ChatProtocolMessage { - bytes payload = 1; - //oneof payload { - // ChatMessagePayload one_to_one_payload = 1; - // ContactUpdatePayload contact_updated_payload = 2; - //} -} diff --git a/services/shhext/chat/db/db.go b/services/shhext/chat/db/db.go new file mode 100644 index 000000000..455180221 --- /dev/null +++ b/services/shhext/chat/db/db.go @@ -0,0 +1,244 @@ +package db + +import ( + "database/sql" + "fmt" + "os" + + sqlite "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation + "github.com/status-im/migrate" + "github.com/status-im/migrate/database/sqlcipher" + "github.com/status-im/migrate/source/go_bindata" + "github.com/status-im/status-go/services/shhext/chat/db/migrations" +) + +const exportDB = "SELECT sqlcipher_export('newdb')" + +// The default number of kdf iterations in sqlcipher (from version 3.0.0) +// https://github.com/sqlcipher/sqlcipher/blob/fda4c68bb474da7e955be07a2b807bda1bb19bd2/CHANGELOG.md#300---2013-11-05 +// https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter +const defaultKdfIterationsNumber = 64000 + +// The reduced number of kdf iterations (for performance reasons) which is +// currently used for derivation of the database key +// https://github.com/status-im/status-go/pull/1343 +// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA +const KdfIterationsNumber = 3200 + +func MigrateDBFile(oldPath string, newPath string, oldKey string, newKey string) error { + _, err := os.Stat(oldPath) + + // No files, nothing to do + if os.IsNotExist(err) { + return nil + } + + // Any other error, throws + if err != nil { + return err + } + + if err := os.Rename(oldPath, newPath); err != nil { + return err + } + + db, err := Open(newPath, oldKey, defaultKdfIterationsNumber) + if err != nil { + return err + } + + keyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey) + + if _, err = db.Exec(keyString); err != nil { + return err + } + + return nil + +} + +// MigrateDBKeyKdfIterations changes the number of kdf iterations executed +// during the database key derivation. This change is necessary because +// of performance reasons. +// https://github.com/status-im/status-go/pull/1343 +// `sqlcipher_export` is used for migration, check out this link for details: +// https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export +func MigrateDBKeyKdfIterations(oldPath string, newPath string, key string) error { + _, err := os.Stat(oldPath) + + // No files, nothing to do + if os.IsNotExist(err) { + return nil + } + + // Any other error, throws + if err != nil { + return err + } + + isEncrypted, err := sqlite.IsEncrypted(oldPath) + if err != nil { + return err + } + + // Nothing to do, move db to the next migration + if !isEncrypted { + return os.Rename(oldPath, newPath) + } + + db, err := Open(oldPath, key, defaultKdfIterationsNumber) + if err != nil { + return err + } + + attach := fmt.Sprintf( + "ATTACH DATABASE '%s' AS newdb KEY '%s'", + newPath, + key) + + if _, err = db.Exec(attach); err != nil { + return err + } + + changeKdfIter := fmt.Sprintf( + "PRAGMA newdb.kdf_iter = %d", + KdfIterationsNumber) + + if _, err = db.Exec(changeKdfIter); err != nil { + return err + } + + if _, err = db.Exec(exportDB); err != nil { + return err + } + + if err = db.Close(); err != nil { + return err + } + + return os.Remove(oldPath) +} + +// EncryptDatabase encrypts an unencrypted database with key +func EncryptDatabase(oldPath string, newPath string, key string) error { + _, err := os.Stat(oldPath) + + // No files, nothing to do + if os.IsNotExist(err) { + return nil + } + + // Any other error, throws + if err != nil { + return err + } + + isEncrypted, err := sqlite.IsEncrypted(oldPath) + if err != nil { + return err + } + + // Nothing to do, already encrypted + if isEncrypted { + return os.Rename(oldPath, newPath) + } + + db, err := Open(oldPath, "", defaultKdfIterationsNumber) + if err != nil { + return err + } + + attach := fmt.Sprintf( + "ATTACH DATABASE '%s' AS newdb KEY '%s'", + newPath, + key) + + if _, err = db.Exec(attach); err != nil { + return err + } + + changeKdfIter := fmt.Sprintf( + "PRAGMA newdb.kdf_iter = %d", + KdfIterationsNumber) + + if _, err = db.Exec(changeKdfIter); err != nil { + return err + } + + if _, err = db.Exec(exportDB); err != nil { + return err + } + + if err = db.Close(); err != nil { + return err + } + + return os.Remove(oldPath) +} + +func migrateDB(db *sql.DB) error { + resources := bindata.Resource( + migrations.AssetNames(), + func(name string) ([]byte, error) { + return migrations.Asset(name) + }, + ) + + source, err := bindata.WithInstance(resources) + if err != nil { + return err + } + + driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{}) + if err != nil { + return err + } + + m, err := migrate.NewWithInstance( + "go-bindata", + source, + "sqlcipher", + driver) + if err != nil { + return err + } + + if err = m.Up(); err != migrate.ErrNoChange { + return err + } + + return nil +} + +func Open(path string, key string, kdfIter int) (*sql.DB, error) { + db, err := sql.Open("sqlite3", path) + if err != nil { + return nil, err + } + + keyString := fmt.Sprintf("PRAGMA key = '%s'", key) + + // Disable concurrent access as not supported by the driver + db.SetMaxOpenConns(1) + + if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil { + return nil, err + } + + if _, err = db.Exec(keyString); err != nil { + return nil, err + } + + kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter) + + if _, err = db.Exec(kdfString); err != nil { + return nil, err + } + + // Migrate db + if err = migrateDB(db); err != nil { + return nil, err + } + + return db, nil +} diff --git a/services/shhext/chat/migrations/bindata.go b/services/shhext/chat/db/migrations/bindata.go similarity index 67% rename from services/shhext/chat/migrations/bindata.go rename to services/shhext/chat/db/migrations/bindata.go index 908726568..df8c0c383 100644 --- a/services/shhext/chat/migrations/bindata.go +++ b/services/shhext/chat/db/migrations/bindata.go @@ -1,21 +1,23 @@ -// Code generated by go-bindata. DO NOT EDIT. +// Code generated by go-bindata. // sources: -// 1536754952_initial_schema.down.sql (83B) -// 1536754952_initial_schema.up.sql (962B) -// 1539249977_update_ratchet_info.down.sql (311B) -// 1539249977_update_ratchet_info.up.sql (368B) -// 1540715431_add_version.down.sql (127B) -// 1540715431_add_version.up.sql (265B) -// 1541164797_add_installations.down.sql (26B) -// 1541164797_add_installations.up.sql (216B) -// static.go (188B) +// 1536754952_initial_schema.down.sql +// 1536754952_initial_schema.up.sql +// 1539249977_update_ratchet_info.down.sql +// 1539249977_update_ratchet_info.up.sql +// 1540715431_add_version.down.sql +// 1540715431_add_version.up.sql +// 1541164797_add_installations.down.sql +// 1541164797_add_installations.up.sql +// 1558084410_add_topic.down.sql +// 1558084410_add_topic.up.sql +// static.go +// DO NOT EDIT! package migrations import ( "bytes" "compress/gzip" - "crypto/sha256" "fmt" "io" "io/ioutil" @@ -28,7 +30,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } var buf bytes.Buffer @@ -36,7 +38,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } if clErr != nil { return nil, err @@ -46,9 +48,8 @@ func bindataRead(data []byte, name string) ([]byte, error) { } type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte + bytes []byte + info os.FileInfo } type bindataFileInfo struct { @@ -92,8 +93,8 @@ func _1536754952_initial_schemaDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x44, 0xcf, 0x76, 0x71, 0x1f, 0x5e, 0x9a, 0x43, 0xd8, 0xcd, 0xb8, 0xc3, 0x70, 0xc3, 0x7f, 0xfc, 0x90, 0xb4, 0x25, 0x1e, 0xf4, 0x66, 0x20, 0xb8, 0x33, 0x7e, 0xb0, 0x76, 0x1f, 0xc, 0xc0, 0x75}} + info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -112,8 +113,8 @@ func _1536754952_initial_schemaUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xea, 0x90, 0x5a, 0x59, 0x3e, 0x3, 0xe2, 0x3c, 0x81, 0x42, 0xcd, 0x4c, 0x9a, 0xe8, 0xda, 0x93, 0x2b, 0x70, 0xa4, 0xd5, 0x29, 0x3e, 0xd5, 0xc9, 0x27, 0xb6, 0xb7, 0x65, 0xff, 0x0, 0xcb, 0xde}} + info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -132,8 +133,8 @@ func _1539249977_update_ratchet_infoDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0xa4, 0xeb, 0xa0, 0xe6, 0xa0, 0xd4, 0x48, 0xbb, 0xad, 0x6f, 0x7d, 0x67, 0x8c, 0xbd, 0x25, 0xde, 0x1f, 0x73, 0x9a, 0xbb, 0xa8, 0xc9, 0x30, 0xb7, 0xa9, 0x7c, 0xaf, 0xb5, 0x1, 0x61, 0xdd}} + info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -152,8 +153,8 @@ func _1539249977_update_ratchet_infoUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0x8e, 0xbf, 0x6f, 0xa, 0xc0, 0xe1, 0x3c, 0x42, 0x28, 0x88, 0x1d, 0xdb, 0xba, 0x1c, 0x83, 0xec, 0xba, 0xd3, 0x5f, 0x5c, 0x77, 0x5e, 0xa7, 0x46, 0x36, 0xec, 0x69, 0xa, 0x4b, 0x17, 0x79}} + info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -172,8 +173,8 @@ func _1540715431_add_versionDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0x9, 0x4, 0xe3, 0x76, 0x2e, 0xb8, 0x9, 0x23, 0xf0, 0x70, 0x93, 0xc4, 0x50, 0xe, 0x9d, 0x84, 0x22, 0x8c, 0x94, 0xd3, 0x24, 0x9, 0x9a, 0xc1, 0xa1, 0x48, 0x45, 0xfd, 0x40, 0x6e, 0xe6}} + info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -192,8 +193,8 @@ func _1540715431_add_versionUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc7, 0x4c, 0x36, 0x96, 0xdf, 0x16, 0x10, 0xa6, 0x27, 0x1a, 0x79, 0x8b, 0x42, 0x83, 0x23, 0xc, 0x7e, 0xb6, 0x3d, 0x2, 0xda, 0xa4, 0xb4, 0xd, 0x27, 0x55, 0xba, 0xdc, 0xb2, 0x88, 0x8f, 0xa6}} + info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -212,8 +213,8 @@ func _1541164797_add_installationsDownSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0xfd, 0xe6, 0xd8, 0xca, 0x3b, 0x38, 0x18, 0xee, 0x0, 0x5f, 0x36, 0x9e, 0x1e, 0xd, 0x19, 0x3e, 0xb4, 0x73, 0x53, 0xe9, 0xa5, 0xac, 0xdd, 0xa1, 0x2f, 0xc7, 0x6c, 0xa8, 0xd9, 0xa, 0x88}} + info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -232,12 +233,52 @@ func _1541164797_add_installationsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2d, 0x18, 0x26, 0xb8, 0x88, 0x47, 0xdb, 0x83, 0xcc, 0xb6, 0x9d, 0x1c, 0x1, 0xae, 0x2f, 0xde, 0x97, 0x82, 0x3, 0x30, 0xa8, 0x63, 0xa1, 0x78, 0x4b, 0xa5, 0x9, 0x8, 0x75, 0xa2, 0x57, 0x81}} + info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } -var _staticGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x41\x8a\x02\x31\x10\x46\xe1\x7d\x4e\xf1\x2f\x67\x60\x3a\xb5\x9f\x13\x0c\x83\x82\xa0\x17\xa8\x4e\x17\x95\xa2\xe9\xa4\x49\x95\xe2\xf1\xdd\x28\xe2\xf2\xc1\xe3\x23\xc2\x89\xcb\xca\x2a\xf0\xe0\xb0\x02\xd9\x66\x59\xfc\x55\x5f\xff\xe7\x1f\xfc\x5d\x8e\x87\x6f\x0c\xf1\x7e\x1d\x45\x1c\xc3\xb4\x06\xac\x45\x47\x54\xc1\x6c\x8d\x87\x89\xa7\xfd\x43\x4a\x89\x48\xfb\xaf\x4a\x93\xc1\x21\xd0\x3e\xcd\xd6\x16\x0e\xc6\xb4\xaf\x8a\xcd\x74\x70\x58\x6f\x8e\xa9\x23\x67\xca\x99\x5c\xc6\xcd\x8a\x38\x79\xad\x72\x0f\x2a\x95\x83\xde\x23\x3d\x81\xac\x1d\x39\x3d\x02\x00\x00\xff\xff\x7c\xfc\xfc\x0b\xbc\x00\x00\x00") +var __1558084410_add_topicDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xc9\x2f\xc8\x4c\x8e\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x8b\xcf\x4c\x29\xb6\xe6\x42\x57\x52\x6c\xcd\x05\x08\x00\x00\xff\xff\xf0\xe3\x8a\xc7\x36\x00\x00\x00") + +func _1558084410_add_topicDownSqlBytes() ([]byte, error) { + return bindataRead( + __1558084410_add_topicDownSql, + "1558084410_add_topic.down.sql", + ) +} + +func _1558084410_add_topicDownSql() (*asset, error) { + bytes, err := _1558084410_add_topicDownSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "1558084410_add_topic.down.sql", size: 54, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var __1558084410_add_topicUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x90\x41\x6b\x85\x30\x10\x84\xef\xf9\x15\x73\x54\xf0\x1f\xf4\xa4\x61\x95\xd0\x74\xd3\xa6\x11\xea\x49\xc4\x78\x58\x10\x2d\x35\x97\xfe\xfb\x62\x79\x4f\x94\xc7\x3b\xcf\xcc\xce\x7c\xab\x3d\x95\x81\x10\xca\xca\x12\xd2\xfa\x2d\xe3\x86\x4c\x01\x12\xa7\x25\x49\xfa\x45\x65\x5d\x05\x76\x01\xdc\x5a\x8b\x77\x6f\xde\x4a\xdf\xe1\x95\x3a\x38\x86\x76\x5c\x5b\xa3\x03\x4c\xc3\xce\x53\xa1\x80\x6d\x1a\x7f\xa6\x74\x8d\xa9\xfc\x45\xa9\xc7\xa6\x5e\x96\x2d\x0d\xf3\x3c\x24\x59\x97\x5e\xe2\xbd\x19\x81\xbe\xc2\x11\x2e\x4e\x6b\x7a\x89\xd7\xcb\xbb\xd8\xb2\xf9\x68\x29\x93\x58\x9c\x7d\xf9\x93\x7d\xb5\xf3\x64\x1a\xfe\x27\xc8\x2e\x7e\x4f\x35\x79\x62\x4d\x9f\xb7\x47\x1c\x72\xbe\x03\xfc\x05\x00\x00\xff\xff\xf3\xa6\x3d\xc3\x2a\x01\x00\x00") + +func _1558084410_add_topicUpSqlBytes() ([]byte, error) { + return bindataRead( + __1558084410_add_topicUpSql, + "1558084410_add_topic.up.sql", + ) +} + +func _1558084410_add_topicUpSql() (*asset, error) { + bytes, err := _1558084410_add_topicUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "1558084410_add_topic.up.sql", size: 298, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _staticGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x41\x6a\x03\x31\x0c\x46\xe1\xbd\x4f\xf1\x2f\x5b\xe8\x58\xfb\x9e\xa0\x94\x16\x0a\xcd\x05\x64\x8f\x90\xc5\x30\xf6\x60\x29\x21\xc7\xcf\x26\x21\x64\xf9\xe0\xf1\x11\xe1\x8f\xeb\xc6\x2a\xf0\xe0\xb0\x0a\xd9\x8b\xac\xfe\xa8\xb7\xef\xff\x0f\x7c\x9d\x7e\x7f\xde\x31\xc5\xc7\x79\x56\x71\x4c\xd3\x16\xb0\x1e\x03\xd1\x04\xc5\x3a\x4f\x13\x4f\xc7\x8b\x94\x12\x91\x8e\x4f\x95\x2e\x93\x43\xa0\x63\x29\xd6\x57\x0e\xc6\x72\x6c\x8a\xdd\x74\x72\xd8\xe8\x8e\x65\x20\x67\xca\x99\x5c\xe6\xc5\xaa\x38\x79\x6b\x72\x0d\xaa\x8d\x83\xd6\x42\xcf\x97\xee\x46\xd6\x81\x9c\x6e\x01\x00\x00\xff\xff\x6c\x21\xbf\x7a\xbf\x00\x00\x00") func staticGoBytes() ([]byte, error) { return bindataRead( @@ -252,8 +293,8 @@ func staticGo() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static.go", size: 188, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x13, 0x54, 0x14, 0x7a, 0xa7, 0x6b, 0x18, 0xbb, 0xa6, 0x2e, 0x5c, 0x9f, 0x2b, 0xa5, 0xb0, 0x48, 0xfb, 0x61, 0xd7, 0x30, 0xe5, 0xdf, 0xaf, 0xcb, 0x94, 0x10, 0x79, 0xd3, 0x7b, 0xbd, 0x1f, 0xfe}} + info := bindataFileInfo{name: "static.go", size: 191, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -261,8 +302,8 @@ func staticGo() (*asset, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) @@ -272,12 +313,6 @@ func Asset(name string) ([]byte, error) { return nil, fmt.Errorf("Asset %s not found", name) } -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - // MustAsset is like Asset but panics when Asset would return an error. // It simplifies safe initialization of global variables. func MustAsset(name string) []byte { @@ -289,18 +324,12 @@ func MustAsset(name string) []byte { return a } -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - // AssetInfo loads and returns the asset info for the given name. // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) @@ -310,33 +339,6 @@ func AssetInfo(name string) (os.FileInfo, error) { return nil, fmt.Errorf("AssetInfo %s not found", name) } -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - // AssetNames returns the names of the assets. func AssetNames() []string { names := make([]string, 0, len(_bindata)) @@ -349,21 +351,15 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "1536754952_initial_schema.down.sql": _1536754952_initial_schemaDownSql, - "1536754952_initial_schema.up.sql": _1536754952_initial_schemaUpSql, - "1539249977_update_ratchet_info.down.sql": _1539249977_update_ratchet_infoDownSql, - "1539249977_update_ratchet_info.up.sql": _1539249977_update_ratchet_infoUpSql, - "1540715431_add_version.down.sql": _1540715431_add_versionDownSql, - "1540715431_add_version.up.sql": _1540715431_add_versionUpSql, - "1541164797_add_installations.down.sql": _1541164797_add_installationsDownSql, - "1541164797_add_installations.up.sql": _1541164797_add_installationsUpSql, - + "1558084410_add_topic.down.sql": _1558084410_add_topicDownSql, + "1558084410_add_topic.up.sql": _1558084410_add_topicUpSql, "static.go": staticGo, } @@ -376,15 +372,15 @@ var _bindata = map[string]func() (*asset, error){ // img/ // a.png // b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error // AssetDir("") will return []string{"data"}. func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { @@ -406,20 +402,21 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ - "1536754952_initial_schema.down.sql": &bintree{_1536754952_initial_schemaDownSql, map[string]*bintree{}}, - "1536754952_initial_schema.up.sql": &bintree{_1536754952_initial_schemaUpSql, map[string]*bintree{}}, + "1536754952_initial_schema.down.sql": &bintree{_1536754952_initial_schemaDownSql, map[string]*bintree{}}, + "1536754952_initial_schema.up.sql": &bintree{_1536754952_initial_schemaUpSql, map[string]*bintree{}}, "1539249977_update_ratchet_info.down.sql": &bintree{_1539249977_update_ratchet_infoDownSql, map[string]*bintree{}}, - "1539249977_update_ratchet_info.up.sql": &bintree{_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}}, - "1540715431_add_version.down.sql": &bintree{_1540715431_add_versionDownSql, map[string]*bintree{}}, - "1540715431_add_version.up.sql": &bintree{_1540715431_add_versionUpSql, map[string]*bintree{}}, - "1541164797_add_installations.down.sql": &bintree{_1541164797_add_installationsDownSql, map[string]*bintree{}}, - "1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}}, - "static.go": &bintree{staticGo, map[string]*bintree{}}, + "1539249977_update_ratchet_info.up.sql": &bintree{_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}}, + "1540715431_add_version.down.sql": &bintree{_1540715431_add_versionDownSql, map[string]*bintree{}}, + "1540715431_add_version.up.sql": &bintree{_1540715431_add_versionUpSql, map[string]*bintree{}}, + "1541164797_add_installations.down.sql": &bintree{_1541164797_add_installationsDownSql, map[string]*bintree{}}, + "1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}}, + "1558084410_add_topic.down.sql": &bintree{_1558084410_add_topicDownSql, map[string]*bintree{}}, + "1558084410_add_topic.up.sql": &bintree{_1558084410_add_topicUpSql, map[string]*bintree{}}, + "static.go": &bintree{staticGo, map[string]*bintree{}}, }} -// RestoreAsset restores an asset under the given directory. +// RestoreAsset restores an asset under the given directory func RestoreAsset(dir, name string) error { data, err := Asset(name) if err != nil { @@ -437,10 +434,14 @@ func RestoreAsset(dir, name string) error { if err != nil { return err } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil } -// RestoreAssets restores an asset under the given directory recursively. +// RestoreAssets restores an asset under the given directory recursively func RestoreAssets(dir, name string) error { children, err := AssetDir(name) // File @@ -458,6 +459,7 @@ func RestoreAssets(dir, name string) error { } func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/services/shhext/chat/encryption.pb.go b/services/shhext/chat/encryption.pb.go index adbe3684d..876c91295 100644 --- a/services/shhext/chat/encryption.pb.go +++ b/services/shhext/chat/encryption.pb.go @@ -18,7 +18,7 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type SignedPreKey struct { SignedPreKey []byte `protobuf:"bytes,1,opt,name=signed_pre_key,json=signedPreKey,proto3" json:"signed_pre_key,omitempty"` @@ -416,7 +416,9 @@ type ProtocolMessage struct { // One to one message, encrypted, indexed by installation_id DirectMessage map[string]*DirectMessageProtocol `protobuf:"bytes,101,rep,name=direct_message,json=directMessage,proto3" json:"direct_message,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Public chats, not encrypted - PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"` + PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"` + // Version of the protocol + Version uint32 `protobuf:"varint,103,opt,name=version,proto3" json:"version,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -482,6 +484,13 @@ func (m *ProtocolMessage) GetPublicMessage() []byte { return nil } +func (m *ProtocolMessage) GetVersion() uint32 { + if m != nil { + return m.Version + } + return 0 +} + func init() { proto.RegisterType((*SignedPreKey)(nil), "chat.SignedPreKey") proto.RegisterType((*Bundle)(nil), "chat.Bundle") @@ -498,40 +507,40 @@ func init() { func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) } var fileDescriptor_8293a649ce9418c6 = []byte{ - // 548 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x51, 0x8b, 0xd3, 0x40, - 0x10, 0x26, 0x49, 0xef, 0xae, 0x9d, 0xe6, 0xd2, 0xb2, 0xa2, 0x84, 0x7a, 0x60, 0x09, 0xa7, 0x06, - 0x84, 0xc2, 0xb5, 0x3e, 0x88, 0x8f, 0x5a, 0xb1, 0x9e, 0xa8, 0xc7, 0xea, 0x83, 0x2f, 0x12, 0xb6, - 0xdd, 0xf5, 0x6e, 0x31, 0xdd, 0x84, 0xdd, 0x6d, 0xa1, 0x7f, 0xce, 0xbf, 0xe2, 0x4f, 0x51, 0xb2, - 0x9b, 0xb4, 0xdb, 0xde, 0x1d, 0xf8, 0xd6, 0x99, 0xf9, 0xf6, 0x9b, 0x6f, 0xbe, 0xe9, 0x04, 0xfa, - 0x4c, 0x2c, 0xe4, 0xa6, 0xd4, 0xbc, 0x10, 0xa3, 0x52, 0x16, 0xba, 0x40, 0xad, 0xc5, 0x0d, 0xd1, - 0xc9, 0x67, 0x08, 0xbf, 0xf2, 0x6b, 0xc1, 0xe8, 0x95, 0x64, 0x1f, 0xd9, 0x06, 0x9d, 0x43, 0xa4, - 0x4c, 0x9c, 0x95, 0x92, 0x65, 0xbf, 0xd8, 0x26, 0xf6, 0x86, 0x5e, 0x1a, 0xe2, 0x50, 0xb9, 0xa8, - 0x18, 0x4e, 0xd6, 0x4c, 0x2a, 0x5e, 0x88, 0xd8, 0x1f, 0x7a, 0xe9, 0x29, 0x6e, 0xc2, 0xe4, 0xaf, - 0x07, 0xc7, 0x6f, 0x56, 0x82, 0xe6, 0x0c, 0x0d, 0xa0, 0xcd, 0x29, 0x13, 0x9a, 0xeb, 0x86, 0x64, - 0x1b, 0xa3, 0xf7, 0xd0, 0xdb, 0x6f, 0xa3, 0x62, 0x7f, 0x18, 0xa4, 0xdd, 0xf1, 0x93, 0x51, 0x25, - 0x6b, 0x64, 0x29, 0x46, 0xae, 0x34, 0xf5, 0x4e, 0x68, 0xb9, 0xc1, 0xa7, 0xae, 0x10, 0x85, 0xce, - 0xa0, 0x53, 0x25, 0x88, 0x5e, 0x49, 0x16, 0xb7, 0x4c, 0x97, 0x5d, 0xa2, 0xaa, 0x6a, 0xbe, 0x64, - 0x4a, 0x93, 0x65, 0x19, 0x1f, 0x0d, 0xbd, 0x34, 0xc0, 0xbb, 0xc4, 0xe0, 0x1b, 0xa0, 0xdb, 0x0d, - 0x50, 0x1f, 0x82, 0x66, 0xec, 0x0e, 0xae, 0x7e, 0xa2, 0x14, 0x8e, 0xd6, 0x24, 0x5f, 0x31, 0x33, - 0x6b, 0x77, 0x8c, 0xac, 0x44, 0xf7, 0x29, 0xb6, 0x80, 0xd7, 0xfe, 0x2b, 0x2f, 0x91, 0xd0, 0xb3, - 0xea, 0xdf, 0x16, 0x42, 0x13, 0x2e, 0x98, 0x44, 0xe7, 0x70, 0x3c, 0x37, 0x29, 0xc3, 0xda, 0x1d, - 0x87, 0xee, 0x90, 0xb8, 0xae, 0xa1, 0x09, 0x3c, 0x2a, 0x25, 0x5f, 0x13, 0xcd, 0xb2, 0x83, 0x15, - 0xf8, 0x66, 0xae, 0x07, 0x75, 0xd5, 0x6d, 0x7c, 0xd9, 0x6a, 0x07, 0xfd, 0x56, 0x72, 0x09, 0xed, - 0x29, 0x9e, 0x31, 0x42, 0x99, 0x74, 0xf5, 0x87, 0x56, 0x7f, 0x08, 0x5e, 0xb3, 0x27, 0x4f, 0xa0, - 0x08, 0xfc, 0x52, 0xc4, 0x81, 0x09, 0xfd, 0xd2, 0xc4, 0x9c, 0xd6, 0xd6, 0xf9, 0x9c, 0x26, 0x67, - 0xd0, 0x9e, 0xce, 0xee, 0xe3, 0x4a, 0x5e, 0x02, 0x7c, 0x9f, 0xdc, 0x5f, 0x3f, 0x64, 0xab, 0xf5, - 0xfd, 0xf6, 0xe0, 0xe1, 0x94, 0x4b, 0xb6, 0xd0, 0x9f, 0x98, 0x52, 0xe4, 0x9a, 0x5d, 0x55, 0x7f, - 0xc1, 0x45, 0x91, 0xa3, 0x0b, 0xe8, 0x56, 0x7c, 0xd9, 0x8d, 0x21, 0xac, 0xfd, 0xe9, 0x5b, 0x7f, - 0x76, 0x8d, 0xb0, 0xdb, 0xf4, 0x05, 0x74, 0xa6, 0xb8, 0x79, 0x60, 0x57, 0x12, 0xd9, 0x07, 0x8d, - 0x07, 0x78, 0xe7, 0x46, 0x05, 0xde, 0xb2, 0xb3, 0x3d, 0xf0, 0x6c, 0x0b, 0x6e, 0x98, 0x63, 0x38, - 0x29, 0xc9, 0x26, 0x2f, 0x08, 0x35, 0xfe, 0x84, 0xb8, 0x09, 0x93, 0x3f, 0x3e, 0xf4, 0x1a, 0xcd, - 0xf5, 0x08, 0xff, 0xb9, 0xd5, 0xe7, 0xd0, 0xe3, 0x42, 0x69, 0x92, 0xe7, 0xa4, 0x3a, 0xbe, 0x8c, - 0x53, 0xa3, 0xb9, 0x83, 0x23, 0x37, 0xfd, 0x81, 0xa2, 0x67, 0x70, 0x62, 0x9f, 0xa8, 0x38, 0x30, - 0xa7, 0xb0, 0xcf, 0xd7, 0x14, 0xd1, 0x17, 0x88, 0xa8, 0xb1, 0x32, 0x5b, 0x5a, 0x21, 0x31, 0x33, - 0xf0, 0xd4, 0xc2, 0x0f, 0x54, 0x8e, 0xf6, 0x6c, 0xaf, 0x4f, 0x88, 0xba, 0x39, 0xf4, 0x14, 0xa2, - 0x72, 0x35, 0xcf, 0xf9, 0x62, 0x4b, 0xf8, 0xd3, 0x0c, 0x7f, 0x6a, 0xb3, 0x35, 0x6c, 0xf0, 0x03, - 0xd0, 0x6d, 0xae, 0x3b, 0xae, 0xe5, 0x62, 0xff, 0x5a, 0x1e, 0xd7, 0x6e, 0xdf, 0xb5, 0x7d, 0xe7, - 0x6c, 0xe6, 0xc7, 0xe6, 0xab, 0x34, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0x54, 0xd8, 0xdf, 0x09, - 0xa9, 0x04, 0x00, 0x00, + // 553 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x5d, 0x8b, 0xd3, 0x40, + 0x14, 0x25, 0x49, 0x77, 0xb7, 0xbd, 0x4d, 0xd3, 0x32, 0xa2, 0x84, 0xba, 0x60, 0x09, 0xab, 0x06, + 0x84, 0xc2, 0xb6, 0x3e, 0x88, 0x8f, 0x5a, 0xb1, 0xae, 0xa8, 0xcb, 0xe8, 0x83, 0x2f, 0x12, 0xa6, + 0xcd, 0xd8, 0x1d, 0x4c, 0x27, 0x61, 0x32, 0x2d, 0xf4, 0xcf, 0xf9, 0xbf, 0x7c, 0x52, 0x32, 0x93, + 0x69, 0x27, 0xdd, 0x5d, 0xf0, 0xad, 0xf7, 0x63, 0xce, 0x3d, 0xf7, 0xdc, 0x9c, 0xc2, 0x80, 0xf2, + 0xa5, 0xd8, 0x15, 0x92, 0xe5, 0x7c, 0x5c, 0x88, 0x5c, 0xe6, 0xa8, 0xb5, 0xbc, 0x21, 0x32, 0xfa, + 0x0c, 0xfe, 0x57, 0xb6, 0xe2, 0x34, 0xbd, 0x16, 0xf4, 0x23, 0xdd, 0xa1, 0x0b, 0x08, 0x4a, 0x15, + 0x27, 0x85, 0xa0, 0xc9, 0x2f, 0xba, 0x0b, 0x9d, 0x91, 0x13, 0xfb, 0xd8, 0x2f, 0xed, 0xae, 0x10, + 0xce, 0xb6, 0x54, 0x94, 0x2c, 0xe7, 0xa1, 0x3b, 0x72, 0xe2, 0x1e, 0x36, 0x61, 0xf4, 0xd7, 0x81, + 0xd3, 0x37, 0x1b, 0x9e, 0x66, 0x14, 0x0d, 0xa1, 0xcd, 0x52, 0xca, 0x25, 0x93, 0x06, 0x64, 0x1f, + 0xa3, 0xf7, 0xd0, 0x6f, 0x8e, 0x29, 0x43, 0x77, 0xe4, 0xc5, 0xdd, 0xc9, 0x93, 0x71, 0x45, 0x6b, + 0xac, 0x21, 0xc6, 0x36, 0xb5, 0xf2, 0x1d, 0x97, 0x62, 0x87, 0x7b, 0x36, 0x91, 0x12, 0x9d, 0x43, + 0xa7, 0x4a, 0x10, 0xb9, 0x11, 0x34, 0x6c, 0xa9, 0x29, 0x87, 0x44, 0x55, 0x95, 0x6c, 0x4d, 0x4b, + 0x49, 0xd6, 0x45, 0x78, 0x32, 0x72, 0x62, 0x0f, 0x1f, 0x12, 0xc3, 0x6f, 0x80, 0x6e, 0x0f, 0x40, + 0x03, 0xf0, 0xcc, 0xda, 0x1d, 0x5c, 0xfd, 0x44, 0x31, 0x9c, 0x6c, 0x49, 0xb6, 0xa1, 0x6a, 0xd7, + 0xee, 0x04, 0x69, 0x8a, 0xf6, 0x53, 0xac, 0x1b, 0x5e, 0xbb, 0xaf, 0x9c, 0x48, 0x40, 0x5f, 0xb3, + 0x7f, 0x9b, 0x73, 0x49, 0x18, 0xa7, 0x02, 0x5d, 0xc0, 0xe9, 0x42, 0xa5, 0x14, 0x6a, 0x77, 0xe2, + 0xdb, 0x4b, 0xe2, 0xba, 0x86, 0xa6, 0xf0, 0xa8, 0x10, 0x6c, 0x4b, 0x24, 0x4d, 0x8e, 0x4e, 0xe0, + 0xaa, 0xbd, 0x1e, 0xd4, 0x55, 0x7b, 0xf0, 0x55, 0xab, 0xed, 0x0d, 0x5a, 0xd1, 0x15, 0xb4, 0x67, + 0x78, 0x4e, 0x49, 0x4a, 0x85, 0xcd, 0xdf, 0xd7, 0xfc, 0x7d, 0x70, 0xcc, 0x9d, 0x1c, 0x8e, 0x02, + 0x70, 0x0b, 0x1e, 0x7a, 0x2a, 0x74, 0x0b, 0x15, 0xb3, 0xb4, 0x96, 0xce, 0x65, 0x69, 0x74, 0x0e, + 0xed, 0xd9, 0xfc, 0x3e, 0xac, 0xe8, 0x25, 0xc0, 0xf7, 0xe9, 0xfd, 0xf5, 0x63, 0xb4, 0x9a, 0xdf, + 0x6f, 0x07, 0x1e, 0xce, 0x98, 0xa0, 0x4b, 0xf9, 0x89, 0x96, 0x25, 0x59, 0xd1, 0xeb, 0xea, 0x13, + 0x5c, 0xe6, 0x19, 0xba, 0x84, 0x6e, 0x85, 0x97, 0xdc, 0x28, 0xc0, 0x5a, 0x9f, 0x81, 0xd6, 0xe7, + 0x30, 0x08, 0xdb, 0x43, 0x5f, 0x40, 0x67, 0x86, 0xcd, 0x03, 0x7d, 0x92, 0x40, 0x3f, 0x30, 0x1a, + 0xe0, 0x83, 0x1a, 0x55, 0xf3, 0x1e, 0x9d, 0x36, 0x9a, 0xe7, 0xfb, 0x66, 0x83, 0x1c, 0xc2, 0x59, + 0x41, 0x76, 0x59, 0x4e, 0x52, 0xa5, 0x8f, 0x8f, 0x4d, 0x18, 0xfd, 0x71, 0xa1, 0x6f, 0x38, 0xd7, + 0x2b, 0xfc, 0xe7, 0x55, 0x9f, 0x43, 0x9f, 0xf1, 0x52, 0x92, 0x2c, 0x23, 0x95, 0xf9, 0x12, 0x96, + 0x2a, 0xce, 0x1d, 0x1c, 0xd8, 0xe9, 0x0f, 0x29, 0x7a, 0x06, 0x67, 0xfa, 0x49, 0x19, 0x7a, 0xca, + 0x0a, 0x4d, 0x3c, 0x53, 0x44, 0x5f, 0x20, 0x48, 0x95, 0x94, 0xc9, 0x5a, 0x13, 0x09, 0xa9, 0x6a, + 0x8f, 0x75, 0xfb, 0x11, 0xcb, 0x71, 0x43, 0xf6, 0xda, 0x42, 0xa9, 0x9d, 0x43, 0x4f, 0x21, 0x28, + 0x36, 0x8b, 0x8c, 0x2d, 0xf7, 0x80, 0x3f, 0xd5, 0xf2, 0x3d, 0x9d, 0x35, 0x6d, 0x96, 0xe7, 0x57, + 0x0d, 0xcf, 0x0f, 0x7f, 0x00, 0xba, 0x3d, 0xe5, 0x0e, 0x1f, 0x5d, 0x36, 0x7d, 0xf4, 0xb8, 0xbe, + 0xc3, 0x5d, 0xdf, 0x85, 0x65, 0xa8, 0xc5, 0xa9, 0xfa, 0xbf, 0x9a, 0xfe, 0x0b, 0x00, 0x00, 0xff, + 0xff, 0x53, 0xcb, 0xc9, 0xb7, 0xc3, 0x04, 0x00, 0x00, } diff --git a/services/shhext/chat/encryption.proto b/services/shhext/chat/encryption.proto index 923aa662c..5ac619119 100644 --- a/services/shhext/chat/encryption.proto +++ b/services/shhext/chat/encryption.proto @@ -78,4 +78,6 @@ message ProtocolMessage { // Public chats, not encrypted bytes public_message = 102; + // Version of the protocol + uint32 version = 103; } diff --git a/services/shhext/chat/protocol.go b/services/shhext/chat/protocol.go index 45a2f21a9..1a763a8cb 100644 --- a/services/shhext/chat/protocol.go +++ b/services/shhext/chat/protocol.go @@ -5,28 +5,35 @@ import ( "errors" "github.com/ethereum/go-ethereum/log" - "github.com/golang/protobuf/proto" + "github.com/status-im/status-go/services/shhext/chat/topic" ) +const protocolCurrentVersion = 1 +const topicNegotiationVersion = 1 + type ProtocolService struct { log log.Logger encryption *EncryptionService + topic *topic.Service addedBundlesHandler func([]IdentityAndIDPair) + onNewTopicHandler func([]*topic.Secret) Enabled bool } var ErrNotProtocolMessage = errors.New("Not a protocol message") // NewProtocolService creates a new ProtocolService instance -func NewProtocolService(encryption *EncryptionService, addedBundlesHandler func([]IdentityAndIDPair)) *ProtocolService { +func NewProtocolService(encryption *EncryptionService, topic *topic.Service, addedBundlesHandler func([]IdentityAndIDPair), onNewTopicHandler func([]*topic.Secret)) *ProtocolService { return &ProtocolService{ log: log.New("package", "status-go/services/sshext.chat"), encryption: encryption, + topic: topic, addedBundlesHandler: addedBundlesHandler, + onNewTopicHandler: onNewTopicHandler, } } -func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage, sendSingle bool) ([]byte, error) { +func (p *ProtocolService) addBundle(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage, sendSingle bool) (*ProtocolMessage, error) { // Get a bundle bundle, err := p.encryption.CreateBundle(myIdentityKey) if err != nil { @@ -42,61 +49,93 @@ func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, m msg.Bundles = []*Bundle{bundle} } - // marshal for sending to wire - marshaledMessage, err := proto.Marshal(msg) - if err != nil { - p.log.Error("encryption-service", "error marshaling message", err) - return nil, err - } - - return marshaledMessage, nil + return msg, nil } // BuildPublicMessage marshals a public chat message given the user identity private key and a payload -func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) { +func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) (*ProtocolMessage, error) { // Build message not encrypted protocolMessage := &ProtocolMessage{ InstallationId: p.encryption.config.InstallationID, PublicMessage: payload, + Version: protocolCurrentVersion, } - return p.addBundleAndMarshal(myIdentityKey, protocolMessage, false) + return p.addBundle(myIdentityKey, protocolMessage, false) } -// BuildDirectMessage marshals a 1:1 chat message given the user identity private key, the recipient's public key, and a payload -func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) { +// BuildDirectMessage returns a 1:1 chat message and optionally a negotiated topic given the user identity private key, the recipient's public key, and a payload +func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) (*ProtocolMessage, []byte, error) { // Encrypt payload encryptionResponse, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, payload) if err != nil { p.log.Error("encryption-service", "error encrypting payload", err) - return nil, err + return nil, nil, err } // Build message protocolMessage := &ProtocolMessage{ InstallationId: p.encryption.config.InstallationID, DirectMessage: encryptionResponse, + Version: protocolCurrentVersion, } - return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true) + msg, err := p.addBundle(myIdentityKey, protocolMessage, true) + if err != nil { + return nil, nil, err + } + + // Check who we are sending the message to, and see if we have a shared secret + // across devices + var installationIDs []string + var sharedSecret *topic.Secret + var agreed bool + for installationID := range protocolMessage.GetDirectMessage() { + if installationID != noInstallationID { + installationIDs = append(installationIDs, installationID) + } + } + if len(installationIDs) != 0 { + sharedSecret, agreed, err = p.topic.Send(myIdentityKey, p.encryption.config.InstallationID, publicKey, installationIDs) + if err != nil { + return nil, nil, err + } + } + + // Call handler + if sharedSecret != nil { + p.onNewTopicHandler([]*topic.Secret{sharedSecret}) + } + + if agreed { + return msg, sharedSecret.Key, nil + } else { + return msg, nil, nil + } } // BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device. -func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) ([]byte, error) { +func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) (*ProtocolMessage, []byte, error) { // Encrypt payload encryptionResponse, err := p.encryption.EncryptPayloadWithDH(destination, payload) if err != nil { p.log.Error("encryption-service", "error encrypting payload", err) - return nil, err + return nil, nil, err } // Build message protocolMessage := &ProtocolMessage{ InstallationId: p.encryption.config.InstallationID, DirectMessage: encryptionResponse, + Version: protocolCurrentVersion, } - return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true) + msg, err := p.addBundle(myIdentityKey, protocolMessage, true) + if err != nil { + return nil, nil, err + } + + return msg, nil, nil } // ProcessPublicBundle processes a received X3DH bundle. @@ -130,18 +169,11 @@ func (p *ProtocolService) ConfirmMessagesProcessed(messageIDs [][]byte) error { } // HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message. -func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte, messageID []byte) ([]byte, error) { +func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, protocolMessage *ProtocolMessage, messageID []byte) ([]byte, error) { if p.encryption == nil { return nil, errors.New("encryption service not initialized") } - // Unmarshal message - protocolMessage := &ProtocolMessage{} - - if err := proto.Unmarshal(payload, protocolMessage); err != nil { - return nil, ErrNotProtocolMessage - } - // Process bundle, deprecated, here for backward compatibility if bundle := protocolMessage.GetBundle(); bundle != nil { // Should we stop processing if the bundle cannot be verified? @@ -177,6 +209,18 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu return nil, err } + p.log.Info("Checking version") + // Handle protocol negotiation for compatible clients + if protocolMessage.Version >= topicNegotiationVersion { + p.log.Info("Version greater than 1 negotianting") + sharedSecret, err := p.topic.Receive(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId()) + if err != nil { + return nil, err + } + + p.onNewTopicHandler([]*topic.Secret{sharedSecret}) + + } return message, nil } diff --git a/services/shhext/chat/protocol_test.go b/services/shhext/chat/protocol_test.go index 0b245ec44..b17becff6 100644 --- a/services/shhext/chat/protocol_test.go +++ b/services/shhext/chat/protocol_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" - "github.com/golang/protobuf/proto" + "github.com/status-im/status-go/services/shhext/chat/topic" "github.com/stretchr/testify/suite" ) @@ -39,31 +39,36 @@ func (s *ProtocolServiceTestSuite) SetupTest() { } addedBundlesHandler := func(addedBundles []IdentityAndIDPair) {} + onNewTopicHandler := func(topic [][]byte) {} + + s.alice = NewProtocolService( + NewEncryptionService(alicePersistence, DefaultEncryptionServiceConfig("1")), + topic.NewService(alicePersistence.GetTopicStorage()), + addedBundlesHandler, + onNewTopicHandler, + ) + + s.bob = NewProtocolService( + NewEncryptionService(bobPersistence, DefaultEncryptionServiceConfig("2")), + topic.NewService(bobPersistence.GetTopicStorage()), + addedBundlesHandler, + onNewTopicHandler, + ) - s.alice = NewProtocolService(NewEncryptionService(alicePersistence, DefaultEncryptionServiceConfig("1")), addedBundlesHandler) - s.bob = NewProtocolService(NewEncryptionService(bobPersistence, DefaultEncryptionServiceConfig("2")), addedBundlesHandler) } func (s *ProtocolServiceTestSuite) TestBuildPublicMessage() { aliceKey, err := crypto.GenerateKey() s.NoError(err) - payload, err := proto.Marshal(&ChatMessagePayload{ - Content: "Test content", - ClockValue: 1, - ContentType: "a", - MessageType: "some type", - }) + payload := []byte("test") s.NoError(err) - marshaledMsg, err := s.alice.BuildPublicMessage(aliceKey, payload) + msg, err := s.alice.BuildPublicMessage(aliceKey, payload) s.NoError(err) - s.NotNil(marshaledMsg, "It creates a message") + s.NotNil(msg, "It creates a message") - unmarshaledMsg := &ProtocolMessage{} - err = proto.Unmarshal(marshaledMsg, unmarshaledMsg) - s.NoError(err) - s.NotNilf(unmarshaledMsg.GetBundles(), "It adds a bundle to the message") + s.NotNilf(msg.GetBundles(), "It adds a bundle to the message") } func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() { @@ -72,24 +77,15 @@ func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() { aliceKey, err := crypto.GenerateKey() s.NoError(err) - payload, err := proto.Marshal(&ChatMessagePayload{ - Content: "Test content", - ClockValue: 1, - ContentType: "a", - MessageType: "some type", - }) - s.NoError(err) + payload := []byte("test") - marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload) + msg, _, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload) s.NoError(err) - s.NotNil(marshaledMsg, "It creates a message") + s.NotNil(msg, "It creates a message") - unmarshaledMsg := &ProtocolMessage{} - err = proto.Unmarshal(marshaledMsg, unmarshaledMsg) - s.NoError(err) - s.NotNilf(unmarshaledMsg.GetBundle(), "It adds a bundle to the message") + s.NotNilf(msg.GetBundle(), "It adds a bundle to the message") - directMessage := unmarshaledMsg.GetDirectMessage() + directMessage := msg.GetDirectMessage() s.NotNilf(directMessage, "It sets the direct message") encryptedPayload := directMessage["none"].GetPayload() @@ -104,18 +100,10 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() { aliceKey, err := crypto.GenerateKey() s.NoError(err) - payload := ChatMessagePayload{ - Content: "Test content", - ClockValue: 1, - ContentType: "a", - MessageType: "some type", - } - - marshaledPayload, err := proto.Marshal(&payload) - s.NoError(err) + payload := []byte("test") // Message is sent with DH - marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, marshaledPayload) + marshaledMsg, _, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload) s.NoError(err) @@ -125,9 +113,6 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() { s.NotNil(unmarshaledMsg) - recoveredPayload := ChatMessagePayload{} - err = proto.Unmarshal(unmarshaledMsg, &recoveredPayload) - - s.NoError(err) - s.Equalf(proto.Equal(&payload, &recoveredPayload), true, "It successfully unmarshal the decrypted message") + recoveredPayload := []byte("test") + s.Equalf(payload, recoveredPayload, "It successfully unmarshal the decrypted message") } diff --git a/services/shhext/chat/sql_lite_persistence.go b/services/shhext/chat/sql_lite_persistence.go index 786492139..9659efa6f 100644 --- a/services/shhext/chat/sql_lite_persistence.go +++ b/services/shhext/chat/sql_lite_persistence.go @@ -3,42 +3,28 @@ package chat import ( "crypto/ecdsa" "database/sql" - "fmt" - "os" "strings" "github.com/ethereum/go-ethereum/crypto" - sqlite "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation dr "github.com/status-im/doubleratchet" "github.com/status-im/migrate/v4" "github.com/status-im/migrate/v4/database/sqlcipher" "github.com/status-im/migrate/v4/source/go_bindata" ecrypto "github.com/status-im/status-go/services/shhext/chat/crypto" - "github.com/status-im/status-go/services/shhext/chat/migrations" + appDB "github.com/status-im/status-go/services/shhext/chat/db" + "github.com/status-im/status-go/services/shhext/chat/topic" ) // A safe max number of rows const maxNumberOfRows = 100000000 -// The default number of kdf iterations in sqlcipher (from version 3.0.0) -// https://github.com/sqlcipher/sqlcipher/blob/fda4c68bb474da7e955be07a2b807bda1bb19bd2/CHANGELOG.md#300---2013-11-05 -// https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter -const defaultKdfIterationsNumber = 64000 - -// The reduced number of kdf iterations (for performance reasons) which is -// currently used for derivation of the database key -// https://github.com/status-im/status-go/pull/1343 -// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA -const kdfIterationsNumber = 3200 - -const exportDB = "SELECT sqlcipher_export('newdb')" - // SQLLitePersistence represents a persistence service tied to an SQLite database type SQLLitePersistence struct { db *sql.DB keysStorage dr.KeysStorage sessionStorage dr.SessionStorage + topicStorage topic.PersistenceService } // SQLLiteKeysStorage represents a keys persistence service tied to an SQLite database @@ -63,187 +49,11 @@ func NewSQLLitePersistence(path string, key string) (*SQLLitePersistence, error) s.sessionStorage = NewSQLLiteSessionStorage(s.db) + s.topicStorage = topic.NewSQLLitePersistence(s.db) + return s, nil } -func MigrateDBFile(oldPath string, newPath string, oldKey string, newKey string) error { - _, err := os.Stat(oldPath) - - // No files, nothing to do - if os.IsNotExist(err) { - return nil - } - - // Any other error, throws - if err != nil { - return err - } - - if err := os.Rename(oldPath, newPath); err != nil { - return err - } - - db, err := openDB(newPath, oldKey, defaultKdfIterationsNumber) - if err != nil { - return err - } - - keyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey) - - if _, err = db.Exec(keyString); err != nil { - return err - } - - return nil - -} - -// MigrateDBKeyKdfIterations changes the number of kdf iterations executed -// during the database key derivation. This change is necessary because -// of performance reasons. -// https://github.com/status-im/status-go/pull/1343 -// `sqlcipher_export` is used for migration, check out this link for details: -// https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export -func MigrateDBKeyKdfIterations(oldPath string, newPath string, key string) error { - _, err := os.Stat(oldPath) - - // No files, nothing to do - if os.IsNotExist(err) { - return nil - } - - // Any other error, throws - if err != nil { - return err - } - - isEncrypted, err := sqlite.IsEncrypted(oldPath) - if err != nil { - return err - } - - // Nothing to do, move db to the next migration - if !isEncrypted { - return os.Rename(oldPath, newPath) - } - - db, err := openDB(oldPath, key, defaultKdfIterationsNumber) - if err != nil { - return err - } - - attach := fmt.Sprintf( - "ATTACH DATABASE '%s' AS newdb KEY '%s'", - newPath, - key) - - if _, err = db.Exec(attach); err != nil { - return err - } - - changeKdfIter := fmt.Sprintf( - "PRAGMA newdb.kdf_iter = %d", - kdfIterationsNumber) - - if _, err = db.Exec(changeKdfIter); err != nil { - return err - } - - if _, err = db.Exec(exportDB); err != nil { - return err - } - - if err = db.Close(); err != nil { - return err - } - - return os.Remove(oldPath) -} - -// EncryptDatabase encrypts an unencrypted database with key -func EncryptDatabase(oldPath string, newPath string, key string) error { - _, err := os.Stat(oldPath) - - // No files, nothing to do - if os.IsNotExist(err) { - return nil - } - - // Any other error, throws - if err != nil { - return err - } - - isEncrypted, err := sqlite.IsEncrypted(oldPath) - if err != nil { - return err - } - - // Nothing to do, already encrypted - if isEncrypted { - return os.Rename(oldPath, newPath) - } - - db, err := openDB(oldPath, "", defaultKdfIterationsNumber) - if err != nil { - return err - } - - attach := fmt.Sprintf( - "ATTACH DATABASE '%s' AS newdb KEY '%s'", - newPath, - key) - - if _, err = db.Exec(attach); err != nil { - return err - } - - changeKdfIter := fmt.Sprintf( - "PRAGMA newdb.kdf_iter = %d", - kdfIterationsNumber) - - if _, err = db.Exec(changeKdfIter); err != nil { - return err - } - - if _, err = db.Exec(exportDB); err != nil { - return err - } - - if err = db.Close(); err != nil { - return err - } - - return os.Remove(oldPath) -} - -func openDB(path string, key string, kdfIter int) (*sql.DB, error) { - db, err := sql.Open("sqlite3", path) - if err != nil { - return nil, err - } - - keyString := fmt.Sprintf("PRAGMA key = '%s'", key) - - // Disable concurrent access as not supported by the driver - db.SetMaxOpenConns(1) - - if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil { - return nil, err - } - - if _, err = db.Exec(keyString); err != nil { - return nil, err - } - - kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter) - - if _, err = db.Exec(kdfString); err != nil { - return nil, err - } - return db, nil -} - // NewSQLLiteKeysStorage creates a new SQLLiteKeysStorage instance associated with the specified database func NewSQLLiteKeysStorage(db *sql.DB) *SQLLiteKeysStorage { return &SQLLiteKeysStorage{ @@ -268,16 +78,21 @@ func (s *SQLLitePersistence) GetSessionStorage() dr.SessionStorage { return s.sessionStorage } +// GetTopicStorage returns the associated topicStorageObject +func (s *SQLLitePersistence) GetTopicStorage() topic.PersistenceService { + return s.topicStorage +} + // Open opens a file at the specified path func (s *SQLLitePersistence) Open(path string, key string) error { - db, err := openDB(path, key, kdfIterationsNumber) + db, err := appDB.Open(path, key, appDB.KdfIterationsNumber) if err != nil { return err } s.db = db - return s.setup() + return nil } // AddPrivateBundle adds the specified BundleContainer to the database @@ -1069,37 +884,3 @@ func toKey(a []byte) dr.Key { copy(k[:], a) return k } - -func (s *SQLLitePersistence) setup() error { - resources := bindata.Resource( - migrations.AssetNames(), - func(name string) ([]byte, error) { - return migrations.Asset(name) - }, - ) - - source, err := bindata.WithInstance(resources) - if err != nil { - return err - } - - driver, err := sqlcipher.WithInstance(s.db, &sqlcipher.Config{}) - if err != nil { - return err - } - - m, err := migrate.NewWithInstance( - "go-bindata", - source, - "sqlcipher", - driver) - if err != nil { - return err - } - - if err = m.Up(); err != migrate.ErrNoChange { - return err - } - - return nil -} diff --git a/services/shhext/chat/test.db b/services/shhext/chat/test.db deleted file mode 100644 index 29a2b84c2..000000000 Binary files a/services/shhext/chat/test.db and /dev/null differ diff --git a/services/shhext/chat/topic/persistence.go b/services/shhext/chat/topic/persistence.go new file mode 100644 index 000000000..2d95ce199 --- /dev/null +++ b/services/shhext/chat/topic/persistence.go @@ -0,0 +1,125 @@ +package topic + +import ( + "database/sql" + "strings" +) + +type PersistenceService interface { + Add(identity []byte, secret []byte, installationID string) error + Get(identity []byte, installationIDs []string) (*Response, error) + All() ([][][]byte, error) +} + +type Response struct { + secret []byte + installationIDs map[string]bool +} + +type SQLLitePersistence struct { + db *sql.DB +} + +func NewSQLLitePersistence(db *sql.DB) *SQLLitePersistence { + return &SQLLitePersistence{db: db} +} + +func (s *SQLLitePersistence) Add(identity []byte, secret []byte, installationID string) error { + tx, err := s.db.Begin() + if err != nil { + return err + } + + insertTopicStmt, err := tx.Prepare("INSERT INTO topics(identity, secret) VALUES (?, ?)") + if err != nil { + _ = tx.Rollback() + return err + } + defer insertTopicStmt.Close() + + _, err = insertTopicStmt.Exec(identity, secret) + if err != nil { + _ = tx.Rollback() + return err + } + + insertInstallationIDStmt, err := tx.Prepare("INSERT INTO topic_installation_ids(id, identity_id) VALUES (?, ?)") + if err != nil { + _ = tx.Rollback() + return err + } + defer insertInstallationIDStmt.Close() + + _, err = insertInstallationIDStmt.Exec(installationID, identity) + if err != nil { + _ = tx.Rollback() + return err + } + return tx.Commit() +} + +func (s *SQLLitePersistence) Get(identity []byte, installationIDs []string) (*Response, error) { + response := &Response{ + installationIDs: make(map[string]bool), + } + args := make([]interface{}, len(installationIDs)+1) + args[0] = identity + for i, installationID := range installationIDs { + args[i+1] = installationID + } + + /* #nosec */ + query := `SELECT secret, id + FROM topics t + JOIN + topic_installation_ids tid + ON t.identity = tid.identity_id + WHERE + t.identity = ? + AND + tid.id IN (?` + strings.Repeat(",?", len(installationIDs)-1) + `)` + + rows, err := s.db.Query(query, args...) + if err != nil && err != sql.ErrNoRows { + return nil, err + } + + for rows.Next() { + var installationID string + var secret []byte + err = rows.Scan(&secret, &installationID) + if err != nil { + return nil, err + } + + response.secret = secret + response.installationIDs[installationID] = true + } + + return response, nil +} + +func (s *SQLLitePersistence) All() ([][][]byte, error) { + query := `SELECT identity, secret + FROM topics` + + var secrets [][][]byte + + rows, err := s.db.Query(query) + if err != nil { + return nil, err + } + + for rows.Next() { + var secret []byte + var identity []byte + err = rows.Scan(&identity, &secret) + if err != nil { + return nil, err + } + + secrets = append(secrets, [][]byte{identity, secret}) + } + + return secrets, nil +} diff --git a/services/shhext/chat/topic/service.go b/services/shhext/chat/topic/service.go new file mode 100644 index 000000000..d93e28f37 --- /dev/null +++ b/services/shhext/chat/topic/service.go @@ -0,0 +1,93 @@ +package topic + +import ( + "crypto/ecdsa" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/ecies" + "github.com/ethereum/go-ethereum/log" +) + +const sskLen = 16 + +type Service struct { + persistence PersistenceService +} + +func NewService(persistence PersistenceService) *Service { + return &Service{persistence: persistence} +} + +func (s *Service) setupTopic(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) { + log.Info("Setup topic called for", "installationID", installationID) + sharedKey, err := ecies.ImportECDSA(myPrivateKey).GenerateShared( + ecies.ImportECDSAPublic(theirPublicKey), + sskLen, + sskLen, + ) + if err != nil { + return nil, err + } + + theirIdentity := crypto.CompressPubkey(theirPublicKey) + if err = s.persistence.Add(theirIdentity, sharedKey, installationID); err != nil { + return nil, err + } + + return &Secret{Key: sharedKey, Identity: theirPublicKey}, err +} + +// Receive will generate a shared secret for a given identity, and return it +func (s *Service) Receive(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) { + return s.setupTopic(myPrivateKey, theirPublicKey, installationID) +} + +// Send returns a shared key and whether it has been acknowledged from all the installationIDs +func (s *Service) Send(myPrivateKey *ecdsa.PrivateKey, myInstallationID string, theirPublicKey *ecdsa.PublicKey, theirInstallationIDs []string) (*Secret, bool, error) { + sharedKey, err := s.setupTopic(myPrivateKey, theirPublicKey, myInstallationID) + if err != nil { + return nil, false, err + } + + theirIdentity := crypto.CompressPubkey(theirPublicKey) + response, err := s.persistence.Get(theirIdentity, theirInstallationIDs) + if err != nil { + return nil, false, err + } + + for _, installationID := range theirInstallationIDs { + if !response.installationIDs[installationID] { + return sharedKey, false, nil + } + } + + return &Secret{ + Key: response.secret, + Identity: theirPublicKey, + }, true, nil +} + +type Secret struct { + Identity *ecdsa.PublicKey + Key []byte +} + +func (s *Service) All() ([]*Secret, error) { + var secrets []*Secret + tuples, err := s.persistence.All() + if err != nil { + return nil, err + } + + for _, tuple := range tuples { + key, err := crypto.DecompressPubkey(tuple[0]) + if err != nil { + return nil, err + } + + secrets = append(secrets, &Secret{Identity: key, Key: tuple[1]}) + + } + + return secrets, nil + +} diff --git a/services/shhext/chat/topic/service_test.go b/services/shhext/chat/topic/service_test.go new file mode 100644 index 000000000..35458ac09 --- /dev/null +++ b/services/shhext/chat/topic/service_test.go @@ -0,0 +1,114 @@ +package topic + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + appDB "github.com/status-im/status-go/services/shhext/chat/db" + "github.com/stretchr/testify/suite" +) + +func TestServiceTestSuite(t *testing.T) { + suite.Run(t, new(ServiceTestSuite)) +} + +type ServiceTestSuite struct { + suite.Suite + service *Service + path string +} + +func (s *ServiceTestSuite) SetupTest() { + dbFile, err := ioutil.TempFile(os.TempDir(), "topic") + s.Require().NoError(err) + s.path = dbFile.Name() + + db, err := appDB.Open(s.path, "", 0) + + s.Require().NoError(err) + + s.service = NewService(NewSQLLitePersistence(db)) +} + +func (s *ServiceTestSuite) TearDownTest() { + os.Remove(s.path) +} + +func (s *ServiceTestSuite) TestSingleInstallationID() { + ourInstallationID := "our" + installationID1 := "1" + installationID2 := "2" + + myKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + theirKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + // We receive a message from installationID1 + sharedKey1, err := s.service.Receive(myKey, &theirKey.PublicKey, installationID1) + s.Require().NoError(err) + s.Require().NotNil(sharedKey1, "it generates a shared key") + + // We want to send a message to installationID1 + sharedKey2, agreed2, err := s.service.Send(myKey, ourInstallationID, &theirKey.PublicKey, []string{installationID1}) + s.Require().NoError(err) + s.Require().True(agreed2) + s.Require().NotNil(sharedKey2, "We can retrieve a shared secret") + s.Require().Equal(sharedKey1, sharedKey2, "The shared secret is the same as the one stored") + + // We want to send a message to multiple installationIDs, one of which we haven't never communicated with + sharedKey3, agreed3, err := s.service.Send(myKey, ourInstallationID, &theirKey.PublicKey, []string{installationID1, installationID2}) + s.Require().NoError(err) + s.Require().NotNil(sharedKey3, "A shared key is returned") + s.Require().False(agreed3) + + // We receive a message from installationID2 + sharedKey4, err := s.service.Receive(myKey, &theirKey.PublicKey, installationID2) + s.Require().NoError(err) + s.Require().NotNil(sharedKey4, "it generates a shared key") + s.Require().Equal(sharedKey1, sharedKey4, "It generates the same key") + + // We want to send a message to installationID 1 & 2, both have been + sharedKey5, agreed5, err := s.service.Send(myKey, ourInstallationID, &theirKey.PublicKey, []string{installationID1, installationID2}) + s.Require().NoError(err) + s.Require().NotNil(sharedKey5, "We can retrieve a shared secret") + s.Require().True(agreed5) + s.Require().Equal(sharedKey1, sharedKey5, "The shared secret is the same as the one stored") + +} + +func (s *ServiceTestSuite) TestAll() { + installationID1 := "1" + installationID2 := "2" + + myKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + theirKey1, err := crypto.GenerateKey() + s.Require().NoError(err) + + theirKey2, err := crypto.GenerateKey() + s.Require().NoError(err) + + // We receive a message from user 1 + sharedKey1, err := s.service.Receive(myKey, &theirKey1.PublicKey, installationID1) + s.Require().NoError(err) + s.Require().NotNil(sharedKey1, "it generates a shared key") + + // We receive a message from user 2 + sharedKey2, err := s.service.Receive(myKey, &theirKey2.PublicKey, installationID2) + s.Require().NoError(err) + s.Require().NotNil(sharedKey2, "it generates a shared key") + + // All the topics are there + topics, err := s.service.All() + s.Require().NoError(err) + expected := []*Secret{ + sharedKey1, + sharedKey2, + } + s.Require().Equal(expected, topics) +} diff --git a/services/shhext/chat/whisper.go b/services/shhext/chat/whisper.go index 1f6118c0f..d726835bd 100644 --- a/services/shhext/chat/whisper.go +++ b/services/shhext/chat/whisper.go @@ -8,10 +8,16 @@ import ( var discoveryTopic = "contact-discovery" var discoveryTopicBytes = toTopic(discoveryTopic) +var topicSalt = []byte{0x01, 0x02, 0x03, 0x04} + func toTopic(s string) whisper.TopicType { return whisper.BytesToTopic(crypto.Keccak256([]byte(s))) } +func SharedSecretToTopic(secret []byte) whisper.TopicType { + return whisper.BytesToTopic(crypto.Keccak256(append(secret, topicSalt...))) +} + func defaultWhisperMessage() whisper.NewMessage { msg := whisper.NewMessage{} @@ -33,22 +39,26 @@ func PublicMessageToWhisper(rpcMsg SendPublicMessageRPC, payload []byte) whisper return msg } -func DirectMessageToWhisper(rpcMsg SendDirectMessageRPC, payload []byte) whisper.NewMessage { +func DirectMessageToWhisper(rpcMsg SendDirectMessageRPC, payload []byte, sharedSecret []byte) whisper.NewMessage { var topicBytes whisper.TopicType + msg := defaultWhisperMessage() if rpcMsg.Chat == "" { - topicBytes = discoveryTopicBytes + if sharedSecret != nil { + topicBytes = SharedSecretToTopic(sharedSecret) + } else { + topicBytes = discoveryTopicBytes + msg.PublicKey = rpcMsg.PubKey + } } else { topicBytes = toTopic(rpcMsg.Chat) + msg.PublicKey = rpcMsg.PubKey } - msg := defaultWhisperMessage() - msg.Topic = topicBytes msg.Payload = payload msg.Sig = rpcMsg.Sig - msg.PublicKey = rpcMsg.PubKey return msg } diff --git a/services/shhext/chat/whisper_test.go b/services/shhext/chat/whisper_test.go index 3c4643e1b..352c90e6f 100644 --- a/services/shhext/chat/whisper_test.go +++ b/services/shhext/chat/whisper_test.go @@ -30,10 +30,27 @@ func TestDirectMessageToWhisper(t *testing.T) { } payload := []byte("test") - whisperMessage := DirectMessageToWhisper(rpcMessage, payload) + whisperMessage := DirectMessageToWhisper(rpcMessage, payload, nil) assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL") assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target") assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time") assert.Equalf(t, whisper.TopicType{0xf8, 0x94, 0x6a, 0xac}, whisperMessage.Topic, "It sets the discovery topic") } + +func TestDirectMessageToWhisperWithSharedSecret(t *testing.T) { + rpcMessage := SendDirectMessageRPC{ + PubKey: []byte("some pubkey"), + Sig: "test", + } + + payload := []byte("test") + secret := []byte("test-secret") + + whisperMessage := DirectMessageToWhisper(rpcMessage, payload, secret) + + assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL") + assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target") + assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time") + assert.Equalf(t, whisper.TopicType{0xd8, 0xa2, 0xf3, 0x64}, whisperMessage.Topic, "It sets the discovery topic") +} diff --git a/services/shhext/filter/service.go b/services/shhext/filter/service.go new file mode 100644 index 000000000..fabf36ecf --- /dev/null +++ b/services/shhext/filter/service.go @@ -0,0 +1,350 @@ +package filter + +import ( + "crypto/ecdsa" + "encoding/hex" + "fmt" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/status-im/status-go/services/shhext/chat/topic" + whisper "github.com/status-im/whisper/whisperv6" + "math/big" + "sync" +) + +const ( + discoveryTopic = "contact-discovery" +) + +// The number of partitions +var nPartitions = big.NewInt(5000) + +func toTopic(s string) []byte { + return crypto.Keccak256([]byte(s))[:whisper.TopicLength] +} + +func chatIDToPartitionedTopic(identity string) (string, error) { + partition := big.NewInt(0) + publicKeyBytes, err := hex.DecodeString(identity) + if err != nil { + return "", err + } + + publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes) + if err != nil { + return "", err + } + + partition.Mod(publicKey.X, nPartitions) + + return fmt.Sprintf("contact-discovery-%d", partition), nil +} + +type FilterAndTopic struct { + FilterID string + Topic []byte + SymKeyID string +} + +type Chat struct { + // ChatID is the identifier of the chat + ChatID string + // SymKeyID is the symmetric key id used for symmetric chats + SymKeyID string + // OneToOne tells us if we need to use asymmetric encryption for this chat + OneToOne bool + // Listen is whether we are actually listening for messages on this chat, or the filter is only created in order to be able to post on the topic + Listen bool + // FilterID the whisper filter id generated + FilterID string + // Identity is the public key of the other recipient for non-public chats + Identity string + // Topic is the whisper topic + Topic []byte +} + +type Service struct { + keyID string + whisper *whisper.Whisper + topic *topic.Service + chats map[string]*Chat + mutex sync.Mutex +} + +func New(k string, w *whisper.Whisper, t *topic.Service) *Service { + return &Service{ + keyID: k, + whisper: w, + topic: t, + mutex: sync.Mutex{}, + chats: make(map[string]*Chat), + } +} + +// LoadDiscovery adds the discovery filter +func (s *Service) LoadDiscovery(myKey *ecdsa.PrivateKey) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + discoveryChat := &Chat{ + ChatID: discoveryTopic, + } + + discoveryResponse, err := s.AddAsymmetricFilter(myKey, discoveryChat.ChatID, true) + if err != nil { + return err + } + + discoveryChat.Topic = discoveryResponse.Topic + discoveryChat.FilterID = discoveryResponse.FilterID + + s.chats[discoveryChat.ChatID] = discoveryChat + return nil +} + +func (s *Service) Init(chats []*Chat) error { + log.Debug("Initializing filter service") + myKey, err := s.whisper.GetPrivateKey(s.keyID) + if err != nil { + return err + } + + // Add our own topic + log.Debug("Loading one to one chats") + identityStr := fmt.Sprintf("%x", crypto.FromECDSAPub(&myKey.PublicKey)) + err = s.LoadOneToOne(myKey, identityStr, true) + if err != nil { + log.Error("Error loading one to one chats", "err", err) + + return err + } + + // Add discovery topic + log.Debug("Loading discovery topics") + err = s.LoadDiscovery(myKey) + if err != nil { + return err + } + + // Add the various one to one and public chats + log.Debug("Loading chats") + for _, chat := range chats { + err = s.Load(myKey, chat) + if err != nil { + return err + } + } + + // Add the negotiated topics + log.Debug("Loading negotiated topics") + secrets, err := s.topic.All() + if err != nil { + return err + } + + for _, secret := range secrets { + s.ProcessNegotiatedSecret(secret) + } + + return nil +} + +func (s *Service) Stop() error { + for _, chat := range s.chats { + if err := s.Remove(chat); err != nil { + return err + } + } + return nil +} + +func (s *Service) Remove(chat *Chat) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + if err := s.whisper.Unsubscribe(chat.ChatID); err != nil { + return err + } + if chat.SymKeyID != "" { + s.whisper.DeleteSymKey(chat.SymKeyID) + } + delete(s.chats, chat.ChatID) + + return nil + +} + +// LoadOneToOne creates two filters for a given chat, one listening to the contact codes +// and another on the partitioned topic. We pass a listen parameter to indicated whether +// we are listening to messages on the partitioned topic +func (s *Service) LoadOneToOne(myKey *ecdsa.PrivateKey, identity string, listen bool) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + contactCodeChatID := identity + "-contact-code" + contactCodeFilter, err := s.AddSymmetric(contactCodeChatID) + if err != nil { + return err + } + + s.chats[contactCodeChatID] = &Chat{ + ChatID: contactCodeChatID, + FilterID: contactCodeFilter.FilterID, + Topic: contactCodeFilter.Topic, + SymKeyID: contactCodeFilter.SymKeyID, + Identity: identity, + } + + partitionedTopicChatID, err := chatIDToPartitionedTopic(identity) + if err != nil { + return err + } + // We set up a filter so we can publish, but we discard envelopes if listen is false + partitionedTopicFilter, err := s.AddAsymmetricFilter(myKey, partitionedTopicChatID, listen) + if err != nil { + return err + } + s.chats[partitionedTopicChatID] = &Chat{ + ChatID: partitionedTopicChatID, + FilterID: partitionedTopicFilter.FilterID, + Topic: partitionedTopicFilter.Topic, + Identity: identity, + Listen: listen, + } + + return nil +} + +func (s *Service) AddSymmetric(chatID string) (*FilterAndTopic, error) { + var symKey []byte + + topic := toTopic(chatID) + topics := [][]byte{topic} + + symKeyID, err := s.whisper.AddSymKeyFromPassword(chatID) + if err != nil { + log.Error("SYM KEYN FAILED", "err", err) + return nil, err + } + + if symKey, err = s.whisper.GetSymKey(symKeyID); err != nil { + return nil, err + } + + f := &whisper.Filter{ + KeySym: symKey, + PoW: 0.002, + AllowP2P: true, + Topics: topics, + Messages: s.whisper.NewMessageStore(), + } + + id, err := s.whisper.Subscribe(f) + if err != nil { + return nil, err + } + + return &FilterAndTopic{ + FilterID: id, + SymKeyID: symKeyID, + Topic: topic, + }, nil +} + +func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, listen bool) (*FilterAndTopic, error) { + var err error + var pow float64 + + if listen { + pow = 0.002 + } else { + // Set high pow so we discard messages + pow = 1 + } + + topic := toTopic(chatID) + topics := [][]byte{topic} + + f := &whisper.Filter{ + KeyAsym: keyAsym, + PoW: pow, + AllowP2P: listen, + Topics: topics, + Messages: s.whisper.NewMessageStore(), + } + + id, err := s.whisper.Subscribe(f) + if err != nil { + return nil, err + } + + return &FilterAndTopic{FilterID: id, Topic: topic}, nil +} + +func (s *Service) LoadPublic(chat *Chat) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + filterAndTopic, err := s.AddSymmetric(chat.ChatID) + if err != nil { + return err + } + + // Add mutex + chat.FilterID = filterAndTopic.FilterID + chat.SymKeyID = filterAndTopic.SymKeyID + chat.Topic = filterAndTopic.Topic + s.chats[chat.ChatID] = chat + return nil +} + +func (s *Service) Load(myKey *ecdsa.PrivateKey, chat *Chat) error { + var err error + log.Debug("Loading chat", "chatID", chat.ChatID) + + // Check we haven't already loaded the chat + if _, ok := s.chats[chat.ChatID]; !ok { + if chat.OneToOne { + err = s.LoadOneToOne(myKey, chat.Identity, false) + + } else { + err = s.LoadPublic(chat) + } + if err != nil { + return err + } + + } + return nil +} + +func negotiatedID(identity *ecdsa.PublicKey) string { + return fmt.Sprintf("%x-negotiated", crypto.FromECDSAPub(identity)) +} + +func (s *Service) Get(identity *ecdsa.PublicKey) *Chat { + return s.chats[negotiatedID(identity)] +} + +func (s *Service) ProcessNegotiatedSecret(secret *topic.Secret) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + keyString := fmt.Sprintf("%x", secret.Key) + filter, err := s.AddSymmetric(keyString) + if err != nil { + return err + } + + identityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(secret.Identity)) + + chat := &Chat{ + ChatID: negotiatedID(secret.Identity), + Topic: filter.Topic, + SymKeyID: filter.SymKeyID, + Identity: identityStr, + } + + s.chats[chat.ChatID] = chat + return nil +} diff --git a/services/shhext/filter/service_test.go b/services/shhext/filter/service_test.go new file mode 100644 index 000000000..98fd83393 --- /dev/null +++ b/services/shhext/filter/service_test.go @@ -0,0 +1,155 @@ +package filter + +import ( + "crypto/ecdsa" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + appDB "github.com/status-im/status-go/services/shhext/chat/db" + "github.com/status-im/status-go/services/shhext/chat/topic" + whisper "github.com/status-im/whisper/whisperv6" + "github.com/stretchr/testify/suite" +) + +func TestServiceTestSuite(t *testing.T) { + suite.Run(t, new(ServiceTestSuite)) +} + +type TestKey struct { + privateKey *ecdsa.PrivateKey + partitionedTopic int +} + +func NewTestKey(privateKey string, partitionedTopic int) (*TestKey, error) { + key, err := crypto.HexToECDSA(privateKey) + if err != nil { + return nil, err + } + + return &TestKey{ + privateKey: key, + partitionedTopic: partitionedTopic, + }, nil + +} + +func (t *TestKey) PublicKeyString() string { + return fmt.Sprintf("%x", crypto.FromECDSAPub(&t.privateKey.PublicKey)) +} + +type ServiceTestSuite struct { + suite.Suite + service *Service + path string + keys []*TestKey +} + +func (s *ServiceTestSuite) SetupTest() { + keyStrs := []string{"c6cbd7d76bc5baca530c875663711b947efa6a86a900a9e8645ce32e5821484e", "d51dd64ad19ea84968a308dca246012c00d2b2101d41bce740acd1c650acc509"} + keyTopics := []int{4490, 3991} + + dbFile, err := ioutil.TempFile(os.TempDir(), "topic") + + s.Require().NoError(err) + s.path = dbFile.Name() + + for i, k := range keyStrs { + testKey, err := NewTestKey(k, keyTopics[i]) + s.Require().NoError(err) + + s.keys = append(s.keys, testKey) + } + + db, err := appDB.Open(s.path, "", 0) + s.Require().NoError(err) + + // Build services + topicService := topic.NewService(topic.NewSQLLitePersistence(db)) + whisper := whisper.New(nil) + keyID, err := whisper.AddKeyPair(s.keys[0].privateKey) + s.Require().NoError(err) + + s.service = New(keyID, whisper, topicService) +} + +func (s *ServiceTestSuite) TearDownTest() { + os.Remove(s.path) +} + +func (s *ServiceTestSuite) TestDiscoveryAndPartitionedTopic() { + chats := []*Chat{} + partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[0].partitionedTopic) + contactCodeTopic := s.keys[0].PublicKeyString() + "-contact-code" + + err := s.service.Init(chats) + s.Require().NoError(err) + + s.Require().Equal(3, len(s.service.chats), "It creates two filters") + + discoveryFilter := s.service.chats[discoveryTopic] + s.Require().NotNil(discoveryFilter, "It adds the discovery filter") + + contactCodeFilter := s.service.chats[contactCodeTopic] + s.Require().NotNil(contactCodeFilter, "It adds the contact code filter") + + partitionedFilter := s.service.chats[partitionedTopic] + s.Require().NotNil(partitionedFilter, "It adds the partitioned filter") +} + +func (s *ServiceTestSuite) TestPublicAndOneToOneChats() { + chats := []*Chat{ + &Chat{ + ChatID: "status", + }, + &Chat{ + ChatID: s.keys[1].PublicKeyString(), + Identity: s.keys[1].PublicKeyString(), + OneToOne: true, + }, + } + partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[1].partitionedTopic) + contactCodeTopic := s.keys[1].PublicKeyString() + "-contact-code" + + err := s.service.Init(chats) + s.Require().NoError(err) + + s.Require().Equal(6, len(s.service.chats), "It creates two additional filters for the one to one and one for the public chat") + + statusFilter := s.service.chats["status"] + s.Require().NotNil(statusFilter, "It creates a filter for the public chat") + s.Require().NotNil(statusFilter.SymKeyID, "It returns a sym key id") + + contactCodeFilter := s.service.chats[contactCodeTopic] + s.Require().NotNil(contactCodeFilter, "It adds the contact code filter") + + partitionedFilter := s.service.chats[partitionedTopic] + s.Require().NotNil(partitionedFilter, "It adds the partitioned filter") +} + +func (s *ServiceTestSuite) TestNegotiatedTopic() { + chats := []*Chat{} + + negotiatedTopic1 := s.keys[0].PublicKeyString() + "-negotiated" + negotiatedTopic2 := s.keys[1].PublicKeyString() + "-negotiated" + + // We send a message to ourselves + _, _, err := s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[0].privateKey.PublicKey, []string{"0-2"}) + s.Require().NoError(err) + + // We send a message to someone else + _, _, err = s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[1].privateKey.PublicKey, []string{"0-2"}) + s.Require().NoError(err) + + err = s.service.Init(chats) + s.Require().NoError(err) + + s.Require().Equal(5, len(s.service.chats), "It creates two additional filters for the negotiated topics") + + negotiatedFilter1 := s.service.chats[negotiatedTopic1] + s.Require().NotNil(negotiatedFilter1, "It adds the negotiated filter") + negotiatedFilter2 := s.service.chats[negotiatedTopic2] + s.Require().NotNil(negotiatedFilter2, "It adds the negotiated filter") +} diff --git a/services/shhext/service.go b/services/shhext/service.go index e9fe2d9fa..2b52c849f 100644 --- a/services/shhext/service.go +++ b/services/shhext/service.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -16,8 +17,12 @@ import ( "github.com/status-im/status-go/db" "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/shhext/chat" + appDB "github.com/status-im/status-go/services/shhext/chat/db" + "github.com/status-im/status-go/services/shhext/chat/topic" "github.com/status-im/status-go/services/shhext/dedup" + "github.com/status-im/status-go/services/shhext/filter" "github.com/status-im/status-go/services/shhext/mailservers" + "github.com/status-im/status-go/signal" whisper "github.com/status-im/whisper/whisperv6" "github.com/syndtr/goleveldb/leveldb" "golang.org/x/crypto/sha3" @@ -60,6 +65,7 @@ type Service struct { cache *mailservers.Cache connManager *mailservers.ConnectionManager lastUsedMonitor *mailservers.LastUsedConnectionMonitor + filter *filter.Service } // Make sure that Service implements node.Service interface. @@ -142,11 +148,11 @@ func (s *Service) initProtocol(address, encKey, password string) error { v4Path := filepath.Join(s.dataDir, fmt.Sprintf("%s.v4.db", s.installationID)) if password != "" { - if err := chat.MigrateDBFile(v0Path, v1Path, "ON", password); err != nil { + if err := appDB.MigrateDBFile(v0Path, v1Path, "ON", password); err != nil { return err } - if err := chat.MigrateDBFile(v1Path, v2Path, password, encKey); err != nil { + if err := appDB.MigrateDBFile(v1Path, v2Path, password, encKey); err != nil { // Remove db file as created with a blank password and never used, // and there's no need to rekey in this case os.Remove(v1Path) @@ -154,13 +160,13 @@ func (s *Service) initProtocol(address, encKey, password string) error { } } - if err := chat.MigrateDBKeyKdfIterations(v2Path, v3Path, encKey); err != nil { + if err := appDB.MigrateDBKeyKdfIterations(v2Path, v3Path, encKey); err != nil { os.Remove(v2Path) os.Remove(v3Path) } // Fix IOS not encrypting database - if err := chat.EncryptDatabase(v3Path, v4Path, encKey); err != nil { + if err := appDB.EncryptDatabase(v3Path, v4Path, encKey); err != nil { os.Remove(v3Path) os.Remove(v4Path) } @@ -189,7 +195,18 @@ func (s *Service) initProtocol(address, encKey, password string) error { } } - s.protocol = chat.NewProtocolService(chat.NewEncryptionService(persistence, chat.DefaultEncryptionServiceConfig(s.installationID)), addedBundlesHandler) + // Initialize topics + topicService := topic.NewService(persistence.GetTopicStorage()) + filterService := filter.New(s.config.AsymKeyID, s.w, topicService) + s.filter = filterService + + s.protocol = chat.NewProtocolService( + chat.NewEncryptionService( + persistence, + chat.DefaultEncryptionServiceConfig(s.installationID)), + topicService, + addedBundlesHandler, + s.onNewTopicHandler) return nil } @@ -291,5 +308,39 @@ func (s *Service) Stop() error { s.requestsRegistry.Clear() s.envelopesMonitor.Stop() s.mailMonitor.Stop() + s.filter.Stop() return nil } + +func (s *Service) GetNegotiatedChat(identity *ecdsa.PublicKey) *filter.Chat { + return s.filter.Get(identity) +} + +func (s *Service) LoadFilters(chats []*filter.Chat) error { + return s.filter.Init(chats) +} + +func (s *Service) RemoveFilter(chat *filter.Chat) { + // remove filter +} + +func (s *Service) onNewTopicHandler(sharedSecrets []*topic.Secret) { + var filters []*signal.Filter + log.Info("NEW TOPIC HANDLER", "secrets", sharedSecrets) + for _, sharedSecret := range sharedSecrets { + err := s.filter.ProcessNegotiatedSecret(sharedSecret) + if err != nil { + log.Error("Failed to process negotiated secret", "err", err) + return + } + + } + // TODO: send back chat filter + log.Info("FILTER IDS", "filter", filters) + if len(filters) != 0 { + log.Info("SENDING FILTERS") + handler := EnvelopeSignalHandler{} + handler.WhisperFilterAdded(filters) + } + +} diff --git a/services/shhext/signal.go b/services/shhext/signal.go index 36c840d9a..614cb747b 100644 --- a/services/shhext/signal.go +++ b/services/shhext/signal.go @@ -35,3 +35,7 @@ func (h EnvelopeSignalHandler) DecryptMessageFailed(pubKey string) { func (h EnvelopeSignalHandler) BundleAdded(identity string, installationID string) { signal.SendBundleAdded(identity, installationID) } + +func (h EnvelopeSignalHandler) WhisperFilterAdded(filters []*signal.Filter) { + signal.SendWhisperFilterAdded(filters) +} diff --git a/signal/events_shhext.go b/signal/events_shhext.go index c44ff632b..62c21acfe 100644 --- a/signal/events_shhext.go +++ b/signal/events_shhext.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "github.com/ethereum/go-ethereum/common" + whisper "github.com/status-im/whisper/whisperv6" ) const ( @@ -31,6 +32,9 @@ const ( // EventBundleAdded is triggered when we receive a bundle EventBundleAdded = "bundles.added" + + // EventWhisperFilterAdded is triggered when we setup a new filter or restore existing ones + EventWhisperFilterAdded = "whisper.filter.added" ) // EnvelopeSignal includes hash of the envelope. @@ -58,6 +62,18 @@ type BundleAddedSignal struct { InstallationID string `json:"installationID"` } +type Filter struct { + Identity string `json:"identity"` + FilterID string `json:"filterId"` + SymKeyID string `json:"symKeyId"` + ChatID string `json:"chatId"` + Topic whisper.TopicType `json:"topic"` +} + +type WhisperFilterAddedSignal struct { + Filters []*Filter `json:"filters"` +} + // SendEnvelopeSent triggered when envelope delivered at least to 1 peer. func SendEnvelopeSent(hash common.Hash) { send(EventEnvelopeSent, EnvelopeSignal{Hash: hash}) @@ -114,3 +130,7 @@ func SendDecryptMessageFailed(sender string) { func SendBundleAdded(identity string, installationID string) { send(EventBundleAdded, BundleAddedSignal{Identity: identity, InstallationID: installationID}) } + +func SendWhisperFilterAdded(filters []*Filter) { + send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters}) +} diff --git a/static/bindata.go b/static/bindata.go index 97119eefd..b3d46c54f 100644 --- a/static/bindata.go +++ b/static/bindata.go @@ -1,26 +1,26 @@ -// Code generated by go-bindata. DO NOT EDIT. +// Code generated by go-bindata. // sources: -// ../config/README.md (3.33kB) -// ../config/cli/fleet-eth.beta.json (3.261kB) -// ../config/cli/fleet-eth.staging.json (1.862kB) -// ../config/cli/fleet-eth.test.json (1.543kB) -// ../config/cli/les-enabled.json (58B) -// ../config/cli/mailserver-enabled.json (176B) -// ../config/status-chain-genesis.json (612B) -// keys/bootnode.key (65B) -// keys/firebaseauthkey (153B) -// keys/test-account1-status-chain.pk (489B) -// keys/test-account1.pk (491B) -// keys/test-account2-status-chain.pk (489B) -// keys/test-account2.pk (491B) -// keys/test-account3-before-eip55.pk (489B) +// ../config/README.md +// ../config/cli/fleet-eth.beta.json +// ../config/cli/fleet-eth.staging.json +// ../config/cli/fleet-eth.test.json +// ../config/cli/les-enabled.json +// ../config/cli/mailserver-enabled.json +// ../config/status-chain-genesis.json +// keys/bootnode.key +// keys/firebaseauthkey +// keys/test-account1-status-chain.pk +// keys/test-account1.pk +// keys/test-account2-status-chain.pk +// keys/test-account2.pk +// keys/test-account3-before-eip55.pk +// DO NOT EDIT! package static import ( "bytes" "compress/gzip" - "crypto/sha256" "fmt" "io" "io/ioutil" @@ -33,7 +33,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } var buf bytes.Buffer @@ -41,7 +41,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } if clErr != nil { return nil, err @@ -51,9 +51,8 @@ func bindataRead(data []byte, name string) ([]byte, error) { } type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte + bytes []byte + info os.FileInfo } type bindataFileInfo struct { @@ -97,8 +96,8 @@ func ConfigReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/README.md", size: 3330, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x65, 0xb9, 0xf5, 0x6, 0xbe, 0x7d, 0x85, 0x3b, 0x8, 0xbc, 0x5c, 0x71, 0x85, 0x19, 0xd1, 0xde, 0x38, 0xb5, 0xe9, 0x90, 0x5c, 0x45, 0xb2, 0xa5, 0x8a, 0x91, 0xee, 0xeb, 0x1e, 0xb4, 0xa9, 0x8f}} + info := bindataFileInfo{name: "../config/README.md", size: 3330, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -117,8 +116,8 @@ func ConfigCliFleetEthBetaJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3261, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2b, 0xae, 0x42, 0x4b, 0xa4, 0xd9, 0x2, 0x69, 0x99, 0x29, 0x7e, 0x1, 0x4e, 0xd9, 0x58, 0x84, 0x28, 0x3a, 0x81, 0xc4, 0xde, 0x1d, 0xea, 0x51, 0xc8, 0x21, 0xff, 0x7b, 0xff, 0x23, 0x1c, 0x16}} + info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3261, mode: os.FileMode(420), modTime: time.Unix(1548939502, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -137,8 +136,8 @@ func ConfigCliFleetEthStagingJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1862, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xae, 0x85, 0xa1, 0x10, 0x16, 0x87, 0x10, 0x1c, 0xc3, 0xf4, 0xc7, 0xc, 0x2e, 0x51, 0xb7, 0x3, 0x61, 0x16, 0x99, 0x84, 0x3d, 0x5d, 0x82, 0x62, 0xfb, 0xf4, 0x5e, 0x19, 0xda, 0xb9, 0xaa, 0xc4}} + info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1862, mode: os.FileMode(420), modTime: time.Unix(1548939502, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -157,8 +156,8 @@ func ConfigCliFleetEthTestJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1543, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x68, 0xef, 0x71, 0xa1, 0x38, 0x37, 0xf0, 0x0, 0xbb, 0x95, 0x26, 0x2a, 0x2a, 0x65, 0x98, 0xfe, 0xe5, 0x3f, 0xbf, 0xb, 0x68, 0xa6, 0xb5, 0xa4, 0x10, 0xc1, 0x4b, 0x67, 0xb4, 0x4e, 0x32, 0xc0}} + info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1543, mode: os.FileMode(420), modTime: time.Unix(1548939502, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -177,8 +176,8 @@ func ConfigCliLesEnabledJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/cli/les-enabled.json", size: 58, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7e, 0xee, 0x27, 0xa7, 0x74, 0xa0, 0x46, 0xa1, 0x41, 0xed, 0x4d, 0x16, 0x5b, 0xf3, 0xf0, 0x7c, 0xc8, 0x2f, 0x6f, 0x47, 0xa4, 0xbb, 0x5f, 0x43, 0x33, 0xd, 0x9, 0x9d, 0xea, 0x9e, 0x15, 0xee}} + info := bindataFileInfo{name: "../config/cli/les-enabled.json", size: 58, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -197,8 +196,8 @@ func ConfigCliMailserverEnabledJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/cli/mailserver-enabled.json", size: 176, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x34, 0xec, 0x81, 0x8b, 0x99, 0xb6, 0xdb, 0xc0, 0x8b, 0x46, 0x97, 0x96, 0xc7, 0x58, 0x30, 0x33, 0xef, 0x54, 0x25, 0x87, 0x7b, 0xb9, 0x94, 0x6b, 0x18, 0xa4, 0x5b, 0x58, 0x67, 0x7c, 0x44, 0xa6}} + info := bindataFileInfo{name: "../config/cli/mailserver-enabled.json", size: 176, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -217,8 +216,8 @@ func ConfigStatusChainGenesisJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../config/status-chain-genesis.json", size: 612, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb, 0xf0, 0xc, 0x1, 0x95, 0x65, 0x6, 0x55, 0x48, 0x8f, 0x83, 0xa0, 0xb4, 0x81, 0xda, 0xad, 0x30, 0x6d, 0xb2, 0x78, 0x1b, 0x26, 0x4, 0x13, 0x12, 0x9, 0x6, 0xae, 0x3a, 0x2c, 0x1, 0x71}} + info := bindataFileInfo{name: "../config/status-chain-genesis.json", size: 612, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -237,8 +236,8 @@ func keysBootnodeKey() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/bootnode.key", size: 65, mode: os.FileMode(0644), modTime: time.Unix(1524646110, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x31, 0xcf, 0x27, 0xd4, 0x96, 0x2e, 0x32, 0xcd, 0x58, 0x96, 0x2a, 0xe5, 0x8c, 0xa0, 0xf1, 0x73, 0x1f, 0xd6, 0xd6, 0x8b, 0xb, 0x73, 0xd3, 0x2c, 0x84, 0x1a, 0x56, 0xa4, 0x74, 0xb6, 0x95, 0x20}} + info := bindataFileInfo{name: "keys/bootnode.key", size: 65, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -257,8 +256,8 @@ func keysFirebaseauthkey() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/firebaseauthkey", size: 153, mode: os.FileMode(0644), modTime: time.Unix(1510765867, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe, 0x69, 0x23, 0x64, 0x7d, 0xf9, 0x14, 0x37, 0x6f, 0x2b, 0x1, 0xf0, 0xb0, 0xa4, 0xb2, 0xd0, 0x18, 0xcd, 0xf9, 0xeb, 0x57, 0xa3, 0xfd, 0x79, 0x25, 0xa7, 0x9c, 0x3, 0xce, 0x26, 0xec, 0xe1}} + info := bindataFileInfo{name: "keys/firebaseauthkey", size: 153, mode: os.FileMode(420), modTime: time.Unix(1536843582, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -277,8 +276,8 @@ func keysTestAccount1StatusChainPk() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/test-account1-status-chain.pk", size: 489, mode: os.FileMode(0644), modTime: time.Unix(1516444049, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0xba, 0x35, 0x1, 0x2b, 0x9d, 0xad, 0xf0, 0x2d, 0x3c, 0x4d, 0x6, 0xb5, 0x22, 0x2, 0x47, 0xd4, 0x1c, 0xf4, 0x31, 0x2f, 0xb, 0x5b, 0x27, 0x5d, 0x43, 0x97, 0x58, 0x2d, 0xf0, 0xe1, 0xbe}} + info := bindataFileInfo{name: "keys/test-account1-status-chain.pk", size: 489, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -297,8 +296,8 @@ func keysTestAccount1Pk() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/test-account1.pk", size: 491, mode: os.FileMode(0644), modTime: time.Unix(1510765867, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9, 0x43, 0xc2, 0xf4, 0x8c, 0xc6, 0x64, 0x25, 0x8c, 0x7, 0x8c, 0xa8, 0x89, 0x2b, 0x7b, 0x9b, 0x4f, 0x81, 0xcb, 0xce, 0x3d, 0xef, 0x82, 0x9c, 0x27, 0x27, 0xa9, 0xc5, 0x46, 0x70, 0x30, 0x38}} + info := bindataFileInfo{name: "keys/test-account1.pk", size: 491, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -317,8 +316,8 @@ func keysTestAccount2StatusChainPk() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/test-account2-status-chain.pk", size: 489, mode: os.FileMode(0644), modTime: time.Unix(1516444049, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9, 0xf8, 0x5c, 0xe9, 0x92, 0x96, 0x2d, 0x88, 0x2b, 0x8e, 0x42, 0x3f, 0xa4, 0x93, 0x6c, 0xad, 0xe9, 0xc0, 0x1b, 0x8a, 0x8, 0x8c, 0x5e, 0x7a, 0x84, 0xa2, 0xf, 0x9f, 0x77, 0x58, 0x2c, 0x2c}} + info := bindataFileInfo{name: "keys/test-account2-status-chain.pk", size: 489, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -337,8 +336,8 @@ func keysTestAccount2Pk() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/test-account2.pk", size: 491, mode: os.FileMode(0644), modTime: time.Unix(1510765867, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9f, 0x72, 0xd5, 0x95, 0x5c, 0x5a, 0x99, 0x9d, 0x2f, 0x21, 0x83, 0xd7, 0x10, 0x17, 0x4a, 0x3d, 0x65, 0xc9, 0x26, 0x1a, 0x2c, 0x9d, 0x65, 0x63, 0xd2, 0xa0, 0xfc, 0x7c, 0x0, 0x87, 0x38, 0x9f}} + info := bindataFileInfo{name: "keys/test-account2.pk", size: 491, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -357,8 +356,8 @@ func keysTestAccount3BeforeEip55Pk() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/test-account3-before-eip55.pk", size: 489, mode: os.FileMode(0644), modTime: time.Unix(1516444049, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x81, 0x40, 0x56, 0xc1, 0x5e, 0x10, 0x6e, 0x28, 0x15, 0x3, 0x4e, 0xc4, 0xc4, 0x71, 0x4d, 0x16, 0x99, 0xcc, 0x1b, 0x63, 0xee, 0x10, 0x20, 0xe4, 0x59, 0x52, 0x3f, 0xc0, 0xad, 0x15, 0x13, 0x72}} + info := bindataFileInfo{name: "keys/test-account3-before-eip55.pk", size: 489, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -366,8 +365,8 @@ func keysTestAccount3BeforeEip55Pk() (*asset, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) @@ -377,12 +376,6 @@ func Asset(name string) ([]byte, error) { return nil, fmt.Errorf("Asset %s not found", name) } -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - // MustAsset is like Asset but panics when Asset would return an error. // It simplifies safe initialization of global variables. func MustAsset(name string) []byte { @@ -394,18 +387,12 @@ func MustAsset(name string) []byte { return a } -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - // AssetInfo loads and returns the asset info for the given name. // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) @@ -415,33 +402,6 @@ func AssetInfo(name string) (os.FileInfo, error) { return nil, fmt.Errorf("AssetInfo %s not found", name) } -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - // AssetNames returns the names of the assets. func AssetNames() []string { names := make([]string, 0, len(_bindata)) @@ -454,31 +414,18 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "../config/README.md": ConfigReadmeMd, - "../config/cli/fleet-eth.beta.json": ConfigCliFleetEthBetaJson, - "../config/cli/fleet-eth.staging.json": ConfigCliFleetEthStagingJson, - "../config/cli/fleet-eth.test.json": ConfigCliFleetEthTestJson, - "../config/cli/les-enabled.json": ConfigCliLesEnabledJson, - "../config/cli/mailserver-enabled.json": ConfigCliMailserverEnabledJson, - "../config/status-chain-genesis.json": ConfigStatusChainGenesisJson, - "keys/bootnode.key": keysBootnodeKey, - "keys/firebaseauthkey": keysFirebaseauthkey, - "keys/test-account1-status-chain.pk": keysTestAccount1StatusChainPk, - "keys/test-account1.pk": keysTestAccount1Pk, - "keys/test-account2-status-chain.pk": keysTestAccount2StatusChainPk, - "keys/test-account2.pk": keysTestAccount2Pk, - "keys/test-account3-before-eip55.pk": keysTestAccount3BeforeEip55Pk, } @@ -491,15 +438,15 @@ var _bindata = map[string]func() (*asset, error){ // img/ // a.png // b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error // AssetDir("") will return []string{"data"}. func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { @@ -521,33 +468,32 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "..": &bintree{nil, map[string]*bintree{ "config": &bintree{nil, map[string]*bintree{ "README.md": &bintree{ConfigReadmeMd, map[string]*bintree{}}, "cli": &bintree{nil, map[string]*bintree{ - "fleet-eth.beta.json": &bintree{ConfigCliFleetEthBetaJson, map[string]*bintree{}}, - "fleet-eth.staging.json": &bintree{ConfigCliFleetEthStagingJson, map[string]*bintree{}}, - "fleet-eth.test.json": &bintree{ConfigCliFleetEthTestJson, map[string]*bintree{}}, - "les-enabled.json": &bintree{ConfigCliLesEnabledJson, map[string]*bintree{}}, + "fleet-eth.beta.json": &bintree{ConfigCliFleetEthBetaJson, map[string]*bintree{}}, + "fleet-eth.staging.json": &bintree{ConfigCliFleetEthStagingJson, map[string]*bintree{}}, + "fleet-eth.test.json": &bintree{ConfigCliFleetEthTestJson, map[string]*bintree{}}, + "les-enabled.json": &bintree{ConfigCliLesEnabledJson, map[string]*bintree{}}, "mailserver-enabled.json": &bintree{ConfigCliMailserverEnabledJson, map[string]*bintree{}}, }}, "status-chain-genesis.json": &bintree{ConfigStatusChainGenesisJson, map[string]*bintree{}}, }}, }}, "keys": &bintree{nil, map[string]*bintree{ - "bootnode.key": &bintree{keysBootnodeKey, map[string]*bintree{}}, - "firebaseauthkey": &bintree{keysFirebaseauthkey, map[string]*bintree{}}, + "bootnode.key": &bintree{keysBootnodeKey, map[string]*bintree{}}, + "firebaseauthkey": &bintree{keysFirebaseauthkey, map[string]*bintree{}}, "test-account1-status-chain.pk": &bintree{keysTestAccount1StatusChainPk, map[string]*bintree{}}, - "test-account1.pk": &bintree{keysTestAccount1Pk, map[string]*bintree{}}, + "test-account1.pk": &bintree{keysTestAccount1Pk, map[string]*bintree{}}, "test-account2-status-chain.pk": &bintree{keysTestAccount2StatusChainPk, map[string]*bintree{}}, - "test-account2.pk": &bintree{keysTestAccount2Pk, map[string]*bintree{}}, + "test-account2.pk": &bintree{keysTestAccount2Pk, map[string]*bintree{}}, "test-account3-before-eip55.pk": &bintree{keysTestAccount3BeforeEip55Pk, map[string]*bintree{}}, }}, }} -// RestoreAsset restores an asset under the given directory. +// RestoreAsset restores an asset under the given directory func RestoreAsset(dir, name string) error { data, err := Asset(name) if err != nil { @@ -565,10 +511,14 @@ func RestoreAsset(dir, name string) error { if err != nil { return err } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil } -// RestoreAssets restores an asset under the given directory recursively. +// RestoreAssets restores an asset under the given directory recursively func RestoreAssets(dir, name string) error { children, err := AssetDir(name) // File @@ -586,6 +536,7 @@ func RestoreAssets(dir, name string) error { } func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/static/encryption_migrations/1536754952_initial_schema.down.sql b/static/chat_db_migrations/1536754952_initial_schema.down.sql similarity index 100% rename from static/encryption_migrations/1536754952_initial_schema.down.sql rename to static/chat_db_migrations/1536754952_initial_schema.down.sql diff --git a/static/encryption_migrations/1536754952_initial_schema.up.sql b/static/chat_db_migrations/1536754952_initial_schema.up.sql similarity index 100% rename from static/encryption_migrations/1536754952_initial_schema.up.sql rename to static/chat_db_migrations/1536754952_initial_schema.up.sql diff --git a/static/encryption_migrations/1539249977_update_ratchet_info.down.sql b/static/chat_db_migrations/1539249977_update_ratchet_info.down.sql similarity index 100% rename from static/encryption_migrations/1539249977_update_ratchet_info.down.sql rename to static/chat_db_migrations/1539249977_update_ratchet_info.down.sql diff --git a/static/encryption_migrations/1539249977_update_ratchet_info.up.sql b/static/chat_db_migrations/1539249977_update_ratchet_info.up.sql similarity index 100% rename from static/encryption_migrations/1539249977_update_ratchet_info.up.sql rename to static/chat_db_migrations/1539249977_update_ratchet_info.up.sql diff --git a/static/encryption_migrations/1540715431_add_version.down.sql b/static/chat_db_migrations/1540715431_add_version.down.sql similarity index 100% rename from static/encryption_migrations/1540715431_add_version.down.sql rename to static/chat_db_migrations/1540715431_add_version.down.sql diff --git a/static/encryption_migrations/1540715431_add_version.up.sql b/static/chat_db_migrations/1540715431_add_version.up.sql similarity index 100% rename from static/encryption_migrations/1540715431_add_version.up.sql rename to static/chat_db_migrations/1540715431_add_version.up.sql diff --git a/static/encryption_migrations/1541164797_add_installations.down.sql b/static/chat_db_migrations/1541164797_add_installations.down.sql similarity index 100% rename from static/encryption_migrations/1541164797_add_installations.down.sql rename to static/chat_db_migrations/1541164797_add_installations.down.sql diff --git a/static/encryption_migrations/1541164797_add_installations.up.sql b/static/chat_db_migrations/1541164797_add_installations.up.sql similarity index 100% rename from static/encryption_migrations/1541164797_add_installations.up.sql rename to static/chat_db_migrations/1541164797_add_installations.up.sql diff --git a/static/chat_db_migrations/1558084410_add_topic.down.sql b/static/chat_db_migrations/1558084410_add_topic.down.sql new file mode 100644 index 000000000..f6775d186 --- /dev/null +++ b/static/chat_db_migrations/1558084410_add_topic.down.sql @@ -0,0 +1,2 @@ +DROP TABLE topic_installation_ids; +DROP TABLE topics; diff --git a/static/chat_db_migrations/1558084410_add_topic.up.sql b/static/chat_db_migrations/1558084410_add_topic.up.sql new file mode 100644 index 000000000..44ef68d0f --- /dev/null +++ b/static/chat_db_migrations/1558084410_add_topic.up.sql @@ -0,0 +1,11 @@ +CREATE TABLE topics ( + identity BLOB NOT NULL PRIMARY KEY ON CONFLICT IGNORE, + secret BLOB NOT NULL +); + +CREATE TABLE topic_installation_ids ( + id TEXT NOT NULL, + identity_id BLOB NOT NULL, + UNIQUE(id, identity_id) ON CONFLICT IGNORE, + FOREIGN KEY (identity_id) REFERENCES topics(identity) +); diff --git a/static/encryption_migrations/static.go b/static/chat_db_migrations/static.go similarity index 82% rename from static/encryption_migrations/static.go rename to static/chat_db_migrations/static.go index a08d44d2a..ec9069288 100644 --- a/static/encryption_migrations/static.go +++ b/static/chat_db_migrations/static.go @@ -1,4 +1,4 @@ // Package static embeds static (JS, HTML) resources right into the binaries package static -//go:generate go-bindata -pkg migrations -o ../../services/shhext/chat/migrations/bindata.go . +//go:generate go-bindata -pkg migrations -o ../../services/shhext/chat/db/migrations/bindata.go . diff --git a/t/bindata.go b/t/bindata.go index fde0a1a18..f921feb9d 100644 --- a/t/bindata.go +++ b/t/bindata.go @@ -1,15 +1,15 @@ -// Code generated by go-bindata. DO NOT EDIT. +// Code generated by go-bindata. // sources: -// config/public-chain-accounts.json (307B) -// config/status-chain-accounts.json (543B) -// config/test-data.json (84B) +// config/public-chain-accounts.json +// config/status-chain-accounts.json +// config/test-data.json +// DO NOT EDIT! package t import ( "bytes" "compress/gzip" - "crypto/sha256" "fmt" "io" "io/ioutil" @@ -22,7 +22,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } var buf bytes.Buffer @@ -30,7 +30,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %v", name, err) } if clErr != nil { return nil, err @@ -40,9 +40,8 @@ func bindataRead(data []byte, name string) ([]byte, error) { } type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte + bytes []byte + info os.FileInfo } type bindataFileInfo struct { @@ -86,8 +85,8 @@ func configPublicChainAccountsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config/public-chain-accounts.json", size: 307, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x76, 0x5d, 0xc0, 0xfe, 0x57, 0x50, 0x18, 0xec, 0x2d, 0x61, 0x1b, 0xa9, 0x81, 0x11, 0x5f, 0x77, 0xf7, 0xb6, 0x67, 0x82, 0x1, 0x40, 0x68, 0x9d, 0xc5, 0x41, 0xaf, 0xce, 0x43, 0x81, 0x92, 0x96}} + info := bindataFileInfo{name: "config/public-chain-accounts.json", size: 307, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -106,8 +105,8 @@ func configStatusChainAccountsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config/status-chain-accounts.json", size: 543, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8e, 0xb3, 0x61, 0x51, 0x70, 0x3c, 0x12, 0x3e, 0xf1, 0x1c, 0x81, 0xfb, 0x9a, 0x7c, 0xe3, 0x63, 0xd0, 0x8f, 0x12, 0xc5, 0x2d, 0xf4, 0xea, 0x27, 0x33, 0xef, 0xca, 0xf9, 0x3f, 0x72, 0x44, 0xbf}} + info := bindataFileInfo{name: "config/status-chain-accounts.json", size: 543, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -126,8 +125,8 @@ func configTestDataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config/test-data.json", size: 84, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xce, 0x9d, 0x80, 0xf5, 0x87, 0xfa, 0x57, 0x1d, 0xa1, 0xd5, 0x7a, 0x10, 0x3, 0xac, 0xd7, 0xf4, 0x64, 0x32, 0x96, 0x2b, 0xb7, 0x21, 0xb7, 0xa6, 0x80, 0x40, 0xe9, 0x65, 0xe3, 0xd6, 0xbd, 0x40}} + info := bindataFileInfo{name: "config/test-data.json", size: 84, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)} + a := &asset{bytes: bytes, info: info} return a, nil } @@ -135,8 +134,8 @@ func configTestDataJson() (*asset, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) @@ -146,12 +145,6 @@ func Asset(name string) ([]byte, error) { return nil, fmt.Errorf("Asset %s not found", name) } -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - // MustAsset is like Asset but panics when Asset would return an error. // It simplifies safe initialization of global variables. func MustAsset(name string) []byte { @@ -163,18 +156,12 @@ func MustAsset(name string) []byte { return a } -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - // AssetInfo loads and returns the asset info for the given name. // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) @@ -184,33 +171,6 @@ func AssetInfo(name string) (os.FileInfo, error) { return nil, fmt.Errorf("AssetInfo %s not found", name) } -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - // AssetNames returns the names of the assets. func AssetNames() []string { names := make([]string, 0, len(_bindata)) @@ -223,9 +183,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "config/public-chain-accounts.json": configPublicChainAccountsJson, - "config/status-chain-accounts.json": configStatusChainAccountsJson, - "config/test-data.json": configTestDataJson, } @@ -238,15 +196,15 @@ var _bindata = map[string]func() (*asset, error){ // img/ // a.png // b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error // AssetDir("") will return []string{"data"}. func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { @@ -268,16 +226,15 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "config": &bintree{nil, map[string]*bintree{ "public-chain-accounts.json": &bintree{configPublicChainAccountsJson, map[string]*bintree{}}, "status-chain-accounts.json": &bintree{configStatusChainAccountsJson, map[string]*bintree{}}, - "test-data.json": &bintree{configTestDataJson, map[string]*bintree{}}, + "test-data.json": &bintree{configTestDataJson, map[string]*bintree{}}, }}, }} -// RestoreAsset restores an asset under the given directory. +// RestoreAsset restores an asset under the given directory func RestoreAsset(dir, name string) error { data, err := Asset(name) if err != nil { @@ -295,10 +252,14 @@ func RestoreAsset(dir, name string) error { if err != nil { return err } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil } -// RestoreAssets restores an asset under the given directory recursively. +// RestoreAssets restores an asset under the given directory recursively func RestoreAssets(dir, name string) error { children, err := AssetDir(name) // File @@ -316,6 +277,7 @@ func RestoreAssets(dir, name string) error { } func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } +