Swift Platform Guide

This guide covers the installation, setup, and usage of MetaMUI Crypto Primitives in Swift projects for iOS, macOS, watchOS, and tvOS.

Installation

Swift Package Manager

Add MetaMUI to your Package.swift:

// swift-tools-version:5.7
import PackageDescription

let package = Package(
    name: "YourProject",
    platforms: [
        .iOS(.v13),
        .macOS(.v10_15),
        .watchOS(.v6),
        .tvOS(.v13)
    ],
    dependencies: [
        .package(url: "https://github.com/metamui/crypto-swift.git", from: "0.1.0")
    ],
    targets: [
        .target(
            name: "YourTarget",
            dependencies: [
                .product(name: "MetaMUICrypto", package: "crypto-swift")
            ]
        )
    ]
)

Xcode Integration

  1. File → Add Package Dependencies
  2. Enter: https://github.com/metamui/crypto-swift.git
  3. Choose version rule: “Up to Next Major Version”
  4. Select the algorithms you need

CocoaPods

pod 'MetaMUICrypto', '~> 0.1.0'

# Or specific algorithms
pod 'MetaMUICrypto/Ed25519'
pod 'MetaMUICrypto/ChaCha20'
pod 'MetaMUICrypto/PostQuantum'

Quick Start

import MetaMUICrypto

// Generate Ed25519 keypair
let keypair = try Ed25519.generateKeypair()

// Sign a message
let message = "Hello, MetaMUI!".data(using: .utf8)!
let signature = try Ed25519.sign(message, privateKey: keypair.privateKey)

// Verify signature
let isValid = try Ed25519.verify(signature, message: message, publicKey: keypair.publicKey)
print("Signature valid: \(isValid)")

// Encrypt with ChaCha20-Poly1305
let key = ChaCha20Poly1305.generateKey()
let cipher = ChaCha20Poly1305(key: key)

let plaintext = "Secret message".data(using: .utf8)!
let nonce = ChaCha20Poly1305.generateNonce()

let encrypted = try cipher.encrypt(plaintext, nonce: nonce)

// Decrypt
let decrypted = try cipher.decrypt(encrypted.ciphertext, tag: encrypted.tag, nonce: nonce)
print("Decrypted: \(String(data: decrypted, encoding: .utf8)!)")

iOS/macOS Integration

Keychain Storage

import MetaMUICrypto
import Security

extension Ed25519.Keypair {
    func saveToKeychain(identifier: String) throws {
        // Save private key
        let privateKeyData = privateKey.rawRepresentation
        
        let privateQuery: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: "\(identifier).private".data(using: .utf8)!,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecValueData as String: privateKeyData,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]
        
        var status = SecItemAdd(privateQuery as CFDictionary, nil)
        if status == errSecDuplicateItem {
            SecItemDelete(privateQuery as CFDictionary)
            status = SecItemAdd(privateQuery as CFDictionary, nil)
        }
        
        guard status == errSecSuccess else {
            throw KeychainError.saveFailed(status)
        }
        
        // Save public key similarly
        let publicKeyData = publicKey.rawRepresentation
        let publicQuery: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: "\(identifier).public".data(using: .utf8)!,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecValueData as String: publicKeyData
        ]
        
        SecItemAdd(publicQuery as CFDictionary, nil)
    }
    
    static func loadFromKeychain(identifier: String) throws -> Ed25519.Keypair {
        // Load private key
        let privateQuery: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: "\(identifier).private".data(using: .utf8)!,
            kSecReturnData as String: true
        ]
        
        var item: CFTypeRef?
        let status = SecItemCopyMatching(privateQuery as CFDictionary, &item)
        
        guard status == errSecSuccess,
              let privateKeyData = item as? Data else {
            throw KeychainError.loadFailed(status)
        }
        
        let privateKey = try Ed25519.PrivateKey(rawRepresentation: privateKeyData)
        return Ed25519.Keypair(privateKey: privateKey)
    }
}

Biometric Authentication

import LocalAuthentication
import MetaMUICrypto

class BiometricCrypto {
    private let context = LAContext()
    
