Skip to content

Commit 6db5a75

Browse files
perf: Add benchmarks and reduce allocations by ~50% for ARC and VOPRF (#346)
### Motivation We've recently added some EC operations internally to support some schemes, including ARC and VOPRF. Some of this code is quite allocation-heavy, which could be avoided with some minimally invasive changes. ### Modifications - Add package benchmarks for ARC and VOPRF. - Make use of the `consuming` keyword in some targeted places to reduce allocations in arithmetic- and append-chains. - Use a Thread-/Task-local FiniteFieldArithmeticContext for EC operations, and thread this through as a `BN_CTX` to BoringSSL functions when available. - Replace EC static computed properties with stored properties for things that are statically knowable about the EC group. ### Result This has resulted in a 50% reduction in allocations for ARC issuance and VOPRF evaluation, and a 34% reduction in ARC verification. ```none SwiftCryptoBenchmarks ============================================================================================================================ ---------------------------------------------------------------------------------------------------------------------------- arc-issue-p384 metrics ---------------------------------------------------------------------------------------------------------------------------- ╒══════════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Malloc (total) * │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ alpha │ 945 │ 945 │ 945 │ 945 │ 945 │ 945 │ 945 │ 3 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ beta │ 464 │ 464 │ 464 │ 464 │ 464 │ 464 │ 464 │ 3 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Δ │ -481 │ -481 │ -481 │ -481 │ -481 │ -481 │ -481 │ 0 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Improvement % │ 51 │ 51 │ 51 │ 51 │ 51 │ 51 │ 51 │ 0 │ ╘══════════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ ---------------------------------------------------------------------------------------------------------------------------- arc-verify-p384 metrics ---------------------------------------------------------------------------------------------------------------------------- ╒══════════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Malloc (total) * │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ alpha │ 410 │ 410 │ 410 │ 415 │ 415 │ 415 │ 415 │ 10 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ beta │ 271 │ 271 │ 271 │ 275 │ 275 │ 275 │ 275 │ 10 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Δ │ -139 │ -139 │ -139 │ -140 │ -140 │ -140 │ -140 │ 0 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Improvement % │ 34 │ 34 │ 34 │ 34 │ 34 │ 34 │ 34 │ 0 │ ╘══════════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ ---------------------------------------------------------------------------------------------------------------------------- voprf-evaluate-p384 metrics ---------------------------------------------------------------------------------------------------------------------------- ╒══════════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Malloc (total) * │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ alpha │ 331 │ 331 │ 331 │ 331 │ 331 │ 331 │ 331 │ 3 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ beta │ 168 │ 168 │ 168 │ 168 │ 168 │ 168 │ 168 │ 3 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Δ │ -163 │ -163 │ -163 │ -163 │ -163 │ -163 │ -163 │ 0 │ ├──────────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Improvement % │ 49 │ 49 │ 49 │ 49 │ 49 │ 49 │ 49 │ 0 │ ╘══════════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ ``` --------- Co-authored-by: Cory Benfield <[email protected]>
1 parent e8d6eba commit 6db5a75

File tree

8 files changed

+312
-113
lines changed

8 files changed

+312
-113
lines changed
+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftCrypto project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import Benchmark
15+
import Crypto
16+
import Foundation
17+
import _CryptoExtras
18+
19+
let benchmarks = {
20+
let defaultMetrics: [BenchmarkMetric] = [.mallocCountTotal, .cpuTotal]
21+
22+
Benchmark(
23+
"arc-issue-p384",
24+
configuration: Benchmark.Configuration(
25+
metrics: defaultMetrics,
26+
scalingFactor: .kilo,
27+
maxDuration: .seconds(10_000_000),
28+
maxIterations: 3
29+
)
30+
) { benchmark in
31+
let privateKey = P384._ARCV1.PrivateKey()
32+
let publicKey = privateKey.publicKey
33+
let requestContext = Data("shared request context".utf8)
34+
let precredential = try publicKey.prepareCredentialRequest(requestContext: requestContext)
35+
let credentialRequest = precredential.credentialRequest
36+
37+
benchmark.startMeasurement()
38+
39+
for _ in benchmark.scaledIterations {
40+
blackHole(try privateKey.issue(credentialRequest))
41+
}
42+
}
43+
44+
Benchmark(
45+
"arc-verify-p384",
46+
configuration: Benchmark.Configuration(
47+
metrics: defaultMetrics,
48+
scalingFactor: .kilo,
49+
maxDuration: .seconds(10_000_000),
50+
maxIterations: 10
51+
)
52+
) { benchmark in
53+
let privateKey = P384._ARCV1.PrivateKey()
54+
let publicKey = privateKey.publicKey
55+
let requestContext = Data("shared request context".utf8)
56+
let (presentationContext, presentationLimit) = (Data("shared presentation context".utf8), 2)
57+
let precredential = try publicKey.prepareCredentialRequest(requestContext: requestContext)
58+
let credentialRequest = precredential.credentialRequest
59+
let credentialResponse = try privateKey.issue(credentialRequest)
60+
var credential = try publicKey.finalize(credentialResponse, for: precredential)
61+
let (presentation, nonce) = try credential.makePresentation(
62+
context: presentationContext,
63+
presentationLimit: presentationLimit
64+
)
65+
66+
benchmark.startMeasurement()
67+
68+
for _ in benchmark.scaledIterations {
69+
blackHole(
70+
try privateKey.verify(
71+
presentation,
72+
requestContext: requestContext,
73+
presentationContext: presentationContext,
74+
presentationLimit: presentationLimit,
75+
nonce: nonce
76+
)
77+
)
78+
}
79+
}
80+
81+
Benchmark(
82+
"voprf-evaluate-p384",
83+
configuration: Benchmark.Configuration(
84+
metrics: defaultMetrics,
85+
scalingFactor: .kilo,
86+
maxDuration: .seconds(10_000_000),
87+
maxIterations: 3
88+
)
89+
) { benchmark in
90+
let privateKey = P384._VOPRF.PrivateKey()
91+
let publicKey = privateKey.publicKey
92+
let privateInput = Data("This is some input data".utf8)
93+
let blindedInput = try publicKey.blind(privateInput)
94+
let blindedElement = blindedInput.blindedElement
95+
96+
benchmark.startMeasurement()
97+
98+
for _ in benchmark.scaledIterations {
99+
blackHole(try privateKey.evaluate(blindedElement))
100+
}
101+
}
102+
}

Benchmarks/Package.swift

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// swift-tools-version: 5.9
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "swift-crypto-benchmarks",
6+
platforms: [.macOS("14")],
7+
dependencies: [
8+
.package(name: "swift-crypto", path: "../"),
9+
.package(url: "https://github.com/ordo-one/package-benchmark", from: "1.22.0"),
10+
],
11+
targets: [
12+
.executableTarget(
13+
name: "SwiftCryptoBenchmarks",
14+
dependencies: [
15+
.product(name: "Benchmark", package: "package-benchmark"),
16+
.product(name: "Crypto", package: "swift-crypto"),
17+
.product(name: "_CryptoExtras", package: "swift-crypto"),
18+
],
19+
path: "Benchmarks/",
20+
plugins: [
21+
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
22+
]
23+
)
24+
]
25+
)

