Skip to content

SRAM Eagle Powertrain

SRAM Eagle Powertrain support is in progress. The bonding handshake has been implemented and now work has begun to reverse engineer the underlying payloads.

Bonding

Values sent to the phone from the bike are mostly encrypted. This means BikeBridge cannot parse what the bike is sending without a handshake with the bike.

The SRAM bond session uses these BLE services/characteristics: - Service: d905ee51-90aa-4c7c-b036-1e01fb8eb7ee - Characteristic: d905ee52-90aa-4c7c-b036-1e01fb8eb7ee

At a high level, BikeBridge does one of two things:

First Connection

  1. Ask the user to authorize bonding by holding the AXS button on the bike
  2. Exchange temporary public keys with the bike
  3. Use the temporary shared key to decrypt the first provisioning message
  4. Save the final 16-byte SRAM bond key returned by the bike
  5. Confirm the session was established by decrypting a second provisioning message

Subsequent Connections

  1. Send 0x73 using the stored key
  2. Decrypt the response with the stored key
  3. If the decrypted message is valid, use that key for encrypted SRAM data reads, otherwise fallback to First Connection steps

Once the session is established, encrypted SRAM characteristic values are decrypted with the 16-byte SRAM bond key.

Diffie-Hellman

First-time bonding uses a small Diffie-Hellman exchange:

public = 5^private mod (2^128 - 713)
secret = devicePublic^private mod (2^128 - 713)

Two helpers cover this:

AES-EAX

Auth/bonding packets use AES-EAX:

packet[0:16]   = nonce
packet[16:-16] = ciphertext
packet[-16:]   = authentication tag

decryptEax(key, ciphertext) verifies the tag first. If the tag is valid, it decrypts the ciphertext with AES-CTR.

During first-pairing flow:

  1. The first encrypted packet decrypts with the intermediate DH key and returns the final 16-byte key
  2. The second encrypted packet decrypts with that final key and should produce hello everyone!\0

CMAC

AES-EAX uses AES-CMAC to authenticate the nonce, optional header data, and ciphertext:

tag = CMAC(key, block(0) + nonce)
      xor CMAC(key, block(1) + header)
      xor CMAC(key, block(2) + ciphertext)

CMAC itself uses raw AES block encryption. In the implementation this is accessed through AES/ECB/NoPadding; this is only the AES block primitive for CMAC, not the payload encryption mode. Payload bytes are decrypted with AES-CTR as required by EAX.

The CMAC double operation derives the standard CMAC subkeys by shifting a 128-bit block left by one bit and applying the 0x87 reduction constant when needed.

Protocol Buffers

After decrypting payloads, SRAM payloads are decoded via Protocol Buffers.

SRAM uses protobuf-c to compile the Protocol Buffers.

Functionality

Assist Mode

Assist mode BLE characteristic: d905009d-90aa-4c7c-b036-1e01fb8eb7ee

Protocol Buffer:

message ApolloAssistMode {
    optional uint32 assist_mode = 12;
    optional uint32 outb_overshoot_deciperc = 13;
    optional uint32 outb_debounce_ms = 14;
    optional uint32 inb_overshoot_deciperc = 15;
    optional uint32 inb_debounce_ms = 16;
    optional uint32 coast_offset_rpm = 25;
    optional uint32 coast_transition_ms = 26;
}

Assist Mode Config

Assist mode config BLE characteristics:

  • Rally: d905009c-90aa-4c7c-b036-1e01fb8eb7ee
  • Range: d905009c-90aa-4c7c-b036-1e01fb8eb7ee

Protocol Buffer:

message ApolloModeConfig {
    optional uint32 power_level = 12;
    optional uint32 sensitivity_scaling = 13;
    optional uint32 gain_level = 14;
    optional uint32 flex_power = 15;
}

Shift Config

Shift config BLE characteristic: d905009e-90aa-4c7c-b036-1e01fb8eb7ee

Protocol Buffer:

message ApolloShiftConfig {
    optional uint32 cadence_setpoint = 16;
    optional bool auto_shift = 17;
    optional uint32 overlap_min = 18;
    optional uint32 overlap_max = 19;
    optional uint32 torque_cadence_gain = 20;
    optional uint32 torque_cadence_limit = 21;
    optional uint32 nom_power = 22;
    optional uint32 initial_outb_offset = 23;
    optional uint32 outb_decay_factor = 24;

    optional uint32 start_gear_idx = 27;
    optional uint32 dflt_cadence_sp_rpm = 28;
    optional uint32 initial_inb_offset = 29;
    optional uint32 inb_decay_factor = 30;
}

See also