    func encryptWithBiometrics(data: Data, completion: @escaping (Result<EncryptedData, Error>) -> Void) {
        let reason = "Authenticate to encrypt data"
        
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in
            if success {
                do {
                    // Generate key after authentication
                    let key = ChaCha20Poly1305.generateKey()
                    let cipher = ChaCha20Poly1305(key: key)
                    let nonce = ChaCha20Poly1305.generateNonce()
                    
                    let encrypted = try cipher.encrypt(data, nonce: nonce)
                    
                    // Store key in keychain with biometric protection
                    self.storeKeyWithBiometricProtection(key, identifier: "biometric_key")
                    
                    completion(.success(EncryptedData(
                        ciphertext: encrypted.ciphertext,
                        tag: encrypted.tag,
                        nonce: nonce
                    )))
                } catch {
                    completion(.failure(error))
                }
            } else {
                completion(.failure(error ?? BiometricError.authenticationFailed))
            }
        }
    }
}

SwiftUI Integration

Crypto View Model

import SwiftUI
import MetaMUICrypto
import Combine

@MainActor
class CryptoViewModel: ObservableObject {
    @Published var publicKey: String = ""
    @Published var isLoading = false
    @Published var error: Error?
    
    private var keypair: Ed25519.Keypair?
    
    func generateKeypair() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            keypair = try await Task.detached {
                try Ed25519.generateKeypair()
            }.value
            
            publicKey = keypair!.publicKey.rawRepresentation.base64EncodedString()
        } catch {
            self.error = error
        }
    }
    
    func signMessage(_ message: String) async -> String? {
        guard let keypair = keypair,
              let messageData = message.data(using: .utf8) else {
            return nil
        }
        
        do {
            let signature = try await Task.detached {
                try Ed25519.sign(messageData, privateKey: keypair.privateKey)
            }.value
            
            return signature.base64EncodedString()
        } catch {
            self.error = error
            return nil
        }
    }
}

struct CryptoView: View {
    @StateObject private var viewModel = CryptoViewModel()
    @State private var message = ""
    @State private var signature = ""
    
    var body: some View {
        VStack(spacing: 20) {
            if viewModel.isLoading {
                ProgressView()
            } else {
                Button("Generate Keypair") {
                    Task {
                        await viewModel.generateKeypair()
                    }
                }
                
                if !viewModel.publicKey.isEmpty {
                    Text("Public Key:")
                        .font(.headline)
                    Text(viewModel.publicKey)
                        .font(.system(.caption, design: .monospaced))
                        .padding()
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                    
                    TextField("Enter message", text: $message)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    Button("Sign Message") {
                        Task {
                            if let sig = await viewModel.signMessage(message) {
                                signature = sig
                            }
                        }
                    }
                    .disabled(message.isEmpty)
                    
                    if !signature.isEmpty {
                        Text("Signature:")
                            .font(.headline)
                        Text(signature)
                            .font(.system(.caption, design: .monospaced))
                            .padding()
                            .background(Color.green.opacity(0.1))
                            .cornerRadius(8)
                    }
                }
            }
        }
        .padding()
        .alert("Error", isPresented: .constant(viewModel.error != nil)) {
            Button("OK") {
                viewModel.error = nil
            }
        } message: {
            Text(viewModel.error?.localizedDescription ?? "")
        }
    }
}

Async/Await Support

import MetaMUICrypto

// Async key generation
func generateKeysAsync() async throws -> Ed25519.Keypair {
    try await withCheckedThrowingContinuation { continuation in
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                let keypair = try Ed25519.generateKeypair()
                continuation.resume(returning: keypair)
            } catch {
                continuation.resume(throwing: error)
            }
        }
    }
}

// Async encryption with progress
func encryptLargeFile(at url: URL, key: Data) async throws -> URL {
    let cipher = ChaCha20Poly1305(key: key)
    let inputHandle = try FileHandle(forReadingFrom: url)
    defer { inputHandle.closeFile() }
    
    let outputURL = url.appendingPathExtension("encrypted")
    FileManager.default.createFile(atPath: outputURL.path, contents: nil)
    let outputHandle = try FileHandle(forWritingTo: outputURL)
    defer { outputHandle.closeFile() }
    
    let chunkSize = 64 * 1024 // 64KB chunks
    var offset: UInt64 = 0
    
    while true {
        let chunk = try await withCheckedThrowingContinuation { continuation in
            DispatchQueue.global().async {
                inputHandle.seek(toFileOffset: offset)
                let data = inputHandle.readData(ofLength: chunkSize)
                continuation.resume(returning: data)
            }
        }
        
        if chunk.isEmpty { break }
        
        let nonce = ChaCha20Poly1305.generateNonce()
        let encrypted = try cipher.encrypt(chunk, nonce: nonce)
        
        // Write nonce + tag + ciphertext
        outputHandle.write(nonce)
        outputHandle.write(encrypted.tag)
        outputHandle.write(encrypted.ciphertext)
        
        offset += UInt64(chunk.count)
    }
    
    return outputURL
}

