docs(manual): batch 2 review — recovery phrase '256-word list' was wrong (it's 248)

The Recovery phrase flow said RecoveryKeyManager.generateRecoveryPhrase()
draws from a 256-word list. Verified the actual list size in
RecoveryKeyManager.kt: the hard-coded WORDLIST array has 248 entries
(python re.findall over the array literal), and the iOS wordlist file
iphone/Closer/Crypto/Resources/wordlist.txt is 247 lines (last word
'real' with no trailing newline, so 248 entries). The '256' is also
wrong in the inline comment in RecoveryKeyManager.kt - a pre-existing
comment bug from before the R24 iOS port (IOS_E2EE_STATUS.md notes the
iOS SPEC.md originally said 256 too, corrected in 922364f). The Android
side never got the same comment fix.

Manual now says 248 with the source-of-truth pointers, and computes
the entropy as 248^10 (a quick sanity check) so the next reader doesn't
trust the wrong number on either side.

Other Batch 2 claims verified clean:
- All 5 Android files in the 'Key Android files' list exist.
- All 2 Cloud Functions files in the 'Key Cloud Functions' list exist.
- Rate limit: 1h window, 10 max, 25h TTL on invite_attempts - all match
  ACCEPT_RATE_LIMIT_WINDOW_MS / ACCEPT_RATE_LIMIT_MAX / ACCEPT_ATTEMPT_TTL_MS
  in acceptInviteCallable.ts and the fieldOverrides entry in
  firestore.indexes.json.
- Couples doc model fields (id, userIds, inviteCode, createdAt,
  streakCount, lastAnsweredAt, currentQuestionId, activePackId,
  encryptionVersion, wrappedCoupleKey, kdfSalt, kdfParams) all match
  the create() payload in acceptInviteCallable.ts. createdAt uses
  FieldValue.serverTimestamp() (manual says 'server-side' - correct).
- EncryptionVersion.STRICT = 2 in EncryptionVersion.kt.
This commit is contained in:
null 2026-07-08 23:20:16 -05:00
parent f5194a4453
commit 1b36eba692
1 changed files with 1 additions and 1 deletions

View File

@ -350,7 +350,7 @@ This prevents brute-forcing the 6-character invite code space.
The recovery phrase is the only human-readable secret in the system. It is never sent to the server in plaintext.
1. When an Android inviter creates a couple, `RecoveryKeyManager.generateRecoveryPhrase()` produces a 10-word phrase from a 256-word list. The phrase has roughly 80 bits of raw entropy; Argon2id makes brute-force infeasible.
1. When an Android inviter creates a couple, `RecoveryKeyManager.generateRecoveryPhrase()` produces a 10-word phrase from a 248-word list (the list is hard-coded as `WORDLIST` in `RecoveryKeyManager.kt`; the iOS `iphone/Closer/Crypto/Resources/wordlist.txt` is a verbatim copy with 248 entries). The phrase has roughly 80 bits of raw entropy (`248^10`); Argon2id makes brute-force infeasible.
2. The inviter encrypts the phrase with the invite code using `encryptPhraseWithCode` and stores the blob on the invite document.
3. The acceptor receives the encrypted blob, decrypts it with the same code, and stores the phrase locally.
4. The phrase is used to unwrap the couple keyset from `wrappedCoupleKey`.