#pragma once // IDS-CAN command-auth response cipher for the OneControl write path. // // 32-round TEA/XTEA-family Feistel, delta 0x9E3779B9, keyed by a per-session // 32-bit "Cypher" with the round constants baked in. // Verified bit-exact against ids_can_auth.py and 51 captured bus pairs. // // uint32_t arithmetic wraps mod 2^32 by definition in C++, so no masking needed. // ESP32 int is 32-bit (uint32_t == unsigned int): host g++ test is representative. #include #include namespace ids_can_auth { // Per-session keys ("Cypher"). REMOTE_CONTROL gates on/off/move. enum SessionKey : uint32_t { MANUFACTURING = 0xB16BA115u, DIAGNOSTIC = 0xBABECAFEu, REPROGRAMMING = 0xDEADBEEFu, REMOTE_CONTROL = 0xB16B00B5u, DAQ = 0x0B00B135u, }; // response = Encrypt(challenge). seed = challenge word, cypher = session key. inline uint32_t encrypt(uint32_t seed, uint32_t cypher) { uint32_t num = cypher; uint32_t sum = 0x9E3779B9u; // TEA golden-ratio delta for (int rounds = 32;;) { seed += ((num << 4) + 1131376761u) ^ (num + sum) ^ ((num >> 5) + 1919510376u); if (--rounds <= 0) break; num += ((seed << 4) + 1948272964u) ^ (seed + sum) ^ ((seed >> 5) + 1400073827u); sum += 0x9E3779B9u; } return seed; } inline uint32_t remote_control_response(uint32_t challenge) { return encrypt(challenge, REMOTE_CONTROL); } // 4 big-endian challenge bytes (as they arrive in the page-42 payload, after the // "00 04" prefix) -> 4 big-endian response bytes (for the page-43 reply). inline void remote_control_response_bytes(const uint8_t challenge[4], uint8_t response[4]) { uint32_t c = (uint32_t)challenge[0] << 24 | (uint32_t)challenge[1] << 16 | (uint32_t)challenge[2] << 8 | (uint32_t)challenge[3]; uint32_t r = remote_control_response(c); response[0] = (uint8_t)(r >> 24); response[1] = (uint8_t)(r >> 16); response[2] = (uint8_t)(r >> 8); response[3] = (uint8_t)(r); } } // namespace ids_can_auth