Repository: decred/dcrd
Pull request #3239 (2024-04-30 18:52:10)

wire: add previous revealed secrets hashes to RS message
For proper blaming behavior, peers who publish their secrets are blamed and removed from the following run if all secrets were revealed, but no other misbehavior was detected.

In order to correctly pin the blame on the peers who disrupted the mix by initially revealing their secrets messages, this blaming will only be triggered when a reveal secrets message is received that does not reference any other received secrets messages.

Because the secrets message hash now commits to these previous hashes by its signature, an additional method is added to return the commitment hash (to be published and verified in key exchange messages) that does not hash the previous messages (as they do not exist at the time of creating the key exchange).

While here, an issue was discovered and corrected in the serialization of the MixVect type.  When the vector has zero length, deserializing would return early after reading the first 0 dimension, but serialization was writing both the count and message size dimensions.  This was corrected by changing the serialization method to return early if the count is zero.


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/connmgr/connmanager.go b/connmgr/connmanager.go
index 1e66da86..6bcc35fd 100644
--- a/connmgr/connmanager.go
+++ b/connmgr/connmanager.go
@@ -379,19 +379,21 @@ out:
 					continue
 				}
 
-				// Otherwise, we will attempt a reconnection if
-				// we do not have enough peers, or if this is a
-				// persistent peer. The connection request is
-				// re added to the pending map, so that
-				// subsequent processing of connections and
-				// failures do not ignore the request.
-				if uint32(len(conns)) < cm.cfg.TargetOutbound ||
-					connReq.Permanent {
-
-					connReq.updateState(ConnPending)
-					log.Debugf("Reconnecting to %v",
-						connReq)
-					pending[msg.id] = connReq
+				// Otherwise, attempt a reconnection when there are not already
+				// enough outbound peers to satisfy the target number of
+				// outbound peers or this is a persistent peer.
+				numConns := uint32(len(conns))
+				if numConns < cm.cfg.TargetOutbound || connReq.Permanent {
+					// The connection request is reused for persistent peers, so
+					// add it back to the pending map in that case so that
+					// subsequent processing of connections and failures do not
+					// ignore the request.
+					if connReq.Permanent {
+						connReq.updateState(ConnPending)
+						log.Debugf("Reconnecting to %v", connReq)
+						pending[msg.id] = connReq
+					}
+
 					cm.handleFailedConn(ctx, connReq)
 				}
 
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 0f4d6c16..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)
@@ -1985,9 +1960,14 @@ func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) {
 		s.connManager.Disconnect(sp.connReq.ID())
 	}
 
-	// Update the address' last seen time if the peer has acknowledged
-	// our version and has sent us its version as well.
-	if sp.VerAckReceived() && sp.VersionKnown() && sp.NA() != nil {
+	// Update the address manager with the last seen time when the peer has
+	// acknowledged our version and has sent us its version as well.  This is
+	// skipped when running on the simulation and regression test networks since
+	// they are only intended to connect to specified peers and actively avoid
+	// advertising and connecting to discovered peers.
+	if !cfg.SimNet && !cfg.RegNet && sp.VerAckReceived() && sp.VersionKnown() &&
+		sp.NA() != nil {
+
 		remoteAddr := wireToAddrmgrNetAddress(sp.NA())
 		err := s.addrManager.Connected(remoteAddr)
 		if err != nil {
@@ -2057,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/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/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