Skip to content

Instantly share code, notes, and snippets.

@joehonton
Created April 14, 2020 14:55
Show Gist options
  • Save joehonton/a647cd7cb629e938a1ac216a519c6485 to your computer and use it in GitHub Desktop.
Save joehonton/a647cd7cb629e938a1ac216a519c6485 to your computer and use it in GitHub Desktop.
import ContactTracing from './contact-tracing.class.js';
// Instantiate with the mobile device's 32-byte private key
const ct = new ContactTracing('a1b2c3d411jj99kk55gg66hhz0y9x8w7');
// Test data for Medium article "Contact Tracing with Android/iPhone"
const encounters = [
{person: 'Uber driver', ts: 'May 15, 2020 16:21:00'},
{person: 'Nearby shopper', ts: 'May 15, 2020 16:51:00'},
{person: 'Stranger', ts: 'May 16, 2020 17:06:00'},
{person: 'Store clerk', ts: 'May 16, 2020 17:11:00'},
];
process.stdout.write(` dtki RPIij person \n`);
process.stdout.write(`---------------- ---------------- ----------------\n`);
for (let encounter of encounters) {
const unixTimestamp = Date.parse(encounter.ts);
const data = ct.refresh(unixTimestamp);
process.stdout.write(`${data.dtki} ${data.RPIij} ${encounter.person} \n`);
}
import hkdf from 'futoin-hkdf';
import crypto from 'crypto';
export default class ContactTracing {
constructor(tracingKey) {
// The mobile device's 32 byte private key, generated exactly once when
// the app is installed, securely stored, and never leaves the device.
this.tk = tracingKey;
}
dailyTracingNumber(unixTimestamp) {
return Math.floor(unixTimestamp / (24*60*60*1000)).toString();
}
dailyTracingKeyInfo(Di) {
return "CT-DTK" + Di;
}
// Use the SHA-256 one-way crytographic hashing function, over the
// private tracing key and the daily tracing number. This results in
// a 32 byte hash, which is truncated to a 16 byte daily tracing key.
dailyTracingKey(dtkInfo) {
const sha256HashLength = 32;
const dtkiTruncateLength = 16;
const dtki = hkdf.expand('sha256', sha256HashLength, this.tk, dtkiTruncateLength, dtkInfo);
return dtki.toString('hex').substring(0, 16);
}
// The number of 10-minute intervals elapsed since midnight UTC
// Result is a number from 0 to 143
timeIntervalNumber(unixTimestamp) {
const secondsSinceEpoch = Math.floor(unixTimestamp/1000);
const wholeDays = Math.floor(secondsSinceEpoch / (24*60*60));
const timeSinceMidnight = secondsSinceEpoch - wholeDays;
return Math.floor(timeSinceMidnight / (60*10)).toString();
}
rollingProximityInfo(TINj) {
return "CT-RPI" + TINj;
}
// Compute a rolling proximity indentifer as an HMAC of the
// daily tracing key and the time interval number.
// Truncate the result to 16 bytes.
rollingProximityIdentifier(dtki, rpiInfo) {
const obj = crypto.createHmac('sha256', dtki);
obj.update(rpiInfo);
const hmac = obj.digest('hex');
return hmac.substring(0, 16);
}
// The mobile device should refresh its rolling proximity identifier every 10 minutes
refresh(unixTimestamp) {
const Di = this.dailyTracingNumber(unixTimestamp);
const dtkInfo = this.dailyTracingKeyInfo(Di);
const dtki = this.dailyTracingKey(dtkInfo);
const TINj = this.timeIntervalNumber(unixTimestamp);
const rpiInfo = this.rollingProximityInfo(TINj);
const RPIij = this.rollingProximityIdentifier(dtki, rpiInfo);
return {
dtki: dtki,
RPIij: RPIij
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment