fix: normalize crypto files to plain ASCII (batch v0.2.14)

- Replace smart quotes, em dash, prime, right arrow in comments with ASCII equivalents
- Affected: CoupleEncryptionManager.kt, FieldEncryptor.kt, RecoveryKeyManager.kt
This commit is contained in:
null 2026-06-19 21:22:27 -05:00
parent 85b4eb589b
commit 70bb0a346c
4 changed files with 20 additions and 14 deletions

View File

@ -9,15 +9,15 @@ import javax.inject.Inject
import javax.inject.Singleton
enum class EncryptionStatus {
/** Local keyset present ready to encrypt/decrypt. */
/** Local keyset present -- ready to encrypt/decrypt. */
UNLOCKED,
/** Found keyset in the invite slot; moved to coupleId slot automatically. */
RECONCILED_FROM_INVITE,
/** encryptionVersion == 1 but no local keyset prompt for recovery phrase. */
/** encryptionVersion == 1 but no local keyset -- prompt for recovery phrase. */
NEEDS_RECOVERY,
/** encryptionVersion == 0 this couple must create a key before writing more answers. */
/** encryptionVersion == 0 -- this couple must create a key before writing more answers. */
NEEDS_ENCRYPTION_UPGRADE,
/** encryptionVersion == 1 with a local key this device must rewrite its legacy answers. */
/** encryptionVersion == 1 with a local key -- this device must rewrite its legacy answers. */
NEEDS_CONTENT_MIGRATION
}
@ -71,7 +71,7 @@ class CoupleEncryptionManager @Inject constructor(
/**
* Called on app launch / Home load after the couple doc is resolved.
* Handles inviter reconciliation (flow B) transparently.
* Handles inviter reconciliation (flow B') transparently.
*/
fun checkStatus(couple: Couple): EncryptionStatus {
if (couple.encryptionVersion == 0) return EncryptionStatus.NEEDS_ENCRYPTION_UPGRADE
@ -111,7 +111,7 @@ class CoupleEncryptionManager @Inject constructor(
): Result<RecoveryKeyManager.WrappedKey> = withContext(Dispatchers.Default) {
runCatching {
val handle = keyStore.loadKeyset(coupleId)
?: error("No local keyset for $coupleId cannot change phrase without recovery first")
?: error("No local keyset for $coupleId -- cannot change phrase without recovery first")
keyManager.wrap(handle, newPhrase)
}
}

View File

@ -11,7 +11,7 @@ import javax.inject.Singleton
* Wire format: "enc:v1:{base64(tinkCiphertext)}"
* Plaintext values (no prefix) pass through unchanged so legacy data works.
*
* AAD = coupleId bytes binds ciphertext to the couple and prevents
* AAD = coupleId bytes -- binds ciphertext to the couple and prevents
* copy-paste of one couple's ciphertext into another couple's document.
*/
@Singleton

View File

@ -11,7 +11,7 @@ import javax.inject.Inject
import javax.inject.Singleton
/**
* Pure crypto helper no Firestore, no Android dependencies.
* Pure crypto helper -- no Firestore, no Android dependencies.
* Handles: keyset generation, Argon2id KDF, couple-key wrap/unwrap, phrase generation.
*
* Argon2id params (m=46 MiB, t=3, p=1): ~2-3 s on a mid-range phone.
@ -38,7 +38,7 @@ class RecoveryKeyManager @Inject constructor() {
/**
* Wraps [keyset] with Argon2id(passphrase, salt) using AES-256-GCM.
* AAD is the fixed constant [WRAP_AAD] so the blob is portable across
* invite couple transition without re-wrapping.
* invite -> couple transition without re-wrapping.
*/
fun wrap(keyset: KeysetHandle, phrase: String): WrappedKey {
val salt = ByteArray(SALT_BYTES).also { SecureRandom().nextBytes(it) }
@ -64,7 +64,7 @@ class RecoveryKeyManager @Inject constructor() {
return deserializeKeyset(keysetBytes)
}
/** Serialize a KeysetHandle to bytes (cleartext JSON stored inside EncryptedSharedPreferences). */
/** Serialize a KeysetHandle to bytes (cleartext JSON -- stored inside EncryptedSharedPreferences). */
fun serializeKeyset(handle: KeysetHandle): ByteArray {
val baos = java.io.ByteArrayOutputStream()
com.google.crypto.tink.CleartextKeysetHandle.write(
@ -102,7 +102,7 @@ class RecoveryKeyManager @Inject constructor() {
private const val PARAMS_TAG = "argon2id;v=19;m=47104;t=3;p=1"
private val WRAP_AAD = "closer_couple_key".toByteArray(Charsets.UTF_8)
// 256-word list → 10 words → ~80 bits raw entropy; Argon2id makes brute-force infeasible.
// 256-word list -> 10 words -> ~80 bits raw entropy; Argon2id makes brute-force infeasible.
val WORDLIST = arrayOf(
"able","acid","acre","aged","aide","also","army","atom",
"baby","back","bake","ball","bank","barn","base","bath",

View File

@ -322,6 +322,7 @@ service cloud.firestore {
match /answers/{userId} {
allow create, update: if isCouplesMember(coupleId)
&& isOwner(userId)
&& request.resource.data.keys().hasOnly(['userId', 'questionId', 'answerType', 'writtenText', 'selectedOptionIds', 'scaleValue', 'createdAt', 'updatedAt'])
&& coupleEncryptionEnabled(coupleId)
&& isEncryptedAnswerPayload(request.resource.data);
allow delete: if isOwner(userId);
@ -334,10 +335,12 @@ service cloud.firestore {
allow create: if isCouplesMember(coupleId)
&& coupleEncryptionEnabled(coupleId)
&& request.resource.data.authorUserId == request.auth.uid
&& request.resource.data.keys().hasOnly(['authorUserId', 'text', 'createdAt'])
&& isCiphertext(request.resource.data.text);
allow update: if isCouplesMember(coupleId)
&& coupleEncryptionEnabled(coupleId)
&& resource.data.authorUserId == request.auth.uid
&& request.resource.data.keys().hasOnly(['text'])
&& isCiphertext(request.resource.data.text);
allow delete: if isCouplesMember(coupleId)
&& resource.data.authorUserId == request.auth.uid;
@ -347,9 +350,11 @@ service cloud.firestore {
match /reactions/{reactionId} {
allow read: if isCouplesMember(coupleId);
allow create: if isCouplesMember(coupleId)
&& request.resource.data.userId == request.auth.uid;
&& request.resource.data.userId == request.auth.uid
&& request.resource.data.keys().hasOnly(['userId', 'emoji', 'createdAt']);
allow update: if isCouplesMember(coupleId)
&& resource.data.userId == request.auth.uid;
&& resource.data.userId == request.auth.uid
&& request.resource.data.keys().hasOnly(['userId', 'emoji', 'createdAt']);
allow delete: if isCouplesMember(coupleId)
&& resource.data.userId == request.auth.uid;
}
@ -457,7 +462,7 @@ service cloud.firestore {
allow read: if isCouplesMember(coupleId);
allow create: if isCouplesMember(coupleId)
&& request.auth.uid == userId
&& request.resource.data.keys().hasAll(['userId', 'questionId', 'answerType', 'createdAt', 'updatedAt'])
&& request.resource.data.keys().hasOnly(['userId', 'questionId', 'answerType', 'writtenText', 'selectedOptionIds', 'scaleValue', 'createdAt', 'updatedAt'])
&& request.resource.data.userId == request.auth.uid
&& request.resource.data.questionId is string
&& request.resource.data.answerType is string
@ -468,6 +473,7 @@ service cloud.firestore {
&& request.resource.data.userId == resource.data.userId
&& request.resource.data.questionId == resource.data.questionId
&& request.resource.data.answerType == resource.data.answerType
&& request.resource.data.keys().hasOnly(['userId', 'questionId', 'answerType', 'writtenText', 'selectedOptionIds', 'scaleValue', 'createdAt', 'updatedAt'])
&& coupleEncryptionEnabled(coupleId)
&& isEncryptedAnswerPayload(request.resource.data);
allow delete: if false;