File: NISTCurvesKeys_boring.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (632 lines) | stat: -rw-r--r-- 25,776 bytes parent folder | download
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2019 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
@_exported import CryptoKit
#else
@_implementationOnly import CCryptoBoringSSL
@_implementationOnly import CCryptoBoringSSLShims
import Foundation

@usableFromInline
protocol OpenSSLSupportedNISTCurve {
    @inlinable
    static var group: BoringSSLEllipticCurveGroup { get }
}

extension OpenSSLSupportedNISTCurve {
    @inlinable
    static var coordinateByteCount: Int {
        self.group.coordinateByteCount
    }
}

extension P256: OpenSSLSupportedNISTCurve {
    @inlinable
    static var group: BoringSSLEllipticCurveGroup {
        try! BoringSSLEllipticCurveGroup(.p256)
    }
}

extension P384: OpenSSLSupportedNISTCurve {
    @inlinable
    static var group: BoringSSLEllipticCurveGroup {
        try! BoringSSLEllipticCurveGroup(.p384)
    }
}

extension P521: OpenSSLSupportedNISTCurve {
    @inlinable
    static var group: BoringSSLEllipticCurveGroup {
        try! BoringSSLEllipticCurveGroup(.p521)
    }
}

@usableFromInline
struct OpenSSLNISTCurvePrivateKeyImpl<Curve: OpenSSLSupportedNISTCurve> {
    @usableFromInline
    var key: BoringSSLECPrivateKeyWrapper<Curve>

    init(compactRepresentable: Bool = true) {
        self.key = try! BoringSSLECPrivateKeyWrapper(compactRepresentable: compactRepresentable)
    }

    init<Bytes: ContiguousBytes>(x963: Bytes) throws {
        self.key = try BoringSSLECPrivateKeyWrapper(x963Representation: x963)
    }

    init<Bytes: ContiguousBytes>(data: Bytes) throws {
        self.key = try BoringSSLECPrivateKeyWrapper(rawRepresentation: data)
    }

    func publicKey() -> OpenSSLNISTCurvePublicKeyImpl<Curve> {
        OpenSSLNISTCurvePublicKeyImpl(wrapping: self.key.publicKey)
    }

    var rawRepresentation: Data {
        self.key.rawRepresentation
    }

    var x963Representation: Data {
        self.key.x963Representation
    }
}

@usableFromInline
struct OpenSSLNISTCurvePublicKeyImpl<Curve: OpenSSLSupportedNISTCurve> {
    @usableFromInline
    var key: BoringSSLECPublicKeyWrapper<Curve>

    init<Bytes: ContiguousBytes>(compactRepresentation: Bytes) throws {
        self.key = try BoringSSLECPublicKeyWrapper(compactRepresentation: compactRepresentation)
    }

    init<Bytes: ContiguousBytes>(x963Representation: Bytes) throws {
        self.key = try BoringSSLECPublicKeyWrapper(x963Representation: x963Representation)
    }

    init<Bytes: ContiguousBytes>(rawRepresentation: Bytes) throws {
        self.key = try BoringSSLECPublicKeyWrapper(rawRepresentation: rawRepresentation)
    }

    init<Bytes: ContiguousBytes>(compressedRepresentation: Bytes) throws {
        self.key = try BoringSSLECPublicKeyWrapper(compressedRepresentation: compressedRepresentation)
    }

    @inlinable
    init(wrapping key: BoringSSLECPublicKeyWrapper<Curve>) {
        self.key = key
    }

    @inlinable
    var compactRepresentation: Data? {
        self.key.compactRepresentation
    }

    @inlinable
    var rawRepresentation: Data {
        self.key.rawRepresentation
    }

    @inlinable
    var x963Representation: Data {
        self.key.x963Representation
    }

    @inlinable
    var compressedRepresentation: Data {
        self.key.compressedRepresentation
    }
}

/// A simple wrapper for an EC_KEY pointer for a private key. This manages the lifetime of that pointer and
/// allows some helper operations.
@usableFromInline
class BoringSSLECPrivateKeyWrapper<Curve: OpenSSLSupportedNISTCurve> {
    @usableFromInline
    var key: OpaquePointer

