3.6 KiB
3.6 KiB
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:
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:
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
- Client connects to
ws://gateway:18789/ws - Gateway sends:
{"type": "event", "event": "connect.challenge", "payload": {"nonce": "...", "ts": ...}} - Client sends connect request with signed device info
- 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
// 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
- On Mac Mini:
cd ~/dev/clawdnode-android && git pull origin main - Android Studio: Sync & rebuild
- On phone: Clear ClawdNode app data (regenerates keys)
- 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
# 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