diff --git a/cmix/client.go b/cmix/client.go
index 996cacdb46016a7675cde906cbcd9c66dc68c766..d164b4d2a60620176fe7fee12c5a26e95e351c99 100644
--- a/cmix/client.go
+++ b/cmix/client.go
@@ -189,9 +189,14 @@ func (c *client) initialize(ndf *ndf.NetworkDefinition) error {
 	// Set up critical message tracking (sendCmix only)
 	critSender := func(msg format.Message, recipient *id.ID, params CMIXParams,
 	) (id.Round, ephemeral.Id, error) {
-		return sendCmixHelper(c.Sender, msg, recipient, params, c.instance,
+		compiler := func(round id.Round) (format.Message, error) {
+			return msg, nil
+		}
+		rid, eid, _, sendErr := sendCmixHelper(c.Sender, compiler, recipient, params, c.instance,
 			c.session.GetCmixGroup(), c.Registrar, c.rng, c.events,
 			c.session.GetTransmissionID(), c.comms)
+		return rid, eid, sendErr
+
 	}
 
 	c.crit = newCritical(c.session.GetKV(), c.Monitor,
diff --git a/cmix/interface.go b/cmix/interface.go
index a5db8b3cfe2089e94d03861769785ab99bd4390a..717dbb0bbe8b26a288aaa79427bc6d2ed07a30a6 100644
--- a/cmix/interface.go
+++ b/cmix/interface.go
@@ -290,6 +290,14 @@ type Client interface {
 
 type ClientErrorReport func(source, message, trace string)
 
+// MessageAssembler func accepts a round ID, returning fingerprint, service, payload & mac.
+// This allows users to pass in a paylaod which will contain the round ID over which the message is sent.
+type MessageAssembler func(rid id.Round) (fingerprint format.Fingerprint, service message.Service, payload, mac []byte)
+
+// messageAssembler is an internal wrapper around MessageAssembler which returns a format.message
+// This is necessary to preserve the interaction between sendCmixHelper and critical messages
+type messageAssembler func(rid id.Round) (format.Message, error)
+
 type clientCommsInterface interface {
 	followNetworkComms
 	SendCmixCommsInterface
diff --git a/cmix/sendCmix.go b/cmix/sendCmix.go
index 73dfcfbd2c02b6f1cafcf49cd4662826edf7e3e8..648e1ef1c8691211f40bf11fc76e86bc17e5dfe7 100644
--- a/cmix/sendCmix.go
+++ b/cmix/sendCmix.go
@@ -56,34 +56,74 @@ import (
 // WARNING: Do not roll your own crypto.
 func (c *client) Send(recipient *id.ID, fingerprint format.Fingerprint,
 	service message.Service, payload, mac []byte, cmixParams CMIXParams) (
+	id.Round, ephemeral.Id, error) {
+	// create an internal assembler function to pass to sendWithAssembler
+	assembler := func(rid id.Round) (format.Fingerprint, message.Service, []byte, []byte) {
+		return fingerprint, service, payload, mac
+	}
+	return c.sendWithAssembler(recipient, assembler, cmixParams)
+}
+
+// SendWithAssembler sends a variable cmix payload to the provided recipient.
+// The payload sent is based on the Complier function passed in, which accepts
+// a round ID and returns the necessary payload data.
+// Returns the round ID of the round the payload was sent or an error if it
+// fails.
+// This does not have end-to-end encryption on it and is used exclusively as
+// a send for higher order cryptographic protocols. Do not use unless
+// implementing a protocol on top.
+//   recipient - cMix ID of the recipient.
+//   assembler - MessageAssembler function, accepting round ID and returning fingerprint
+//   format.Fingerprint, service message.Service, payload, mac []byte
+// Will return an error if the network is unhealthy or if it fails to send
+// (along with the reason). Blocks until successful sends or errors.
+// WARNING: Do not roll your own crypto.
+func (c *client) SendWithAssembler(recipient *id.ID, assembler MessageAssembler, cmixParams CMIXParams) (
+	id.Round, ephemeral.Id, error) {
+	// Critical messaging and assembler-based message payloads are not compatible
+	if cmixParams.Critical {
+		return 0, ephemeral.Id{}, errors.New("Cannot send critical messages with a message assembler")
+	}
+	return c.sendWithAssembler(recipient, assembler, cmixParams)
+}
+
+// sendWithAssembler wraps the passed in MessageAssembler in a messageAssembler for sendCmixHelper,
+// and sets up critical message handling where applicable.
+func (c *client) sendWithAssembler(recipient *id.ID, assembler MessageAssembler, cmixParams CMIXParams) (
 	id.Round, ephemeral.Id, error) {
 	if !c.Monitor.IsHealthy() {
 		return 0, ephemeral.Id{}, errors.New(
 			"Cannot send cmix message when the network is not healthy")
 	}
 
-	if len(payload) != c.maxMsgLen {
-		return 0, ephemeral.Id{}, errors.Errorf(
-			"bad message length (%d, need %d)",
-			len(payload), c.maxMsgLen)
-	}
+	// Create an internal messageAssembler which returns a format.Message
+	assemblerFunc := func(rid id.Round) (format.Message, error) {
+		fingerprint, service, payload, mac := assembler(rid)
 
-	// Build message. Will panic if inputs are not correct.
-	msg := format.NewMessage(c.session.GetCmixGroup().GetP().ByteLen())
-	msg.SetContents(payload)
-	msg.SetKeyFP(fingerprint)
-	msg.SetSIH(service.Hash(msg.GetContents()))
-	msg.SetMac(mac)
+		if len(payload) != c.maxMsgLen {
+			return format.Message{}, errors.Errorf(
+				"bad message length (%d, need %d)",
+				len(payload), c.maxMsgLen)
+		}
 
-	jww.TRACE.Printf("sendCmix Contents: %v, KeyFP: %v, MAC: %v, SIH: %v",
-		msg.GetContents(), msg.GetKeyFP(), msg.GetMac(),
-		msg.GetSIH())
+		// Build message. Will panic if inputs are not correct.
+		msg := format.NewMessage(c.session.GetCmixGroup().GetP().ByteLen())
+		msg.SetContents(payload)
+		msg.SetKeyFP(fingerprint)
+		msg.SetSIH(service.Hash(msg.GetContents()))
+		msg.SetMac(mac)
 
-	if cmixParams.Critical {
-		c.crit.AddProcessing(msg, recipient, cmixParams)
+		jww.TRACE.Printf("sendCmix Contents: %v, KeyFP: %v, MAC: %v, SIH: %v",
+			msg.GetContents(), msg.GetKeyFP(), msg.GetMac(),
+			msg.GetSIH())
+
+		if cmixParams.Critical {
+			c.crit.AddProcessing(msg, recipient, cmixParams)
+		}
+		return msg, nil
 	}
 
-	rid, ephID, rtnErr := sendCmixHelper(c.Sender, msg, recipient, cmixParams,
+	rid, ephID, msg, rtnErr := sendCmixHelper(c.Sender, assemblerFunc, recipient, cmixParams,
 		c.instance, c.session.GetCmixGroup(), c.Registrar, c.rng, c.events,
 		c.session.GetTransmissionID(), c.comms)
 
@@ -104,10 +144,10 @@ func (c *client) Send(recipient *id.ID, fingerprint format.Fingerprint,
 // If the message is successfully sent, the ID of the round sent it is returned,
 // which can be registered with the network instance to get a callback on its
 // status.
-func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
+func sendCmixHelper(sender gateway.Sender, assembler messageAssembler, recipient *id.ID,
 	cmixParams CMIXParams, instance *network.Instance, grp *cyclic.Group,
 	nodes nodes.Registrar, rng *fastRNG.StreamGenerator, events event.Reporter,
-	senderId *id.ID, comms SendCmixCommsInterface) (id.Round, ephemeral.Id, error) {
+	senderId *id.ID, comms SendCmixCommsInterface) (id.Round, ephemeral.Id, format.Message, error) {
 
 	timeStart := netTime.Now()
 	maxTimeout := sender.GetHostParams().SendTimeout
@@ -120,15 +160,11 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 	}
 
 	jww.INFO.Printf("[Send-%s] Looking for round to send cMix message to "+
-		"%s (msgDigest: %s)", cmixParams.DebugTag, recipient, msg.Digest())
+		"%s", cmixParams.DebugTag, recipient)
 
 	stream := rng.GetStream()
 	defer stream.Close()
 
-	// Flip leading bits randomly to thwart a tagging attack.
-	// See cmix.SetGroupBits for more info.
-	cmix.SetGroupBits(msg, grp, stream)
-
 	for numRoundTries := uint(0); numRoundTries < cmixParams.RoundTries; numRoundTries++ {
 		elapsed := netTime.Since(timeStart)
 		jww.TRACE.Printf("[Send-%s] try %d, elapsed: %s",
@@ -136,15 +172,15 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 
 		if elapsed > cmixParams.Timeout {
 			jww.INFO.Printf("[Send-%s] No rounds to send to %s "+
-				"(msgDigest: %s) were found before timeout %s",
-				cmixParams.DebugTag, recipient, msg.Digest(), cmixParams.Timeout)
-			return 0, ephemeral.Id{}, errors.New("Sending cmix message timed out")
+				"were found before timeout %s",
+				cmixParams.DebugTag, recipient, cmixParams.Timeout)
+			return 0, ephemeral.Id{}, format.Message{}, errors.New("Sending cmix message timed out")
 		}
 
 		if numRoundTries > 0 {
 			jww.INFO.Printf("[Send-%s] Attempt %d to find round to send "+
-				"message to %s (msgDigest: %s)", cmixParams.DebugTag,
-				numRoundTries+1, recipient, msg.Digest())
+				"message to %s", cmixParams.DebugTag,
+				numRoundTries+1, recipient)
 		}
 
 		// Find the best round to send to, excluding attempted rounds
@@ -152,8 +188,8 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 		bestRound, err := instance.GetWaitingRounds().GetUpcomingRealtime(
 			remainingTime, attempted, sendTimeBuffer)
 		if err != nil {
-			jww.WARN.Printf("[Send-%s] Failed to GetUpcomingRealtime "+
-				"(msgDigest: %s): %+v", cmixParams.DebugTag, msg.Digest(), err)
+			jww.WARN.Printf("[Send-%s] Failed to GetUpcomingRealtime: "+
+				"%+v", cmixParams.DebugTag, err)
 		}
 
 		if bestRound == nil {
@@ -185,6 +221,16 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 			continue
 		}
 
+		msg, err := assembler(id.Round(bestRound.ID))
+		if err != nil {
+			jww.ERROR.Printf("Failed to compile message: %+v", err)
+			return 0, ephemeral.Id{}, format.Message{}, err
+		}
+
+		// Flip leading bits randomly to thwart a tagging attack.
+		// See cmix.SetGroupBits for more info.
+		cmix.SetGroupBits(msg, grp, stream)
+
 		// Retrieve host and key information from round
 		firstGateway, roundKeys, err := processRound(
 			nodes, bestRound, recipient.String(), msg.Digest())
@@ -201,7 +247,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 		wrappedMsg, encMsg, ephID, err := buildSlotMessage(msg, recipient,
 			firstGateway, stream, senderId, bestRound, roundKeys)
 		if err != nil {
-			return 0, ephemeral.Id{}, err
+			return 0, ephemeral.Id{}, format.Message{}, err
 		}
 
 		jww.INFO.Printf("[Send-%s] Sending to EphID %d (%s), on round %d "+
@@ -251,7 +297,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 
 		// Exit if the thread has been stopped
 		if stoppable.CheckErr(err) {
-			return 0, ephemeral.Id{}, err
+			return 0, ephemeral.Id{}, format.Message{}, err
 		}
 
 		// If the comm errors or the message fails to send, continue retrying
@@ -260,7 +306,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 				jww.ERROR.Printf("[Send-%s] SendCmix failed to send to "+
 					"EphID %d (%s) on round %d: %+v", cmixParams.DebugTag,
 					ephID.Int64(), recipient, bestRound.ID, err)
-				return 0, ephemeral.Id{}, err
+				return 0, ephemeral.Id{}, format.Message{}, err
 			}
 
 			jww.ERROR.Printf("[Send-%s] SendCmix failed to send to "+
@@ -280,7 +326,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 			jww.INFO.Print(m)
 			events.Report(1, "MessageSend", "Metric", m)
 
-			return id.Round(bestRound.ID), ephID, nil
+			return id.Round(bestRound.ID), ephID, msg, nil
 		} else {
 			jww.FATAL.Panicf("[Send-%s] Gateway %s returned no error, "+
 				"but failed to accept message when sending to EphID %d (%s) "+
@@ -289,6 +335,6 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 		}
 
 	}
-	return 0, ephemeral.Id{},
+	return 0, ephemeral.Id{}, format.Message{},
 		errors.New("failed to send the message, unknown error")
 }
diff --git a/go.mod b/go.mod
index c07f1e0a18e016ce446e62ec1e45d7f0c2bec363..5bb8e838962b73a263217f083138058fcdb87375 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/spf13/viper v1.12.0
 	gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f
 	gitlab.com/elixxir/comms v0.0.4-0.20220805121030-b95005ac4528
-	gitlab.com/elixxir/crypto v0.0.7-0.20220606201132-c370d5039cea
+	gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46
 	gitlab.com/elixxir/ekv v0.1.7
 	gitlab.com/elixxir/primitives v0.0.3-0.20220606195757-40f7a589347f
 	gitlab.com/xx_network/comms v0.0.4-0.20220630163702-f3d372ef6acd
diff --git a/go.sum b/go.sum
index 35b35f27bea260a64edf8071d298894195082a41..cccd675d6b722e0aaac060aeb9f6d0c0bd2630a2 100644
--- a/go.sum
+++ b/go.sum
@@ -429,8 +429,8 @@ gitlab.com/elixxir/comms v0.0.4-0.20220805121030-b95005ac4528/go.mod h1:Zi1O21+p
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
 gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
 gitlab.com/elixxir/crypto v0.0.7-0.20220317172048-3de167bd9406/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10=
-gitlab.com/elixxir/crypto v0.0.7-0.20220606201132-c370d5039cea h1:+FjwbKl6X9TDT7qd7gG5N5PSbziPWP3NgjK5ci1b7/8=
-gitlab.com/elixxir/crypto v0.0.7-0.20220606201132-c370d5039cea/go.mod h1:Oy+VWQ2Sa0Ybata3oTV+Yc46hkaDwAsuIMW0wJ01z2M=
+gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46 h1:C8nAiMnL8IOGjQ5qErbpzAjRMVFMoB1GunYk8pGOEz8=
+gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46/go.mod h1:Oy+VWQ2Sa0Ybata3oTV+Yc46hkaDwAsuIMW0wJ01z2M=
 gitlab.com/elixxir/ekv v0.1.7 h1:OW2z+N4QCqqMFzouAwFTWWMKz0Y/PDhyYReN7gQ5NiQ=
 gitlab.com/elixxir/ekv v0.1.7/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=