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:
Pedro Pombeiro 2019-11-21 17:19:22 +01:00 committed by GitHub
parent 2dd74da23d
commit ed5a5c154d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2389 changed files with 1074609 additions and 485 deletions

View File

@ -9,3 +9,9 @@ update_configs:
default_assignees: default_assignees:
- "adambabik" - "adambabik"
- "cammellos" - "cammellos"
- package_manager: "go:modules"
directory: "/protocol"
update_schedule: "weekly"
default_assignees:
- "adambabik"
- "cammellos"

View File

@ -1,7 +1,6 @@
{ {
"recipients": [ "recipients": [
"adambabik", "adambabik",
"mandrigin",
"corpetty" "corpetty"
] ]
} }

View File

@ -19,7 +19,7 @@ import (
"github.com/status-im/status-go/account/generator" "github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/extkeys" "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 // errors
@ -325,7 +325,7 @@ func (m *Manager) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password
return address, "", err return address, "", err
} }
pubKey = statusproto.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey)) pubKey = protocol.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
return return
} }
@ -349,7 +349,7 @@ func (m *Manager) importExtendedKey(keyPurpose extkeys.KeyPurpose, extKey *extke
if err != nil { if err != nil {
return address, "", err return address, "", err
} }
pubKey = statusproto.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey)) pubKey = protocol.EncodeHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
return return
} }

View File

@ -2,7 +2,7 @@ package account
import ( import (
"github.com/ethereum/go-ethereum/crypto" "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) { 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) pubKeyBytes := crypto.FromECDSAPub(&key.PublicKey)
addressBytes := crypto.PubkeyToAddress(key.PublicKey) addressBytes := crypto.PubkeyToAddress(key.PublicKey)
privKey = statusproto.EncodeHex(privKeyBytes) privKey = protocol.EncodeHex(privKeyBytes)
pubKey = statusproto.EncodeHex(pubKeyBytes) pubKey = protocol.EncodeHex(pubKeyBytes)
address = addressBytes.Hex() address = addressBytes.Hex()
return return

View File

@ -5,7 +5,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/extkeys" "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 { type account struct {
@ -14,7 +14,7 @@ type account struct {
} }
func (a *account) toAccountInfo() AccountInfo { 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() addressHex := crypto.PubkeyToAddress(a.privateKey.PublicKey).Hex()
return AccountInfo{ return AccountInfo{

View File

@ -7,7 +7,7 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/status-im/status-go/account/generator" "github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/extkeys" "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. // 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() privateKeyECDSA := extendedKey.ToECDSA()
address := crypto.PubkeyToAddress(privateKeyECDSA.PublicKey) address := crypto.PubkeyToAddress(privateKeyECDSA.PublicKey)
publicKeyHex := statusproto.EncodeHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey)) publicKeyHex := protocol.EncodeHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
return address.Hex(), publicKeyHex, nil return address.Hex(), publicKeyHex, nil
} }

View File

@ -28,6 +28,7 @@ import (
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/node" "github.com/status-im/status-go/node"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
protocol "github.com/status-im/status-go/protocol/types"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
accountssvc "github.com/status-im/status-go/services/accounts" accountssvc "github.com/status-im/status-go/services/accounts"
"github.com/status-im/status-go/services/browsers" "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/services/wallet"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
statusproto "github.com/status-im/status-protocol-go/types"
) )
const ( const (
@ -1022,6 +1022,6 @@ func (b *StatusBackend) SignHash(hexEncodedHash string) (string, error) {
return "", fmt.Errorf("SignHash: could not sign the hash: %v", err) return "", fmt.Errorf("SignHash: could not sign the hash: %v", err)
} }
hexEncodedSignature := statusproto.EncodeHex(signature) hexEncodedSignature := protocol.EncodeHex(signature)
return hexEncodedSignature, nil return hexEncodedSignature, nil
} }

View File

