mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-10-18 07:33:46 +00:00
Merge 698f911edd63430c56374eed8f35a193b6ff53f0 into 9b84b361225facd8e623f25d76f889d3dc54a879
This commit is contained in:
commit
e223044648
@ -63,6 +63,14 @@ final class VerificationModel: ObservableObject {
|
||||
return VerificationService.shared.buildMyQRString(nickname: currentNickname, npub: npub) ?? ""
|
||||
}
|
||||
|
||||
func identityVerificationCard() -> IdentityVerificationCard {
|
||||
IdentityVerificationCard.current(
|
||||
nickname: currentNickname,
|
||||
npub: try? chatViewModel.idBridge.getCurrentNostrIdentity()?.npub,
|
||||
noiseFingerprint: chatViewModel.getMyFingerprint()
|
||||
)
|
||||
}
|
||||
|
||||
func verifyScannedPayload(_ payload: String) -> VerificationScanOutcome {
|
||||
guard let qr = VerificationService.shared.verifyScannedQR(payload) else {
|
||||
return .invalid
|
||||
|
||||
71
bitchat/Identity/IdentityVerificationCard.swift
Normal file
71
bitchat/Identity/IdentityVerificationCard.swift
Normal file
@ -0,0 +1,71 @@
|
||||
//
|
||||
// IdentityVerificationCard.swift
|
||||
// bitchat
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Public-only identity card for out-of-band verification (#183).
|
||||
///
|
||||
/// Contains nickname, npub, and Noise fingerprint — never private key material.
|
||||
/// Passphrase-sealed backup export remains future work; this card is the safe
|
||||
/// first step for sharing who you are on bitchat.
|
||||
struct IdentityVerificationCard: Equatable {
|
||||
let nickname: String
|
||||
let npub: String?
|
||||
let noiseFingerprint: String
|
||||
|
||||
var plainText: String {
|
||||
var lines: [String] = [
|
||||
String(
|
||||
localized: "identity.card.title",
|
||||
defaultValue: "bitchat identity card",
|
||||
comment: "Title line when exporting a public identity verification card"
|
||||
),
|
||||
"",
|
||||
String(
|
||||
format: String(
|
||||
localized: "identity.card.nickname",
|
||||
defaultValue: "nickname: %@",
|
||||
comment: "Nickname line on the public identity card; %@ is the nickname"
|
||||
),
|
||||
locale: .current,
|
||||
nickname
|
||||
),
|
||||
String(
|
||||
format: String(
|
||||
localized: "identity.card.fingerprint",
|
||||
defaultValue: "noise fingerprint: %@",
|
||||
comment: "Fingerprint line on the public identity card; %@ is the hex fingerprint"
|
||||
),
|
||||
locale: .current,
|
||||
noiseFingerprint
|
||||
)
|
||||
]
|
||||
if let npub, !npub.isEmpty {
|
||||
lines.append(
|
||||
String(
|
||||
format: String(
|
||||
localized: "identity.card.npub",
|
||||
defaultValue: "npub: %@",
|
||||
comment: "Nostr npub line on the public identity card; %@ is the npub"
|
||||
),
|
||||
locale: .current,
|
||||
npub
|
||||
)
|
||||
)
|
||||
}
|
||||
return lines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
static func current(nickname: String, npub: String?, noiseFingerprint: String) -> IdentityVerificationCard {
|
||||
IdentityVerificationCard(
|
||||
nickname: nickname,
|
||||
npub: npub,
|
||||
noiseFingerprint: noiseFingerprint
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -13393,6 +13393,936 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"identity.card.copy" : {
|
||||
"comment" : "Button that copies the public identity verification card as plain text",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "نسخ بطاقة الهوية"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "পরিচয় কার্ড কপি করুন"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identitätskarte kopieren"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "copy identity card"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "copiar tarjeta de identidad"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "کپی کارت هویت"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "kopyahin ang identity card"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "copier la carte d'identité"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "העתקת כרטיס הזהות"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "पहचान कार्ड कॉपी करें"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "salin kartu identitas"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "copia scheda identità"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "身元カードをコピー"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "신원 카드 복사"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "salin kad identiti"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "परिचय कार्ड प्रतिलिपि गर्नुहोस्"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identiteitskaart kopiëren"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "kopiuj kartę tożsamości"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "copiar cartão de identidade"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "copiar cartão de identidade"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "копировать карточку личности"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "kopiera identitetskortet"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "அடையாள அட்டையை நகலெடு"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "คัดลอกบัตรประจำตัว"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "kimlik kartını kopyala"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "копіювати картку особи"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "شناختی کارڈ کاپی کریں"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "sao chép thẻ danh tính"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "复制身份卡"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "複製身分卡"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"identity.card.fingerprint" : {
|
||||
"comment" : "Fingerprint line on the public identity card; %@ is the hex fingerprint",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "بصمة noise: %@"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise ফিঙ্গারপ্রিন্ট: %@"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise-fingerabdruck: %@"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise fingerprint: %@"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "huella noise: %@"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "اثر انگشت noise: %@"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "fingerprint ng noise: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "empreinte noise : %@"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "טביעת אצבע noise: %@"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise फ़िंगरप्रिंट: %@"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "sidik jari noise: %@"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "impronta noise: %@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise フィンガープリント:%@"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise 지문: %@"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "cap jari noise: %@"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise फिंगरप्रिन्ट: %@"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise-vingerafdruk: %@"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "odcisk noise: %@"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "impressão digital noise: %@"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "impressão digital noise: %@"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "отпечаток noise: %@"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise-fingeravtryck: %@"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise கைரேகை: %@"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ลายนิ้วมือ noise: %@"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise parmak izi: %@"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "відбиток noise: %@"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise فنگر پرنٹ: %@"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "dấu vân tay noise: %@"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise 指纹:%@"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "noise 指紋:%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"identity.card.nickname" : {
|
||||
"comment" : "Nickname line on the public identity card; %@ is the nickname",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "الاسم المستعار: %@"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ডাকনাম: %@"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "spitzname: %@"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "nickname: %@"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "apodo: %@"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "نام مستعار: %@"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "palayaw: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "pseudo : %@"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "כינוי: %@"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "उपनाम: %@"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "nama panggilan: %@"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "soprannome: %@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ニックネーム:%@"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "닉네임: %@"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "nama panggilan: %@"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "उपनाम: %@"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bijnaam: %@"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "pseudonim: %@"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "apelido: %@"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "apelido: %@"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "псевдоним: %@"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "smeknamn: %@"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "புனைப்பெயர்: %@"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ชื่อเล่น: %@"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "takma ad: %@"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "псевдонім: %@"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "عرفی نام: %@"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "biệt danh: %@"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "昵称:%@"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "暱稱:%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"identity.card.npub" : {
|
||||
"comment" : "Nostr npub line on the public identity card; %@ is the npub",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub : %@"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub:%@"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub: %@"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub:%@"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "npub:%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"identity.card.title" : {
|
||||
"comment" : "Title line when exporting a public identity verification card",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "بطاقة هوية bitchat"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat পরিচয় কার্ড"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat-identitätskarte"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat identity card"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "tarjeta de identidad de bitchat"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "کارت هویت bitchat"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identity card ng bitchat"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "carte d'identité bitchat"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "כרטיס זהות של bitchat"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat पहचान कार्ड"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "kartu identitas bitchat"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "scheda identità bitchat"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat 身元カード"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat 신원 카드"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "kad identiti bitchat"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat परिचय कार्ड"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat-identiteitskaart"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "karta tożsamości bitchat"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "cartão de identidade bitchat"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "cartão de identidade do bitchat"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "карточка личности bitchat"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat-identitetskort"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat அடையாள அட்டை"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "บัตรประจำตัว bitchat"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat kimlik kartı"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "картка особи bitchat"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat شناختی کارڈ"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "thẻ danh tính bitchat"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat 身份卡"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "bitchat 身分卡"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"image_picker.none_selected" : {
|
||||
"comment" : "Placeholder shown in the image picker before a selection",
|
||||
"extractionState" : "manual",
|
||||
|
||||
@ -460,6 +460,17 @@ struct VerificationSheetView: View {
|
||||
|
||||
// Centered controls moved up
|
||||
VStack(spacing: 10) {
|
||||
if !showingScanner {
|
||||
Button(action: copyIdentityVerificationCard) {
|
||||
Label(
|
||||
String(localized: "identity.card.copy", defaultValue: "copy identity card", comment: "Button that copies the public identity verification card as plain text"),
|
||||
systemImage: "doc.on.doc"
|
||||
)
|
||||
.bitchatFont(size: 13)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
|
||||
if showingScanner {
|
||||
Button(action: { showingScanner = false }) {
|
||||
Label("show my qr", systemImage: "qrcode")
|
||||
@ -492,4 +503,15 @@ struct VerificationSheetView: View {
|
||||
.themedSheetBackground()
|
||||
.onDisappear { showingScanner = false }
|
||||
}
|
||||
|
||||
private func copyIdentityVerificationCard() {
|
||||
let text = verificationModel.identityVerificationCard().plainText
|
||||
#if os(iOS)
|
||||
UIPasteboard.general.string = text
|
||||
#else
|
||||
let pb = NSPasteboard.general
|
||||
pb.clearContents()
|
||||
pb.setString(text, forType: .string)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
69
bitchatTests/IdentityVerificationCardTests.swift
Normal file
69
bitchatTests/IdentityVerificationCardTests.swift
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// IdentityVerificationCardTests.swift
|
||||
// bitchatTests
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct IdentityVerificationCardTests {
|
||||
@Test func plainTextIncludesPublicFieldsOnly() {
|
||||
let card = IdentityVerificationCard.current(
|
||||
nickname: "alice",
|
||||
npub: "npub1test",
|
||||
noiseFingerprint: "abcd1234"
|
||||
)
|
||||
let text = card.plainText
|
||||
#expect(text.contains("alice"))
|
||||
#expect(text.contains("npub1test"))
|
||||
#expect(text.contains("abcd1234"))
|
||||
#expect(!text.lowercased().contains("private key"))
|
||||
#expect(!text.lowercased().contains("seed"))
|
||||
}
|
||||
|
||||
/// The signed QR URL is deliberately absent. `verifyScannedQR` rejects a
|
||||
/// payload older than `verificationQRMaxAgeSeconds` (5 minutes), so a URL
|
||||
/// on a card people keep and forward would stop verifying almost at once
|
||||
/// while still reading as the card's most authoritative line.
|
||||
@Test func plainTextOmitsTheExpiringVerificationURL() {
|
||||
let text = IdentityVerificationCard.current(
|
||||
nickname: "alice",
|
||||
npub: "npub1test",
|
||||
noiseFingerprint: "abcd1234"
|
||||
).plainText
|
||||
#expect(!text.contains("bitchat://"))
|
||||
#expect(!text.lowercased().contains("scan to verify"))
|
||||
}
|
||||
|
||||
/// No npub yet means no npub line — not an empty one.
|
||||
@Test func plainTextOmitsTheNpubLineWhenThereIsNoNpub() {
|
||||
let withNpub = IdentityVerificationCard.current(
|
||||
nickname: "alice",
|
||||
npub: "npub1test",
|
||||
noiseFingerprint: "abcd1234"
|
||||
).plainText
|
||||
let withoutNpub = IdentityVerificationCard.current(
|
||||
nickname: "alice",
|
||||
npub: nil,
|
||||
noiseFingerprint: "abcd1234"
|
||||
).plainText
|
||||
#expect(withNpub.contains("npub1test"))
|
||||
#expect(!withoutNpub.contains("npub"))
|
||||
#expect(withoutNpub.contains("alice"))
|
||||
#expect(withoutNpub.contains("abcd1234"))
|
||||
}
|
||||
|
||||
/// An empty npub string is the same as none — an empty line would look
|
||||
/// like a missing key rather than an absent identity.
|
||||
@Test func plainTextTreatsAnEmptyNpubAsAbsent() {
|
||||
let text = IdentityVerificationCard.current(
|
||||
nickname: "alice",
|
||||
npub: "",
|
||||
noiseFingerprint: "abcd1234"
|
||||
).plainText
|
||||
#expect(!text.contains("npub"))
|
||||
}
|
||||
}
|
||||
31
docs/IDENTITY-VERIFICATION-CARD.md
Normal file
31
docs/IDENTITY-VERIFICATION-CARD.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Identity verification card export (#183)
|
||||
|
||||
## Scope (this PR)
|
||||
|
||||
Public **identity verification card** export only:
|
||||
|
||||
- Nickname
|
||||
- Noise fingerprint
|
||||
- Nostr npub (when available)
|
||||
|
||||
Available from the verification sheet via **copy identity card**.
|
||||
|
||||
## Why the signed QR URL is not on the card
|
||||
|
||||
The verification sheet's QR encodes a signed `bitchat://verify?...` URL, and it
|
||||
would be the natural thing to put on a text card. It is deliberately left off:
|
||||
`VerificationService.verifyScannedQR` rejects a payload older than
|
||||
`TransportConfig.verificationQRMaxAgeSeconds` (5 minutes), so a URL copied onto
|
||||
a card people keep and forward would stop verifying almost immediately — while
|
||||
still reading as the most authoritative line on it.
|
||||
|
||||
The fingerprint and npub have no expiry, which is what makes them the right
|
||||
fields for an out-of-band card. Scan the QR for a live, signed exchange; use the
|
||||
card when the other person is not in front of a screen.
|
||||
|
||||
## Out of scope (future)
|
||||
|
||||
- Passphrase-sealed Noise identity backup / restore (see open PR #1520)
|
||||
- Private key or seed export in any form
|
||||
|
||||
The card gives people a safe, shareable way to confirm who they are out-of-band before marking a fingerprint verified.
|
||||
Loading…
x
Reference in New Issue
Block a user