diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6f4785e7ed4fc39fd98959cdc061b098caa5f4bf..366b0a914049071de28e752cc340f322243f7b34 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,25 +14,29 @@ before_script:
   - mkdir -p ~/.ssh
   - chmod 700 ~/.ssh
   - ssh-keyscan -t rsa $GITLAB_SERVER > ~/.ssh/known_hosts
+  - rm -rf ~/.gitconfig
   - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://gitlab.com/"
   - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://git.xx.network/" --add
   - export PATH=$HOME/go/bin:$PATH
 
 stages:
+  - test
   - build
   - trigger_integration
 
-build:
-  stage: build
+test:
+  stage: test
   image: $DOCKER_IMAGE
   script:
     - git clean -ffdx
     - go mod vendor -v
-    - go build ./...
     - go mod tidy
-
+    - go build ./...
     - mkdir -p testdata
+
+    # Test coverage
     - go-acc --covermode atomic --output testdata/coverage.out ./... -- -v
+    # Exclude some specific packages and files
     - cat testdata/coverage.out | grep -v pb[.]go > testdata/coverage-real.out
     - go tool cover -func=testdata/coverage-real.out
     - go tool cover -html=testdata/coverage-real.out -o testdata/coverage.html
@@ -41,15 +45,25 @@ build:
     - go tool cover -func=testdata/coverage-real.out | grep "total:" | awk '{print $3}' | sed 's/\%//g' > testdata/coverage-percentage.txt
     - export CODE_CHECK=$(echo "$(cat testdata/coverage-percentage.txt) >= $MIN_CODE_COVERAGE" | bc -l)
     - (if [ "$CODE_CHECK" == "1" ]; then echo "Minimum coverage of $MIN_CODE_COVERAGE succeeded"; else echo "Minimum coverage of $MIN_CODE_COVERAGE failed"; exit 1; fi);
+  artifacts:
+    paths:
+      - vendor/
+      - testdata/
+
+build:
+  stage: build
+  image: $DOCKER_IMAGE
+  script:
+    - go mod vendor -v
+    - go build ./...
     - mkdir -p release
     - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' ./...
     - cd release
   artifacts:
     paths:
-      - vendor/
-      - testdata/
       - release/
 
+
 trigger-integration:
   stage: trigger_integration
   trigger:
diff --git a/Makefile b/Makefile
index 617caf39fb466279f636b39923aedd9aee036463..53618bfb42c37e16bffa8b8554700c2507ca55eb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,25 +1,21 @@
 .PHONY: update master release update_master update_release build clean
 
-setup:
-	git config --global --add url."git@gitlab.com:".insteadOf "https://gitlab.com/"
-
 clean:
-	rm -rf vendor/
-	go mod vendor
+	go mod tidy
+	go mod vendor -e
 
 update:
 	-GOFLAGS="" go get all
 
 build:
 	go build ./...
-	go mod tidy
 
 update_release:
-	GOFLAGS="" go get -u gitlab.com/xx_network/primitives@release
+	GOFLAGS="" go get gitlab.com/xx_network/primitives@release
 
 update_master:
-	GOFLAGS="" go get -u gitlab.com/xx_network/primitives@master
+	GOFLAGS="" go get gitlab.com/xx_network/primitives@master
 
 master: update_master clean build
 
-release: update_release clean  build
+release: update_release clean build
diff --git a/authorizer/dns.go b/authorizer/dns.go
index d7c3ded08014b3ca0647ce556190f0c342c667a9..c5ea47f58409224b9f36b458c0cf0ae604979945 100644
--- a/authorizer/dns.go
+++ b/authorizer/dns.go
@@ -9,7 +9,6 @@ package authorizer
 
 import (
 	"encoding/hex"
-	"fmt"
 )
 
 const (
@@ -32,5 +31,5 @@ func GetGatewayDns(gwID []byte) string {
 	if len(encoded) > maxGwIdLength {
 		encoded = encoded[:maxGwIdLength]
 	}
-	return fmt.Sprintf("%s.%s", encoded, DomainName)
+	return encoded + "." + DomainName
 }
diff --git a/current/activity.go b/current/activity.go
index dbeaf23c77dfe99b9a2d22349e1fe77409142ebf..d53ce8f01a45c1c802e5a4e9b4ce18e5370f8dbd 100644
--- a/current/activity.go
+++ b/current/activity.go
@@ -8,7 +8,7 @@
 package current
 
 import (
-	"fmt"
+	"strconv"
 
 	"github.com/pkg/errors"
 
@@ -55,7 +55,7 @@ func (a Activity) String() string {
 	case CRASH:
 		return "CRASH"
 	default:
-		return fmt.Sprintf("UNKNOWN STATE: %d", a)
+		return "UNKNOWN ACTIVITY: " + strconv.FormatUint(uint64(a), 10)
 	}
 }
 
@@ -78,6 +78,6 @@ func (a Activity) ConvertToRoundState() (states.Round, error) {
 	default:
 		// Unsupported conversion. Return an arbitrary round and error
 		return states.Round(99), errors.Errorf(
-			"unable to convert activity %+v to valid state", a)
+			"unable to convert activity %s (%d) to a valid state", a, a)
 	}
 }
