Repository: decred/dcrd
Pull request #3243 (2024-05-03 18:07:07)

wire: Add Opcode field to MixPairReqUTXO
Validating contributed signatures of vote, revocation, and treasury generation outputs requires knowing the exact opcode tag used by the previous output script, as it is committed to by the sighash.  Include it in the MixPairReqUTXO so that all clients are able to validate the merged signatures in the coinjoin.  The special value 0 (OP_FALSE) will be used for regular tree outputs that do not use a no-op tag.

This is a breaking API change but the wire module has not seen a release since mixing messages were added.


Patch:
diff --git a/blockchain/chaingen/generator.go b/blockchain/chaingen/generator.go
index b51b54f1..4631de7a 100644
--- a/blockchain/chaingen/generator.go
+++ b/blockchain/chaingen/generator.go
@@ -1371,7 +1371,7 @@ func (g *Generator) calcNextRequiredDiffASERT() uint32 {
 		g.params.WorkDiffV2HalfLifeSecs)
 }
 
-// calcNextRequiredDifficultyEMA returns the required proof-of-work difficulty
+// CalcNextRequiredDifficulty returns the required proof-of-work difficulty
 // for the block after the current tip block the generator is associated with
 // using the difficulty algorithm configured for the generator.
 func (g *Generator) CalcNextRequiredDifficulty() uint32 {
diff --git a/internal/mining/mining.go b/internal/mining/mining.go
index d2b2be20..ef51e961 100644
--- a/internal/mining/mining.go
+++ b/internal/mining/mining.go
@@ -897,7 +897,15 @@ func (g *BlkTmplGenerator) handleTooFewVoters(nextHeight int64,
 			return nil, err
 		}
 		header := &block.Header
-		header.MerkleRoot = calcBlockMerkleRoot(block.Transactions, block.STransactions, hdrCmtActive)
+		header.MerkleRoot = calcBlockMerkleRoot(block.Transactions,
+			block.STransactions, hdrCmtActive)
+
+		// Calculate the required difficulty for the block.
+		reqDifficulty, err := g.cfg.CalcNextRequiredDifficulty(prevHash, ts)
+		if err != nil {
+			return nil, makeError(ErrGettingDifficulty, err.Error())
+		}
+		header.Bits = reqDifficulty
 
 		// Calculate the stake root or commitment root depending on the result
 		// of the header commitments agenda vote.
diff --git a/rpcadaptors.go b/rpcadaptors.go
index 643f9f80..ea22f783 100644
--- a/rpcadaptors.go
+++ b/rpcadaptors.go
@@ -94,7 +94,7 @@ func (p *rpcPeer) LastPingNonce() uint64 {
 // This function is safe for concurrent access and is part of the rpcserver.Peer
 // interface implementation.
 func (p *rpcPeer) IsTxRelayDisabled() bool {
-	return (*serverPeer)(p).relayTxDisabled()
+	return (*serverPeer)(p).disableRelayTx.Load()
 }
 
 // BanScore returns the current integer value that represents how close the peer
diff --git a/server.go b/server.go
index 1c0ee6d2..a6692915 100644
--- a/server.go
+++ b/server.go
@@ -551,8 +551,7 @@ type serverPeer struct {
 	server         *server
 	persistent     bool
 	continueHash   atomic.Pointer[chainhash.Hash]
-	relayMtx       sync.Mutex
-	disableRelayTx bool
+	disableRelayTx atomic.Bool
 	isWhitelisted  bool
 	knownAddresses *apbf.Filter
 	banScore       connmgr.DynamicBanScore
@@ -575,8 +574,7 @@ type serverPeer struct {
 	blockProcessed chan struct{}
 
 	// peerNa is network address of the peer connected to.
-	peerNa    *wire.NetAddress
-	peerNaMtx sync.Mutex
+	peerNa atomic.Pointer[wire.NetAddress]
 
 	// announcedBlock tracks the most recent block announced to this peer and is
 	// used to filter duplicates.
@@ -824,25 +822,6 @@ func (sp *serverPeer) addressKnown(na *addrmgr.NetAddress) bool {
 	return sp.knownAddresses.Contains([]byte(na.Key()))
 }
 
-// setDisableRelayTx toggles relaying of transactions for the given peer.
-// It is safe for concurrent access.
-func (sp *serverPeer) setDisableRelayTx(disable bool) {
-	sp.relayMtx.Lock()
-	sp.disableRelayTx = disable
-	sp.relayMtx.Unlock()
-}
-
-// relayTxDisabled returns whether or not relaying of transactions for the given
-// peer is disabled.
-// It is safe for concurrent access.
-func (sp *serverPeer) relayTxDisabled() bool {
-	sp.relayMtx.Lock()
-	isDisabled := sp.disableRelayTx
-	sp.relayMtx.Unlock()
-
-	return isDisabled
-}
-
 // wireToAddrmgrNetAddress converts a wire NetAddress to an address manager
 // NetAddress.
 func wireToAddrmgrNetAddress(netAddr *wire.NetAddress) *addrmgr.NetAddress {
@@ -1022,12 +1001,10 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) {
 		}
 	}
 
-	sp.peerNaMtx.Lock()
-	sp.peerNa = &msg.AddrYou
-	sp.peerNaMtx.Unlock()
+	sp.peerNa.Store(&msg.AddrYou)
 
 	// Choose whether or not to relay transactions.
-	sp.setDisableRelayTx(msg.DisableRelayTx)
+	sp.disableRelayTx.Store(msg.DisableRelayTx)
 
 	// Add the remote peer time as a sample for creating an offset against
 	// the local clock to keep the network time in sync.
@@ -1861,9 +1838,7 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool {
 		return false
 	}
 
-	sp.peerNaMtx.Lock()
-	na := sp.peerNa
-	sp.peerNaMtx.Unlock()
+	na := sp.peerNa.Load()
 
 	// Add the new peer and start it.
 	srvrLog.Debugf("New peer %s", sp)
@@ -2062,9 +2037,9 @@ func (s *server) handleRelayInvMsg(state *peerState, msg relayMsg) {
 		}
 
 		if iv.Type == wire.InvTypeTx {
-			// Don't relay the transaction to the peer when it has
-			// transaction relaying disabled.
-			if sp.relayTxDisabled() {
+			// Don't relay the transaction to the peer when it has transaction
+			// relaying disabled.
+			if sp.disableRelayTx.Load() {
 				return
 			}
 		}
diff --git a/upnp.go b/upnp.go
index b6879678..4f1c0118 100644
--- a/upnp.go
+++ b/upnp.go
@@ -188,7 +188,7 @@ func getChildDevice(d *device, deviceType string) *device {
 	return nil
 }
 
-// getChildDevice searches the service list of device for a service with the
+// getChildService searches the service list of device for a service with the
 // given type.
 func getChildService(d *device, serviceType string) *service {
 	for i := range d.ServiceList.Service {
@@ -281,7 +281,7 @@ type soapEnvelope struct {
 	Body    soapBody `xml:"Body"`
 }
 
-// soapRequests performs a soap request with the given parameters and returns
+// soapRequest performs a soap request with the given parameters and returns
 // the xml replied stripped of the soap headers. in the case that the request is
 // unsuccessful the an error is returned.
 func soapRequest(url, function, message string) (replyXML []byte, err error) {
diff --git a/wire/msggetinitstate.go b/wire/msggetinitstate.go
index c787c772..350b6fe4 100644
--- a/wire/msggetinitstate.go
+++ b/wire/msggetinitstate.go
@@ -29,10 +29,6 @@ const (
 	// InitStateTSpends is the init state type used to request tpends for
 	// voting.
 	InitStateTSpends = "tspends"
-
-	// InitStateMixPairReqs is the init state type used to request mixing pair
-	// request messages.
-	InitStateMixPairReqs = "mixpairreqs"
 )
 
 // MsgGetInitState implements the Message interface and represents a
diff --git a/wire/msginitstate.go b/wire/msginitstate.go
index 05a71fdf..1ec60ec3 100644
--- a/wire/msginitstate.go
+++ b/wire/msginitstate.go
@@ -22,10 +22,6 @@ const MaxISVotesAtHeadPerMsg = 40 // 8 * 5
 // message.
 const MaxISTSpendsAtHeadPerMsg = 7
 
-// MaxISMixPairReqsPerMsg is the maximum number of pair request mix messages
-// at head per message.
-const MaxISMixPairReqsPerMsg = 100
-
 // MsgInitState implements the Message interface and represents an initial
 // state message.  It is used to receive ephemeral startup information from a
 // remote peer, such as blocks that can be mined upon, votes for such blocks
@@ -34,10 +30,9 @@ const MaxISMixPairReqsPerMsg = 100
 // The content of such a message depends upon what the local peer requested
 // during a previous GetInitState msg.
 type MsgInitState struct {
-	BlockHashes      []chainhash.Hash
-	VoteHashes       []chainhash.Hash
-	TSpendHashes     []chainhash.Hash
-	MixPairReqHashes []chainhash.Hash
+	BlockHashes  []chainhash.Hash
+	VoteHashes   []chainhash.Hash
+	TSpendHashes []chainhash.Hash
 }
 
 // AddBlockHash adds a new block hash to the message. Up to
@@ -82,20 +77,6 @@ func (msg *MsgInitState) AddTSpendHash(hash *chainhash.Hash) error {
 	return nil
 }
 
-// AddMixPairReqHash adds a new mixing pair request message hash to the message.
-// Up to MaxISMixPRsPerMsg may be added before this function errors out.
-func (msg *MsgInitState) AddMixPairReqHash(hash *chainhash.Hash) error {
-	const op = "MsgInitState.AddMixPairReqHash"
-	if len(msg.MixPairReqHashes)+1 > MaxISMixPairReqsPerMsg {
-		msg := fmt.Sprintf("too many mixpairreq hashes for message [max %v]",
-			MaxISMixPairReqsPerMsg)
-		return messageError(op, ErrTooManyManyMixPairReqs, msg)
-	}
-
-	msg.MixPairReqHashes = append(msg.MixPairReqHashes, *hash)
-	return nil
-}
-
 // BtcDecode decodes r using the protocol encoding into the receiver.
 // This is part of the Message interface implementation.
 func (msg *MsgInitState) BtcDecode(r io.Reader, pver uint32) error {
@@ -163,29 +144,6 @@ func (msg *MsgInitState) BtcDecode(r io.Reader, pver uint32) error {
 		}
 	}
 
-	// Read num mixpairreq hashes (when enabled by protocol).
-	if pver < MixVersion {
-		return nil
-	}
-
-	count, err = ReadVarInt(r, pver)
-	if err != nil {
-		return err
-	}
-	if count > MaxISMixPairReqsPerMsg {
-		msg := fmt.Sprintf("too many mixpairreq hashes for message "+
-			"[count %v, max %v]", count, MaxISMixPairReqsPerMsg)
-		return messageError(op, ErrTooManyManyMixPairReqs, msg)
-	}
-
-	msg.MixPairReqHashes = make([]chainhash.Hash, count)
-	for i := uint64(0); i < count; i++ {
-		err := readElement(r, &msg.MixPairReqHashes[i])
-		if err != nil {
-			return err
-		}
-	}
-
 	return nil
 }
 
@@ -259,30 +217,6 @@ func (msg *MsgInitState) BtcEncode(w io.Writer, pver uint32) error {
 		}
 	}
 
-	// Write mixpairreq hashes when enabled by protocol.
-	if pver < MixVersion {
-		return nil
-	}
-
-	count = len(msg.MixPairReqHashes)
-	if count > MaxISMixPairReqsPerMsg {
-		msg := fmt.Sprintf("too many mixpairreq hashes for message "+
-			"[count %v, max %v]", count, MaxISMixPairReqsPerMsg)
-		return messageError(op, ErrTooManyManyMixPairReqs, msg)
-	}
-
-	err = WriteVarInt(w, pver, uint64(count))
-	if err != nil {
-		return err
-	}
-
-	for i := range msg.MixPairReqHashes {
-		err = writeElement(w, &msg.MixPairReqHashes[i])
-		if err != nil {
-			return err
-		}
-	}
-
 	return nil
 }
 
@@ -299,29 +233,21 @@ func (msg *MsgInitState) MaxPayloadLength(pver uint32) uint32 {
 		return 0
 	}
 
-	max := uint32(VarIntSerializeSize(MaxISBlocksAtHeadPerMsg)) +
+	return uint32(VarIntSerializeSize(MaxISBlocksAtHeadPerMsg)) +
 		(MaxISBlocksAtHeadPerMsg * chainhash.HashSize) +
 		uint32(VarIntSerializeSize(MaxISVotesAtHeadPerMsg)) +
 		(MaxISVotesAtHeadPerMsg * chainhash.HashSize) +
 		uint32(VarIntSerializeSize(MaxISTSpendsAtHeadPerMsg)) +
 		(MaxISTSpendsAtHeadPerMsg * chainhash.HashSize)
-
-	if pver >= MixVersion {
-		max += uint32(VarIntSerializeSize(MaxISMixPairReqsPerMsg)) +
-			(MaxISMixPairReqsPerMsg * chainhash.HashSize)
-	}
-
-	return max
 }
 
 // NewMsgInitState returns a new Decred initstate message that conforms to the
 // Message interface using the defaults for the fields.
 func NewMsgInitState() *MsgInitState {
 	return &MsgInitState{
-		BlockHashes:      make([]chainhash.Hash, 0, MaxISBlocksAtHeadPerMsg),
-		VoteHashes:       make([]chainhash.Hash, 0, MaxISVotesAtHeadPerMsg),
-		TSpendHashes:     make([]chainhash.Hash, 0, MaxISTSpendsAtHeadPerMsg),
-		MixPairReqHashes: make([]chainhash.Hash, 0, MaxISMixPairReqsPerMsg),
+		BlockHashes:  make([]chainhash.Hash, 0, MaxISBlocksAtHeadPerMsg),
+		VoteHashes:   make([]chainhash.Hash, 0, MaxISVotesAtHeadPerMsg),
+		TSpendHashes: make([]chainhash.Hash, 0, MaxISTSpendsAtHeadPerMsg),
 	}
 }
 
@@ -356,17 +282,9 @@ func NewMsgInitStateFilled(blockHashes []chainhash.Hash, voteHashes []chainhash.
 		return nil, messageError(op, ErrTooManyTSpends, msg)
 	}
 
-	// Not preallocated.  The purpose of this function is to create a
-	// MsgInitState from already allocated data.  Assume that if the
-	// caller also has mixpairreq hashes to include, they have also been
-	// preallocated, so there is no reason to allocate the memory here
-	// too.
-	var mixPairReqHashes []chainhash.Hash
-
 	return &MsgInitState{
-		BlockHashes:      blockHashes,
-		VoteHashes:       voteHashes,
-		TSpendHashes:     tspendHashes,
-		MixPairReqHashes: mixPairReqHashes,
+		BlockHashes:  blockHashes,
+		VoteHashes:   voteHashes,
+		TSpendHashes: tspendHashes,
 	}, nil
 }
diff --git a/wire/msgmixkeyexchange.go b/wire/msgmixkeyexchange.go
index e7bfa4ab..62c4553f 100644
--- a/wire/msgmixkeyexchange.go
+++ b/wire/msgmixkeyexchange.go
@@ -30,6 +30,7 @@ type MsgMixKeyExchange struct {
 	Signature  [64]byte
 	Identity   [33]byte
 	SessionID  [32]byte
+	Epoch      uint64
 	Run        uint32
 	ECDH       [33]byte   // Secp256k1 public key
 	PQPK       [1218]byte // Sntrup4591761 public key
@@ -53,7 +54,7 @@ func (msg *MsgMixKeyExchange) BtcDecode(r io.Reader, pver uint32) error {
 	}
 
 	err := readElements(r, &msg.Signature, &msg.Identity, &msg.SessionID,
-		&msg.Run, &msg.ECDH, &msg.PQPK, &msg.Commitment)
+		&msg.Epoch, &msg.Run, &msg.ECDH, &msg.PQPK, &msg.Commitment)
 	if err != nil {
 		return err
 	}
@@ -146,8 +147,8 @@ func (msg *MsgMixKeyExchange) writeMessageNoSignature(op string, w io.Writer, pv
 		return messageError(op, ErrTooManyPrevMixMsgs, msg)
 	}
 
-	err := writeElements(w, &msg.Identity, &msg.SessionID, msg.Run,
-		&msg.ECDH, &msg.PQPK, &msg.Commitment)
+	err := writeElements(w, &msg.Identity, &msg.SessionID, msg.Epoch,
+		msg.Run, &msg.ECDH, &msg.PQPK, &msg.Commitment)
 	if err != nil {
 		return err
 	}
@@ -188,7 +189,7 @@ func (msg *MsgMixKeyExchange) MaxPayloadLength(pver uint32) uint32 {
 	}
 
 	// See tests for this calculation.
-	return 17803
+	return 17811
 }
 
 // Pub returns the message sender's public key identity.
@@ -219,12 +220,14 @@ func (msg *MsgMixKeyExchange) GetRun() uint32 {
 // NewMsgMixKeyExchange returns a new mixkeyxchg message that conforms to the
 // Message interface using the passed parameters and defaults for the
 // remaining fields.
-func NewMsgMixKeyExchange(identity [33]byte, sid [32]byte, run uint32,
-	ecdh [33]byte, pqpk [1218]byte, commitment [32]byte, seenPRs []chainhash.Hash) *MsgMixKeyExchange {
+func NewMsgMixKeyExchange(identity [33]byte, sid [32]byte, epoch uint64,
+	run uint32, ecdh [33]byte, pqpk [1218]byte, commitment [32]byte,
+	seenPRs []chainhash.Hash) *MsgMixKeyExchange {
 
 	return &MsgMixKeyExchange{
 		Identity:   identity,
 		SessionID:  sid,
+		Epoch:      epoch,
 		Run:        run,
 		ECDH:       ecdh,
 		PQPK:       pqpk,
diff --git a/wire/msgmixpairreq.go b/wire/msgmixpairreq.go
index 4b6c74e9..378d1635 100644
--- a/wire/msgmixpairreq.go
+++ b/wire/msgmixpairreq.go
@@ -41,11 +41,15 @@ const (
 // mix.  It includes a proof that the output is able to be spent, by
 // containing a signature and the necessary data needed to prove key
 // possession.
+//
+// The opcode defines the type of standard script.  0 is used for regular
+// outputs, otherwise it may be one of OP_SSGEN/OP_SSRTX/OP_TGEN.
 type MixPairReqUTXO struct {
 	OutPoint  OutPoint
 	Script    []byte // Only used for P2SH
 	PubKey    []byte
 	Signature []byte
+	Opcode    byte
 }
 
 // MsgMixPairReq implements the Message interface and represents a mixing pair
@@ -176,6 +180,13 @@ func (msg *MsgMixPairReq) BtcDecode(r io.Reader, pver uint32) error {
 			return err
 		}
 		utxo.Signature = sig
+
+		var opcode byte
+		err = readElement(r, &opcode)
+		if err != nil {
+			return err
+		}
+		utxo.Opcode = opcode
 	}
 	msg.UTXOs = utxos
 
@@ -337,6 +348,11 @@ func (msg *MsgMixPairReq) writeMessageNoSignature(op string, w io.Writer, pver u
 		if err != nil {
 			return err
 		}
+
+		err = writeElement(w, utxo.Opcode)
+		if err != nil {
+			return err
+		}
 	}
 
 	var hasChange uint8
@@ -379,7 +395,7 @@ func (msg *MsgMixPairReq) MaxPayloadLength(pver uint32) uint32 {
 	}
 
 	// See tests for this calculation.
-	return 8476336
+	return 8476848
 }
 
 // Pub returns the message sender's public key identity.
diff --git a/wire/msgmixsecrets.go b/wire/msgmixsecrets.go
index 53d0b06e..7532cc0b 100644
--- a/wire/msgmixsecrets.go
+++ b/wire/msgmixsecrets.go
@@ -25,6 +25,7 @@ type MsgMixSecrets struct {
 	Seed            [32]byte
 	SlotReserveMsgs [][]byte
 	DCNetMsgs       MixVect
+	SeenSecrets     []chainhash.Hash
 
 	// hash records the hash of the message.  It is a member of the
 	// message for convenience and performance, but is never automatically
@@ -43,6 +44,9 @@ func writeMixVect(op string, w io.Writer, pver uint32, vec MixVect) error {
 	if err != nil {
 		return err
 	}
+	if len(vec) == 0 {
+		return nil
+	}
 	err = WriteVarInt(w, pver, MixMsgSize)
 	if err != nil {
 		return err
@@ -138,6 +142,25 @@ func (msg *MsgMixSecrets) BtcDecode(r io.Reader, pver uint32) error {
 	}
 	msg.DCNetMsgs = dcnetMsgs
 
+	count, err := ReadVarInt(r, pver)
+	if err != nil {
+		return err
+	}
+	if count > MaxMixPeers {
+		msg := fmt.Sprintf("too many previous referenced messages [count %v, max %v]",
+			count, MaxMixPeers)
+		return messageError(op, ErrTooManyPrevMixMsgs, msg)
+	}
+
+	seen := make([]chainhash.Hash, count)
+	for i := range seen {
+		err := readElement(r, &seen[i])
+		if err != nil {
+			return err
+		}
+	}
+	msg.SeenSecrets = seen
+
 	return nil
 }
 
@@ -173,6 +196,17 @@ func (msg *MsgMixSecrets) Hash() chainhash.Hash {
 	return msg.hash
 }
 
+// Commitment returns a hash committing to the contents of the reveal secrets
+// message without committing to any previous seen messages or the message
+// signature.  This is the hash that is referenced by peers' key exchange
+// messages.
+func (msg *MsgMixSecrets) Commitment(h hash.Hash) chainhash.Hash {
+	msgCopy := *msg
+	msgCopy.SeenSecrets = nil
+	msgCopy.WriteHash(h)
+	return msgCopy.hash
+}
+
 // WriteHash serializes the message to a hasher and records the sum in the
 // message's Hash field.
 //
@@ -196,6 +230,14 @@ func (msg *MsgMixSecrets) WriteHash(h hash.Hash) {
 //
 // This method never errors for invalid message construction.
 func (msg *MsgMixSecrets) writeMessageNoSignature(op string, w io.Writer, pver uint32) error {
+	// Limit to max previous messages hashes.
+	count := len(msg.SeenSecrets)
+	if count > MaxMixPeers {
+		msg := fmt.Sprintf("too many previous referenced messages [count %v, max %v]",
+			count, MaxMixPeers)
+		return messageError(op, ErrTooManyPrevMixMsgs, msg)
+	}
+
 	err := writeElements(w, &msg.Identity, &msg.SessionID, msg.Run, &msg.Seed)
 	if err != nil {
 		return err
@@ -217,6 +259,17 @@ func (msg *MsgMixSecrets) writeMessageNoSignature(op string, w io.Writer, pver u
 		return err
 	}
 
+	err = WriteVarInt(w, pver, uint64(count))
+	if err != nil {
+		return err
+	}
+	for i := range msg.SeenSecrets {
+		err := writeElement(w, &msg.SeenSecrets[i])
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -242,7 +295,7 @@ func (msg *MsgMixSecrets) MaxPayloadLength(pver uint32) uint32 {
 	}
 
 	// See tests for this calculation
-	return 54444
+	return 70831
 }
 
 // Pub returns the message sender's public key identity.
@@ -255,13 +308,14 @@ func (msg *MsgMixSecrets) Sig() []byte {
 	return msg.Signature[:]
 }
 
-// PrevMsgs returns nil.  Previous messages are not needed to perform blame
-// assignment, because of the assumption that all previous messages must have
-// been received for a blame stage to be necessary.  Additionally, a
-// commitment to the secrets message is included in the key exchange, and
-// future message hashes are not available at that time.
+// PrevMsgs returns previously revealed secrets messages by other peers.  An
+// honest peer who needs to report blame assignment does not need to reference
+// any previous secrets messages, and a secrets message with other referenced
+// secrets is necessary to begin blame assignment.  Dishonest peers who
+// initially reveal their secrets without blame assignment being necessary are
+// themselves removed in future runs.
 func (msg *MsgMixSecrets) PrevMsgs() []chainhash.Hash {
-	return nil
+	return msg.SeenSecrets
 }
 
 // Sid returns the session ID.
@@ -287,5 +341,6 @@ func NewMsgMixSecrets(identity [33]byte, sid [32]byte, run uint32,
 		Seed:            seed,
 		SlotReserveMsgs: slotReserveMsgs,
 		DCNetMsgs:       dcNetMsgs,
+		SeenSecrets:     make([]chainhash.Hash, 0),
 	}
 }


Test patch:
diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go
index 4d65f3e4..11f61833 100644
--- a/bech32/bech32_test.go
+++ b/bech32/bech32_test.go
@@ -546,7 +546,7 @@ func BenchmarkConvertBitsDown(b *testing.B) {
 	}
 }
 
-// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior
+// BenchmarkConvertBitsUp benchmarks the speed and memory allocation behavior
 // of ConvertBits when converting from a lower base into a higher base (e.g. 5
 // => 8).
 //
diff --git a/wire/message_test.go b/wire/message_test.go
index fee36296..bcf43112 100644
--- a/wire/message_test.go
+++ b/wire/message_test.go
@@ -84,7 +84,7 @@ func TestMessage(t *testing.T) {
 	if err != nil {
 		t.Errorf("NewMsgMixPairReq: %v", err)
 	}
-	msgMixKE := NewMsgMixKeyExchange([33]byte{}, [32]byte{}, 1, [33]byte{}, [1218]byte{}, [32]byte{}, []chainhash.Hash{})
+	msgMixKE := NewMsgMixKeyExchange([33]byte{}, [32]byte{}, 1, 1, [33]byte{}, [1218]byte{}, [32]byte{}, []chainhash.Hash{})
 	msgMixCT := NewMsgMixCiphertexts([33]byte{}, [32]byte{}, 1, [][1047]byte{}, []chainhash.Hash{})
 	msgMixSR := NewMsgMixSlotReserve([33]byte{}, [32]byte{}, 1, [][][]byte{{{}}}, []chainhash.Hash{})
 	msgMixDC := NewMsgMixDCNet([33]byte{}, [32]byte{}, 1, []MixVect{make(MixVect, 1)}, []chainhash.Hash{})
@@ -121,9 +121,9 @@ func TestMessage(t *testing.T) {
 		{msgCFHeaders, msgCFHeaders, pver, MainNet, 58},
 		{msgCFTypes, msgCFTypes, pver, MainNet, 26},
 		{msgGetInitState, msgGetInitState, pver, MainNet, 25},
-		{msgInitState, msgInitState, pver, MainNet, 28},
+		{msgInitState, msgInitState, pver, MainNet, 27},
 		{msgMixPR, msgMixPR, pver, MainNet, 165},
-		{msgMixKE, msgMixKE, pver, MainNet, 1441},
+		{msgMixKE, msgMixKE, pver, MainNet, 1449},
 		{msgMixCT, msgMixCT, pver, MainNet, 158},
 		{msgMixSR, msgMixSR, pver, MainNet, 161},
 		{msgMixDC, msgMixDC, pver, MainNet, 181},
diff --git a/wire/msginitstate_test.go b/wire/msginitstate_test.go
index 73814cd3..520e7bd5 100644
--- a/wire/msginitstate_test.go
+++ b/wire/msginitstate_test.go
@@ -30,7 +30,7 @@ func TestInitState(t *testing.T) {
 	// Ensure max payload returns the expected value for latest protocol
 	// version. a var int and n * hashes for each of block, vote and tspend
 	// hashes.
-	wantPayload := uint32((1 + 32*8) + (1 + 32*40) + (1 + 32*7) + (1 + 32*100))
+	wantPayload := uint32((1 + 32*8) + (1 + 32*40) + (1 + 32*7))
 	maxPayload := msg.MaxPayloadLength(pver)
 	if maxPayload != wantPayload {
 		t.Errorf("MaxPayloadLength: wrong max payload length for "+
@@ -119,7 +119,6 @@ func TestInitStateWire(t *testing.T) {
 		0x00, // Varint for number of blocks
 		0x00, // Varint for number of votes
 		0x00, // Varint for number of tspends
-		0x00, // Varint for number of mixpairreqs
 	}
 
 	fakeBlock1, _ := chainhash.NewHashFromStr("4433221144332211443322114" +
@@ -138,12 +137,6 @@ func TestInitStateWire(t *testing.T) {
 		"999999999999999999991199999999999999919")
 	fakeTSpend3, _ := chainhash.NewHashFromStr("aaaaaaaaaaaa9200aaaaaa" +
 		"aaaaaaaaaaaaaaaaaaa9a9a9aaaaaaaaaaaaaaa")
-	fakeMixPairReq1, _ := chainhash.NewHashFromStr("bbbbbbbbbbbb9200bbbbbb" +
-		"bbbbbbbbbbbbbbbbbbb9b9b9bbbbbbbbbbbbbbb")
-	fakeMixPairReq2, _ := chainhash.NewHashFromStr("cccccccccccc9200cccccc" +
-		"ccccccccccccccccccc9c9c9ccccccccccccccc")
-	fakeMixPairReq3, _ := chainhash.NewHashFromStr("dddddddddddd9200dddddd" +
-		"ddddddddddddddddddd9d9d9ddddddddddddddd")
 
 	// MsgInitState message with multiple values for each hash.
 	multiData := NewMsgInitState()
@@ -155,9 +148,6 @@ func TestInitStateWire(t *testing.T) {
 	multiData.AddTSpendHash(fakeTSpend1)
 	multiData.AddTSpendHash(fakeTSpend2)
 	multiData.AddTSpendHash(fakeTSpend3)
-	multiData.AddMixPairReqHash(fakeMixPairReq1)
-	multiData.AddMixPairReqHash(fakeMixPairReq2)
-	multiData.AddMixPairReqHash(fakeMixPairReq3)
 
 	multiDataEncoded := []byte{
 		0x02,                                           // Varint for number of block hashes
@@ -195,19 +185,6 @@ func TestInitStateWire(t *testing.T) {
 		0x9a, 0x9a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
 		0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x20,
 		0xa9, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00,
-		0x03,                                           // Varint for number of mixpr hashes
-		0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x9b, // Fake mixpairreq 1
-		0x9b, 0x9b, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
-		0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x0b, 0x20,
-		0xb9, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x0b, 0x00,
-		0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x9c, // Fake mixpairreq 2
-		0x9c, 0x9c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
-		0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x0c, 0x20,
-		0xc9, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x0c, 0x00,
-		0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x9d, // Fake mixpairreq 3
-		0x9d, 0x9d, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
-		0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x0d, 0x20,
-		0xd9, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x0d, 0x00,
 	}
 
 	tests := []struct {
@@ -278,12 +255,6 @@ func TestInitStateWireErrors(t *testing.T) {
 		"999999999999999999991199999999999999919")
 	fakeTSpend3, _ := chainhash.NewHashFromStr("aaaaaaaaaaaa9200aaaaaa" +
 		"aaaaaaaaaaaaaaaaaaa9a9a9aaaaaaaaaaaaaaa")
-	fakeMixPairReq1, _ := chainhash.NewHashFromStr("bbbbbbbbbbbb9200bbbbbb" +
-		"bbbbbbbbbbbbbbbbbbb9b9b9bbbbbbbbbbbbbbb")
-	fakeMixPairReq2, _ := chainhash.NewHashFromStr("cccccccccccc9200cccccc" +
-		"ccccccccccccccccccc9c9c9ccccccccccccccc")
-	fakeMixPairReq3, _ := chainhash.NewHashFromStr("dddddddddddd9200dddddd" +
-		"ddddddddddddddddddd9d9d9ddddddddddddddd")
 
 	// MsgInitState message with multiple values for each hash.
 	baseMsg := NewMsgInitState()
@@ -295,9 +266,6 @@ func TestInitStateWireErrors(t *testing.T) {
 	baseMsg.AddTSpendHash(fakeTSpend1)
 	baseMsg.AddTSpendHash(fakeTSpend2)
 	baseMsg.AddTSpendHash(fakeTSpend3)
-	baseMsg.AddMixPairReqHash(fakeMixPairReq1)
-	baseMsg.AddMixPairReqHash(fakeMixPairReq2)
-	baseMsg.AddMixPairReqHash(fakeMixPairReq3)
 
 	baseMsgEncoded := []byte{
 		0x02,                                           // Varint for number of block hashes
@@ -335,19 +303,6 @@ func TestInitStateWireErrors(t *testing.T) {
 		0x9a, 0x9a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
 		0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x20,
 		0xa9, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00,
-		0x03,                                           // Varint for number of mixpairreq hashes
-		0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x9b, // Fake mixpairreq 1
-		0x9b, 0x9b, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
-		0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x0b, 0x20,
-		0xb9, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x0b, 0x00,
-		0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x9c, // Fake mixpairreq 2
-		0x9c, 0x9c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
-		0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x0c, 0x20,
-		0xc9, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x0c, 0x00,
-		0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x9d, // Fake mixpairreq 3
-		0x9d, 0x9d, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
-		0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x0d, 0x20,
-		0xd9, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x0d, 0x00,
 	}
 
 	// Message that forces an error by having more than the max allowed
diff --git a/wire/msgmixkeyexchange_test.go b/wire/msgmixkeyexchange_test.go
index e5f83a32..94118c97 100644
--- a/wire/msgmixkeyexchange_test.go
+++ b/wire/msgmixkeyexchange_test.go
@@ -21,18 +21,19 @@ func newTestMixKeyExchange() *MsgMixKeyExchange {
 	id := *(*[33]byte)(repeat(0x81, 33))
 	sid := *(*[32]byte)(repeat(0x82, 32))
 
-	const run = uint32(0x83838383)
+	const epoch = uint64(0x8383838383838383)
+	const run = uint32(0x84848484)
 
-	ecdh := *(*[33]byte)(repeat(0x84, 33))
-	pqpk := *(*[1218]byte)(repeat(0x85, 1218))
-	commitment := *(*[32]byte)(repeat(0x86, 32))
+	ecdh := *(*[33]byte)(repeat(0x85, 33))
+	pqpk := *(*[1218]byte)(repeat(0x86, 1218))
+	commitment := *(*[32]byte)(repeat(0x87, 32))
 
 	seenPRs := make([]chainhash.Hash, 4)
-	for b := byte(0x87); b < 0x8B; b++ {
-		copy(seenPRs[b-0x87][:], repeat(b, 32))
+	for b := byte(0x88); b < 0x8C; b++ {
+		copy(seenPRs[b-0x88][:], repeat(b, 32))
 	}
 
-	ke := NewMsgMixKeyExchange(id, sid, run, ecdh, pqpk, commitment, seenPRs)
+	ke := NewMsgMixKeyExchange(id, sid, epoch, run, ecdh, pqpk, commitment, seenPRs)
 	ke.Signature = sig
 
 	return ke
@@ -53,16 +54,17 @@ func TestMsgMixKeyExchangeWire(t *testing.T) {
 	expected = append(expected, repeat(0x80, 64)...)   // Signature
 	expected = append(expected, repeat(0x81, 33)...)   // Identity
 	expected = append(expected, repeat(0x82, 32)...)   // Session ID
-	expected = append(expected, repeat(0x83, 4)...)    // Run
-	expected = append(expected, repeat(0x84, 33)...)   // ECDH public key
-	expected = append(expected, repeat(0x85, 1218)...) // PQ public key
-	expected = append(expected, repeat(0x86, 32)...)   // Secrets commitment
-	// Four seen PRs (repeating 32 bytes of 0x87, 0x88, 0x89, 0x8a)
+	expected = append(expected, repeat(0x83, 8)...)    // Epoch
+	expected = append(expected, repeat(0x84, 4)...)    // Run
+	expected = append(expected, repeat(0x85, 33)...)   // ECDH public key
+	expected = append(expected, repeat(0x86, 1218)...) // PQ public key
+	expected = append(expected, repeat(0x87, 32)...)   // Secrets commitment
+	// Four seen PRs (repeating 32 bytes of 0x88, 0x89, 0x8a, 0x8b)
 	expected = append(expected, 0x04)
-	expected = append(expected, repeat(0x87, 32)...)
 	expected = append(expected, repeat(0x88, 32)...)
 	expected = append(expected, repeat(0x89, 32)...)
 	expected = append(expected, repeat(0x8a, 32)...)
+	expected = append(expected, repeat(0x8b, 32)...)
 
 	expectedSerializationEqual(t, buf.Bytes(), expected)
 
@@ -155,6 +157,7 @@ func TestMsgMixKeyExchangeMaxPayloadLength(t *testing.T) {
 	var expectedLen uint32 = 64 + // Signature
 		33 + // Identity
 		32 + // Session ID
+		8 + // Epoch
 		4 + // Run
 		33 + // ECDH public key
 		1218 + // sntrup4591761 public key
diff --git a/wire/msgmixpairreq_test.go b/wire/msgmixpairreq_test.go
index 36ac28f7..3396ae4b 100644
--- a/wire/msgmixpairreq_test.go
+++ b/wire/msgmixpairreq_test.go
@@ -56,6 +56,7 @@ func newMixPairReqArgs() *mixPairReqArgs {
 			Script:    []byte{},
 			PubKey:    repeat(0x8B, 33),
 			Signature: repeat(0x8C, 64),
+			Opcode:    0xBB, // OP_SSGEN
 		},
 		{
 			OutPoint: OutPoint{
@@ -66,6 +67,7 @@ func newMixPairReqArgs() *mixPairReqArgs {
 			Script:    repeat(0x90, 25),
 			PubKey:    repeat(0x91, 33),
 			Signature: repeat(0x92, 64),
+			Opcode:    0xBC, // OP_SSRTX
 		},
 	}
 
@@ -129,6 +131,7 @@ func TestMsgMixPairReqWire(t *testing.T) {
 	expected = append(expected, repeat(0x8b, 33)...)
 	expected = append(expected, 0x40) // 64-byte signature
 	expected = append(expected, repeat(0x8c, 64)...)
+	expected = append(expected, 0xBB) // Opcode
 	// Second UTXO 8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d:0x8e8e8e8e
 	expected = append(expected, repeat(0x8d, 32)...) // Hash
 	expected = append(expected, repeat(0x8e, 4)...)  // Index
@@ -139,6 +142,7 @@ func TestMsgMixPairReqWire(t *testing.T) {
 	expected = append(expected, repeat(0x91, 33)...)
 	expected = append(expected, 0x40) // 64-byte signature
 	expected = append(expected, repeat(0x92, 64)...)
+	expected = append(expected, 0xBC) // Opcode
 	// Change output
 	expected = append(expected, 0x01) // Has change = true
 	expected = append(expected, []byte{
@@ -285,7 +289,8 @@ func TestMsgMixPairReqMaxPayloadLength(t *testing.T) {
 		1 + // Tree
 		varBytesLen(MaxMixPairReqUTXOScriptLen) + // P2SH redeem script
 		varBytesLen(33) + // Pubkey
-		varBytesLen(64) // Signature
+		varBytesLen(64) + // Signature
+		1 // Opcode
 	var maxTxOutLen uint32 = 8 + // Value
 		2 + // Version
 		varBytesLen(16384) // PkScript (txscript.MaxScriptLen)
diff --git a/wire/msgmixsecrets_test.go b/wire/msgmixsecrets_test.go
index 2905d94e..e0fc7935 100644
--- a/wire/msgmixsecrets_test.go
+++ b/wire/msgmixsecrets_test.go
@@ -12,6 +12,7 @@ import (
 	"testing"
 
 	"github.com/davecgh/go-spew/spew"
+	"github.com/decred/dcrd/chaincfg/chainhash"
 )
 
 func newTestMixSecrets() *MsgMixSecrets {
@@ -34,7 +35,13 @@ func newTestMixSecrets() *MsgMixSecrets {
 		copy(m[b-0x89][:], repeat(b, 20))
 	}
 
+	seenRSs := make([]chainhash.Hash, 4)
+	for b := byte(0x8D); b < 0x91; b++ {
+		copy(seenRSs[b-0x8D][:], repeat(b, 32))
+	}
+
 	rs := NewMsgMixSecrets(id, sid, run, seed, sr, m)
+	rs.SeenSecrets = seenRSs
 	rs.Signature = sig
 
 	return rs
@@ -67,12 +74,18 @@ func TestMsgMixSecretsWire(t *testing.T) {
 	expected = append(expected, repeat(0x87, 32)...)
 	expected = append(expected, 0x20)
 	expected = append(expected, repeat(0x88, 32)...)
-	// Four slot reservation mixed messages (repeating 20 bytes of 0x89, 0x8a, 0x8b, 0x8c)
+	// Four xor dc-net mixed messages (repeating 20 bytes of 0x89, 0x8a, 0x8b, 0x8c)
 	expected = append(expected, 0x04, 0x14)
 	expected = append(expected, repeat(0x89, 20)...)
 	expected = append(expected, repeat(0x8a, 20)...)
 	expected = append(expected, repeat(0x8b, 20)...)
 	expected = append(expected, repeat(0x8c, 20)...)
+	// Four seen RSs (repeating 32 bytes of 0x8d, 0x8e, 0x8f, 0x90)
+	expected = append(expected, 0x04)
+	expected = append(expected, repeat(0x8d, 32)...)
+	expected = append(expected, repeat(0x8e, 32)...)
+	expected = append(expected, repeat(0x8f, 32)...)
+	expected = append(expected, repeat(0x90, 32)...)
 
 	expectedSerializationEqual(t, buf.Bytes(), expected)
 
@@ -168,7 +181,9 @@ func TestMsgMixSecretsMaxPayloadLength(t *testing.T) {
 		MaxMixMcount*varBytesLen(MaxMixFieldValLen) + // Unpadded SR values
 		uint32(VarIntSerializeSize(MaxMixMcount)) + // DC-net message count
 		uint32(VarIntSerializeSize(MixMsgSize)) + // DC-net message size
-		MaxMixMcount*MixMsgSize // DC-net messages
+		MaxMixMcount*MixMsgSize + // DC-net messages
+		uint32(VarIntSerializeSize(MaxMixPeers)) + // RS count
+		32*MaxMixPeers // RS hashes
 
 	tests := []struct {
 		name string