Combine Integration

import Combine
import MetaMUICrypto

class CryptoService {
    enum CryptoError: Error {
        case encryptionFailed
        case decryptionFailed
        case keyGenerationFailed
    }
    
    func generateKeypairPublisher() -> AnyPublisher<Ed25519.Keypair, Error> {
        Future { promise in
            DispatchQueue.global(qos: .userInitiated).async {
                do {
                    let keypair = try Ed25519.generateKeypair()
                    promise(.success(keypair))
                } catch {
                    promise(.failure(CryptoError.keyGenerationFailed))
                }
            }
        }
        .eraseToAnyPublisher()
    }
    
    func encryptPublisher(data: Data, key: Data) -> AnyPublisher<EncryptedData, Error> {
        Future { promise in
            do {
                let cipher = ChaCha20Poly1305(key: key)
                let nonce = ChaCha20Poly1305.generateNonce()
                let encrypted = try cipher.encrypt(data, nonce: nonce)
                
                promise(.success(EncryptedData(
                    ciphertext: encrypted.ciphertext,
                    tag: encrypted.tag,
                    nonce: nonce
                )))
            } catch {
                promise(.failure(CryptoError.encryptionFailed))
            }
        }
        .subscribe(on: DispatchQueue.global(qos: .userInitiated))
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
    }
}

// Usage with Combine
class ViewModel: ObservableObject {
    @Published var encryptedData: EncryptedData?
    private var cancellables = Set<AnyCancellable>()
    private let cryptoService = CryptoService()
    
    func encryptMessage(_ message: String) {
        guard let data = message.data(using: .utf8) else { return }
        let key = ChaCha20Poly1305.generateKey()
        
        cryptoService.encryptPublisher(data: data, key: key)
            .sink(
                receiveCompletion: { completion in
                    if case .failure(let error) = completion {
                        print("Encryption failed: \(error)")
                    }
                },
                receiveValue: { [weak self] encrypted in
                    self?.encryptedData = encrypted
                }
            )
            .store(in: &cancellables)
    }
}

Performance Optimization

Batch Operations

import MetaMUICrypto

extension Ed25519 {
    static func batchSign(messages: [Data], privateKey: PrivateKey) throws -> [Data] {
        try messages.map { message in
            try sign(message, privateKey: privateKey)
        }
    }
    
    static func batchVerifyParallel(
        signatures: [Data],
        messages: [Data],
        publicKeys: [PublicKey]
    ) async throws -> [Bool] {
        try await withThrowingTaskGroup(of: (Int, Bool).self) { group in
            for (index, (signature, message, publicKey)) in zip(signatures, messages, publicKeys).enumerated() {
                group.addTask {
                    let isValid = try Ed25519.verify(signature, message: message, publicKey: publicKey)
                    return (index, isValid)
                }
            }
            
            var results = Array(repeating: false, count: signatures.count)
            for try await (index, isValid) in group {
                results[index] = isValid
            }
            
            return results
        }
    }
}

Memory Management

import MetaMUICrypto

class SecureData {
    private var data: Data
    
    init(_ data: Data) {
        self.data = data
    }
    
    deinit {
        // Clear sensitive data from memory
        data.withUnsafeMutableBytes { bytes in
            memset_s(bytes.baseAddress, bytes.count, 0, bytes.count)
        }
    }
    
    func withData<T>(_ body: (Data) throws -> T) rethrows -> T {
        try body(data)
    }
}

// Usage
func processSecretKey() {
    let secretKey = SecureData(ChaCha20Poly1305.generateKey())
    
    secretKey.withData { key in
        let cipher = ChaCha20Poly1305(key: key)
        // Use cipher...
    }
    // Key is automatically cleared when SecureData is deallocated
}

Testing

XCTest Integration

import XCTest
import MetaMUICrypto

final class CryptoTests: XCTestCase {
    func testEd25519SignatureRoundtrip() throws {
        // Given
        let keypair = try Ed25519.generateKeypair()
        let message = "Test message".data(using: .utf8)!
        
        // When
        let signature = try Ed25519.sign(message, privateKey: keypair.privateKey)
        
        // Then
        XCTAssertTrue(try Ed25519.verify(signature, message: message, publicKey: keypair.publicKey))
    }
    
