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

Build from source:

cd metamui-crypto-swift
swift build

Running Tests

cd metamui-crypto-swift
swift run TestFalconSwift

Using in Your Project

To use as a local dependency, add a path reference in 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(path: "../metamui-crypto-swift")
    ],
    targets: [
        .target(
            name: "YourTarget",
            dependencies: [
                .product(name: "MetaMUICrypto", package: "metamui-crypto-swift")
            ]
        )
    ]
)

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
}

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

Common Patterns

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