diff --git a/network/fingerprints.go b/network/fingerprints.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c44331bb2bc9427a11981985c84e38e9012615c
--- /dev/null
+++ b/network/fingerprints.go
@@ -0,0 +1,131 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package network
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/primitives/format"
+	"sync"
+)
+
+// Processor is an object which ties an interfaces.MessageProcessorFP
+// to a lock. This prevents the processor from being used in multiple
+// different threads.
+type Processor struct {
+	interfaces.MessageProcessorFP
+	sync.Mutex
+}
+
+// NewFingerprints is a constructor function for the Processor object.
+func newProcessor(mp interfaces.MessageProcessorFP) *Processor {
+	return &Processor{
+		MessageProcessorFP: mp,
+		Mutex:              sync.Mutex{},
+	}
+}
+
+// Fingerprints is a thread-safe map, mapping format.Fingerprint's to
+// a Processor object.
+type Fingerprints struct {
+	fingerprints map[format.Fingerprint]*Processor
+	sync.RWMutex
+}
+
+// NewFingerprints is a constructor function for the Fingerprints tracker.
+func NewFingerprints() *Fingerprints {
+	return &Fingerprints{
+		fingerprints: make(map[format.Fingerprint]*Processor),
+		RWMutex:      sync.RWMutex{},
+	}
+}
+
+// Get is a thread-safe getter for the Fingerprints map. Get returns the mapped
+// processor and true (representing that it exists in the map) if the provided
+// fingerprint has an entry. Otherwise, Get returns nil and false.
+func (f *Fingerprints) Get(fingerprint format.Fingerprint) (*Processor, bool) {
+	f.RLock()
+	defer f.RUnlock()
+	fp, exists := f.fingerprints[fingerprint]
+	if !exists {
+		return nil, false
+	}
+	return fp, true
+}
+
+// AddFingerprint is a thread-safe setter for the Fingerprints map. AddFingerprint
+// maps the given fingerprint key to the processor value. If there is already
+// an entry for this fingerprint, the method returns with no write operation.
+func (f *Fingerprints) AddFingerprint(fingerprint format.Fingerprint,
+	processor interfaces.MessageProcessorFP) {
+	f.Lock()
+	defer f.Unlock()
+
+	f.addFingerprint(fingerprint, processor)
+
+}
+
+// AddFingerprints is a thread-safe setter for multiple entries into
+// the Fingerprints map. If there is not a 1:1 relationship between
+// fingerprints and  processors slices (i.e. the lengths of these slices
+// are equivalent), an error will be returned.
+// Otherwise, each fingerprint is written to the associated processor.
+// If there is already an entry for the given fingerprint/processor pair,
+// no write operation will occur for this pair.
+func (f *Fingerprints) AddFingerprints(fps []format.Fingerprint,
+	processors []interfaces.MessageProcessorFP) error {
+	f.Lock()
+	defer f.Unlock()
+
+	if len(fps) != len(processors) {
+		return errors.Errorf("Canot perform a batch add when there are "+
+			"not an equal amount of fingerprints and processors. "+
+			"Given %d fingerprints and %d processors.", len(fps), len(processors))
+	}
+
+	for i, fp := range fps {
+		f.addFingerprint(fp, processors[i])
+	}
+
+	return nil
+}
+
+// addFingerprint is a non-thread-safe helper function which writes a Processor
+// to the given fingerprint key. If an entry already exists for this fingerprint key,
+// no write operation occurs.
+func (f *Fingerprints) addFingerprint(fingerprint format.Fingerprint,
+	processor interfaces.MessageProcessorFP) {
+
+	if _, exists := f.fingerprints[fingerprint]; exists {
+		return
+	}
+
+	newMsgProc := newProcessor(processor)
+
+	f.fingerprints[fingerprint] = newMsgProc
+}
+
+// RemoveFingerprint is a thread-safe deletion operation on the Fingerprints map.
+// It will remove the entry for the given fingerprint from the map.
+func (f *Fingerprints) RemoveFingerprint(fingerprint format.Fingerprint) {
+	f.Lock()
+	defer f.Unlock()
+
+	delete(f.fingerprints, fingerprint)
+}
+
+// RemoveFingerprints is a thread-safe batch deletion operation on the Fingerprints map.
+// It will remove the entries for the given fingerprints from the map.
+func (f *Fingerprints) RemoveFingerprints(fingerprint []format.Fingerprint) {
+	f.Lock()
+	defer f.Unlock()
+
+	for _, fp := range fingerprint {
+		delete(f.fingerprints, fp)
+	}
+}
diff --git a/network/fingerprints_test.go b/network/fingerprints_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6a688ec3410a0a0067a1acbaadebbff06694d249
--- /dev/null
+++ b/network/fingerprints_test.go
@@ -0,0 +1,240 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package network
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/primitives/format"
+	"reflect"
+	"strconv"
+	"sync"
+	"testing"
+)
+
+// Unit test.
+func TestNewFingerprints(t *testing.T) {
+	expected := &Fingerprints{
+		fingerprints: make(map[format.Fingerprint]*Processor),
+		RWMutex:      sync.RWMutex{},
+	}
+
+	received := NewFingerprints()
+
+	if !reflect.DeepEqual(expected, received) {
+		t.Fatalf("NewFingerprint error: Did not construct expected object."+
+			"\nExpected: %v"+
+			"\nReceived: %v", expected, received)
+	}
+}
+
+// Unit test.
+func TestFingerprints_Get(t *testing.T) {
+	// Construct fingerprint map
+	fpTracker := NewFingerprints()
+
+	// Construct fingerprint and processor values
+	fp := format.NewFingerprint([]byte("test"))
+	mp := NewMockMsgProcessor(t)
+
+	// Add the values to the tracker
+	fpTracker.AddFingerprint(fp, mp)
+
+	// Attempt to retrieve value from map
+	received, exists := fpTracker.Get(fp)
+	if !exists {
+		t.Fatalf("Get error: Did not retrieve fingerprint (%s) that "+
+			"should have been in map.", fp)
+	}
+
+	// Check that received value contains the expected data
+	expected := newProcessor(mp)
+	if !reflect.DeepEqual(received, expected) {
+		t.Fatalf("Get error: Map does not contain expected data."+
+			"\nExpected: %v"+
+			"\nReceived: %v", expected, received)
+	}
+
+}
+
+// Unit test.
+func TestFingerprints_AddFingerprint(t *testing.T) {
+	// Construct fingerprint map
+	fpTracker := NewFingerprints()
+
+	// Construct fingerprint and processor values
+	fp := format.NewFingerprint([]byte("test"))
+	mp := NewMockMsgProcessor(t)
+
+	// Add the values to the tracker
+	fpTracker.AddFingerprint(fp, mp)
+
+	// Check that the fingerprint key has a map entry
+	received, exists := fpTracker.fingerprints[fp]
+	if !exists {
+		t.Fatalf("AddFingerprint did not write to map as expected. "+
+			"Fingerprint %s not found in map", fp)
+	}
+
+	// Check that received value contains the expected data
+	expected := newProcessor(mp)
+	if !reflect.DeepEqual(received, expected) {
+		t.Fatalf("AddFingerprint error: Map does not contain expected data."+
+			"\nExpected: %v"+
+			"\nReceived: %v", expected, received)
+	}
+}
+
+// Unit test.
+func TestFingerprints_AddFingerprints(t *testing.T) {
+	// Construct fingerprints map
+	fpTracker := NewFingerprints()
+
+	// Construct slices of fingerprints and processors
+	numTests := 100
+	fingerprints := make([]format.Fingerprint, 0, numTests)
+	processors := make([]interfaces.MessageProcessorFP, 0, numTests)
+	for i := 0; i < numTests; i++ {
+		fp := format.NewFingerprint([]byte(strconv.Itoa(i)))
+		mp := NewMockMsgProcessor(t)
+
+		fingerprints = append(fingerprints, fp)
+		processors = append(processors, mp)
+	}
+
+	// Add slices to map
+	err := fpTracker.AddFingerprints(fingerprints, processors)
+	if err != nil {
+		t.Fatalf("AddFingerprints unexpected error: %v", err)
+	}
+
+	// Make sure every fingerprint is mapped to it's expected processor
+	for i, expected := range fingerprints {
+		received, exists := fpTracker.fingerprints[expected]
+		if !exists {
+			t.Errorf("AddFingerprints did not write to map as expected. "+
+				"Fingerprint number %d (value: %s) not found in map", i, expected)
+		}
+
+		if !reflect.DeepEqual(received, expected) {
+			t.Fatalf("AddFingerprints error: Map does not contain expected data for "+
+				"fingerprint number %d."+
+				"\nExpected: %v"+
+				"\nReceived: %v", i, expected, received)
+		}
+	}
+
+}
+
+// Error case: Call Fingerprints.AddFingerprints with fingerprint and processor
+// slices of different lengths.
+func TestFingerprints_AddFingerprints_Error(t *testing.T) {
+	// Construct fingerprint map
+	fpTracker := NewFingerprints()
+
+	// Construct 2 slices of different lengths
+	fingerprints := []format.Fingerprint{
+		format.NewFingerprint([]byte("1")),
+		format.NewFingerprint([]byte("2")),
+		format.NewFingerprint([]byte("3")),
+	}
+	processors := []interfaces.MessageProcessorFP{
+		NewMockMsgProcessor(t),
+	}
+
+	// Attempt to add fingerprints
+	err := fpTracker.AddFingerprints(fingerprints, processors)
+	if err == nil {
+		t.Fatalf("AddFingerprints should have received an error with mismatched " +
+			"slices length")
+	}
+
+}
+
+func TestFingerprints_RemoveFingerprint(t *testing.T) {
+
+	// Construct fingerprint map
+	fpTracker := NewFingerprints()
+
+	// Construct fingerprint and processor values
+	fp := format.NewFingerprint([]byte("test"))
+	mp := NewMockMsgProcessor(t)
+
+	// Add the values to the tracker
+	fpTracker.AddFingerprint(fp, mp)
+
+	// Remove value from tracker
+	fpTracker.RemoveFingerprint(fp)
+
+	// Check that value no longer exists within the map
+	if _, exists := fpTracker.fingerprints[fp]; exists {
+		t.Fatalf("RemoveFingerprint error: "+
+			"Fingerprint %s exists in map after a RemoveFingerprint call", fp)
+	}
+}
+
+// Unit test.
+func TestFingerprints_RemoveFingerprints(t *testing.T) {
+	// Construct fingerprints map
+	fpTracker := NewFingerprints()
+
+	// Construct slices of fingerprints and processors
+	numTests := 100
+	fingerprints := make([]format.Fingerprint, 0, numTests)
+	processors := make([]interfaces.MessageProcessorFP, 0, numTests)
+	for i := 0; i < numTests; i++ {
+		fp := format.NewFingerprint([]byte(strconv.Itoa(i)))
+		mp := NewMockMsgProcessor(t)
+
+		fingerprints = append(fingerprints, fp)
+		processors = append(processors, mp)
+	}
+
+	// Add slices to map
+	err := fpTracker.AddFingerprints(fingerprints, processors)
+	if err != nil {
+		t.Fatalf("AddFingerprints unexpected error: %v", err)
+	}
+
+	fpTracker.RemoveFingerprints(fingerprints)
+
+	// Make sure every fingerprint is mapped to it's expected processor
+	for i, expected := range fingerprints {
+
+		if received, exists := fpTracker.fingerprints[expected]; !exists {
+			t.Fatalf("RemoveFingerprints error: Map does not contain "+
+				"expected data for fingerprint number %d."+
+				"\nExpected: %v"+
+				"\nReceived: %v", i, expected, received)
+		}
+
+	}
+
+}
+
+// todo: consider moving this to a test utils somewhere else.. maybe in the interfaces package?
+type MockMsgProcessor struct{}
+
+func NewMockMsgProcessor(face interface{}) *MockMsgProcessor {
+	switch face.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("NewMockMsgProcessor is restricted to testing only. Got %T", face)
+	}
+
+	return &MockMsgProcessor{}
+}
+
+func (mock *MockMsgProcessor) MarkFingerprintUsed(fingerprint format.Fingerprint) {
+	return
+}
+
+func (mock *MockMsgProcessor) Process(message format.Message, fingerprint format.Fingerprint) {
+	return
+}