    init(compactRepresentable: Bool) throws {
        // We cannot handle allocation failure.
        let group = Curve.group
        self.key = try! group.makeUnsafeOwnedECKey()

        // If we've been asked to generate a compact representable key, we need to try a few times. This loop shouldn't
        // execute more than 100 times: if it does, we'll crash because something bad is happening.
        for _ in 0 ..< 100 {
            // We generate FIPS compliant keys to match the behaviour of CryptoKit on Apple platforms.
            guard CCryptoBoringSSL_EC_KEY_generate_key(self.key) != 0 else {
                throw CryptoKitError.internalBoringSSLError()
            }

            // We want to generate FIPS compliant keys. If this isn't, loop around again.
            if CCryptoBoringSSL_EC_KEY_check_fips(self.key) == 0 {
                continue
            }

            if !compactRepresentable || _isCompactRepresentable(group: group, publicKeyPoint: self.publicKeyPoint) {
                return
            }
        }

        fatalError("Looped more than 100 times trying to generate a key")
    }

    init<Bytes: ContiguousBytes>(x963Representation bytes: Bytes) throws {
        // Before we do anything, we validate that the x963 representation has the right number of bytes.
        // This is because BoringSSL will quietly accept shorter byte counts, though it will reject longer ones.
        // This brings our behaviour into line with CryptoKit
        let group = Curve.group
        let length = bytes.withUnsafeBytes { $0.count }
        guard length == (group.coordinateByteCount * 3) + 1 else {
            throw CryptoKitError.incorrectParameterSize
        }

        self.key = try group.makeUnsafeOwnedECKey()

        // First, try to grab the numbers.
        var (x, y, k) = try bytes.readx963PrivateNumbers()

        // Then we set the private key first, then the public key. In this order, BoringSSL will check the key
        // validity for us.
        try self.setPrivateKey(k)
        try self.setPublicKey(x: &x, y: &y)
    }

    init<Bytes: ContiguousBytes>(rawRepresentation bytes: Bytes) throws {
        let group = Curve.group

        // Before we do anything, we validate that the raw representation has the right number of bytes.
        // This is because BoringSSL will quietly accept shorter byte counts, though it will reject longer ones.
        // This brings our behaviour into line with CryptoKit
        let length = bytes.withUnsafeBytes { $0.count }
        guard length == group.coordinateByteCount else {
            throw CryptoKitError.incorrectParameterSize
        }

        self.key = try group.makeUnsafeOwnedECKey()

        // The raw representation is just the bytes that make up k.
        let k = try ArbitraryPrecisionInteger(bytes: bytes)

        // Begin by setting the private key.
        try self.setPrivateKey(k)

        // Now calculate the public one and set it.
        let point = try EllipticCurvePoint(multiplying: k, on: group)
        try self.setPublicKey(point: point)
    }

    func setPrivateKey(_ keyScalar: ArbitraryPrecisionInteger) throws {
        try keyScalar.withUnsafeBignumPointer { bigNum in
            guard CCryptoBoringSSL_EC_KEY_set_private_key(self.key, bigNum) != 0 else {
                throw CryptoKitError.internalBoringSSLError()
            }
        }
    }

    func setPublicKey(x: inout ArbitraryPrecisionInteger, y: inout ArbitraryPrecisionInteger) throws {
        try x.withUnsafeMutableBignumPointer { xPointer in
            try y.withUnsafeMutableBignumPointer { yPointer in
                // This function is missing some const declarations here, which is why we need the bignums inout.
                // If that gets fixed, we can clean this function up.
                guard CCryptoBoringSSL_EC_KEY_set_public_key_affine_coordinates(self.key, xPointer, yPointer) != 0 else {
                    throw CryptoKitError.internalBoringSSLError()
                }
            }
        }
    }

    func setPublicKey(point: EllipticCurvePoint) throws {
        try point.withPointPointer { ecPointer in
            guard CCryptoBoringSSL_EC_KEY_set_public_key(self.key, ecPointer) != 0 else {
                throw CryptoKitError.internalBoringSSLError()
            }
        }
    }

    var publicKey: BoringSSLECPublicKeyWrapper<Curve> {
        // This is a weird little trick we can do here: because EC_KEY is both private and public depending on
        // its internal state, we can just vend a pointer to ourself and this will work.
        try! BoringSSLECPublicKeyWrapper(unsafeTakingOwnership: CCryptoBoringSSL_EC_KEY_dup(self.key))
    }

