damus

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

Keys.swift (5539B)


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