import Foundation
import CryptoKit
/// Byte-level helpers shared by the whole SDK: the two base64 alphabets the
/// protocol uses (standard, padded — inside envelopes; URL-safe, unpadded —
/// for identifiers), hex, UTF-8 and randomness.
public enum Bytes {
/// Standard base64 with padding (`+`, `/`, `=`), as used inside sealed envelopes.
public static func base64(_ data: Data) -> String { data.base64EncodedString() }
/// Decodes standard base64; `nil` on any malformed input.
public static func fromBase64(_ string: String) -> Data? {
Data(base64Encoded: string)
}
/// URL-safe base64 without padding (`-`, `_`), as used for secrets, ids and keys.
public static func base64URL(_ data: Data) -> String {
var s = data.base64EncodedString()
s = s.replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_")
while s.hasSuffix("=") { s.removeLast() }
return s
}
/// Decodes URL-safe base64 (padding optional); `nil` on any malformed input.
public static func fromBase64URL(_ string: String) -> Data? {
var s = string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
let rem = s.count % 4
if rem == 1 { return nil }
if rem > 0 { s += String(repeating: "=", count: 4 - rem) }
return Data(base64Encoded: s)
}
/// Lowercase hex.
public static func hex(_ data: Data) -> String {
data.map { String(format: "%02x", $0) }.joined()
}
/// Decodes lowercase or uppercase hex of even length; `nil` otherwise.
public static func fromHex(_ string: String) -> Data? {
let chars = Array(string.utf8)
guard chars.count % 2 == 0 else { return nil }
var out = Data(capacity: chars.count / 2)
var i = 0
while i < chars.count {
guard let hi = nibble(chars[i]), let lo = nibble(chars[i + 1]) else { return nil }
out.append(hi << 4 | lo)
i += 2
}
return out
}
private static func nibble(_ c: UInt8) -> UInt8? {
switch c {
case 0x30...0x39: return c - 0x30
case 0x61...0x66: return c - 0x61 + 10
case 0x41...0x46: return c - 0x41 + 10
default: return nil
}
}
/// UTF-8 bytes of a string.
public static func utf8(_ string: String) -> Data { Data(string.utf8) }
/// Cryptographically secure random bytes.
public static func random(_ count: Int) -> Data {
var data = Data(count: count)
var rng = SystemRandomNumberGenerator()
for i in 0..<count { data[i] = UInt8.random(in: .min ... .max, using: &rng) }
return data
}
/// SHA-256 digest.
public static func sha256(_ data: Data) -> Data { Data(SHA256.hash(data: data)) }
/// Constant-time equality (length-then-XOR, no early exit on the bytes).
public static func constantTimeEquals(_ a: Data, _ b: Data) -> Bool {
guard a.count == b.count else { return false }
var acc: UInt8 = 0
for (x, y) in zip(a, b) { acc |= x ^ y }
return acc == 0
}
}