    @usableFromInline
    var publicKeyPoint: EllipticCurvePoint {
        try! EllipticCurvePoint(copying: CCryptoBoringSSL_EC_KEY_get0_public_key(self.key)!, on: Curve.group)
    }

    @usableFromInline
    var privateKeyScalar: ArbitraryPrecisionInteger {
        try! ArbitraryPrecisionInteger(copying: CCryptoBoringSSL_EC_KEY_get0_private_key(self.key)!)
    }

    @inlinable
    var rawRepresentation: Data {
        // The raw representation is just the bytes that make up k. This try! should only fire if we have internal
        // consistency errors.
        var bytes = Data()
        bytes.reserveCapacity(Curve.group.coordinateByteCount)
        try! bytes.append(bytesOf: self.privateKeyScalar, paddedToSize: Curve.group.coordinateByteCount)
        return bytes
    }

    @inlinable
    var x963Representation: Data {
        // The x9.63 private key format is a discriminator byte (0x4) concatenated with the X and Y points
        // of the public key, and the K value of the secret scalar. Let's load that in.
        let group = Curve.group
        let pointByteCount = group.coordinateByteCount
        let privateKey = self.privateKeyScalar
        let (x, y) = try! self.publicKeyPoint.affineCoordinates(group: group)

        var bytes = Data()
        bytes.reserveCapacity(1 + (group.coordinateByteCount * 3))

        // These try!s should only trigger in the case of internal consistency errors.
        bytes.append(0x4)
        try! bytes.append(bytesOf: x, paddedToSize: pointByteCount)
        try! bytes.append(bytesOf: y, paddedToSize: pointByteCount)
        try! bytes.append(bytesOf: privateKey, paddedToSize: pointByteCount)

        return bytes
    }

    func keyExchange(publicKey: BoringSSLECPublicKeyWrapper<Curve>) throws -> SecureBytes {
        let pubKeyPoint = publicKey.publicKeyPoint
        let outputSize = Curve.group.coordinateByteCount

        return try SecureBytes(unsafeUninitializedCapacity: outputSize) { secretPtr, secretSize in
            let rc = pubKeyPoint.withPointPointer { pointPtr in
                CCryptoBoringSSL_ECDH_compute_key(secretPtr.baseAddress, secretPtr.count, pointPtr, self.key, nil)
            }

            if rc == -1 {
                throw CryptoKitError.internalBoringSSLError()
            }
            precondition(rc == outputSize, "Unexpectedly short secret.")
            secretSize = Int(rc)
        }
    }

    func sign<D: Digest>(digest: D) throws -> ECDSASignature {
        let optionalRawSignature: UnsafeMutablePointer<ECDSA_SIG>? = digest.withUnsafeBytes { digestPtr in
            CCryptoBoringSSLShims_ECDSA_do_sign(digestPtr.baseAddress, digestPtr.count, self.key)
        }
        guard let rawSignature = optionalRawSignature else {
            throw CryptoKitError.internalBoringSSLError()
        }

        return ECDSASignature(takingOwnershipOf: rawSignature)
    }

    deinit {
        CCryptoBoringSSL_EC_KEY_free(self.key)
    }
}

/// A simple wrapper for an EC_KEY pointer for a public key. This manages the lifetime of that pointer and
/// allows some helper operations.
@usableFromInline
class BoringSSLECPublicKeyWrapper<Curve: OpenSSLSupportedNISTCurve> {
    @usableFromInline
    var key: OpaquePointer

