diff --git a/bitchat/App/VerificationModel.swift b/bitchat/App/VerificationModel.swift index 992713c2..d7ea4f63 100644 --- a/bitchat/App/VerificationModel.swift +++ b/bitchat/App/VerificationModel.swift @@ -162,11 +162,30 @@ final class VerificationModel: ObservableObject { } else { vouchers = [] } + // Each voucher is named by the name YOU VERIFIED IT UNDER, not by + // whatever it announces now. + // + // This list is a trust attribution — "people you verified vouch for + // this peer" — so it has the same problem as a seal, one level out. Use + // the live name and a voucher that renamed is attributed under its new + // one: Eve gets verified as "ravi", vouches for Mallory, renames to + // "medic", and Mallory's sheet reads "vouched by medic". The vouch is + // real and the key is trusted; the NAME beside it was never verified, + // which is the whole subject of this change. + // + // Order: a petname first, because it is your own label and nothing on + // the network can influence it; then the pinned baseline; then the + // claimed nickname, for vouchers recorded before baselines existed — + // the same fail-open as everywhere else here. let voucherNames = vouchers.compactMap { record -> String? in guard let social = chatViewModel.identityManager.getSocialIdentity(for: record.voucherFingerprint) else { return nil } if let petname = social.localPetname, !petname.isEmpty { return petname } + if let pinned = chatViewModel.identityManager.trustedNickname( + fingerprint: record.voucherFingerprint), !pinned.isEmpty { + return pinned + } return social.claimedNickname.isEmpty ? nil : social.claimedNickname } diff --git a/bitchatTests/Services/SecureIdentityStateManagerNicknameBindingTests.swift b/bitchatTests/Services/SecureIdentityStateManagerNicknameBindingTests.swift index 5ac5da12..9a455438 100644 --- a/bitchatTests/Services/SecureIdentityStateManagerNicknameBindingTests.swift +++ b/bitchatTests/Services/SecureIdentityStateManagerNicknameBindingTests.swift @@ -350,6 +350,25 @@ struct SecureIdentityStateManagerNicknameBindingTests { "while the stored verification is untouched — re-verifying is the recovery") } + @Test + func aVoucherIsNamedByTheNameItWasVerifiedUnder() { + // The voucher list in the fingerprint sheet is a trust attribution, so + // it has the seal's problem one level out: name a voucher by what it + // announces NOW and a renamed voucher is credited under the new name. + // Eve verified as "ravi", vouches for Mallory, renames to "medic", and + // Mallory's sheet reads "vouched by medic". + // + // The baseline outlives the rename precisely so the sheet can say + // "ravi" — the person you actually checked. Pinned at the manager, + // which is where the view reads it from. + let manager = makeManager() + announce(manager, vouchee, as: "ravi") + manager.setVerified(fingerprint: vouchee, verified: true) + announce(manager, vouchee, as: "medic") + #expect(manager.trustedNickname(fingerprint: vouchee) == "ravi", + "a voucher that renamed is still named by what it was verified as") + } + // MARK: - What counts as the same name @Test