Skip to content

Commit ff0f781

Browse files
extras: Add EC toolbox abstractions and OPRF(P-384, SHA-384) VOPRF API (#292)
## Motivation We would like to provide support for the P384-SHA384 Verifiable Oblivious Pseudorandom Function (VOPRF) as defined in [RFC 9497: VOPRF Protocol](https://www.rfc-editor.org/rfc/rfc9497.html#name-voprf-protocol). ## Modifications - Add protocols for some "ECToolbox" abstractions: `Group`, `GroupElement`, `GroupScalar`, `HashToGroup`. - Add implementations backed by BoringSSL. - Duplicate some utilities from Crypto into _CryptoExtras: hexadecimal bytes and I2OSP. - Add VOPRF API, rooted at `enum P384._VOPRF`. ## Results - New internal abstractions that make it easier to bring up new implementations. - New API for `OPRF(P-384, SHA-384)` in VOPRF mode. ## Tests - Test vectors for internal `HashToField` implementation. - Test vectors for internal VOPRF implementation. - Tests of the VOPRF public API.
1 parent a3b7196 commit ff0f781

28 files changed

+4061
-16
lines changed

Package.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,18 @@ let package = Package(
172172
],
173173
swiftSettings: swiftSettings
174174
),
175-
.testTarget(name: "_CryptoExtrasTests", dependencies: ["_CryptoExtras"]),
175+
.testTarget(
176+
name: "_CryptoExtrasTests",
177+
dependencies: ["_CryptoExtras"],
178+
resources: [
179+
.copy("ECToolbox/H2CVectors/P256_XMD-SHA-256_SSWU_RO_.json"),
180+
.copy("ECToolbox/H2CVectors/P384_XMD-SHA-384_SSWU_RO_.json"),
181+
.copy("OPRFs/OPRFVectors/OPRFVectors-VOPRFDraft8.json"),
182+
.copy("OPRFs/OPRFVectors/OPRFVectors-VOPRFDraft19.json"),
183+
.copy("OPRFs/OPRFVectors/OPRFVectors-edgecases.json"),
184+
],
185+
swiftSettings: swiftSettings
186+
),
176187
.testTarget(name: "CryptoBoringWrapperTests", dependencies: ["CryptoBoringWrapper"]),
177188
],
178189
cxxLanguageStandard: .cxx11

Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,20 @@ int CCryptoBoringSSLShims_EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, void *out,
134134
size_t *out_len, const void *in,
135135
size_t in_len);
136136

137+
int CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu(const EC_GROUP *group, EC_POINT *out,
138+
const void *dst, size_t dst_len,
139+
const void *msg, size_t msg_len);
140+
141+
int CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu(const EC_GROUP *group, EC_POINT *out,
142+
const void *dst, size_t dst_len,
143+
const void *msg, size_t msg_len);
144+
145+
size_t CCryptoBoringSSLShims_EC_POINT_point2oct(const EC_GROUP *group,
146+
const EC_POINT *point,
147+
point_conversion_form_t form,
148+
void *buf, size_t max_out,
149+
BN_CTX *ctx);
150+
137151
#if defined(__cplusplus)
138152
}
139153
#endif // defined(__cplusplus)

Sources/CCryptoBoringSSLShims/shims.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,23 @@ int CCryptoBoringSSLShims_EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, void *out,
168168
size_t in_len) {
169169
return CCryptoBoringSSL_EVP_PKEY_decrypt(ctx, out, out_len, in, in_len);
170170
}
171+
172+
int CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu(const EC_GROUP *group, EC_POINT *out,
173+
const void *dst, size_t dst_len,
174+
const void *msg, size_t msg_len) {
175+
return CCryptoBoringSSL_EC_hash_to_curve_p256_xmd_sha256_sswu(group, out, dst, dst_len, msg, msg_len);
176+
}
177+
178+
int CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu(const EC_GROUP *group, EC_POINT *out,
179+
const void *dst, size_t dst_len,
180+
const void *msg, size_t msg_len) {
181+
return CCryptoBoringSSL_EC_hash_to_curve_p384_xmd_sha384_sswu(group, out, dst, dst_len, msg, msg_len);
182+
}
183+
184+
size_t CCryptoBoringSSLShims_EC_POINT_point2oct(const EC_GROUP *group,
185+
const EC_POINT *point,
186+
point_conversion_form_t form,
187+
void *buf, size_t max_out,
188+
BN_CTX *ctx) {
189+
return CCryptoBoringSSL_EC_POINT_point2oct(group, point, form, buf, max_out, ctx);
190+
}

