package com.peachpasswords.sync import com.peachpasswords.vault.VaultCrypto import org.bouncycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.generators.HKDFBytesGenerator import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters import org.bouncycastle.crypto.params.HKDFParameters import org.bouncycastle.crypto.params.X25519PrivateKeyParameters /** * Root→device key derivation, retained **for tests only** — campaign task 2.4. * * Production no longer derives any device key material from the vault root: * device identities are random per installation (`randomLocalIdentityCredentials`) * and route capabilities come from `Rendezvous.deriveMeshSecret`. Nothing in * `app/src/main` may reference this file; fixtures use it only to build * deterministic keypairs. */ private val LEGACY_SYNC_IDENTITY_SALT = "peach-sync-identity-v1".encodeToByteArray() private val LEGACY_SYNC_SIGNING_SALT = "peach-sync-signing-v1".encodeToByteArray() internal fun deriveSyncIdentityKeypair( masterKey: ByteArray, deviceId: String, ): SyncIdentityKeypair { require(masterKey.isNotEmpty()) { "masterKey must not be empty" } require(deviceId.isNotEmpty()) { "deviceId must not be empty" } val seed = legacyHkdf(masterKey, LEGACY_SYNC_IDENTITY_SALT, deviceId.encodeToByteArray(), 32) val priv = X25519PrivateKeyParameters(seed, 0) return SyncIdentityKeypair(publicKey = priv.generatePublicKey().encoded, secretKey = seed) } internal fun deriveSyncSigningKeypair( masterKey: ByteArray, deviceId: String, ): SyncSigningKeypair { require(masterKey.isNotEmpty()) { "masterKey must not be empty" } require(deviceId.isNotEmpty()) { "deviceId must not be empty" } val seed = legacyHkdf(masterKey, LEGACY_SYNC_SIGNING_SALT, deviceId.encodeToByteArray(), 32) return try { val privateKey = Ed25519PrivateKeyParameters(seed, 0) SyncSigningKeypair( privateKey = privateKey, publicKeyRaw = privateKey.generatePublicKey().encoded, ) } finally { VaultCrypto.zero(seed) } } private fun legacyHkdf( ikm: ByteArray, salt: ByteArray, info: ByteArray, length: Int, ): ByteArray { val generator = HKDFBytesGenerator(SHA256Digest()) generator.init(HKDFParameters(ikm, salt, info)) val out = ByteArray(length) generator.generateBytes(out, 0, length) return out }