    init<Bytes: ContiguousBytes>(compactRepresentation bytes: Bytes) throws {
        let group = Curve.group

        // Before we do anything, we validate that the compact representation has the right number of bytes.
        // This is because BoringSSL will quietly accept shorter byte counts, though it will reject longer ones.
        // This brings our behaviour into line with CryptoKit
        let length = bytes.withUnsafeBytes { $0.count }
        guard length == group.coordinateByteCount else {
            throw CryptoKitError.incorrectParameterSize
        }

        self.key = try group.makeUnsafeOwnedECKey()

        // The compact representation is simply the X coordinate: deserializing then requires us to do a little math,
        // as discussed in https://datatracker.ietf.org/doc/html/draft-jivsov-ecc-compact-05#section-4.1
        var x = try ArbitraryPrecisionInteger(bytes: bytes)

        // We now need to solve the curve equation in Weierstrass form. This form is y² = x³ + ax + b. We need a and b.
        // We also need a finite field context, which means we need the order of the underlying prime field. We call that
        // p, for later.
        let (p, a, b) = group.weierstrassCoefficients
        let context = try FiniteFieldArithmeticContext(fieldSize: p)
        let xCubed = try (context.multiply(context.square(x), x))
        let ax = try context.multiply(a, x)
        let ySquared = try context.add(context.add(xCubed, ax), b)

        // We want the positive square root value of y, which conveniently is what we can get. We will call this yPrime.
        // We then need to calculate y = min(yPrime, p-yPrime) where p is the order of the underlying finite field.
        let yPrime = try context.positiveSquareRoot(ySquared)
        var y = min(yPrime, try context.subtract(yPrime, from: p))

        // This is the full set of coordinates. We're done.
        try self.setPublicKey(x: &x, y: &y)
    }

    init<Bytes: ContiguousBytes>(x963Representation bytes: Bytes) throws {
        // Before we do anything, we validate that the x963 representation has the right number of bytes.
        // This is because BoringSSL will quietly accept shorter byte counts, though it will reject longer ones.
        // This brings our behaviour into line with CryptoKit
        let group = Curve.group
        let length = bytes.withUnsafeBytes { $0.count }

        switch length {
        case (group.coordinateByteCount * 2) + 1:
            var (x, y) = try bytes.readx963PublicNumbers()
            self.key = try group.makeUnsafeOwnedECKey()
            try self.setPublicKey(x: &x, y: &y)

        default:
            throw CryptoKitError.incorrectParameterSize
        }
    }

    init<Bytes: ContiguousBytes>(compressedRepresentation bytes: Bytes) throws {
        let group = Curve.group
        let length = bytes.withUnsafeBytes { $0.count }

        switch length {
        case group.coordinateByteCount + 1:
            var (x, yBit) = try bytes.readx963CompressedPublicNumbers()
            self.key = try group.makeUnsafeOwnedECKey()
            try self.setPublicKey(x: &x, yBit: yBit)

        default:
            throw CryptoKitError.incorrectParameterSize
        }
    }

    init<Bytes: ContiguousBytes>(rawRepresentation bytes: Bytes) throws {
        let group = Curve.group

        // Before we do anything, we validate that the raw representation has the right number of bytes.
        // This is because BoringSSL will quietly accept shorter byte counts, though it will reject longer ones.
        // This brings our behaviour into line with CryptoKit
        let length = bytes.withUnsafeBytes { $0.count }
        guard length == group.coordinateByteCount * 2 else {
            throw CryptoKitError.incorrectParameterSize
        }

        self.key = try group.makeUnsafeOwnedECKey()

        // The raw representation is identical to the x963 representation, without the leading 0x4.
        var (x, y): (ArbitraryPrecisionInteger, ArbitraryPrecisionInteger) = try bytes.withUnsafeBytes { bytesPtr in
            try readRawPublicNumbers(copyingBytes: bytesPtr)
        }

        // Then we set the public key and we're done.
        try self.setPublicKey(x: &x, y: &y)
    }

    /// Takes ownership of the pointer. If this throws, ownership of the pointer has not been taken.
    @usableFromInline
    init(unsafeTakingOwnership ownedPointer: OpaquePointer) throws {
        guard let newKeyGroup = CCryptoBoringSSL_EC_KEY_get0_group(ownedPointer) else {
            throw CryptoKitError.internalBoringSSLError()
        }
        let groupEqual = Curve.group.withUnsafeGroupPointer { ourCurvePointer in
            CCryptoBoringSSL_EC_GROUP_cmp(newKeyGroup, ourCurvePointer, nil)
        }
        guard groupEqual == 0 else {
            throw CryptoKitError.incorrectParameterSize
        }

        self.key = ownedPointer
    }

    @inlinable
    var compactRepresentation: Data? {
        let group = Curve.group
        guard _isCompactRepresentable(group: group, publicKeyPoint: self.publicKeyPoint) else {
            return nil
        }

        // The compact representation is simply the X coordinate. This try! should only fire on internal consistency
        // errors.
        var bytes = Data()
        bytes.reserveCapacity(group.coordinateByteCount)

        let (x, _) = try! self.publicKeyPoint.affineCoordinates(group: group)
        try! bytes.append(bytesOf: x, paddedToSize: group.coordinateByteCount)
        return bytes
    }

