Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e525b10
[release-v2.1] mixing: Remove reference to x25519
davecgh Apr 6, 2026
389d538
[release-v2.1] mixing/utxoproof: Add benchmarks.
davecgh Apr 6, 2026
be2c4ea
[release-v2.1] mixing: Avoid hash.Hash and Sum(nil) calls
davecgh Apr 6, 2026
6f4cecb
[release-v2.1] mixing: Add signature tests and benchmarks
davecgh Apr 6, 2026
876ffa6
[release-v2.1] mixing: Optimize message signing and sig verification
davecgh Apr 6, 2026
f1d9304
[release-v2.1] server: Update mempool orphan eviction debug log.
davecgh Apr 6, 2026
01d5995
[release-v2.1] rpcserver: Rename mix msg acceptance iface method.
davecgh Apr 6, 2026
3b313f9
[release-v2.1] mixpool: Use slices for containment check.
davecgh Apr 6, 2026
d8c9406
[release-v2.1] mixpool: Rename orphan type to orphanMsg.
davecgh Apr 6, 2026
1841127
[release-v2.1] mixpool: Consolidate orphan addition.
davecgh Apr 6, 2026
7a85980
[release-v2.1] mixpool: Consolidate orphan removal.
davecgh Apr 6, 2026
a8fb8cf
[release-v2.1] mixpool: Store orphan struct vs msg in id map.
davecgh Apr 6, 2026
e0f1b32
[release-v2.1] mixpool: Implement message source tracking.
davecgh Apr 6, 2026
bffc083
[release-v2.1] mixpool: Proactive orphan limit eviction.
davecgh Apr 6, 2026
9a2e90a
[release-v2.1] mixing: Increment PRNG seed nonce for each test
davecgh Apr 6, 2026
32d57af
[release-v2.1] mixclient: Disable logs to backend after test finish
davecgh Apr 6, 2026
4d60437
[release-v2.1] mixclient: Avoid test hangs caused by epoch ticker
davecgh Apr 6, 2026
d35d9c5
[release-v2.1] mixing: Impose stricter message limits
davecgh Apr 6, 2026
0d19a2e
[release-v2.1] mixpool: Reject PRs duplicating inputs
davecgh Apr 6, 2026
f27aa60
[release-v2.1] main: Use backported mixing updates.
davecgh Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require (
github.com/decred/dcrd/dcrutil/v4 v4.0.3
github.com/decred/dcrd/gcs/v4 v4.1.1
github.com/decred/dcrd/math/uint256 v1.0.2
github.com/decred/dcrd/mixing v0.6.1
github.com/decred/dcrd/mixing v0.7.0
github.com/decred/dcrd/peer/v3 v3.2.0
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.4.0
github.com/decred/dcrd/rpcclient/v8 v8.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ github.com/decred/dcrd/hdkeychain/v3 v3.1.3 h1:Kn2wfj5cOR6pQO/WrYOMT1KK12IgWFEeQ
github.com/decred/dcrd/hdkeychain/v3 v3.1.3/go.mod h1:mDAuGaH6InRD+hKVeVJsjLD/ih1mD3aCKURNHS8Tq2s=
github.com/decred/dcrd/math/uint256 v1.0.2 h1:o8peafL5QmuXGTergI3YDpDU0eq5Z0pQi88B8ym4PRA=
github.com/decred/dcrd/math/uint256 v1.0.2/go.mod h1:7M/y9wJJvlyNG/f/X6mxxhxo9dgloZHFiOfbiscl75A=
github.com/decred/dcrd/mixing v0.6.1 h1:nysHraShVbgKIrqpa04364YTchmVtqgaphTNWqs7nHc=
github.com/decred/dcrd/mixing v0.6.1/go.mod h1:M+Ao9h49usdSEsC0N6kMafijwgYh7E57RTKh8cCw02o=
github.com/decred/dcrd/mixing v0.7.0 h1:rKs5/6E8szo561kKp54iXdcC7GlQBIE9IP3utgKxjAM=
github.com/decred/dcrd/mixing v0.7.0/go.mod h1:UE7+nPvXA6C7GdIFXJmz0ksmxykwn88zzbQFwAopahE=
github.com/decred/dcrd/peer/v3 v3.2.0 h1:6PxoGcsyjV3me/WLjIZdmWs4Pma5jYGi40CmzNCzeNY=
github.com/decred/dcrd/peer/v3 v3.2.0/go.mod h1:OUFvMboEQvnq4V8CRw2RUn/fFls0rpVuDN0QT0zq8RA=
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.4.0 h1:BBVaYemabsFsaqNVlCmacoZlSsLDqwHYIs8ty6fg59Q=
Expand Down
5 changes: 3 additions & 2 deletions internal/netsync/manager.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2025 The Decred developers
// Copyright (c) 2015-2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -1056,7 +1056,8 @@ func (m *SyncManager) OnMixMsg(peer *Peer, msg mixing.Message) ([]mixing.Message
return nil, nil
}

