fix password issue

ober

07e3948cf8bd4b0bfd81d23be7bea19227b47628

diff --git a/.gitignore b/.gitignore
index f0f9ab0..3b7ec3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,8 @@ signal_tui_shim.dylib
 signal_tui_shim.so
 signal_log_shim.dylib
 signal_log_shim.so
+signal_security_shim.dylib
+signal_security_shim.so
 *.db
 *.trace
 /trace
diff --git a/signal/logdb.ss b/signal/logdb.ss
index 69a0c9b..2254654 100644
--- a/signal/logdb.ss
+++ b/signal/logdb.ss
@@ -338,6 +338,34 @@
            [sealed (log-aead-seal key nonce plain aad)])
       (bv-append *jlog-magic* salt generation-bytes sealed)))
 
+  ;; Pre-hardening (before P1 #38 / commit ef4e7c3) container format: magic ||
+  ;; salt || nonce(12, random) || AEAD(plain, aad = magic only). Same magic
+  ;; prefix as the current format, so jlog-container-bytes? cannot distinguish
+  ;; them by inspection alone. decrypt-container tries the current
+  ;; (generation-counter) format first and only falls back to this one when
+  ;; that AEAD open fails, so a genuinely wrong passphrase still fails closed
+  ;; against both formats. Returns #f (not this format / wrong passphrase) or
+  ;; (list key salt plain).
+  (def (try-decrypt-legacy-nonce-container bytes passphrase)
+    (let* ([magic-len (bytevector-length *jlog-magic*)]
+           [need (+ magic-len *jlog-salt-len* *jlog-nonce-len*)])
+      (and (>= (bytevector-length bytes) need)
+           (let* ([salt (bv-sub bytes magic-len *jlog-salt-len*)]
+                  [nonce (bv-sub bytes (+ magic-len *jlog-salt-len*)
+                                 *jlog-nonce-len*)]
+                  [sealed-start (+ magic-len *jlog-salt-len* *jlog-nonce-len*)]
+                  [sealed (bv-sub bytes sealed-start
+                                  (- (bytevector-length bytes) sealed-start))]
+                  [key (derive-key passphrase salt)])
+             (guard (e [(condition? e) #f])
+               (list key salt (log-aead-open key nonce sealed *jlog-magic*)))))))
+
+  ;; A 5th legacy-nonce-format? value flags a container decrypted via the
+  ;; pre-hardening fallback above; callers that persist a real generation
+  ;; counter (open-jlog-container) must migrate on this, and callers that
+  ;; should never see it (incremental checkpoints, always written in the
+  ;; current format) must fail closed instead of trusting the placeholder 0
+  ;; generation.
   (def (decrypt-container bytes passphrase)
     (let* ([magic-len (bytevector-length *jlog-magic*)]
            [need (+ magic-len *jlog-salt-len* *jlog-generation-len*)])
@@ -352,10 +380,16 @@
              [sealed (bv-sub bytes sealed-start
                              (- (bytevector-length bytes) sealed-start))]
              [key (derive-key passphrase salt)]
-             [aad (container-aad salt nonce generation-bytes)]
-             [plain (guard (e [(condition? e) (raise-passphrase-auth-error)])
-                      (log-aead-open key nonce sealed aad))])
-        (values key salt generation plain))))
+             [aad (container-aad salt nonce generation-bytes)])
+        (guard (e [(condition? e)
+                   (let ([legacy (try-decrypt-legacy-nonce-container
+                                   bytes passphrase)])
+                     (if legacy
+                       (values (list-ref legacy 0) (list-ref legacy 1) 0
+                               (list-ref legacy 2) #t)
+                       (raise-passphrase-auth-error)))])
+          (values key salt generation (log-aead-open key nonce sealed aad)
+                  #f)))))
 
   ;; Anti-rollback high-water mark stored OUTSIDE the mutable container file.
   (def (generation-marker-path path)
@@ -649,9 +683,10 @@
       (make-logdb-handle 'jsqlite log)))
 
   (def (open-jlog-container path passphrase bytes)
-    (let-values ([(key salt generation plain)
+    (let-values ([(key salt generation plain legacy-nonce?)
                   (decrypt-container bytes passphrase)])
-      (let ([marker (read-generation-marker path)])
+      (let* ([marker (read-generation-marker path)]
+             [generation (if legacy-nonce? (+ marker 1) generation)])
         (when (< generation marker)
           (error 'logdb-open
                  "encrypted log generation regression detected (rollback)"
@@ -668,6 +703,16 @@
                               (make-bytevector *jlog-chain-hash-len* 0)
                               0 0 0 #f)])
               (start-jlog-flusher! log)
+              ;; Re-seal immediately under the current format so the
+              ;; pre-hardening random-nonce container never persists past its
+              ;; first successful open.
+              (when legacy-nonce?
+                (display
+                  (string-append
+                    "jerboa-signal: message log is in a pre-hardening"
+                    " container format; migrating to the current format...\n")
+                  (current-error-port))
+                (persist-jlog-legacy! log))
               (make-logdb-handle 'jsqlite log)))))))
 
   ;; Open an incremental log: decrypt the head container (fails closed on a wrong
@@ -691,8 +736,13 @@
           (let ([ckpt-bytes (read-file-bytevector* (checkpoint-path path))])
             (unless (jlog-container-bytes? ckpt-bytes)
               (error 'logdb-open "incremental log checkpoint missing" path))
-            (let-values ([(ckpt-key ckpt-salt ckpt-gen ckpt-plain)
+            (let-values ([(ckpt-key ckpt-salt ckpt-gen ckpt-plain
+                           ckpt-legacy-nonce?)
                           (decrypt-container ckpt-bytes passphrase)])
+              (when ckpt-legacy-nonce?
+                (error 'logdb-open
+                       "incremental log checkpoint has an unexpected container format"
+                       path))
               (unless (= ckpt-gen ckpt-generation)
                 (error 'logdb-open
                        "incremental log checkpoint generation mismatch"
diff --git a/signal/tui/main.ss b/signal/tui/main.ss
index a88ce74..571472a 100644
--- a/signal/tui/main.ss
+++ b/signal/tui/main.ss
@@ -2940,17 +2940,32 @@
 
   ;; --- @mention support ---
 
-  ;; All direct conversation titles (contact names) as mention candidates.
+  ;; Mention candidates scoped to the currently open conversation only: for a
+  ;; direct chat, the other party; for a group, the distinct senders seen so
+  ;; far in that conversation. There is no group roster API wired up (see
+  ;; docs/full-port.md #15, "Groups V2" member/role tracking is still
+  ;; unimplemented), so message history is the best available proxy for
+  ;; "who's in this room."
   (def (mention-candidates state)
-    (let loop ([convs (tui-state-conversations state)]
-               [acc '()])
+    (let ([conv (selected-conversation state)])
+      (if (not conv)
+        '()
+        (case (conversation-kind conv)
+          [(direct) (list (conversation-title conv))]
+          [(group) (conversation-participant-senders conv)]
+          [else '()]))))
+
+  (def (conversation-participant-senders conv)
+    (let loop ([msgs (conversation-messages conv)] [acc '()])
       (cond
-        [(null? convs) (reverse acc)]
+        [(null? msgs) (reverse acc)]
         [else
-         (let ([conv (car convs)])
-           (loop (cdr convs)
-                 (if (eq? (conversation-kind conv) 'direct)
-                   (cons (conversation-title conv) acc)
+         (let ([sender (chat-message-sender (car msgs))])
+           (loop (cdr msgs)
+                 (if (and (non-empty-string? sender)
+                          (not (string=? sender "You"))
+                          (not (member sender acc)))
+                   (cons sender acc)
                    acc)))])))
 
   ;; Filter candidates by the partial query after the @.
diff --git a/tests/test-logdb-crypto.ss b/tests/test-logdb-crypto.ss
index 06b5b55..5960aff 100644
--- a/tests/test-logdb-crypto.ss
+++ b/tests/test-logdb-crypto.ss
@@ -14,7 +14,8 @@
                   make-date make-time)
         (except (jerboa prelude) meta atom?)
         (only (std security taint) safe-delete-file)
-        (signal logdb))
+        (signal logdb)
+        (signal log_crypto))
 
 (def (check label pred)
   (unless pred
@@ -130,6 +131,71 @@
              #f)))
   (clean! p))
 
+;; Pre-hardening compat (regression for the format break found 2026-07-23):
+;; a container in the format that predates commit ef4e7c3 (magic || salt ||
+;; nonce(12, random) || AEAD(plain, aad=magic), no generation field) must
+;; still open with the correct passphrase, self-migrating to the current
+;; generation-counter format, and must still fail closed with a wrong one.
+(def *legacy-salt-len* 16)
+(def *legacy-nonce-len* 12)
+(def *legacy-key-len* 32)
+(def *legacy-scrypt-n* 16384)
+(def *legacy-scrypt-r* 8)
+(def *legacy-scrypt-p* 1)
+(def *legacy-magic* (string->utf8 "JSQLITELOGv1\n"))
+
+(def (write-legacy-nonce-container! p passphrase)
+  (let* ([salt (log-random-bytes *legacy-salt-len*)]
+         [nonce (log-random-bytes *legacy-nonce-len*)]
+         [key (log-scrypt-key passphrase salt *legacy-key-len*
+                              *legacy-scrypt-n* *legacy-scrypt-r*
+                              *legacy-scrypt-p*)]
+         [sealed (log-aead-seal key nonce (make-bytevector 0 0) *legacy-magic*)]
+         [container (let* ([total (+ (bytevector-length *legacy-magic*)
+                                     *legacy-salt-len* *legacy-nonce-len*
+                                     (bytevector-length sealed))]
+                           [out (make-bytevector total 0)])
+                      (bytevector-copy! *legacy-magic* 0 out 0
+                                        (bytevector-length *legacy-magic*))
+                      (bytevector-copy! salt 0 out
+                                        (bytevector-length *legacy-magic*)
+                                        *legacy-salt-len*)
+                      (bytevector-copy! nonce 0 out
+                                        (+ (bytevector-length *legacy-magic*)
+                                           *legacy-salt-len*)
+                                        *legacy-nonce-len*)
+                      (bytevector-copy! sealed 0 out
+                                        (+ (bytevector-length *legacy-magic*)
+                                           *legacy-salt-len* *legacy-nonce-len*)
+                                        (bytevector-length sealed))
+                      out)])
+    (write-file-bytevector! p container)))
+
+(let ([p "/tmp/jerboa-signal-logdb-legacy-nonce-test.db"])
+  (clean! p)
+  (write-legacy-nonce-container! p "legacy horse")
+  (check "legacy-nonce container opens and migrates with correct passphrase"
+         (let ([h (logdb-open p "legacy horse")])
+           (and h (begin (logdb-close h) #t))))
+  (check "migrated container now opens via the current format (no fallback needed)"
+         (let ([h (logdb-open p "legacy horse")])
+           (and h (begin (logdb-close h) #t))))
+  (check "migrated container has a real (small) generation counter"
+         (< (container-generation (read-file-bytevector p)) 1000))
+  (clean! p))
+
+(let ([p "/tmp/jerboa-signal-logdb-legacy-nonce-authfail-test.db"])
+  (clean! p)
+  (write-legacy-nonce-container! p "legacy horse")
+  (check "legacy-nonce container still fails closed on wrong passphrase"
+         (guard (e [(condition? e)
+                    (and (who-condition? e)
+                         (eq? (condition-who e) 'logdb-passphrase-mismatch))])
+           (let ([h (logdb-open p "wrong horse")])
+             (when h (logdb-close h))
+             #f)))
+  (clean! p))
+
 ;; Key zeroing: jlog-close! zeroes the derived AEAD key bytevector.
 (let ([p "/tmp/jerboa-signal-logdb-keyzero-test.db"])
   (clean! p)