From f17caaa7f2b71e0119b3226b9fe87ab1b0213fa9 Mon Sep 17 00:00:00 2001
From: Benjamin Wenger <ben@elixxir.ioo>
Date: Fri, 23 Sep 2022 08:31:53 -0700
Subject: [PATCH] in progress impl

---
 channels/channelMessages.proto     |  22 ++----
 channels/eventModel.go             |  73 +++++++++++++++-----
 channels/identityStore.go          |  31 +++++++++
 channels/interface.go              |  23 ++++++-
 channels/manager.go                |  81 +++++++++++++++++++---
 channels/nameService.go            |   1 +
 channels/nickname.go               | 107 +++++++++++++++++++++++++++++
 channels/sendTracker.go            |  53 +++++++++++++-
 cmix/identity/receptionID/store.go |   3 +-
 go.mod                             |   2 +-
 go.sum                             |   2 +
 11 files changed, 347 insertions(+), 51 deletions(-)
 create mode 100644 channels/identityStore.go
 create mode 100644 channels/nickname.go

diff --git a/channels/channelMessages.proto b/channels/channelMessages.proto
index 91dfa6d06..f64265e9a 100644
--- a/channels/channelMessages.proto
+++ b/channels/channelMessages.proto
@@ -26,7 +26,11 @@ message ChannelMessage{
 
     // Payload is the actual message payload. It will be processed differently
     // based on the PayloadType.
-    bytes  Payload  = 4;
+    bytes  Payload = 4;
+
+    // nickname is the name which the user is using for this message
+    // it will not be longer than 24 characters
+    string Nickname = 5;
 }
 
 // UserMessage is a message sent by a user who is a member within the channel.
@@ -36,29 +40,13 @@ message UserMessage {
     // ChannelMessage.
     bytes  Message = 1;
 
-    // ValidationSignature is the signature validating this user owns their
-    // username and may send messages to the channel under this username. This
-    // signature is provided by UD and may be validated by all members of the
-    // channel.
-    //
-    //  ValidationSignature = Sig(UD_ECCPrivKey, Username | ECCPublicKey | UsernameLease)
-    bytes  ValidationSignature = 2;
-
     // Signature is the signature proving this message has been sent by the
     // owner of this user's public key.
     //
     //  Signature = Sig(User_ECCPublicKey, Message)
     bytes  Signature = 3;
 
-    // Username is the username the user has registered with the channel and
-    // with UD.
-    string Username = 4;
-
     // ECCPublicKey is the user's EC Public key. This is provided by the
     // network.
     bytes  ECCPublicKey = 5;
-
-    // UsernameLease is the lease that has been provided to the username. This
-    // value is provide by UD.
-    int64  UsernameLease = 6;
 }
diff --git a/channels/eventModel.go b/channels/eventModel.go
index 9ebb58f48..dbff99e5d 100644
--- a/channels/eventModel.go
+++ b/channels/eventModel.go
@@ -9,6 +9,7 @@ package channels
 
 import (
 	"github.com/golang/protobuf/proto"
+	"github.com/golang/protobuf/ptypes/timestamp"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/cmix/identity/receptionID"
 	"gitlab.com/elixxir/primitives/states"
@@ -28,7 +29,8 @@ const AdminUsername = "Admin"
 type SentStatus uint8
 
 const (
-	Sent SentStatus = iota
+	Unsent SentStatus = iota
+	Sent
 	Delivered
 	Failed
 )
@@ -46,9 +48,20 @@ type EventModel interface {
 	// ReceiveMessage is called whenever a message is received on a given
 	// channel. It may be called multiple times on the same message. It is
 	// incumbent on the user of the API to filter such called by message ID.
+	//
+	// the api needs to return a uuid of the message which it may be
+	// referenced at a later time
+	//
+	// messageID, timestamp, and round are all nillable and may be updated
+	// based upon the UUID at a later date. A time of time.Time{} will be
+	// passed for a nilled timestamp.
+	//
+	// Nickname may be empty, in which case the UI is expected to display
+	// the codename
 	ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID,
-		senderUsername, text string, timestamp time.Time, lease time.Duration,
-		round rounds.Round, status SentStatus)
+		nickname, codename, extension, color, text string,
+		timestamp time.Time, lease time.Duration, round rounds.Round,
+		status SentStatus) uint64
 
 	// ReceiveReply is called whenever a message is received that is a reply on
 	// a given channel. It may be called multiple times on the same message. It
