stablyai/orca · error · Error

Invalid E2EE v2 ready

Error message

Invalid E2EE v2 ready

What it means

Thrown by `acceptReady` when the ready JSON parsed successfully but `session.acceptReady(ready)` returned `false`. That boolean is false when `validateMobileE2EEV2Handshake` rejects the shape, or when the desktop public key in the handshake does not constant-time-equal the pinned key. This is the cryptographic pin check failing.

Source

Thrown at mobile/src/transport/mobile-e2ee-v2-physical-channel.ts:133

    } else if (typeof plaintext === 'string') {
      this.args.onText(plaintext)
    } else {
      this.args.onBinary(plaintext)
    }
  }

  private acceptReady(raw: unknown): void {
    if (typeof raw !== 'string') {
      throw new Error('Expected plaintext E2EE v2 ready')
    }
    let ready: unknown
    try {
      ready = JSON.parse(raw)
    } catch {
      throw new Error('Invalid E2EE v2 ready JSON')
    }
    if (!this.args.session.acceptReady(ready)) {
      throw new Error('Invalid E2EE v2 ready')
    }
    this.state = 'awaiting-authenticated'
    this.outboundQueue.enqueue({
      kind: 'text',
      plaintext: JSON.stringify({
        type: 'e2ee_auth',
        v: 2,
        transcriptHashB64: this.args.session.transcriptHashB64,
        deviceToken: this.args.deviceToken
      })
    })
  }

  private async openBinary(raw: unknown, generation: number): Promise<Uint8Array | null> {
    const bytes = await this.args.decodeBinary(raw)
    if (!bytes || generation !== this.generation) {
      return null
    }

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Confirm the `desktopPublicKeyB64` passed to `MobileE2EEV2ClientSession.create` matches the host's current long-term key (re-pair if the desktop rotated keys).
  2. Inspect the ready payload against `validateMobileE2EEV2Handshake` to see whether schema or pin failed.
  3. Abort the link on this error — do not retry with the same pin.

Example fix

// before
session.acceptReady(ready) // silently false, then channel throws

// after
const handshake = validateMobileE2EEV2Handshake(session.hello, ready)
if (!handshake) reportError('ready schema rejected')
else if (!equalBytes(handshake.desktopPublicKey, pinnedDesktopPublicKey))
  reportError('desktop key pin mismatch — re-pair required')
Defensive patterns

Strategy: validation

Validate before calling

import { validateMobileE2EEV2Handshake } from '../../../src/shared/mobile-e2ee-v2-contract'
const handshake = validateMobileE2EEV2Handshake(session.hello, readyPayload)
if (!handshake || !equalBytes(handshake.desktopPublicKey, pinnedDesktopPublicKey)) {
  // abort before acceptReady
}

Type guard

function pinsDesktopKey(handshake: { desktopPublicKey: Uint8Array } | null, pinned: Uint8Array): boolean {
  return !!handshake && equalBytes(handshake.desktopPublicKey, pinned)
}

Try / catch

channel.onError = (e) => { if (e.message === 'Invalid E2EE v2 ready') { markHostForRepair(hostId); link.close() } }

Prevention

When it happens

Trigger: Desktop's `desktopPublicKey` in the ready message differs from the `pinnedDesktopPublicKey` configured at session creation; ready message schema mismatch (wrong fields, wrong version, missing nonce); a different host answered the relay connection than the one the mobile pinned.

Common situations: Pinning the wrong public key (stale host profile after a desktop re-install); connecting to the wrong host via relay; a man-in-the-middle attempt that cannot produce the pinned key; protocol version skew where the ready schema changed.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/df485aef6116d258. Report an issue: GitHub.