diff --git a/broadcast/asymmetric.go b/broadcast/asymmetric.go new file mode 100644 index 0000000000000000000000000000000000000000..79fc4b4d0988319d6f9e0380fa6bbb4cd6d038ff --- /dev/null +++ b/broadcast/asymmetric.go @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/multicastRSA" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" +) + +const ( + asymmetricBroadcastServiceTag = "AsymmBcast" + asymmCMixSendTag = "AsymmetricBroadcast" +) + +// MaxAsymmetricPayloadSize returns the maximum size for an asymmetric broadcast payload +func (bc *broadcastClient) maxAsymmetricPayload() int { + return bc.maxParts() * bc.channel.MaxAsymmetricPayloadSize() +} + +// BroadcastAsymmetric broadcasts the payload to the channel. Requires a healthy network state to send +// Payload must be equal to bc.MaxAsymmetricPayloadSize, and the channel PrivateKey must be passed in +// Broadcast method must be set to asymmetric +// When a payload is sent, it is split into partitons of size bc.channel.MaxAsymmetricPayloadSize +// which are each encrypted using multicastRSA +func (bc *broadcastClient) BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + if bc.param.Method != Asymmetric { + return 0, ephemeral.Id{}, errors.Errorf(errBroadcastMethodType, Asymmetric, bc.param.Method) + } + + if !bc.net.IsHealthy() { + return 0, ephemeral.Id{}, errors.New(errNetworkHealth) + } + + if len(payload) != bc.maxAsymmetricPayload() { + return 0, ephemeral.Id{}, + errors.Errorf(errPayloadSize, len(payload), bc.maxAsymmetricPayload()) + } + + numParts := bc.maxParts() + size := bc.channel.MaxAsymmetricPayloadSize() + var mac []byte + var fp format.Fingerprint + var sequential []byte + for i := 0; i < numParts; i++ { + // Encrypt payload to send using asymmetric channel + var encryptedPayload []byte + var err error + encryptedPayload, mac, fp, err = bc.channel.EncryptAsymmetric(payload[:size], pk, bc.rng.GetStream()) + if err != nil { + return 0, ephemeral.Id{}, errors.WithMessage(err, "Failed to encrypt asymmetric broadcast message") + } + payload = payload[size:] + sequential = append(sequential, encryptedPayload...) + } + + // Create service object to send message + service := message.Service{ + Identifier: bc.channel.ReceptionID.Bytes(), + Tag: asymmetricBroadcastServiceTag, + } + + if cMixParams.DebugTag == cmix.DefaultDebugTag { + cMixParams.DebugTag = asymmCMixSendTag + } + + sizedPayload, err := NewSizedBroadcast(bc.net.GetMaxMessageLength(), sequential) + if err != nil { + return id.Round(0), ephemeral.Id{}, err + } + + return bc.net.Send( + bc.channel.ReceptionID, fp, service, sizedPayload, mac, cMixParams) +} + +// Helper function for maximum number of encrypted message parts +func (bc *broadcastClient) maxParts() int { + encPartSize := bc.channel.RsaPubKey.Size() + maxSend := bc.net.GetMaxMessageLength() + return maxSend / encPartSize +} diff --git a/broadcast/asymmetric_test.go b/broadcast/asymmetric_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2df1c2eb3d5c46de22c0c530b01f08ab97e6cd94 --- /dev/null +++ b/broadcast/asymmetric_test.go @@ -0,0 +1,139 @@ +package broadcast + +import ( + "bytes" + "fmt" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + crypto "gitlab.com/elixxir/crypto/broadcast" + cMixCrypto "gitlab.com/elixxir/crypto/cmix" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/crypto/signature/rsa" + "reflect" + "sync" + "testing" + "time" +) + +// Tests that symmetricClient adheres to the Symmetric interface. +var _ Channel = (*broadcastClient)(nil) + +// Tests that symmetricClient adheres to the Symmetric interface. +var _ Client = (cmix.Client)(nil) + +func Test_asymmetricClient_Smoke(t *testing.T) { + cMixHandler := newMockCmixHandler() + rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) + pk, err := rsa.GenerateKey(rngGen.GetStream(), 4096) + if err != nil { + t.Fatalf("Failed to generate priv key: %+v", err) + } + cname := "MyChannel" + cdesc := "This is my channel about stuff." + csalt := cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32) + cpubkey := pk.GetPublic() + cid, err := crypto.NewChannelID(cname, cdesc, csalt, rsa.CreatePublicKeyPem(cpubkey)) + if err != nil { + t.Errorf("Failed to create channel ID: %+v", err) + } + channel := crypto.Channel{ + ReceptionID: cid, + Name: cname, + Description: cdesc, + Salt: csalt, + RsaPubKey: cpubkey, + } + + const n = 5 + cbChans := make([]chan []byte, n) + clients := make([]Channel, n) + for i := range clients { + cbChan := make(chan []byte, 10) + cb := func(payload []byte, _ receptionID.EphemeralIdentity, + _ rounds.Round) { + cbChan <- payload + } + + s, err := NewBroadcastChannel(channel, cb, newMockCmix(cMixHandler), rngGen, Param{Method: Asymmetric}) + if err != nil { + t.Errorf("Failed to create broadcast channel: %+v", err) + } + + cbChans[i] = cbChan + clients[i] = s + + // Test that Get returns the expected channel + if !reflect.DeepEqual(s.Get(), channel) { + t.Errorf("Client %d returned wrong channel."+ + "\nexpected: %+v\nreceived: %+v", i, channel, s.Get()) + } + } + + // Send broadcast from each client + for i := range clients { + payload := make([]byte, clients[i].MaxPayloadSize()) + copy(payload, + fmt.Sprintf("Hello from client %d of %d.", i, len(clients))) + + // Start processes that waits for each client to receive broadcast + var wg sync.WaitGroup + for j := range cbChans { + wg.Add(1) + go func(i, j int, cbChan chan []byte) { + defer wg.Done() + select { + case r := <-cbChan: + if !bytes.Equal(payload, r) { + t.Errorf("Client %d failed to receive expected "+ + "payload from client %d."+ + "\nexpected: %q\nreceived: %q", j, i, payload, r) + } + case <-time.After(time.Second): + t.Errorf("Client %d timed out waiting for broadcast "+ + "payload from client %d.", j, i) + } + }(i, j, cbChans[j]) + } + + // Broadcast payload + _, _, err := clients[i].BroadcastAsymmetric(pk, payload, cmix.GetDefaultCMIXParams()) + if err != nil { + t.Errorf("Client %d failed to send broadcast: %+v", i, err) + } + + // Wait for all clients to receive payload or time out + wg.Wait() + } + + // Stop each client + for i := range clients { + clients[i].Stop() + } + + payload := make([]byte, clients[0].MaxPayloadSize()) + copy(payload, "This message should not get through.") + + // Start waiting on channels and error if anything is received + var wg sync.WaitGroup + for i := range cbChans { + wg.Add(1) + go func(i int, cbChan chan []byte) { + defer wg.Done() + select { + case r := <-cbChan: + t.Errorf("Client %d received message: %q", i, r) + case <-time.After(25 * time.Millisecond): + } + }(i, cbChans[i]) + } + + // Broadcast payload + _, _, err = clients[0].BroadcastAsymmetric(pk, payload, cmix.GetDefaultCMIXParams()) + if err != nil { + t.Errorf("Client 0 failed to send broadcast: %+v", err) + } + + wg.Wait() +} diff --git a/broadcast/broadcastClient.go b/broadcast/broadcastClient.go new file mode 100644 index 0000000000000000000000000000000000000000..54555a3875db03af4331438e0dcb92922ad3aa06 --- /dev/null +++ b/broadcast/broadcastClient.go @@ -0,0 +1,111 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/identity" + "gitlab.com/elixxir/client/cmix/message" + crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/signature/rsa" +) + +// Param encapsulates configuration options for a broadcastClient +type Param struct { + Method Method +} + +// broadcastClient implements the Channel interface for sending/receiving asymmetric or symmetric broadcast messages +type broadcastClient struct { + channel crypto.Channel + net Client + rng *fastRNG.StreamGenerator + param Param +} + +// NewBroadcastChannel creates a channel interface based on crypto.Channel, accepts net client connection & callback for received messages +func NewBroadcastChannel(channel crypto.Channel, listenerCb ListenerFunc, net Client, rng *fastRNG.StreamGenerator, param Param) (Channel, error) { + bc := &broadcastClient{ + channel: channel, + net: net, + rng: rng, + param: param, + } + + if !bc.verifyID() { + jww.FATAL.Panicf("Failed ID verification for broadcast channel") + } + + // Add channel's identity + net.AddIdentity(channel.ReceptionID, identity.Forever, true) + + p := &processor{ + c: &channel, + cb: listenerCb, + method: param.Method, + } + var tag string + switch param.Method { + case Symmetric: + tag = symmetricBroadcastServiceTag + case Asymmetric: + tag = asymmetricBroadcastServiceTag + default: + return nil, errors.Errorf("Cannot make broadcast client for unknown broadcast method %s", param.Method) + } + service := message.Service{ + Identifier: channel.ReceptionID.Bytes(), + Tag: tag, + } + + net.AddService(channel.ReceptionID, service, p) + + jww.INFO.Printf("New %s broadcast client created for channel %q (%s)", + param.Method, channel.Name, channel.ReceptionID) + + return bc, nil +} + +// Stop unregisters the listener callback and stops the channel's identity +// from being tracked. +func (bc *broadcastClient) Stop() { + // Removes currently tracked identity + bc.net.RemoveIdentity(bc.channel.ReceptionID) + + // Delete all registered services + bc.net.DeleteClientService(bc.channel.ReceptionID) +} + +// Get returns the underlying crypto.Channel object +func (bc *broadcastClient) Get() crypto.Channel { + return bc.channel +} + +// verifyID generates a symmetric ID based on the info in the channel & compares it to the one passed in +// TODO: it seems very odd to me that we do this, rather than just making the ID a private/ephemeral component like the key +func (bc *broadcastClient) verifyID() bool { + gen, err := crypto.NewChannelID(bc.channel.Name, bc.channel.Description, bc.channel.Salt, rsa.CreatePublicKeyPem(bc.channel.RsaPubKey)) + if err != nil { + jww.FATAL.Panicf("[verifyID] Failed to generate verified channel ID") + return false + } + return bc.channel.ReceptionID.Cmp(gen) +} + +func (bc *broadcastClient) MaxPayloadSize() int { + switch bc.param.Method { + case Symmetric: + return bc.maxSymmetricPayload() + case Asymmetric: + return bc.maxAsymmetricPayload() + default: + return -1 + } +} diff --git a/broadcast/interface.go b/broadcast/interface.go index 1da049fc8807fd0d170242b4d1c0de9ff68060e4..2c7d9e33820ce78a1532b693ace989fa7ea386f2 100644 --- a/broadcast/interface.go +++ b/broadcast/interface.go @@ -10,33 +10,54 @@ package broadcast import ( "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/cmix/rounds" crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/multicastRSA" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" + "time" ) -// ListenerFunc is registered when creating a new symmetric broadcasting channel +// ListenerFunc is registered when creating a new broadcasting channel // and receives all new broadcast messages for the channel. type ListenerFunc func(payload []byte, receptionID receptionID.EphemeralIdentity, round rounds.Round) -// Symmetric manages the listening and broadcasting of a symmetric broadcast -// channel. -type Symmetric interface { - // MaxPayloadSize returns the maximum size for a broadcasted payload. +type Channel interface { + // MaxPayloadSize returns the maximum size for a broadcast payload. Different math depending on broadcast method. MaxPayloadSize() int - // Get returns the crypto.Symmetric object containing the cryptographic and - // identifying information about the channel. - Get() crypto.Symmetric + // Get returns the underlying crypto.Channel + Get() crypto.Channel // Broadcast broadcasts the payload to the channel. The payload size must be // equal to MaxPayloadSize. Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) + // BroadcastAsymmetric broadcasts an asymmetric payload to the channel. The payload size must be + // equal to MaxPayloadSize & private key for channel must be passed in + BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) + // Stop unregisters the listener callback and stops the channel's identity // from being tracked. Stop() } + +// Client contains the methods from cmix.Client that are required by +// symmetricClient. +type Client interface { + GetMaxMessageLength() int + Send(recipient *id.ID, fingerprint format.Fingerprint, + service message.Service, payload, mac []byte, + cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) + IsHealthy() bool + AddIdentity(id *id.ID, validUntil time.Time, persistent bool) + AddService(clientID *id.ID, newService message.Service, + response message.Processor) + DeleteClientService(clientID *id.ID) + RemoveIdentity(id *id.ID) +} diff --git a/broadcast/method.go b/broadcast/method.go new file mode 100644 index 0000000000000000000000000000000000000000..e493151504b00fabf320429b5da52a474f23efed --- /dev/null +++ b/broadcast/method.go @@ -0,0 +1,27 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +// Method enum for broadcast type +type Method uint8 + +const ( + Symmetric Method = iota + Asymmetric +) + +func (m Method) String() string { + switch m { + case Symmetric: + return "Symmetric" + case Asymmetric: + return "Asymmetric" + default: + return "Unknown" + } +} diff --git a/broadcast/processor.go b/broadcast/processor.go index 419072fdc06517f57b0ec1d788d9ec284dc4acfd..ec9ab810dc0aa16eefbb0add3ee44c53f55597d3 100644 --- a/broadcast/processor.go +++ b/broadcast/processor.go @@ -20,27 +20,55 @@ const ( errDecrypt = "[BCAST] Failed to decrypt payload for broadcast %s (%q): %+v" ) -// processor manages the reception and decryption of a broadcast message. -// Adheres to the message.Processor interface. +// processor struct for message handling type processor struct { - s *crypto.Symmetric - cb ListenerFunc + c *crypto.Channel + cb ListenerFunc + method Method } // Process decrypts the broadcast message and sends the results on the callback. func (p *processor) Process(msg format.Message, receptionID receptionID.EphemeralIdentity, round rounds.Round) { - payload, err := p.s.Decrypt(msg.GetContents(), msg.GetMac(), msg.GetKeyFP()) - if err != nil { - jww.ERROR.Printf(errDecrypt, p.s.ReceptionID, p.s.Name, err) - return + var payload []byte + var err error + switch p.method { + case Asymmetric: + // We use sized broadcast to fill any remaining bytes in the cmix payload, decode it here + unsizedPayload, err := DecodeSizedBroadcast(msg.GetContents()) + if err != nil { + jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) + return + } + encPartSize := p.c.RsaPubKey.Size() // Size of each chunk returned by multicast RSA encryption + numParts := len(unsizedPayload) / encPartSize // Number of chunks in the payload + // Iterate through & decrypt each chunk, appending to aggregate payload + for i := 0; i < numParts; i++ { + var decrypted []byte + decrypted, err = p.c.DecryptAsymmetric(unsizedPayload[:encPartSize]) + if err != nil { + jww.ERROR.Printf(errDecrypt, p.c.ReceptionID, p.c.Name, err) + return + } + unsizedPayload = unsizedPayload[encPartSize:] + payload = append(payload, decrypted...) + } + + case Symmetric: + payload, err = p.c.DecryptSymmetric(msg.GetContents(), msg.GetMac(), msg.GetKeyFP()) + if err != nil { + jww.ERROR.Printf(errDecrypt, p.c.ReceptionID, p.c.Name, err) + return + } + default: + jww.ERROR.Printf("Unrecognized broadcast method %d", p.method) } go p.cb(payload, receptionID, round) } -// String returns a string identifying the processor for debugging purposes. +// String returns a string identifying the symmetricProcessor for debugging purposes. func (p *processor) String() string { - return "symmetricChannel-" + p.s.Name + return "broadcastChannel-" + p.c.Name } diff --git a/broadcast/processor_test.go b/broadcast/processor_test.go index 04e2f4a791485e7b5b7410f62785164d9d18f753..9e48f372e92663eea061a8a7d72e0fbe13d58eee 100644 --- a/broadcast/processor_test.go +++ b/broadcast/processor_test.go @@ -29,7 +29,7 @@ func Test_processor_Process(t *testing.T) { if err != nil { t.Errorf("Failed to generate RSA key: %+v", err) } - s := &crypto.Symmetric{ + s := &crypto.Channel{ ReceptionID: id.NewIdFromString("channel", id.User, t), Name: "MyChannel", Description: "This is my channel that I channel stuff on.", @@ -43,14 +43,15 @@ func Test_processor_Process(t *testing.T) { } p := &processor{ - s: s, - cb: cb, + c: s, + cb: cb, + method: Symmetric, } msg := format.NewMessage(4092) payload := make([]byte, msg.ContentsSize()) _, _ = rng.Read(payload) - encryptedPayload, mac, fp := p.s.Encrypt(payload, rng) + encryptedPayload, mac, fp := p.c.EncryptSymmetric(payload, rng) msg.SetContents(encryptedPayload) msg.SetMac(mac) msg.SetKeyFP(fp) diff --git a/broadcast/sizedBroadcast.go b/broadcast/sizedBroadcast.go index e909ab30014ca926ad03a9040d8e5bbc9211ca29..d05effe1bcf93b07a0f3cd3533c28df8136c3a46 100644 --- a/broadcast/sizedBroadcast.go +++ b/broadcast/sizedBroadcast.go @@ -37,9 +37,12 @@ const ( +---------+-----------------+ */ -// NewSizedBroadcast creates a new broadcast with its size information embedded. +// NewSizedBroadcast creates a new broadcast payload of size maxPayloadSize that +// contains the given payload so that it fits completely inside a broadcasted +// cMix message payload. The length of the payload is stored internally and used +// to strip extraneous padding when decoding the payload. // The maxPayloadSize is the maximum size of the resulting payload. Returns an -// error when the sized broadcast cannot fit in the max payload size. +// error when the provided payload cannot fit in the max payload size. func NewSizedBroadcast(maxPayloadSize int, payload []byte) ([]byte, error) { if len(payload)+sizedBroadcastMinSize > maxPayloadSize { return nil, errors.Errorf(errNewSizedBroadcastMaxSize, @@ -49,10 +52,14 @@ func NewSizedBroadcast(maxPayloadSize int, payload []byte) ([]byte, error) { b := make([]byte, sizeSize) binary.LittleEndian.PutUint16(b, uint16(len(payload))) - return append(b, payload...), nil + sizedPayload := make([]byte, maxPayloadSize) + copy(sizedPayload, append(b, payload...)) + + return sizedPayload, nil } -// DecodeSizedBroadcast the data into its original payload of the correct size. +// DecodeSizedBroadcast decodes the data into its original payload stripping off +// extraneous padding. func DecodeSizedBroadcast(data []byte) ([]byte, error) { if len(data) < sizedBroadcastMinSize { return nil, errors.Errorf( @@ -68,8 +75,9 @@ func DecodeSizedBroadcast(data []byte) ([]byte, error) { return data[sizeSize : size+sizeSize], nil } -// MaxSizedBroadcastPayloadSize returns the maximum payload size in a sized -// broadcast for the given out message max payload size. +// MaxSizedBroadcastPayloadSize returns the maximum size of a payload that can +// fit in a sized broadcast message for the given maximum cMix message payload +// size. func MaxSizedBroadcastPayloadSize(maxPayloadSize int) int { return maxPayloadSize - sizedBroadcastMinSize } diff --git a/broadcast/symmetric.go b/broadcast/symmetric.go new file mode 100644 index 0000000000000000000000000000000000000000..ca0c528c588b184a72dc788a2fa83c0423ca5268 --- /dev/null +++ b/broadcast/symmetric.go @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" +) + +// Error messages. +const ( + // symmetricClient.Broadcast + errNetworkHealth = "cannot send broadcast when the network is not healthy" + errPayloadSize = "size of payload %d must be %d" + errBroadcastMethodType = "cannot call %s broadcast using %s channel" +) + +// Tags. +const ( + symmCMixSendTag = "SymmBcast" + symmetricBroadcastServiceTag = "SymmetricBroadcast" +) + +// MaxSymmetricPayloadSize returns the maximum size for a broadcasted payload. +func (bc *broadcastClient) maxSymmetricPayload() int { + return bc.net.GetMaxMessageLength() +} + +// Broadcast broadcasts a payload over a symmetric channel. +// broadcast method must be set to Symmetric +// Network must be healthy to send +// Requires a payload of size bc.MaxSymmetricPayloadSize() +func (bc *broadcastClient) Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + if bc.param.Method != Symmetric { + return 0, ephemeral.Id{}, errors.Errorf(errBroadcastMethodType, Symmetric, bc.param.Method) + } + + if !bc.net.IsHealthy() { + return 0, ephemeral.Id{}, errors.New(errNetworkHealth) + } + + if len(payload) != bc.maxSymmetricPayload() { + return 0, ephemeral.Id{}, + errors.Errorf(errPayloadSize, len(payload), bc.maxSymmetricPayload()) + } + + // Encrypt payload + rng := bc.rng.GetStream() + encryptedPayload, mac, fp := bc.channel.EncryptSymmetric(payload, rng) + rng.Close() + + // Create service + service := message.Service{ + Identifier: bc.channel.ReceptionID.Bytes(), + Tag: symmetricBroadcastServiceTag, + } + + if cMixParams.DebugTag == cmix.DefaultDebugTag { + cMixParams.DebugTag = symmCMixSendTag + } + + return bc.net.Send( + bc.channel.ReceptionID, fp, service, encryptedPayload, mac, cMixParams) +} diff --git a/broadcast/symmetricClient.go b/broadcast/symmetricClient.go deleted file mode 100644 index 28d718868a0659f9f2816f7fd2b16a4feb9cb116..0000000000000000000000000000000000000000 --- a/broadcast/symmetricClient.go +++ /dev/null @@ -1,143 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -//////////////////////////////////////////////////////////////////////////////// - -package broadcast - -import ( - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/cmix" - "gitlab.com/elixxir/client/cmix/identity" - "gitlab.com/elixxir/client/cmix/message" - crypto "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/elixxir/crypto/fastRNG" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "time" -) - -// Error messages. -const ( - // symmetricClient.Broadcast - errNetworkHealth = "cannot send broadcast when the network is not healthy" - errPayloadSize = "size of payload %d must be %d" -) - -// Tags. -const ( - cMixSendTag = "SymmBcast" - symmetricBroadcastServiceTag = "SymmetricBroadcast" -) - -// symmetricClient manages the sending and receiving of symmetric broadcast -// messages on a given symmetric broadcast channel. Adheres to the Symmetric -// interface. -type symmetricClient struct { - channel crypto.Symmetric - net Client - rng *fastRNG.StreamGenerator -} - -// Client contains the methods from cmix.Client that are required by -// symmetricClient. -type Client interface { - GetMaxMessageLength() int - Send(recipient *id.ID, fingerprint format.Fingerprint, - service message.Service, payload, mac []byte, - cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) - IsHealthy() bool - AddIdentity(id *id.ID, validUntil time.Time, persistent bool) - AddService(clientID *id.ID, newService message.Service, - response message.Processor) - DeleteClientService(clientID *id.ID) - RemoveIdentity(id *id.ID) -} - -// NewSymmetricClient generates a new Symmetric for the given channel. It starts -// listening for new messages on the callback immediately. -func NewSymmetricClient(channel crypto.Symmetric, listenerCb ListenerFunc, - net Client, rng *fastRNG.StreamGenerator) Symmetric { - // Add channel's identity - net.AddIdentity(channel.ReceptionID, identity.Forever, true) - - // Create new service - service := message.Service{ - Identifier: channel.ReceptionID.Bytes(), - Tag: symmetricBroadcastServiceTag, - } - - // Create new message processor - p := &processor{ - s: &channel, - cb: listenerCb, - } - - // Add service - net.AddService(channel.ReceptionID, service, p) - - jww.INFO.Printf("New symmetric broadcast client created for channel %q (%s)", - channel.Name, channel.ReceptionID) - - return &symmetricClient{ - channel: channel, - net: net, - rng: rng, - } -} - -// MaxPayloadSize returns the maximum size for a broadcasted payload. -func (s *symmetricClient) MaxPayloadSize() int { - return s.net.GetMaxMessageLength() -} - -// Get returns the crypto.Symmetric object containing the cryptographic and -// identifying information about the channel. -func (s *symmetricClient) Get() crypto.Symmetric { - return s.channel -} - -// Broadcast broadcasts the payload to the channel. -func (s *symmetricClient) Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( - id.Round, ephemeral.Id, error) { - if !s.net.IsHealthy() { - return 0, ephemeral.Id{}, errors.New(errNetworkHealth) - } - - if len(payload) != s.MaxPayloadSize() { - return 0, ephemeral.Id{}, - errors.Errorf(errPayloadSize, len(payload), s.MaxPayloadSize()) - } - - // Encrypt payload - rng := s.rng.GetStream() - encryptedPayload, mac, fp := s.channel.Encrypt(payload, rng) - rng.Close() - - // Create service - service := message.Service{ - Identifier: s.channel.ReceptionID.Bytes(), - Tag: symmetricBroadcastServiceTag, - } - - if cMixParams.DebugTag == cmix.DefaultDebugTag { - cMixParams.DebugTag = cMixSendTag - } - - return s.net.Send( - s.channel.ReceptionID, fp, service, encryptedPayload, mac, cMixParams) -} - -// Stop unregisters the listener callback and stops the channel's identity -// from being tracked. -func (s *symmetricClient) Stop() { - // Removes currently tracked identity - s.net.RemoveIdentity(s.channel.ReceptionID) - - // Delete all registered services - s.net.DeleteClientService(s.channel.ReceptionID) -} diff --git a/broadcast/symmetricClient_test.go b/broadcast/symmetric_test.go similarity index 81% rename from broadcast/symmetricClient_test.go rename to broadcast/symmetric_test.go index 4b6f60ac89fb1b7a06e5ce1a3fa7398720848250..a35ea5ecdca77b9abb5d874b3a28c99be6f08a7a 100644 --- a/broadcast/symmetricClient_test.go +++ b/broadcast/symmetric_test.go @@ -17,7 +17,7 @@ import ( cMixCrypto "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/crypto/signature/rsa" "reflect" "sync" "testing" @@ -25,7 +25,7 @@ import ( ) // Tests that symmetricClient adheres to the Symmetric interface. -var _ Symmetric = (*symmetricClient)(nil) +var _ Channel = (*broadcastClient)(nil) // Tests that symmetricClient adheres to the Symmetric interface. var _ Client = (cmix.Client)(nil) @@ -36,18 +36,26 @@ func Test_symmetricClient_Smoke(t *testing.T) { // Initialise objects used by all clients cMixHandler := newMockCmixHandler() rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) - channel := crypto.Symmetric{ - ReceptionID: id.NewIdFromString("ReceptionID", id.User, t), - Name: "MyChannel", - Description: "This is my channel about stuff.", - Salt: cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32), - RsaPubKey: newRsaPubKey(64, t), + cname := "MyChannel" + cdesc := "This is my channel about stuff." + csalt := cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32) + cpubkey := newRsaPubKey(64, t) + cid, err := crypto.NewChannelID(cname, cdesc, csalt, rsa.CreatePublicKeyPem(cpubkey)) + if err != nil { + t.Errorf("Failed to create channel ID: %+v", err) + } + channel := crypto.Channel{ + ReceptionID: cid, + Name: cname, + Description: cdesc, + Salt: csalt, + RsaPubKey: cpubkey, } // Set up callbacks, callback channels, and the symmetric clients const n = 5 cbChans := make([]chan []byte, n) - clients := make([]Symmetric, n) + clients := make([]Channel, n) for i := range clients { cbChan := make(chan []byte, 10) cb := func(payload []byte, _ receptionID.EphemeralIdentity, @@ -55,8 +63,10 @@ func Test_symmetricClient_Smoke(t *testing.T) { cbChan <- payload } - s := NewSymmetricClient(channel, cb, newMockCmix(cMixHandler), rngGen) - + s, err := NewBroadcastChannel(channel, cb, newMockCmix(cMixHandler), rngGen, Param{Method: Symmetric}) + if err != nil { + t.Errorf("Failed to create broadcast channel: %+v", err) + } cbChans[i] = cbChan clients[i] = s @@ -126,7 +136,7 @@ func Test_symmetricClient_Smoke(t *testing.T) { } // Broadcast payload - _, _, err := clients[0].Broadcast(payload, cmix.GetDefaultCMIXParams()) + _, _, err = clients[0].Broadcast(payload, cmix.GetDefaultCMIXParams()) if err != nil { t.Errorf("Client 0 failed to send broadcast: %+v", err) } diff --git a/cmd/broadcast.go b/cmd/broadcast.go new file mode 100644 index 0000000000000000000000000000000000000000..dde6480640ae9060008236c46e92017653851c8e --- /dev/null +++ b/cmd/broadcast.go @@ -0,0 +1,282 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/utils" + "os" + "time" +) + +// singleCmd is the single-use subcommand that allows for sending and responding +// to single-use messages. +var broadcastCmd = &cobra.Command{ + Use: "broadcast", + Short: "Send broadcast messages", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + client := initClient() + + // Write user contact to file + user := client.GetUser() + jww.INFO.Printf("User: %s", user.ReceptionID) + jww.INFO.Printf("User Transmission: %s", user.TransmissionID) + writeContact(user.GetContact()) + + err := client.StartNetworkFollower(5 * time.Second) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } + + // Wait until connected or crash on timeout + connected := make(chan bool, 10) + client.GetNetworkInterface().AddHealthCallback( + func(isconnected bool) { + connected <- isconnected + }) + waitUntilConnected(connected) + + /* Set up underlying crypto broadcast.Channel */ + var channel *crypto.Channel + var pk *rsa.PrivateKey + keyPath := viper.GetString("keyPath") + path, err := utils.ExpandPath(viper.GetString("chanPath")) + if utils.Exists(path) { + // Load symmetric from path + cBytes, err := utils.ReadFile(path) + if err != nil { + jww.FATAL.Panicf("Failed to read channel from file at %s: %+v", path, err) + } + channel, err = crypto.UnmarshalChannel(cBytes) + if err != nil { + jww.FATAL.Panicf("Failed to unmarshal channel data %+v: %+v", cBytes, err) + } + } else { + // Load in broadcast channel info + name := viper.GetString("name") + desc := viper.GetString("description") + if name == "" { + jww.FATAL.Panicf("Name cannot be empty") + } else if desc == "" { + jww.FATAL.Panicf("description cannot be empty") + } + + var channel *crypto.Channel + if viper.GetBool("new") { + // Create a new broadcast channel + channel, pk, err = crypto.NewChannel(name, desc, client.GetRng().GetStream()) + if err != nil { + jww.FATAL.Panicf("Failed to create new channel: %+v", err) + } + + if keyPath != "" { + err = utils.WriteFile(path, rsa.CreatePrivateKeyPem(pk), os.ModePerm, os.ModeDir) + if err != nil { + jww.ERROR.Printf("Failed to write private key to path %s: %+v", path, err) + } + } else { + fmt.Printf("Private key generated for channel: %+v", rsa.CreatePrivateKeyPem(pk)) + } + } else { + // Read rest of info from config & build object manually + pubKeyBytes := []byte(viper.GetString("rsaPub")) + pubKey, err := rsa.LoadPublicKeyFromPem(pubKeyBytes) + if err != nil { + jww.FATAL.Panicf("Failed to load public key at path: %+v", err) + } + salt := []byte(viper.GetString("salt")) + + rid, err := crypto.NewChannelID(name, desc, salt, pubKeyBytes) + if err != nil { + jww.FATAL.Panicf("Failed to generate channel ID: %+v", err) + } + + channel = &crypto.Channel{ + ReceptionID: rid, + Name: name, + Description: desc, + Salt: salt, + RsaPubKey: pubKey, + } + + // Load key if it's there + if keyPath != "" { + if ep, err := utils.ExpandPath(keyPath); err == nil { + keyBytes, err := utils.ReadFile(ep) + if err != nil { + jww.ERROR.Printf("Failed to read private key from %s: %+v", ep, err) + } + pk, err = rsa.LoadPrivateKeyFromPem(keyBytes) + if err != nil { + jww.ERROR.Printf("Failed to load private key %+v: %+v", keyBytes, err) + } + } else { + jww.ERROR.Printf("Failed to expand private key path: %+v", err) + } + + } + } + + // Save channel to disk + cBytes, err := channel.Marshal() + if err != nil { + jww.ERROR.Printf("Failed to marshal channel to bytes: %+v", err) + } + // Write to file if there + if path != "" { + err = utils.WriteFile(path, cBytes, os.ModePerm, os.ModeDir) + if err != nil { + jww.ERROR.Printf("Failed to write channel to file %s: %+v", path, err) + } + } else { + fmt.Printf("Channel marshalled: %+v", cBytes) + } + } + + /* Broadcast client setup */ + + // Create receiver callback + receiveChan := make(chan []byte, 100) + cb := func(payload []byte, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + jww.INFO.Printf("Received symmetric message from %s over round %d", receptionID, round) + receiveChan <- payload + } + + // Select broadcast method + var method broadcast.Method + symmetric := viper.GetBool("symmetric") + asymmetric := viper.GetBool("asymmetric") + if symmetric && asymmetric { + jww.FATAL.Panicf("Cannot simultaneously broadcast symmetric & asymmetric") + } + if symmetric { + method = broadcast.Symmetric + } else if asymmetric { + method = broadcast.Asymmetric + } + + // Connect to broadcast channel + bcl, err := broadcast.NewBroadcastChannel(*channel, cb, client.GetNetworkInterface(), client.GetRng(), broadcast.Param{Method: method}) + + /* Create properly sized broadcast message */ + message := viper.GetString("broadcast") + fmt.Println(message) + var broadcastMessage []byte + if message != "" { + broadcastMessage, err = broadcast.NewSizedBroadcast(bcl.MaxPayloadSize(), []byte(message)) + if err != nil { + jww.ERROR.Printf("Failed to create sized broadcast: %+v", err) + } + + } + + /* Broadcast message to the channel */ + switch method { + case broadcast.Symmetric: + rid, eid, err := bcl.Broadcast(broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send symmetric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent symmetric broadcast message to %s over round %d", eid, rid) + case broadcast.Asymmetric: + if pk == nil { + jww.FATAL.Panicf("CANNOT SEND ASYMMETRIC BROADCAST WITHOUT PRIVATE KEY") + } + rid, eid, err := bcl.BroadcastAsymmetric(pk, broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send asymmetric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent asymmetric broadcast message to %s over round %d", eid, rid) + default: + jww.WARN.Printf("Unknown broadcast type (this should not happen)") + } + + /* Receive broadcast messages over the channel */ + waitSecs := viper.GetUint("waitTimeout") + expectedCnt := viper.GetUint("receiveCount") + waitTimeout := time.Duration(waitSecs) * time.Second + receivedCount := uint(0) + done := false + for !done && expectedCnt != 0 { + timeout := time.NewTimer(waitTimeout) + select { + case receivedPayload := <-receiveChan: + receivedCount++ + receivedBroadcast, err := broadcast.DecodeSizedBroadcast(receivedPayload) + if err != nil { + jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) + continue + } + fmt.Printf("Symmetric broadcast message %d/%d received: %s\n", receivedCount, expectedCnt, string(receivedBroadcast)) + if receivedCount == expectedCnt { + done = true + } + case <-timeout.C: + fmt.Println("Timed out") + jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) + done = true + } + } + + jww.INFO.Printf("Received %d/%d Messages!", receivedCount, expectedCnt) + bcl.Stop() + err = client.StopNetworkFollower() + if err != nil { + jww.WARN.Printf("Failed to cleanly close threads: %+v\n", err) + } + }, +} + +func init() { + // Single-use subcommand options + broadcastCmd.Flags().StringP("name", "", "", + "Symmetric channel name") + _ = viper.BindPFlag("name", broadcastCmd.Flags().Lookup("name")) + + broadcastCmd.Flags().StringP("rsaPub", "", "", + "Broadcast channel rsa pub key") + _ = viper.BindPFlag("rsaPub", broadcastCmd.Flags().Lookup("rsaPub")) + + broadcastCmd.Flags().StringP("salt", "", "", + "Broadcast channel salt") + _ = viper.BindPFlag("salt", broadcastCmd.Flags().Lookup("salt")) + + broadcastCmd.Flags().StringP("description", "", "", + "Broadcast channel description") + _ = viper.BindPFlag("description", broadcastCmd.Flags().Lookup("description")) + + broadcastCmd.Flags().StringP("chanPath", "", "", + "Broadcast channel output path") + _ = viper.BindPFlag("chanPath", broadcastCmd.Flags().Lookup("chanPath")) + + broadcastCmd.Flags().StringP("keyPath", "", "", + "Broadcast channel private key output path") + _ = viper.BindPFlag("keyPath", broadcastCmd.Flags().Lookup("keyPath")) + + broadcastCmd.Flags().BoolP("new", "", false, + "Create new broadcast channel") + _ = viper.BindPFlag("new", broadcastCmd.Flags().Lookup("new")) + + broadcastCmd.Flags().StringP("broadcast", "", "", + "Message contents for broadcast") + _ = viper.BindPFlag("broadcast", broadcastCmd.Flags().Lookup("broadcast")) + + broadcastCmd.Flags().BoolP("symmetric", "", false, + "Set broadcast method to symmetric") + _ = viper.BindPFlag("symmetric", broadcastCmd.Flags().Lookup("symmetric")) + + broadcastCmd.Flags().BoolP("asymmetric", "", false, + "Set broadcast method to asymmetric") + _ = viper.BindPFlag("asymmetric", broadcastCmd.Flags().Lookup("asymmetric")) + + rootCmd.AddCommand(broadcastCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 1d6d3c922dc686d24b47432170fca6493f3d9e7b..5d9cd10007b81a8dd2c92bfee2c819c374be9d71 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1040,9 +1040,9 @@ func init() { "Ensure successful message sending by checking for round completion") viper.BindPFlag("verify-sends", rootCmd.Flags().Lookup("verify-sends")) - rootCmd.Flags().UintP("receiveCount", + rootCmd.PersistentFlags().UintP("receiveCount", "", 1, "How many messages we should wait for before quitting") - viper.BindPFlag("receiveCount", rootCmd.Flags().Lookup("receiveCount")) + viper.BindPFlag("receiveCount", rootCmd.PersistentFlags().Lookup("receiveCount")) rootCmd.PersistentFlags().UintP("waitTimeout", "", 15, "The number of seconds to wait for messages to arrive") viper.BindPFlag("waitTimeout", diff --git a/go.mod b/go.mod index 5cc7bdbc98c27ad9aad0f873ef32a516cc24c9f8..45cbd29359501276d3da18b7745794d21cdaaa8c 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f gitlab.com/elixxir/comms v0.0.4-0.20220323190139-9ed75f3a8b2c - gitlab.com/elixxir/crypto v0.0.7-0.20220425192911-a23209a58073 + gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b gitlab.com/elixxir/ekv v0.1.7 gitlab.com/elixxir/primitives v0.0.3-0.20220330212736-cce83b5f948f gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac - gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71 + gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed diff --git a/go.sum b/go.sum index b2b24d4d75babae71f71578a773220de0cf06b99..4a738e6d8a959874976a8053d6ffe015b5e1216d 100644 --- a/go.sum +++ b/go.sum @@ -317,6 +317,14 @@ gitlab.com/elixxir/crypto v0.0.7-0.20220425192754-f9f5ca6ad2eb h1:K10bnRhnZS6XFx gitlab.com/elixxir/crypto v0.0.7-0.20220425192754-f9f5ca6ad2eb/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= gitlab.com/elixxir/crypto v0.0.7-0.20220425192911-a23209a58073 h1:T0BK30t1F9M8RCdsuMN1bL34fSLNPqjqC+oufnjPh3I= gitlab.com/elixxir/crypto v0.0.7-0.20220425192911-a23209a58073/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= +gitlab.com/elixxir/crypto v0.0.7-0.20220509151343-6e5a514a65fd h1:3lU8kF5ItUZYIuKHF6qrC3o1yiL6fKdF5p1HJRB9iik= +gitlab.com/elixxir/crypto v0.0.7-0.20220509151343-6e5a514a65fd/go.mod h1:cJF80ad9YCR+UcOlZNzfDVBAQqGEEhhs3y5taMEvXaE= +gitlab.com/elixxir/crypto v0.0.7-0.20220510191648-70e5e956d3d5 h1:uzzFrmqx0CnqQ7AInGi6PA8w1mm2032hIpYn2G2SDKA= +gitlab.com/elixxir/crypto v0.0.7-0.20220510191648-70e5e956d3d5/go.mod h1:cJF80ad9YCR+UcOlZNzfDVBAQqGEEhhs3y5taMEvXaE= +gitlab.com/elixxir/crypto v0.0.7-0.20220513133141-46faee2d8fca h1:0EINaZCe/0dPbQD2msOj8fwKpZ5lnZ57Y6zUZm85rEo= +gitlab.com/elixxir/crypto v0.0.7-0.20220513133141-46faee2d8fca/go.mod h1:cJF80ad9YCR+UcOlZNzfDVBAQqGEEhhs3y5taMEvXaE= +gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b h1:HZlcbi+rTr1MACtLoNUJDV2iTvy7JtkRjWWaPF45bcM= +gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b/go.mod h1:L3duHa+GppnsY8x22/ixrEdQSM7WXa1ORWPS90HZ/JI= gitlab.com/elixxir/ekv v0.1.6 h1:M2hUSNhH/ChxDd+s8xBqSEKgoPtmE6hOEBqQ73KbN6A= gitlab.com/elixxir/ekv v0.1.6/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/ekv v0.1.7 h1:OW2z+N4QCqqMFzouAwFTWWMKz0Y/PDhyYReN7gQ5NiQ= @@ -345,6 +353,10 @@ gitlab.com/xx_network/crypto v0.0.5-0.20220222212031-750f7e8a01f4 h1:95dZDMn/hpL gitlab.com/xx_network/crypto v0.0.5-0.20220222212031-750f7e8a01f4/go.mod h1:6apvsoHCQJDjO0J4E3uhR3yO9tTz/Mq5be5rjB3tQPU= gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71 h1:N2+Jja4xNg66entu6rGvzRcf3Vc785xgiaHeDPYnBvg= gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220502201458-dabab1ef2982 h1:iUMwO/lIeOEmqNTMPtr9d7H6Y0cGe4DaHhd+ISCts6E= +gitlab.com/xx_network/crypto v0.0.5-0.20220502201458-dabab1ef2982/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce h1:zkPtJLJUpoWDYD4z30nmKU8OvnIOnBvSu7e6q6WhY5A= +gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=