ChirpIdentity.kt

2.5 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    /**18     * This device's signed Pro license blob (`PPK-…`), set only when Pro. Sent19     * in the outbound PAIR_IDENTITY so the peer can activate it after SAS — one20     * Pro device grants Pro to the other when pairing. Either direction.21     */22    val license: String? = null,23)2425fun getOrCreateDeviceId(context: Context): String {26    val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)27    val current = prefs.getString(DEVICE_ID_KEY, null)28    if (!current.isNullOrEmpty()) return current29    val fresh = UUID.randomUUID().toString()30    prefs.edit().putString(DEVICE_ID_KEY, fresh).apply()31    return fresh32}3334fun getDeviceName(context: Context): String {35    val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)36    return prefs.getString(DEVICE_NAME_KEY, null).orEmpty().ifBlank { defaultDeviceName() }37}3839fun setDeviceName(context: Context, name: String) {40    val trimmed = name.trim()41    require(trimmed.isNotEmpty()) { "device name must not be empty" }42    require(trimmed.length <= 64) { "device name must be 64 chars or fewer" }43    context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)44        .edit()45        .putString(DEVICE_NAME_KEY, trimmed)46        .apply()47}4849fun buildLocalIdentity(context: Context, masterKey: ByteArray): LocalChirpIdentity {50    val deviceId = getOrCreateDeviceId(context)51    val deviceName = getDeviceName(context)52    val kp = deriveSyncIdentityKeypair(masterKey, deviceId)53    val signing = deriveSyncSigningKeypair(masterKey, deviceId)54    return LocalChirpIdentity(55        deviceId = deviceId,56        deviceName = deviceName,57        deviceType = extensionDeviceType(),58        longTermPublicKeyHex = pubkeyToHex(kp.publicKey),59        signingPublicKeyHex = pubkeyToHex(signing.publicKeyRaw),60    )61}6263fun deriveLocalSyncKeypair(masterKey: ByteArray, deviceId: String): SyncIdentityKeypair {64    return deriveSyncIdentityKeypair(masterKey, deviceId)65}6667private fun defaultDeviceName(): String {68    return "Android"69}7071private fun extensionDeviceType(): DeviceType {72    return DeviceType.ANDROID73}