# How Noise Handshakes Are Initiated and Managed for Secure Peer Sessions in Bitchat Android

> Discover how Bitchat Android initiates and manages Noise handshakes for secure peer sessions using Noise XX pattern and NoiseSessionManager for robust encryption.

- Repository: [permissionlesstech/bitchat-android](https://github.com/permissionlesstech/bitchat-android)
- Tags: internals
- Published: 2026-07-28

---

**Bitchat Android employs the Noise XX handshake pattern (Curve25519 + ChaChaPoly + SHA-256) to establish mutually authenticated encrypted channels, with `NoiseSessionManager` coordinating initiation and collision handling while `NoiseSession` executes the low-level cryptographic state machine.**

The `permissionlesstech/bitchat-android` repository implements decentralized peer-to-peer messaging using the Noise Protocol Framework to guarantee confidentiality and identity verification. This article examines how the Android client initiates, manages, and completes Noise handshakes to establish secure sessions between mesh network participants.

## Handshake Initiation Architecture

### Creating Initiator Sessions in NoiseSessionManager

When an outbound connection is required, the system invokes `initiateHandshake()` in [`NoiseSessionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSessionManager.kt). This method validates existing session states, then instantiates a new `NoiseSession` configured as the initiator.

```kotlin
// NoiseSessionManager.kt
fun initiateHandshake(peerID: String, replaceEstablished: Boolean = false): ByteArray? {
    // … (checks for existing session, stale hand‑shake, etc.) …
    val session = NoiseSession(
        peerID = peerID,
        isInitiator = true,
        localStaticPrivateKey = localStaticPrivateKey,
        localStaticPublicKey = localStaticPublicKey
    )
    addSession(peerID, session)
    return session.startHandshake()          // <- first XX message (e)
}

```

The manager immediately calls `session.startHandshake()` to generate the first handshake message.

### Generating the First Handshake Message

Inside [`NoiseSession.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSession.kt) (lines 74–106), the `startHandshake()` method initializes a `HandshakeState` with role **INITIATOR** and writes the first message containing the 32-byte ephemeral public key defined by `XX_MESSAGE_1_SIZE`.

```kotlin
// NoiseSession.kt
fun startHandshake(): ByteArray {
    val handshakeState = HandshakeState()
    handshakeState.initialize(noiseProtocolName, HandshakeState.INITIATOR)
    val message = ByteArray(XX_MESSAGE_1_SIZE)
    handshakeState.writeMessage(message, 0, ephemeralKey, ephemeralKey.size)
    return message
}

```

This 32-byte payload represents the ephemeral public key (`e`) sent to the responder to begin the XX protocol.

## Processing Inbound Handshake Traffic

### Message Routing and Session Lookup

Incoming handshake bytes are routed to `processHandshakeMessage()` in [`NoiseSessionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSessionManager.kt). The method delegates to `processHandshakeMessageWithResult()` to determine session ownership:

```kotlin
// NoiseSessionManager.kt
fun processHandshakeMessage(peerID: String, message: ByteArray): ByteArray? {
    return processHandshakeMessageWithResult(peerID, message).response
}

```

The manager first checks for an existing **responder candidate** (used during replacement handshakes). If none exists, it either creates a new responder session or reuses the current one, handling several edge cases:

- **Existing initiator session still handshaking and receives message 1** → Possible collision detected (lines 220–236)
- **Existing established session** → triggers replacement handshake logic, storing the candidate in `responderCandidates` (lines 127–135)
- **Stale handshake detected** → removes the stale session before creating a new one (lines 138–144)

### Collision Detection and Role Resolution

When both peers simultaneously initiate connections, the implementation resolves the collision deterministically: the peer with the higher `localPeerID` yields to the responder role. This logic appears in [`NoiseSessionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSessionManager.kt) lines 118–122, ensuring that exactly one initiator/responder pair emerges from simultaneous connection attempts.

## Handshake State Machine Execution

### Advancing the XX Pattern

The `processHandshakeMessage()` method in [`NoiseSession.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSession.kt) (lines 118–176) drives the cryptographic state machine. For responders receiving the first message, it initializes the handshake with role **RESPONDER**:

```kotlin
// NoiseSession.kt
fun processHandshakeMessage(message: ByteArray): ByteArray? {
    if (!isInitiator && handshakeState == null) {
        initializeNoiseHandshake(HandshakeState.RESPONDER)
    }
    // Feed message to state machine
    handshakeState.readMessage(message, 0, message.size, payloadBuffer, 0)
    // Check state transition...
}

```

Depending on the internal state (`WRITE_MESSAGE`, `SPLIT`, or `FAILED`), the method produces a response message or completes the handshake.

### Session Establishment and Transport Key Derivation

Upon reaching `HandshakeState.SPLIT`, `completeHandshake()` (lines 81–129) executes four critical steps:

1. **Verifies the remote static key** against the claimed peer ID using `NoisePeerIdentity.matchesClaimedPeerID`
2. **Derives transport ciphers** by calling `handshakeState.split()` to create `sendCipher` and `receiveCipher` pairs
3. **Captures the session token** by cloning the 32-byte handshake hash before destroying the state
4. **Resets security counters** and initializes sliding-window replay protection, marking the session as **Established**

## Session Replacement and Lifecycle Management

### Handling Replacement Handshakes

When an established session receives a fresh handshake initiation (e.g., due to transport-layer changes), the manager treats the new session as a **replacement candidate** stored in `responderCandidates`. The candidate must successfully authenticate the remote static key before evicting the existing session (`processHandshakeMessageWithResult` lines 64–70).

### Stale Handshake Cleanup

A scheduled sweeper executes every `HANDSHAKE_SWEEP_INTERVAL_MS` (2 seconds) to invoke `cleanupStaleHandshakes()`. This method iterates over both `sessions` and `responderCandidates` maps, removing any handshake that has not progressed within `HANDSHAKE_TIMEOUT_MS` (10 seconds) (lines 15–33).

## Post-Handshake Secure Communication

Once a session is **Established**, callers use `NoiseSessionManager.encrypt()` and `decrypt()`, which delegate to the `NoiseSession` transport ciphers. The payload format follows `<nonce><ciphertext>` where the nonce is a 4-byte big-endian counter. Replay protection uses a sliding-window implementation via `isValidNonce()` and `markNonceAsSeen()`.

The handshake hash (32 bytes) returned by `session.getHandshakeHash()` serves as the **session token** (`SESSION_TOKEN_SIZE`). The `getAuthenticatedSession()` method constructs an `AuthenticatedNoiseSession` containing the remote static key and hashed token, providing cryptographically-bound identity for higher-level UI and re-key decisions.

## Summary

- **Noise XX Pattern**: Bitchat Android uses Curve25519, ChaChaPoly, and SHA-256 for mutually authenticated encryption
- **Central Coordination**: [`NoiseSessionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSessionManager.kt) handles initiation, collision resolution, and lifecycle management
- **Cryptographic Implementation**: [`NoiseSession.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSession.kt) manages the state machine, key derivation, and transport cipher setup
- **Collision Resolution**: Deterministic role assignment based on `localPeerID` comparison prevents handshake deadlocks
- **Automatic Cleanup**: A 2-second interval sweeper removes handshakes stalled longer than 10 seconds
- **Identity Binding**: Remote static keys are verified against 16-character mesh IDs using `NoisePeerIdentity`

## Frequently Asked Questions

### What Noise pattern does Bitchat Android use?

Bitchat Android implements the **Noise XX pattern** using Curve25519 for elliptic curve operations, ChaChaPoly for authenticated encryption, and SHA-256 for hashing. This pattern provides mutual authentication where both parties transmit their static public keys, ensuring each peer can verify the other's identity before establishing the transport channel.

### How does Bitchat handle simultaneous connection attempts?

When two peers initiate handshakes simultaneously, `NoiseSessionManager` detects the collision and resolves it deterministically: the peer with the higher `localPeerID` yields to the responder role while the other continues as initiator. This logic in lines 220–236 of [`NoiseSessionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSessionManager.kt) guarantees that exactly one cryptographic session emerges from concurrent connection attempts.

### What happens if a handshake message is lost or delayed?

If a handshake fails to complete within `HANDSHAKE_TIMEOUT_MS` (10 seconds), the scheduled sweeper removes the stale session during its next cleanup cycle (`HANDSHAKE_SWEEP_INTERVAL_MS` is 2 seconds). The application can then retry connection establishment by calling `initiateHandshake()` again, which creates a fresh session state.

### How is the peer identity verified during the handshake?

During `completeHandshake()` (lines 81–129 in [`NoiseSession.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NoiseSession.kt)), the implementation extracts the remote static public key and calls `NoisePeerIdentity.matchesClaimedPeerID()`. This derives the 16-character mesh ID from the 32-byte static key and confirms it matches the claimed peer identifier, preventing man-in-the-middle attacks where an attacker might present a different ephemeral key.