Created
April 14, 2020 14:55
-
-
Save joehonton/a647cd7cb629e938a1ac216a519c6485 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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