Skip to content

Instantly share code, notes, and snippets.

@tav
Last active April 7, 2020 11:37
Show Gist options
  • Save tav/ebf96a28087de9c3b846f4170f1433c2 to your computer and use it in GitHub Desktop.
Save tav/ebf96a28087de9c3b846f4170f1433c2 to your computer and use it in GitHub Desktop.

Viral Tracing Bluetooth Protocol

Background

  • We use Ed25519 for creating digital signatures. Public keys are 32 bytes in length, and signatures are 64 bytes.

  • We will start out with support for BLE (Bluetooth Low Energy) and add support for older versions later.

  • The term "table" is used in this document to refer to a hierarchical container for storage of related data. It does not necessarily imply the use of a relational datasystem.

Protocol

  • Every hour, the app will generate a new Ed25519 SessionKey. This key will be validated for local uniqueness and stored in a local session_key table:

    message SessionKey {
      option (primary_fields) = "public_key";
      google.protobuf.Timestamp create_time = 1;
      bytes private_key = 2;
      bytes public_key = 3;
    }
  • The app will, acting as a peripheral device, broadcast the public key component of its current SessionKey using BLE advertisement packets. The advertisement will use the Viral Tracing service UUID:

    ServiceUUID = "0c86fc28-2ce7-40cf-9f65-8b6c5a191100"

    The public key will be included as the value for the characteristic:

    CharacteristicUUID = "ae2e879f-0b38-4fe3-9a23-f667fedffd70"
  • The app will, acting as a central device, listen out for such broadcasts from others. Depending on the platform, it may need to connect to the peripheral to read the value of the characteristic, i.e. the public key.

  • This information is then stored locally by adding an entry to the contact table:

    message Contact {
      ProximityLevel proximity = 1;
      bytes public_key = 2;
      google.protobuf.Timestamp seen_time = 3;
    }

    Where proximity is defined as a coarse-grained enum:

    enum ProximityLevel {
      PROXIMITY_UNSPECIFIED = 0;
      PROXIMITY_LEVEL_1 = 1; // less than 2m
      PROXIMITY_LEVEL_2 = 2; // between 2-4m
      PROXIMITY_LEVEL_3 = 3; // between 4-10m
      PROXIMITY_LEVEL_4 = 4; // more than 10m
    }
  • Now if this was the first time that the app had seen a public key, or if it didn't have a corresponding proof for its current SessionKey, it will establish a connection to the peripheral advertising it — possibly re-using the connection it had used to read the characteristic — and write the following request:

    type ProofRequest struct {
        uint8     version       // defaulting to 1 for now
        [32]byte  sender_key
        [32]byte  receiver_key
        [64]byte  signature
    }

    The message will include the public key of the sender, i.e. the central device, the public key of the receiver, i.e. the specific public key seen from the peripheral, and the proof signature.

    The proof signature is generated by creating an Ed25519 signature with the sender's public key on the concatenation of the prefix contact: with the raw bytes of the receiver's public key.

    On receiving this proof, the receiving peripheral device will validate it, i.e. that it was for a SessionKey it'd recently used, had a valid signature, etc., and store it locally in its proof table:

    message Proof {
      option (primary_fields) = "contact_key,session_key";
      bytes contact_key = 1;
      google.protobuf.Timestamp create_time = 2;
      bytes session_key = 3;
      bytes signature = 4;
    }

    The peripheral will then write a response on the established connection:

    type ProofResponse struct {
        [32]byte  signature
    }

    The proof signature will be generated in the exact manner as in the first proof, except with the sender and receiver reversed. The connection initiator, i.e. the central device, will similarly validate and store the proof locally.

    With both proofs exchanged, the connection can now be terminated.

  • When establishing connections, the app will preference connections to devices it has least recently seen — especially ones it hasn't seen before.

  • The app will, every day, purge all data from tables which are more than 90 days old.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment