{"record":{"id":"0c69e4bea9860f1b","repo":"schollz/croc","slug":"sender-did-not-confirm-the-croc-pake-handshake","errorCode":null,"errorMessage":"Sender did not confirm the croc PAKE handshake","messagePattern":"Sender did not confirm the croc PAKE handshake","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":709,"sourceCode":"    }\n    const finished = await wasm().pakeUpdate(pake.handle, peerPake.b);\n    const peerKeys = await wasm().derivePeerKeys(\n      finished.key,\n      peerPake.b2,\n      PAKE_PURPOSE_TRANSFER,\n      room,\n      curve,\n      pake.bytes,\n      peerPake.b,\n    );\n    await sendControl(control, {\n      t: \"pake-confirm\",\n      v: PAKE_PROTOCOL_VERSION,\n      b: peerKeys.confirmationA,\n    });\n    const confirmationB = await receiveControl(control);\n    if (confirmationB.t !== \"pake-confirm\" || !confirmationB.b) {\n      throw new Error(\"Sender did not confirm the croc PAKE handshake\");\n    }\n    requirePakeVersion(confirmationB.v);\n    if (!(await wasm().confirmPeerKey(peerKeys.confirmationB, confirmationB.b))) {\n      throw new Error(\"Sender PAKE confirmation failed\");\n    }\n    key = peerKeys.key;\n    data = await openDataConnections(\n      settings,\n      room,\n      dataPorts(relay.banner),\n      signal,\n    );\n    await sendControl(control, {\n      t: \"externalip\",\n      m: relay.externalIP,\n      b: peerPake.b,\n    }, key);\n    const peerIP = await receiveControl(control, key);","sourceCodeStart":691,"sourceCodeEnd":727,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L691-L727","documentation":"Thrown by the recipient when the sender does not answer the 'pake-confirm' message with a 'pake-confirm' of its own. After both sides derive keys, they exchange key-confirmation tokens to prove they derived the same shared key; silence or a wrong message type here means the sender never completed (or corrupted) its half of the handshake, so no encrypted channel is established.","triggerScenarios":"Sender disconnects after sending its pake reply but before confirmation; sender throws in confirmPeerKey on its side and reports an error control message instead; relay drops or reorders the confirmation frame; incompatible peer that ends the handshake after key derivation.","commonSituations":"Sender user cancels at the 'Securing channel…' stage; sender's wasm confirmPeerKey fails (wrong passphrase bytes, room mismatch) so it errors out; transient relay disconnection mid-handshake; mock senders that stop after the pake exchange.","solutions":["Check the sender-side error — the counterpart failure is usually 'Recipient PAKE confirmation failed' or a socket error, which identifies the root cause","Confirm the code phrase is identical on both sides (codeComponents must yield the same room and passphrase)","Retry the transfer; transient relay drops during handshake are common on unstable links","Upgrade both peers to the same protocol version before debugging deeper"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isPakeConfirm(m: { t: string; b?: Uint8Array }): boolean {\n  return m.t === \"pake-confirm\" && !!m.b && m.b.byteLength > 0;\n}","tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message === \"Sender did not confirm the croc PAKE handshake\") {\n    // often transient (peer dropped mid-handshake): retry once with a fresh code phrase\n  }\n  throw e;\n}","preventionTips":["Retry handshake failures with a new code phrase before investigating deeper","Check the peer side first — its error names the actual failure (often confirmPeerKey)","Keep handshakes short: long gaps between pake and confirm invite relay timeouts"],"tags":["pake","handshake","security","network"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}