    @inlinable
    var rawRepresentation: Data {
        // The raw representation is the X coordinate concatenated with the Y coordinate: essentially, it's
        // the x963 representation without the leading byte.
        self.x963Representation.dropFirst()
    }

    @inlinable
    var x963Representation: Data {
        // The x963 representation is the X coordinate concatenated with the Y coordinate, prefixed by the byte 0x04.
        let group = Curve.group
        let (x, y) = try! self.publicKeyPoint.affineCoordinates(group: group)
        let pointByteCount = group.coordinateByteCount

        var bytes = Data()
        bytes.reserveCapacity(1 + (group.coordinateByteCount * 2))

        // These try!s should only trigger on internal consistency errors.
        bytes.append(0x4)
        try! bytes.append(bytesOf: x, paddedToSize: pointByteCount)
        try! bytes.append(bytesOf: y, paddedToSize: pointByteCount)

        return bytes
    }

    @inlinable
    var compressedRepresentation: Data {
        // The x963 representation is the X coordinate, prefixed by the byte 0x02 or 0x03 depending on whether the Y coordinate is odd or even.
        // We calculate this by playing games with the x963Representation. We can safely assume that this Data is zero-indexed, because
        // we just created it above.
        var bytes = self.x963Representation
        let yMask = bytes.last! & 0x1
        bytes[0] = 0x2 | yMask
        return bytes.dropLast(Curve.group.coordinateByteCount)
    }

    deinit {
        CCryptoBoringSSL_EC_KEY_free(self.key)
    }

    @usableFromInline
    var publicKeyPoint: EllipticCurvePoint {
        try! EllipticCurvePoint(copying: CCryptoBoringSSL_EC_KEY_get0_public_key(self.key)!, on: Curve.group)
    }

    func setPublicKey(x: inout ArbitraryPrecisionInteger, y: inout ArbitraryPrecisionInteger) throws {
        try x.withUnsafeMutableBignumPointer { xPointer in
            try y.withUnsafeMutableBignumPointer { yPointer in
                // This function is missing some const declarations here, which is why we need the bignums inout.
                // If that gets fixed, we can clean this function up.
                guard CCryptoBoringSSL_EC_KEY_set_public_key_affine_coordinates(self.key, xPointer, yPointer) != 0 else {
                    throw CryptoKitError.internalBoringSSLError()
                }
            }
        }
    }

    func setPublicKey(x: inout ArbitraryPrecisionInteger, yBit: Bool) throws {
        try x.withUnsafeMutableBignumPointer { xPointer in
            // We cannot handle allocation errors.
            let point = try Curve.group.makeUnsafeOwnedECPoint()
            defer {
                // We either error, or EC_KEY_set_public_key dups the key,
                // so we must always free.
                CCryptoBoringSSL_EC_POINT_free(point)
            }
            let rc = Curve.group.withUnsafeGroupPointer { groupPtr in
                CCryptoBoringSSL_EC_POINT_set_compressed_coordinates_GFp(groupPtr, point, xPointer, yBit ? 1 : 0, nil)
            }

            guard rc == 1 else {
                throw CryptoKitError.internalBoringSSLError()
            }

            guard CCryptoBoringSSL_EC_KEY_set_public_key(self.key, point) == 1 else {
                throw CryptoKitError.internalBoringSSLError()
            }
        }
    }

    func isValidSignature<D: Digest>(_ signature: ECDSASignature, for digest: D) -> Bool {
        let rc: CInt = signature.withUnsafeSignaturePointer { signaturePointer in
            digest.withUnsafeBytes { digestPointer in
                CCryptoBoringSSLShims_ECDSA_do_verify(digestPointer.baseAddress, digestPointer.count, signaturePointer, self.key)
            }
        }

        return rc == 1
    }
}

