SyncIdentity.kt

3.4 KB · raw

1package com.peachpasswords.sync23import com.peachpasswords.vault.VaultCrypto4import org.bouncycastle.crypto.digests.Blake3Digest5import org.bouncycastle.crypto.params.X25519PrivateKeyParameters6import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters78data class SyncIdentityKeypair(9    val publicKey: ByteArray,10    val secretKey: ByteArray,11)1213data class SyncSigningKeypair(14    val privateKey: Ed25519PrivateKeyParameters,15    val publicKeyRaw: ByteArray,16)17/** Random per-installation credentials; private material is stored only in the local vault. */18data class LocalIdentityCredentials(19    val deviceId: String,20    val x25519PrivateKey: ByteArray,21    val x25519PublicKey: ByteArray,22    val ed25519Seed: ByteArray,23    val ed25519PublicKey: ByteArray,24)2526fun randomLocalIdentityCredentials(deviceId: String, random: java.security.SecureRandom = java.security.SecureRandom()): LocalIdentityCredentials {27    require(deviceId.isNotBlank())28    val x = ByteArray(32).also(random::nextBytes)29    val e = ByteArray(32).also(random::nextBytes)30    val xPrivate = X25519PrivateKeyParameters(x, 0)31    val ePrivate = Ed25519PrivateKeyParameters(e, 0)32    return LocalIdentityCredentials(deviceId, x, xPrivate.generatePublicKey().encoded, e, ePrivate.generatePublicKey().encoded)33}3435fun localSyncKeypair(credentials: LocalIdentityCredentials): SyncIdentityKeypair =36    SyncIdentityKeypair(credentials.x25519PublicKey.copyOf(), credentials.x25519PrivateKey.copyOf())3738fun localSigningKeypair(credentials: LocalIdentityCredentials): SyncSigningKeypair =39    SyncSigningKeypair(Ed25519PrivateKeyParameters(credentials.ed25519Seed, 0), credentials.ed25519PublicKey.copyOf())4041private val VAULT_ID_INFO = "peach-vault-id".encodeToByteArray()4243fun computeVaultId(masterKey: ByteArray): String {44    require(masterKey.isNotEmpty()) { "masterKey must not be empty" }45    val buf = masterKey + VAULT_ID_INFO46    val digest = Blake3Digest(256)47    digest.update(buf, 0, buf.size)48    val out = ByteArray(32)49    return try {50        digest.doFinal(out, 0)51        out.joinToString("") { "%02x".format(it) }52    } finally {53        VaultCrypto.zero(out)54        VaultCrypto.zero(buf)55    }56}5758fun verifyEd25519(publicKeyRaw: ByteArray, message: ByteArray, signature: ByteArray): Boolean {59    return try {60        if (publicKeyRaw.size != 32 || signature.size != 64) return false61        val publicKey = org.bouncycastle.crypto.params.Ed25519PublicKeyParameters(publicKeyRaw, 0)62        val verifier = org.bouncycastle.crypto.signers.Ed25519Signer()63        verifier.init(false, publicKey)64        verifier.update(message, 0, message.size)65        verifier.verifySignature(signature)66    } catch (_: Exception) {67        false68    }69}7071fun signEd25519(privateKey: Ed25519PrivateKeyParameters, message: ByteArray): ByteArray {72    val signer = org.bouncycastle.crypto.signers.Ed25519Signer()73    signer.init(true, privateKey)74    signer.update(message, 0, message.size)75    return signer.generateSignature()76}7778fun pubkeyToHex(pk: ByteArray): String = pk.joinToString("") { "%02x".format(it) }7980fun hexToBytes(hex: String): ByteArray {81    require(hex.length % 2 == 0) { "hex length must be even" }82    val out = ByteArray(hex.length / 2)83    for (i in out.indices) {84        val byte = hex.substring(i * 2, i * 2 + 2).toInt(16)85        out[i] = byte.toByte()86    }87    return out88}8990fun hexToPubkey(hex: String): ByteArray {91    require(hex.length == 64) { "public key must be exactly 32 bytes" }92    return hexToBytes(hex)93}