a6e123e928
This commit removes rust-nostr dependency, and replaces the NIP-44 logic with a new NIP-44 module based on the Swift NostrSDK implementation. The decision to move away from rust-nostr and the Swift NostrSDK was made for the following reasons: 1. `rust-nostr` caused the app size to double 2. We only need NIP44 functionality, and we don't need to bring everything else 3. The Swift NostrSDK caused conflicts around the secp256k1 dependency that is hard to address 4. The way we do things in the codebase is far different from the Swift NostrSDK, and we optimize it for use with NostrDB. Bringing it an outside library causes significant complexity in integration with NostrDB, and would effectively cause the codebase to be split into two different ways of achieving the same results. Therefore it is cleaner if we stick to our own Nostr structures and functions and focus on maintaining them. However, the library CryptoSwift was added as a dependency, to bring in ChaCha20 which is not supported by CryptoKit (CryptoKit supports the ChaCha20-Poly1305 cipher, but NIP-44 uses ChaCha20 with HMAC-SHA256 instead) Closes: https://github.com/damus-io/damus/issues/2849 Changelog-Changed: Made internal changes to reduce the app binary size Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
219 lines
5.4 KiB
Swift
219 lines
5.4 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
|
|
|
|
init(pubkey: Pubkey, privkey: Privkey) {
|
|
self.pubkey = pubkey
|
|
self.privkey = privkey
|
|
}
|
|
|
|
init?(privkey: Privkey) {
|
|
self.privkey = privkey
|
|
guard let pubkey = privkey_to_pubkey_raw(sec: privkey.bytes) else { return nil }
|
|
self.pubkey = pubkey
|
|
}
|
|
|
|
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")
|
|
}
|