    func testChaCha20Poly1305Encryption() throws {
        // Given
        let key = ChaCha20Poly1305.generateKey()
        let cipher = ChaCha20Poly1305(key: key)
        let plaintext = "Secret data".data(using: .utf8)!
        let nonce = ChaCha20Poly1305.generateNonce()
        let aad = "metadata".data(using: .utf8)!
        
        // When
        let encrypted = try cipher.encrypt(plaintext, nonce: nonce, associatedData: aad)
        let decrypted = try cipher.decrypt(encrypted.ciphertext, tag: encrypted.tag, nonce: nonce, associatedData: aad)
        
        // Then
        XCTAssertEqual(decrypted, plaintext)
    }
    
    func testPerformance() throws {
        let keypair = try Ed25519.generateKeypair()
        let message = Data(repeating: 0, count: 1024)
        
        measure {
            _ = try? Ed25519.sign(message, privateKey: keypair.privateKey)
        }
    }
}

Test Utilities

extension Data {
    static func random(count: Int) -> Data {
        var data = Data(count: count)
        _ = data.withUnsafeMutableBytes { bytes in
            SecRandomCopyBytes(kSecRandomDefault, count, bytes.baseAddress!)
        }
        return data
    }
}

extension XCTestCase {
    func assertCryptoEqual(_ expression1: @autoclosure () throws -> Data,
                          _ expression2: @autoclosure () throws -> Data,
                          file: StaticString = #filePath,
                          line: UInt = #line) {
        do {
            let data1 = try expression1()
            let data2 = try expression2()
            
            // Constant-time comparison
            guard data1.count == data2.count else {
                XCTFail("Data lengths don't match", file: file, line: line)
                return
            }
            
            var equal = true
            for (byte1, byte2) in zip(data1, data2) {
                equal = equal && (byte1 == byte2)
            }
            
            XCTAssertTrue(equal, "Crypto data not equal", file: file, line: line)
        } catch {
            XCTFail("Threw error: \(error)", file: file, line: line)
        }
    }
}

Common Patterns

Protocol-Oriented Crypto

protocol CryptoProvider {
    associatedtype PublicKey
    associatedtype PrivateKey
    associatedtype Signature
    
    static func generateKeypair() throws -> (PublicKey, PrivateKey)
    static func sign(_ message: Data, privateKey: PrivateKey) throws -> Signature
    static func verify(_ signature: Signature, message: Data, publicKey: PublicKey) throws -> Bool
}

extension Ed25519: CryptoProvider {
    typealias Signature = Data
}

// Generic crypto operations
func performCryptoOperation<T: CryptoProvider>(using provider: T.Type, message: Data) throws {
    let (publicKey, privateKey) = try provider.generateKeypair()
    let signature = try provider.sign(message, privateKey: privateKey)
    let isValid = try provider.verify(signature, message: message, publicKey: publicKey)
    print("Signature valid: \(isValid)")
}

Error Handling

enum CryptoOperationError: LocalizedError {
    case invalidKeySize
    case encryptionFailed(underlying: Error)
    case decryptionFailed(underlying: Error)
    case verificationFailed
    
    var errorDescription: String? {
        switch self {
        case .invalidKeySize:
            return "Invalid key size provided"
        case .encryptionFailed(let error):
            return "Encryption failed: \(error.localizedDescription)"
        case .decryptionFailed(let error):
            return "Decryption failed: \(error.localizedDescription)"
        case .verificationFailed:
            return "Signature verification failed"
        }
    }
}

// Usage
func encryptSafely(data: Data, key: Data) -> Result<EncryptedData, CryptoOperationError> {
    guard key.count == 32 else {
        return .failure(.invalidKeySize)
    }
    
    do {
        let cipher = ChaCha20Poly1305(key: key)
        let nonce = ChaCha20Poly1305.generateNonce()
        let encrypted = try cipher.encrypt(data, nonce: nonce)
        return .success(EncryptedData(ciphertext: encrypted.ciphertext, tag: encrypted.tag, nonce: nonce))
    } catch {
        return .failure(.encryptionFailed(underlying: error))
    }
}

Best Practices

  1. Use Keychain for key storage on iOS/macOS
  2. Enable compiler optimizations for crypto code
  3. Clear sensitive data from memory after use
  4. Use async/await for CPU-intensive operations
  5. Validate input sizes before crypto operations
  6. Handle errors gracefully with proper error types
  7. Test with release builds for accurate performance

Troubleshooting

Common Issues

  1. Keychain Access Errors
    // Add entitlements for keychain access
    // Enable "Keychain Sharing" capability in Xcode
    
  2. Performance Issues
    // Use release builds for testing
    // Enable "Whole Module Optimization"
    // Profile with Instruments
    
  3. Memory Warnings
    // Process large files in chunks
    // Use autoreleasepool for loops
    autoreleasepool {
        // Crypto operations
    }
    

Resources