Files
damus/damus/Util/Keys.swift
Daniel D’Aquino 54674104ea Change DamusUserDefaults to mirror settings from app container
... to shared container instead of migrating

This commit is a reimplementation of DamusUserDefaults that mirrors
settings from the app to the shared container (instead of migrating
values over).

This new implementation brings the benefit of being backwards compatible
with the user's settings. That is, even if the user upgrades or
downgrades between various versions and changes settings along the way,
the main settings in the app will stay consistent between Damus versions
— that is, changes to the settings would not be lost between
downgrades/upgrades

General settings test
----------------------

PASS

Device: iPhone 15 Pro simulator
iOS: 17.0.1
Damus: This commit
Setup: A device with non-standard settings
Steps:
1. Flash Damus on the device
2. Check any non-default settings that were there before. Ensure that settings remained the same. PASS
3. Change one setting (any setting) to a non-default value
4. Restart Damus
5. Ensure settings change in step 3 persisted on the device

Notification settings test
--------------------------

PASS

Device: iPhone 15 Pro simulator
iOS: 17.0.1
Damus: This commit
Setup:
- Two phones running Damus on different accounts
- Local relay with strfry-push-notify test setup
- Apple push notification test tool

Coverage:
1. Mention notifications
2. DM notifications
3. Reaction notifications
4. Repost notifications

Steps for each notification type:
1. Use the secondary phone to generate a push notification
2. Trigger the push notification (Send push notification from test tool)
3. Ensure that the notification is received on the other device
4. Turn off notifications for that notification type on settings
5. Trigger the same push notification (Resend push notification from test tool)
6. Ensure that the notification is not received on the other device
7. Turn on notifications for that notification type on settings
8. Trigger the same push notification (Resend from test tool)
9. Ensure that notification appears on the device

Result: PASS (notifications are received when enabled and not received when disabled)
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-10 11:07:01 -08:00

208 lines
5.1 KiB
Swift

