Fork me on GitHub

Project Notes

#571 TraceTogether Token

Learning about the Singapore Government’s latest BLE TraceTogether Token for pandemic contact tracing, and sniffing the adveritsements with a little bit of Swift code.

Build

Notes

The Singapore Government has really done an outstanding job for citizens and residents in its COVID-19 response, and has made “doing the right thing for you, your family and community” an uncontroversial and obvious responsibility for us all.

The track and trace capability is backed by 3 convenient tools:

  • SafeEntry - QR code checkin/out
  • TraceTogether App - BLE contact recording for mobile phones
  • TraceTogether Token - stand-alone BLE contact recording token

These notes are a quick summary of what I have learned about the TraceTogether Token, largely from the published information and open source code released by the Government.

How the TraceTogether Mobile Application Works

The TraceTogether Mobile Applications implement the BlueTrace Protocol, which has been open-sourced as OpenTrace with reference implementation.

The BlueTrace White Paper describes its operation. It uses a hybrid centralised/distributed architecture to ensure privacy while everything is “fine”, but provides a mechanism for contact tracers to contact and request the upload of pseudonymised logs when needed for tracing. I believe in essence:

  • the application is registered centrally (so the health service has basic contact details when needed to chase down contacts)
  • the application gets anonymised and encrpyted “tempId” tokens from the central service.
  • the app hands out these tokens when it encounters another device
  • the app stores tokens it receives from other devices for a period of time (21 days by default for OpenTrace).
  • if/when contact tracing required, the app user is asked to upload their contact logs (requires a key to be provided by the central service). The central service can decrypt the encounter tokens and perform their job of contact tracing

Key points to note with the TraceTogether app implementation:

  • it requires periodic connectivity to the central service to get new tempIds
  • tempIds are symmetrically encrypted by the central service, cannot be decrypted by the app, and only decrypted if uploaded to the central service as part of a contact tracing exercise.

Encounter messages are a JSON format. Here’s an example:

ASCII:  {"id":"iGHGqN+EhRinvjYJxY8CZ2VUlNIjT1GW58MjC8iuzHxv2f3mx2Ai6hZgIQdZii90NRoUwmL1M9LAabu398QGMQ==","mp":"POCOPHONE F1","o":"SG_MOH","v":2}

Where:

  • id: tempId for the encounter
  • mp: device identifier (used for distance calibration)
  • o: organisation where the device is enrolled
  • v: BlueTrace protocol version

How the TraceTogether Token Works

TraceTogether Tokens only have BLE connectivity and run under tight hardware and battery constraints, so the cannot implement the full BlueTrace Protocol.

They run a modified protocol called “BlueTrace Lite” which is described in the TraceTogether Token Technical Write-up. As far as I am aware, we have yet to see an open-sourced reference implementation. Full details are only available to manufacturers and partners that have signed up to the interoperability programme.

If I am reading the official documentation correctly, tokens are pre-loaded with an assigned Company ID (Version & ID) and Global Replay Protection Key (RPK). These are used to calculate the Message Authentication Code (MAC) for each encounter token packet, also including a “Company Encoded UUID”. It appears the “Company Encoded UUID” is generated by the token, and changes every full 5 minute scan cycle or so.

Tokens run a scan/advertised cycle. They validate the MAC of encounter tokens received, and apply some rules for which to keep and and which to discard (i.e. top 30 unique by RSSI in a 5 minute period).

When required for contact tracing, the current encounter log can apparently be read from the device via BLE GATT, however the encryption and authorisation requirements are not stated.

token_algo_summary

Publically available TraceTogether information appears to imply that the app and tokens are able to communicate with each other. I presume this means that the TraceTogether applications have had BlueTrace Lite compatibility added, though this is not included in the OpenTrace reference implementation. NB: I don’t see this specifically mentioned in the release notes, so I am just guessing.

TraceTogether Token Hardware

See the TraceTogether Token Technical Write-up for more info.

The token I’m looking at is I think is the second major version.

