Add topic negotiation

This commit add topic negotiation to the protocol.

On receiving a message from a client with version >= 1, we will generate
a shared key using Diffie-Hellman. We will record also which
installationID has sent us a message.

This key will be passed back to the above layer, which will then use to
start listening to a whisper topic (the `chat` namespace has no
knowledge of whisper).

When sending a message to a set of InstallationIDs, we check whether we
have agreed on a topic with all of them, and if so, we will send on this
separate topic, otherwise we fallback on discovery.

This change is backward compatible, as long as there is no downgrade of
the app on the other side.

A few changes:

* Factored out the DB in a separate namespace as now it is
being used by multiple services (TopicService and EncryptionService).

* Factored out multidevice management in a separate namespace

* Moved all the test to test the whole protoocl rather than just the encryption service

* Moved all the filter management in status-go
This commit is contained in:
Andrea Maria Piana 2019-05-17 13:06:56 +02:00
parent 047c9b5263
commit cef7f367ab
36 changed files with 1675 additions and 824 deletions

View File

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

View File

@ -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
@ -42,7 +42,6 @@ func bindataRead(data []byte, name string) ([]byte, error) {
type asset struct {
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
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{}},
}}
// 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, "/")...)...)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -48,7 +50,6 @@ func bindataRead(data []byte, name string) ([]byte, error) {
type asset struct {
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
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,7 +402,6 @@ 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{}},
@ -416,10 +411,12 @@ var _bintree = &bintree{nil, 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, "/")...)...)
}

View File

@ -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"`
@ -417,6 +417,8 @@ type ProtocolMessage struct {
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"`
// 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,
}

View File

@ -78,4 +78,6 @@ message ProtocolMessage {
// Public chats, not encrypted
bytes public_message = 102;
// Version of the protocol
uint32 version = 103;
}

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

@ -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 == "" {
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -53,7 +53,6 @@ func bindataRead(data []byte, name string) ([]byte, error) {
type asset struct {
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
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,7 +468,6 @@ 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{
@ -547,7 +493,7 @@ var _bintree = &bintree{nil, 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, "/")...)...)
}

View File

@ -0,0 +1,2 @@
DROP TABLE topic_installation_ids;
DROP TABLE topics;

View File

@ -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)
);

View File

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

View File

@ -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
@ -42,7 +42,6 @@ func bindataRead(data []byte, name string) ([]byte, error) {
type asset struct {
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
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,7 +226,6 @@ 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{}},
@ -277,7 +234,7 @@ var _bintree = &bintree{nil, 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, "/")...)...)
}