ChirpIdentity.kt

2.8 KB · raw

1package com.peachpasswords.sync23import android.content.Context4import java.util.UUID56private const val DEVICE_ID_KEY = "peach_chirp_device_id"7private const val DEVICE_NAME_KEY = "peach_chirp_device_name"8private const val PREFS_FILE = "peach_chirp_identity"910data class LocalChirpIdentity(11    val deviceId: String,12    val deviceName: String,13    val deviceType: DeviceType,14    val longTermPublicKeyHex: String,15    val signingPublicKeyHex: String,16    val wsPort: Int? = null,17    val relayUrl: String = Rendezvous.DEFAULT_RELAY_URL,18    /**19     * This device's signed Pro license blob (`PPK-…`), set only when Pro. Sent20     * in the outbound PAIR_IDENTITY so the peer can activate it after SAS — one21     * Pro device grants Pro to the other when pairing. Either direction.22     */23    val license: String? = null,24)2526fun getOrCreateDeviceId(context: Context): String {27    val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)28    val current = prefs.getString(DEVICE_ID_KEY, null)29    if (!current.isNullOrEmpty()) return current30    val fresh = UUID.randomUUID().toString()31    prefs.edit().putString(DEVICE_ID_KEY, fresh).apply()32    return fresh33}3435fun getDeviceName(context: Context): String {36    val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)37    return prefs.getString(DEVICE_NAME_KEY, null).orEmpty().ifBlank { defaultDeviceName() }38}3940fun setDeviceName(context: Context, name: String) {41    val trimmed = name.trim()42    require(trimmed.isNotEmpty()) { "device name must not be empty" }43    require(trimmed.length <= 64) { "device name must be 64 chars or fewer" }44    context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)45        .edit()46        .putString(DEVICE_NAME_KEY, trimmed)47        .apply()48}4950fun buildLocalIdentity(context: Context, masterKey: ByteArray): LocalChirpIdentity {51    val deviceId = getOrCreateDeviceId(context)52    val deviceName = getDeviceName(context)53    val kp = deriveSyncIdentityKeypair(masterKey, deviceId)54    val signing = deriveSyncSigningKeypair(masterKey, deviceId)55    return LocalChirpIdentity(56        deviceId = deviceId,57        deviceName = deviceName,58        deviceType = extensionDeviceType(),59        longTermPublicKeyHex = pubkeyToHex(kp.publicKey),60        signingPublicKeyHex = pubkeyToHex(signing.publicKeyRaw),61        // No LAN port is advertised: peers reconnect through the relay62        // (docs/sync-lan-retirement.md), never by dialling this device.63        wsPort = null,64        relayUrl = RelaySettingsStore.readRelayUrl(context),65    )66}6768fun deriveLocalSyncKeypair(masterKey: ByteArray, deviceId: String): SyncIdentityKeypair {69    return deriveSyncIdentityKeypair(masterKey, deviceId)70}7172private fun defaultDeviceName(): String {73    return "Android"74}7576private fun extensionDeviceType(): DeviceType {77    return DeviceType.ANDROID78}