clawd/memory/2026-01-28-clawdnode-debug.md

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

  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

// 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

# 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