It’s a well made case that is designed for access and repair e.g. battery replacement. No permanent bonding, yeah! It could probably be scaled down & slimmed down quite a bit, however absolute minimal size may not have been a key design requirement (a more chunky fob does make it easier to handle and less likely to be lost I guess).

Basic observations:

  • main processor - STM32WB55CGU6
    • Ultra-low-power dual core Arm Cortex-M4 MCU 64 MHz, Cortex-M0+ 32MHz with 1 Mbyte of Flash memory, Bluetooth LE 5.0, 802.15.4, Zigbee, Thread, USB, LCD, AES-256
  • 64Mb flash storage - MX25R6435F
  • powered by a replaceable CR2477 lithium battery
  • a separate RTC and lithium battery backup. Not sure what it is - possibly something like a Maxim DS1390U-33 or Microchip MCP79510-I/MS. Prevents losing real time even during main battery replacement. Interesting that the design uses a seperate external RTC, even though the STM32WB55CGU6 has an integrated RTC.

A look inside the token:

token_open

token_pcb_annotated

As far as I can gather, the basic functional architecture is something like this:

token_architecture

Sniffing the BLE Advertisements

See TTTScan for a little bit of MacOSX Swift code for sniffing BLE advertisements, made with some code and ideas I borrowed from BLE_Scan. The project needs to be built with xcode.

I can’t vouch for the accuracy of this or whether I’m reading the advertisements correctly, but I do appear to be seeing a couple of TraceTogether Tokens. Here’s some sample console output:

$ ./TTTScan
Scan start...
BLE is now powered on
Device : 297BB29D-A41F-46D1-AE53-FE5857318EDA - RSSI : -84 DeviceName : n/a
  --> serviceData : [FE9F: <00000000 00000000 00000000 00000000 00000000>]
