stablyai/orca · error · Error

Invalid ${label}: expected ${expected} bytes, got ${bytes.le

Error message

Invalid ${label}: expected ${expected} bytes, got ${bytes.length}

What it means

Thrown by `requireLength` inside `deriveMobileE2EEV2KeySchedule` when the shared secret, client nonce, or desktop nonce is not exactly 32 bytes. The HKDF expansion assumes 32-byte inputs; a different length means an upstream cryptographic primitive returned the wrong size (e.g. a truncated X25519 shared secret or a nonce that was not generated by `expo-crypto.getRandomBytes(32)`).

Source

Thrown at mobile/src/transport/mobile-e2ee-v2-key-schedule.ts:46

    desktopToMobileKey: expanded.slice(32, 64),
    sessionId: expanded.slice(64, 96),
    transcriptHash
  }
}

function concatBytes(parts: readonly Uint8Array[]): Uint8Array {
  const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0))
  let offset = 0
  for (const part of parts) {
    result.set(part, offset)
    offset += part.length
  }
  return result
}

function requireLength(bytes: Uint8Array, expected: number, label: string): void {
  if (bytes.length !== expected) {
    throw new Error(`Invalid ${label}: expected ${expected} bytes, got ${bytes.length}`)
  }
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Verify the desktop ready message decodes client/desktop nonces to exactly 32 bytes before calling `acceptReady`.
  2. Confirm `deriveSharedKey` (X25519) is producing 32 bytes — check the pinned desktop public key is a valid 32-byte point.
  3. Regenerate the client nonce via `ExpoCrypto.getRandomBytes(32)` rather than reusing a buffer of another length.

Example fix

// before
acceptReady({ ..., clientNonce: shortNonce, desktopNonce: shortNonce })
// -> Invalid client nonce: expected 32 bytes, got 16

// after
const clientNonce = ExpoCrypto.getRandomBytes(32)
// validate desktopNonce length before acceptReady:
if (handshake.desktopNonce.length !== 32) throw new Error('bad desktop nonce')
Defensive patterns

Strategy: validation

Validate before calling

function validKeyScheduleInputs(sharedSecret: Uint8Array, clientNonce: Uint8Array, desktopNonce: Uint8Array): boolean {
  return sharedSecret.length === 32 && clientNonce.length === 32 && desktopNonce.length === 32
}
if (!validKeyScheduleInputs(secret, cNonce, dNonce)) throw new Error('bad crypto lengths')

Type guard

function is32Bytes(b: Uint8Array): b is Uint8Array { return b.length === 32 }

Try / catch

try { deriveMobileE2EEV2KeySchedule({ sharedSecret, transcript, clientNonce, desktopNonce }) } catch (e) { if (e.message.startsWith('Invalid ')) { /* abort handshake, log which input was wrong size */ } else throw e }

Prevention

When it happens

Trigger: `acceptReady` invoked with a `handshake.clientNonce`/`handshake.desktopNonce` that is not 32 bytes; `deriveSharedKey` returning something other than 32 bytes due to a malformed public key; a key schedule test passing hand-rolled buffers of the wrong length.

Common situations: Corrupted or base64-decoded-with-wrong-alphabet nonce from the desktop; a pre-release desktop that sends 16-byte nonces; tests that hand-construct a handshake without respecting the 32-byte contract; a public key from `publicKeyFromBase64` that decoded to a non-32-byte scalar.

Related errors


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