import CryptoKit import Foundation enum XRPError: LocalizedError { case invalidBase58 case invalidAddress case invalidSeed case unsupportedSeed case rpc(String) case network(String) var errorDescription: String? { switch self { case .invalidBase58: return "Invalid Base58 text" case .invalidAddress: return "Invalid XRP address" case .invalidSeed: return "Check the recovery phrase or secret" case .unsupportedSeed: return "Unsupported XRP secret" case .rpc(let message): return message case .network(let message): return message } } } enum XRPHash { static func sha256(_ bytes: [UInt8]) -> [UInt8] { Array(SHA256.hash(data: Data(bytes))) } static func sha512(_ bytes: [UInt8]) -> [UInt8] { Array(SHA512.hash(data: Data(bytes))) } /// First 32 bytes of SHA-512, used by the XRP Ledger everywhere. static func sha512Half(_ bytes: [UInt8]) -> [UInt8] { Array(sha512(bytes).prefix(32)) } static func ripemd160(_ bytes: [UInt8]) -> [UInt8] { RIPEMD160.hash(bytes) } /// Base58Check double-SHA-256 checksum. static func checksum(_ bytes: [UInt8]) -> [UInt8] { Array(sha256(sha256(bytes)).prefix(4)) } } enum Hex { static func encode(_ bytes: [UInt8]) -> String { bytes.map { String(format: "%02x", $0) }.joined() } static func decode(_ string: String) -> [UInt8]? { var text = string.trimmingCharacters(in: .whitespacesAndNewlines) if text.hasPrefix("0x") || text.hasPrefix("0X") { text = String(text.dropFirst(2)) } guard text.count % 2 == 0 else { return nil } var bytes: [UInt8] = [] bytes.reserveCapacity(text.count / 2) var index = text.startIndex while index < text.endIndex { let next = text.index(index, offsetBy: 2) guard let byte = UInt8(text[index..