accepted, err := m.cfg.MixPool.AcceptMessage(msg)
source := mixpool.Uint64Source(peer.ID())
accepted, err := m.cfg.MixPool.AcceptMessage(msg, source)

// Remove message from request maps. Either the mixpool already knows
// about it and as such we shouldn't have any more instances of trying
Expand Down
9 changes: 5 additions & 4 deletions internal/rpcserver/interface.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019-2025 The Decred developers
// Copyright (c) 2019-2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -20,6 +20,7 @@ import (
"github.com/decred/dcrd/internal/mining"
"github.com/decred/dcrd/math/uint256"
"github.com/decred/dcrd/mixing"
"github.com/decred/dcrd/mixing/mixpool"
"github.com/decred/dcrd/peer/v3"
"github.com/decred/dcrd/rpc/jsonrpc/types/v4"
"github.com/decred/dcrd/txscript/v4/stdaddr"
Expand Down Expand Up @@ -200,9 +201,9 @@ type SyncManager interface {
// This method may report a false positive, but never a false negative.
RecentlyConfirmedTxn(hash *chainhash.Hash) bool

// SubmitMixMessage submits the mixing message to the network after
// processing it locally.
SubmitMixMessage(msg mixing.Message) error
// AcceptMixMessage attempts to accept a mixing message to the local mixing
// pool.
AcceptMixMessage(msg mixing.Message, src mixpool.Source) error
}