diff --git a/current/activity_test.go b/current/activity_test.go
index f4884090a7ce250fc8c061a7d86e7a43993e571c..9759b1fe2938ae5547694ec7989fa75c4e500e47 100644
--- a/current/activity_test.go
+++ b/current/activity_test.go
@@ -18,7 +18,7 @@ import (
 // Consistency test of Activity.String.
 func TestActivity_String(t *testing.T) {
 	expected := []string{"NOT_STARTED", "WAITING", "PRECOMPUTING", "STANDBY",
-		"REALTIME", "COMPLETED", "ERROR", "CRASH", "UNKNOWN STATE: 8"}
+		"REALTIME", "COMPLETED", "ERROR", "CRASH", "UNKNOWN ACTIVITY: 8"}
 
 	for st := NOT_STARTED; st <= NUM_STATES; st++ {
 		if st.String() != expected[st] {
diff --git a/fact/fact.go b/fact/fact.go
index d8e5a117f6ba9920434a33129637bcec81ec354f..b9592c189b734383b64ae1a5f28749f131c3d8dc 100644
--- a/fact/fact.go
+++ b/fact/fact.go
@@ -42,7 +42,7 @@ type Fact struct {
 // validation error.
 func NewFact(ft FactType, fact string) (Fact, error) {
 	if len(fact) > maxFactLen {
-		return Fact{}, errors.Errorf("Fact (%s) exceeds maximum character limit"+
+		return Fact{}, errors.Errorf("Fact (%s) exceeds maximum character limit " +
 			"for a fact (%d characters)", fact, maxFactLen)
 	}
 
@@ -71,7 +71,7 @@ func UnstringifyFact(s string) (Fact, error) {
 	}
 
 	if len(s) > maxFactLen {
-		return Fact{}, errors.Errorf("Fact (%s) exceeds maximum character limit"+
+		return Fact{}, errors.Errorf("Fact (%s) exceeds maximum character limit " +
 			"for a fact (%d characters)", s, maxFactLen)
 	}
 
@@ -111,7 +111,7 @@ func ValidateFact(fact Fact) error {
 	case Nickname:
 		return validateNickname(fact.Fact)
 	default:
-		return errors.Errorf("Unknown fact type: %v", fact.T)
+		return errors.Errorf("Unknown fact type: %d", fact.T)
 	}
 }
 
@@ -130,8 +130,7 @@ func extractNumberInfo(fact string) (number, countryCode string) {
 func validateEmail(email string) error {
 	// Check that the input is validly formatted
 	if err := checkmail.ValidateFormat(email); err != nil {
-		return errors.Errorf(
-			"Could not validate format for email [%s]: %v", email, err)
+		return errors.Wrapf(err, "Could not validate format for email %q", email)
 	}
 
 	return nil
@@ -140,30 +139,32 @@ func validateEmail(email string) error {
 // Checks if the number and country code passed in is parse-able
 // and is a valid phone number with that information
 func validateNumber(number, countryCode string) error {
-	errCh := make(chan error)
-
-	go func() {
+	catchPanic := func(number, countryCode string) (err error) {
 		defer func() {
 			if r := recover(); r != nil {
-				errCh <- errors.Errorf("Crash occured on phone "+
-					"validation of: number: %s, country code: %s", number,
-					countryCode)
+				err = errors.Errorf("Crash occured on phone validation of: "+
+					"number: %s, country code: %s: %+v", number, countryCode, r)
 			}
 		}()
 
 		if len(number) == 0 || len(countryCode) == 0 {
-			errCh <- errors.New("Number or input are of length 0")
+			err = errors.New("Number or input are of length 0")
+			return err
 		}
 		num, err := libphonenumber.Parse(number, countryCode)
 		if err != nil || num == nil {
-			errCh <- errors.Errorf("Could not parse number [%s]: %v", number, err)
+			err = errors.Wrapf(err, "Could not parse number %q", number)
+			return err
 		}
 		if !libphonenumber.IsValidNumber(num) {
-			errCh <- errors.Errorf("Could not validate number [%s]: %v", number, err)
+			err = errors.Errorf("Could not validate number %q", number)
+			return err
 		}
-		errCh <- nil
-	}()
-	return <-errCh
+
+		return nil
+	}
+
+	return catchPanic(number, countryCode)
 }
 
 func validateNickname(nickname string) error {
diff --git a/fact/factList.go b/fact/factList.go
index 897a255b0601ee2efc0105e25cd7a4a8feeeb33a..8057efc0853af2355d662119c09e4e4bd60c3ae3 100644
--- a/fact/factList.go
+++ b/fact/factList.go
@@ -37,14 +37,17 @@ func UnstringifyFactList(s string) (FactList, string, error) {
 	parts := strings.SplitN(s, factBreak, 2)
 	if len(parts) != 2 {
 		return nil, "", errors.New("Invalid fact string passed")
+	} else if parts[0] == "" {
+		return nil, parts[1], nil
 	}
 	factStrings := strings.Split(parts[0], factDelimiter)
 
-	var factList []Fact
+	factList := make([]Fact, 0, len(factStrings))
 	for _, fString := range factStrings {
 		fact, err := UnstringifyFact(fString)
 		if err != nil {
-			jww.WARN.Printf("Fact failed to unstringify, dropped: %s", err)
+			jww.WARN.Printf(
+				"Fact %q failed to unstringify, dropped: %s", fString, err)
 		} else {
 			factList = append(factList, fact)
 		}
diff --git a/fact/factList_test.go b/fact/factList_test.go
index 9c4897c695fe706d45f3e1667f3b369e1e849da0..b40dd39aa1892dfe3869b7e4bccb98edbde9b530 100644
--- a/fact/factList_test.go
+++ b/fact/factList_test.go
@@ -35,6 +35,23 @@ func TestFactList_Stringify_UnstringifyFactList(t *testing.T) {
 	}
 }
 
+// Tests that a nil FactList marshalled by FactList.Stringify and unmarshalled
+// by UnstringifyFactList matches the original.
+func TestUnstringifyFactList_NilFactList(t *testing.T) {
+	var expected FactList
+
+	flString := expected.Stringify()
+	factList, _, err := UnstringifyFactList(flString)
+	if err != nil {
+		t.Fatalf("Failed to unstringify %q: %+v", flString, err)
+	}
+
+	if !reflect.DeepEqual(factList, expected) {
+		t.Errorf("Unexpected unstringified FactList."+
+			"\nexpected: %v\nreceived: %v", expected, factList)
+	}
+}
+
 // Error path: Tests that UnstringifyFactList returns an error for a malformed
 // stringified FactList.
 func Test_UnstringifyFactList_MissingFactBreakError(t *testing.T) {
diff --git a/fact/fact_test.go b/fact/fact_test.go
index 449261b199bfb32f43215bdb45ef181545542024..11a67d17449f3d54d2e5e49d4ae39c803e1dc424 100644
--- a/fact/fact_test.go
+++ b/fact/fact_test.go
@@ -139,7 +139,7 @@ func TestUnstringifyFact_Error(t *testing.T) {
 		expectedErr string
 	}{
 		{"", "stringified facts must at least have a type at the start"},
-		{longFact, fmt.Sprintf("Fact (%s) exceeds maximum character limitfor "+
+		{longFact, fmt.Sprintf("Fact (%s) exceeds maximum character limit for "+
 			"a fact (%d characters)", longFact, maxFactLen)},
 		{"P", "stringified facts must be at least 1 character long"},
 		{"QA", `Failed to unstringify fact type for "QA"`},
@@ -221,8 +221,8 @@ func Test_validateNumber_Error(t *testing.T) {
 		{"5", "", "Number or input are of length 0"},
 		{"", "US", "Number or input are of length 0"},
 		// {"020 8743 8000135", "UK", `Could not parse number "020 8743 8000135"`},
-		{"8005559486", "UK", `Could not parse number [8005559486]`},
-		{"+343511234567", "ES", `Could not validate number [+343511234567]`},
+		{"8005559486", "UK", `Could not parse number "8005559486"`},
+		{"+343511234567", "ES", `Could not validate number "+343511234567"`},
 	}
 
 	for i, tt := range tests {
diff --git a/fact/type.go b/fact/type.go
index 1ba30054e09cf17c5bb1ff7090e86f4521377fb0..68501213187e33e5284725319c1b5dc841b402a2 100644
--- a/fact/type.go
+++ b/fact/type.go
@@ -8,7 +8,7 @@
 package fact
 
 import (
-	"fmt"
+	"strconv"
 
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
@@ -36,7 +36,7 @@ func (t FactType) String() string {
 	case Nickname:
 		return "Nickname"
 	default:
-		return fmt.Sprintf("Unknown Fact FactType: %d", t)
+		return "Unknown Fact FactType: " + strconv.FormatUint(uint64(t), 10)
 	}
 }
 
@@ -68,10 +68,15 @@ func UnstringifyFactType(s string) (FactType, error) {
 	case "N":
 		return Nickname, nil
 	}
-	return 3, errors.Errorf("Unknown Fact FactType: %s", s)
+	return 99, errors.Errorf("Unknown Fact FactType: %s", s)
 }
 
 // IsValid determines if the FactType is one of the defined types.
 func (t FactType) IsValid() bool {
-	return t == Username || t == Email || t == Phone || t == Nickname
+	switch t {
+	case Username, Email, Phone, Nickname:
+		return true
+	default:
+		return false
+	}
 }
diff --git a/format/fingerprint.go b/format/fingerprint.go
index 884f6bc0d1d5fee697048e3b75da962a3b506b46..f567005e1ffeaae43506ef801ffd6c2ebac7cf05 100644
--- a/format/fingerprint.go
+++ b/format/fingerprint.go
@@ -9,6 +9,9 @@ package format
 
 import (
 	"encoding/base64"
+	"encoding/json"
+
+	"github.com/pkg/errors"
 )
 
 type Fingerprint [KeyFPLen]byte
@@ -30,3 +33,25 @@ func (fp Fingerprint) Bytes() []byte {
 func (fp Fingerprint) String() string {
 	return base64.StdEncoding.EncodeToString(fp.Bytes())
 }
+
+// MarshalJSON adheres to the json.Marshaler interface.
+func (fp Fingerprint) MarshalJSON() ([]byte, error) {
+	return json.Marshal(fp[:])
+}
+
+// UnmarshalJSON adheres to the json.Unmarshaler interface.
+func (fp *Fingerprint) UnmarshalJSON(data []byte) error {
+	var fpBytes []byte
+	err := json.Unmarshal(data, &fpBytes)
+	if err != nil {
+		return err
+	}
+
+	if len(fpBytes) != KeyFPLen {
+		return errors.Errorf("length of fingerprint must be %d", KeyFPLen)
+	}
+
+	copy(fp[:], fpBytes[:])
+
+	return nil
+}
diff --git a/format/fingerprint_test.go b/format/fingerprint_test.go
index 1e10e80ac808d82872fbc6c96a21ac678754a107..c625515359e0b507b6c745dde43ef0f2b0e67625 100644
--- a/format/fingerprint_test.go
+++ b/format/fingerprint_test.go
@@ -10,6 +10,7 @@ package format
 import (
 	"bytes"
 	"encoding/base64"
+	"encoding/json"
 	"math/rand"
 	"testing"
 )
@@ -66,3 +67,26 @@ func TestFingerprint_String(t *testing.T) {
 			"\nexpected: %s\nreceived: %s", expectedString, fp.String())
 	}
 }
+
+func Test_Fingerprint_JSON_Marshal_Unmarshal(t *testing.T) {
+	prng := rand.New(rand.NewSource(74043))
+	fpBytes := make([]byte, KeyFPLen)
+	prng.Read(fpBytes)
+
+	expected := NewFingerprint(fpBytes)
+
+	data, err := json.Marshal(expected)
+	if err != nil {
+		t.Errorf("Failed to marshal %T: %+v", expected, err)
+	}
+
+	var fp Fingerprint
+	if err = json.Unmarshal(data, &fp); err != nil {
+		t.Errorf("Failed to unmarshal %T: %+v", fp, err)
+	}
+
+	if expected != fp {
+		t.Errorf("Unexpected fingerprint.\nexpected: %s\nreceived: %s",
+			expected, fp)
+	}
+}
diff --git a/go.mod b/go.mod
index ac1188b2f7eafa1f85fa7b53b5bf9e6f63b22de8..4e5ec0c2411d49b4e120c296bdb1e6bf69f38be6 100644
--- a/go.mod
+++ b/go.mod
@@ -8,15 +8,13 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/jwalterweatherman v1.1.0
 	github.com/ttacon/libphonenumber v1.2.1
-	gitlab.com/xx_network/primitives v0.0.4-0.20230203173415-81c2cb07da44
-	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	gitlab.com/xx_network/primitives v0.0.4-0.20230724185812-bc6fc6e5341b
+	golang.org/x/crypto v0.5.0
 )
 
 require (
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/stretchr/testify v1.6.1 // indirect
 	github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
-	golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect
+	golang.org/x/sys v0.10.0 // indirect
 	google.golang.org/protobuf v1.26.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
 )
diff --git a/go.sum b/go.sum
index de5165c3854b38f978ed2c5f912e2a0ef899cd29..3a5c28dc4197a8e12be2fa981f69e43be6d98343 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,5 @@
 github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ=
 github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
@@ -16,32 +15,21 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
 github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w=
 github.com/ttacon/libphonenumber v1.2.1 h1:fzOfY5zUADkCkbIafAed11gL1sW+bJ26p6zWLBMElR4=
 github.com/ttacon/libphonenumber v1.2.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M=
-gitlab.com/xx_network/primitives v0.0.4-0.20230203173415-81c2cb07da44 h1:vNm76SCeKZiCaVL0rCIcqDxMzSVL50g3XO6dQYN8r3Q=
-gitlab.com/xx_network/primitives v0.0.4-0.20230203173415-81c2cb07da44/go.mod h1:wUxbEBGOBJZ/RkAiVAltlC1uIlIrU0dE113Nq7HiOhw=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gitlab.com/xx_network/primitives v0.0.4-0.20230724185812-bc6fc6e5341b h1:oymmRpA+5/SeBp+MgFeYyuB8S9agfetGDnxBXXq9utE=
+gitlab.com/xx_network/primitives v0.0.4-0.20230724185812-bc6fc6e5341b/go.mod h1:vI6JXexgqihcIVFGsZAwGlHWGT14XC24NwEB2c6q9nc=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/knownRounds/knownRounds.go b/knownRounds/knownRounds.go
index f8b25a115fb14e615a26af33a7addfedcb6e1d0b..aba5f1aa066cc9675bff9161569ee431615814e3 100644
--- a/knownRounds/knownRounds.go
+++ b/knownRounds/knownRounds.go
@@ -291,7 +291,7 @@ func (kr *KnownRounds) Forward(rid id.Round) {
 // unknown and ending with
 func (kr *KnownRounds) RangeUnchecked(oldestUnknown id.Round, threshold uint,
 	roundCheck func(id id.Round) bool, maxPickups int) (
-	id.Round, []id.Round, []id.Round) {
+	earliestRound id.Round, has, unknown []id.Round) {
 
 	newestRound := kr.lastChecked
 
@@ -303,10 +303,8 @@ func (kr *KnownRounds) RangeUnchecked(oldestUnknown id.Round, threshold uint,
 		oldestPossibleEarliestRound = newestRound - id.Round(threshold)
 	}
 
-	earliestRound := kr.lastChecked + 1
-	var unknown []id.Round
-
-	has := make([]id.Round, 0, maxPickups)
+	earliestRound = kr.lastChecked + 1
+	has = make([]id.Round, 0, maxPickups)
 
 	// If the oldest unknown round is outside the range we are attempting to
 	// check, then skip checking
@@ -368,16 +366,9 @@ func (kr *KnownRounds) RangeUncheckedMaskedRange(mask *KnownRounds,
 	numChecked := 0
 
 	if mask.firstUnchecked != mask.lastChecked {
-		jww.TRACE.Printf("mask (before Forward()) {\n\tbitStream:      %064b\n\tfirstUnchecked: %d\n\tlastChecked:    %d\n\tfuPos:          %d\n}", mask.bitStream, mask.firstUnchecked, mask.lastChecked, mask.fuPos)
 		mask.Forward(kr.firstUnchecked)
 		subSample, delta := kr.subSample(mask.firstUnchecked, mask.lastChecked)
 		// FIXME: it is inefficient to make a copy of the mask here.
-
-		jww.TRACE.Printf("mask (after Forward()) {\n\tbitStream:      %064b\n\tfirstUnchecked: %d\n\tlastChecked:    %d\n\tfuPos:          %d\n}", mask.bitStream, mask.firstUnchecked, mask.lastChecked, mask.fuPos)
-		jww.TRACE.Printf("kr {\n\tbitStream:      %064b\n\tfirstUnchecked: %d\n\tlastChecked:    %d\n\tfuPos:          %d\n}", kr.bitStream, kr.firstUnchecked, kr.lastChecked, kr.fuPos)
-		jww.TRACE.Printf("delta: %d", delta)
-		jww.TRACE.Printf("subSample:     %064b", subSample)
-		// jww.TRACE.Printf("maskSubSample: %064b", maskSubSample)
 		result := subSample.implies(mask.bitStream)
 
 		for i := mask.firstUnchecked + id.Round(delta) - 1; i >= mask.firstUnchecked && numChecked < maxChecked; i, numChecked = i-1, numChecked+1 {
diff --git a/nicknames/isValid.go b/nicknames/isValid.go
index 2c6244d8acb40cf055e2e7877f1675f8a03d7dad..b412c46653134368af72a6316ca58ebfd66cf461 100644
--- a/nicknames/isValid.go
+++ b/nicknames/isValid.go
@@ -27,7 +27,7 @@ var ErrNicknameTooLong = errors.Errorf("nicknames must be %d "+
 func IsValid(nick string) error {
 	if nick == "" {
 		jww.INFO.Printf(
-			"empty nickname passed, treating like no nickname")
+			"Empty nickname passed; treating it as if no nickname was set.")
 		return nil
 	}
 
diff --git a/notifications/data.go b/notifications/data.go
index 9b0aad2f3dfae84d79d74df566fa4eeaa927cb4c..50899f7b0b4cd1c2c515625cef3a2613d6905371 100644
--- a/notifications/data.go
+++ b/notifications/data.go
@@ -11,6 +11,7 @@ import (
 	"bytes"
 	"encoding/base64"
 	"encoding/csv"
+	"strconv"
 	"strings"
 
 	"github.com/pkg/errors"
@@ -24,6 +25,16 @@ type Data struct {
 	MessageHash []byte
 }
 
+func (d *Data) String() string {
+	fields := []string{
+		strconv.FormatInt(d.EphemeralID, 10),
+		strconv.FormatUint(d.RoundID, 10),
+		base64.StdEncoding.EncodeToString(d.IdentityFP),
+		base64.StdEncoding.EncodeToString(d.MessageHash),
+	}
+	return "{" + strings.Join(fields, " ") + "}"
+}
+
 // BuildNotificationCSV converts the [Data] list into a CSV of the specified max
 // size and return it along with the included [Data] entries. Any [Data] entries
 // over that size are excluded.
@@ -35,7 +46,7 @@ func BuildNotificationCSV(ndList []*Data, maxSize int) ([]byte, []*Data) {
 	var buf bytes.Buffer
 	var numWritten int
 
-	for _, nd := range ndList {
+	for i, nd := range ndList {
 		var line bytes.Buffer
 		w := csv.NewWriter(&line)
 		output := []string{
@@ -43,7 +54,8 @@ func BuildNotificationCSV(ndList []*Data, maxSize int) ([]byte, []*Data) {
 			base64.StdEncoding.EncodeToString(nd.IdentityFP)}
 
 		if err := w.Write(output); err != nil {
-			jww.FATAL.Printf("Failed to write notificationsCSV line: %+v", err)
+			jww.FATAL.Printf("Failed to write record %d of %d to "+
+				"notifications CSV line buffer: %+v", i, len(ndList), err)
 		}
 		w.Flush()
 
@@ -52,7 +64,8 @@ func BuildNotificationCSV(ndList []*Data, maxSize int) ([]byte, []*Data) {
 		}
 
 		if _, err := buf.Write(line.Bytes()); err != nil {
-			jww.FATAL.Printf("Failed to write to notificationsCSV: %+v", err)
+			jww.FATAL.Printf("Failed to write record %d of %d to "+
+				"notifications CSV: %+v", i, len(ndList), err)
 		}
 
 		numWritten++
@@ -66,21 +79,25 @@ func DecodeNotificationsCSV(data string) ([]*Data, error) {
 	r := csv.NewReader(strings.NewReader(data))
 	records, err := r.ReadAll()
 	if err != nil {
-		return nil, errors.WithMessage(err, "Failed to decode notifications CSV")
+		return nil, errors.Wrapf(err, "Failed to read notifications CSV records.")
 	}
 
 	list := make([]*Data, len(records))
 	for i, tuple := range records {
 		messageHash, err := base64.StdEncoding.DecodeString(tuple[0])
 		if err != nil {
-			return nil, errors.WithMessage(err, "Failed decode an element")
+			return nil, errors.Wrapf(err,
+				"Failed to decode MessageHash for record %d of %d",
+				i, len(records))
 		}
+
 		identityFP, err := base64.StdEncoding.DecodeString(tuple[1])
 		if err != nil {
-			return nil, errors.WithMessage(err, "Failed decode an element")
+			return nil, errors.Wrapf(err,
+				"Failed to decode IdentityFP for record %d of %d",
+				i, len(records))
 		}
 		list[i] = &Data{
-			EphemeralID: 0,
 			IdentityFP:  identityFP,
 			MessageHash: messageHash,
 		}
diff --git a/notifications/data_test.go b/notifications/data_test.go
index 44938899342269ffb53c64ee80a25654ce07f9e6..d4b4da519b332aca50159a94cb05d3e6c3a1ccb7 100644
--- a/notifications/data_test.go
+++ b/notifications/data_test.go
@@ -141,7 +141,7 @@ GsvgcJsHWAg/YdN1vAK0HfT5GSnhj9qeb4LlTnSOgec=,nku9b+NM3LqEPujWPoxP/hzr6lRtj6wT3Q=
 func TestDecodeNotificationsCSV_InvalidMessageHashError(t *testing.T) {
 	invalidCSV := `U4x/lrFkvxuXu59LtHLonnZND6SugndnVI=,39ebTXZCm2F6DJ+fDTulWwzA1hRMiIU1hA==
 `
-	expectedErr := "Failed decode an element"
+	expectedErr := "Failed to decode MessageHash for record 0 of 1"
 	_, err := DecodeNotificationsCSV(invalidCSV)
 	if err == nil || !strings.Contains(err.Error(), expectedErr) {
 		t.Errorf("Unexpected error for invalid MessageHash."+
@@ -154,7 +154,7 @@ func TestDecodeNotificationsCSV_InvalidMessageHashError(t *testing.T) {
 func TestDecodeNotificationsCSV_InvalididentityFPError(t *testing.T) {
 	invalidCSV := `U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=,39ebTXZCm2F6DJ1hRMiIU1hA==
 `
-	expectedErr := "Failed decode an element"
+	expectedErr := "Failed to decode IdentityFP for record 0 of 1"
 	_, err := DecodeNotificationsCSV(invalidCSV)
 	if err == nil || !strings.Contains(err.Error(), expectedErr) {
 		t.Errorf("Unexpected error for invalid identityFP."+
@@ -166,7 +166,7 @@ func TestDecodeNotificationsCSV_InvalididentityFPError(t *testing.T) {
 // an invalid identityFP.
 func TestDecodeNotificationsCSV_NoEofError(t *testing.T) {
 	invalidCSV := `U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=,39ebTXZCm2F6DJ+fDTulWwzA1hRMiIU1hA==,"`
-	expectedErr := "Failed to decode notifications CSV"
+	expectedErr := "Failed to read notifications CSV records."
 	_, err := DecodeNotificationsCSV(invalidCSV)
 	if err == nil || !strings.Contains(err.Error(), expectedErr) {
 		t.Errorf("Unexpected error for invalid identityFP."+
diff --git a/states/state.go b/states/state.go
index 95cd44c7ad00c378861bdc9cd2c897d93b89745e..a53e5bf3a67b3366292acf1ac6e76e541299bf9c 100644
--- a/states/state.go
+++ b/states/state.go
@@ -7,9 +7,7 @@
 
 package states
 
-import (
-	"fmt"
-)
+import "strconv"
 
 // This holds the enum for the states of a round. It is in primitives so
 // other repos such as registration/permissioning, gateway, and client can
@@ -49,6 +47,6 @@ func (r Round) String() string {
 	case FAILED:
 		return "FAILED"
 	default:
-		return fmt.Sprintf("UNKNOWN STATE: %d", r)
+		return "UNKNOWN STATE: " + strconv.FormatUint(uint64(r), 10)
 	}
 }