Files
damus/damus/Core/Nostr/Keys.swift
ericholguin 65a22813a3 refactor: Adding structure
Huge refactor to add better structure to the project.
Separating features with their associated view and model structure.
This should be better organization and will allow us to improve the
overall architecture in the future.

I forsee many more improvements that can follow this change. e.g. MVVM Arch
As well as cleaning up duplicate, unused, functionality.
Many files have global functions that can also be moved or be renamed.

damus/
├── Features/
│   ├── <Feature>/
│   │   ├── Views/
│   │   └── Models/
├── Shared/
│   ├── Components/
│   ├── Media/
│   ├── Buttons/
│   ├── Extensions/
│   ├── Empty Views/
│   ├── ErrorHandling/
│   ├── Modifiers/
│   └── Utilities/
├── Core/
│   ├── Nostr/
│   ├── NIPs/
│   ├── DIPs/
│   ├── Types/
│   ├── Networking/
│   └── Storage/

Signed-off-by: ericholguin <ericholguin@apache.org>
2025-08-06 10:24:00 -07:00

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")
}