@ -18,15 +18,15 @@ import (
"github.com/status-im/status-go/api" "github.com/status-im/status-go/api"
"github.com/status-im/status-go/logutils" "github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/params" "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/rpc"
"github.com/status-im/status-go/services/shhext" "github.com/status-im/status-go/services/shhext"
"github.com/status-im/status-go/t/helpers" "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/sha3"
"golang.org/x/crypto/ssh/terminal" "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 ( const (
@ -182,7 +182,7 @@ func verifyMailserverBehavior(mailserverNode *enode.Node) {
logger.Error("Error requesting historic messages from mailserver", "error", err) logger.Error("Error requesting historic messages from mailserver", "error", err)
os.Exit(2) os.Exit(2)
} }
requestID := statusproto.BytesToHash(requestIDBytes) requestID := protocol.BytesToHash(requestIDBytes)
// wait for mailserver request sent event // wait for mailserver request sent event
err = waitForMailServerRequestSent(mailServerResponseWatcher, requestID, time.Duration(*timeout)*time.Second) 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 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) timeoutTimer := time.NewTimer(timeout)
for { for {
select { 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) timeoutTimer := time.NewTimer(timeout)
for { for {
select { select {
@ -408,7 +408,7 @@ func waitForEnvelopeEvents(events chan whispertypes.EnvelopeEvent, hashes []stri
} }
// helper for checking LastEnvelopeHash // helper for checking LastEnvelopeHash
func isEmptyEnvelope(hash statusproto.Hash) bool { func isEmptyEnvelope(hash protocol.Hash) bool {
for _, b := range hash { for _, b := range hash {
if b != 0 { if b != 0 {
return false return false

View File

@ -6,8 +6,8 @@ import (
"errors" "errors"
"time" "time"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
) )
@ -57,7 +57,7 @@ type TopicHistory struct {
Current time.Time Current time.Time
End time.Time End time.Time
RequestID statusproto.Hash RequestID protocol.Hash
} }
// Key returns unique identifier for this TopicHistory. // 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. // Pending returns true if this topic was requested from a mail server.
func (t TopicHistory) Pending() bool { 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. // HistoryRequest is kept in the database while request is in the progress.
@ -127,7 +127,7 @@ type HistoryRequest struct {
histories []TopicHistory histories []TopicHistory
// Generated ID // Generated ID
ID statusproto.Hash ID protocol.Hash
// List of the topics // List of the topics
TopicHistoryKeys []TopicHistoryKey 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. // Replace saves request with new ID and all data attached to the old one.
func (req HistoryRequest) Replace(id statusproto.Hash) error { func (req HistoryRequest) Replace(id protocol.Hash) error {
if (req.ID != statusproto.Hash{}) { if (req.ID != protocol.Hash{}) {
if err := req.Delete(); err != nil { if err := req.Delete(); err != nil {
return err return err
} }

View File

@ -3,8 +3,8 @@ package db
import ( import (
"time" "time"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
"github.com/syndtr/goleveldb/leveldb/errors" "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. // 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} req := HistoryRequest{requestDB: h.requestDB, topicDB: h.topicDB, ID: id}
err := req.Load() err := req.Load()
if err != nil { if err != nil {

View File

@ -4,8 +4,8 @@ import (
"testing" "testing"
"time" "time"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -43,7 +43,7 @@ func TestGetExistingHistory(t *testing.T) {
func TestNewHistoryRequest(t *testing.T) { func TestNewHistoryRequest(t *testing.T) {
store := createInMemStore(t) store := createInMemStore(t)
id := statusproto.Hash{1} id := protocol.Hash{1}
req, err := store.GetRequest(id) req, err := store.GetRequest(id)
require.Error(t, err) require.Error(t, err)
req = store.NewRequest() req = store.NewRequest()
@ -61,8 +61,8 @@ func TestNewHistoryRequest(t *testing.T) {
func TestGetAllRequests(t *testing.T) { func TestGetAllRequests(t *testing.T) {
store := createInMemStore(t) store := createInMemStore(t)
idOne := statusproto.Hash{1} idOne := protocol.Hash{1}
idTwo := statusproto.Hash{2} idTwo := protocol.Hash{2}
req := store.NewRequest() req := store.NewRequest()
req.ID = idOne req.ID = idOne

View File

@ -4,8 +4,8 @@ import (
"testing" "testing"
"time" "time"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -80,7 +80,7 @@ func TestAddHistory(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
th := TopicHistory{db: topicdb, Topic: topic, Current: now} 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 := HistoryRequest{requestDB: requestdb, topicDB: topicdb, ID: id}
req.AddHistory(th) req.AddHistory(th)

4
go.mod
View File

@ -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/gomarkdown/markdown => github.com/status-im/markdown v0.0.0-20191113114344-af599402d015
replace github.com/status-im/status-go/protocol => ./protocol
require ( require (
github.com/beevik/ntp v0.2.0 github.com/beevik/ntp v0.2.0
github.com/btcsuite/btcd v0.0.0-20191011042131-c3151ef50de9 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/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
github.com/status-im/migrate/v4 v4.6.2-status.2 github.com/status-im/migrate/v4 v4.6.2-status.2
github.com/status-im/rendezvous v1.3.0 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/tcp-shaker v0.0.0-20191114194237-215893130501
github.com/status-im/whisper v1.6.1 github.com/status-im/whisper v1.6.1
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0

2
go.sum
View File

@ -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/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 h1:7RK/MXXW+tlm0asKm1u7Qp7Yni6AO29a7j8+E4Lbjg4=
github.com/status-im/rendezvous v1.3.0/go.mod h1:+hzjuP+j/XzLPeF6E50b88pWOTLdTcwjvNYt+Gh1W1s= 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 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/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= github.com/status-im/whisper v1.5.2 h1:26NgiKusmPic38eQdtXnaY+iaQ/LuQ3Dh0kCGYT/Uxs=

View File

@ -18,11 +18,11 @@ import (
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/profiling" "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/personal"
"github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
protocol "github.com/status-im/status-protocol-go"
validator "gopkg.in/go-playground/validator.v9" validator "gopkg.in/go-playground/validator.v9"
) )

View File

@ -31,10 +31,10 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/multiaccounts/accounts" "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/signal"
. "github.com/status-im/status-go/t/utils" //nolint: golint . "github.com/status-im/status-go/t/utils" //nolint: golint
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
statusproto "github.com/status-im/status-protocol-go/types"
"github.com/stretchr/testify/require" "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) 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) { if whisperService.HasKeyPair(chatPubKeyHex) {
t.Error("identity already present in whisper") t.Error("identity already present in whisper")
return false return false

View File

@ -9,7 +9,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/status-im/status-protocol-go/zaputil" "github.com/status-im/status-go/protocol/zaputil"
) )
type gethLoggerCore struct { type gethLoggerCore struct {

View File

@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
whisper "github.com/status-im/whisper/whisperv6" whisper "github.com/status-im/whisper/whisperv6"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/syndtr/goleveldb/leveldb" "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 { func countMessages(t *testing.T, db DB) int {
var ( var (
count int count int
zero statusproto.Hash zero protocol.Hash
emptyTopic whispertypes.TopicType emptyTopic whispertypes.TopicType
) )

View File

@ -4,14 +4,14 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
) )
const ( const (
// DBKeyLength is a size of the envelope key. // DBKeyLength is a size of the envelope key.
DBKeyLength = statusproto.HashLength + timestampLength + whispertypes.TopicLength DBKeyLength = protocol.HashLength + timestampLength + whispertypes.TopicLength
CursorLength = statusproto.HashLength + timestampLength CursorLength = protocol.HashLength + timestampLength
) )
var ( var (
@ -31,11 +31,11 @@ func (k *DBKey) Bytes() []byte {
} }
func (k *DBKey) Topic() whispertypes.TopicType { 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 { func (k *DBKey) EnvelopeHash() protocol.Hash {
return statusproto.BytesToHash(k.raw[timestampLength : statusproto.HashLength+timestampLength]) return protocol.BytesToHash(k.raw[timestampLength : protocol.HashLength+timestampLength])
} }
func (k *DBKey) Cursor() []byte { func (k *DBKey) Cursor() []byte {
@ -44,11 +44,11 @@ func (k *DBKey) Cursor() []byte {
} }
// NewDBKey creates a new DBKey with the given values. // 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 var k DBKey
k.raw = make([]byte, DBKeyLength) k.raw = make([]byte, DBKeyLength)
binary.BigEndian.PutUint32(k.raw, timestamp) binary.BigEndian.PutUint32(k.raw, timestamp)
copy(k.raw[timestampLength:], h[:]) copy(k.raw[timestampLength:], h[:])
copy(k.raw[timestampLength+statusproto.HashLength:], topic[:]) copy(k.raw[timestampLength+protocol.HashLength:], topic[:])
return &k return &k
} }

View File

@ -3,15 +3,15 @@ package mailserver
import ( import (
"testing" "testing"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestNewDBKey(t *testing.T) { func TestNewDBKey(t *testing.T) {
topic := whispertypes.BytesToTopic([]byte{0x01, 0x02, 0x03, 0x04}) 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) 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} 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()) require.Equal(t, expected, dbKey.Bytes())

View File

@ -30,8 +30,8 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
whisper "github.com/status-im/whisper/whisperv6" whisper "github.com/status-im/whisper/whisperv6"
prom "github.com/prometheus/client_golang/prometheus" prom "github.com/prometheus/client_golang/prometheus"
@ -199,7 +199,7 @@ func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope)
return return
} }
requestID := statusproto.Hash(request.Hash()) requestID := protocol.Hash(request.Hash())
peerID := peerIDString(peer) peerID := peerIDString(peer)
log.Info("[mailserver:DeliverMail] delivering mail", log.Info("[mailserver:DeliverMail] delivering mail",
@ -356,7 +356,7 @@ func (s *WMailServer) Deliver(peer *whisper.Peer, r whisper.MessagesRequest) {
deliveryAttemptsCounter.Inc() deliveryAttemptsCounter.Inc()
var ( var (
requestIDHash = statusproto.BytesToHash(r.ID) requestIDHash = protocol.BytesToHash(r.ID)
requestIDStr = requestIDHash.String() requestIDStr = requestIDHash.String()
peerID = peerIDString(peer) peerID = peerIDString(peer)
err error 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) { func (s *WMailServer) createIterator(lower, upper uint32, cursor []byte, bloom []byte, limit uint32) (Iterator, error) {
var ( var (
emptyHash statusproto.Hash emptyHash protocol.Hash
emptyTopic whispertypes.TopicType emptyTopic whispertypes.TopicType
ku, kl *DBKey ku, kl *DBKey
) )
@ -620,7 +620,7 @@ func (s *WMailServer) processRequestInBundles(
requestID string, requestID string,
output chan<- []rlp.RawValue, output chan<- []rlp.RawValue,
cancel <-chan struct{}, cancel <-chan struct{},
) ([]byte, statusproto.Hash) { ) ([]byte, protocol.Hash) {
timer := prom.NewTimer(requestsInBundlesDuration) timer := prom.NewTimer(requestsInBundlesDuration)
defer timer.ObserveDuration() defer timer.ObserveDuration()
@ -631,7 +631,7 @@ func (s *WMailServer) processRequestInBundles(
processedEnvelopes int processedEnvelopes int
processedEnvelopesSize int64 processedEnvelopesSize int64
nextCursor []byte nextCursor []byte
lastEnvelopeHash statusproto.Hash lastEnvelopeHash protocol.Hash
) )
log.Info("[mailserver:processRequestInBundles] processing request", log.Info("[mailserver:processRequestInBundles] processing request",
@ -760,13 +760,13 @@ func (s *WMailServer) sendRawEnvelopes(peer *whisper.Peer, envelopes []rlp.RawVa
return nil 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) payload := whisper.CreateMailServerRequestCompletedPayload(common.Hash(requestID), common.Hash(lastEnvelopeHash), cursor)
return s.w.SendHistoricMessageResponse(peer, payload) return s.w.SendHistoricMessageResponse(peer, payload)
} }
// this method doesn't return an error because it is already in the error handling chain // 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) payload := whisper.CreateMailServerRequestFailedPayload(common.Hash(requestID), errorToReport)
err := s.w.SendHistoricMessageResponse(peer, payload) err := s.w.SendHistoricMessageResponse(peer, payload)

View File

@ -7,8 +7,8 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
whisper "github.com/status-im/whisper/whisperv6" whisper "github.com/status-im/whisper/whisperv6"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors" "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) { func (db *LevelDB) Prune(t time.Time, batchSize int) (int, error) {
defer recoverLevelDBPanics("Prune") defer recoverLevelDBPanics("Prune")
var zero statusproto.Hash var zero protocol.Hash
var emptyTopic whispertypes.TopicType var emptyTopic whispertypes.TopicType
kl := NewDBKey(0, emptyTopic, zero) kl := NewDBKey(0, emptyTopic, zero)
ku := NewDBKey(uint32(t.Unix()), 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 { func (db *LevelDB) SaveEnvelope(env *whisper.Envelope) error {
defer recoverLevelDBPanics("SaveEnvelope") 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) rawEnvelope, err := rlp.EncodeToBytes(env)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err)) log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))

View File

@ -15,8 +15,8 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
whisper "github.com/status-im/whisper/whisperv6" 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) { func (i *PostgresDB) Prune(t time.Time, batch int) (int, error) {
var zero statusproto.Hash var zero protocol.Hash
var emptyTopic whispertypes.TopicType var emptyTopic whispertypes.TopicType
kl := NewDBKey(0, emptyTopic, zero) kl := NewDBKey(0, emptyTopic, zero)
ku := NewDBKey(uint32(t.Unix()), 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 { func (i *PostgresDB) SaveEnvelope(env *whisper.Envelope) error {
topic := whispertypes.TopicType(env.Topic) 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) rawEnvelope, err := rlp.EncodeToBytes(env)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err)) log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))

View File

@ -33,8 +33,8 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
whispertypes "github.com/status-im/status-protocol-go/transport/whisper/types" whispertypes "github.com/status-im/status-go/protocol/transport/whisper/types"
statusproto "github.com/status-im/status-protocol-go/types" protocol "github.com/status-im/status-go/protocol/types"
whisper "github.com/status-im/whisper/whisperv6" whisper "github.com/status-im/whisper/whisperv6"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -259,7 +259,7 @@ func (s *MailserverSuite) TestArchive() {
s.NoError(err) s.NoError(err)
s.server.Archive(env) 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) archivedEnvelope, err := s.server.db.GetEnvelope(key)
s.NoError(err) s.NoError(err)
@ -279,7 +279,7 @@ func (s *MailserverSuite) TestManageLimits() {
} }
func (s *MailserverSuite) TestDBKey() { func (s *MailserverSuite) TestDBKey() {
var h statusproto.Hash var h protocol.Hash
var emptyTopic whispertypes.TopicType var emptyTopic whispertypes.TopicType
i := uint32(time.Now().Unix()) i := uint32(time.Now().Unix())
k := NewDBKey(i, emptyTopic, h) k := NewDBKey(i, emptyTopic, h)
@ -307,7 +307,7 @@ func (s *MailserverSuite) TestRequestPaginationLimit() {
env, err := generateEnvelope(sentTime) env, err := generateEnvelope(sentTime)
s.NoError(err) s.NoError(err)
s.server.Archive(env) 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())) archiveKeys = append(archiveKeys, fmt.Sprintf("%x", key.Cursor()))
sentEnvelopes = append(sentEnvelopes, env) sentEnvelopes = append(sentEnvelopes, env)
sentHashes = append(sentHashes, env.Hash()) sentHashes = append(sentHashes, env.Hash())
@ -771,7 +771,7 @@ func generateEnvelope(sentTime time.Time) (*whisper.Envelope, error) {
func processRequestAndCollectHashes( func processRequestAndCollectHashes(
server *WMailServer, lower, upper uint32, cursor []byte, bloom []byte, limit int, 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) iter, _ := server.createIterator(lower, upper, cursor, nil, 0)
defer iter.Release() defer iter.Release()
bundles := make(chan []rlp.RawValue, 10) bundles := make(chan []rlp.RawValue, 10)

View File

@ -16,11 +16,11 @@ import (
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/profiling" "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/personal"
"github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
protocol "github.com/status-im/status-protocol-go"
validator "gopkg.in/go-playground/validator.v9" validator "gopkg.in/go-playground/validator.v9"
) )

View File

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/status-im/status-go/mailserver" "github.com/status-im/status-go/mailserver"
"github.com/status-im/status-go/params" "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/incentivisation"
"github.com/status-im/status-go/services/peer" "github.com/status-im/status-go/services/peer"
"github.com/status-im/status-go/services/personal" "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/services/whisperbridge"
"github.com/status-im/status-go/static" "github.com/status-im/status-go/static"
"github.com/status-im/status-go/timesource" "github.com/status-im/status-go/timesource"
gethbridge "github.com/status-im/status-protocol-go/bridge/geth"
whisper "github.com/status-im/whisper/whisperv6" whisper "github.com/status-im/whisper/whisperv6"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
) )
@ -316,7 +316,7 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb
return return
} }
// Register Whisper status-protocol-go bridge // Register Whisper status-go/protocol bridge
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var whisper *whisper.Whisper var whisper *whisper.Whisper
if err := ctx.Service(&whisper); err != nil { if err := ctx.Service(&whisper); err != nil {

View File

@ -2,7 +2,7 @@ GO111MODULE = on
ENABLE_METRICS ?= true ENABLE_METRICS ?= true
BUILD_FLAGS ?= $(shell echo "-ldflags '\ 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: test:
go test ./... go test ./...

View File

@ -1,4 +1,4 @@
# status-protocol-go # status-go/protocol
This is the Status Protocol implementation in Go. This is the Status Protocol implementation in Go.

View 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()
}

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

View 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,
}
}

View File

@ -1,7 +1,7 @@
package gethbridge package gethbridge
import ( 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" whisper "github.com/status-im/whisper/whisperv6"
) )

View 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,
}
}

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

View File

@ -2,7 +2,7 @@ package gethbridge
import ( import (
"github.com/ethereum/go-ethereum/event" "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 { type gethSubscriptionWrapper struct {

View File

@ -1,7 +1,7 @@
package gethbridge package gethbridge
import ( 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" whisper "github.com/status-im/whisper/whisperv6"
) )

View File

@ -1,7 +1,7 @@
package gethbridge package gethbridge
import ( 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" whisper "github.com/status-im/whisper/whisperv6"
) )

View File

@ -4,7 +4,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"time" "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" whisper "github.com/status-im/whisper/whisperv6"
) )

View 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"

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

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

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

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

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

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

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

View File

@ -4,7 +4,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"github.com/golang/protobuf/proto" "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" datasyncnode "github.com/vacp2p/mvds/node"
datasyncproto "github.com/vacp2p/mvds/protobuf" datasyncproto "github.com/vacp2p/mvds/protobuf"
datasynctransport "github.com/vacp2p/mvds/transport" datasynctransport "github.com/vacp2p/mvds/transport"

View File

@ -5,7 +5,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"errors" "errors"
"github.com/golang/protobuf/proto" "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/protobuf"
"github.com/vacp2p/mvds/state" "github.com/vacp2p/mvds/state"
"github.com/vacp2p/mvds/transport" "github.com/vacp2p/mvds/transport"

View 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"])
}

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

View File

@ -13,8 +13,8 @@ import (
dr "github.com/status-im/doubleratchet" dr "github.com/status-im/doubleratchet"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/status-im/status-protocol-go/crypto" "github.com/status-im/status-go/protocol/crypto"
"github.com/status-im/status-protocol-go/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
) )
var ( var (

View 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, "/")...)...)
}

View File

@ -0,0 +1,4 @@
DROP TABLE sessions;
DROP TABLE bundles;
DROP TABLE keys;
DROP TABLE ratchet_info;

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
ALTER TABLE keys DROP COLUMN session_id;
ALTER TABLE sessions DROP COLUMN keys_count;
ALTER TABLE bundles DROP COLUMN version;

View File

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

View File

@ -0,0 +1 @@
DROP TABLE installations;

View File

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

View File

@ -0,0 +1,2 @@
DROP TABLE secret_installation_ids;
DROP TABLE secrets;

View File

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

View File

@ -0,0 +1 @@
ALTER TABLE installations DROP COLUMN version;

View File

@ -0,0 +1 @@
ALTER TABLE installations ADD version INTEGER DEFAULT 0;

View File

@ -0,0 +1 @@
DROP TABLE contact_code_config;

View File

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

View File

@ -0,0 +1 @@
DROP TABLE installations_metadata;

View File

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

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

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

View File

@ -8,8 +8,8 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
dr "github.com/status-im/doubleratchet" dr "github.com/status-im/doubleratchet"
ecrypto "github.com/status-im/status-protocol-go/crypto" ecrypto "github.com/status-im/status-go/protocol/crypto"
"github.com/status-im/status-protocol-go/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
) )
// RatchetInfo holds the current ratchet state. // RatchetInfo holds the current ratchet state.

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

View 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

View File

@ -11,9 +11,9 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/status-im/status-protocol-go/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-protocol-go/encryption/publisher" "github.com/status-im/status-go/protocol/encryption/publisher"
"github.com/status-im/status-protocol-go/encryption/sharedsecret" "github.com/status-im/status-go/protocol/encryption/sharedsecret"
) )
//go:generate protoc --go_out=. ./protocol_message.proto //go:generate protoc --go_out=. ./protocol_message.proto

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

View 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

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

View 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