{"record":{"id":"c524d1f3aeaad405","repo":"stablyai/orca","slug":"invalid-client-nonce-length-clientnonce-length","errorCode":null,"errorMessage":"Invalid client nonce length: ${clientNonce.length}","messagePattern":"Invalid client nonce length: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"mobile/src/transport/mobile-e2ee-v2-client-session.ts","lineNumber":40,"sourceCode":"  private constructor(\n    private readonly clientSecretKey: Uint8Array,\n    private readonly pinnedDesktopPublicKey: Uint8Array,\n    hello: MobileE2EEV2Hello\n  ) {\n    this.hello = hello\n  }\n\n  static create(args: {\n    desktopPublicKeyB64: string\n    transport: MobileE2EETransport\n    relayHostId?: string\n    clientNonce?: Uint8Array\n    clientKeyPair?: { publicKey: Uint8Array; secretKey: Uint8Array }\n  }): MobileE2EEV2ClientSession {\n    const keyPair = args.clientKeyPair ?? generateKeyPair()\n    const clientNonce = args.clientNonce ?? ExpoCrypto.getRandomBytes(32)\n    if (clientNonce.length !== 32) {\n      throw new Error(`Invalid client nonce length: ${clientNonce.length}`)\n    }\n    return new MobileE2EEV2ClientSession(\n      keyPair.secretKey,\n      publicKeyFromBase64(args.desktopPublicKeyB64),\n      {\n        type: 'e2ee_hello',\n        v: 2,\n        clientPublicKeyB64: publicKeyToBase64(keyPair.publicKey),\n        clientNonceB64: encodeBase64(clientNonce),\n        capabilities: { framing: [2], payloadKinds: ['text', 'binary'] },\n        context: {\n          protocol: 'orca-mobile-e2ee',\n          initiator: 'mobile',\n          responder: 'desktop',\n          transport: args.transport,\n          ...(args.relayHostId ? { relayHostId: args.relayHostId } : {})\n        }\n      }","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/mobile-e2ee-v2-client-session.ts#L22-L58","documentation":"MobileE2EEV2ClientSession.create received a clientNonce whose byte length is not exactly 32. The nonce is either caller-supplied (args.clientNonce) or generated by ExpoCrypto.getRandomBytes(32). A caller-supplied nonce of the wrong length trips this guard before the hello is constructed, since the E2EE v2 key schedule derives session keys from a 32-byte client nonce.","triggerScenarios":"A test or caller passed a clientNonce of the wrong length (e.g. 16 bytes from nacl.randomBytes(16)); expo-crypto.getRandomValues was shimmed incorrectly in a test environment returning wrong-length output; a nonce was truncated or double-encoded before being passed.","commonSituations":"Unit test mocking ExpoCrypto.getRandomBytes to return a short buffer; a caller reusing a nonce from a different protocol with a different length; nonce deserialized from base64 incorrectly (off-by-one decode); dev-build expo-crypto native module misconfigured.","solutions":["Omit clientNonce in production code so ExpoCrypto.getRandomBytes(32) generates the correct length.","In tests, pass clientNonce: ExpoCrypto.getRandomBytes(32) or a known 32-byte Uint8Array.","Validate nonce length at the call site before passing to create().","Ensure the expo-crypto native module is properly linked in custom dev builds."],"exampleFix":"// before (test passing wrong-length nonce)\nconst session = MobileE2EEV2ClientSession.create({\n  desktopPublicKeyB64,\n  transport: 'direct',\n  clientNonce: new Uint8Array(16) // wrong!\n})\n\n// after\nconst session = MobileE2EEV2ClientSession.create({\n  desktopPublicKeyB64,\n  transport: 'direct',\n  clientNonce: new Uint8Array(32) // or omit entirely\n})","handlingStrategy":"validation","validationCode":"function isValidClientNonce(nonce: Uint8Array): boolean {\n  return nonce instanceof Uint8Array && nonce.length === 32\n}\n\n// Validate before creating the session\nif (clientNonce && !isValidClientNonce(clientNonce)) {\n  throw new Error(`clientNonce must be 32 bytes, got ${clientNonce.length}`)\n}","typeGuard":"function isNonce32(nonce: unknown): nonce is Uint8Array {\n  return nonce instanceof Uint8Array && nonce.length === 32\n}","tryCatchPattern":"try {\n  const session = MobileE2EEV2ClientSession.create({ desktopPublicKeyB64, transport, clientNonce })\n} catch (e) {\n  if (e.message.startsWith('Invalid client nonce length')) {\n    // Regenerate the nonce with the correct length\n    session = MobileE2EEV2ClientSession.create({ desktopPublicKeyB64, transport })\n  }\n}","preventionTips":["Omit clientNonce in production to let ExpoCrypto.getRandomBytes(32) generate it correctly.","In tests, always pass a 32-byte Uint8Array or the real ExpoCrypto.getRandomBytes(32).","Validate nonce length at the call site before passing to create().","Ensure expo-crypto is properly linked in custom dev builds."],"tags":["e2ee","crypto","nonce","validation","mobile","v2-handshake"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}