@@ -56,10 +69,20 @@ type EventModel interface {
 	//
 	// Messages may arrive our of order, so a reply in theory can arrive before
 	// the initial message. As a result, it may be important to buffer replies.
+	//
+	// the api needs to return a uuid of the message which it may be
+	// referenced at a later time
+	//
+	// messageID, timestamp, and round are all nillable and may be updated
+	// based upon the UUID at a later date. A time of time.Time{} will be
+	// passed for a nilled timestamp.
+	//
+	// Nickname may be empty, in which case the UI is expected to display
+	// the codename
 	ReceiveReply(channelID *id.ID, messageID cryptoChannel.MessageID,
-		reactionTo cryptoChannel.MessageID, senderUsername string,
-		text string, timestamp time.Time, lease time.Duration,
-		round rounds.Round, status SentStatus)
+		reactionTo cryptoChannel.MessageID, nickname, codename, extension,
+		color, text string, timestamp time.Time, lease time.Duration,
+		round rounds.Round, status SentStatus) uint64
 
 	// ReceiveReaction is called whenever a reaction to a message is received
 	// on a given channel. It may be called multiple times on the same reaction.
@@ -69,14 +92,29 @@ type EventModel interface {
 	// Messages may arrive our of order, so a reply in theory can arrive before
 	// the initial message. As a result, it may be important to buffer
 	// reactions.
+	//
+	// the api needs to return a uuid of the message which it may be
+	// referenced at a later time
+	//
+	// messageID, timestamp, and round are all nillable and may be updated
+	// based upon the UUID at a later date. A time of time.Time{} will be
+	// passed for a nilled timestamp.
+	//
+	// Nickname may be empty, in which case the UI is expected to display
+	// the codename
 	ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID,
-		reactionTo cryptoChannel.MessageID, senderUsername string,
-		reaction string, timestamp time.Time, lease time.Duration,
-		round rounds.Round, status SentStatus)
+		reactionTo cryptoChannel.MessageID, nickname, codename, extension,
+		color, reaction string, timestamp time.Time, lease time.Duration,
+		round rounds.Round, status SentStatus) uint64
 
 	// UpdateSentStatus is called whenever the sent status of a message has
 	// changed.
-	UpdateSentStatus(messageID cryptoChannel.MessageID, status SentStatus)
+	//
+	// messageID, timestamp, and round are all nillable and may be updated
+	// based upon the UUID at a later date. A time of time.Time{} will be
+	// passed for a nilled timestamp. If a nil value is passed, make no update
+	UpdateSentStatus(uuid uint64, messageID cryptoChannel.MessageID,
+		timestamp time.Time, round rounds.Round, status SentStatus)
 
 	// unimplemented
 	// IgnoreMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID)
@@ -89,12 +127,14 @@ type EventModel interface {
 // types. Default ones for Text, Reaction, and AdminText.
 type MessageTypeReceiveMessage func(channelID *id.ID,
 	messageID cryptoChannel.MessageID, messageType MessageType,
-	senderUsername string, content []byte, timestamp time.Time,
-	lease time.Duration, round rounds.Round, status SentStatus)
+	nickname, codename, extension, color string, content []byte,
+	timestamp time.Time, lease time.Duration, round rounds.Round,
+	status SentStatus)
 
 // updateStatusFunc is a function type for EventModel.UpdateSentStatus so it can
 // be mocked for testing where used.
-type updateStatusFunc func(messageID cryptoChannel.MessageID, status SentStatus)
+type updateStatusFunc func(uuid uint64, messageID cryptoChannel.MessageID,
+	timestamp time.Time, round rounds.Round, status SentStatus)
 
 // events is an internal structure that processes events and stores the handlers
 // for those events.