// UtxoEntry represents a utxo entry for use with the RPC server.
Expand Down
6 changes: 4 additions & 2 deletions internal/rpcserver/rpcserver.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2025 The Decred developers
// Copyright (c) 2015-2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -50,6 +50,7 @@ import (
"github.com/decred/dcrd/internal/mining/cpuminer"
"github.com/decred/dcrd/internal/version"
"github.com/decred/dcrd/mixing"
"github.com/decred/dcrd/mixing/mixpool"
"github.com/decred/dcrd/rpc/jsonrpc/types/v4"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/txscript/v4/stdaddr"
Expand Down Expand Up @@ -4348,7 +4349,8 @@ func handleSendRawMixMessage(_ context.Context, s *Server, cmd interface{}) (int
msg.WriteHash(s.blake256Hasher)
s.blake256HaserMu.Unlock()

err = s.cfg.SyncMgr.SubmitMixMessage(msg)
// Use 0 for the source to represent the local node.
err = s.cfg.SyncMgr.AcceptMixMessage(msg, mixpool.ZeroSource)
if err != nil {
// XXX: consider a better error code/function
str := fmt.Sprintf("Rejected mix message: %s", err)
Expand Down
15 changes: 9 additions & 6 deletions internal/rpcserver/rpcserverhandlers_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2025 The Decred developers
// Copyright (c) 2020-2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -44,6 +44,7 @@ import (
"github.com/decred/dcrd/internal/version"
"github.com/decred/dcrd/math/uint256"
"github.com/decred/dcrd/mixing"
"github.com/decred/dcrd/mixing/mixpool"
"github.com/decred/dcrd/peer/v3"
"github.com/decred/dcrd/rpc/jsonrpc/types/v4"
"github.com/decred/dcrd/txscript/v4"
Expand Down Expand Up @@ -572,7 +573,7 @@ func (c *testAddrManager) LocalAddresses() []addrmgr.LocalAddr {
type testSyncManager struct {
isCurrent bool
submitBlockErr error
submitMixErr error
acceptMixErr error
syncPeerID int32
syncHeight int64
processTransaction []*dcrutil.Tx
Expand All @@ -592,10 +593,6 @@ func (s *testSyncManager) SubmitBlock(block *dcrutil.Block) error {
return s.submitBlockErr
}

func (s *testSyncManager) SubmitMixMessage(msg mixing.Message) error {
return s.submitMixErr
}

// SyncPeer returns a mocked id of the current peer being synced with.
func (s *testSyncManager) SyncPeerID() int32 {
return s.syncPeerID
Expand All @@ -619,6 +616,12 @@ func (s *testSyncManager) RecentlyConfirmedTxn(hash *chainhash.Hash) bool {
return s.recentlyConfirmedTxn
}

// AcceptMixMessage provides a mock implementation for attempting to accept a
// mixing message to the local mixing pool.
func (s *testSyncManager) AcceptMixMessage(msg mixing.Message, src mixpool.Source) error {
return s.acceptMixErr
}

// testExistsAddresser provides a mock exists addresser by implementing the
// ExistsAddresser interface.
type testExistsAddresser struct {
Expand Down
7 changes: 3 additions & 4 deletions mixing/dcnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ import (
// SRMixPads creates a vector of exponential DC-net pads from a vector of
// shared secrets with each participating peer in the DC-net.
func SRMixPads(kp [][]byte, my uint32) []*big.Int {
h := blake256.New()
h := blake256.NewHasher256()
scratch := make([]byte, 8)
digest := make([]byte, blake256.Size)
pads := make([]*big.Int, len(kp))
partialPad := new(big.Int)
for j := uint32(0); j < uint32(len(kp)); j++ {
Expand All @@ -30,8 +29,8 @@ func SRMixPads(kp [][]byte, my uint32) []*big.Int {
h.Reset()
h.Write(kp[i])
h.Write(scratch)
digest = h.Sum(digest[:0])
partialPad.SetBytes(digest)
digest := h.Sum256()
partialPad.SetBytes(digest[:])
if my > i {
pads[j].Add(pads[j], partialPad)
} else {
Expand Down
2 changes: 1 addition & 1 deletion mixing/keyagreement.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (kx *KX) SharedSecrets(k *RevealedKeys, sid []byte, run uint32, mcounts []u
// XOR ECDH and both sntrup4591761 keys into a single
// shared key. If sntrup4591761 is discovered to be
// broken in the future, the security only reduces to
// that of x25519.
// that of ECDH.
// If the message belongs to our own peer, only XOR
// the sntrup4591761 key once. The decapsulated and
// cleartext keys are equal in this case, and would
Expand Down
42 changes: 42 additions & 0 deletions mixing/limits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package mixing

// The wire package's limits on mixing messages are excessively large. In
// practice, much lower limits are defined below and used by mixpool and
// mixclient. These limits are enforced by the mixing module to avoid a
// wire protocol bump.
//
// These limits are a tradeoff between mix quality, theoretical maximum
// messages sizes, and the size of the resulting mix transaction. In the
// largest mix defined by these limits, 64 total peers may each spend five
// P2PKH outputs each while contributing 17 P2PKH outputs (16 mixed outputs
// plus one change output) with the estimated size of the resulting transaction
// equaling 92309 bytes, just under the 100k standard threshold.
//
// These constants may change at any point without a major API bump to the
// mixing module.
const (
// MaxPeers is the maximum number of peers allowed together in a
// single mix session. This restricts the maximum dimensions of the
// slot reservation and XOR DC-net matrices and the maximum number of
// previous messages that may be referenced by mix messages.
MaxPeers = 64

// MaxMcount is the maximum number of mixed messages that any single
// peer can contribute to the mix.
MaxMcount = 16

// MaxMtot is the maximum number of mixed messages in total that can
// be created during a session (the sum of all mcounts).
MaxMtot = 1024

// MaxMixTxSerializeSize is the maximum size of a mix transaction.
// This value should not exceed mempool standardness limits.
MaxMixTxSerializeSize = 100000

// MaxMixAmount is the maximum value of a mixed output.
MaxMixAmount = 21000000_00000000 / MinPeers
)
82 changes: 48 additions & 34 deletions mixing/mixclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,9 @@ type Client struct {

logger slog.Logger

testTickC chan struct{}
testHooks map[hook]hookFunc
testWaiting chan struct{}
testTickC chan time.Time
testHooks map[hook]hookFunc
}

// NewClient creates a wallet's mixing client manager.
Expand All @@ -417,7 +418,7 @@ func NewClient(w Wallet) *Client {
height: height,
warming: make(chan struct{}),
workQueue: make(chan *queueWork, runtime.NumCPU()),
blake256Hasher: blake256.New(),
blake256Hasher: blake256.NewHasher256(),
epoch: w.Mixpool().Epoch(),
stopping: make(chan struct{}),
}
Expand Down Expand Up @@ -668,26 +669,28 @@ func (c *Client) waitForEpoch(ctx context.Context) (time.Time, error) {
now := time.Now().UTC()
epoch := now.Truncate(c.epoch).Add(c.epoch)
duration := epoch.Sub(now)
timer := time.NewTimer(duration)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return epoch, ctx.Err()
case <-c.stopping:
if !timer.Stop() {
<-timer.C
}
c.runWG.Wait()
return epoch, ErrStopping
case <-c.testTickC:
if !timer.Stop() {
<-timer.C

testWaiting := c.testWaiting
var timerC, testTickC <-chan time.Time
if testWaiting == nil {
timerC = time.After(duration)
}

for {
select {
case <-ctx.Done():
return epoch, ctx.Err()
case <-c.stopping:
c.runWG.Wait()
return epoch, ErrStopping
case <-timerC:
return epoch, nil
case testWaiting <- struct{}{}:
testWaiting = nil
testTickC = c.testTickC
case testEpoch := <-testTickC:
return testEpoch, nil
}
return time.Now(), nil
case <-timer.C:
return epoch, nil
}
}

Expand All @@ -700,6 +703,11 @@ func (p *peer) msgJitter() time.Duration {
// small amount of jitter is added to help avoid timing deanonymization
// attacks.
func (c *Client) prDelay(ctx context.Context, p *peer) error {
// No delay in tests.
if c.testTickC != nil {
return nil
}

now := time.Now().UTC()
epoch := now.Truncate(c.epoch).Add(c.epoch)
sendBefore := epoch.Add(-timeoutDuration - maxJitter)
Expand All @@ -717,18 +725,13 @@ func (c *Client) prDelay(ctx context.Context, p *peer) error {
<-timer.C
}
return ctx.Err()
case <-c.testTickC:
if !timer.Stop() {
<-timer.C
}
return nil
case <-timer.C:
return nil
}
}

func (c *Client) testTick() {
c.testTickC <- struct{}{}
func (c *Client) testTick(t time.Time) {
c.testTickC <- t
}

func (c *Client) testHook(stage hook, ps *pairedSessions, s *sessionRun, p *peer) {
Expand Down Expand Up @@ -858,12 +861,23 @@ func (c *Client) epochTicker(ctx context.Context) error {
if err != nil {
return err
}
timerC := time.After(timeoutDuration + 2*time.Second)
testWaiting := c.testWaiting
var testTickC <-chan time.Time
for {
select {
case <-ctx.Done():
return err

select {
case <-time.After(timeoutDuration + 2*time.Second):
case <-c.testTickC:
case <-ctx.Done():
return err
case <-timerC:

case testWaiting <- struct{}{}:
testWaiting = nil
testTickC = c.testTickC
continue
case <-testTickC:
}
break
}
c.mu.Lock()
c.removeUnresponsiveDuringEpoch(prevPRs, uint64(firstEpoch.Unix()))
Expand Down
20 changes: 13 additions & 7 deletions mixing/mixclient/client_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2024 The Decred developers
// Copyright (c) 2024-2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -45,7 +45,8 @@ const (

func newTestClient(w *testWallet, logger slog.Logger) *Client {
c := NewClient(w)
c.testTickC = make(chan struct{})
c.testWaiting = make(chan struct{})
c.testTickC = make(chan time.Time)
c.SetLogger(logger)
return c
}
Expand Down Expand Up @@ -114,7 +115,7 @@ func (w *testWallet) SignInput(tx *wire.MsgTx, index int, prevScript []byte) err
}

func (w *testWallet) SubmitMixMessage(ctx context.Context, msg mixing.Message) error {
_, err := w.mixpool.AcceptMessage(msg)
_, err := w.mixpool.AcceptMessage(msg, mixpool.ZeroSource)
return err
}

Expand Down Expand Up @@ -223,7 +224,8 @@ func TestHonest(t *testing.T) {
<-doneRun
}()

c.testTick()
<-c.testWaiting
c.testTick(time.Now().Truncate(time.Second))

var g errgroup.Group
for i := range peers {
Expand All @@ -235,7 +237,8 @@ func TestHonest(t *testing.T) {

go func() {
for {
c.testTick()
<-c.testWaiting
c.testTick(time.Now().Truncate(time.Second))
select {
case <-ctx.Done():
return
Expand Down Expand Up @@ -329,8 +332,11 @@ func testDisruption(t *testing.T, misbehavingID *identity, h hook, f hookFunc) {
}()

testTick := func() {
c.testTick()
c2.testTick()
<-c.testWaiting
<-c2.testWaiting
t := time.Now().Truncate(time.Second)
c.testTick(t)
c2.testTick(t)
}
testTick()

Expand Down
Loading
Loading