damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

Keys.swift (5233B)


      1 //
      2 //  Keys.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2022-05-21.
      6 //
      7 
      8 import Foundation
      9 import secp256k1
     10 
     11 let PUBKEY_HRP = "npub"
     12 
     13 // some random pubkey
     14 let ANON_PUBKEY = Pubkey(Data([
     15     0x85, 0x41, 0x5d, 0x63, 0x5c, 0x2b, 0xaf, 0x55,
     16     0xf5, 0xb9, 0xa1, 0xa6, 0xce, 0xb7, 0x75, 0xcc,
     17     0x5c, 0x45, 0x4a, 0x3a, 0x61, 0xb5, 0x3f, 0xe8,
     18     0x50, 0x42, 0xdc, 0x42, 0xac, 0xe1, 0x7f, 0x12
     19 ]))
     20 
     21 struct FullKeypair: Equatable {
     22     let pubkey: Pubkey
     23     let privkey: Privkey
     24 
     25     func to_keypair() -> Keypair {
     26         return Keypair(pubkey: pubkey, privkey: privkey)
     27     }
     28 }
     29 
     30 struct Keypair {
     31     let pubkey: Pubkey
     32     let privkey: Privkey?
     33     //let pubkey_bech32: String
     34     //let privkey_bech32: String?
     35 
     36     static var empty: Keypair {
     37         Keypair(pubkey: .empty, privkey: nil)
     38     }
     39 
     40     func to_full() -> FullKeypair? {
     41         guard let privkey = self.privkey else {
     42             return nil
     43         }
     44         
     45         return FullKeypair(pubkey: pubkey, privkey: privkey)
     46     }
     47 
     48     static func just_pubkey(_ pk: Pubkey) -> Keypair {
     49         return .init(pubkey: pk, privkey: nil)
     50     }
     51 
     52     init(pubkey: Pubkey, privkey: Privkey?) {
     53         self.pubkey = pubkey
     54         self.privkey = privkey
     55         //self.pubkey_bech32 = pubkey.npub
     56         //self.privkey_bech32 = privkey?.nsec
     57     }
     58 }
     59 
     60 enum Bech32Key {
     61     case pub(Pubkey)
     62     case sec(Privkey)
     63 }
     64 
     65 func decode_bech32_key(_ key: String) -> Bech32Key? {
     66     guard let decoded = try? bech32_decode(key),
     67           decoded.data.count == 32
     68     else {
     69         return nil
     70     }
     71 
     72     if decoded.hrp == "npub" {
     73         return .pub(Pubkey(decoded.data))
     74     } else if decoded.hrp == "nsec" {
     75         return .sec(Privkey(decoded.data))
     76     }
     77     
     78     return nil
     79 }
     80 
     81 func bech32_privkey(_ privkey: Privkey) -> String {
     82     return bech32_encode(hrp: "nsec", privkey.bytes)
     83 }
     84 
     85 func bech32_pubkey(_ pubkey: Pubkey) -> String {
     86     return bech32_encode(hrp: "npub", pubkey.bytes)
     87 }
     88 
     89 func bech32_pubkey_decode(_ pubkey: String) -> Pubkey? {
     90     guard let decoded = try? bech32_decode(pubkey),
     91           decoded.hrp == "npub",
     92           decoded.data.count == 32
     93     else {
     94         return nil
     95     }
     96 
     97     return Pubkey(decoded.data)
     98 }
     99 
    100 func bech32_nopre_pubkey(_ pubkey: Pubkey) -> String {
    101     return bech32_encode(hrp: "", pubkey.bytes)
    102 }
    103 
    104 func bech32_note_id(_ evid: NoteId) -> String {
    105     return bech32_encode(hrp: "note", evid.bytes)
    106 }
    107 
    108 func generate_new_keypair() -> FullKeypair {
    109     let key = try! secp256k1.Signing.PrivateKey()
    110     let privkey = Privkey(key.rawRepresentation)
    111     let pubkey = Pubkey(Data(key.publicKey.xonly.bytes))
    112     return FullKeypair(pubkey: pubkey, privkey: privkey)
    113 }
    114 
    115 func privkey_to_pubkey_raw(sec: [UInt8]) -> Pubkey? {
    116     guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else {
    117         return nil
    118     }
    119     return Pubkey(Data(key.publicKey.xonly.bytes))
    120 }
    121 
    122 func privkey_to_pubkey(privkey: Privkey) -> Pubkey? {
    123     return privkey_to_pubkey_raw(sec: privkey.bytes)
    124 }
    125 
    126 func save_pubkey(pubkey: Pubkey) {
    127     DamusUserDefaults.standard.set(pubkey.hex(), forKey: "pubkey")
    128 }
    129 
    130 enum Keys {
    131     @KeychainStorage(account: "privkey")
    132     static var privkey: String?
    133 }
    134 
    135 func save_privkey(privkey: Privkey) throws {
    136     Keys.privkey = privkey.hex()
    137 }
    138 
    139 func clear_saved_privkey() throws {
    140     Keys.privkey = nil
    141 }
    142 
    143 func clear_saved_pubkey() {
    144     DamusUserDefaults.standard.removeObject(forKey: "pubkey")
    145 }
    146 
    147 func save_keypair(pubkey: Pubkey, privkey: Privkey) throws {
    148     save_pubkey(pubkey: pubkey)
    149     try save_privkey(privkey: privkey)
    150 }
    151 
    152 func clear_keypair() throws {
    153     try clear_saved_privkey()
    154     clear_saved_pubkey()
    155 }
    156 
    157 func get_saved_keypair() -> Keypair? {
    158     do {
    159         try removePrivateKeyFromUserDefaults()
    160 
    161         guard let pubkey = get_saved_pubkey(),
    162               let pk = hex_decode(pubkey)
    163         else {
    164             return nil
    165         }
    166 
    167         let privkey = get_saved_privkey().flatMap { sec in
    168             hex_decode(sec).map { Privkey(Data($0)) }
    169         }
    170 
    171         return Keypair(pubkey: Pubkey(Data(pk)), privkey: privkey)
    172     } catch {
    173         return nil
    174     }
    175 }
    176 
    177 func get_saved_pubkey() -> String? {
    178     return DamusUserDefaults.standard.string(forKey: "pubkey")
    179 }
    180 
    181 func get_saved_privkey() -> String? {
    182     let mkey = Keys.privkey
    183     return mkey.map { $0.trimmingCharacters(in: .whitespaces) }
    184 }
    185 
    186 /**
    187  Detects whether a string might contain an nsec1 prefixed private key.
    188  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.
    189  */
    190 func contentContainsPrivateKey(_ content: String) -> Bool {
    191     if #available(iOS 16.0, *) {
    192         return content.contains(/nsec1[02-9ac-z]+/)
    193     } else {
    194         let regex = try! NSRegularExpression(pattern: "nsec1[02-9ac-z]+")
    195         return (regex.firstMatch(in: content, range: NSRange(location: 0, length: content.count)) != nil)
    196     }
    197 
    198 }
    199 
    200 fileprivate func removePrivateKeyFromUserDefaults() throws {
    201     guard let privkey_str = DamusUserDefaults.standard.string(forKey: "privkey"),
    202           let privkey = hex_decode_privkey(privkey_str)
    203     else { return }
    204 
    205     try save_privkey(privkey: privkey)
    206     DamusUserDefaults.standard.removeObject(forKey: "privkey")
    207 }