# ClawdNode Android Debugging Session - 2025-01-28 ## Problem ClawdNode Android app fails to connect to Clawdbot gateway - shows "failed to connect". ## Root Cause #1 (Fixed Earlier) **Cryptographic algorithm mismatch**: Gateway uses Ed25519 signatures, Android app was using ECDSA P-256. Fixed by switching to Bouncy Castle Ed25519. ## Root Cause #2 (Fixed 2025-01-28) **Signature payload format was WRONG!** Android was signing: ``` $nonce:$signedAt ``` Gateway actually expects (from `gateway/device-auth.js`): ``` v2|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce ``` Example: `v2|abc123def|clawdbot-android|node|node||1706470000000|gateway-token|challenge-nonce` ## Solution Implemented (Commit a1e94f5) ### DeviceIdentity.kt Updated `signChallenge()` to accept all required parameters and build correct payload: ```kotlin fun signChallenge( nonce: String, clientId: String, clientMode: String, role: String, scopes: String = "", token: String = "" ): SignedChallenge { val signedAt = System.currentTimeMillis() val payload = listOf( "v2", deviceId, clientId, clientMode, role, scopes, signedAt.toString(), token, nonce ).joinToString("|") // ... sign payload with Ed25519 } ``` ### GatewayClient.kt Updated `handleConnectChallenge()` to pass all parameters: ```kotlin signedChallenge = deviceIdentity.signChallenge( nonce = nonce, clientId = Protocol.CLIENT_ID, // "clawdbot-android" clientMode = Protocol.MODE, // "node" role = Protocol.ROLE, // "node" scopes = "", token = token // gateway token ) ``` ## Gateway Protocol Details (from source code analysis) ### WebSocket Handshake Flow 1. Client connects to `ws://gateway:18789/ws` 2. Gateway sends: `{"type": "event", "event": "connect.challenge", "payload": {"nonce": "...", "ts": ...}}` 3. Client sends connect request with signed device info 4. Gateway verifies signature, responds with `hello-ok` ### Device Auth Payload (v2) Built by `buildDeviceAuthPayload()` in `gateway/device-auth.js`: ``` v2|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce ``` ### Key Functions ```javascript // Building payload (gateway/device-auth.js) buildDeviceAuthPayload(params) { const base = [ "v2", params.deviceId, params.clientId, params.clientMode, params.role, params.scopes.join(","), String(params.signedAtMs), params.token ?? "", params.nonce ?? "" ]; return base.join("|"); } // Signing (infra/device-identity.js) signDevicePayload(privateKeyPem, payload) { return crypto.sign(null, Buffer.from(payload, "utf8"), key).toString("base64url"); } ``` ### Device Identity Format - **Public key**: 32 bytes raw, base64url encoded - **Device ID**: SHA-256 hash of raw public key bytes, hex encoded - **Signature**: Ed25519 signature of UTF-8 payload bytes, base64url encoded ## Android Implementation Notes - Using Bouncy Castle Ed25519 (Android Keystore only supports Ed25519 on API 33+) - Keys stored in EncryptedSharedPreferences (AES-256) - 32-byte seed for private key, generates public key ## Next Steps to Test 1. On Mac Mini: `cd ~/dev/clawdnode-android && git pull origin main` 2. Android Studio: Sync & rebuild 3. On phone: Clear ClawdNode app data (regenerates keys) 4. Test connection ## Git Repo `git@zurich.inou.com:clawdnode-android.git` ## Network Details - Gateway: ws://100.123.216.65:18789/ws (Tailscale WebSocket) - Phone Tailscale IP: 100.102.141.81 - Gateway host: james ## Debugging Commands ```bash # Watch gateway logs (check for signature verification errors) journalctl -u clawdbot -f | grep -i "device\|signature\|connect" # Or if running via pm2/direct: tail -f ~/.clawdbot/logs/*.log ```