mirror of
https://github.com/status-im/status-go.git
synced 2025-03-01 07:00:49 +00:00
Move to monorepo structure (#1684)
Move to a monorepo structure with submodules - Rename status-protocol-go to status-go/protocol
This commit is contained in:
parent
2dd74da23d
commit
ed5a5c154d
@ -9,3 +9,9 @@ update_configs:
|
||||
default_assignees:
|
||||
- "adambabik"
|
||||
- "cammellos"
|
||||
- package_manager: "go:modules"
|
||||
directory: "/protocol"
|
||||
update_schedule: "weekly"
|
||||
default_assignees:
|
||||
- "adambabik"
|
||||
- "cammellos"
|
||||
|
3
.github/package-lock-snitch.config.json
vendored
3
.github/package-lock-snitch.config.json
vendored
@ -1,7 +1,6 @@
|
||||
{
|
||||
"recipients": [
|
||||
"adambabik",
|
||||
"mandrigin",
|
||||
"corpetty"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
|
||||
"github.com/status-im/status-go/account/generator"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
// errors
|
||||
@ -325,7 +325,7 @@ func (m *Manager) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password
|
||||
return address, "", err
|
||||
}
|
||||
|
||||
pubKey = statusproto.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
pubKey = protocol.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
|
||||
return
|
||||
}
|
||||
@ -349,7 +349,7 @@ func (m *Manager) importExtendedKey(keyPurpose extkeys.KeyPurpose, extKey *extke
|
||||
if err != nil {
|
||||
return address, "", err
|
||||
}
|
||||
pubKey = statusproto.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
pubKey = protocol.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package account
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
func CreateAddress() (address, pubKey, privKey string, err error) {
|
||||
@ -15,8 +15,8 @@ func CreateAddress() (address, pubKey, privKey string, err error) {
|
||||
pubKeyBytes := crypto.FromECDSAPub(&key.PublicKey)
|
||||
addressBytes := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
privKey = statusproto.EncodeHex(privKeyBytes)
|
||||
pubKey = statusproto.EncodeHex(pubKeyBytes)
|
||||
privKey = protocol.EncodeHex(privKeyBytes)
|
||||
pubKey = protocol.EncodeHex(pubKeyBytes)
|
||||
address = addressBytes.Hex()
|
||||
|
||||
return
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
type account struct {
|
||||
@ -14,7 +14,7 @@ type account struct {
|
||||
}
|
||||
|
||||
func (a *account) toAccountInfo() AccountInfo {
|
||||
publicKeyHex := statusproto.EncodeHex(crypto.FromECDSAPub(&a.privateKey.PublicKey))
|
||||
publicKeyHex := protocol.EncodeHex(crypto.FromECDSAPub(&a.privateKey.PublicKey))
|
||||
addressHex := crypto.PubkeyToAddress(a.privateKey.PublicKey).Hex()
|
||||
|
||||
return AccountInfo{
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/status-im/status-go/account/generator"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
// OnboardingAccount is returned during onboarding and contains its ID and the mnemonic to re-generate the same account Info keys.
|
||||
@ -107,7 +107,7 @@ func (o *Onboarding) deriveAccount(masterExtendedKey *extkeys.ExtendedKey, purpo
|
||||
|
||||
privateKeyECDSA := extendedKey.ToECDSA()
|
||||
address := crypto.PubkeyToAddress(privateKeyECDSA.PublicKey)
|
||||
publicKeyHex := statusproto.EncodeHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
|
||||
publicKeyHex := protocol.EncodeHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
|
||||
|
||||
return address.Hex(), publicKeyHex, nil
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/node"
|
||||
"github.com/status-im/status-go/params"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
accountssvc "github.com/status-im/status-go/services/accounts"
|
||||
"github.com/status-im/status-go/services/browsers"
|
||||
@ -40,7 +41,6 @@ import (
|
||||
"github.com/status-im/status-go/services/wallet"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -1022,6 +1022,6 @@ func (b *StatusBackend) SignHash(hexEncodedHash string) (string, error) {
|
||||
return "", fmt.Errorf("SignHash: could not sign the hash: %v", err)
|
||||
}
|
||||
|
||||
hexEncodedSignature := statusproto.EncodeHex(signature)
|
||||
hexEncodedSignature := protocol.EncodeHex(signature)
|
||||
return hexEncodedSignature, nil
|
||||
}
|
||||
|
@ -18,15 +18,15 @@ import (
|
||||
"github.com/status-im/status-go/api"
|
||||
"github.com/status-im/status-go/logutils"
|
||||
"github.com/status-im/status-go/params"
|
||||
gethbridge "github.com/status-im/status-go/protocol/bridge/geth"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
"github.com/status-im/status-go/t/helpers"
|
||||
gethbridge "github.com/status-im/status-protocol-go/bridge/geth"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -182,7 +182,7 @@ func verifyMailserverBehavior(mailserverNode *enode.Node) {
|
||||
logger.Error("Error requesting historic messages from mailserver", "error", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
requestID := statusproto.BytesToHash(requestIDBytes)
|
||||
requestID := protocol.BytesToHash(requestIDBytes)
|
||||
|
||||
// wait for mailserver request sent event
|
||||
err = waitForMailServerRequestSent(mailServerResponseWatcher, requestID, time.Duration(*timeout)*time.Second)
|
||||
@ -333,7 +333,7 @@ func joinPublicChat(w whispertypes.Whisper, rpcClient *rpc.Client, name string)
|
||||
return keyID, topic, filterID, err
|
||||
}
|
||||
|
||||
func waitForMailServerRequestSent(events chan whispertypes.EnvelopeEvent, requestID statusproto.Hash, timeout time.Duration) error {
|
||||
func waitForMailServerRequestSent(events chan whispertypes.EnvelopeEvent, requestID protocol.Hash, timeout time.Duration) error {
|
||||
timeoutTimer := time.NewTimer(timeout)
|
||||
for {
|
||||
select {
|
||||
@ -348,7 +348,7 @@ func waitForMailServerRequestSent(events chan whispertypes.EnvelopeEvent, reques
|
||||
}
|
||||
}
|
||||
|
||||
func waitForMailServerResponse(events chan whispertypes.EnvelopeEvent, requestID statusproto.Hash, timeout time.Duration) (*whispertypes.MailServerResponse, error) {
|
||||
func waitForMailServerResponse(events chan whispertypes.EnvelopeEvent, requestID protocol.Hash, timeout time.Duration) (*whispertypes.MailServerResponse, error) {
|
||||
timeoutTimer := time.NewTimer(timeout)
|
||||
for {
|
||||
select {
|
||||
@ -408,7 +408,7 @@ func waitForEnvelopeEvents(events chan whispertypes.EnvelopeEvent, hashes []stri
|
||||
}
|
||||
|
||||
// helper for checking LastEnvelopeHash
|
||||
func isEmptyEnvelope(hash statusproto.Hash) bool {
|
||||
func isEmptyEnvelope(hash protocol.Hash) bool {
|
||||
for _, b := range hash {
|
||||
if b != 0 {
|
||||
return false
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
@ -57,7 +57,7 @@ type TopicHistory struct {
|
||||
Current time.Time
|
||||
End time.Time
|
||||
|
||||
RequestID statusproto.Hash
|
||||
RequestID protocol.Hash
|
||||
}
|
||||
|
||||
// Key returns unique identifier for this TopicHistory.
|
||||
@ -115,7 +115,7 @@ func (t TopicHistory) SameRange(other TopicHistory) bool {
|
||||
|
||||
// Pending returns true if this topic was requested from a mail server.
|
||||
func (t TopicHistory) Pending() bool {
|
||||
return t.RequestID != statusproto.Hash{}
|
||||
return t.RequestID != protocol.Hash{}
|
||||
}
|
||||
|
||||
// HistoryRequest is kept in the database while request is in the progress.
|
||||
@ -127,7 +127,7 @@ type HistoryRequest struct {
|
||||
histories []TopicHistory
|
||||
|
||||
// Generated ID
|
||||
ID statusproto.Hash
|
||||
ID protocol.Hash
|
||||
// List of the topics
|
||||
TopicHistoryKeys []TopicHistoryKey
|
||||
}
|
||||
@ -167,8 +167,8 @@ func (req HistoryRequest) Save() error {
|
||||
}
|
||||
|
||||
// Replace saves request with new ID and all data attached to the old one.
|
||||
func (req HistoryRequest) Replace(id statusproto.Hash) error {
|
||||
if (req.ID != statusproto.Hash{}) {
|
||||
func (req HistoryRequest) Replace(id protocol.Hash) error {
|
||||
if (req.ID != protocol.Hash{}) {
|
||||
if err := req.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package db
|
||||
import (
|
||||
"time"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
)
|
||||
|
||||
@ -44,7 +44,7 @@ func (h HistoryStore) NewHistory(topic whispertypes.TopicType, duration time.Dur
|
||||
}
|
||||
|
||||
// GetRequest loads HistoryRequest from database.
|
||||
func (h HistoryStore) GetRequest(id statusproto.Hash) (HistoryRequest, error) {
|
||||
func (h HistoryStore) GetRequest(id protocol.Hash) (HistoryRequest, error) {
|
||||
req := HistoryRequest{requestDB: h.requestDB, topicDB: h.topicDB, ID: id}
|
||||
err := req.Load()
|
||||
if err != nil {
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -43,7 +43,7 @@ func TestGetExistingHistory(t *testing.T) {
|
||||
|
||||
func TestNewHistoryRequest(t *testing.T) {
|
||||
store := createInMemStore(t)
|
||||
id := statusproto.Hash{1}
|
||||
id := protocol.Hash{1}
|
||||
req, err := store.GetRequest(id)
|
||||
require.Error(t, err)
|
||||
req = store.NewRequest()
|
||||
@ -61,8 +61,8 @@ func TestNewHistoryRequest(t *testing.T) {
|
||||
|
||||
func TestGetAllRequests(t *testing.T) {
|
||||
store := createInMemStore(t)
|
||||
idOne := statusproto.Hash{1}
|
||||
idTwo := statusproto.Hash{2}
|
||||
idOne := protocol.Hash{1}
|
||||
idTwo := protocol.Hash{2}
|
||||
|
||||
req := store.NewRequest()
|
||||
req.ID = idOne
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -80,7 +80,7 @@ func TestAddHistory(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
th := TopicHistory{db: topicdb, Topic: topic, Current: now}
|
||||
id := statusproto.Hash{1}
|
||||
id := protocol.Hash{1}
|
||||
|
||||
req := HistoryRequest{requestDB: requestdb, topicDB: topicdb, ID: id}
|
||||
req.AddHistory(th)
|
||||
|
4
go.mod
4
go.mod
@ -10,6 +10,8 @@ replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.2019071716
|
||||
|
||||
replace github.com/gomarkdown/markdown => github.com/status-im/markdown v0.0.0-20191113114344-af599402d015
|
||||
|
||||
replace github.com/status-im/status-go/protocol => ./protocol
|
||||
|
||||
require (
|
||||
github.com/beevik/ntp v0.2.0
|
||||
github.com/btcsuite/btcd v0.0.0-20191011042131-c3151ef50de9
|
||||
@ -31,7 +33,7 @@ require (
|
||||
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
|
||||
github.com/status-im/migrate/v4 v4.6.2-status.2
|
||||
github.com/status-im/rendezvous v1.3.0
|
||||
github.com/status-im/status-protocol-go v0.5.2
|
||||
github.com/status-im/status-go/protocol v0.5.2
|
||||
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501
|
||||
github.com/status-im/whisper v1.6.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
|
2
go.sum
2
go.sum
@ -599,8 +599,6 @@ github.com/status-im/migrate/v4 v4.6.2-status.2 h1:SdC+sMDl/aI7vUlwD2qj2p7KsK4T6
|
||||
github.com/status-im/migrate/v4 v4.6.2-status.2/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8=
|
||||
github.com/status-im/rendezvous v1.3.0 h1:7RK/MXXW+tlm0asKm1u7Qp7Yni6AO29a7j8+E4Lbjg4=
|
||||
github.com/status-im/rendezvous v1.3.0/go.mod h1:+hzjuP+j/XzLPeF6E50b88pWOTLdTcwjvNYt+Gh1W1s=
|
||||
github.com/status-im/status-protocol-go v0.5.2 h1:C6m6N6TLzJbuJmV4u8iNzs0cj+Q1CfBWdS0LZLtGkN8=
|
||||
github.com/status-im/status-protocol-go v0.5.2/go.mod h1:L5/7fKnycEBOiLm3TuCHDUNcn0kNNhSNsYLkqbUQngg=
|
||||
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 h1:oa0KU5jJRNtXaM/P465MhvSFo/HM2O8qi2DDuPcd7ro=
|
||||
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501/go.mod h1:RYo/itke1oU5k/6sj9DNM3QAwtE5rZSgg5JnkOv83hk=
|
||||
github.com/status-im/whisper v1.5.2 h1:26NgiKusmPic38eQdtXnaY+iaQ/LuQ3Dh0kCGYT/Uxs=
|
||||
|
@ -18,11 +18,11 @@ import (
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/profiling"
|
||||
protocol "github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/services/typeddata"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
protocol "github.com/status-im/status-protocol-go"
|
||||
validator "gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
|
@ -31,10 +31,10 @@ import (
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/status-im/status-go/signal"
|
||||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
||||
"github.com/status-im/status-go/transactions"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -461,7 +461,7 @@ func testLoginWithKeycard(t *testing.T, feed *event.Feed) bool { //nolint: gocyc
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
chatPubKeyHex := statusproto.EncodeHex(crypto.FromECDSAPub(&chatPrivKey.PublicKey))
|
||||
chatPubKeyHex := protocol.EncodeHex(crypto.FromECDSAPub(&chatPrivKey.PublicKey))
|
||||
if whisperService.HasKeyPair(chatPubKeyHex) {
|
||||
t.Error("identity already present in whisper")
|
||||
return false
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-protocol-go/zaputil"
|
||||
"github.com/status-im/status-go/protocol/zaputil"
|
||||
)
|
||||
|
||||
type gethLoggerCore struct {
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
@ -118,7 +118,7 @@ func testMessagesCount(t *testing.T, expected int, s *WMailServer) {
|
||||
func countMessages(t *testing.T, db DB) int {
|
||||
var (
|
||||
count int
|
||||
zero statusproto.Hash
|
||||
zero protocol.Hash
|
||||
emptyTopic whispertypes.TopicType
|
||||
)
|
||||
|
||||
|
@ -4,14 +4,14 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DBKeyLength is a size of the envelope key.
|
||||
DBKeyLength = statusproto.HashLength + timestampLength + whispertypes.TopicLength
|
||||
CursorLength = statusproto.HashLength + timestampLength
|
||||
DBKeyLength = protocol.HashLength + timestampLength + whispertypes.TopicLength
|
||||
CursorLength = protocol.HashLength + timestampLength
|
||||
)
|
||||
|
||||
var (
|
||||
@ -31,11 +31,11 @@ func (k *DBKey) Bytes() []byte {
|
||||
}
|
||||
|
||||
func (k *DBKey) Topic() whispertypes.TopicType {
|
||||
return whispertypes.BytesToTopic(k.raw[timestampLength+statusproto.HashLength:])
|
||||
return whispertypes.BytesToTopic(k.raw[timestampLength+protocol.HashLength:])
|
||||
}
|
||||
|
||||
func (k *DBKey) EnvelopeHash() statusproto.Hash {
|
||||
return statusproto.BytesToHash(k.raw[timestampLength : statusproto.HashLength+timestampLength])
|
||||
func (k *DBKey) EnvelopeHash() protocol.Hash {
|
||||
return protocol.BytesToHash(k.raw[timestampLength : protocol.HashLength+timestampLength])
|
||||
}
|
||||
|
||||
func (k *DBKey) Cursor() []byte {
|
||||
@ -44,11 +44,11 @@ func (k *DBKey) Cursor() []byte {
|
||||
}
|
||||
|
||||
// NewDBKey creates a new DBKey with the given values.
|
||||
func NewDBKey(timestamp uint32, topic whispertypes.TopicType, h statusproto.Hash) *DBKey {
|
||||
func NewDBKey(timestamp uint32, topic whispertypes.TopicType, h protocol.Hash) *DBKey {
|
||||
var k DBKey
|
||||
k.raw = make([]byte, DBKeyLength)
|
||||
binary.BigEndian.PutUint32(k.raw, timestamp)
|
||||
copy(k.raw[timestampLength:], h[:])
|
||||
copy(k.raw[timestampLength+statusproto.HashLength:], topic[:])
|
||||
copy(k.raw[timestampLength+protocol.HashLength:], topic[:])
|
||||
return &k
|
||||
}
|
||||
|
@ -3,15 +3,15 @@ package mailserver
|
||||
import (
|
||||
"testing"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewDBKey(t *testing.T) {
|
||||
topic := whispertypes.BytesToTopic([]byte{0x01, 0x02, 0x03, 0x04})
|
||||
|
||||
hash := statusproto.BytesToHash([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32})
|
||||
hash := protocol.BytesToHash([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32})
|
||||
dbKey := NewDBKey(0xabcdef12, topic, hash)
|
||||
expected := []byte{0xab, 0xcd, 0xef, 0x12, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x01, 0x02, 0x03, 0x04}
|
||||
require.Equal(t, expected, dbKey.Bytes())
|
||||
|
@ -30,8 +30,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/status-im/status-go/params"
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
prom "github.com/prometheus/client_golang/prometheus"
|
||||
@ -199,7 +199,7 @@ func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope)
|
||||
return
|
||||
}
|
||||
|
||||
requestID := statusproto.Hash(request.Hash())
|
||||
requestID := protocol.Hash(request.Hash())
|
||||
peerID := peerIDString(peer)
|
||||
|
||||
log.Info("[mailserver:DeliverMail] delivering mail",
|
||||
@ -356,7 +356,7 @@ func (s *WMailServer) Deliver(peer *whisper.Peer, r whisper.MessagesRequest) {
|
||||
deliveryAttemptsCounter.Inc()
|
||||
|
||||
var (
|
||||
requestIDHash = statusproto.BytesToHash(r.ID)
|
||||
requestIDHash = protocol.BytesToHash(r.ID)
|
||||
requestIDStr = requestIDHash.String()
|
||||
peerID = peerIDString(peer)
|
||||
err error
|
||||
@ -592,7 +592,7 @@ func (s *WMailServer) exceedsPeerRequests(peer []byte) bool {
|
||||
|
||||
func (s *WMailServer) createIterator(lower, upper uint32, cursor []byte, bloom []byte, limit uint32) (Iterator, error) {
|
||||
var (
|
||||
emptyHash statusproto.Hash
|
||||
emptyHash protocol.Hash
|
||||
emptyTopic whispertypes.TopicType
|
||||
ku, kl *DBKey
|
||||
)
|
||||
@ -620,7 +620,7 @@ func (s *WMailServer) processRequestInBundles(
|
||||
requestID string,
|
||||
output chan<- []rlp.RawValue,
|
||||
cancel <-chan struct{},
|
||||
) ([]byte, statusproto.Hash) {
|
||||
) ([]byte, protocol.Hash) {
|
||||
timer := prom.NewTimer(requestsInBundlesDuration)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
@ -631,7 +631,7 @@ func (s *WMailServer) processRequestInBundles(
|
||||
processedEnvelopes int
|
||||
processedEnvelopesSize int64
|
||||
nextCursor []byte
|
||||
lastEnvelopeHash statusproto.Hash
|
||||
lastEnvelopeHash protocol.Hash
|
||||
)
|
||||
|
||||
log.Info("[mailserver:processRequestInBundles] processing request",
|
||||
@ -760,13 +760,13 @@ func (s *WMailServer) sendRawEnvelopes(peer *whisper.Peer, envelopes []rlp.RawVa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WMailServer) sendHistoricMessageResponse(peer *whisper.Peer, requestID, lastEnvelopeHash statusproto.Hash, cursor []byte) error {
|
||||
func (s *WMailServer) sendHistoricMessageResponse(peer *whisper.Peer, requestID, lastEnvelopeHash protocol.Hash, cursor []byte) error {
|
||||
payload := whisper.CreateMailServerRequestCompletedPayload(common.Hash(requestID), common.Hash(lastEnvelopeHash), cursor)
|
||||
return s.w.SendHistoricMessageResponse(peer, payload)
|
||||
}
|
||||
|
||||
// this method doesn't return an error because it is already in the error handling chain
|
||||
func (s *WMailServer) trySendHistoricMessageErrorResponse(peer *whisper.Peer, requestID statusproto.Hash, errorToReport error) {
|
||||
func (s *WMailServer) trySendHistoricMessageErrorResponse(peer *whisper.Peer, requestID protocol.Hash, errorToReport error) {
|
||||
payload := whisper.CreateMailServerRequestFailedPayload(common.Hash(requestID), errorToReport)
|
||||
|
||||
err := s.w.SendHistoricMessageResponse(peer, payload)
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/status-im/status-go/params"
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
@ -90,7 +90,7 @@ func (db *LevelDB) GetEnvelope(key *DBKey) ([]byte, error) {
|
||||
func (db *LevelDB) Prune(t time.Time, batchSize int) (int, error) {
|
||||
defer recoverLevelDBPanics("Prune")
|
||||
|
||||
var zero statusproto.Hash
|
||||
var zero protocol.Hash
|
||||
var emptyTopic whispertypes.TopicType
|
||||
kl := NewDBKey(0, emptyTopic, zero)
|
||||
ku := NewDBKey(uint32(t.Unix()), emptyTopic, zero)
|
||||
@ -140,7 +140,7 @@ func (db *LevelDB) Prune(t time.Time, batchSize int) (int, error) {
|
||||
func (db *LevelDB) SaveEnvelope(env *whisper.Envelope) error {
|
||||
defer recoverLevelDBPanics("SaveEnvelope")
|
||||
|
||||
key := NewDBKey(env.Expiry-env.TTL, whispertypes.TopicType(env.Topic), statusproto.Hash(env.Hash()))
|
||||
key := NewDBKey(env.Expiry-env.TTL, whispertypes.TopicType(env.Topic), protocol.Hash(env.Hash()))
|
||||
rawEnvelope, err := rlp.EncodeToBytes(env)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))
|
||||
|
@ -15,8 +15,8 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
@ -159,7 +159,7 @@ func (i *PostgresDB) GetEnvelope(key *DBKey) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (i *PostgresDB) Prune(t time.Time, batch int) (int, error) {
|
||||
var zero statusproto.Hash
|
||||
var zero protocol.Hash
|
||||
var emptyTopic whispertypes.TopicType
|
||||
kl := NewDBKey(0, emptyTopic, zero)
|
||||
ku := NewDBKey(uint32(t.Unix()), emptyTopic, zero)
|
||||
@ -180,7 +180,7 @@ func (i *PostgresDB) Prune(t time.Time, batch int) (int, error) {
|
||||
|
||||
func (i *PostgresDB) SaveEnvelope(env *whisper.Envelope) error {
|
||||
topic := whispertypes.TopicType(env.Topic)
|
||||
key := NewDBKey(env.Expiry-env.TTL, topic, statusproto.Hash(env.Hash()))
|
||||
key := NewDBKey(env.Expiry-env.TTL, topic, protocol.Hash(env.Hash()))
|
||||
rawEnvelope, err := rlp.EncodeToBytes(env)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))
|
||||
|
@ -33,8 +33,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/status-im/status-go/params"
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
statusproto "github.com/status-im/status-protocol-go/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@ -259,7 +259,7 @@ func (s *MailserverSuite) TestArchive() {
|
||||
s.NoError(err)
|
||||
|
||||
s.server.Archive(env)
|
||||
key := NewDBKey(env.Expiry-env.TTL, whispertypes.TopicType(env.Topic), statusproto.Hash(env.Hash()))
|
||||
key := NewDBKey(env.Expiry-env.TTL, whispertypes.TopicType(env.Topic), protocol.Hash(env.Hash()))
|
||||
archivedEnvelope, err := s.server.db.GetEnvelope(key)
|
||||
s.NoError(err)
|
||||
|
||||
@ -279,7 +279,7 @@ func (s *MailserverSuite) TestManageLimits() {
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) TestDBKey() {
|
||||
var h statusproto.Hash
|
||||
var h protocol.Hash
|
||||
var emptyTopic whispertypes.TopicType
|
||||
i := uint32(time.Now().Unix())
|
||||
k := NewDBKey(i, emptyTopic, h)
|
||||
@ -307,7 +307,7 @@ func (s *MailserverSuite) TestRequestPaginationLimit() {
|
||||
env, err := generateEnvelope(sentTime)
|
||||
s.NoError(err)
|
||||
s.server.Archive(env)
|
||||
key := NewDBKey(env.Expiry-env.TTL, whispertypes.TopicType(env.Topic), statusproto.Hash(env.Hash()))
|
||||
key := NewDBKey(env.Expiry-env.TTL, whispertypes.TopicType(env.Topic), protocol.Hash(env.Hash()))
|
||||
archiveKeys = append(archiveKeys, fmt.Sprintf("%x", key.Cursor()))
|
||||
sentEnvelopes = append(sentEnvelopes, env)
|
||||
sentHashes = append(sentHashes, env.Hash())
|
||||
@ -771,7 +771,7 @@ func generateEnvelope(sentTime time.Time) (*whisper.Envelope, error) {
|
||||
|
||||
func processRequestAndCollectHashes(
|
||||
server *WMailServer, lower, upper uint32, cursor []byte, bloom []byte, limit int,
|
||||
) ([]common.Hash, []byte, statusproto.Hash) {
|
||||
) ([]common.Hash, []byte, protocol.Hash) {
|
||||
iter, _ := server.createIterator(lower, upper, cursor, nil, 0)
|
||||
defer iter.Release()
|
||||
bundles := make(chan []rlp.RawValue, 10)
|
||||
|
@ -16,11 +16,11 @@ import (
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/profiling"
|
||||
protocol "github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/services/typeddata"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
protocol "github.com/status-im/status-protocol-go"
|
||||
validator "gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/status-im/status-go/mailserver"
|
||||
"github.com/status-im/status-go/params"
|
||||
gethbridge "github.com/status-im/status-go/protocol/bridge/geth"
|
||||
"github.com/status-im/status-go/services/incentivisation"
|
||||
"github.com/status-im/status-go/services/peer"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
@ -33,7 +34,6 @@ import (
|
||||
"github.com/status-im/status-go/services/whisperbridge"
|
||||
"github.com/status-im/status-go/static"
|
||||
"github.com/status-im/status-go/timesource"
|
||||
gethbridge "github.com/status-im/status-protocol-go/bridge/geth"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
@ -316,7 +316,7 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb
|
||||
return
|
||||
}
|
||||
|
||||
// Register Whisper status-protocol-go bridge
|
||||
// Register Whisper status-go/protocol bridge
|
||||
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var whisper *whisper.Whisper
|
||||
if err := ctx.Service(&whisper); err != nil {
|
||||
|
@ -2,7 +2,7 @@ GO111MODULE = on
|
||||
|
||||
ENABLE_METRICS ?= true
|
||||
BUILD_FLAGS ?= $(shell echo "-ldflags '\
|
||||
-X github.com/status-im/status-protocol-go/vendor/github.com/ethereum/go-ethereum/metrics.EnabledStr=$(ENABLE_METRICS)'")
|
||||
-X github.com/status-im/status-go/protocol/vendor/github.com/ethereum/go-ethereum/metrics.EnabledStr=$(ENABLE_METRICS)'")
|
||||
|
||||
test:
|
||||
go test ./...
|
@ -1,4 +1,4 @@
|
||||
# status-protocol-go
|
||||
# status-go/protocol
|
||||
|
||||
This is the Status Protocol implementation in Go.
|
||||
|
31
protocol/bridge/geth/envelope.go
Normal file
31
protocol/bridge/geth/envelope.go
Normal file
@ -0,0 +1,31 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
type gethEnvelopeWrapper struct {
|
||||
envelope *whisper.Envelope
|
||||
}
|
||||
|
||||
// NewGethEnvelopeWrapper returns an object that wraps Geth's Envelope in a whispertypes interface
|
||||
func NewGethEnvelopeWrapper(e *whisper.Envelope) whispertypes.Envelope {
|
||||
return &gethEnvelopeWrapper{
|
||||
envelope: e,
|
||||
}
|
||||
}
|
||||
|
||||
// GetGethEnvelopeFrom retrieves the underlying whisper Envelope struct from a wrapped Envelope interface
|
||||
func GetGethEnvelopeFrom(f whispertypes.Envelope) *whisper.Envelope {
|
||||
return f.(*gethEnvelopeWrapper).envelope
|
||||
}
|
||||
|
||||
func (w *gethEnvelopeWrapper) Hash() protocol.Hash {
|
||||
return protocol.Hash(w.envelope.Hash())
|
||||
}
|
||||
|
||||
func (w *gethEnvelopeWrapper) Bloom() []byte {
|
||||
return w.envelope.Bloom()
|
||||
}
|
30
protocol/bridge/geth/envelope_error.go
Normal file
30
protocol/bridge/geth/envelope_error.go
Normal file
@ -0,0 +1,30 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
// NewGethEnvelopeErrorWrapper returns a whispertypes.EnvelopeError object that mimics Geth's EnvelopeError
|
||||
func NewGethEnvelopeErrorWrapper(envelopeError *whisper.EnvelopeError) *whispertypes.EnvelopeError {
|
||||
if envelopeError == nil {
|
||||
panic("envelopeError should not be nil")
|
||||
}
|
||||
|
||||
return &whispertypes.EnvelopeError{
|
||||
Hash: protocol.Hash(envelopeError.Hash),
|
||||
Code: mapGethErrorCode(envelopeError.Code),
|
||||
Description: envelopeError.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func mapGethErrorCode(code uint) uint {
|
||||
switch code {
|
||||
case whisper.EnvelopeTimeNotSynced:
|
||||
return whispertypes.EnvelopeTimeNotSynced
|
||||
case whisper.EnvelopeOtherError:
|
||||
return whispertypes.EnvelopeOtherError
|
||||
}
|
||||
return whispertypes.EnvelopeOtherError
|
||||
}
|
34
protocol/bridge/geth/envelope_event.go
Normal file
34
protocol/bridge/geth/envelope_event.go
Normal file
@ -0,0 +1,34 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
// NewGethEnvelopeEventWrapper returns a whispertypes.EnvelopeEvent object that mimics Geth's EnvelopeEvent
|
||||
func NewGethEnvelopeEventWrapper(envelopeEvent *whisper.EnvelopeEvent) *whispertypes.EnvelopeEvent {
|
||||
if envelopeEvent == nil {
|
||||
panic("envelopeEvent should not be nil")
|
||||
}
|
||||
|
||||
wrappedData := envelopeEvent.Data
|
||||
switch data := envelopeEvent.Data.(type) {
|
||||
case []whisper.EnvelopeError:
|
||||
wrappedData := make([]whispertypes.EnvelopeError, len(data))
|
||||
for index, envError := range data {
|
||||
wrappedData[index] = *NewGethEnvelopeErrorWrapper(&envError)
|
||||
}
|
||||
case *whisper.MailServerResponse:
|
||||
wrappedData = NewGethMailServerResponseWrapper(data)
|
||||
case whisper.SyncEventResponse:
|
||||
wrappedData = NewGethSyncEventResponseWrapper(data)
|
||||
}
|
||||
return &whispertypes.EnvelopeEvent{
|
||||
Event: whispertypes.EventType(envelopeEvent.Event),
|
||||
Hash: protocol.Hash(envelopeEvent.Hash),
|
||||
Batch: protocol.Hash(envelopeEvent.Batch),
|
||||
Peer: whispertypes.EnodeID(envelopeEvent.Peer),
|
||||
Data: wrappedData,
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
20
protocol/bridge/geth/mailserver_response.go
Normal file
20
protocol/bridge/geth/mailserver_response.go
Normal file
@ -0,0 +1,20 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
// NewGethMailServerResponseWrapper returns a whispertypes.MailServerResponse object that mimics Geth's MailServerResponse
|
||||
func NewGethMailServerResponseWrapper(mailServerResponse *whisper.MailServerResponse) *whispertypes.MailServerResponse {
|
||||
if mailServerResponse == nil {
|
||||
panic("mailServerResponse should not be nil")
|
||||
}
|
||||
|
||||
return &whispertypes.MailServerResponse{
|
||||
LastEnvelopeHash: protocol.Hash(mailServerResponse.LastEnvelopeHash),
|
||||
Cursor: mailServerResponse.Cursor,
|
||||
Error: mailServerResponse.Error,
|
||||
}
|
||||
}
|
103
protocol/bridge/geth/public_whisper_api.go
Normal file
103
protocol/bridge/geth/public_whisper_api.go
Normal file
@ -0,0 +1,103 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
type gethPublicWhisperAPIWrapper struct {
|
||||
publicWhisperAPI *whisper.PublicWhisperAPI
|
||||
}
|
||||
|
||||
// NewGethPublicWhisperAPIWrapper returns an object that wraps Geth's PublicWhisperAPI in a whispertypes interface
|
||||
func NewGethPublicWhisperAPIWrapper(publicWhisperAPI *whisper.PublicWhisperAPI) whispertypes.PublicWhisperAPI {
|
||||
if publicWhisperAPI == nil {
|
||||
panic("publicWhisperAPI cannot be nil")
|
||||
}
|
||||
|
||||
return &gethPublicWhisperAPIWrapper{
|
||||
publicWhisperAPI: publicWhisperAPI,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPrivateKey imports the given private key.
|
||||
func (w *gethPublicWhisperAPIWrapper) AddPrivateKey(ctx context.Context, privateKey protocol.HexBytes) (string, error) {
|
||||
return w.publicWhisperAPI.AddPrivateKey(ctx, hexutil.Bytes(privateKey))
|
||||
}
|
||||
|
||||
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
|
||||
func (w *gethPublicWhisperAPIWrapper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
|
||||
return w.publicWhisperAPI.GenerateSymKeyFromPassword(ctx, passwd)
|
||||
}
|
||||
|
||||
// DeleteKeyPair removes the key with the given key if it exists.
|
||||
func (w *gethPublicWhisperAPIWrapper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
|
||||
return w.publicWhisperAPI.DeleteKeyPair(ctx, key)
|
||||
}
|
||||
|
||||
// NewMessageFilter creates a new filter that can be used to poll for
|
||||
// (new) messages that satisfy the given criteria.
|
||||
func (w *gethPublicWhisperAPIWrapper) NewMessageFilter(req whispertypes.Criteria) (string, error) {
|
||||
topics := make([]whisper.TopicType, len(req.Topics))
|
||||
for index, tt := range req.Topics {
|
||||
topics[index] = whisper.TopicType(tt)
|
||||
}
|
||||
|
||||
criteria := whisper.Criteria{
|
||||
SymKeyID: req.SymKeyID,
|
||||
PrivateKeyID: req.PrivateKeyID,
|
||||
Sig: req.Sig,
|
||||
MinPow: req.MinPow,
|
||||
Topics: topics,
|
||||
AllowP2P: req.AllowP2P,
|
||||
}
|
||||
return w.publicWhisperAPI.NewMessageFilter(criteria)
|
||||
}
|
||||
|
||||
// GetFilterMessages returns the messages that match the filter criteria and
|
||||
// are received between the last poll and now.
|
||||
func (w *gethPublicWhisperAPIWrapper) GetFilterMessages(id string) ([]*whispertypes.Message, error) {
|
||||
msgs, err := w.publicWhisperAPI.GetFilterMessages(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedMsgs := make([]*whispertypes.Message, len(msgs))
|
||||
for index, msg := range msgs {
|
||||
wrappedMsgs[index] = &whispertypes.Message{
|
||||
Sig: msg.Sig,
|
||||
TTL: msg.TTL,
|
||||
Timestamp: msg.Timestamp,
|
||||
Topic: whispertypes.TopicType(msg.Topic),
|
||||
Payload: msg.Payload,
|
||||
Padding: msg.Padding,
|
||||
PoW: msg.PoW,
|
||||
Hash: msg.Hash,
|
||||
Dst: msg.Dst,
|
||||
P2P: msg.P2P,
|
||||
}
|
||||
}
|
||||
return wrappedMsgs, nil
|
||||
}
|
||||
|
||||
// Post posts a message on the Whisper network.
|
||||
// returns the hash of the message in case of success.
|
||||
func (w *gethPublicWhisperAPIWrapper) Post(ctx context.Context, req whispertypes.NewMessage) ([]byte, error) {
|
||||
msg := whisper.NewMessage{
|
||||
SymKeyID: req.SymKeyID,
|
||||
PublicKey: req.PublicKey,
|
||||
Sig: req.SigID, // Sig is really a SigID
|
||||
TTL: req.TTL,
|
||||
Topic: whisper.TopicType(req.Topic),
|
||||
Payload: req.Payload,
|
||||
Padding: req.Padding,
|
||||
PowTime: req.PowTime,
|
||||
PowTarget: req.PowTarget,
|
||||
TargetPeer: req.TargetPeer,
|
||||
}
|
||||
return w.publicWhisperAPI.Post(ctx, msg)
|
||||
}
|
@ -2,7 +2,7 @@ package gethbridge
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
)
|
||||
|
||||
type gethSubscriptionWrapper struct {
|
@ -1,7 +1,7 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gethbridge
|
||||
|
||||
import (
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"time"
|
||||
|
||||
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
16
protocol/bridge/nimbus/cfuncs.go
Normal file
16
protocol/bridge/nimbus/cfuncs.go
Normal file
@ -0,0 +1,16 @@
|
||||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
/*
|
||||
|
||||
#include <libnimbus.h>
|
||||
|
||||
// onMessageHandler gateway function
|
||||
void onMessageHandler_cgo(received_message * msg, void* udata)
|
||||
{
|
||||
void onMessageHandler(received_message* msg, void* udata);
|
||||
onMessageHandler(msg, udata);
|
||||
}
|
||||
*/
|
||||
import "C"
|
61
protocol/bridge/nimbus/filter.go
Normal file
61
protocol/bridge/nimbus/filter.go
Normal file
@ -0,0 +1,61 @@
|
||||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
)
|
||||
|
||||
type nimbusFilterWrapper struct {
|
||||
filter *C.filter_options
|
||||
id string
|
||||
own bool
|
||||
}
|
||||
|
||||
// NewNimbusFilterWrapper returns an object that wraps Nimbus's Filter in a whispertypes interface
|
||||
func NewNimbusFilterWrapper(f *C.filter_options, id string, own bool) whispertypes.Filter {
|
||||
wrapper := &nimbusFilterWrapper{
|
||||
filter: f,
|
||||
id: id,
|
||||
own: own,
|
||||
}
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// GetNimbusFilterFrom retrieves the underlying whisper Filter struct from a wrapped Filter interface
|
||||
func GetNimbusFilterFrom(f whispertypes.Filter) *C.filter_options {
|
||||
return f.(*nimbusFilterWrapper).filter
|
||||
}
|
||||
|
||||
// ID returns the filter ID
|
||||
func (w *nimbusFilterWrapper) ID() string {
|
||||
return w.id
|
||||
}
|
||||
|
||||
// Free frees the C memory associated with the filter
|
||||
func (w *nimbusFilterWrapper) Free() {
|
||||
if !w.own {
|
||||
panic("native filter is not owned by Go")
|
||||
}
|
||||
|
||||
if w.filter.privateKeyID != nil {
|
||||
C.free(unsafe.Pointer(w.filter.privateKeyID))
|
||||
w.filter.privateKeyID = nil
|
||||
}
|
||||
if w.filter.symKeyID != nil {
|
||||
C.free(unsafe.Pointer(w.filter.symKeyID))
|
||||
w.filter.symKeyID = nil
|
||||
}
|
||||
}
|
44
protocol/bridge/nimbus/node.go
Normal file
44
protocol/bridge/nimbus/node.go
Normal file
@ -0,0 +1,44 @@
|
||||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func StartNimbus(privateKey *ecdsa.PrivateKey, listenAddr string, staging bool) error {
|
||||
C.NimMain()
|
||||
|
||||
port, err := strconv.Atoi(strings.Split(listenAddr, ":")[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse port number from %s", listenAddr)
|
||||
}
|
||||
|
||||
privateKeyC := C.CBytes(crypto.FromECDSA(privateKey))
|
||||
defer C.free(privateKeyC)
|
||||
if !C.nimbus_start(C.ushort(port), true, false, 0.002, (*C.uchar)(privateKeyC), C.bool(staging)) {
|
||||
return errors.New("failed to start Nimbus node")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
213
protocol/bridge/nimbus/public_whisper_api.go
Normal file
213
protocol/bridge/nimbus/public_whisper_api.go
Normal file
@ -0,0 +1,213 @@
|
||||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
type nimbusPublicWhisperAPIWrapper struct {
|
||||
filterMessagesMu *sync.Mutex
|
||||
filterMessages *map[string]*list.List
|
||||
routineQueue *RoutineQueue
|
||||
}
|
||||
|
||||
// NewNimbusPublicWhisperAPIWrapper returns an object that wraps Nimbus's PublicWhisperAPI in a whispertypes interface
|
||||
func NewNimbusPublicWhisperAPIWrapper(filterMessagesMu *sync.Mutex, filterMessages *map[string]*list.List, routineQueue *RoutineQueue) whispertypes.PublicWhisperAPI {
|
||||
return &nimbusPublicWhisperAPIWrapper{
|
||||
filterMessagesMu: filterMessagesMu,
|
||||
filterMessages: filterMessages,
|
||||
routineQueue: routineQueue,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPrivateKey imports the given private key.
|
||||
func (w *nimbusPublicWhisperAPIWrapper) AddPrivateKey(ctx context.Context, privateKey protocol.HexBytes) (string, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
privKeyC := C.CBytes(privateKey)
|
||||
defer C.free(unsafe.Pointer(privKeyC))
|
||||
|
||||
idC := C.malloc(C.size_t(C.ID_LEN))
|
||||
defer C.free(idC)
|
||||
if C.nimbus_add_keypair((*C.uchar)(privKeyC), (*C.uchar)(idC)) {
|
||||
c <- protocol.EncodeHex(C.GoBytes(idC, C.ID_LEN))
|
||||
} else {
|
||||
c <- errors.New("failed to add private key to Nimbus")
|
||||
}
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return retVal.(string), nil
|
||||
}
|
||||
|
||||
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
|
||||
func (w *nimbusPublicWhisperAPIWrapper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
passwordC := C.CString(passwd)
|
||||
defer C.free(unsafe.Pointer(passwordC))
|
||||
|
||||
idC := C.malloc(C.size_t(C.ID_LEN))
|
||||
defer C.free(idC)
|
||||
if C.nimbus_add_symkey_from_password(passwordC, (*C.uchar)(idC)) {
|
||||
c <- protocol.EncodeHex(C.GoBytes(idC, C.ID_LEN))
|
||||
} else {
|
||||
c <- errors.New("failed to add symkey to Nimbus")
|
||||
}
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return retVal.(string), nil
|
||||
}
|
||||
|
||||
// DeleteKeyPair removes the key with the given key if it exists.
|
||||
func (w *nimbusPublicWhisperAPIWrapper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
keyC, err := decodeHexID(key)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(keyC))
|
||||
|
||||
c <- C.nimbus_delete_keypair(keyC)
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return retVal.(bool), nil
|
||||
}
|
||||
|
||||
// NewMessageFilter creates a new filter that can be used to poll for
|
||||
// (new) messages that satisfy the given criteria.
|
||||
func (w *nimbusPublicWhisperAPIWrapper) NewMessageFilter(req whispertypes.Criteria) (string, error) {
|
||||
// topics := make([]whisper.TopicType, len(req.Topics))
|
||||
// for index, tt := range req.Topics {
|
||||
// topics[index] = whisper.TopicType(tt)
|
||||
// }
|
||||
|
||||
// criteria := whisper.Criteria{
|
||||
// SymKeyID: req.SymKeyID,
|
||||
// PrivateKeyID: req.PrivateKeyID,
|
||||
// Sig: req.Sig,
|
||||
// MinPow: req.MinPow,
|
||||
// Topics: topics,
|
||||
// AllowP2P: req.AllowP2P,
|
||||
// }
|
||||
// return w.publicWhisperAPI.NewMessageFilter(criteria)
|
||||
// TODO
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
// GetFilterMessages returns the messages that match the filter criteria and
|
||||
// are received between the last poll and now.
|
||||
func (w *nimbusPublicWhisperAPIWrapper) GetFilterMessages(id string) ([]*whispertypes.Message, error) {
|
||||
idC := C.CString(id)
|
||||
defer C.free(unsafe.Pointer(idC))
|
||||
|
||||
var (
|
||||
messageList *list.List
|
||||
ok bool
|
||||
)
|
||||
w.filterMessagesMu.Lock()
|
||||
defer w.filterMessagesMu.Unlock()
|
||||
if messageList, ok = (*w.filterMessages)[id]; !ok {
|
||||
return nil, fmt.Errorf("no filter with ID %s", id)
|
||||
}
|
||||
|
||||
retVal := make([]*whispertypes.Message, messageList.Len())
|
||||
if messageList.Len() == 0 {
|
||||
return retVal, nil
|
||||
}
|
||||
|
||||
elem := messageList.Front()
|
||||
index := 0
|
||||
for elem != nil {
|
||||
retVal[index] = (elem.Value).(*whispertypes.Message)
|
||||
index++
|
||||
next := elem.Next()
|
||||
messageList.Remove(elem)
|
||||
elem = next
|
||||
}
|
||||
return retVal, nil
|
||||
}
|
||||
|
||||
// Post posts a message on the Whisper network.
|
||||
// returns the hash of the message in case of success.
|
||||
func (w *nimbusPublicWhisperAPIWrapper) Post(ctx context.Context, req whispertypes.NewMessage) ([]byte, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
msg := C.post_message{
|
||||
ttl: C.uint32_t(req.TTL),
|
||||
powTime: C.double(req.PowTime),
|
||||
powTarget: C.double(req.PowTarget),
|
||||
}
|
||||
if req.SigID != "" {
|
||||
sourceID, err := decodeHexID(req.SigID)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
msg.sourceID = sourceID
|
||||
defer C.free(unsafe.Pointer(sourceID))
|
||||
}
|
||||
if req.SymKeyID != "" {
|
||||
symKeyID, err := decodeHexID(req.SymKeyID)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
msg.symKeyID = symKeyID
|
||||
defer C.free(unsafe.Pointer(symKeyID))
|
||||
}
|
||||
if req.PublicKey != nil && len(req.PublicKey) > 0 {
|
||||
msg.pubKey = (*C.uchar)(C.CBytes(req.PublicKey))
|
||||
defer C.free(unsafe.Pointer(msg.pubKey))
|
||||
}
|
||||
msg.payloadLen = C.size_t(len(req.Payload))
|
||||
msg.payload = (*C.uchar)(C.CBytes(req.Payload))
|
||||
defer C.free(unsafe.Pointer(msg.payload))
|
||||
msg.paddingLen = C.size_t(len(req.Padding))
|
||||
msg.padding = (*C.uchar)(C.CBytes(req.Padding))
|
||||
defer C.free(unsafe.Pointer(msg.padding))
|
||||
copyTopicToCBuffer(&msg.topic[0], req.Topic[:])
|
||||
|
||||
// TODO: return envelope hash once nimbus_post is improved to return it
|
||||
if C.nimbus_post(&msg) {
|
||||
c <- make([]byte, 0)
|
||||
return
|
||||
}
|
||||
c <- fmt.Errorf("failed to post message symkeyid=%s pubkey=%#x topic=%#x", req.SymKeyID, req.PublicKey, req.Topic[:])
|
||||
// hashC := C.nimbus_post(&msg)
|
||||
// if hashC == nil {
|
||||
// return nil, errors.New("Nimbus failed to post message")
|
||||
// }
|
||||
// return hex.DecodeString(C.GoString(hashC))
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return retVal.([]byte), nil
|
||||
}
|
55
protocol/bridge/nimbus/routine_queue.go
Normal file
55
protocol/bridge/nimbus/routine_queue.go
Normal file
@ -0,0 +1,55 @@
|
||||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// RoutineQueue provides a mechanism for marshalling function calls
|
||||
// so that they are run in a specific thread (the thread where
|
||||
// RoutineQueue is initialized).
|
||||
type RoutineQueue struct {
|
||||
tid int
|
||||
events chan event
|
||||
}
|
||||
|
||||
// NewRoutineQueue returns a new RoutineQueue object.
|
||||
func NewRoutineQueue() *RoutineQueue {
|
||||
q := &RoutineQueue{
|
||||
tid: syscall.Gettid(),
|
||||
events: make(chan event, 20),
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// event represents an event triggered by the user.
|
||||
type event struct {
|
||||
f func(chan<- interface{})
|
||||
done chan interface{}
|
||||
}
|
||||
|
||||
func (q *RoutineQueue) HandleEvent() {
|
||||
select {
|
||||
case ev := <-q.events:
|
||||
ev.f(ev.done)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Send executes the passed function. This method can be called safely from a
|
||||
// goroutine in order to execute a Nimbus function. It is important to note that the
|
||||
// passed function won't be executed immediately, instead it will be added to
|
||||
// the user events queue.
|
||||
func (q *RoutineQueue) Send(f func(chan<- interface{})) interface{} {
|
||||
ev := event{f: f, done: make(chan interface{}, 1)}
|
||||
defer close(ev.done)
|
||||
if syscall.Gettid() == q.tid {
|
||||
f(ev.done)
|
||||
return <-ev.done
|
||||
}
|
||||
q.events <- ev
|
||||
return <-ev.done
|
||||
}
|
436
protocol/bridge/nimbus/whisper.go
Normal file
436
protocol/bridge/nimbus/whisper.go
Normal file
@ -0,0 +1,436 @@
|
||||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR} -lnimbus -lpcre -lm
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
void onMessageHandler_cgo(received_message * msg, void* udata); // Forward declaration.
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
gopointer "github.com/mattn/go-pointer"
|
||||
whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
type nimbusWhisperWrapper struct {
|
||||
timesource func() time.Time
|
||||
filters map[string]whispertypes.Filter
|
||||
filterMessagesMu sync.Mutex
|
||||
filterMessages map[string]*list.List
|
||||
routineQueue *RoutineQueue
|
||||
tid int
|
||||
}
|
||||
|
||||
// NewNimbusWhisperWrapper returns an object that wraps Nimbus' Whisper in a whispertypes interface
|
||||
func NewNimbusWhisperWrapper() whispertypes.Whisper {
|
||||
return &nimbusWhisperWrapper{
|
||||
timesource: func() time.Time { return time.Now() },
|
||||
filters: map[string]whispertypes.Filter{},
|
||||
filterMessages: map[string]*list.List{},
|
||||
routineQueue: NewRoutineQueue(),
|
||||
tid: syscall.Gettid(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) PublicWhisperAPI() whispertypes.PublicWhisperAPI {
|
||||
return NewNimbusPublicWhisperAPIWrapper(&w.filterMessagesMu, &w.filterMessages, w.routineQueue)
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) Poll() {
|
||||
if syscall.Gettid() != w.tid {
|
||||
panic("Poll called from wrong thread")
|
||||
}
|
||||
|
||||
C.nimbus_poll()
|
||||
|
||||
w.routineQueue.HandleEvent()
|
||||
}
|
||||
|
||||
// MinPow returns the PoW value required by this node.
|
||||
func (w *nimbusWhisperWrapper) MinPow() float64 {
|
||||
return w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
c <- float64(C.nimbus_get_min_pow())
|
||||
}).(float64)
|
||||
}
|
||||
|
||||
// BloomFilter returns the aggregated bloom filter for all the topics of interest.
|
||||
// The nodes are required to send only messages that match the advertised bloom filter.
|
||||
// If a message does not match the bloom, it will tantamount to spam, and the peer will
|
||||
// be disconnected.
|
||||
func (w *nimbusWhisperWrapper) BloomFilter() []byte {
|
||||
return w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
// Allocate a buffer for Nimbus to return the bloom filter on
|
||||
dataC := C.malloc(C.size_t(C.BLOOM_LEN))
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
|
||||
C.nimbus_get_bloom_filter((*C.uchar)(unsafe.Pointer(dataC)))
|
||||
|
||||
// Move the returned data into a Go array
|
||||
data := make([]byte, C.BLOOM_LEN)
|
||||
copy(data, C.GoBytes(dataC, C.BLOOM_LEN))
|
||||
c <- data
|
||||
}).([]byte)
|
||||
}
|
||||
|
||||
// GetCurrentTime returns current time.
|
||||
func (w *nimbusWhisperWrapper) GetCurrentTime() time.Time {
|
||||
return w.timesource()
|
||||
}
|
||||
|
||||
// SetTimeSource assigns a particular source of time to a whisper object.
|
||||
func (w *nimbusWhisperWrapper) SetTimeSource(timesource func() time.Time) {
|
||||
w.timesource = timesource
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) SubscribeEnvelopeEvents(eventsProxy chan<- whispertypes.EnvelopeEvent) whispertypes.Subscription {
|
||||
// TODO: when mailserver support is implemented
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// SelectedKeyPairID returns the id of currently selected key pair.
|
||||
// It helps distinguish between different users w/o exposing the user identity itself.
|
||||
func (w *nimbusWhisperWrapper) SelectedKeyPairID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
idC, err := decodeHexID(id)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(idC))
|
||||
privKeyC := C.malloc(whispertypes.AesKeyLength)
|
||||
defer C.free(unsafe.Pointer(privKeyC))
|
||||
|
||||
if ok := C.nimbus_get_private_key(idC, (*C.uchar)(unsafe.Pointer(privKeyC))); !ok {
|
||||
c <- errors.New("failed to get private key from Nimbus")
|
||||
return
|
||||
}
|
||||
|
||||
pk, err := crypto.ToECDSA(C.GoBytes(privKeyC, C.PRIVKEY_LEN))
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
|
||||
c <- pk
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return retVal.(*ecdsa.PrivateKey), nil
|
||||
}
|
||||
|
||||
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
|
||||
func (w *nimbusWhisperWrapper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
privKey := crypto.FromECDSA(key)
|
||||
privKeyC := C.CBytes(privKey)
|
||||
defer C.free(unsafe.Pointer(privKeyC))
|
||||
|
||||
idC := C.malloc(C.size_t(C.ID_LEN))
|
||||
defer C.free(idC)
|
||||
if !C.nimbus_add_keypair((*C.uchar)(unsafe.Pointer(privKeyC)), (*C.uchar)(idC)) {
|
||||
c <- errors.New("failed to add keypair to Nimbus")
|
||||
return
|
||||
}
|
||||
|
||||
c <- protocol.EncodeHex(C.GoBytes(idC, C.ID_LEN))
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return retVal.(string), nil
|
||||
}
|
||||
|
||||
// DeleteKeyPair deletes the specified key if it exists.
|
||||
func (w *nimbusWhisperWrapper) DeleteKeyPair(key string) bool {
|
||||
return w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
keyC, err := decodeHexID(key)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(keyC))
|
||||
|
||||
c <- C.nimbus_delete_keypair(keyC)
|
||||
}).(bool)
|
||||
}
|
||||
|
||||
// SelectKeyPair adds cryptographic identity, and makes sure
|
||||
// that it is the only private key known to the node.
|
||||
func (w *nimbusWhisperWrapper) SelectKeyPair(key *ecdsa.PrivateKey) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) AddSymKeyDirect(key []byte) (string, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
keyC := C.CBytes(key)
|
||||
defer C.free(unsafe.Pointer(keyC))
|
||||
|
||||
idC := C.malloc(C.size_t(C.ID_LEN))
|
||||
defer C.free(idC)
|
||||
if !C.nimbus_add_symkey((*C.uchar)(unsafe.Pointer(keyC)), (*C.uchar)(idC)) {
|
||||
c <- errors.New("failed to add symkey to Nimbus")
|
||||
return
|
||||
}
|
||||
|
||||
c <- protocol.EncodeHex(C.GoBytes(idC, C.ID_LEN))
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return retVal.(string), nil
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) AddSymKeyFromPassword(password string) (string, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
passwordC := C.CString(password)
|
||||
defer C.free(unsafe.Pointer(passwordC))
|
||||
|
||||
idC := C.malloc(C.size_t(C.ID_LEN))
|
||||
defer C.free(idC)
|
||||
if C.nimbus_add_symkey_from_password(passwordC, (*C.uchar)(idC)) {
|
||||
id := C.GoBytes(idC, C.ID_LEN)
|
||||
c <- protocol.EncodeHex(id)
|
||||
} else {
|
||||
c <- errors.New("failed to add symkey to Nimbus")
|
||||
}
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return retVal.(string), nil
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) DeleteSymKey(id string) bool {
|
||||
return w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
idC, err := decodeHexID(id)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(idC))
|
||||
|
||||
c <- C.nimbus_delete_symkey(idC)
|
||||
}).(bool)
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) GetSymKey(id string) ([]byte, error) {
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
idC, err := decodeHexID(id)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(idC))
|
||||
|
||||
// Allocate a buffer for Nimbus to return the symkey on
|
||||
dataC := C.malloc(C.size_t(C.SYMKEY_LEN))
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
if ok := C.nimbus_get_symkey(idC, (*C.uchar)(unsafe.Pointer(dataC))); !ok {
|
||||
c <- errors.New("symkey not found")
|
||||
return
|
||||
}
|
||||
|
||||
c <- C.GoBytes(dataC, C.SYMKEY_LEN)
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return retVal.([]byte), nil
|
||||
}
|
||||
|
||||
//export onMessageHandler
|
||||
func onMessageHandler(msg *C.received_message, udata unsafe.Pointer) {
|
||||
messageList := (gopointer.Restore(udata)).(*list.List)
|
||||
|
||||
topic := whispertypes.TopicType{}
|
||||
copy(topic[:], C.GoBytes(unsafe.Pointer(&msg.topic[0]), whispertypes.TopicLength)[:whispertypes.TopicLength])
|
||||
wrappedMsg := &whispertypes.Message{
|
||||
TTL: uint32(msg.ttl),
|
||||
Timestamp: uint32(msg.timestamp),
|
||||
Topic: topic,
|
||||
Payload: C.GoBytes(unsafe.Pointer(msg.decoded), C.int(msg.decodedLen)),
|
||||
PoW: float64(msg.pow),
|
||||
Hash: C.GoBytes(unsafe.Pointer(&msg.hash[0]), protocol.HashLength),
|
||||
P2P: true,
|
||||
}
|
||||
if msg.source != nil {
|
||||
wrappedMsg.Sig = append([]byte{0x04}, C.GoBytes(unsafe.Pointer(msg.source), whispertypes.PubKeyLength)...)
|
||||
}
|
||||
if msg.recipientPublicKey != nil {
|
||||
wrappedMsg.Dst = append([]byte{0x04}, C.GoBytes(unsafe.Pointer(msg.recipientPublicKey), whispertypes.PubKeyLength)...)
|
||||
}
|
||||
|
||||
messageList.PushBack(wrappedMsg)
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) Subscribe(opts *whispertypes.SubscriptionOptions) (string, error) {
|
||||
f, err := w.createFilterWrapper("", opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
retVal := w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
// Create a message store for this filter, so we can add new messages to it from the nimbus_subscribe_filter callback
|
||||
messageList := list.New()
|
||||
idC := C.malloc(C.size_t(C.ID_LEN))
|
||||
defer C.free(idC)
|
||||
if !C.nimbus_subscribe_filter(
|
||||
GetNimbusFilterFrom(f),
|
||||
(C.received_msg_handler)(unsafe.Pointer(C.onMessageHandler_cgo)), gopointer.Save(messageList),
|
||||
(*C.uchar)(idC)) {
|
||||
c <- errors.New("failed to subscribe to filter in Nimbus")
|
||||
return
|
||||
}
|
||||
filterID := C.GoString((*C.char)(idC))
|
||||
|
||||
w.filterMessagesMu.Lock()
|
||||
w.filterMessages[filterID] = messageList // TODO: Check if this is done too late (race condition with onMessageHandler)
|
||||
w.filterMessagesMu.Unlock()
|
||||
|
||||
f.(*nimbusFilterWrapper).id = filterID
|
||||
|
||||
c <- filterID
|
||||
})
|
||||
if err, ok := retVal.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return retVal.(string), nil
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) GetFilter(id string) whispertypes.Filter {
|
||||
idC := C.CString(id)
|
||||
defer C.free(unsafe.Pointer(idC))
|
||||
|
||||
panic("GetFilter not implemented")
|
||||
// pFilter := C.nimbus_get_filter(idC)
|
||||
// return NewNimbusFilterWrapper(pFilter, id, false)
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) Unsubscribe(id string) error {
|
||||
return w.routineQueue.Send(func(c chan<- interface{}) {
|
||||
idC, err := decodeHexID(id)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(idC))
|
||||
|
||||
if ok := C.nimbus_unsubscribe_filter(idC); !ok {
|
||||
c <- errors.New("filter not found")
|
||||
return
|
||||
}
|
||||
|
||||
w.filterMessagesMu.Lock()
|
||||
if messageList, ok := w.filterMessages[id]; ok {
|
||||
gopointer.Unref(gopointer.Save(messageList))
|
||||
delete(w.filterMessages, id)
|
||||
}
|
||||
w.filterMessagesMu.Unlock()
|
||||
|
||||
if f, ok := w.filters[id]; ok {
|
||||
f.(*nimbusFilterWrapper).Free()
|
||||
delete(w.filters, id)
|
||||
}
|
||||
|
||||
c <- nil
|
||||
}).(error)
|
||||
}
|
||||
|
||||
func decodeHexID(id string) (*C.uint8_t, error) {
|
||||
idBytes, err := protocol.DecodeHex(id)
|
||||
if err == nil && len(idBytes) != C.ID_LEN {
|
||||
err = fmt.Errorf("ID length must be %v bytes, actual length is %v", C.ID_LEN, len(idBytes))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*C.uint8_t)(C.CBytes(idBytes)), nil
|
||||
}
|
||||
|
||||
// copyTopicToCBuffer copies a Go topic buffer to a C topic buffer without allocating new memory
|
||||
func copyTopicToCBuffer(dst *C.uchar, topic []byte) {
|
||||
if len(topic) != whispertypes.TopicLength {
|
||||
panic("invalid Whisper topic buffer size")
|
||||
}
|
||||
|
||||
p := (*[whispertypes.TopicLength]C.uchar)(unsafe.Pointer(dst))
|
||||
for index, b := range topic {
|
||||
p[index] = C.uchar(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) createFilterWrapper(id string, opts *whispertypes.SubscriptionOptions) (whispertypes.Filter, error) {
|
||||
if len(opts.Topics) != 1 {
|
||||
return nil, errors.New("currently only 1 topic is supported by the Nimbus bridge")
|
||||
}
|
||||
|
||||
filter := C.filter_options{
|
||||
minPow: C.double(opts.PoW),
|
||||
allowP2P: C.int(1),
|
||||
}
|
||||
copyTopicToCBuffer(&filter.topic[0], opts.Topics[0])
|
||||
if opts.PrivateKeyID != "" {
|
||||
if idC, err := decodeHexID(opts.PrivateKeyID); err == nil {
|
||||
filter.privateKeyID = idC
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opts.SymKeyID != "" {
|
||||
if idC, err := decodeHexID(opts.SymKeyID); err == nil {
|
||||
filter.symKeyID = idC
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewNimbusFilterWrapper(&filter, id, true), nil
|
||||
}
|
||||
|
||||
func (w *nimbusWhisperWrapper) SendMessagesRequest(peerID []byte, r whispertypes.MessagesRequest) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
|
||||
// which is known to implement MailServer interface, and is supposed to process this
|
||||
// request and respond with a number of peer-to-peer messages (possibly expired),
|
||||
// which are not supposed to be forwarded any further.
|
||||
// The whisper protocol is agnostic of the format and contents of envelope.
|
||||
func (w *nimbusWhisperWrapper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope whispertypes.Envelope, timeout time.Duration) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// SyncMessages can be sent between two Mail Servers and syncs envelopes between them.
|
||||
func (w *nimbusWhisperWrapper) SyncMessages(peerID []byte, req whispertypes.SyncMailRequest) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
219
protocol/chat.go
Normal file
219
protocol/chat.go
Normal file
@ -0,0 +1,219 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
v1protocol "github.com/status-im/status-go/protocol/v1"
|
||||
)
|
||||
|
||||
type ChatType int
|
||||
|
||||
const (
|
||||
ChatTypeOneToOne ChatType = iota + 1
|
||||
ChatTypePublic
|
||||
ChatTypePrivateGroupChat
|
||||
)
|
||||
|
||||
type Chat struct {
|
||||
// ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one
|
||||
// is the hex encoded public key and for group chats is a random uuid appended with
|
||||
// the hex encoded pk of the creator of the chat
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
// Active indicates whether the chat has been soft deleted
|
||||
Active bool `json:"active"`
|
||||
|
||||
ChatType ChatType `json:"chatType"`
|
||||
|
||||
// Only filled for one to one chats
|
||||
PublicKey *ecdsa.PublicKey `json:"-"`
|
||||
|
||||
// Timestamp indicates the last time this chat has received/sent a message
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
// LastClockValue indicates the last clock value to be used when sending messages
|
||||
LastClockValue uint64 `json:"lastClockValue"`
|
||||
// DeletedAtClockValue indicates the clock value at time of deletion, messages
|
||||
// with lower clock value of this should be discarded
|
||||
DeletedAtClockValue uint64 `json:"deletedAtClockValue"`
|
||||
|
||||
// Denormalized fields
|
||||
UnviewedMessagesCount uint `json:"unviewedMessagesCount"`
|
||||
LastMessageContentType string `json:"lastMessageContentType"`
|
||||
LastMessageContent string `json:"lastMessageContent"`
|
||||
LastMessageTimestamp int64 `json:"lastMessageTimestamp"`
|
||||
LastMessageClockValue int64 `json:"lastMessageClockValue"`
|
||||
|
||||
// Group chat fields
|
||||
// Members are the members who have been invited to the group chat
|
||||
Members []ChatMember `json:"members"`
|
||||
// MembershipUpdates is all the membership events in the chat
|
||||
MembershipUpdates []ChatMembershipUpdate `json:"membershipUpdates"`
|
||||
}
|
||||
|
||||
func (c *Chat) MembersAsPublicKeys() ([]*ecdsa.PublicKey, error) {
|
||||
publicKeys := make([]string, len(c.Members))
|
||||
for idx, item := range c.Members {
|
||||
publicKeys[idx] = item.ID
|
||||
}
|
||||
return stringSliceToPublicKeys(publicKeys, true)
|
||||
}
|
||||
|
||||
func (c *Chat) updateChatFromProtocolGroup(g *v1protocol.Group) {
|
||||
// ID
|
||||
c.ID = g.ChatID()
|
||||
|
||||
// Name
|
||||
c.Name = g.Name()
|
||||
|
||||
// Members
|
||||
members := g.Members()
|
||||
admins := g.Admins()
|
||||
joined := g.Joined()
|
||||
chatMembers := make([]ChatMember, 0, len(members))
|
||||
for _, m := range members {
|
||||
chatMember := ChatMember{
|
||||
ID: m,
|
||||
}
|
||||
chatMember.Admin = stringSliceContains(admins, m)
|
||||
chatMember.Joined = stringSliceContains(joined, m)
|
||||
chatMembers = append(chatMembers, chatMember)
|
||||
}
|
||||
c.Members = chatMembers
|
||||
|
||||
// MembershipUpdates
|
||||
updates := g.Updates()
|
||||
membershipUpdates := make([]ChatMembershipUpdate, 0, len(updates))
|
||||
for _, update := range updates {
|
||||
membershipUpdate := ChatMembershipUpdate{
|
||||
Type: update.Type,
|
||||
Name: update.Name,
|
||||
ClockValue: uint64(update.ClockValue), // TODO: get rid of type casting
|
||||
Signature: update.Signature,
|
||||
From: update.From,
|
||||
Member: update.Member,
|
||||
Members: update.Members,
|
||||
}
|
||||
membershipUpdate.setID()
|
||||
membershipUpdates = append(membershipUpdates, membershipUpdate)
|
||||
}
|
||||
c.MembershipUpdates = membershipUpdates
|
||||
}
|
||||
|
||||
// ChatMembershipUpdate represent an event on membership of the chat
|
||||
type ChatMembershipUpdate struct {
|
||||
// Unique identifier for the event
|
||||
ID string `json:"id"`
|
||||
// Type indicates the kind of event (i.e changed-name, added-member, etc)
|
||||
Type string `json:"type"`
|
||||
// Name represents the name in the event of changing name events
|
||||
Name string `json:"name,omitempty"`
|
||||
// Clock value of the event
|
||||
ClockValue uint64 `json:"clockValue"`
|
||||
// Signature of the event
|
||||
Signature string `json:"signature"`
|
||||
// Hex encoded public key of the creator of the event
|
||||
From string `json:"from"`
|
||||
// Target of the event for single-target events
|
||||
Member string `json:"member,omitempty"`
|
||||
// Target of the event for multi-target events
|
||||
Members []string `json:"members,omitempty"`
|
||||
}
|
||||
|
||||
func (u *ChatMembershipUpdate) setID() {
|
||||
sum := sha1.Sum([]byte(u.Signature))
|
||||
u.ID = hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// ChatMember represents a member who participates in a group chat
|
||||
type ChatMember struct {
|
||||
// ID is the hex encoded public key of the member
|
||||
ID string `json:"id"`
|
||||
// Admin indicates if the member is an admin of the group chat
|
||||
Admin bool `json:"admin"`
|
||||
// Joined indicates if the member has joined the group chat
|
||||
Joined bool `json:"joined"`
|
||||
}
|
||||
|
||||
func (c ChatMember) PublicKey() (*ecdsa.PublicKey, error) {
|
||||
b, err := protocol.DecodeHex(c.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypto.UnmarshalPubkey(b)
|
||||
}
|
||||
|
||||
func oneToOneChatID(publicKey *ecdsa.PublicKey) string {
|
||||
return protocol.EncodeHex(crypto.FromECDSAPub(publicKey))
|
||||
}
|
||||
|
||||
func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey) Chat {
|
||||
return Chat{
|
||||
ID: oneToOneChatID(publicKey),
|
||||
Name: name,
|
||||
Active: true,
|
||||
ChatType: ChatTypeOneToOne,
|
||||
PublicKey: publicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func CreatePublicChat(name string) Chat {
|
||||
return Chat{
|
||||
ID: name,
|
||||
Name: name,
|
||||
Active: true,
|
||||
ChatType: ChatTypePublic,
|
||||
}
|
||||
}
|
||||
|
||||
func createGroupChat() Chat {
|
||||
return Chat{
|
||||
Active: true,
|
||||
ChatType: ChatTypePrivateGroupChat,
|
||||
}
|
||||
}
|
||||
|
||||
func findChatByID(chatID string, chats []*Chat) *Chat {
|
||||
for _, c := range chats {
|
||||
if c.ID == chatID {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringSliceToPublicKeys(slice []string, prefixed bool) ([]*ecdsa.PublicKey, error) {
|
||||
result := make([]*ecdsa.PublicKey, len(slice))
|
||||
for idx, item := range slice {
|
||||
var (
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
if prefixed {
|
||||
b, err = protocol.DecodeHex(item)
|
||||
} else {
|
||||
b, err = hex.DecodeString(item)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[idx], err = crypto.UnmarshalPubkey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func stringSliceContains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
28
protocol/chat_group_proxy.go
Normal file
28
protocol/chat_group_proxy.go
Normal file
@ -0,0 +1,28 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
v1protocol "github.com/status-im/status-go/protocol/v1"
|
||||
)
|
||||
|
||||
func newProtocolGroupFromChat(chat *Chat) (*v1protocol.Group, error) {
|
||||
return v1protocol.NewGroup(chat.ID, chatToFlattenMembershipUpdate(chat))
|
||||
}
|
||||
|
||||
func chatToFlattenMembershipUpdate(chat *Chat) []v1protocol.MembershipUpdateFlat {
|
||||
result := make([]v1protocol.MembershipUpdateFlat, len(chat.MembershipUpdates))
|
||||
for idx, update := range chat.MembershipUpdates {
|
||||
result[idx] = v1protocol.MembershipUpdateFlat{
|
||||
ChatID: chat.ID,
|
||||
From: update.From,
|
||||
Signature: update.Signature,
|
||||
MembershipUpdateEvent: v1protocol.MembershipUpdateEvent{
|
||||
Name: update.Name,
|
||||
Type: update.Type,
|
||||
ClockValue: int64(update.ClockValue), // TODO: remove type difference
|
||||
Member: update.Member,
|
||||
Members: update.Members,
|
||||
},
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
108
protocol/contact.go
Normal file
108
protocol/contact.go
Normal file
@ -0,0 +1,108 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/protocol/identity/alias"
|
||||
"github.com/status-im/status-go/protocol/identity/identicon"
|
||||
protocol "github.com/status-im/status-go/protocol/types"
|
||||
)
|
||||
|
||||
const (
|
||||
contactBlocked = "contact/blocked"
|
||||
contactAdded = "contact/added"
|
||||
contactRequestReceived = "contact/request-received"
|
||||
)
|
||||
|
||||
// ContactDeviceInfo is a struct containing information about a particular device owned by a contact
|
||||
type ContactDeviceInfo struct {
|
||||
// The installation id of the device
|
||||
InstallationID string `json:"id"`
|
||||
// Timestamp represents the last time we received this info
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
// FCMToken is to be used for push notifications
|
||||
FCMToken string `json:"fcmToken"`
|
||||
}
|
||||
|
||||
// Contact has information about a "Contact". A contact is not necessarily one
|
||||
// that we added or added us, that's based on SystemTags.
|
||||
type Contact struct {
|
||||
// ID of the contact. It's a hex-encoded public key (prefixed with 0x).
|
||||
ID string `json:"id"`
|
||||
// Ethereum address of the contact
|
||||
Address string `json:"address"`
|
||||
// ENS name of contact
|
||||
Name string `json:"name,omitempty"`
|
||||
// EnsVerified whether we verified the name of the contact
|
||||
ENSVerified bool `json:"ensVerified"`
|
||||
// EnsVerifiedAt the time we last verified the name
|
||||
ENSVerifiedAt int64 `json:"ensVerifiedAt"`
|
||||
// Generated username name of the contact
|
||||
Alias string `json:"alias,omitempty"`
|
||||
// Identicon generated from public key
|
||||
Identicon string `json:"identicon"`
|
||||
// Photo is the base64 encoded photo
|
||||
Photo string `json:"photoPath,omitempty"`
|
||||
// LastUpdated is the last time we received an update from the contact
|
||||
// updates should be discarded if last updated is less than the one stored
|
||||
LastUpdated int64 `json:"lastUpdated"`
|
||||
// SystemTags contains information about whether we blocked/added/have been
|
||||
// added.
|
||||
SystemTags []string `json:"systemTags"`
|
||||
|
||||
DeviceInfo []ContactDeviceInfo `json:"deviceInfo"`
|
||||
TributeToTalk string `json:"tributeToTalk"`
|
||||
}
|
||||
|
||||
func (c Contact) PublicKey() (*ecdsa.PublicKey, error) {
|
||||
b, err := protocol.DecodeHex(c.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypto.UnmarshalPubkey(b)
|
||||
}
|
||||
|
||||
func (c Contact) IsAdded() bool {
|
||||
return existsInStringSlice(c.SystemTags, contactAdded)
|
||||
}
|
||||
|
||||
func (c Contact) HasBeenAdded() bool {
|
||||
return existsInStringSlice(c.SystemTags, contactRequestReceived)
|
||||
}
|
||||
|
||||
func (c Contact) IsBlocked() bool {
|
||||
return existsInStringSlice(c.SystemTags, contactBlocked)
|
||||
}
|
||||
|
||||
// existsInStringSlice checks if a string is in a set.
|
||||
func existsInStringSlice(set []string, find string) bool {
|
||||
for _, s := range set {
|
||||
if s == find {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildContact(publicKey *ecdsa.PublicKey) (*Contact, error) {
|
||||
address := strings.ToLower(crypto.PubkeyToAddress(*publicKey).Hex())
|
||||
|
||||
id := "0x" + hex.EncodeToString(crypto.FromECDSAPub(publicKey))
|
||||
|
||||
identicon, err := identicon.GenerateBase64(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contact := &Contact{
|
||||
ID: id,
|
||||
Address: address[2:],
|
||||
Alias: alias.GenerateFromPublicKey(publicKey),
|
||||
Identicon: identicon,
|
||||
}
|
||||
|
||||
return contact, nil
|
||||
}
|
134
protocol/crypto/crypto_test.go
Normal file
134
protocol/crypto/crypto_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractSignatures(t *testing.T) {
|
||||
const content1 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35441"
|
||||
const content2 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35440"
|
||||
|
||||
key1, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
key2, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
signature1, err := Sign(content1, key1)
|
||||
require.NoError(t, err)
|
||||
|
||||
signature2, err := Sign(content2, key2)
|
||||
require.NoError(t, err)
|
||||
|
||||
key1String := hex.EncodeToString(crypto.FromECDSAPub(&key1.PublicKey))
|
||||
key2String := hex.EncodeToString(crypto.FromECDSAPub(&key2.PublicKey))
|
||||
|
||||
pair1 := [2]string{content1, signature1}
|
||||
pair2 := [2]string{content2, signature2}
|
||||
|
||||
signaturePairs := [][2]string{pair1, pair2}
|
||||
|
||||
extractedSignatures, err := ExtractSignatures(signaturePairs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{key1String, key2String}, extractedSignatures)
|
||||
|
||||
// Test wrong content
|
||||
pair3 := [2]string{content1, signature2}
|
||||
|
||||
signaturePairs = [][2]string{pair1, pair2, pair3}
|
||||
|
||||
extractedSignatures, err = ExtractSignatures(signaturePairs)
|
||||
require.NoError(t, err)
|
||||
// The public key is neither the one which generated the content, nor the one generated the signature
|
||||
require.NotEqual(t, []string{key1String, key2String, key1String}, extractedSignatures)
|
||||
require.NotEqual(t, []string{key1String, key2String, key2String}, extractedSignatures)
|
||||
}
|
||||
|
||||
func TestVerifySignature(t *testing.T) {
|
||||
const content1 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35441"
|
||||
const content2 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35440"
|
||||
|
||||
key1, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
key2, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
signature1, err := Sign(content1, key1)
|
||||
require.NoError(t, err)
|
||||
|
||||
signature2, err := Sign(content2, key2)
|
||||
require.NoError(t, err)
|
||||
|
||||
key1String := hex.EncodeToString(crypto.FromECDSAPub(&key1.PublicKey))
|
||||
key2String := hex.EncodeToString(crypto.FromECDSAPub(&key2.PublicKey))
|
||||
|
||||
pair1 := [3]string{content1, signature1, key1String}
|
||||
pair2 := [3]string{content2, signature2, key2String}
|
||||
|
||||
signaturePairs := [][3]string{pair1, pair2}
|
||||
|
||||
err = VerifySignatures(signaturePairs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test wrong content
|
||||
pair3 := [3]string{content1, signature2, key2String}
|
||||
|
||||
signaturePairs = [][3]string{pair1, pair2, pair3}
|
||||
|
||||
err = VerifySignatures(signaturePairs)
|
||||
require.Error(t, err)
|
||||
|
||||
// Test wrong signature
|
||||
pair3 = [3]string{content1, signature2, key1String}
|
||||
|
||||
signaturePairs = [][3]string{pair1, pair2, pair3}
|
||||
|
||||
err = VerifySignatures(signaturePairs)
|
||||
require.Error(t, err)
|
||||
|
||||
// Test wrong pubkey
|
||||
pair3 = [3]string{content1, signature1, key2String}
|
||||
|
||||
signaturePairs = [][3]string{pair1, pair2, pair3}
|
||||
|
||||
err = VerifySignatures(signaturePairs)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSymmetricEncryption(t *testing.T) {
|
||||
const rawKey = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
expectedPlaintext := []byte("test")
|
||||
key, err := hex.DecodeString(rawKey)
|
||||
require.Nil(t, err, "Key should be generated without errors")
|
||||
|
||||
cyphertext1, err := EncryptSymmetric(key, expectedPlaintext)
|
||||
require.Nil(t, err, "Cyphertext should be generated without errors")
|
||||
|
||||
cyphertext2, err := EncryptSymmetric(key, expectedPlaintext)
|
||||
require.Nil(t, err, "Cyphertext should be generated without errors")
|
||||
|
||||
require.Equalf(
|
||||
t,
|
||||
32,
|
||||
len(cyphertext1),
|
||||
"Cyphertext with the correct length should be generated")
|
||||
|
||||
require.NotEqualf(
|
||||
t,
|
||||
cyphertext1,
|
||||
cyphertext2,
|
||||
"Same plaintext should not be encrypted in the same way")
|
||||
|
||||
plaintext, err := DecryptSymmetric(key, cyphertext1)
|
||||
require.Nil(t, err, "Cyphertext should be decrypted without errors")
|
||||
|
||||
require.Equalf(
|
||||
t,
|
||||
expectedPlaintext,
|
||||
plaintext,
|
||||
"Cypther text should be decrypted successfully")
|
||||
}
|
@ -4,7 +4,7 @@ import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
datasyncpeer "github.com/status-im/status-protocol-go/datasync/peer"
|
||||
datasyncpeer "github.com/status-im/status-go/protocol/datasync/peer"
|
||||
datasyncnode "github.com/vacp2p/mvds/node"
|
||||
datasyncproto "github.com/vacp2p/mvds/protobuf"
|
||||
datasynctransport "github.com/vacp2p/mvds/transport"
|
@ -5,7 +5,7 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"github.com/golang/protobuf/proto"
|
||||
datasyncpeer "github.com/status-im/status-protocol-go/datasync/peer"
|
||||
datasyncpeer "github.com/status-im/status-go/protocol/datasync/peer"
|
||||
"github.com/vacp2p/mvds/protobuf"
|
||||
"github.com/vacp2p/mvds/state"
|
||||
"github.com/vacp2p/mvds/transport"
|
351
protocol/encryption/encryption_multi_device_test.go
Normal file
351
protocol/encryption/encryption_multi_device_test.go
Normal file
@ -0,0 +1,351 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
|
||||
)
|
||||
|
||||
const (
|
||||
aliceUser = "alice"
|
||||
bobUser = "bob"
|
||||
)
|
||||
|
||||
func TestEncryptionServiceMultiDeviceSuite(t *testing.T) {
|
||||
suite.Run(t, new(EncryptionServiceMultiDeviceSuite))
|
||||
}
|
||||
|
||||
type serviceAndKey struct {
|
||||
services []*Protocol
|
||||
key *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
type EncryptionServiceMultiDeviceSuite struct {
|
||||
suite.Suite
|
||||
services map[string]*serviceAndKey
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func setupUser(user string, s *EncryptionServiceMultiDeviceSuite, n int) error {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.services[user] = &serviceAndKey{
|
||||
key: key,
|
||||
services: make([]*Protocol, n),
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
installationID := fmt.Sprintf("%s%d", user, i+1)
|
||||
dbFileName := fmt.Sprintf("sqlite-persistence-test-%s.sql", installationID)
|
||||
dbPath, err := ioutil.TempFile("", dbFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := sqlite.Open(dbPath.Name(), "some-key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protocol := New(
|
||||
db,
|
||||
installationID,
|
||||
func(s []*multidevice.Installation) {},
|
||||
func(s []*sharedsecret.Secret) {},
|
||||
func(*ProtocolMessageSpec) {},
|
||||
s.logger.With(zap.String("user", user)),
|
||||
)
|
||||
s.services[user].services[i] = protocol
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
|
||||
s.logger = tt.MustCreateTestLogger()
|
||||
|
||||
s.services = make(map[string]*serviceAndKey)
|
||||
err := setupUser(aliceUser, s, 4)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = setupUser(bobUser, s, 4)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceMultiDeviceSuite) TearDownTest() {
|
||||
_ = s.logger.Sync()
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
|
||||
aliceKey := s.services[aliceUser].key
|
||||
|
||||
alice2Bundle, err := s.services[aliceUser].services[1].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice2IdentityPK, err := ExtractIdentity(alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice2Identity := fmt.Sprintf("0x%x", crypto.FromECDSAPub(alice2IdentityPK))
|
||||
|
||||
alice3Bundle, err := s.services[aliceUser].services[2].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice3IdentityPK, err := ExtractIdentity(alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice3Identity := fmt.Sprintf("0x%x", crypto.FromECDSAPub(alice3IdentityPK))
|
||||
|
||||
// Add alice2 bundle
|
||||
response, err := s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(multidevice.Installation{
|
||||
Identity: alice2Identity,
|
||||
Version: 1,
|
||||
ID: "alice2",
|
||||
}, *response[0])
|
||||
|
||||
// Add alice3 bundle
|
||||
response, err = s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice3Bundle)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(multidevice.Installation{
|
||||
Identity: alice3Identity,
|
||||
Version: 1,
|
||||
ID: "alice3",
|
||||
}, *response[0])
|
||||
|
||||
// No installation is enabled
|
||||
alice1MergedBundle1, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(1, len(alice1MergedBundle1.GetSignedPreKeys()))
|
||||
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"])
|
||||
|
||||
// We enable the installations
|
||||
err = s.services[aliceUser].services[0].EnableInstallation(&aliceKey.PublicKey, "alice2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.services[aliceUser].services[0].EnableInstallation(&aliceKey.PublicKey, "alice3")
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice1MergedBundle2, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We get back a bundle with all the installations
|
||||
s.Require().Equal(3, len(alice1MergedBundle2.GetSignedPreKeys()))
|
||||
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice1"])
|
||||
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice2"])
|
||||
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice3"])
|
||||
|
||||
// We disable the installations
|
||||
err = s.services[aliceUser].services[0].DisableInstallation(&aliceKey.PublicKey, "alice2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice1MergedBundle3, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We get back a bundle with all the installations
|
||||
s.Require().Equal(2, len(alice1MergedBundle3.GetSignedPreKeys()))
|
||||
s.Require().NotNil(alice1MergedBundle3.GetSignedPreKeys()["alice1"])
|
||||
s.Require().NotNil(alice1MergedBundle3.GetSignedPreKeys()["alice3"])
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundleOutOfOrder() {
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice1 creates a bundle
|
||||
alice1Bundle, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice2 Receives the bundle
|
||||
_, err = s.services[aliceUser].services[1].ProcessPublicBundle(aliceKey, alice1Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice2 Creates a Bundle
|
||||
_, err = s.services[aliceUser].services[1].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We enable the installation
|
||||
err = s.services[aliceUser].services[1].EnableInstallation(&aliceKey.PublicKey, "alice1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// It should contain both bundles
|
||||
alice2MergedBundle1, err := s.services[aliceUser].services[1].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().NotNil(alice2MergedBundle1.GetSignedPreKeys()["alice1"])
|
||||
s.Require().NotNil(alice2MergedBundle1.GetSignedPreKeys()["alice2"])
|
||||
}
|
||||
|
||||
func pairDevices(s *serviceAndKey, target int) error {
|
||||
device := s.services[target]
|
||||
for i := 0; i < len(s.services); i++ {
|
||||
b, err := s.services[i].GetBundle(s.key)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = device.ProcessPublicBundle(s.key, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = device.EnableInstallation(&s.key.PublicKey, s.services[i].encryptor.config.InstallationID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceMultiDeviceSuite) TestMaxDevices() {
|
||||
err := pairDevices(s.services[aliceUser], 0)
|
||||
s.Require().NoError(err)
|
||||
alice1 := s.services[aliceUser].services[0]
|
||||
bob1 := s.services[bobUser].services[0]
|
||||
aliceKey := s.services[aliceUser].key
|
||||
bobKey := s.services[bobUser].key
|
||||
|
||||
// Check bundle is ok
|
||||
// No installation is enabled
|
||||
aliceBundle, err := alice1.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check all installations are correctly working, and that the oldest device is not there
|
||||
preKeys := aliceBundle.GetSignedPreKeys()
|
||||
s.Require().Equal(3, len(preKeys))
|
||||
s.Require().NotNil(preKeys["alice1"])
|
||||
// alice2 being the oldest device is rotated out, as we reached the maximum
|
||||
s.Require().Nil(preKeys["alice2"])
|
||||
s.Require().NotNil(preKeys["alice3"])
|
||||
s.Require().NotNil(preKeys["alice4"])
|
||||
|
||||
// We propagate this to bob
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message to alice
|
||||
msg, err := bob1.BuildDirectMessage(bobKey, &aliceKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
payload := msg.Message.GetDirectMessage()
|
||||
s.Require().Equal(3, len(payload))
|
||||
s.Require().NotNil(payload["alice1"])
|
||||
s.Require().NotNil(payload["alice3"])
|
||||
s.Require().NotNil(payload["alice4"])
|
||||
|
||||
// We disable the last installation
|
||||
err = s.services[aliceUser].services[0].DisableInstallation(&aliceKey.PublicKey, "alice4")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We check the bundle is updated
|
||||
aliceBundle, err = alice1.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check all installations are there
|
||||
preKeys = aliceBundle.GetSignedPreKeys()
|
||||
s.Require().Equal(3, len(preKeys))
|
||||
s.Require().NotNil(preKeys["alice1"])
|
||||
s.Require().NotNil(preKeys["alice2"])
|
||||
s.Require().NotNil(preKeys["alice3"])
|
||||
// alice4 is disabled at this point, alice2 is back in
|
||||
s.Require().Nil(preKeys["alice4"])
|
||||
|
||||
// We propagate this to bob
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message to alice
|
||||
msg, err = bob1.BuildDirectMessage(bobKey, &aliceKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
payload = msg.Message.GetDirectMessage()
|
||||
s.Require().Equal(3, len(payload))
|
||||
s.Require().NotNil(payload["alice1"])
|
||||
s.Require().NotNil(payload["alice2"])
|
||||
s.Require().NotNil(payload["alice3"])
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceMultiDeviceSuite) TestMaxDevicesRefreshedBundle() {
|
||||
alice1 := s.services[aliceUser].services[0]
|
||||
alice2 := s.services[aliceUser].services[1]
|
||||
alice3 := s.services[aliceUser].services[2]
|
||||
alice4 := s.services[aliceUser].services[3]
|
||||
bob1 := s.services[bobUser].services[0]
|
||||
bobKey := s.services[bobUser].key
|
||||
aliceKey := s.services[aliceUser].key
|
||||
|
||||
// We create alice bundles, in order
|
||||
alice1Bundle, err := alice1.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice2Bundle, err := alice2.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice3Bundle, err := alice3.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice4Bundle, err := alice4.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send all the bundles to bob
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, alice1Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, alice3Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, alice4Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message to alice
|
||||
msg1, err := bob1.BuildDirectMessage(bobKey, &aliceKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
payload := msg1.Message.GetDirectMessage()
|
||||
s.Require().Equal(3, len(payload))
|
||||
// Alice1 is the oldest bundle and is rotated out
|
||||
// as we send maximum to 3 devices
|
||||
s.Require().Nil(payload["alice1"])
|
||||
s.Require().NotNil(payload["alice2"])
|
||||
s.Require().NotNil(payload["alice3"])
|
||||
s.Require().NotNil(payload["alice4"])
|
||||
|
||||
// We send a message to bob from alice1, the timestamp should be refreshed
|
||||
msg2, err := alice1.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice1Bundle = msg2.Message.GetBundles()[0]
|
||||
|
||||
// Bob processes the bundle
|
||||
_, err = bob1.ProcessPublicBundle(bobKey, alice1Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message to alice
|
||||
msg3, err := bob1.BuildDirectMessage(bobKey, &aliceKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
payload = msg3.Message.GetDirectMessage()
|
||||
s.Require().Equal(3, len(payload))
|
||||
// Alice 1 is added back to the list of active devices
|
||||
s.Require().NotNil(payload["alice1"])
|
||||
// Alice 2 is rotated out as the oldest device in terms of activity
|
||||
s.Require().Nil(payload["alice2"])
|
||||
// Alice 3, 4 are still in
|
||||
s.Require().NotNil(payload["alice3"])
|
||||
s.Require().NotNil(payload["alice4"])
|
||||
}
|
985
protocol/encryption/encryption_test.go
Normal file
985
protocol/encryption/encryption_test.go
Normal file
@ -0,0 +1,985 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
|
||||
)
|
||||
|
||||
var cleartext = []byte("hello")
|
||||
var aliceInstallationID = "1"
|
||||
var bobInstallationID = "2"
|
||||
var defaultMessageID = []byte("default")
|
||||
|
||||
func TestEncryptionServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(EncryptionServiceTestSuite))
|
||||
}
|
||||
|
||||
type EncryptionServiceTestSuite struct {
|
||||
suite.Suite
|
||||
logger *zap.Logger
|
||||
alice *Protocol
|
||||
bob *Protocol
|
||||
aliceDBPath *os.File
|
||||
bobDBPath *os.File
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) initDatabases(config encryptorConfig) {
|
||||
var err error
|
||||
|
||||
s.aliceDBPath, err = ioutil.TempFile("", "alice.db.sql")
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.bobDBPath, err = ioutil.TempFile("", "bob.db.sql")
|
||||
s.Require().NoError(err)
|
||||
|
||||
db, err := sqlite.Open(s.aliceDBPath.Name(), "alice-key")
|
||||
s.Require().NoError(err)
|
||||
config.InstallationID = aliceInstallationID
|
||||
s.alice = NewWithEncryptorConfig(
|
||||
db,
|
||||
aliceInstallationID,
|
||||
config,
|
||||
func(s []*multidevice.Installation) {},
|
||||
func(s []*sharedsecret.Secret) {},
|
||||
func(*ProtocolMessageSpec) {},
|
||||
s.logger.With(zap.String("user", "alice")),
|
||||
)
|
||||
|
||||
db, err = sqlite.Open(s.bobDBPath.Name(), "bob-key")
|
||||
s.Require().NoError(err)
|
||||
config.InstallationID = bobInstallationID
|
||||
s.bob = NewWithEncryptorConfig(
|
||||
db,
|
||||
bobInstallationID,
|
||||
config,
|
||||
func(s []*multidevice.Installation) {},
|
||||
func(s []*sharedsecret.Secret) {},
|
||||
func(*ProtocolMessageSpec) {},
|
||||
s.logger.With(zap.String("user", "bob")),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) SetupTest() {
|
||||
s.logger = zap.NewNop()
|
||||
s.initDatabases(defaultEncryptorConfig("none", s.logger))
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TearDownTest() {
|
||||
os.Remove(s.aliceDBPath.Name())
|
||||
os.Remove(s.bobDBPath.Name())
|
||||
_ = s.logger.Sync()
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestGetBundle() {
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
aliceBundle1, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
s.NotNil(aliceBundle1, "It creates a bundle")
|
||||
|
||||
aliceBundle2, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(aliceBundle1.GetSignedPreKeys(), aliceBundle2.GetSignedPreKeys(), "It returns the same signed pre keys")
|
||||
s.NotEqual(aliceBundle1.Timestamp, aliceBundle2.Timestamp, "It refreshes the timestamp")
|
||||
}
|
||||
|
||||
// Alice sends Bob an encrypted message with DH using an ephemeral key
|
||||
// and Bob's identity key.
|
||||
// Bob is able to decrypt it.
|
||||
// Alice does not re-use the symmetric key
|
||||
func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() {
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
response1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext)
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse1 := response1.Message.GetDirectMessage()
|
||||
|
||||
installationResponse1 := encryptionResponse1["none"]
|
||||
// That's for any device
|
||||
s.Require().NotNil(installationResponse1)
|
||||
|
||||
cyphertext1 := installationResponse1.Payload
|
||||
ephemeralKey1 := installationResponse1.GetDHHeader().GetKey()
|
||||
s.NotNil(ephemeralKey1, "It generates an ephemeral key for DH exchange")
|
||||
s.NotNil(cyphertext1, "It generates an encrypted payload")
|
||||
s.NotEqual(cyphertext1, cleartext, "It encrypts the payload correctly")
|
||||
|
||||
// On the receiver side, we should be able to decrypt using our private key and the ephemeral just sent
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using DH")
|
||||
|
||||
// The next message will not be re-using the same key
|
||||
response2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext)
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse2 := response2.Message.GetDirectMessage()
|
||||
|
||||
installationResponse2 := encryptionResponse2[aliceInstallationID]
|
||||
|
||||
cyphertext2 := installationResponse2.GetPayload()
|
||||
ephemeralKey2 := installationResponse2.GetDHHeader().GetKey()
|
||||
s.NotEqual(cyphertext1, cyphertext2, "It does not re-use the symmetric key")
|
||||
s.NotEqual(ephemeralKey1, ephemeralKey2, "It does not re-use the ephemeral key")
|
||||
|
||||
decryptedPayload2, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(cleartext, decryptedPayload2, "It correctly decrypts the payload using DH")
|
||||
}
|
||||
|
||||
// Alice has Bob's bundle
|
||||
// Alice sends Bob an encrypted message with X3DH and DR using an ephemeral key
|
||||
// and Bob's bundle.
|
||||
func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() {
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send a message using the bundle
|
||||
response1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext)
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse1 := response1.Message.GetDirectMessage()
|
||||
|
||||
installationResponse1 := encryptionResponse1[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse1)
|
||||
|
||||
cyphertext1 := installationResponse1.GetPayload()
|
||||
x3dhHeader := installationResponse1.GetX3DHHeader()
|
||||
drHeader := installationResponse1.GetDRHeader()
|
||||
|
||||
s.NotNil(cyphertext1, "It generates an encrypted payload")
|
||||
s.NotEqual(cyphertext1, cleartext, "It encrypts the payload correctly")
|
||||
|
||||
// Check X3DH Header
|
||||
bundleID := bobBundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()
|
||||
|
||||
s.NotNil(x3dhHeader, "It adds an x3dh header")
|
||||
s.NotNil(x3dhHeader.GetKey(), "It adds an ephemeral key")
|
||||
s.Equal(x3dhHeader.GetId(), bundleID, "It sets the bundle id")
|
||||
|
||||
// Check DR Header
|
||||
s.NotNil(drHeader, "It adds a DR header")
|
||||
s.NotNil(drHeader.GetKey(), "It adds a key to the DR header")
|
||||
s.Equal(bundleID, drHeader.GetId(), "It adds the bundle id")
|
||||
s.Equal(uint32(0), drHeader.GetN(), "It adds the correct message number")
|
||||
s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain")
|
||||
|
||||
// Bob is able to decrypt it using the bundle
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using X3DH")
|
||||
}
|
||||
|
||||
// Alice has Bob's bundle
|
||||
// Alice sends Bob 2 encrypted messages with X3DH and DR using an ephemeral key
|
||||
// and Bob's bundle.
|
||||
// Alice sends another message. This message should be using a DR
|
||||
// and should include the initial x3dh message
|
||||
// Bob receives only the last one, he should be able to decrypt it
|
||||
// nolint: megacheck
|
||||
func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() {
|
||||
cleartext1 := []byte("message 1")
|
||||
cleartext2 := []byte("message 2")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send a message using the bundle
|
||||
_, err = s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send another message using the bundle
|
||||
response, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext2)
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse := response.Message.GetDirectMessage()
|
||||
|
||||
installationResponse := encryptionResponse[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse)
|
||||
|
||||
cyphertext1 := installationResponse.GetPayload()
|
||||
x3dhHeader := installationResponse.GetX3DHHeader()
|
||||
drHeader := installationResponse.GetDRHeader()
|
||||
|
||||
s.NotNil(cyphertext1, "It generates an encrypted payload")
|
||||
s.NotEqual(cyphertext1, cleartext2, "It encrypts the payload correctly")
|
||||
|
||||
// Check X3DH Header
|
||||
bundleID := bobBundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()
|
||||
|
||||
s.NotNil(x3dhHeader, "It adds an x3dh header")
|
||||
s.NotNil(x3dhHeader.GetKey(), "It adds an ephemeral key")
|
||||
s.Equal(x3dhHeader.GetId(), bundleID, "It sets the bundle id")
|
||||
|
||||
// Check DR Header
|
||||
s.NotNil(drHeader, "It adds a DR header")
|
||||
s.NotNil(drHeader.GetKey(), "It adds a key to the DR header")
|
||||
s.Equal(bundleID, drHeader.GetId(), "It adds the bundle id")
|
||||
|
||||
s.Equal(uint32(1), drHeader.GetN(), "It adds the correct message number")
|
||||
s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain")
|
||||
|
||||
// Bob is able to decrypt it using the bundle
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH")
|
||||
}
|
||||
|
||||
// Alice has Bob's bundle
|
||||
// Alice sends Bob an encrypted message with X3DH using an ephemeral key
|
||||
// and Bob's bundle.
|
||||
// Bob's receives the message
|
||||
// Bob replies to the message
|
||||
// Alice replies to the message
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestConversation() {
|
||||
cleartext1 := []byte("message 1")
|
||||
cleartext2 := []byte("message 2")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
response, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob replies to the message
|
||||
response, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, cleartext1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send another message using the bundle
|
||||
response, err = s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext2)
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse := response.Message.GetDirectMessage()
|
||||
|
||||
installationResponse := encryptionResponse[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse)
|
||||
|
||||
cyphertext1 := installationResponse.GetPayload()
|
||||
x3dhHeader := installationResponse.GetX3DHHeader()
|
||||
drHeader := installationResponse.GetDRHeader()
|
||||
|
||||
s.NotNil(cyphertext1, "It generates an encrypted payload")
|
||||
s.NotEqual(cyphertext1, cleartext2, "It encrypts the payload correctly")
|
||||
|
||||
// It does not send the x3dh bundle
|
||||
s.Nil(x3dhHeader, "It does not add an x3dh header")
|
||||
|
||||
// Check DR Header
|
||||
bundleID := bobBundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()
|
||||
|
||||
s.NotNil(drHeader, "It adds a DR header")
|
||||
s.NotNil(drHeader.GetKey(), "It adds a key to the DR header")
|
||||
s.Equal(bundleID, drHeader.GetId(), "It adds the bundle id")
|
||||
|
||||
s.Equal(uint32(0), drHeader.GetN(), "It adds the correct message number")
|
||||
s.Equal(uint32(1), drHeader.GetPn(), "It adds the correct length of the message chain")
|
||||
|
||||
// Bob is able to decrypt it using the bundle
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH")
|
||||
}
|
||||
|
||||
// Previous implementation allowed max maxSkip keys in the same receiving chain
|
||||
// leading to a problem whereby dropped messages would accumulate and eventually
|
||||
// we would not be able to decrypt any new message anymore.
|
||||
// Here we are testing that maxSkip only applies to *consecutive* messages, not
|
||||
// overall.
|
||||
func (s *EncryptionServiceTestSuite) TestMaxSkipKeys() {
|
||||
bobText := []byte("text")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
|
||||
for i := 0; i < s.alice.encryptor.config.MaxSkip; i++ {
|
||||
_, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
_, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage2, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message, we should have maxSkip + 1 keys in the db, but
|
||||
// we should not throw an error
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Test that an error is thrown if max skip is reached
|
||||
func (s *EncryptionServiceTestSuite) TestMaxSkipKeysError() {
|
||||
bobText := []byte("text")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
|
||||
for i := 0; i < s.alice.encryptor.config.MaxSkip+1; i++ {
|
||||
_, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, defaultMessageID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: too many messages"), err)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestMaxMessageKeysPerSession() {
|
||||
config := defaultEncryptorConfig("none", zap.NewNop())
|
||||
// Set MaxKeep and MaxSkip to an high value so it does not interfere
|
||||
config.MaxKeep = 100000
|
||||
config.MaxSkip = 100000
|
||||
|
||||
s.initDatabases(config)
|
||||
|
||||
bobText := []byte("text")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We create just enough messages so that the first key should be deleted
|
||||
|
||||
nMessages := s.alice.encryptor.config.MaxMessageKeysPerSession
|
||||
messages := make([]*ProtocolMessage, nMessages)
|
||||
for i := 0; i < nMessages; i++ {
|
||||
m, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
messages[i] = m.Message
|
||||
}
|
||||
|
||||
// Another message to trigger the deletion
|
||||
m, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, m.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We decrypt the first message, and it should fail
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[0], defaultMessageID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// We decrypt the second message, and it should be decrypted
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[1], defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestMaxKeep() {
|
||||
config := defaultEncryptorConfig("none", s.logger)
|
||||
// Set MaxMessageKeysPerSession to an high value so it does not interfere
|
||||
config.MaxMessageKeysPerSession = 100000
|
||||
|
||||
s.initDatabases(config)
|
||||
|
||||
bobText := []byte("text")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We decrypt all messages but 1 & 2
|
||||
messages := make([]*ProtocolMessage, s.alice.encryptor.config.MaxKeep)
|
||||
for i := 0; i < s.alice.encryptor.config.MaxKeep; i++ {
|
||||
m, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
messages[i] = m.Message
|
||||
s.Require().NoError(err)
|
||||
|
||||
if i != 0 && i != 1 {
|
||||
messageID := []byte(fmt.Sprintf("%d", i))
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, m.Message, messageID)
|
||||
s.Require().NoError(err)
|
||||
err = s.alice.ConfirmMessageProcessed(messageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We decrypt the first message, and it should fail, as it should have been removed
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[0], defaultMessageID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// We decrypt the second message, and it should be decrypted
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[1], defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Alice has Bob's bundle
|
||||
// Bob has Alice's bundle
|
||||
// Bob sends a message to alice
|
||||
// Alice sends a message to Bob
|
||||
// Bob receives alice message
|
||||
// Alice receives Bob message
|
||||
// Bob sends another message to alice and vice-versa.
|
||||
func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
|
||||
bobText1 := []byte("bob text 1")
|
||||
bobText2 := []byte("bob text 2")
|
||||
aliceText1 := []byte("alice text 1")
|
||||
aliceText2 := []byte("alice text 2")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, aliceText1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob replies to the message
|
||||
bobMessage2, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, aliceText2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func publish(
|
||||
e *Protocol,
|
||||
privateKey *ecdsa.PrivateKey,
|
||||
publicKey *ecdsa.PublicKey,
|
||||
errChan chan error,
|
||||
output chan *ProtocolMessage,
|
||||
) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
|
||||
// Simulate 5% of the messages dropped
|
||||
if rand.Intn(100) <= 95 {
|
||||
wg.Add(1)
|
||||
// Simulate out of order messages
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
|
||||
response, err := e.BuildDirectMessage(privateKey, publicKey, cleartext)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
output <- response.Message
|
||||
}()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
close(output)
|
||||
close(errChan)
|
||||
}
|
||||
|
||||
func receiver(
|
||||
s *Protocol,
|
||||
privateKey *ecdsa.PrivateKey,
|
||||
publicKey *ecdsa.PublicKey,
|
||||
errChan chan error,
|
||||
input chan *ProtocolMessage,
|
||||
) {
|
||||
i := 0
|
||||
|
||||
for payload := range input {
|
||||
actualCleartext, err := s.HandleMessage(privateKey, publicKey, payload, defaultMessageID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(actualCleartext, cleartext) {
|
||||
errChan <- errors.New("Decrypted value does not match")
|
||||
return
|
||||
}
|
||||
i++
|
||||
}
|
||||
close(errChan)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestRandomised() {
|
||||
|
||||
seed := time.Now().UTC().UnixNano()
|
||||
rand.Seed(seed)
|
||||
|
||||
// Print so that if it fails it can be replicated
|
||||
fmt.Printf("Starting test with seed: %x\n", seed)
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceChan := make(chan *ProtocolMessage, 100)
|
||||
bobChan := make(chan *ProtocolMessage, 100)
|
||||
|
||||
alicePublisherErrChan := make(chan error, 1)
|
||||
bobPublisherErrChan := make(chan error, 1)
|
||||
|
||||
aliceReceiverErrChan := make(chan error, 1)
|
||||
bobReceiverErrChan := make(chan error, 1)
|
||||
|
||||
// Set up alice publishe
|
||||
go publish(s.alice, aliceKey, &bobKey.PublicKey, alicePublisherErrChan, bobChan)
|
||||
// Set up bob publisher
|
||||
go publish(s.bob, bobKey, &aliceKey.PublicKey, bobPublisherErrChan, aliceChan)
|
||||
|
||||
// Set up bob receiver
|
||||
go receiver(s.bob, bobKey, &aliceKey.PublicKey, bobReceiverErrChan, bobChan)
|
||||
|
||||
// Set up alice receiver
|
||||
go receiver(s.alice, aliceKey, &bobKey.PublicKey, aliceReceiverErrChan, aliceChan)
|
||||
|
||||
aliceErr := <-alicePublisherErrChan
|
||||
s.Require().NoError(aliceErr)
|
||||
|
||||
bobErr := <-bobPublisherErrChan
|
||||
s.Require().NoError(bobErr)
|
||||
|
||||
aliceErr = <-aliceReceiverErrChan
|
||||
s.Require().NoError(aliceErr)
|
||||
|
||||
bobErr = <-bobReceiverErrChan
|
||||
s.Require().NoError(bobErr)
|
||||
}
|
||||
|
||||
// Edge cases
|
||||
|
||||
// The bundle is lost
|
||||
func (s *EncryptionServiceTestSuite) TestBundleNotExisting() {
|
||||
aliceText := []byte("alice text")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle without saving it
|
||||
bobBundleContainer, err := NewBundleContainer(bobKey, bobInstallationID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = SignBundle(bobKey, bobBundleContainer)
|
||||
s.Require().NoError(err)
|
||||
|
||||
bobBundle := bobBundleContainer.GetBundle()
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, aliceText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message, and returns a bundlenotfound error
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage.Message, defaultMessageID)
|
||||
s.Require().Error(err)
|
||||
s.Equal(errSessionNotFound, err)
|
||||
}
|
||||
|
||||
// Device is not included in the bundle
|
||||
func (s *EncryptionServiceTestSuite) TestDeviceNotIncluded() {
|
||||
bobDevice2InstallationID := "bob2"
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle without saving it
|
||||
bobBundleContainer, err := NewBundleContainer(bobKey, bobDevice2InstallationID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = SignBundle(bobKey, bobBundleContainer)
|
||||
s.Require().NoError(err)
|
||||
|
||||
bobBundle := bobBundleContainer.GetBundle()
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("does not matter"))
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message, and returns a bundlenotfound error
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage.Message, defaultMessageID)
|
||||
s.Require().Error(err)
|
||||
s.Equal(ErrDeviceNotFound, err)
|
||||
}
|
||||
|
||||
// A new bundle has been received
|
||||
func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
||||
config := defaultEncryptorConfig("none", s.logger)
|
||||
// Set up refresh interval to "always"
|
||||
config.BundleRefreshInterval = 1000
|
||||
|
||||
s.initDatabases(config)
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create bundles
|
||||
bobBundle1, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(1), bobBundle1.GetSignedPreKeys()[bobInstallationID].GetVersion())
|
||||
|
||||
// Sleep the required time so that bundle is refreshed
|
||||
time.Sleep(time.Duration(config.BundleRefreshInterval) * time.Millisecond)
|
||||
|
||||
// Create bundles
|
||||
bobBundle2, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(2), bobBundle2.GetSignedPreKeys()[bobInstallationID].GetVersion())
|
||||
|
||||
// We add the first bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
response1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("anything"))
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse1 := response1.Message.GetDirectMessage()
|
||||
|
||||
installationResponse1 := encryptionResponse1[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse1)
|
||||
|
||||
// This message is using bobBundle1
|
||||
|
||||
x3dhHeader1 := installationResponse1.GetX3DHHeader()
|
||||
s.NotNil(x3dhHeader1)
|
||||
s.Equal(bobBundle1.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey(), x3dhHeader1.GetId())
|
||||
|
||||
// Bob decrypts the message
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add the second bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
response2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("anything"))
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse2 := response2.Message.GetDirectMessage()
|
||||
|
||||
installationResponse2 := encryptionResponse2[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse2)
|
||||
|
||||
// This message is using bobBundle2
|
||||
|
||||
x3dhHeader2 := installationResponse2.GetX3DHHeader()
|
||||
s.NotNil(x3dhHeader2)
|
||||
s.Equal(bobBundle2.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey(), x3dhHeader2.GetId())
|
||||
|
||||
// Bob decrypts the message
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestMessageConfirmation() {
|
||||
bobText1 := []byte("bob text 1")
|
||||
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
_, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
bobMessage1ID := []byte("bob-message-1-id")
|
||||
|
||||
// Alice receives the message once
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, bobMessage1ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message twice
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, bobMessage1ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice confirms the message
|
||||
err = s.alice.ConfirmMessageProcessed(bobMessage1ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice decrypts it again, it should fail
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, bobMessage1ID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage2, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
bobMessage2ID := []byte("bob-message-2-id")
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage3, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
bobMessage3ID := []byte("bob-message-3-id")
|
||||
|
||||
// Alice receives message 3 once
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage3.Message, bobMessage3ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives message 3 twice
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage3.Message, bobMessage3ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives message 2 once
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, bobMessage2ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives message 2 twice
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, bobMessage2ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice confirms the messages
|
||||
err = s.alice.ConfirmMessageProcessed(bobMessage2ID)
|
||||
s.Require().NoError(err)
|
||||
err = s.alice.ConfirmMessageProcessed(bobMessage3ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice decrypts it again, it should fail
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage3.Message, bobMessage3ID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// Alice decrypts it again, it should fail
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, bobMessage2ID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
}
|
@ -13,8 +13,8 @@ import (
|
||||
dr "github.com/status-im/doubleratchet"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-protocol-go/crypto"
|
||||
"github.com/status-im/status-protocol-go/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/crypto"
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
)
|
||||
|
||||
var (
|
655
protocol/encryption/migrations/migrations.go
Normal file
655
protocol/encryption/migrations/migrations.go
Normal file
@ -0,0 +1,655 @@
|
||||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// 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)
|
||||
// 1558084410_add_secret.down.sql (56B)
|
||||
// 1558084410_add_secret.up.sql (301B)
|
||||
// 1558588866_add_version.down.sql (47B)
|
||||
// 1558588866_add_version.up.sql (57B)
|
||||
// 1559627659_add_contact_code.down.sql (32B)
|
||||
// 1559627659_add_contact_code.up.sql (198B)
|
||||
// 1561368210_add_installation_metadata.down.sql (35B)
|
||||
// 1561368210_add_installation_metadata.up.sql (267B)
|
||||
// doc.go (377B)
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var __1536754952_initial_schemaDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x4e\x2d\x2e\xce\xcc\xcf\x2b\xb6\xe6\x42\x12\x4c\x2a\xcd\x4b\xc9\x49\x45\x15\xcb\x4e\xad\x44\x15\x28\x4a\x2c\x49\xce\x48\x2d\x89\xcf\xcc\x4b\xcb\xb7\xe6\x02\x04\x00\x00\xff\xff\x72\x61\x3f\x92\x53\x00\x00\x00")
|
||||
|
||||
func _1536754952_initial_schemaDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1536754952_initial_schemaDownSql,
|
||||
"1536754952_initial_schema.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1536754952_initial_schemaDownSql() (*asset, error) {
|
||||
bytes, err := _1536754952_initial_schemaDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1536754952_initial_schemaUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x52\xc1\x8e\x9b\x30\x10\xbd\xe7\x2b\xe6\x98\x48\x39\xf4\xde\x13\xb0\x13\x84\x4a\xcd\xd6\x0b\x52\xf7\x64\x79\xe3\x69\xb0\x16\x1b\x64\x3b\xab\xe6\xef\x2b\x20\xa5\xb8\x65\xdb\xde\x98\xc7\x9b\x99\x37\xef\x39\xe3\x98\xd4\x08\x75\x92\x96\x08\x9e\xbc\xd7\xbd\xf5\xb0\xdf\x01\xa8\xd6\x41\x5a\x56\xe9\x71\xfa\xf6\x62\xb8\xbe\x74\xfa\x1c\x43\x4e\xbf\xc9\x40\x0b\xe6\xfa\x3e\x88\x73\x2b\xb5\x15\xaf\x74\x5b\x60\x4f\x56\xfd\x1d\xb6\x50\xb0\x1a\x73\xe4\xd3\x14\x3a\xbf\x6d\xd0\x57\x70\x44\xf7\x81\x86\x75\x3d\x58\x58\x97\x5a\x4d\x13\x80\x55\x35\xb0\xa6\x2c\xe1\x91\x17\x9f\x13\xfe\x0c\x9f\xf0\x79\xfc\xdf\xb0\xe2\x4b\x83\x7b\xad\x0e\x50\x31\xc8\x2a\x76\x2a\x8b\xac\x06\x8e\x8f\x65\x92\xe1\xee\xf0\x71\xb7\x8b\x3c\x7a\xa5\xdb\xec\xcf\xec\xc7\x22\x71\x59\x30\x0e\x35\xfe\x22\xec\xd5\xac\x75\x18\xf2\x5e\x5e\x68\x9b\x3f\x8b\x80\xfd\xbd\xef\xb8\x66\xff\xa7\xae\x97\xab\x55\x1d\xcd\xd2\xb4\x22\x1b\x74\xd8\x58\xa4\xad\x0f\xb2\xeb\x64\xd0\xbd\x15\x5a\x41\x8d\x5f\xeb\x88\x70\x8f\x34\x0e\x4a\x5f\x2c\x29\x31\xb8\x0d\xf5\x6b\x3b\x23\xa1\x45\xce\x2a\x8e\x63\x7b\xd0\x86\x7c\x90\x66\x80\x86\x3d\x15\x39\xc3\x07\x48\x8b\x7c\xf4\x26\xda\x4c\xdf\x07\xed\x48\x41\x5a\x55\x25\x26\x0c\x1e\xf0\x94\x34\x65\x0d\x1f\xfe\xbc\xd5\xc9\x70\x6e\x29\x08\x6d\xbf\xf5\xd3\xc1\xf3\xf1\xe2\xf7\xac\xa7\xb1\x43\x4b\x86\x9c\xec\xa2\x93\xde\x77\xc8\xdf\x8c\xa1\xe0\xde\x4b\xf6\x9f\x06\xde\xdf\xd3\xa2\xe8\xb8\xec\xda\x0c\x72\x6c\x39\x55\x1c\x8b\x9c\x4d\x16\xfe\x6a\x3c\x00\xc7\x13\x72\x64\x19\x3e\xfd\x4c\x77\x1f\x47\x71\x18\xad\xf9\x11\x00\x00\xff\xff\xa9\x50\xa8\xb2\xc2\x03\x00\x00")
|
||||
|
||||
func _1536754952_initial_schemaUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1536754952_initial_schemaUpSql,
|
||||
"1536754952_initial_schema.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1536754952_initial_schemaUpSql() (*asset, error) {
|
||||
bytes, err := _1536754952_initial_schemaUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1539249977_update_ratchet_infoDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\x41\x4b\xc4\x30\x10\x85\xef\xf9\x15\xef\xd8\xc2\x9e\xbc\xee\xa9\x8d\x53\x29\x86\x64\x8d\x29\xe8\x29\xd4\xed\xe8\x06\xdb\xec\xd2\x46\xa1\xff\x5e\x22\x52\x59\xf4\x3a\xdf\xf7\x78\x6f\x6e\xad\x39\xc0\x55\xb5\x22\xcc\x7d\x3a\x9e\x38\xf9\x10\x5f\xcf\xfe\xf3\x66\x2f\x84\xb4\x54\x39\xfa\x07\xa3\x10\xc0\xcb\x47\x1c\x46\xf6\x61\x40\xad\x4c\x0d\x6d\x1c\x74\xa7\xd4\x4e\x00\x7c\x39\xf1\xc4\x73\x3f\xfa\x77\x5e\xbf\x71\xbe\x86\x81\x63\x0a\x69\xfd\xeb\x2f\xeb\x34\x71\x9a\xc3\x71\xf3\xaf\x70\x88\x4b\xea\xc7\xb1\x4f\xe1\x1c\x73\x9f\xa3\x27\x77\x25\x74\xba\x7d\xe8\xa8\xd8\x16\xed\xb6\xae\x12\x46\x43\x1a\xdd\xa8\x56\x3a\x58\x3a\xa8\x4a\x52\x8e\x34\xc6\x52\x7b\xa7\x71\x4f\xcf\xf8\x0d\x96\xb0\xd4\x90\x25\x2d\xe9\xf1\xe7\xc1\xa5\x58\xc2\x5b\xe4\xc1\x5f\x66\xce\xf3\x4a\x51\xee\xc5\x57\x00\x00\x00\xff\xff\x69\x51\x9b\xb4\x37\x01\x00\x00")
|
||||
|
||||
func _1539249977_update_ratchet_infoDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1539249977_update_ratchet_infoDownSql,
|
||||
"1539249977_update_ratchet_info.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1539249977_update_ratchet_infoDownSql() (*asset, error) {
|
||||
bytes, err := _1539249977_update_ratchet_infoDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1539249977_update_ratchet_infoUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\x41\x4f\x84\x30\x10\x85\xef\xfd\x15\x73\x84\x84\x93\x57\x4e\x50\x07\x43\xac\xed\x5a\x4b\xa2\xa7\x06\x97\xd1\x6d\x16\xca\x86\x56\x13\xfe\xbd\xa9\x31\x28\xea\xf5\xbd\x6f\xde\x7b\x73\x8d\x02\x0d\x42\xa3\xd5\x1d\x04\x0a\xc1\xcd\x3e\x94\xec\xa7\x7a\xa6\x35\x29\x5a\x1d\xc0\x54\xb5\x40\x58\xfa\x78\x3c\x51\xb4\xce\xbf\xcc\x25\x63\x5c\x63\x65\xf0\x1f\xcf\xbe\x5f\x41\xc6\x00\x9e\xdf\xfc\x30\x92\x75\x03\xd4\x42\xd5\x20\x95\x01\xd9\x09\x51\x30\x00\xba\x9c\x68\xa2\xa5\x1f\xed\x99\xd6\x4f\x3b\xa9\x6e\x20\x1f\x5d\x5c\xff\xf2\x61\x9d\x26\x8a\x8b\x3b\x6e\xfc\xce\x76\x3e\xc4\x7e\x1c\xfb\xe8\x66\x9f\xfa\x0c\x3e\x9a\x1d\xd0\xc9\xf6\xbe\xc3\x6c\x5b\x54\x6c\x5d\xc5\xef\xe3\x1c\x94\x04\xae\x64\x23\x5a\x6e\x40\xe3\x41\x54\x1c\x53\x46\xa3\x34\xb6\x37\x12\x6e\xf1\x09\xbe\x93\x72\xd0\xd8\xa0\x46\xc9\xf1\xe1\xeb\xe3\x90\x05\xf7\xea\x69\xb0\x97\x85\xd2\xde\x9c\xe5\x25\xfb\x08\x00\x00\xff\xff\xb6\x31\x2b\x32\x70\x01\x00\x00")
|
||||
|
||||
func _1539249977_update_ratchet_infoUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1539249977_update_ratchet_infoUpSql,
|
||||
"1539249977_update_ratchet_info.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1539249977_update_ratchet_infoUpSql() (*asset, error) {
|
||||
bytes, err := _1539249977_update_ratchet_infoUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1540715431_add_versionDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\x4e\xad\x2c\x56\x70\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\x4e\x2d\x2e\xce\xcc\xcf\x8b\xcf\x4c\xb1\xe6\x42\x56\x08\x15\x47\x55\x0c\xd2\x1d\x9f\x9c\x5f\x9a\x57\x82\xaa\x38\xa9\x34\x2f\x25\x27\x15\x55\x6d\x59\x6a\x11\xc8\x00\x6b\x2e\x40\x00\x00\x00\xff\xff\xda\x5d\x80\x2d\x7f\x00\x00\x00")
|
||||
|
||||
func _1540715431_add_versionDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1540715431_add_versionDownSql,
|
||||
"1540715431_add_version.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1540715431_add_versionDownSql() (*asset, error) {
|
||||
bytes, err := _1540715431_add_versionDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1540715431_add_versionUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\xcd\xb1\x0e\x02\x21\x0c\xc6\xf1\xdd\xa7\xf8\x1e\xc1\xdd\x09\xa4\x67\x4c\x7a\x90\x90\x32\x93\xe8\x31\x5c\x54\x2e\x8a\x98\xf8\xf6\x06\xe3\xc2\xa2\xae\x6d\xff\xbf\x1a\x62\x12\xc2\xe0\xdd\x88\x53\x7a\x96\xcd\x4a\xb1\x90\x87\x28\xcd\xf4\x9e\x40\x19\x83\xad\xe3\x30\x5a\x94\x74\x8d\xb9\x5e\xb0\xb7\x42\x3b\xf2\xb0\x4e\x60\x03\x33\x0c\x0d\x2a\xb0\x60\xfd\xab\x2f\x65\x5e\x72\x9c\x27\x68\x76\xba\x3f\xfe\x2c\xbb\xa0\x01\xf1\xb8\xd4\x7c\xff\xfb\xe7\xa1\xe6\xe9\x9c\x3a\xe5\x91\x6e\x4d\xfe\x4a\xbc\x02\x00\x00\xff\xff\x0e\x27\x2c\x52\x09\x01\x00\x00")
|
||||
|
||||
func _1540715431_add_versionUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1540715431_add_versionUpSql,
|
||||
"1540715431_add_version.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1540715431_add_versionUpSql() (*asset, error) {
|
||||
bytes, err := _1540715431_add_versionUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1541164797_add_installationsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\xb6\xe6\x02\x04\x00\x00\xff\xff\xd8\xbf\x14\x75\x1a\x00\x00\x00")
|
||||
|
||||
func _1541164797_add_installationsDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1541164797_add_installationsDownSql,
|
||||
"1541164797_add_installations.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1541164797_add_installationsDownSql() (*asset, error) {
|
||||
bytes, err := _1541164797_add_installationsDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1541164797_add_installationsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xce\xb1\x6a\xc3\x30\x14\x85\xe1\xdd\x4f\x71\x46\x1b\xbc\x74\xee\x24\xc9\xd7\x46\x70\xb9\x6a\x5d\x09\xba\x05\x05\x6b\x10\xd8\x4a\xc0\x5a\xf2\xf6\xc1\x43\x20\xce\xfc\x7f\x70\x8e\x99\x49\x79\x82\x57\x9a\x09\xb9\xec\x35\xae\x6b\xac\xf9\x56\x76\xa0\x6d\x80\xbc\xa4\x52\x73\x7d\x40\xb3\xd3\x10\xe7\x21\x81\xb9\x3f\xca\x1b\xbe\xe4\x05\x9e\xfe\xfd\x09\xd4\xbc\xa5\xbd\xc6\xed\x8e\x20\x7f\x76\x12\x1a\xa0\xed\x04\x2b\x67\x96\x4a\xbc\xae\x69\x81\x76\x8e\x49\x09\x06\x1a\x55\x60\x8f\xaf\x23\x06\xb1\xbf\x81\xda\xd7\x8b\xfe\x73\xb5\x83\x13\x18\x27\x23\x5b\xe3\x31\xd3\x0f\x2b\x43\x4d\xf7\xdd\x3c\x03\x00\x00\xff\xff\x28\x14\xac\x9d\xd8\x00\x00\x00")
|
||||
|
||||
func _1541164797_add_installationsUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1541164797_add_installationsUpSql,
|
||||
"1541164797_add_installations.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1541164797_add_installationsUpSql() (*asset, error) {
|
||||
bytes, err := _1541164797_add_installationsUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 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}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1558084410_add_secretDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x4e\x4d\x2e\x4a\x2d\x89\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x8b\xcf\x4c\x29\xb6\xe6\xc2\x50\x53\x6c\xcd\x05\x08\x00\x00\xff\xff\xd3\xcd\x41\x83\x38\x00\x00\x00")
|
||||
|
||||
func _1558084410_add_secretDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558084410_add_secretDownSql,
|
||||
"1558084410_add_secret.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558084410_add_secretDownSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_secretDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558084410_add_secret.down.sql", size: 56, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x49, 0xb, 0x65, 0xdf, 0x59, 0xbf, 0xe9, 0x5, 0x5b, 0x6f, 0xd5, 0x3a, 0xb7, 0x57, 0xe8, 0x78, 0x38, 0x73, 0x53, 0x57, 0xf7, 0x24, 0x4, 0xe4, 0xa2, 0x49, 0x22, 0xa2, 0xc6, 0xfd, 0x80, 0xa4}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1558084410_add_secretUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x50\xcf\x0a\x82\x30\x1c\xbe\xef\x29\xbe\xa3\x82\x6f\xd0\x49\xc7\x4f\x19\xad\xdf\x6a\x4d\xc8\x93\x48\xf3\x30\x10\x83\xdc\xa5\xb7\x0f\x23\x45\xa1\xce\xdf\xff\x4f\x5a\xca\x1d\xc1\xe5\x85\x26\x4c\xfd\xfd\xd9\xc7\x09\x89\x00\x82\xef\xc7\x18\xe2\x0b\x85\x36\x05\xd8\x38\x70\xad\x35\xce\x56\x9d\x72\xdb\xe0\x48\x0d\x0c\x43\x1a\x2e\xb5\x92\x0e\xaa\x62\x63\x29\x13\xf8\x9a\xec\x65\x22\x3d\x08\xf1\x23\xaa\x0d\xe3\x14\xbb\x61\xe8\x62\x78\x8c\x6d\xf0\x4b\x34\x1c\xdd\xdc\xaa\xce\x36\x75\xda\xe0\xf7\xd6\x33\x58\xb3\xba\xd4\x94\x04\x9f\x6d\x79\xe9\x9f\x82\xa5\xb1\xa4\x2a\xfe\x4c\x48\x76\x7c\x4b\x25\x59\x62\x49\xd7\xe5\x8a\x15\x4f\xe7\x09\xef\x00\x00\x00\xff\xff\xa6\xbb\x2c\x23\x2d\x01\x00\x00")
|
||||
|
||||
func _1558084410_add_secretUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558084410_add_secretUpSql,
|
||||
"1558084410_add_secret.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558084410_add_secretUpSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_secretUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558084410_add_secret.up.sql", size: 301, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0x32, 0x36, 0x8e, 0x47, 0xb0, 0x8f, 0xc1, 0xc6, 0xf7, 0xc6, 0x9f, 0x2d, 0x44, 0x75, 0x2b, 0x26, 0xec, 0x6, 0xa0, 0x7b, 0xa5, 0xbd, 0xc8, 0x76, 0x8a, 0x82, 0x68, 0x2, 0x42, 0xb5, 0xf4}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1558588866_add_versionDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x56\x70\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\x4b\x2d\x2a\xce\xcc\xcf\xb3\xe6\x02\x04\x00\x00\xff\xff\xdf\x6b\x9f\xbb\x2f\x00\x00\x00")
|
||||
|
||||
func _1558588866_add_versionDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558588866_add_versionDownSql,
|
||||
"1558588866_add_version.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558588866_add_versionDownSql() (*asset, error) {
|
||||
bytes, err := _1558588866_add_versionDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558588866_add_version.down.sql", size: 47, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x52, 0x34, 0x3c, 0x46, 0x4a, 0xf0, 0x72, 0x47, 0x6f, 0x49, 0x5c, 0xc7, 0xf9, 0x32, 0xce, 0xc4, 0x3d, 0xfd, 0x61, 0xa1, 0x8b, 0x8f, 0xf2, 0x31, 0x34, 0xde, 0x15, 0x49, 0xa6, 0xde, 0xb9}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1558588866_add_versionUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x56\x70\x74\x71\x51\x28\x4b\x2d\x2a\xce\xcc\xcf\x53\xf0\xf4\x0b\x71\x75\x77\x0d\x52\x70\x71\x75\x73\x0c\xf5\x09\x51\x30\xb0\xe6\x02\x04\x00\x00\xff\xff\x14\x7b\x07\xb5\x39\x00\x00\x00")
|
||||
|
||||
func _1558588866_add_versionUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558588866_add_versionUpSql,
|
||||
"1558588866_add_version.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558588866_add_versionUpSql() (*asset, error) {
|
||||
bytes, err := _1558588866_add_versionUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558588866_add_version.up.sql", size: 57, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2a, 0xea, 0x64, 0x39, 0x61, 0x20, 0x83, 0x83, 0xb, 0x2e, 0x79, 0x64, 0xb, 0x53, 0xfa, 0xfe, 0xc6, 0xf7, 0x67, 0x42, 0xd3, 0x4f, 0xdc, 0x7e, 0x30, 0x32, 0xe8, 0x14, 0x41, 0xe9, 0xe7, 0x3b}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1559627659_add_contact_codeDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xce\xcf\x2b\x49\x4c\x2e\x89\x4f\xce\x4f\x49\x8d\x4f\xce\xcf\x4b\xcb\x4c\xb7\xe6\x02\x04\x00\x00\xff\xff\x73\x7b\x50\x80\x20\x00\x00\x00")
|
||||
|
||||
func _1559627659_add_contact_codeDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1559627659_add_contact_codeDownSql,
|
||||
"1559627659_add_contact_code.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1559627659_add_contact_codeDownSql() (*asset, error) {
|
||||
bytes, err := _1559627659_add_contact_codeDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1559627659_add_contact_code.down.sql", size: 32, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x5d, 0x64, 0x6d, 0xce, 0x24, 0x42, 0x20, 0x8d, 0x4f, 0x37, 0xaa, 0x9d, 0xc, 0x57, 0x98, 0xc1, 0xd1, 0x1a, 0x34, 0xcd, 0x9f, 0x8f, 0x34, 0x86, 0xb3, 0xd3, 0xdc, 0xf1, 0x7d, 0xe5, 0x1b, 0x6e}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1559627659_add_contact_codeUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\xce\xc1\x8e\x82\x30\x18\x04\xe0\x7b\x9f\x62\x6e\x40\xb2\x07\xf6\xcc\xa9\xbb\xfb\xaf\x21\xd6\x62\x4a\x31\x72\x22\xb5\xa0\x34\x21\x45\xa1\xf8\xfc\x06\x13\xe3\xc5\xeb\xe4\x9b\xc9\xfc\x2a\xe2\x9a\xa0\xf9\x8f\x20\xd8\xd1\x07\x63\x43\x63\xc7\xb6\x6b\xec\xe8\xcf\xee\x82\x98\x01\x58\xbc\xbb\x2d\xcf\x68\x0e\x93\x71\x3e\xe0\x6e\x26\xdb\x9b\x29\xfe\x4e\x20\x0b\x0d\x59\x09\x81\xbd\xca\x77\x5c\xd5\xd8\x52\x8d\x3f\xfa\xe7\x95\xd0\x88\x8e\xd1\x17\x03\x06\x33\x87\xe6\xba\x9c\x06\x37\xf7\x5d\x8b\x5c\x6a\xda\x90\x7a\x57\x5f\x3c\x65\x49\xc6\x58\x2e\x4b\x52\x7a\x55\xc5\xc7\x4f\x07\x2e\x2a\x2a\x11\xaf\xe3\x48\x93\x8c\x3d\x02\x00\x00\xff\xff\xdc\x7c\x0c\xd3\xc6\x00\x00\x00")
|
||||
|
||||
func _1559627659_add_contact_codeUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1559627659_add_contact_codeUpSql,
|
||||
"1559627659_add_contact_code.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1559627659_add_contact_codeUpSql() (*asset, error) {
|
||||
bytes, err := _1559627659_add_contact_codeUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1559627659_add_contact_code.up.sql", size: 198, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x16, 0xf6, 0xc2, 0x62, 0x9c, 0xd2, 0xc9, 0x1e, 0xd8, 0xea, 0xaa, 0xea, 0x95, 0x8f, 0x89, 0x6a, 0x85, 0x5d, 0x9d, 0x99, 0x78, 0x3c, 0x90, 0x66, 0x99, 0x3e, 0x4b, 0x19, 0x62, 0xfb, 0x31, 0x4d}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1561368210_add_installation_metadataDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x8e\xcf\x4d\x2d\x49\x4c\x49\x2c\x49\xb4\xe6\x02\x04\x00\x00\xff\xff\x03\x72\x7f\x08\x23\x00\x00\x00")
|
||||
|
||||
func _1561368210_add_installation_metadataDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1561368210_add_installation_metadataDownSql,
|
||||
"1561368210_add_installation_metadata.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1561368210_add_installation_metadataDownSql() (*asset, error) {
|
||||
bytes, err := _1561368210_add_installation_metadataDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1561368210_add_installation_metadata.down.sql", size: 35, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa8, 0xde, 0x3f, 0xd2, 0x4a, 0x50, 0x98, 0x56, 0xe3, 0xc0, 0xcd, 0x9d, 0xb0, 0x34, 0x3b, 0xe5, 0x62, 0x18, 0xb5, 0x20, 0xc9, 0x3e, 0xdc, 0x6a, 0x40, 0x36, 0x66, 0xea, 0x51, 0x8c, 0x71, 0xf5}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1561368210_add_installation_metadataUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\xce\xc1\x8a\x83\x30\x10\xc6\xf1\xbb\x4f\xf1\xdd\x54\xf0\x0d\xf6\x14\xb3\x23\x08\x21\xd9\x95\x04\x7a\x93\x60\x52\x08\xd5\x58\xe8\x50\xf0\xed\x8b\x87\x42\xed\xc1\xeb\xcc\xef\x83\xbf\x1c\x48\x58\x82\x15\xad\x22\xa4\xfc\x60\x3f\xcf\x9e\xd3\x9a\xc7\x25\xb2\x0f\x9e\x3d\x50\x15\x40\x0a\x31\x73\xe2\x0d\xad\x32\x2d\xb4\xb1\xd0\x4e\xa9\x66\xff\x7c\x8e\x52\x80\xa5\x8b\x3d\x80\xec\x97\x78\xbc\xe2\x97\x3a\xe1\x94\x45\x59\xee\x20\xc4\x67\x9a\xe2\xc8\xdb\xfd\xdc\x5d\xa7\x65\xe4\xf5\x16\xf3\xa9\x72\xba\xff\x77\x54\xbd\x83\x9b\xef\xc0\x1a\x46\x43\x1a\xdd\xa9\x5e\x5a\x0c\xf4\xa7\x84\xa4\xa2\xfe\x29\x5e\x01\x00\x00\xff\xff\x5d\x6f\xe6\xd3\x0b\x01\x00\x00")
|
||||
|
||||
func _1561368210_add_installation_metadataUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1561368210_add_installation_metadataUpSql,
|
||||
"1561368210_add_installation_metadata.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1561368210_add_installation_metadataUpSql() (*asset, error) {
|
||||
bytes, err := _1561368210_add_installation_metadataUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1561368210_add_installation_metadata.up.sql", size: 267, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb4, 0x71, 0x8f, 0x29, 0xb1, 0xaa, 0xd6, 0xd1, 0x8c, 0x17, 0xef, 0x6c, 0xd5, 0x80, 0xb8, 0x2c, 0xc3, 0xfe, 0xec, 0x24, 0x4d, 0xc8, 0x25, 0xd3, 0xb4, 0xcd, 0xa9, 0xac, 0x63, 0x61, 0xb2, 0x9c}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8f\xbb\x6e\xc3\x30\x0c\x45\x77\x7f\xc5\x45\x96\x2c\xb5\xb4\x74\xea\xd6\xb1\x7b\x7f\x80\x91\x68\x89\x88\x1e\xae\x48\xe7\xf1\xf7\x85\xd3\x02\xcd\xd6\xf5\x00\xe7\xf0\xd2\x7b\x7c\x66\x51\x2c\x52\x18\xa2\x68\x1c\x58\x95\xc6\x1d\x27\x0e\xb4\x29\xe3\x90\xc4\xf2\x76\x72\xa1\x57\xaf\x46\xb6\xe9\x2c\xd5\x57\x49\x83\x8c\xfd\xe5\xf5\x30\x79\x8f\x40\xed\x68\xc8\xd4\x62\xe1\x47\x4b\xa1\x46\xc3\xa4\x25\x5c\xc5\x32\x08\xeb\xe0\x45\x6e\x0e\xef\x86\xc2\xa4\x06\xcb\x64\x47\x85\x65\x46\x20\xe5\x3d\xb3\xf4\x81\xd4\xe7\x93\xb4\x48\x46\x6e\x47\x1f\xcb\x13\xd9\x17\x06\x2a\x85\x23\x96\xd1\xeb\xc3\x55\xaa\x8c\x28\x83\x83\xf5\x71\x7f\x01\xa9\xb2\xa1\x51\x65\xdd\xfd\x4c\x17\x46\xeb\xbf\xe7\x41\x2d\xfe\xff\x11\xae\x7d\x9c\x15\xa4\xe0\xdb\xca\xc1\x38\xba\x69\x5a\x29\x9c\x29\x31\xf4\xab\x88\xf1\x34\x79\x9f\xfa\x5b\xe2\xc6\xbb\xf5\xbc\x71\x5e\xcf\x09\x3f\x35\xe9\x4d\x31\x77\x38\xe7\xff\x80\x4b\x1d\x6e\xfa\x0e\x00\x00\xff\xff\x9d\x60\x3d\x88\x79\x01\x00\x00")
|
||||
|
||||
func docGoBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_docGo,
|
||||
"doc.go",
|
||||
)
|
||||
}
|
||||
|
||||
func docGo() (*asset, error) {
|
||||
bytes, err := docGoBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "doc.go", size: 377, mode: os.FileMode(0644), modTime: time.Unix(1564484687, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xef, 0xaf, 0xdf, 0xcf, 0x65, 0xae, 0x19, 0xfc, 0x9d, 0x29, 0xc1, 0x91, 0xaf, 0xb5, 0xd5, 0xb1, 0x56, 0xf3, 0xee, 0xa8, 0xba, 0x13, 0x65, 0xdb, 0xab, 0xcf, 0x4e, 0xac, 0x92, 0xe9, 0x60, 0xf1}}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// 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 {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
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 {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
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 {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
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))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _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_secret.down.sql": _1558084410_add_secretDownSql,
|
||||
|
||||
"1558084410_add_secret.up.sql": _1558084410_add_secretUpSql,
|
||||
|
||||
"1558588866_add_version.down.sql": _1558588866_add_versionDownSql,
|
||||
|
||||
"1558588866_add_version.up.sql": _1558588866_add_versionUpSql,
|
||||
|
||||
"1559627659_add_contact_code.down.sql": _1559627659_add_contact_codeDownSql,
|
||||
|
||||
"1559627659_add_contact_code.up.sql": _1559627659_add_contact_codeUpSql,
|
||||
|
||||
"1561368210_add_installation_metadata.down.sql": _1561368210_add_installation_metadataDownSql,
|
||||
|
||||
"1561368210_add_installation_metadata.up.sql": _1561368210_add_installation_metadataUpSql,
|
||||
|
||||
"doc.go": docGo,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// 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
|
||||
// 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, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"1536754952_initial_schema.down.sql": {_1536754952_initial_schemaDownSql, map[string]*bintree{}},
|
||||
"1536754952_initial_schema.up.sql": {_1536754952_initial_schemaUpSql, map[string]*bintree{}},
|
||||
"1539249977_update_ratchet_info.down.sql": {_1539249977_update_ratchet_infoDownSql, map[string]*bintree{}},
|
||||
"1539249977_update_ratchet_info.up.sql": {_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}},
|
||||
"1540715431_add_version.down.sql": {_1540715431_add_versionDownSql, map[string]*bintree{}},
|
||||
"1540715431_add_version.up.sql": {_1540715431_add_versionUpSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.down.sql": {_1541164797_add_installationsDownSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.up.sql": {_1541164797_add_installationsUpSql, map[string]*bintree{}},
|
||||
"1558084410_add_secret.down.sql": {_1558084410_add_secretDownSql, map[string]*bintree{}},
|
||||
"1558084410_add_secret.up.sql": {_1558084410_add_secretUpSql, map[string]*bintree{}},
|
||||
"1558588866_add_version.down.sql": {_1558588866_add_versionDownSql, map[string]*bintree{}},
|
||||
"1558588866_add_version.up.sql": {_1558588866_add_versionUpSql, map[string]*bintree{}},
|
||||
"1559627659_add_contact_code.down.sql": {_1559627659_add_contact_codeDownSql, map[string]*bintree{}},
|
||||
"1559627659_add_contact_code.up.sql": {_1559627659_add_contact_codeUpSql, map[string]*bintree{}},
|
||||
"1561368210_add_installation_metadata.down.sql": {_1561368210_add_installation_metadataDownSql, map[string]*bintree{}},
|
||||
"1561368210_add_installation_metadata.up.sql": {_1561368210_add_installation_metadataUpSql, map[string]*bintree{}},
|
||||
"doc.go": {docGo, map[string]*bintree{}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
DROP TABLE sessions;
|
||||
DROP TABLE bundles;
|
||||
DROP TABLE keys;
|
||||
DROP TABLE ratchet_info;
|
@ -0,0 +1,40 @@
|
||||
CREATE TABLE sessions (
|
||||
dhr BLOB,
|
||||
dhs_public BLOB,
|
||||
dhs_private BLOB,
|
||||
root_chain_key BLOB,
|
||||
send_chain_key BLOB,
|
||||
send_chain_n INTEGER,
|
||||
recv_chain_key BLOB,
|
||||
recv_chain_n INTEGER,
|
||||
step INTEGER,
|
||||
pn INTEGER,
|
||||
id BLOB NOT NULL PRIMARY KEY,
|
||||
UNIQUE(id) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
CREATE TABLE keys (
|
||||
public_key BLOB NOT NULL,
|
||||
msg_num INTEGER,
|
||||
message_key BLOB NOT NULL,
|
||||
UNIQUE (msg_num, message_key) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
CREATE TABLE bundles (
|
||||
identity BLOB NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
private_key BLOB,
|
||||
signed_pre_key BLOB NOT NULL PRIMARY KEY ON CONFLICT IGNORE,
|
||||
timestamp UNSIGNED BIG INT NOT NULL,
|
||||
expired BOOLEAN DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE ratchet_info (
|
||||
bundle_id BLOB NOT NULL,
|
||||
ephemeral_key BLOB,
|
||||
identity BLOB NOT NULL,
|
||||
symmetric_key BLOB NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
UNIQUE(bundle_id, identity) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY (bundle_id) REFERENCES bundles(signed_pre_key)
|
||||
);
|
@ -0,0 +1,11 @@
|
||||
DROP TABLE ratchet_info_v2;
|
||||
|
||||
CREATE TABLE ratchet_info (
|
||||
bundle_id BLOB NOT NULL,
|
||||
ephemeral_key BLOB,
|
||||
identity BLOB NOT NULL,
|
||||
symmetric_key BLOB NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
UNIQUE(bundle_id, identity) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY (bundle_id) REFERENCES bundles(signed_pre_key)
|
||||
);
|
@ -0,0 +1,13 @@
|
||||
DELETE FROM sessions;
|
||||
DELETE FROM keys;
|
||||
DROP TABLE ratchet_info;
|
||||
|
||||
CREATE TABLE ratchet_info_v2 (
|
||||
bundle_id BLOB NOT NULL,
|
||||
ephemeral_key BLOB,
|
||||
identity BLOB NOT NULL,
|
||||
symmetric_key BLOB NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
UNIQUE(bundle_id, identity, installation_id) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY (bundle_id) REFERENCES bundles(signed_pre_key)
|
||||
);
|
@ -0,0 +1,3 @@
|
||||
ALTER TABLE keys DROP COLUMN session_id;
|
||||
ALTER TABLE sessions DROP COLUMN keys_count;
|
||||
ALTER TABLE bundles DROP COLUMN version;
|
@ -0,0 +1,5 @@
|
||||
DELETE FROM keys;
|
||||
ALTER TABLE keys ADD COLUMN seq_num INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE keys ADD COLUMN session_id BLOB;
|
||||
ALTER TABLE sessions ADD COLUMN keys_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE bundles ADD COLUMN version INTEGER NOT NULL DEFAULT 0;
|
@ -0,0 +1 @@
|
||||
DROP TABLE installations;
|
@ -0,0 +1,7 @@
|
||||
CREATE TABLE installations (
|
||||
identity BLOB NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
timestamp UNSIGNED BIG INT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 1,
|
||||
UNIQUE(identity, installation_id) ON CONFLICT REPLACE
|
||||
);
|
@ -0,0 +1,2 @@
|
||||
DROP TABLE secret_installation_ids;
|
||||
DROP TABLE secrets;
|
@ -0,0 +1,11 @@
|
||||
CREATE TABLE secrets (
|
||||
identity BLOB NOT NULL PRIMARY KEY ON CONFLICT IGNORE,
|
||||
secret BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE secret_installation_ids (
|
||||
id TEXT NOT NULL,
|
||||
identity_id BLOB NOT NULL,
|
||||
UNIQUE(id, identity_id) ON CONFLICT IGNORE,
|
||||
FOREIGN KEY (identity_id) REFERENCES secrets(identity)
|
||||
);
|
@ -0,0 +1 @@
|
||||
ALTER TABLE installations DROP COLUMN version;
|
@ -0,0 +1 @@
|
||||
ALTER TABLE installations ADD version INTEGER DEFAULT 0;
|
@ -0,0 +1 @@
|
||||
DROP TABLE contact_code_config;
|
@ -0,0 +1,6 @@
|
||||
CREATE TABLE contact_code_config (
|
||||
unique_constraint varchar(1) NOT NULL PRIMARY KEY DEFAULT 'X',
|
||||
last_published INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
INSERT INTO contact_code_config VALUES ('X', 0);
|
@ -0,0 +1 @@
|
||||
DROP TABLE installations_metadata;
|
@ -0,0 +1,8 @@
|
||||
CREATE TABLE installation_metadata (
|
||||
identity BLOB NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
device_type TEXT NOT NULL DEFAULT '',
|
||||
fcm_token TEXT NOT NULL DEFAULT '',
|
||||
UNIQUE(identity, installation_id) ON CONFLICT REPLACE
|
||||
);
|
9
protocol/encryption/migrations/sqlite/doc.go
Normal file
9
protocol/encryption/migrations/sqlite/doc.go
Normal file
@ -0,0 +1,9 @@
|
||||
// This file is necessary because "github.com/status-im/migrate/v4"
|
||||
// can't handle files starting with a prefix. At least that's the case
|
||||
// for go-bindata.
|
||||
// If go-bindata is called from the same directory, asset names
|
||||
// have no prefix and "github.com/status-im/migrate/v4" works as expected.
|
||||
|
||||
package sqlite
|
||||
|
||||
//go:generate go-bindata -pkg migrations -o ../migrations.go .
|
312
protocol/encryption/multidevice/persistence_test.go
Normal file
312
protocol/encryption/multidevice/persistence_test.go
Normal file
@ -0,0 +1,312 @@
|
||||
package multidevice
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
dbPath = "/tmp/status-key-store.db"
|
||||
)
|
||||
|
||||
func TestSQLLitePersistenceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SQLLitePersistenceTestSuite))
|
||||
}
|
||||
|
||||
type SQLLitePersistenceTestSuite struct {
|
||||
suite.Suite
|
||||
// nolint: structcheck, megacheck
|
||||
db *sql.DB
|
||||
service *sqlitePersistence
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) SetupTest() {
|
||||
os.Remove(dbPath)
|
||||
|
||||
db, err := sqlite.Open(dbPath, "test-key")
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = newSQLitePersistence(db)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallations() {
|
||||
identity := []byte("alice")
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1, Enabled: true},
|
||||
{ID: "alice-2", Version: 2, Enabled: true},
|
||||
}
|
||||
addedInstallations, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
s.Require().Equal(installations, addedInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationVersions() {
|
||||
identity := []byte("alice")
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1, Enabled: true},
|
||||
}
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
|
||||
installationsWithDowngradedVersion := []*Installation{
|
||||
{ID: "alice-1", Version: 0},
|
||||
}
|
||||
|
||||
_, err = s.service.AddInstallations(
|
||||
identity,
|
||||
3,
|
||||
installationsWithDowngradedVersion,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err = s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
installations = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-3", Version: 3},
|
||||
}
|
||||
|
||||
_, err = s.service.AddInstallations(
|
||||
identity,
|
||||
2,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
installations = []*Installation{
|
||||
{ID: "alice-2", Version: 2, Enabled: true},
|
||||
{ID: "alice-3", Version: 3, Enabled: true},
|
||||
{ID: "alice-4", Version: 4, Enabled: true},
|
||||
}
|
||||
|
||||
_, err = s.service.AddInstallations(
|
||||
identity,
|
||||
3,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
false,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Nil(actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add the installations again
|
||||
installations = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
addedInstallations, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(0, len(addedInstallations))
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{{ID: "alice-2", Version: 2, Enabled: true}}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{{ID: "alice-2", Version: 2, Enabled: true}}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
|
||||
err = s.service.EnableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err = s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected = []*Installation{
|
||||
{ID: "alice-1", Version: 1, Enabled: true},
|
||||
{ID: "alice-2", Version: 2, Enabled: true},
|
||||
}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestGetInstallations() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetInstallations(identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyMetadata := &InstallationMetadata{}
|
||||
|
||||
expected := []*Installation{
|
||||
{ID: "alice-1", Version: 1, Timestamp: 1, Enabled: false, InstallationMetadata: emptyMetadata},
|
||||
{ID: "alice-2", Version: 2, Timestamp: 1, Enabled: true, InstallationMetadata: emptyMetadata},
|
||||
}
|
||||
s.Require().Equal(2, len(actualInstallations))
|
||||
s.Require().ElementsMatch(expected, actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestSetMetadata() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
_, err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyMetadata := &InstallationMetadata{}
|
||||
setMetadata := &InstallationMetadata{
|
||||
Name: "a",
|
||||
FCMToken: "b",
|
||||
DeviceType: "c",
|
||||
}
|
||||
|
||||
err = s.service.SetInstallationMetadata(identity, "alice-2", setMetadata)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetInstallations(identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{
|
||||
{ID: "alice-1", Version: 1, Timestamp: 1, Enabled: false, InstallationMetadata: emptyMetadata},
|
||||
{ID: "alice-2", Version: 2, Timestamp: 1, Enabled: true, InstallationMetadata: setMetadata},
|
||||
}
|
||||
s.Require().ElementsMatch(expected, actualInstallations)
|
||||
}
|
@ -8,8 +8,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
dr "github.com/status-im/doubleratchet"
|
||||
|
||||
ecrypto "github.com/status-im/status-protocol-go/crypto"
|
||||
"github.com/status-im/status-protocol-go/encryption/multidevice"
|
||||
ecrypto "github.com/status-im/status-go/protocol/crypto"
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
)
|
||||
|
||||
// RatchetInfo holds the current ratchet state.
|
204
protocol/encryption/persistence_keys_storage_test.go
Normal file
204
protocol/encryption/persistence_keys_storage_test.go
Normal file
@ -0,0 +1,204 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
dr "github.com/status-im/doubleratchet"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
)
|
||||
|
||||
var (
|
||||
pubKey1 = dr.Key{0xe3, 0xbe, 0xb9, 0x4e, 0x70, 0x17, 0x37, 0xc, 0x1, 0x8f, 0xa9, 0x7e, 0xef, 0x4, 0xfb, 0x23, 0xac, 0xea, 0x28, 0xf7, 0xa9, 0x56, 0xcc, 0x1d, 0x46, 0xf3, 0xb5, 0x1d, 0x7d, 0x7d, 0x5e, 0x2c}
|
||||
pubKey2 = dr.Key{0xec, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40}
|
||||
mk1 = dr.Key{0x00, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40}
|
||||
mk2 = dr.Key{0x01, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40}
|
||||
mk3 = dr.Key{0x02, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40}
|
||||
mk4 = dr.Key{0x03, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40}
|
||||
mk5 = dr.Key{0x04, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40}
|
||||
)
|
||||
|
||||
func TestSQLLitePersistenceKeysStorageTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SQLLitePersistenceKeysStorageTestSuite))
|
||||
}
|
||||
|
||||
type SQLLitePersistenceKeysStorageTestSuite struct {
|
||||
suite.Suite
|
||||
service dr.KeysStorage
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) SetupTest() {
|
||||
dir, err := ioutil.TempDir("", "keys-storage-persistence")
|
||||
s.Require().NoError(err)
|
||||
|
||||
key := "blahblahblah"
|
||||
|
||||
db, err := sqlite.Open(filepath.Join(dir, "db.sql"), key)
|
||||
s.Require().NoError(err)
|
||||
|
||||
p := newSQLitePersistence(db)
|
||||
s.service = p.KeysStorage()
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLiteGetMissing() {
|
||||
// Act.
|
||||
_, ok, err := s.service.Get(pubKey1, 0)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.False(ok, "It returns false")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLite_Put() {
|
||||
// Act and assert.
|
||||
err := s.service.Put([]byte("session-id"), pubKey1, 0, mk1, 1)
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLite_DeleteOldMks() {
|
||||
// Insert keys out-of-order
|
||||
err := s.service.Put([]byte("session-id"), pubKey1, 0, mk1, 1)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey1, 1, mk2, 2)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey1, 2, mk3, 20)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey1, 3, mk4, 21)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey1, 4, mk5, 22)
|
||||
s.NoError(err)
|
||||
|
||||
err = s.service.DeleteOldMks([]byte("session-id"), 20)
|
||||
s.NoError(err)
|
||||
|
||||
_, ok, err := s.service.Get(pubKey1, 0)
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey1, 1)
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey1, 2)
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey1, 3)
|
||||
s.NoError(err)
|
||||
s.True(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey1, 4)
|
||||
s.NoError(err)
|
||||
s.True(ok)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLite_TruncateMks() {
|
||||
// Insert keys out-of-order
|
||||
err := s.service.Put([]byte("session-id"), pubKey2, 2, mk5, 5)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey2, 0, mk3, 3)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey1, 1, mk2, 2)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey2, 1, mk4, 4)
|
||||
s.NoError(err)
|
||||
err = s.service.Put([]byte("session-id"), pubKey1, 0, mk1, 1)
|
||||
s.NoError(err)
|
||||
|
||||
err = s.service.TruncateMks([]byte("session-id"), 2)
|
||||
s.NoError(err)
|
||||
|
||||
_, ok, err := s.service.Get(pubKey1, 0)
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey1, 1)
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey2, 0)
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey2, 1)
|
||||
s.NoError(err)
|
||||
s.True(ok)
|
||||
|
||||
_, ok, err = s.service.Get(pubKey2, 2)
|
||||
s.NoError(err)
|
||||
s.True(ok)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLite_Count() {
|
||||
|
||||
// Act.
|
||||
cnt, err := s.service.Count(pubKey1)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.EqualValues(0, cnt, "It returns 0 when no keys are in the database")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLite_Delete() {
|
||||
// Arrange.
|
||||
|
||||
// Act and assert.
|
||||
err := s.service.DeleteMk(pubKey1, 0)
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceKeysStorageTestSuite) TestKeysStorageSqlLite_Flow() {
|
||||
|
||||
// Act.
|
||||
err := s.service.Put([]byte("session-id"), pubKey1, 0, mk1, 1)
|
||||
s.NoError(err)
|
||||
|
||||
k, ok, err := s.service.Get(pubKey1, 0)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.True(ok, "It returns true")
|
||||
s.Equal(mk1, k, "It returns the message key")
|
||||
|
||||
// Act.
|
||||
_, ok, err = s.service.Get(pubKey2, 0)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.False(ok, "It returns false when querying non existing public key")
|
||||
|
||||
// Act.
|
||||
_, ok, err = s.service.Get(pubKey1, 1)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.False(ok, "It returns false when querying the wrong msg number")
|
||||
|
||||
// Act.
|
||||
cnt, err := s.service.Count(pubKey1)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.EqualValues(1, cnt)
|
||||
|
||||
// Act and assert.
|
||||
err = s.service.DeleteMk(pubKey1, 1)
|
||||
s.NoError(err)
|
||||
|
||||
// Act and assert.
|
||||
err = s.service.DeleteMk(pubKey2, 0)
|
||||
s.NoError(err)
|
||||
|
||||
// Act.
|
||||
err = s.service.DeleteMk(pubKey1, 0)
|
||||
s.NoError(err)
|
||||
|
||||
cnt, err = s.service.Count(pubKey1)
|
||||
|
||||
// Assert.
|
||||
s.NoError(err)
|
||||
s.EqualValues(0, cnt)
|
||||
}
|
341
protocol/encryption/persistence_test.go
Normal file
341
protocol/encryption/persistence_test.go
Normal file
@ -0,0 +1,341 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
)
|
||||
|
||||
func TestSQLLitePersistenceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SQLLitePersistenceTestSuite))
|
||||
}
|
||||
|
||||
type SQLLitePersistenceTestSuite struct {
|
||||
suite.Suite
|
||||
// nolint: structcheck, megacheck
|
||||
db *sql.DB
|
||||
service *sqlitePersistence
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) SetupTest() {
|
||||
dir, err := ioutil.TempDir("", "sqlite-persistence")
|
||||
s.Require().NoError(err)
|
||||
|
||||
db, err := sqlite.Open(filepath.Join(dir, "db.sql"), "test-key")
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = newSQLitePersistence(db)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestPrivateBundle() {
|
||||
installationID := "1"
|
||||
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualKey, err := s.service.GetPrivateKeyBundle([]byte("non-existing"))
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualKey)
|
||||
|
||||
anyPrivateBundle, err := s.service.GetAnyPrivateBundle([]byte("non-existing-id"), []*multidevice.Installation{{ID: installationID, Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Nil(anyPrivateBundle)
|
||||
|
||||
bundle, err := NewBundleContainer(key, installationID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.AddPrivateBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundleID := bundle.GetBundle().GetSignedPreKeys()[installationID].GetSignedPreKey()
|
||||
|
||||
actualKey, err = s.service.GetPrivateKeyBundle(bundleID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle.GetPrivateSignedPreKey(), actualKey, "It returns the same key")
|
||||
|
||||
identity := crypto.CompressPubkey(&key.PublicKey)
|
||||
anyPrivateBundle, err = s.service.GetAnyPrivateBundle(identity, []*multidevice.Installation{{ID: installationID, Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.NotNil(anyPrivateBundle)
|
||||
s.Equal(bundle.GetBundle().GetSignedPreKeys()[installationID].SignedPreKey, anyPrivateBundle.GetBundle().GetSignedPreKeys()[installationID].SignedPreKey, "It returns the same bundle")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestPublicBundle() {
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
bundleContainer, err := NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle := bundleContainer.GetBundle()
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
|
||||
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the right prekeys")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() {
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
// Create & add initial bundle
|
||||
bundleContainer, err := NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle := bundleContainer.GetBundle()
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create & add a new bundle
|
||||
bundleContainer, err = NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
bundle = bundleContainer.GetBundle()
|
||||
// We set the version
|
||||
bundle.GetSignedPreKeys()["1"].Version = 1
|
||||
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
|
||||
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the right prekeys")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() {
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
// Create & add initial bundle
|
||||
bundleContainer, err := NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle1 := bundleContainer.GetBundle()
|
||||
err = s.service.AddPublicBundle(bundle1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create & add a new bundle
|
||||
bundleContainer, err = NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle2 := bundleContainer.GetBundle()
|
||||
// We set the version
|
||||
bundle2.GetSignedPreKeys()["1"].Version = 1
|
||||
|
||||
err = s.service.AddPublicBundle(bundle2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Add again the initial bundle
|
||||
err = s.service.AddPublicBundle(bundle1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle2.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
|
||||
s.Equal(bundle2.GetSignedPreKeys()["1"].GetVersion(), uint32(1))
|
||||
s.Equal(bundle2.GetSignedPreKeys()["1"].GetSignedPreKey(), actualBundle.GetSignedPreKeys()["1"].GetSignedPreKey(), "It sets the right prekeys")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() {
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
bundleContainer, err := NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle := bundleContainer.GetBundle()
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Adding it again does not throw an error
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Adding a different bundle
|
||||
bundleContainer, err = NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
// We set the version
|
||||
bundle = bundleContainer.GetBundle()
|
||||
bundle.GetSignedPreKeys()["1"].Version = 1
|
||||
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Returns the most recent bundle
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity")
|
||||
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the signed pre keys")
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() {
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
bundleContainer, err := NewBundleContainer(key, "1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle := bundleContainer.GetBundle()
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Adding it again does not throw an error
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Adding a different bundle from a different instlation id
|
||||
bundleContainer, err = NewBundleContainer(key, "2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
bundle = bundleContainer.GetBundle()
|
||||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Returns the most recent bundle
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey,
|
||||
[]*multidevice.Installation{
|
||||
{ID: "1", Version: 1},
|
||||
{ID: "2", Version: 1},
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity")
|
||||
s.NotNil(actualBundle.GetSignedPreKeys()["1"])
|
||||
s.NotNil(actualBundle.GetSignedPreKeys()["2"])
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestRatchetInfoPrivateBundle() {
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Add a private bundle
|
||||
bundle, err := NewBundleContainer(key, "2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.AddPrivateBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.AddRatchetInfo(
|
||||
[]byte("symmetric-key"),
|
||||
[]byte("their-public-key"),
|
||||
bundle.GetBundle().GetSignedPreKeys()["2"].GetSignedPreKey(),
|
||||
[]byte("ephemeral-public-key"),
|
||||
"1",
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
ratchetInfo, err := s.service.GetRatchetInfo(bundle.GetBundle().GetSignedPreKeys()["2"].GetSignedPreKey(), []byte("their-public-key"), "1")
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(ratchetInfo)
|
||||
s.NotNil(ratchetInfo.ID, "It adds an id")
|
||||
s.Equal(ratchetInfo.PrivateKey, bundle.GetPrivateSignedPreKey(), "It returns the private key")
|
||||
s.Equal(ratchetInfo.Sk, []byte("symmetric-key"), "It returns the symmetric key")
|
||||
s.Equal(ratchetInfo.Identity, []byte("their-public-key"), "It returns the identity of the contact")
|
||||
s.Equal(ratchetInfo.PublicKey, bundle.GetBundle().GetSignedPreKeys()["2"].GetSignedPreKey(), "It returns the public key of the bundle")
|
||||
s.Equal(bundle.GetBundle().GetSignedPreKeys()["2"].GetSignedPreKey(), ratchetInfo.BundleID, "It returns the bundle id")
|
||||
s.Equal([]byte("ephemeral-public-key"), ratchetInfo.EphemeralKey, "It returns the ratchet ephemeral key")
|
||||
s.Equal("1", ratchetInfo.InstallationID, "It returns the right installation id")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestRatchetInfoPublicBundle() {
|
||||
installationID := "1"
|
||||
theirPublicKey := []byte("their-public-key")
|
||||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Add a private bundle
|
||||
bundle, err := NewBundleContainer(key, installationID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.AddPublicBundle(bundle.GetBundle())
|
||||
s.Require().NoError(err)
|
||||
|
||||
signedPreKey := bundle.GetBundle().GetSignedPreKeys()[installationID].GetSignedPreKey()
|
||||
|
||||
err = s.service.AddRatchetInfo(
|
||||
[]byte("symmetric-key"),
|
||||
theirPublicKey,
|
||||
signedPreKey,
|
||||
[]byte("public-ephemeral-key"),
|
||||
installationID,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
ratchetInfo, err := s.service.GetRatchetInfo(signedPreKey, theirPublicKey, installationID)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(ratchetInfo, "It returns the ratchet info")
|
||||
|
||||
s.NotNil(ratchetInfo.ID, "It adds an id")
|
||||
s.Nil(ratchetInfo.PrivateKey, "It does not return the private key")
|
||||
s.Equal(ratchetInfo.Sk, []byte("symmetric-key"), "It returns the symmetric key")
|
||||
s.Equal(ratchetInfo.Identity, theirPublicKey, "It returns the identity of the contact")
|
||||
s.Equal(ratchetInfo.PublicKey, signedPreKey, "It returns the public key of the bundle")
|
||||
s.Equal(installationID, ratchetInfo.InstallationID, "It returns the right installationID")
|
||||
s.Nilf(ratchetInfo.PrivateKey, "It does not return the private key")
|
||||
|
||||
ratchetInfo, err = s.service.GetAnyRatchetInfo(theirPublicKey, installationID)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(ratchetInfo, "It returns the ratchet info")
|
||||
s.NotNil(ratchetInfo.ID, "It adds an id")
|
||||
s.Nil(ratchetInfo.PrivateKey, "It does not return the private key")
|
||||
s.Equal(ratchetInfo.Sk, []byte("symmetric-key"), "It returns the symmetric key")
|
||||
s.Equal(ratchetInfo.Identity, theirPublicKey, "It returns the identity of the contact")
|
||||
s.Equal(ratchetInfo.PublicKey, signedPreKey, "It returns the public key of the bundle")
|
||||
s.Equal(signedPreKey, ratchetInfo.BundleID, "It returns the bundle id")
|
||||
s.Equal(installationID, ratchetInfo.InstallationID, "It saves the right installation ID")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestRatchetInfoNoBundle() {
|
||||
err := s.service.AddRatchetInfo(
|
||||
[]byte("symmetric-key"),
|
||||
[]byte("their-public-key"),
|
||||
[]byte("non-existing-bundle"),
|
||||
[]byte("non-existing-ephemeral-key"),
|
||||
"none",
|
||||
)
|
||||
|
||||
s.Error(err, "It returns an error")
|
||||
|
||||
_, err = s.service.GetRatchetInfo([]byte("non-existing-bundle"), []byte("their-public-key"), "none")
|
||||
s.Require().NoError(err)
|
||||
|
||||
ratchetInfo, err := s.service.GetAnyRatchetInfo([]byte("their-public-key"), "4")
|
||||
s.Require().NoError(err)
|
||||
s.Nil(ratchetInfo, "It returns nil when no bundle is there")
|
||||
}
|
||||
|
||||
// TODO: Add test for MarkBundleExpired
|
@ -11,9 +11,9 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/status-im/status-protocol-go/encryption/multidevice"
|
||||
"github.com/status-im/status-protocol-go/encryption/publisher"
|
||||
"github.com/status-im/status-protocol-go/encryption/sharedsecret"
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/encryption/publisher"
|
||||
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
|
||||
)
|
||||
|
||||
//go:generate protoc --go_out=. ./protocol_message.proto
|
199
protocol/encryption/protocol_test.go
Normal file
199
protocol/encryption/protocol_test.go
Normal file
@ -0,0 +1,199 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
||||
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
|
||||
)
|
||||
|
||||
func TestProtocolServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ProtocolServiceTestSuite))
|
||||
}
|
||||
|
||||
type ProtocolServiceTestSuite struct {
|
||||
suite.Suite
|
||||
aliceDBPath *os.File
|
||||
bobDBPath *os.File
|
||||
alice *Protocol
|
||||
bob *Protocol
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) SetupTest() {
|
||||
var err error
|
||||
|
||||
s.logger = tt.MustCreateTestLogger()
|
||||
|
||||
s.aliceDBPath, err = ioutil.TempFile("", "alice.db.sql")
|
||||
s.Require().NoError(err)
|
||||
aliceDBKey := "alice"
|
||||
|
||||
s.bobDBPath, err = ioutil.TempFile("", "bob.db.sql")
|
||||
s.Require().NoError(err)
|
||||
bobDBKey := "bob"
|
||||
|
||||
addedBundlesHandler := func(addedBundles []*multidevice.Installation) {}
|
||||
onNewSharedSecretHandler := func(secret []*sharedsecret.Secret) {}
|
||||
|
||||
db, err := sqlite.Open(s.aliceDBPath.Name(), aliceDBKey)
|
||||
s.Require().NoError(err)
|
||||
s.alice = New(
|
||||
db,
|
||||
"1",
|
||||
addedBundlesHandler,
|
||||
onNewSharedSecretHandler,
|
||||
func(*ProtocolMessageSpec) {},
|
||||
s.logger.With(zap.String("user", "alice")),
|
||||
)
|
||||
|
||||
db, err = sqlite.Open(s.bobDBPath.Name(), bobDBKey)
|
||||
s.Require().NoError(err)
|
||||
s.bob = New(
|
||||
db,
|
||||
"2",
|
||||
addedBundlesHandler,
|
||||
onNewSharedSecretHandler,
|
||||
func(*ProtocolMessageSpec) {},
|
||||
s.logger.With(zap.String("user", "bob")),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TearDownTest() {
|
||||
os.Remove(s.aliceDBPath.Name())
|
||||
os.Remove(s.bobDBPath.Name())
|
||||
_ = s.logger.Sync()
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestBuildPublicMessage() {
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
payload := []byte("test")
|
||||
s.NoError(err)
|
||||
|
||||
msg, err := s.alice.BuildPublicMessage(aliceKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(msg, "It creates a message")
|
||||
|
||||
s.NotNilf(msg.Message.GetBundles(), "It adds a bundle to the message")
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() {
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
payload := []byte("test")
|
||||
|
||||
msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(msgSpec, "It creates a message spec")
|
||||
|
||||
msg := msgSpec.Message
|
||||
s.NotNil(msg, "It creates a messages")
|
||||
|
||||
s.NotNilf(msg.GetBundles(), "It adds a bundle to the message")
|
||||
|
||||
directMessage := msg.GetDirectMessage()
|
||||
s.NotNilf(directMessage, "It sets the direct message")
|
||||
|
||||
encryptedPayload := directMessage["none"].GetPayload()
|
||||
s.NotNilf(encryptedPayload, "It sets the payload of the message")
|
||||
|
||||
s.NotEqualf(payload, encryptedPayload, "It encrypts the payload")
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
payload := []byte("test")
|
||||
|
||||
// Message is sent with DH
|
||||
msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(msgSpec)
|
||||
|
||||
msg := msgSpec.Message
|
||||
s.Require().NotNil(msg)
|
||||
|
||||
// Bob is able to decrypt the message
|
||||
unmarshaledMsg, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, msg, []byte("message-id"))
|
||||
s.NoError(err)
|
||||
s.NotNil(unmarshaledMsg)
|
||||
|
||||
recoveredPayload := []byte("test")
|
||||
s.Equalf(payload, recoveredPayload, "It successfully unmarshal the decrypted message")
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestSecretNegotiation() {
|
||||
var secretResponse []*sharedsecret.Secret
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
payload := []byte("test")
|
||||
|
||||
s.bob.onNewSharedSecretHandler = func(secret []*sharedsecret.Secret) {
|
||||
secretResponse = secret
|
||||
}
|
||||
msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(msgSpec, "It creates a message spec")
|
||||
|
||||
bundle := msgSpec.Message.GetBundles()[0]
|
||||
s.Require().NotNil(bundle)
|
||||
|
||||
signedPreKeys := bundle.GetSignedPreKeys()
|
||||
s.Require().NotNil(signedPreKeys)
|
||||
|
||||
signedPreKey := signedPreKeys["1"]
|
||||
s.Require().NotNil(signedPreKey)
|
||||
|
||||
s.Require().Equal(uint32(1), signedPreKey.GetProtocolVersion())
|
||||
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, msgSpec.Message, []byte("message-id"))
|
||||
s.NoError(err)
|
||||
|
||||
s.Require().NotNil(secretResponse)
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestPropagatingSavedSharedSecretsOnStart() {
|
||||
var secretResponse []*sharedsecret.Secret
|
||||
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
// Generate and save a shared secret.
|
||||
generatedSecret, err := s.alice.secret.Generate(aliceKey, &bobKey.PublicKey, "installation-1")
|
||||
s.NoError(err)
|
||||
|
||||
s.alice.onNewSharedSecretHandler = func(secret []*sharedsecret.Secret) {
|
||||
secretResponse = secret
|
||||
}
|
||||
|
||||
err = s.alice.Start(aliceKey)
|
||||
s.NoError(err)
|
||||
|
||||
s.Require().NotNil(secretResponse)
|
||||
s.Require().Len(secretResponse, 1)
|
||||
s.Equal(crypto.FromECDSAPub(generatedSecret.Identity), crypto.FromECDSAPub(secretResponse[0].Identity))
|
||||
s.Equal(generatedSecret.Key, secretResponse[0].Key)
|
||||
}
|
31
protocol/encryption/publisher/publisher_test.go
Normal file
31
protocol/encryption/publisher/publisher_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package publisher
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
)
|
||||
|
||||
func TestServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(PublisherTestSuite))
|
||||
}
|
||||
|
||||
type PublisherTestSuite struct {
|
||||
suite.Suite
|
||||
publisher *Publisher
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (p *PublisherTestSuite) SetupTest(installationID string) {
|
||||
p.logger = tt.MustCreateTestLogger()
|
||||
p.publisher = New(p.logger)
|
||||
}
|
||||
|
||||
func (p *PublisherTestSuite) TearDownTest() {
|
||||
_ = p.logger.Sync()
|
||||
}
|
||||
|
||||
// TODO(adam): provide more tests
|
120
protocol/encryption/sharedsecret/service_test.go
Normal file
120
protocol/encryption/sharedsecret/service_test.go
Normal file
@ -0,0 +1,120 @@
|
||||
package sharedsecret
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/protocol/sqlite"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SharedSecretTestSuite))
|
||||
}
|
||||
|
||||
type SharedSecretTestSuite struct {
|
||||
suite.Suite
|
||||
service *SharedSecret
|
||||
path string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (s *SharedSecretTestSuite) SetupTest() {
|
||||
s.logger = tt.MustCreateTestLogger()
|
||||
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "sharedsecret")
|
||||
s.Require().NoError(err)
|
||||
s.path = dbFile.Name()
|
||||
|
||||
db, err := sqlite.Open(s.path, "")
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = New(db, s.logger)
|
||||
}
|
||||
|
||||
func (s *SharedSecretTestSuite) TearDownTest() {
|
||||
os.Remove(s.path)
|
||||
_ = s.logger.Sync()
|
||||
}
|
||||
|
||||
func (s *SharedSecretTestSuite) 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.Generate(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.Agreed(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.Agreed(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.Generate(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.Agreed(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 *SharedSecretTestSuite) 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.Generate(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.Generate(myKey, &theirKey2.PublicKey, installationID2)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey2, "it generates a shared key")
|
||||
|
||||
// All the secrets are there
|
||||
secrets, err := s.service.All()
|
||||
s.Require().NoError(err)
|
||||
expected := []*Secret{
|
||||
sharedKey1,
|
||||
sharedKey2,
|
||||
}
|
||||
s.Require().Equal(expected, secrets)
|
||||
}
|
209
protocol/encryption/x3dh_test.go
Normal file
209
protocol/encryption/x3dh_test.go
Normal file
@ -0,0 +1,209 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
alicePrivateKey = "00000000000000000000000000000000"
|
||||
aliceEphemeralKey = "11111111111111111111111111111111"
|
||||
bobPrivateKey = "22222222222222222222222222222222"
|
||||
bobSignedPreKey = "33333333333333333333333333333333"
|
||||
)
|
||||
|
||||
var sharedKey = []byte{0xa4, 0xe9, 0x23, 0xd0, 0xaf, 0x8f, 0xe7, 0x8a, 0x5, 0x63, 0x63, 0xbe, 0x20, 0xe7, 0x1c, 0xa, 0x58, 0xe5, 0x69, 0xea, 0x8f, 0xc1, 0xf7, 0x92, 0x89, 0xec, 0xa1, 0xd, 0x9f, 0x68, 0x13, 0x3a}
|
||||
|
||||
func bobBundle() (*Bundle, error) {
|
||||
privateKey, err := crypto.ToECDSA([]byte(bobPrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedPreKey, err := crypto.ToECDSA([]byte(bobSignedPreKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compressedPreKey := crypto.CompressPubkey(&signedPreKey.PublicKey)
|
||||
|
||||
signature, err := crypto.Sign(crypto.Keccak256(compressedPreKey), privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedPreKeys := make(map[string]*SignedPreKey)
|
||||
signedPreKeys[bobInstallationID] = &SignedPreKey{SignedPreKey: compressedPreKey}
|
||||
|
||||
bundle := Bundle{
|
||||
Identity: crypto.CompressPubkey(&privateKey.PublicKey),
|
||||
SignedPreKeys: signedPreKeys,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
return &bundle, nil
|
||||
}
|
||||
|
||||
func TestNewBundleContainer(t *testing.T) {
|
||||
privateKey, err := crypto.ToECDSA([]byte(alicePrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
bundleContainer, err := NewBundleContainer(privateKey, bobInstallationID)
|
||||
require.NoError(t, err, "Bundle container should be created successfully")
|
||||
|
||||
err = SignBundle(privateKey, bundleContainer)
|
||||
require.NoError(t, err, "Bundle container should be signed successfully")
|
||||
|
||||
require.NoError(t, err, "Bundle container should be created successfully")
|
||||
|
||||
bundle := bundleContainer.Bundle
|
||||
require.NotNil(t, bundle, "Bundle should be generated without errors")
|
||||
|
||||
signatureMaterial := append([]byte(bobInstallationID), bundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()...)
|
||||
signatureMaterial = append(signatureMaterial, []byte("0")...)
|
||||
signatureMaterial = append(signatureMaterial, []byte(fmt.Sprint(bundle.GetTimestamp()))...)
|
||||
recoveredPublicKey, err := crypto.SigToPub(
|
||||
crypto.Keccak256(signatureMaterial),
|
||||
bundle.Signature,
|
||||
)
|
||||
|
||||
require.NoError(t, err, "Public key should be recovered from the bundle successfully")
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
privateKey.PublicKey,
|
||||
*recoveredPublicKey,
|
||||
"The correct public key should be recovered",
|
||||
)
|
||||
}
|
||||
|
||||
func TestSignBundle(t *testing.T) {
|
||||
privateKey, err := crypto.ToECDSA([]byte(alicePrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
bundleContainer1, err := NewBundleContainer(privateKey, "1")
|
||||
require.NoError(t, err, "Bundle container should be created successfully")
|
||||
|
||||
bundle1 := bundleContainer1.Bundle
|
||||
require.NotNil(t, bundle1, "Bundle should be generated without errors")
|
||||
|
||||
// We add a signed pre key
|
||||
signedPreKeys := bundle1.GetSignedPreKeys()
|
||||
signedPreKeys["2"] = &SignedPreKey{SignedPreKey: []byte("key")}
|
||||
|
||||
err = SignBundle(privateKey, bundleContainer1)
|
||||
require.NoError(t, err)
|
||||
|
||||
signatureMaterial := append([]byte("1"), bundle1.GetSignedPreKeys()["1"].GetSignedPreKey()...)
|
||||
signatureMaterial = append(signatureMaterial, []byte("0")...)
|
||||
signatureMaterial = append(signatureMaterial, []byte("2")...)
|
||||
signatureMaterial = append(signatureMaterial, []byte("key")...)
|
||||
signatureMaterial = append(signatureMaterial, []byte("0")...)
|
||||
signatureMaterial = append(signatureMaterial, []byte(fmt.Sprint(bundle1.GetTimestamp()))...)
|
||||
|
||||
recoveredPublicKey, err := crypto.SigToPub(
|
||||
crypto.Keccak256(signatureMaterial),
|
||||
bundleContainer1.GetBundle().Signature,
|
||||
)
|
||||
|
||||
require.NoError(t, err, "Public key should be recovered from the bundle successfully")
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
privateKey.PublicKey,
|
||||
*recoveredPublicKey,
|
||||
"The correct public key should be recovered",
|
||||
)
|
||||
}
|
||||
|
||||
func TestExtractIdentity(t *testing.T) {
|
||||
privateKey, err := crypto.ToECDSA([]byte(alicePrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
bundleContainer, err := NewBundleContainer(privateKey, "1")
|
||||
require.NoError(t, err, "Bundle container should be created successfully")
|
||||
|
||||
err = SignBundle(privateKey, bundleContainer)
|
||||
require.NoError(t, err, "Bundle container should be signed successfully")
|
||||
|
||||
bundle := bundleContainer.Bundle
|
||||
require.NotNil(t, bundle, "Bundle should be generated without errors")
|
||||
|
||||
recoveredPublicKey, err := ExtractIdentity(bundle)
|
||||
|
||||
require.NoError(t, err, "Public key should be recovered from the bundle successfully")
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
privateKey.PublicKey,
|
||||
*recoveredPublicKey,
|
||||
"The correct public key should be recovered",
|
||||
)
|
||||
}
|
||||
|
||||
// Alice wants to send a message to Bob
|
||||
func TestX3dhActive(t *testing.T) {
|
||||
bobIdentityKey, err := crypto.ToECDSA([]byte(bobPrivateKey))
|
||||
require.NoError(t, err, "Bundle identity key should be generated without errors")
|
||||
|
||||
bobSignedPreKey, err := crypto.ToECDSA([]byte(bobSignedPreKey))
|
||||
require.NoError(t, err, "Bundle signed pre key should be generated without errors")
|
||||
|
||||
aliceIdentityKey, err := crypto.ToECDSA([]byte(alicePrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
aliceEphemeralKey, err := crypto.ToECDSA([]byte(aliceEphemeralKey))
|
||||
require.NoError(t, err, "Ephemeral key should be generated without errors")
|
||||
|
||||
x3dh, err := x3dhActive(
|
||||
ecies.ImportECDSA(aliceIdentityKey),
|
||||
ecies.ImportECDSAPublic(&bobSignedPreKey.PublicKey),
|
||||
ecies.ImportECDSA(aliceEphemeralKey),
|
||||
ecies.ImportECDSAPublic(&bobIdentityKey.PublicKey),
|
||||
)
|
||||
require.NoError(t, err, "Shared key should be generated without errors")
|
||||
require.Equal(t, sharedKey, x3dh, "Should generate the correct key")
|
||||
}
|
||||
|
||||
// Bob receives a message from Alice
|
||||
func TestPerformPassiveX3DH(t *testing.T) {
|
||||
alicePrivateKey, err := crypto.ToECDSA([]byte(alicePrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
bobSignedPreKey, err := crypto.ToECDSA([]byte(bobSignedPreKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
aliceEphemeralKey, err := crypto.ToECDSA([]byte(aliceEphemeralKey))
|
||||
require.NoError(t, err, "Ephemeral key should be generated without errors")
|
||||
|
||||
bobPrivateKey, err := crypto.ToECDSA([]byte(bobPrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
||||
x3dh, err := PerformPassiveX3DH(
|
||||
&alicePrivateKey.PublicKey,
|
||||
bobSignedPreKey,
|
||||
&aliceEphemeralKey.PublicKey,
|
||||
bobPrivateKey,
|
||||
)
|
||||
require.NoError(t, err, "Shared key should be generated without errors")
|
||||
require.Equal(t, sharedKey, x3dh, "Should generate the correct key")
|
||||
}
|
||||
|
||||
func TestPerformActiveX3DH(t *testing.T) {
|
||||
bundle, err := bobBundle()
|
||||
require.NoError(t, err, "Test bundle should be generated without errors")
|
||||
|
||||
privateKey, err := crypto.ToECDSA([]byte(bobPrivateKey))
|
||||
require.NoError(t, err, "Private key should be imported without errors")
|
||||
|
||||
signedPreKey := bundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()
|
||||
|
||||
actualSharedSecret, actualEphemeralKey, err := PerformActiveX3DH(bundle.GetIdentity(), signedPreKey, privateKey)
|
||||
require.NoError(t, err, "No error should be reported")
|
||||
require.NotNil(t, actualEphemeralKey, "An ephemeral key-pair should be generated")
|
||||
require.NotNil(t, actualSharedSecret, "A shared key should be generated")
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user