@@ -150,7 +190,9 @@ type triggerEventFunc func(chID *id.ID, umi *userMessageInternal,
 //
 // It will call the appropriate MessageTypeHandler assuming one exists.
 func (e *events) triggerEvent(chID *id.ID, umi *userMessageInternal,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round,
+	Identity cryptoChannel.Identity, ts timestamp.Timestamp,
+	receptionID receptionID.EphemeralIdentity,
+	round rounds.Round,
 	status SentStatus) {
 	um := umi.GetUserMessage()
 	cm := umi.GetChannelMessage()
@@ -168,9 +210,6 @@ func (e *events) triggerEvent(chID *id.ID, umi *userMessageInternal,
 		return
 	}
 
-	// Modify the timestamp to reduce the chance message order will be ambiguous
-	ts := mutateTimestamp(round.Timestamps[states.QUEUED], umi.GetMessageID())
-
 	// Call the listener. This is already in an instanced event, no new thread needed.
 	listener(chID, umi.GetMessageID(), messageType, um.Username,
 		cm.Payload, ts, time.Duration(cm.Lease), round, status)
diff --git a/channels/identityStore.go b/channels/identityStore.go
new file mode 100644
index 000000000..2432a86c8
--- /dev/null
+++ b/channels/identityStore.go
@@ -0,0 +1,31 @@
+package channels
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+	"gitlab.com/xx_network/primitives/netTime"
+)
+
+const (
+	identityStoreStorageKey     = "identityStoreStorageKey"
+	identityStoreStorageVersion = 0
+)
+
+func storeIdentity(kv *versioned.KV, ident cryptoChannel.PrivateIdentity) error {
+	data := ident.Marshal()
+	obj := &versioned.Object{
+		Version:   identityStoreStorageVersion,
+		Timestamp: netTime.Now(),
+		Data:      data,
+	}
+
+	return kv.Set(identityStoreStorageKey, obj)
+}
+
+func loadIdentity(kv *versioned.KV) (cryptoChannel.PrivateIdentity, error) {
+	obj, err := kv.Get(identityStoreStorageKey, identityStoreStorageVersion)
+	if err != nil {
+		return cryptoChannel.PrivateIdentity{}, err
+	}
+	return cryptoChannel.UnmarshalPrivateIdentity(obj.Data)
+}
diff --git a/channels/interface.go b/channels/interface.go
index 622ee6802..fd029b16e 100644
--- a/channels/interface.go
+++ b/channels/interface.go
@@ -9,6 +9,7 @@ package channels
 
 import (
 	"gitlab.com/elixxir/client/cmix"
+	"gitlab.com/elixxir/client/cmix/pickup/store"
 	"gitlab.com/elixxir/client/cmix/rounds"
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
@@ -27,6 +28,13 @@ var ValidForever = time.Duration(math.MaxInt64)
 
 type Manager interface {
 
+	// GetIdentity returns the public identity associated with this channel manager
+	GetIdentity() store.Identity
+
+	// GetStorageTag returns the tag at which this manager is store for loading
+	// it is derived from the public key
+	GetStorageTag() string
+
 	// JoinChannel joins the given channel. It will fail if the channel has
 	// already been joined.
 	JoinChannel(channel *cryptoBroadcast.Channel) error
@@ -42,8 +50,8 @@ type Manager interface {
 	// possible to define the largest payload that can be sent, but
 	// it will always be possible to send a payload of 802 bytes at minimum
 	// Them meaning of validUntil depends on the use case.
-	SendGeneric(channelID *id.ID, messageType MessageType, msg []byte,
-		validUntil time.Duration, params cmix.CMIXParams) (
+	SendGeneric(channelID *id.ID, messageType MessageType,
+		msg []byte, validUntil time.Duration, params cmix.CMIXParams) (
 		cryptoChannel.MessageID, rounds.Round, ephemeral.Id, error)
 
 	// SendAdminGeneric is used to send a raw message over a channel encrypted
@@ -106,4 +114,15 @@ type Manager interface {
 	// underlying state tracking for message pickup for the channel, causing all
 	// messages to be re-retrieved from the network
 	ReplayChannel(chID *id.ID) error
+
+	// SetNickname sets the nickname for a channel after checking that the nickname
+	// is valid using IsNicknameValid
+	SetNickname(newNick string, ch *id.ID) error
+
+	// DeleteNickname removes the nickname for a given channel, using the codename
+	// for that channel instead
+	DeleteNickname(ch *id.ID)
+
+	// GetNickname returns the nickname for the given channel if it exists
+	GetNickname(ch *id.ID) (nickname string, exists bool)
 }
diff --git a/channels/manager.go b/channels/manager.go
index 4547cf57f..71181eec1 100644
--- a/channels/manager.go
+++ b/channels/manager.go
@@ -11,12 +11,16 @@
 package channels
 
 import (
+	"crypto/ed25519"
+	"encoding/base64"
+	"fmt"
 	"gitlab.com/elixxir/client/broadcast"
 	"gitlab.com/elixxir/client/cmix"
 	"gitlab.com/elixxir/client/cmix/message"
 	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/storage/versioned"
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
+	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
@@ -24,20 +28,27 @@ import (
 	"time"
 )
 
+const storageTagFormat = "channelManagerStorageTag-%s"
+
 type manager struct {
+	// Sender Identity
+	me cryptoChannel.PrivateIdentity
+
 	// List of all channels
 	channels map[id.ID]*joinedChannel
 	mux      sync.RWMutex
 
 	// External references
-	kv   *versioned.KV
-	net  Client
-	rng  *fastRNG.StreamGenerator
-	name NameService
+	kv  *versioned.KV
+	net Client
+	rng *fastRNG.StreamGenerator
 
 	// Events model
 	*events
 
+	// nicknames
+	*nicknameManager
+
 	//send tracker
 	st *sendTracker
 
@@ -64,19 +75,52 @@ type Client interface {
 	RemoveHealthCallback(uint64)
 }
 
-// NewManager creates a new channel.Manager. It prefixes the KV with the
-// username so that multiple instances for multiple users will not error.
-func NewManager(kv *versioned.KV, net Client,
-	rng *fastRNG.StreamGenerator, name NameService, model EventModel) Manager {
+// NewManager creates a new channel.Manager from a private identity. It
+// prefixes the KV with a tag derived from the public key which can be retried
+// for reloading using Manage.GetStorageTag.
+func NewManager(identity cryptoChannel.PrivateIdentity, kv *versioned.KV,
+	net Client, rng *fastRNG.StreamGenerator, model EventModel) (Manager, error) {
+
+	// Prefix the kv with the username so multiple can be run
+	kv = kv.Prefix(getStorageTag(identity.PubKey))
+
+	if err := storeIdentity(kv, identity); err != nil {
+		return nil, err
+	}
+
+	m := setupManager(identity, kv, net, rng, model)
+
+	return &m, nil
+}
+
+// LoadManager restores a channel.Manager from disk stored at the given
+//storage tag.
+func LoadManager(storageTag string, kv *versioned.KV, net Client,
+	rng *fastRNG.StreamGenerator, name NameService, model EventModel) (Manager,
+	error) {
 
 	// Prefix the kv with the username so multiple can be run
-	kv = kv.Prefix(name.GetUsername())
+	kv = kv.Prefix(storageTag)
+
+	//load the identity
+	identity, err := loadIdentity(kv)
+	if err != nil {
+		return nil, err
+	}
+
+	m := setupManager(identity, kv, net, rng, model)
+
+	return &m
+}
+
+func setupManager(identity cryptoChannel.PrivateIdentity, kv *versioned.KV,
+	net Client, rng *fastRNG.StreamGenerator, model EventModel) *manager {
 
 	m := manager{
+		me:             identity,
 		kv:             kv,
 		net:            net,
 		rng:            rng,
-		name:           name,
 		broadcastMaker: broadcast.NewBroadcastChannel,
 	}
 
@@ -87,6 +131,8 @@ func NewManager(kv *versioned.KV, net Client,
 
 	m.loadChannels()
 
+	m.nicknameManager = loadOrNewNicknameManager(kv)
+
 	return &m
 }
 
@@ -163,3 +209,18 @@ func (m *manager) ReplayChannel(chID *id.ID) error {
 	return nil
 
 }
+
+// GetStorageTag returns the tag at which this manager is store for loading
+// it is derived from the public key
+func (m *manager) GetStorageTag() string {
+	return getStorageTag(m.me.PubKey)
+}
+
+// GetIdentity returns the public identity associated with this channel manager
+func (m *manager) GetIdentity() cryptoChannel.Identity {
+	return m.me.Identity
+}
+
+func getStorageTag(pub ed25519.PublicKey) string {
+	return fmt.Sprintf(storageTagFormat, base64.StdEncoding.EncodeToString(pub))
+}
diff --git a/channels/nameService.go b/channels/nameService.go
index f1d93b876..d77fa9414 100644
--- a/channels/nameService.go
+++ b/channels/nameService.go
@@ -14,6 +14,7 @@ import (
 
 // NameService is an interface which encapsulates
 // the user identity channel tracking service.
+// NameService is currently unused
 type NameService interface {
 
 	// GetUsername returns the username.
diff --git a/channels/nickname.go b/channels/nickname.go
new file mode 100644
index 000000000..a7dfb4521
--- /dev/null
+++ b/channels/nickname.go
@@ -0,0 +1,107 @@
+package channels
+
+import (
+	"encoding/json"
+	"errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
+	"sync"
+)
+
+const (
+	nicknameStoreStorageKey     = "nicknameStoreStorageKey"
+	nicknameStoreStorageVersion = 0
+)
+
+type nicknameManager struct {
+	byChannel map[id.ID]string
+
+	mux sync.RWMutex
+
+	kv *versioned.KV
+}
+
+// loadOrNewNicknameManager returns the stored nickname manager if there is
+// one or returns a new one
+func loadOrNewNicknameManager(kv *versioned.KV) *nicknameManager {
+	nm := &nicknameManager{
+		byChannel: make(map[id.ID]string),
+		kv:        kv,
+	}
+	err := nm.load()
+	if nm.kv.Exists(err) {
+		jww.FATAL.Panicf("Failed to load nicknameManager: %+v", err)
+	}
+
+	return nm, nil
+
+}
+
+// GetNickname returns the nickname for the given channel if it exists
+func (nm *nicknameManager) GetNickname(ch *id.ID) (nickname string, exists bool) {
+	nm.mux.RLock()
+	defer nm.mux.RUnlock()
+
+	nickname, exists = nm.byChannel[*ch]
+	return
+}
+
+// SetNickname sets the nickname for a channel after checking that the nickname
+// is valid using IsNicknameValid
+func (nm *nicknameManager) SetNickname(newNick string, ch *id.ID) error {
+	nm.mux.Lock()
+	defer nm.mux.Unlock()
+
+	if err := IsNicknameValid(newNick); err != nil {
+		return err
+	}
+
+	nm.byChannel[*ch] = newNick
+	return nil
+}
+
+// DeleteNickname removes the nickname for a given channel, using the codename
+// for that channel instead
+func (nm *nicknameManager) DeleteNickname(ch *id.ID) {
+	nm.mux.Lock()
+	defer nm.mux.Unlock()
+
+	delete(nm.byChannel, *ch)
+}
+
+// save stores the nickname manager to disk. It must occur under the mux.
+func (nm *nicknameManager) save() error {
+	data, err := json.Marshal(&nm.byChannel)
+	if err != nil {
+		return err
+	}
+	obj := &versioned.Object{
+		Version:   nicknameStoreStorageVersion,
+		Timestamp: netTime.Now(),
+		Data:      data,
+	}
+
+	return nm.kv.Set(nicknameStoreStorageKey, obj)
+}
+
+// load restores the nickname manager from disk.
+func (nm *nicknameManager) load() error {
+	obj, err := nm.kv.Get(nicknameStoreStorageKey, nicknameStoreStorageVersion)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(obj.Data, &nm.byChannel)
+}
+
+// IsNicknameValid checks if a nickname is valid
+//
+// rules
+//   - a Nickname must not be longer than 24 characters
+// todo: add character filtering
+func IsNicknameValid(nm string) error {
+	if len([]rune(nm)) > 24 {
+		return errors.New("nicknames must be 24 characters in length or less")
+	}
+}
diff --git a/channels/sendTracker.go b/channels/sendTracker.go
index 3913b666d..f471cd60d 100644
--- a/channels/sendTracker.go
+++ b/channels/sendTracker.go
@@ -23,7 +23,11 @@ import (
 const (
 	sendTrackerStorageKey     = "sendTrackerStorageKey"
 	sendTrackerStorageVersion = 0
-	getRoundResultsTimeout    = 60 * time.Second
+
+	sendTrackerUnsentStorageKey     = "sendTrackerUnsentStorageKey"
+	sendTrackerUnsentStorageVersion = 0
+
+	getRoundResultsTimeout = 60 * time.Second
 	// number of times it will attempt to get round status before the round
 	// is assumed to have failed. Tracking per round does not persist across
 	// runs
@@ -34,6 +38,7 @@ type tracked struct {
 	MsgID     cryptoChannel.MessageID
 	ChannelID *id.ID
 	RoundID   id.Round
+	UUID      uint64
 }
 
 // the sendTracker tracks outbound messages and denotes when they are delivered
@@ -45,6 +50,8 @@ type sendTracker struct {
 
 	byMessageID map[cryptoChannel.MessageID]*tracked
 
+	unsent map[uint64]*tracked
+
 	mux sync.RWMutex
 
 	trigger      triggerEventFunc
@@ -69,6 +76,7 @@ func loadSendTracker(net Client, kv *versioned.KV, trigger triggerEventFunc,
 	st := &sendTracker{
 		byRound:      make(map[id.Round][]*tracked),
 		byMessageID:  make(map[cryptoChannel.MessageID]*tracked),
+		unsent:       make(map[uint64]*tracked),
 		trigger:      trigger,
 		adminTrigger: adminTrigger,
 		updateStatus: updateStatus,
@@ -81,6 +89,13 @@ func loadSendTracker(net Client, kv *versioned.KV, trigger triggerEventFunc,
 	}*/
 	st.load()
 
+	//denote all unsent messages as failed and clear
+	for uuid := range st.unsent {
+		updateStatus(uuid, cryptoChannel.MessageID{},
+			time.Time{}, rounds.Round{}, Failed)
+	}
+	st.unsent = make(map[uint64]*tracked)
+
 	//register to check all outstanding rounds when the network becomes healthy
 	var callBackID uint64
 	callBackID = net.AddHealthCallback(func(f bool) {
@@ -89,6 +104,7 @@ func loadSendTracker(net Client, kv *versioned.KV, trigger triggerEventFunc,
 		}
 		net.RemoveHealthCallback(callBackID)
 		for rid := range st.byRound {
+
 			rr := &roundResults{
 				round: rid,
 				st:    st,
@@ -102,15 +118,32 @@ func loadSendTracker(net Client, kv *versioned.KV, trigger triggerEventFunc,
 
 // store writes the list of rounds that have been
 func (st *sendTracker) store() error {
+
+	//save sent messages
 	data, err := json.Marshal(&st.byRound)
 	if err != nil {
 		return err
 	}
-	return st.kv.Set(sendTrackerStorageKey, &versioned.Object{
+	err = st.kv.Set(sendTrackerStorageKey, &versioned.Object{
 		Version:   sendTrackerStorageVersion,
 		Timestamp: time.Now(),
 		Data:      data,
 	})
+	if err != nil {
+		return err
+	}
+
+	//save unsent messages
+	data, err = json.Marshal(&st.unsent)
+	if err != nil {
+		return err
+	}
+
+	return st.kv.Set(sendTrackerUnsentStorageKey, &versioned.Object{
+		Version:   sendTrackerUnsentStorageVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	})
 }
 
 // load will get the stored rounds to be checked from disk and builds
@@ -132,9 +165,25 @@ func (st *sendTracker) load() error {
 			st.byMessageID[roundList[j].MsgID] = roundList[j]
 		}
 	}
+
+	obj, err = st.kv.Get(sendTrackerUnsentStorageKey, sendTrackerUnsentStorageVersion)
+	if err != nil {
+		return err
+	}
+
+	err = json.Unmarshal(obj.Data, &st.unsent)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
+func denotePendingSend(channelID *id.ID,
+	umi *userMessageInternal, round rounds.Round) {
+
+}
+
 // send tracks a generic send message
 func (st *sendTracker) send(channelID *id.ID,
 	umi *userMessageInternal, round rounds.Round) {
diff --git a/cmix/identity/receptionID/store.go b/cmix/identity/receptionID/store.go
index 967c22e03..78400d30f 100644
--- a/cmix/identity/receptionID/store.go
+++ b/cmix/identity/receptionID/store.go
@@ -29,8 +29,7 @@ const (
 	receptionStoreStorageVersion = 0
 )
 
-var InvalidRequestedNumIdentities = errors.New("cannot get less than one identity(s)")
-
+var InvalidRequestedNumIdentities = errors.New("cannot get less than one identity(s)")code
 type Store struct {
 	// Identities which are being actively checked
 	active  []*registration
diff --git a/go.mod b/go.mod
index 227ba6e5c..b02daa4ba 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/stretchr/testify v1.8.0
 	gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f
 	gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd
-	gitlab.com/elixxir/crypto v0.0.7-0.20220920002307-5541473e9aa7
+	gitlab.com/elixxir/crypto v0.0.7-0.20220923135535-fb10d313632a
 	gitlab.com/elixxir/ekv v0.2.1
 	gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6
 	gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3
diff --git a/go.sum b/go.sum
index c534518a5..662bf87b4 100644
--- a/go.sum
+++ b/go.sum
@@ -636,6 +636,8 @@ gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp0
 gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
 gitlab.com/elixxir/crypto v0.0.7-0.20220920002307-5541473e9aa7 h1:9IsBtL8zcUG86XcfNUVIKcnlL5tyKlyQt1cJ5nogr1U=
 gitlab.com/elixxir/crypto v0.0.7-0.20220920002307-5541473e9aa7/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
+gitlab.com/elixxir/crypto v0.0.7-0.20220923135535-fb10d313632a h1:n12oIGF9GzfA/0ZTggCjubTxOZPF8p9WINfkdko9e7E=
+gitlab.com/elixxir/crypto v0.0.7-0.20220923135535-fb10d313632a/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
 gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is=
 gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-- 
GitLab