extension ContiguousBytes {
    func readx963PrivateNumbers() throws -> (x: ArbitraryPrecisionInteger, y: ArbitraryPrecisionInteger, k: ArbitraryPrecisionInteger) {
        // The x9.63 private key format is a discriminator byte (0x4) concatenated with the X and Y points
        // of the public key, and the K value of the secret scalar. Let's load that in.
        return try self.withUnsafeBytes { bytesPtr in
            guard bytesPtr.first == 0x04 else {
                throw CryptoKitError.incorrectKeySize // This is the same error CryptoKit throws on Apple platforms.
            }

            let stride = (bytesPtr.count - 1) / 3
            var offset = 1
            let xPointer = UnsafeRawBufferPointer(rebasing: bytesPtr[offset ..< (offset + stride)])
            offset += stride
            let yPointer = UnsafeRawBufferPointer(rebasing: bytesPtr[offset ..< (offset + stride)])
            offset += stride
            let kPointer = UnsafeRawBufferPointer(rebasing: bytesPtr[offset ..< (offset + stride)])

            let x = try ArbitraryPrecisionInteger(bytes: xPointer)
            let y = try ArbitraryPrecisionInteger(bytes: yPointer)
            let k = try ArbitraryPrecisionInteger(bytes: kPointer)

            return (x: x, y: y, k: k)
        }
    }

    @inlinable
    func readx963PublicNumbers() throws -> (x: ArbitraryPrecisionInteger, y: ArbitraryPrecisionInteger) {
        // The x9.63 public key format is a discriminator byte (0x4) concatenated with the X and Y points
        // of the public key. Let's load that in.
        return try self.withUnsafeBytes { bytesPtr in
            guard bytesPtr.first == 0x04 else {
                throw CryptoKitError.incorrectKeySize // This is the same error CryptoKit throws on Apple platforms.
            }

            return try readRawPublicNumbers(copyingBytes: UnsafeRawBufferPointer(rebasing: bytesPtr[1...]))
        }
    }

    @inlinable
    func readx963CompressedPublicNumbers() throws -> (x: ArbitraryPrecisionInteger, yBit: Bool) {
        // The x9.63 compressed public key format is a discriminator byte (0x2 or 0x3) that signals which
        // of the possible two Y values is being used, concatenated with the X point of the key.
        return try self.withUnsafeBytes { bytesPtr in
            let yBit: Bool

            switch bytesPtr.first {
            case 0x03:
                yBit = true
            case 0x02:
                yBit = false
            default:
                throw CryptoKitError.incorrectKeySize // This is the same error CryptoKit throws on Apple platforms.
            }

            let xBytes = UnsafeRawBufferPointer(rebasing: bytesPtr.dropFirst())
            let x = try ArbitraryPrecisionInteger(bytes: xBytes)

            return (x: x, yBit: yBit)
        }
    }
}

@usableFromInline
func readRawPublicNumbers(copyingBytes bytesPtr: UnsafeRawBufferPointer) throws -> (x: ArbitraryPrecisionInteger, y: ArbitraryPrecisionInteger) {
    let stride = bytesPtr.count / 2
    var offset = 0
    let xPointer = UnsafeRawBufferPointer(rebasing: bytesPtr[offset ..< (offset + stride)])
    offset += stride
    let yPointer = UnsafeRawBufferPointer(rebasing: bytesPtr[offset ..< (offset + stride)])

    // We cannot handle allocation errors, so we check for fatal error.
    let x = try ArbitraryPrecisionInteger(bytes: xPointer)
    let y = try ArbitraryPrecisionInteger(bytes: yPointer)

    return (x: x, y: y)
}

/// In a number of places we need to know if an EC key is compact representable. This function implements that check.
///
/// The check is defined in https://tools.ietf.org/id/draft-jivsov-ecc-compact-05.html#rfc.section.4.2.1. Specifically, a
/// point is compact representable if its y coordinate is the smaller of min(y, p-y) where p is the order of the prime field.
@usableFromInline
func _isCompactRepresentable(group: BoringSSLEllipticCurveGroup, publicKeyPoint: EllipticCurvePoint) -> Bool {
    // We have three try!s here: any of those failing is the result of an allocation error, and we cannot recover from
    // those.
    let (_, y) = try! publicKeyPoint.affineCoordinates(group: group)
    let p = group.weierstrassCoefficients.field
    let context = try! FiniteFieldArithmeticContext(fieldSize: p)
    let newY = try! context.subtract(y, from: group.order)

    // The point is compact representable if y is less than or equal to newY.
    return y <= newY
}
#endif // CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API