Device : 297BB29D-A41F-46D1-AE53-FE5857318EDA - RSSI : -84 DeviceName : n/a
Device : F05C059D-8EE0-455F-890F-1071B54236B7 - RSSI : -70 DeviceName : n/a
Device : F05C059D-8EE0-455F-890F-1071B54236B7 - RSSI : -70 DeviceName : n/a
Device : 61E68541-A6A7-43F0-AF01-AE369DB9FB0E - RSSI : -51 DeviceName : n/a
  --> serviceData : [FFFF: <b8dfb789 55b6d6a9 09cded68 6b081ce7 7fbb3833>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x33
  --> TTToken : companyEncryptedUUID = 0xD6A909CDED686B081CE7
  --> TTToken : MAC = 0xB8DFB78955B6
Device : 61E68541-A6A7-43F0-AF01-AE369DB9FB0E - RSSI : -51 DeviceName : n/a
Device : 7F67B9A2-81E0-4AC6-8AE0-06FF1F5A255E - RSSI : -90 DeviceName : n/a
Device : 9EF5B8D1-A526-4BE6-B60E-424BD8D0D664 - RSSI : -63 DeviceName : n/a
  --> serviceData : [FFFF: <da652a96 857c81ad 9529c91a 9e8d23ff 7e713833>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x33
  --> TTToken : companyEncryptedUUID = 0x81AD9529C91A9E8D23FF
  --> TTToken : MAC = 0xDA652A96857C
Device : 9EF5B8D1-A526-4BE6-B60E-424BD8D0D664 - RSSI : -63 DeviceName : n/a
Device : 03E14BF2-EF66-491A-962B-A23537CEBAFC - RSSI : -84 DeviceName : Sunshine's Boss
Device : 03E14BF2-EF66-491A-962B-A23537CEBAFC - RSSI : -85 DeviceName : G-"/Is8j!ort?2`4
Device : 651DC103-1275-4B9E-963A-DF128D77259E - RSSI : -51 DeviceName : n/a
  --> serviceData : [FFFF: <b8dfb789 55b6d6a9 09cded68 6b081ce7 7fbb3833>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x33
  --> TTToken : companyEncryptedUUID = 0xD6A909CDED686B081CE7
  --> TTToken : MAC = 0xB8DFB78955B6
Device : 651DC103-1275-4B9E-963A-DF128D77259E - RSSI : -51 DeviceName : n/a
Device : C2CE2F18-FE42-4721-847F-B90585E7F5C1 - RSSI : -86 DeviceName : n/a
Device : C2CE2F18-FE42-4721-847F-B90585E7F5C1 - RSSI : -88 DeviceName : n/a
Device : CB5D9CCE-03F0-4AE8-B1F6-F05A0E0AD534 - RSSI : -62 DeviceName : n/a
  --> serviceData : [FFFF: <da652a96 857c81ad 9529c91a 9e8d23ff 7e713833>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x33
  --> TTToken : companyEncryptedUUID = 0x81AD9529C91A9E8D23FF
  --> TTToken : MAC = 0xDA652A96857C
Device : CB5D9CCE-03F0-4AE8-B1F6-F05A0E0AD534 - RSSI : -63 DeviceName : n/a
Device : BEADF56D-F024-444C-92BB-B1CA1E2CC4F6 - RSSI : -89 DeviceName : n/a
Device : BEADF56D-F024-444C-92BB-B1CA1E2CC4F6 - RSSI : -88 DeviceName : n/a
Device : D0E980CC-A0B0-4C7C-8DA1-768727CE68A7 - RSSI : -94 DeviceName : LE-Boomz
Device : 963F1840-DA74-4EF6-B542-A913DB98E6CD - RSSI : -52 DeviceName : n/a
  --> serviceData : [FFFF: <b8dfb789 55b6d6a9 09cded68 6b081ce7 7fbb3833>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x33
  --> TTToken : companyEncryptedUUID = 0xD6A909CDED686B081CE7
  --> TTToken : MAC = 0xB8DFB78955B6
Device : 963F1840-DA74-4EF6-B542-A913DB98E6CD - RSSI : -52 DeviceName : n/a
Device : 45DD3008-0EF7-4C72-AF68-839B8B4E45E0 - RSSI : -85 DeviceName : n/a
  --> serviceData : [FFFF: <daa96da7 dcfb4b09 9f314835 5fa81600 559f3820>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x20
  --> TTToken : companyEncryptedUUID = 0x4B099F3148355FA81600
  --> TTToken : MAC = 0xDAA96DA7DCFB
Device : 45DD3008-0EF7-4C72-AF68-839B8B4E45E0 - RSSI : -85 DeviceName : n/a
Device : 31631232-B67A-4C60-8E1B-26701E64C7C5 - RSSI : -69 DeviceName : n/a
Device : 31631232-B67A-4C60-8E1B-26701E64C7C5 - RSSI : -86 DeviceName : n/a
Device : 52817F59-FC26-40D1-B030-B138068A5D4E - RSSI : -62 DeviceName : n/a
  --> serviceData : [FFFF: <da652a96 857c81ad 9529c91a 9e8d23ff 7e713833>]
  --> TTToken : seems like this might be one: Version = 0x38 ID = 0x33
  --> TTToken : companyEncryptedUUID = 0x81AD9529C91A9E8D23FF
  --> TTToken : MAC = 0xDA652A96857C
^C
$

Credits and References

About LEAP#571 BLEARM

This page is a web-friendly rendering of my project notes shared in the LEAP GitHub repository.

Project Source on GitHub Return to the LEAP Catalog
About LEAP

LEAP is my personal collection of electronics projects - usually involving an Arduino or other microprocessor in one way or another. Some are full-blown projects, while many are trivial breadboard experiments, intended to learn and explore something interesting.

Projects are often inspired by things found wild on the net, or ideas from the many great electronics podcasts and YouTube channels. Feel free to borrow liberally, and if you spot any issues do let me know or send a pull-request.

NOTE: For a while I included various scale modelling projects here too, but I've now split them off into a new repository: check out LittleModelArt if you are looking for these projects.

Project Gallery view the projects as an image gallery Notebook reference materials and other notes Follow the Blog follow projects and notes as they are published in your favourite feed reader