Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions Sources/_CryptoExtras/ECToolbox/BoringSSL/ECToolbox_boring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@ extension P256: OpenSSLSupportedNISTCurve {
@inlinable
static var hashToFieldByteCount: Int { 48 }

@TaskLocal
@usableFromInline
// NOTE: This could be a let when Swift 6.0 is the minimum supported version.
static var __ffac = try! FiniteFieldArithmeticContext(fieldSize: P256.group.order)
static var __ffac: FiniteFieldArithmeticContext {
let key = "com.apple.swift-crypto.P256.__ffac"
if let value = Thread.current.threadDictionary[key] as? FiniteFieldArithmeticContext {
return value
}
let value = try! FiniteFieldArithmeticContext(fieldSize: P256.group.order)
Thread.current.threadDictionary[key] = value
return value
}
}

/// NOTE: This conformance applies to this type from the Crypto module even if it comes from the SDK.
Expand All @@ -92,10 +98,16 @@ extension P384: OpenSSLSupportedNISTCurve {
@inlinable
static var hashToFieldByteCount: Int { 72 }

@TaskLocal
@usableFromInline
// NOTE: This could be a let when Swift 6.0 is the minimum supported version.
static var __ffac = try! FiniteFieldArithmeticContext(fieldSize: P384.group.order)
static var __ffac: FiniteFieldArithmeticContext {
let key = "com.apple.swift-crypto.P384.__ffac"
if let value = Thread.current.threadDictionary[key] as? FiniteFieldArithmeticContext {
return value
}
let value = try! FiniteFieldArithmeticContext(fieldSize: P384.group.order)
Thread.current.threadDictionary[key] = value
return value
}
}

/// NOTE: This conformance applies to this type from the Crypto module even if it comes from the SDK.
Expand All @@ -119,10 +131,16 @@ extension P521: OpenSSLSupportedNISTCurve {
@inlinable
static var hashToFieldByteCount: Int { 98 }

@TaskLocal
@usableFromInline
// NOTE: This could be a let when Swift 6.0 is the minimum supported version.
static var __ffac = try! FiniteFieldArithmeticContext(fieldSize: P521.group.order)
static var __ffac: FiniteFieldArithmeticContext {
let key = "com.apple.swift-crypto.P521.__ffac"
if let value = Thread.current.threadDictionary[key] as? FiniteFieldArithmeticContext {
return value
}
let value = try! FiniteFieldArithmeticContext(fieldSize: P521.group.order)
Thread.current.threadDictionary[key] = value
return value
}
}

@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2025 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Crypto
import Foundation
import XCTest

@testable import _CryptoExtras

final class ECToolboxBoringSSLTests: XCTestCase {
func testThreadLocalFFAC() async {
await testThreadLocalFFAC(P256.self)
await testThreadLocalFFAC(P384.self)
await testThreadLocalFFAC(P521.self)
}

func testThreadLocalFFAC(_ Curve: (some OpenSSLSupportedNISTCurve & Sendable).Type) async {
let numThreads = 3
let numReadsPerThread = 2

var threads:
[(
thread: Thread,
thisThreadDidReads: XCTestExpectation,
allThreadsDidReads: XCTestExpectation,
thisThreadFinished: XCTestExpectation
)] = []

var objectIdentifiers: [(threadID: Int, ffacID: ObjectIdentifier)] = []
let lock = NSLock()

for i in 1...numThreads {
let thisThreadDidReads = expectation(description: "this thread did its reads")
let allThreadsDidReads = expectation(description: "all threads did their reads")
let thisThreadFinished = expectation(description: "this thread is finished")
let thread = Thread {
for _ in 1...numReadsPerThread {
lock.lock()
objectIdentifiers.append((i, ObjectIdentifier(Curve.__ffac)))
lock.unlock()
}
thisThreadDidReads.fulfill()
XCTWaiter().wait(for: [allThreadsDidReads], timeout: .greatestFiniteMagnitude)
thisThreadFinished.fulfill()
}
thread.name = "thread-\(i)"
threads.append((thread, thisThreadDidReads, allThreadsDidReads, thisThreadFinished))
thread.start()
}
await fulfillment(of: threads.map(\.thisThreadDidReads), timeout: 0.5)
for thread in threads { thread.allThreadsDidReads.fulfill() }
await fulfillment(of: threads.map(\.thisThreadFinished), timeout: 0.5)

XCTAssertEqual(objectIdentifiers.count, numThreads * numReadsPerThread)
for threadID in 1...numThreads {
let partitionBoundary = objectIdentifiers.partition(by: { $0.threadID == threadID })
let otherThreadsObjIDs = objectIdentifiers[..<partitionBoundary].map(\.ffacID)
let thisThreadObjIDs = objectIdentifiers[partitionBoundary...].map(\.ffacID)
let intersection = Set(thisThreadObjIDs).intersection(Set(otherThreadsObjIDs))
XCTAssertEqual(thisThreadObjIDs.count, numReadsPerThread, "Thread should read \(numReadsPerThread) times.")
XCTAssertEqual(Set(thisThreadObjIDs).count, 1, "Thread should see same object on every read.")
XCTAssert(intersection.isEmpty, "Thread should see different objects from other threads.")
}
}
}