p2p-swift / Sources / IrdP2PCrypto / Bytes.swift
454 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-swift.git