Sources/CryptoBoringWrapper/EC/EllipticCurve.swift

+9-17
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@
2020
package final class BoringSSLEllipticCurveGroup {
2121
@usableFromInline var _group: OpaquePointer
2222

23+
@usableFromInline package let order: ArbitraryPrecisionInteger
24+
25+
@usableFromInline package let generator: EllipticCurvePoint
26+
2327
@usableFromInline
2428
package init(_ curve: CurveName) throws {
2529
guard let group = CCryptoBoringSSL_EC_GROUP_new_by_curve_name(curve.baseNID) else {
2630
throw CryptoBoringWrapperError.internalBoringSSLError()
2731
}
2832

2933
self._group = group
34+
35+
let baseOrder = CCryptoBoringSSL_EC_GROUP_get0_order(self._group)!
36+
self.order = try! ArbitraryPrecisionInteger(copying: baseOrder)
37+
38+
self.generator = try EllipticCurvePoint(_generatorOf: self._group)
3039
}
3140

3241
deinit {
@@ -68,23 +77,6 @@ extension BoringSSLEllipticCurveGroup {
6877
try body(self._group)
6978
}
7079

71-
@usableFromInline
72-
package var order: ArbitraryPrecisionInteger {
73-
// Groups must have an order.
74-
let baseOrder = CCryptoBoringSSL_EC_GROUP_get0_order(self._group)!
75-
return try! ArbitraryPrecisionInteger(copying: baseOrder)
76-
}
77-
78-
@usableFromInline
79-
package var generator: EllipticCurvePoint {
80-
get throws {
81-
guard let generatorPtr = CCryptoBoringSSL_EC_GROUP_get0_generator(self._group) else {
82-
throw CryptoBoringWrapperError.internalBoringSSLError()
83-
}
84-
return try EllipticCurvePoint(copying: generatorPtr, on: self)
85-
}
86-
}
87-
8880
/// An elliptic curve can be represented in a Weierstrass form: `y² = x³ + ax + b`. This
8981
/// property provides the values of a and b on the curve.
9082
@usableFromInline

0 commit comments

Comments
 (0)