# Message Routing Differences Between Broadcast and Direct Peer-to-Peer in mpcium

> Explore message routing differences in mpcium. Understand how broadcast pub/sub with signatures and direct peer-to-peer encrypted channels work to secure communication.

- Repository: [Fystack Labs/mpcium](https://github.com/fystack/mpcium)
- Tags: deep-dive
- Published: 2026-03-02

---

**mpcium implements two distinct NATS-based communication paths: broadcast routing uses pub/sub with cryptographic signatures on global topics for all participants, while direct peer-to-peer routing uses encrypted point-to-point channels with recipient-specific subjects.**

The mpcium library separates MPC session communication into broadcast and direct messaging layers to optimize for both global coordination and private node-to-node exchanges. Understanding these message routing differences between broadcast and direct peer-to-peer mechanisms is critical for debugging distributed threshold signature operations and ensuring secure message delivery across the network.

## Transport Layer Architecture

### Broadcast Pub/Sub Mechanics

Broadcast communication leverages **NATS publish/subscribe** on a shared **broadcast subject** that every participant in the MPC session subscribes to. This path is selected when `routing.IsBroadcast` evaluates to true and the recipient list is empty, indicating a message intended for all parties. The `PubSub` interface in [`pkg/messaging/pubsub.go`](https://github.com/fystack/mpcium/blob/main/pkg/messaging/pubsub.go) handles these transmissions, publishing signed messages through `pubSub.Publish` without encryption since the content is meant for global visibility.

### Direct Point-to-Point Channels

Direct peer-to-peer messaging utilizes **NATS request/reply** (or plain publish) on **direct subjects** that encode both sender and receiver IDs. When `routing.IsBroadcast` is false or specific recipients are listed in `routing.To`, the session iterates through target party IDs and composes unique topics using `TopicComposer.ComposeDirectTopic(fromID, toID)`. This approach creates isolated communication channels such as `mpc.direct:<fromID>:<toID>:<walletID>`, ensuring messages reach only intended recipients.

## Security Models: Authentication vs Confidentiality

### Broadcast Message Signatures

In the broadcast path, the `TssMessage` is **signed** with the node’s identity key via `identityStore.SignMessage` and transmitted as-is. Receiving nodes validate authenticity through `identityStore.VerifyMessage` in the broadcast handler. No encryption is applied because the message content must remain readable by all session participants, relying solely on **signature verification** to prevent tampering.

### Direct Message Encryption

For direct routing, the `TssMessage` is **encrypted** for the specific target node using `identityStore.EncryptMessage`, and no digital signature is attached. According to the mpcium source code, confidentiality already guarantees authenticity in this model—only the holder of the correct private key can decrypt the payload. This eliminates the overhead of additional signatures while ensuring privacy for sensitive MPC protocol messages exchanged between specific parties.

## Routing Logic and Implementation

### Broadcast Pathway

The `handleTssMessage` function in [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go) (lines 121‑135) processes broadcast sends by attaching a signature and publishing to the global topic:

```go
// Broadcast implementation from session.go:121-135
if routing.IsBroadcast && len(routing.To) == 0 {
    signature, _ := s.identityStore.SignMessage(&tssMsg)
    tssMsg.Signature = signature
    msg, _ := types.MarshalTssMessage(&tssMsg)
    s.pubSub.Publish(s.topicComposer.ComposeBroadcastTopic(), msg)
}

```

### Direct Peer-to-Peer Pathway

The same function handles direct routing (lines 48‑71) by iterating over recipients, encrypting payloads, and dispatching via the `DirectMessaging` interface from [`pkg/messaging/point2point.go`](https://github.com/fystack/mpcium/blob/main/pkg/messaging/point2point.go):

```go
// Direct implementation from session.go:48-71
msg, _ := types.MarshalTssMessage(&tssMsg)
selfID := partyIDToNodeID(s.selfPartyID)
for _, to := range routing.To {
    toNodeID := partyIDToNodeID(to)
    topic := s.topicComposer.ComposeDirectTopic(selfID, toNodeID)
    if selfID == toNodeID {
        s.direct.SendToSelf(topic, msg) // local delivery without encryption
    } else {
        cipher, _ := s.identityStore.EncryptMessage(msg, toNodeID)
        s.direct.SendToOther(topic, cipher) // remote delivery with encryption
    }
}

```

Local self-messages bypass encryption through `SendToSelf`, while remote messages use `SendToOther` with optional retry logic via `SendToOtherWithRetry` for reliability.

## Topic Composition Strategy

### Global Broadcast Subjects

The `TopicComposer.ComposeBroadcastTopic()` method generates a single subject pattern like `mpc.<walletID>.broadcast`, creating a unified namespace where all nodes listen for session-wide coordination messages.

### Directed Subject Naming

For point-to-point communication, `TopicComposer.ComposeDirectTopic(fromID, toID)` constructs targeted subjects following the pattern `mpc.direct:<fromID>:<toID>:<walletID>`. This naming convention enables selective message routing where nodes subscribe only to topics matching their own ID as the recipient, filtering irrelevant traffic at the NATS subscription level.

## Message Reception Flows

### Subscribing to Broadcasts

Each MPC session establishes a **broadcast subscription** once via `subscribeBroadcastAsync` (lines 86‑92 in [`session.go`](https://github.com/fystack/mpcium/blob/main/session.go)). The handler receives raw NATS messages and immediately verifies signatures before forwarding to the TSS state machine:

```go
func (s *session) subscribeBroadcastAsync() {
    go func() {
        topic := s.topicComposer.ComposeBroadcastTopic()
        sub, err := s.pubSub.Subscribe(topic, func(natMsg *nats.Msg) {
            s.receiveBroadcastTssMessage(natMsg.Data) // verifies signature
        })
        s.broadcastSub = sub
    }()
}

```

### Handling Direct Messages

Nodes listen on direct subjects through `subscribeDirectTopicAsync` (lines 64‑70), which registers handlers that invoke `receiveP2PTssMessage`. This function extracts the sender ID from the topic string using `extractSenderIDFromDirectTopic` (lines 85‑90), decrypts payloads using `identityStore.DecryptMessage` (unless the message originated from self), and processes the unmarshaled `TssMessage`:

```go
func (s *session) receiveP2PTssMessage(topic string, cipher []byte) {
    senderID := extractSenderIDFromDirectTopic(topic)
    var plaintext []byte
    if senderID == partyIDToNodeID(s.selfPartyID) {
        plaintext = cipher // self-message, no decryption needed
    } else {
        plaintext, _ = s.identityStore.DecryptMessage(cipher, senderID)
    }
    msg, _ := types.UnmarshalTssMessage(plaintext)
    s.receiveTssMessage(msg)
}

```

## Error Handling and Reliability

Broadcast routing aborts message processing immediately if signature verification fails or unmarshaling errors occur, treating these as fatal protocol violations. Direct routing handles **decryption failures** by producing errors that cause message discard, while transport-level retries are managed through `SendToOtherWithRetry` to handle transient network failures between specific peers.

## Summary

- **Broadcast routing** uses NATS pub/sub on shared topics with **signature-based authentication**, suitable for global coordination where all parties require message visibility.
- **Direct peer-to-peer routing** employs NATS point-to-point channels with **encryption-based confidentiality**, routing messages through recipient-specific subjects constructed via `TopicComposer.ComposeDirectTopic`.
- The routing decision hinges on the `routing.IsBroadcast` flag and the length of `routing.To`, implemented centrally in [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go).
- Security models differ fundamentally: broadcasts rely on digital signatures for integrity, while direct messages rely on encryption for both privacy and authenticity.
- Error handling distinguishes between signature verification failures (broadcast) and decryption failures (direct), with the latter supporting retry mechanisms for enhanced reliability.

## Frequently Asked Questions

### Does mpcium encrypt broadcast messages?

No, broadcast messages are not encrypted. They are signed using the sender's identity key via `identityStore.SignMessage`, but transmitted in plaintext since they must be readable by all session participants. Authentication relies entirely on signature verification through `identityStore.VerifyMessage` upon receipt.

### How does mpcium handle messages sent to the same node?

When the sender and recipient IDs match in direct routing, mpcium invokes `direct.SendToSelf` (as seen in [`session.go`](https://github.com/fystack/mpcium/blob/main/session.go) lines 58‑60). This local delivery path skips encryption and decryption overhead, passing the marshaled message directly to the local handler without traversing the network layer.

### What NATS subject patterns does mpcium use for routing?

Broadcast messages use subjects generated by `TopicComposer.ComposeBroadcastTopic()`, following the pattern `mpc.<walletID>.broadcast`. Direct messages use `TopicComposer.ComposeDirectTopic(fromID, toID)`, creating subjects like `mpc.direct:<fromID>:<toID>:<walletID>`, ensuring unique routing paths between specific node pairs.

### What happens if a direct message fails to decrypt?

Decryption failures in direct messaging return errors that cause the message to be discarded without processing. Unlike broadcast failures—which typically abort the session—direct message errors are isolated to the specific communication channel. The system supports reliability through `SendToOtherWithRetry`, which handles transient failures and network timeouts for peer-to-peer transmissions.