133 lines
3.2 KiB
Swift
133 lines
3.2 KiB
Swift
import Foundation
|
|
import CryptoKit
|
|
|
|
// MARK: - Data ↔ Hex
|
|
|
|
extension Data {
|
|
/// Convert data to lowercase hex string
|
|
var hexString: String {
|
|
map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
|
|
/// Initialize Data from a hex string
|
|
init?(hexString: String) {
|
|
let hex = hexString.lowercased()
|
|
guard hex.count % 2 == 0 else { return nil }
|
|
var data = Data(capacity: hex.count / 2)
|
|
var index = hex.startIndex
|
|
while index < hex.endIndex {
|
|
let nextIndex = hex.index(index, offsetBy: 2)
|
|
guard let byte = UInt8(hex[index..<nextIndex], radix: 16) else { return nil }
|
|
data.append(byte)
|
|
index = nextIndex
|
|
}
|
|
self = data
|
|
}
|
|
|
|
/// Generate random bytes
|
|
static func randomBytes(_ count: Int) -> Data {
|
|
var data = Data(count: count)
|
|
data.withUnsafeMutableBytes { ptr in
|
|
_ = SecRandomCopyBytes(kSecRandomDefault, count, ptr.baseAddress!)
|
|
}
|
|
return data
|
|
}
|
|
}
|
|
|
|
// MARK: - Data ↔ Base64 (Protocol Wire Format)
|
|
|
|
extension Data {
|
|
/// Encode to standard base64 string (matching Python's base64.b64encode)
|
|
func base64EncodedString() -> String {
|
|
self.base64EncodedString(options: [])
|
|
}
|
|
|
|
/// Decode from base64 string
|
|
static func fromBase64(_ string: String) throws -> Data {
|
|
// Try standard base64 first, then URL-safe
|
|
if let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters) {
|
|
return data
|
|
}
|
|
throw CryptoError.invalidBase64
|
|
}
|
|
}
|
|
|
|
// MARK: - UInt32 Big-Endian
|
|
|
|
extension UInt32 {
|
|
var bigEndianData: Data {
|
|
var value = self.bigEndian
|
|
return Data(bytes: &value, count: 4)
|
|
}
|
|
}
|
|
|
|
// MARK: - CryptoKit Key → Data
|
|
|
|
extension Curve25519.KeyAgreement.PublicKey {
|
|
var rawData: Data {
|
|
Data(rawRepresentation)
|
|
}
|
|
}
|
|
|
|
extension Curve25519.KeyAgreement.PrivateKey {
|
|
var rawData: Data {
|
|
Data(rawRepresentation)
|
|
}
|
|
}
|
|
|
|
extension Curve25519.Signing.PublicKey {
|
|
var rawData: Data {
|
|
Data(rawRepresentation)
|
|
}
|
|
}
|
|
|
|
extension Curve25519.Signing.PrivateKey {
|
|
var rawData: Data {
|
|
Data(rawRepresentation)
|
|
}
|
|
}
|
|
|
|
// MARK: - String helpers
|
|
|
|
extension String {
|
|
/// Trim whitespace and newlines
|
|
var trimmed: String {
|
|
trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
}
|
|
|
|
// MARK: - Dictionary merge helper
|
|
|
|
extension Dictionary where Key == String, Value == Any {
|
|
func string(for key: String) -> String? {
|
|
self[key] as? String
|
|
}
|
|
|
|
func int(for key: String) -> Int? {
|
|
if let i = self[key] as? Int { return i }
|
|
if let s = self[key] as? String, let i = Int(s) { return i }
|
|
return nil
|
|
}
|
|
|
|
func dict(for key: String) -> [String: Any]? {
|
|
self[key] as? [String: Any]
|
|
}
|
|
|
|
func array(for key: String) -> [[String: Any]]? {
|
|
self[key] as? [[String: Any]]
|
|
}
|
|
|
|
func data(for key: String) -> Data? {
|
|
if let hex = self[key] as? String {
|
|
return Data(hexString: hex)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func bool(for key: String) -> Bool? {
|
|
if let b = self[key] as? Bool { return b }
|
|
if let i = self[key] as? Int { return i != 0 }
|
|
return nil
|
|
}
|
|
}
|