# How MPC Nodes Implement Message Encryption Using Ed25519 and X25519 ECDH in mpcium

> Learn how mpcium MPC nodes use Ed25519 and X25519 for secure message encryption. Discover AES-GCM symmetric keys for robust point-to-point communication.

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

---

**In mpcium, MPC nodes establish per-peer symmetric keys via X25519 Elliptic Curve Diffie-Hellman (ECDH) and encrypt point-to-point TSS messages using AES-GCM, with Ed25519 providing the underlying identity and signature verification infrastructure.**

The fystack/mpcium repository implements a hybrid encryption scheme that secures multi-party computation (MPC) communication. While **Ed25519** handles identity verification and message signing, the actual **message encryption between MPC nodes** relies on X25519 ECDH to generate shared secrets, which are then used as symmetric keys for AES-GCM encryption of direct TSS messages.

## X25519 ECDH Key Exchange and Symmetric Key Derivation

When a node receives an `ECDHMessage` in [`pkg/mpc/key_exchange_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/key_exchange_session.go) (lines 24-27), it first verifies the Ed25519 signature to authenticate the sender. The node then performs an X25519 ECDH operation between its private key and the peer's public key to generate a shared secret.

The implementation derives a 32-byte symmetric key from this shared secret using HKDF. The `deriveSymmetricKey` function produces the encryption key, which is immediately stored via `SetSymmetricKey` for future use.

```go
// pkg/mpc/key_exchange_session.go lines 24-27
sharedSecret, _ := e.privateKey.ECDH(peerPublicKey)        // X25519 shared secret
symmetricKey := e.deriveSymmetricKey(sharedSecret, ecdhMsg.From)
e.identityStore.SetSymmetricKey(ecdhMsg.From, symmetricKey) // store per‑peer key

```

## Storing Per-Peer Encryption Keys

The `SetSymmetricKey` method in [`pkg/identity/identity.go`](https://github.com/fystack/mpcium/blob/main/pkg/identity/identity.go) (line 415) persists the 32-byte symmetric key in the identity store, indexed by the peer's node ID. This creates a secure lookup table mapping each verified peer to its unique encryption key, enabling efficient key retrieval during subsequent message exchanges.

## Encrypting Direct TSS Messages

For point-to-point communication, the session handler in [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go) (lines 58-70) marshals TSS messages and invokes `identityStore.EncryptMessage`. This wrapper method retrieves the stored symmetric key for the target peer and applies AES-GCM encryption via the encryption package.

The process produces a ciphertext that includes the nonce prepended to the encrypted payload, eliminating the need for separate nonce transmission.

```go
// pkg/mpc/session.go lines 58-70 (simplified flow)
msg, _ := types.MarshalTssMessage(&tssMsg)               // serialize
cipher, err := s.identityStore.EncryptMessage(msg, toNodeID) // AES‑GCM
s.direct.SendToOther(topic, cipher)                       // send encrypted payload

```

## AES-GCM Implementation with Nonce Embedding

The actual cryptographic operations are implemented in [`pkg/encryption/aes.go`](https://github.com/fystack/mpcium/blob/main/pkg/encryption/aes.go) (lines 40-86). The `EncryptAESGCMWithNonceEmbed` function generates a cryptographically secure random nonce, seals the plaintext using AES-GCM, and returns the concatenation `nonce‖ciphertext`.

```go
// pkg/encryption/aes.go lines 40-61 (EncryptAESGCMWithNonceEmbed)
func EncryptAESGCMWithNonceEmbed(plaintext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    // … create GCM, generate nonce …
    ciphertext := aead.Seal(nil, nonce, plaintext, nil)
    return append(nonce, ciphertext...), nil // nonce prefixed
}

```

Decryption uses `DecryptAESGCMWithNonceEmbed`, which extracts the prepended nonce and verifies the authentication tag before returning the plaintext, ensuring both confidentiality and integrity.

## Decrypting Incoming Messages

Upon receiving an encrypted payload, the node calls `DecryptMessage` from [`pkg/identity/identity.go`](https://github.com/fystack/mpcium/blob/main/pkg/identity/identity.go) (lines 503-514). This method retrieves the sender-specific symmetric key and invokes the AES-GCM decryption routine. After successful authentication and decryption, the plaintext is unmarshaled into a TSS message structure for processing.

```go
// Decryption flow in session handling
plaintext, err := s.identityStore.DecryptMessage(cipher, senderID) // AES‑GCM
msg, err := types.UnmarshalTssMessage(plaintext)                 // deserialize
s.receiveTssMessage(msg)                                        // process

```

## Summary

- **X25519 ECDH** in [`pkg/mpc/key_exchange_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/key_exchange_session.go) establishes per-peer symmetric keys, providing forward secrecy for **message encryption between MPC nodes**.
- **HKDF derivation** converts X25519 shared secrets into 32-byte AES keys stored via `SetSymmetricKey` in [`pkg/identity/identity.go`](https://github.com/fystack/mpcium/blob/main/pkg/identity/identity.go).
- **AES-GCM encryption** in [`pkg/encryption/aes.go`](https://github.com/fystack/mpcium/blob/main/pkg/encryption/aes.go) provides authenticated encryption with nonce embedding via `EncryptAESGCMWithNonceEmbed`.
- **Ed25519 signatures** verify peer identity during the key exchange phase, preventing man-in-the-middle attacks before symmetric encryption begins.
- Direct peer-to-point TSS messages receive full AES-GCM encryption, while broadcast messages remain signed but unencrypted to allow NAT server inspection.

## Frequently Asked Questions

### What is the difference between Ed25519 and X25519 in mpcium's encryption scheme?

Ed25519 provides the cryptographic identity layer and digital signatures used to verify `ECDHMessage` authenticity. X25519 performs the Elliptic Curve Diffie-Hellman key exchange that generates the actual shared secrets used for symmetric encryption. Together, they form the "Ed2SSI9" flow: Ed25519 for identity, X25519 for key establishment.

### Why does mpcium use AES-GCM instead of encrypting directly with Ed25519?

AES-GCM is used because symmetric encryption is orders of magnitude faster than asymmetric operations and produces smaller ciphertexts. After the initial X25519 ECDH handshake establishes the symmetric key, AES-GCM efficiently handles the high-volume TSS message traffic between MPC nodes without the computational overhead of asymmetric encryption.

### How does mpcium distinguish between broadcast and direct message encryption?

Broadcast messages are signed with Ed25519 but transmitted in plaintext, allowing intermediate NAT servers to inspect routing metadata. Only direct point-to-point TSS messages are encrypted using the per-peer AES-GCM keys established via X25519 ECDH, ensuring payload confidentiality between specific nodes.

### Where are the symmetric encryption keys stored in mpcium?

The 32-byte symmetric keys derived from X25519 ECDH are stored in the identity store via `SetSymmetricKey` in [`pkg/identity/identity.go`](https://github.com/fystack/mpcium/blob/main/pkg/identity/identity.go) (line 415). These keys are indexed by peer node ID and persist for the duration of the session, enabling rapid lookup during `EncryptMessage` and `DecryptMessage` operations.