//
// Keys.swift
// damus
//
// Created by William Casarin on 2022-05-21.
//
import Foundation
import secp256k1
let PUBKEY_HRP = "npub"
// some random pubkey
let ANON_PUBKEY = Pubkey(Data([
0x85, 0x41, 0x5d, 0x63, 0x5c, 0x2b, 0xaf, 0x55,
0xf5, 0xb9, 0xa1, 0xa6, 0xce, 0xb7, 0x75, 0xcc,
0x5c, 0x45, 0x4a, 0x3a, 0x61, 0xb5, 0x3f, 0xe8,
0x50, 0x42, 0xdc, 0x42, 0xac, 0xe1, 0x7f, 0x12
]))
struct FullKeypair: Equatable {
let pubkey: Pubkey
let privkey: Privkey
func to_keypair() -> Keypair {
return Keypair(pubkey: pubkey, privkey: privkey)
}
}
struct Keypair {
let pubkey: Pubkey
let privkey: Privkey?
//let pubkey_bech32: String
//let privkey_bech32: String?
static var empty: Keypair {
Keypair(pubkey: .empty, privkey: nil)
}
func to_full() -> FullKeypair? {
guard let privkey = self.privkey else {
return nil
}
return FullKeypair(pubkey: pubkey, privkey: privkey)
}
static func just_pubkey(_ pk: Pubkey) -> Keypair {
return .init(pubkey: pk, privkey: nil)
}
init(pubkey: Pubkey, privkey: Privkey?) {
self.pubkey = pubkey
self.privkey = privkey
//self.pubkey_bech32 = pubkey.npub
//self.privkey_bech32 = privkey?.nsec
}
}
enum Bech32Key {
case pub(Pubkey)
case sec(Privkey)
}
func decode_bech32_key(_ key: String) -> Bech32Key? {
guard let decoded = try? bech32_decode(key),
decoded.data.count == 32
else {
return nil
}
if decoded.hrp == "npub" {
return .pub(Pubkey(decoded.data))
} else if decoded.hrp == "nsec" {
return .sec(Privkey(decoded.data))
}
return nil
}
func bech32_privkey(_ privkey: Privkey) -> String {
return bech32_encode(hrp: "nsec", privkey.bytes)
}
func bech32_pubkey(_ pubkey: Pubkey) -> String {
return bech32_encode(hrp: "npub", pubkey.bytes)
}
func bech32_pubkey_decode(_ pubkey: String) -> Pubkey? {
guard let decoded = try? bech32_decode(pubkey),
decoded.hrp == "npub",
decoded.data.count == 32
else {
return nil
}
return Pubkey(decoded.data)
}
func bech32_nopre_pubkey(_ pubkey: Pubkey) -> String {
return bech32_encode(hrp: "", pubkey.bytes)
}
func bech32_note_id(_ evid: NoteId) -> String {
return bech32_encode(hrp: "note", evid.bytes)
}
func generate_new_keypair() -> FullKeypair {
let key = try! secp256k1.Signing.PrivateKey()
let privkey = Privkey(key.rawRepresentation)
let pubkey = Pubkey(Data(key.publicKey.xonly.bytes))
return FullKeypair(pubkey: pubkey, privkey: privkey)
}
func privkey_to_pubkey_raw(sec: [UInt8]) -> Pubkey? {
guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else {
return nil
}
return Pubkey(Data(key.publicKey.xonly.bytes))
}
func privkey_to_pubkey(privkey: Privkey) -> Pubkey? {
return privkey_to_pubkey_raw(sec: privkey.bytes)
}
func save_pubkey(pubkey: Pubkey) {
DamusUserDefaults.standard.set(pubkey.hex(), forKey: "pubkey")
}
enum Keys {
@KeychainStorage(account: "privkey")
static var privkey: String?
}
func save_privkey(privkey: Privkey) throws {
Keys.privkey = privkey.hex()
}
func clear_saved_privkey() throws {
Keys.privkey = nil
}
func clear_saved_pubkey() {
DamusUserDefaults.standard.removeObject(forKey: "pubkey")
}
func save_keypair(pubkey: Pubkey, privkey: Privkey) throws {
save_pubkey(pubkey: pubkey)
try save_privkey(privkey: privkey)
}
func clear_keypair() throws {
try clear_saved_privkey()
clear_saved_pubkey()
}
func get_saved_keypair() -> Keypair? {
do {
try removePrivateKeyFromUserDefaults()
guard let pubkey = get_saved_pubkey(),
let pk = hex_decode(pubkey)
else {
return nil
}
let privkey = get_saved_privkey().flatMap { sec in
hex_decode(sec).map { Privkey(Data($0)) }
}
return Keypair(pubkey: Pubkey(Data(pk)), privkey: privkey)
} catch {
return nil
}
}
func get_saved_pubkey() -> String? {
return DamusUserDefaults.standard.string(forKey: "pubkey")
}
func get_saved_privkey() -> String? {
let mkey = Keys.privkey
return mkey.map { $0.trimmingCharacters(in: .whitespaces) }
}
/**
Detects whether a string might contain an nsec1 prefixed private key.
It does not determine if it's the current user's private key and does not verify if it is properly encoded or has the right length.
*/
func contentContainsPrivateKey(_ content: String) -> Bool {
if #available(iOS 16.0, *) {
return content.contains(/nsec1[02-9ac-z]+/)
} else {
let regex = try! NSRegularExpression(pattern: "nsec1[02-9ac-z]+")
return (regex.firstMatch(in: content, range: NSRange(location: 0, length: content.count)) != nil)
}
}
fileprivate func removePrivateKeyFromUserDefaults() throws {
guard let privkey_str = DamusUserDefaults.standard.string(forKey: "privkey"),
let privkey = hex_decode_privkey(privkey_str)
else { return }
try save_privkey(privkey: privkey)
DamusUserDefaults.standard.removeObject(forKey: "privkey")
}