Sources/CryptoBoringWrapper/EC/EllipticCurve.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/// A wrapper around BoringSSL's EC_GROUP object that handles reference counting and
1717
/// liveness.
1818
@usableFromInline
19-
package class BoringSSLEllipticCurveGroup {
19+
package final class BoringSSLEllipticCurveGroup {
2020
/* private but usableFromInline */ @usableFromInline var _group: OpaquePointer
2121

2222
@usableFromInline
@@ -72,6 +72,16 @@ extension BoringSSLEllipticCurveGroup {
7272
return try! ArbitraryPrecisionInteger(copying: baseOrder)
7373
}
7474

75+
@usableFromInline
76+
package var generator: EllipticCurvePoint {
77+
get throws {
78+
guard let generatorPtr = CCryptoBoringSSL_EC_GROUP_get0_generator(self._group) else {
79+
throw CryptoBoringWrapperError.internalBoringSSLError()
80+
}
81+
return try EllipticCurvePoint(copying: generatorPtr, on: self)
82+
}
83+
}
84+
7585
/// An elliptic curve can be represented in a Weierstrass form: `y² = x³ + ax + b`. This
7686
/// property provides the values of a and b on the curve.
7787
@usableFromInline
@@ -102,6 +112,20 @@ extension BoringSSLEllipticCurveGroup {
102112
case p384
103113
case p521
104114
}
115+
116+
@usableFromInline
117+
var curveName: CurveName? {
118+
switch CCryptoBoringSSL_EC_GROUP_get_curve_name(self._group) {
119+
case NID_X9_62_prime256v1:
120+
return .p256
121+
case NID_secp384r1:
122+
return .p384
123+
case NID_secp521r1:
124+
return .p521
125+
default:
126+
return nil
127+
}
128+
}
105129
}
106130

107131
extension BoringSSLEllipticCurveGroup.CurveName {

Sources/CryptoBoringWrapper/EC/EllipticCurvePoint.swift

Lines changed: 238 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,266 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
@_implementationOnly import CCryptoBoringSSL
15+
@_implementationOnly import CCryptoBoringSSLShims
16+
import struct Foundation.Data
17+
import protocol Foundation.ContiguousBytes
1518

1619
/// A wrapper around BoringSSL's EC_POINT with some lifetime management.
1720
@usableFromInline
18-
package class EllipticCurvePoint {
21+
package final class EllipticCurvePoint {
1922
/* private but @usableFromInline */ @usableFromInline var _basePoint: OpaquePointer
2023

2124
@usableFromInline
22-
package init(multiplying scalar: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
25+
package init(copying pointer: OpaquePointer, on group: BoringSSLEllipticCurveGroup) throws {
2326
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
24-
guard let basePoint = CCryptoBoringSSL_EC_POINT_new(groupPtr) else {
27+
guard let pointPtr = CCryptoBoringSSL_EC_POINT_dup(pointer, groupPtr) else {
2528
throw CryptoBoringWrapperError.internalBoringSSLError()
2629
}
27-
return basePoint
30+
return pointPtr
2831
}
32+
}
33+
34+
@usableFromInline
35+
package convenience init(copying other: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
36+
try self.init(copying: other._basePoint, on: group)
37+
}
2938

39+
@usableFromInline
40+
package init(_pointAtInfinityOn group: BoringSSLEllipticCurveGroup) throws {
41+
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
42+
guard let pointPtr = CCryptoBoringSSL_EC_POINT_new(groupPtr) else {
43+
throw CryptoBoringWrapperError.internalBoringSSLError()
44+
}
45+
return pointPtr
46+
}
47+
}
48+
49+
@usableFromInline
50+
package convenience init(multiplying scalar: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
51+
try self.init(_pointAtInfinityOn: group)
3052
try group.withUnsafeGroupPointer { groupPtr in
31-
try scalar.withUnsafeBignumPointer { bigNumPtr in
32-
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, self._basePoint, bigNumPtr, nil, nil, nil) != 0 else {
53+
try scalar.withUnsafeBignumPointer { scalarPtr in
54+
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, self._basePoint, scalarPtr, nil, nil, nil) == 1 else {
3355
throw CryptoBoringWrapperError.internalBoringSSLError()
3456
}
3557
}
3658
}
3759
}
3860

39-
package init(copying pointer: OpaquePointer, on group: BoringSSLEllipticCurveGroup) throws {
40-
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
41-
guard let basePoint = CCryptoBoringSSL_EC_POINT_dup(pointer, groupPtr) else {
42-
throw CryptoBoringWrapperError.internalBoringSSLError()
61+
deinit {
62+
CCryptoBoringSSL_EC_POINT_free(self._basePoint)
63+
}
64+
65+
@usableFromInline
66+
package func multiply(by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
67+
try self.withPointPointer { selfPtr in
68+
try rhs.withUnsafeBignumPointer { rhsPtr in
69+
try group.withUnsafeGroupPointer { groupPtr in
70+
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, selfPtr, nil, selfPtr, rhsPtr, nil) != 0 else {
71+
throw CryptoBoringWrapperError.internalBoringSSLError()
72+
}
73+
}
4374
}
44-
return basePoint
4575
}
4676
}
4777

48-
deinit {
49-
CCryptoBoringSSL_EC_POINT_free(self._basePoint)
78+
@usableFromInline
79+
package convenience init(multiplying lhs: EllipticCurvePoint, by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
80+
try self.init(copying: lhs, on: group)
81+
try self.multiply(by: rhs, on: group)
82+
}
83+
84+
@usableFromInline
85+
package func multiplying(by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
86+
try EllipticCurvePoint(multiplying: self, by: rhs, on: group)
87+
}
88+
89+
@usableFromInline
90+
package static func multiplying(_ lhs: EllipticCurvePoint, by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
91+
try EllipticCurvePoint(multiplying: lhs, by: rhs, on: group)
92+
}
93+
94+
@usableFromInline
95+
package func add(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
96+
try self.withPointPointer { selfPtr in
97+
try group.withUnsafeGroupPointer { groupPtr in
98+
try rhs.withPointPointer { rhsPtr in
99+
guard CCryptoBoringSSL_EC_POINT_add(groupPtr, selfPtr, selfPtr, rhsPtr, nil) != 0 else {
100+
throw CryptoBoringWrapperError.internalBoringSSLError()
101+
}
102+
}
103+
}
104+
}
105+
}
106+
107+
@usableFromInline
108+
package convenience init(adding lhs: EllipticCurvePoint, _ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup ) throws {
109+
try self.init(copying: lhs, on: group)
110+
try self.add(rhs, on: group)
111+
}
112+
113+
@usableFromInline
114+
package func adding(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
115+
try EllipticCurvePoint(adding: self, rhs, on: group)
116+
}
117+
118+
@usableFromInline
119+
package static func adding(_ lhs: EllipticCurvePoint, _ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
120+
try EllipticCurvePoint(adding: lhs, rhs, on: group)
121+
}
122+
123+
@usableFromInline
124+
package func invert(on group: BoringSSLEllipticCurveGroup) throws {
125+
try self.withPointPointer { selfPtr in
126+
try group.withUnsafeGroupPointer { groupPtr in
127+
guard CCryptoBoringSSL_EC_POINT_invert(groupPtr, selfPtr, nil) != 0 else {
128+
throw CryptoBoringWrapperError.internalBoringSSLError()
129+
}
130+
}
131+
}
132+
}
133+
134+
@usableFromInline
135+
package convenience init(inverting point: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
136+
try self.init(copying: point, on: group)
137+
try self.invert(on: group)
138+
}
139+
140+
@usableFromInline
141+
package func inverting(on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
142+
try EllipticCurvePoint(inverting: self, on: group)
143+
}
144+
145+
@usableFromInline
146+
package static func inverting(_ point: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
147+
try EllipticCurvePoint(inverting: point, on: group)
148+
}
149+
150+
@usableFromInline
151+
package func subtract(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
152+
try self.add(rhs.inverting(on: group), on: group)
153+
}
154+
155+
@usableFromInline
156+
package convenience init(subtracting rhs: EllipticCurvePoint, from lhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
157+
try self.init(copying: lhs, on: group)
158+
try self.subtract(rhs, on: group)
159+
}
160+
161+
@usableFromInline
162+
package func subtracting(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
163+
try EllipticCurvePoint(subtracting: rhs, from: self, on: group)
164+
}
165+
166+
@usableFromInline
167+
package static func subtracting(_ rhs: EllipticCurvePoint, from lhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
168+
try EllipticCurvePoint(subtracting: rhs, from: lhs, on: group)
169+
}
170+
171+
@usableFromInline
172+
package convenience init<MessageBytes: ContiguousBytes, DSTBytes: ContiguousBytes>(hashing msg: MessageBytes, to group: BoringSSLEllipticCurveGroup, domainSeparationTag: DSTBytes) throws {
173+
let hashToCurveFunction = switch group.curveName {
174+
case .p256: CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu
175+
case .p384: CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu
176+
case .p521: throw CryptoBoringWrapperError.invalidParameter // BoringSSL doesn't have a hash_to_curve API for P521.
177+
case .none: throw CryptoBoringWrapperError.internalBoringSSLError()
178+
}
179+
180+
try self.init(_pointAtInfinityOn: group)
181+
try msg.withUnsafeBytes { msgPtr in
182+
try group.withUnsafeGroupPointer { groupPtr in
183+
try domainSeparationTag.withUnsafeBytes { dstPtr in
184+
guard hashToCurveFunction(
185+
groupPtr,
186+
self._basePoint,
187+
dstPtr.baseAddress,
188+
dstPtr.count,
189+
msgPtr.baseAddress,
190+
msgPtr.count
191+
) == 1 else { throw CryptoBoringWrapperError.internalBoringSSLError() }
192+
}
193+
}
194+
}
195+
}
196+
197+
@usableFromInline
198+
package func isEqual(to rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) -> Bool {
199+
self.withPointPointer { selfPtr in
200+
group.withUnsafeGroupPointer { groupPtr in
201+
rhs.withPointPointer { rhsPtr in
202+
switch CCryptoBoringSSL_EC_POINT_cmp(groupPtr, selfPtr, rhsPtr, nil) {
203+
case 0: return true
204+
case 1: return false
205+
default:
206+
// EC_POINT_cmp returns an error when comparing points on different groups.
207+
// We treat that as not equal, so we'll just clear the error and return false.
208+
CCryptoBoringSSL_ERR_clear_error()
209+
return false
210+
}
211+
}
212+
}
213+
}
214+
}
215+
216+
@usableFromInline
217+
package convenience init<Bytes: ContiguousBytes>(x962Representation bytes: Bytes, on group: BoringSSLEllipticCurveGroup) throws {
218+
try self.init(_pointAtInfinityOn: group)
219+
guard group.withUnsafeGroupPointer({ groupPtr in
220+
bytes.withUnsafeBytes { dataPtr in
221+
CCryptoBoringSSL_EC_POINT_oct2point(
222+
groupPtr,
223+
self._basePoint,
224+
dataPtr.baseAddress,
225+
dataPtr.count,
226+
nil
227+
)
228+
}
229+
}) == 1 else {
230+
throw CryptoBoringWrapperError.invalidParameter
231+
}
232+
}
233+
234+
@usableFromInline
235+
package func x962RepresentationByteCount(compressed: Bool, on group: BoringSSLEllipticCurveGroup) throws -> Int {
236+
let numBytesNeeded = group.withUnsafeGroupPointer { groupPtr in
237+
CCryptoBoringSSL_EC_POINT_point2oct(
238+
groupPtr,
239+
self._basePoint,
240+
compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
241+
nil,
242+
0,
243+
nil
244+
)
245+
}
246+
guard numBytesNeeded != 0 else {
247+
throw CryptoBoringWrapperError.internalBoringSSLError()
248+
}
249+
return numBytesNeeded
250+
}
251+
252+
@usableFromInline
253+
package func x962Representation(compressed: Bool, on group: BoringSSLEllipticCurveGroup) throws -> Data {
254+
let numBytesNeeded = try self.x962RepresentationByteCount(compressed: compressed, on: group)
255+
256+
var buf = Data(repeating: 0, count: numBytesNeeded)
257+
258+
let numBytesWritten = group.withUnsafeGroupPointer { groupPtr in
259+
buf.withUnsafeMutableBytes { bufPtr in
260+
CCryptoBoringSSLShims_EC_POINT_point2oct(
261+
groupPtr,
262+
self._basePoint,
263+
compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
264+
bufPtr.baseAddress,
265+
numBytesNeeded,
266+
nil
267+
)
268+
}
269+
}
270+
guard numBytesWritten == numBytesNeeded else {
271+
throw CryptoBoringWrapperError.internalBoringSSLError()
272+
}
273+
274+
return buf
50275
}
51276
}
52277

0 commit comments

Comments
 (0)