import CoreImage import CoreImage.CIFilterBuiltins import Foundation import UIKit import Vision /// Renders a QR image (used by the UI and by the self-test, which round-trips /// an image through the reader). enum QRCodeImage { static func make(_ text: String, scale: CGFloat = 10) -> UIImage? { guard !text.isEmpty else { return nil } let filter = CIFilter.qrCodeGenerator() filter.message = Data(text.utf8) filter.correctionLevel = "M" guard let output = filter.outputImage else { return nil } let scaled = output.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) let context = CIContext() guard let cgImage = context.createCGImage(scaled, from: scaled.extent) else { return nil } return UIImage(cgImage: cgImage) } } /// A payment request decoded from a QR code: the address plus whatever else the /// code carried. struct QRPayload { var address: String var amount: String? var tag: UInt32? var paymentId: String? var memo: String? } enum QRCodeReader { /// Reads the first QR code found in an image (used for photo-library import). static func decodeText(in data: Data) -> String? { guard let image = UIImage(data: data), let cgImage = image.cgImage else { return nil } return decodeText(in: cgImage) } static func decodeText(in image: UIImage) -> String? { guard let cgImage = image.cgImage else { return nil } return decodeText(in: cgImage) } private static func decodeText(in cgImage: CGImage) -> String? { if let text = decodeWithVision(cgImage) { return text } // Vision's barcode detector does not work on the iOS simulator, so fall // back to CoreImage, which does (and adds redundancy on device). return decodeWithCoreImage(cgImage) } private static func decodeWithVision(_ cgImage: CGImage) -> String? { let request = VNDetectBarcodesRequest() request.symbologies = [.qr] let handler = VNImageRequestHandler(cgImage: cgImage, orientation: .up) try? handler.perform([request]) for observation in request.results ?? [] { if let payload = observation.payloadStringValue, !payload.isEmpty { return payload } } return nil } private static func decodeWithCoreImage(_ cgImage: CGImage) -> String? { let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) let features = detector?.features(in: CIImage(cgImage: cgImage)) ?? [] for feature in features { if let code = feature as? CIQRCodeFeature, let message = code.messageString, !message.isEmpty { return message } } return nil } /// Understands plain addresses as well as the payment URIs wallets emit: /// `monero:` (tx_amount, tx_payment_id, tx_description) and `ripple:` /// (amount, dt). static func parse(_ raw: String) -> QRPayload? { let text = raw.trimmingCharacters(in: .whitespacesAndNewlines) guard !text.isEmpty else { return nil } var body = text var query: [String: String] = [:] if let separator = text.firstIndex(of: "?") { body = String(text[text.startIndex.. Bool { let text = address.trimmingCharacters(in: .whitespacesAndNewlines) guard text.count == 95 || text.count == 106 else { return false } return text.allSatisfy { moneroAlphabet.contains($0) } } static func isXRP(_ address: String) -> Bool { XRPKey.isValidAddress(address.trimmingCharacters(in: .whitespacesAndNewlines)) } }