Skip to content

Commit

Permalink
extras: Add EC toolbox abstractions and OPRF(P-384, SHA-384) VOPRF API (
Browse files Browse the repository at this point in the history
#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.
  • Loading branch information
simonjbeaumont authored Nov 22, 2024
1 parent a3b7196 commit ff0f781
Show file tree
Hide file tree
Showing 28 changed files with 4,061 additions and 16 deletions.
13 changes: 12 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,18 @@ let package = Package(
],
swiftSettings: swiftSettings
),
.testTarget(name: "_CryptoExtrasTests", dependencies: ["_CryptoExtras"]),
.testTarget(
name: "_CryptoExtrasTests",
dependencies: ["_CryptoExtras"],
resources: [
.copy("ECToolbox/H2CVectors/P256_XMD-SHA-256_SSWU_RO_.json"),
.copy("ECToolbox/H2CVectors/P384_XMD-SHA-384_SSWU_RO_.json"),
.copy("OPRFs/OPRFVectors/OPRFVectors-VOPRFDraft8.json"),
.copy("OPRFs/OPRFVectors/OPRFVectors-VOPRFDraft19.json"),
.copy("OPRFs/OPRFVectors/OPRFVectors-edgecases.json"),
],
swiftSettings: swiftSettings
),
.testTarget(name: "CryptoBoringWrapperTests", dependencies: ["CryptoBoringWrapper"]),
],
cxxLanguageStandard: .cxx11
Expand Down
14 changes: 14 additions & 0 deletions Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ int CCryptoBoringSSLShims_EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, void *out,
size_t *out_len, const void *in,
size_t in_len);

int CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len);

int CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len);

size_t CCryptoBoringSSLShims_EC_POINT_point2oct(const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
void *buf, size_t max_out,
BN_CTX *ctx);

#if defined(__cplusplus)
}
#endif // defined(__cplusplus)
Expand Down
20 changes: 20 additions & 0 deletions Sources/CCryptoBoringSSLShims/shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,23 @@ int CCryptoBoringSSLShims_EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, void *out,
size_t in_len) {
return CCryptoBoringSSL_EVP_PKEY_decrypt(ctx, out, out_len, in, in_len);
}

int CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len) {
return CCryptoBoringSSL_EC_hash_to_curve_p256_xmd_sha256_sswu(group, out, dst, dst_len, msg, msg_len);
}

int CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len) {
return CCryptoBoringSSL_EC_hash_to_curve_p384_xmd_sha384_sswu(group, out, dst, dst_len, msg, msg_len);
}

size_t CCryptoBoringSSLShims_EC_POINT_point2oct(const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
void *buf, size_t max_out,
BN_CTX *ctx) {
return CCryptoBoringSSL_EC_POINT_point2oct(group, point, form, buf, max_out, ctx);
}
26 changes: 25 additions & 1 deletion Sources/CryptoBoringWrapper/EC/EllipticCurve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/// A wrapper around BoringSSL's EC_GROUP object that handles reference counting and
/// liveness.
@usableFromInline
package class BoringSSLEllipticCurveGroup {
package final class BoringSSLEllipticCurveGroup {
/* private but usableFromInline */ @usableFromInline var _group: OpaquePointer

@usableFromInline
Expand Down Expand Up @@ -72,6 +72,16 @@ extension BoringSSLEllipticCurveGroup {
return try! ArbitraryPrecisionInteger(copying: baseOrder)
}

@usableFromInline
package var generator: EllipticCurvePoint {
get throws {
guard let generatorPtr = CCryptoBoringSSL_EC_GROUP_get0_generator(self._group) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return try EllipticCurvePoint(copying: generatorPtr, on: self)
}
}

/// An elliptic curve can be represented in a Weierstrass form: `y² = x³ + ax + b`. This
/// property provides the values of a and b on the curve.
@usableFromInline
Expand Down Expand Up @@ -102,6 +112,20 @@ extension BoringSSLEllipticCurveGroup {
case p384
case p521
}

@usableFromInline
var curveName: CurveName? {
switch CCryptoBoringSSL_EC_GROUP_get_curve_name(self._group) {
case NID_X9_62_prime256v1:
return .p256
case NID_secp384r1:
return .p384
case NID_secp521r1:
return .p521
default:
return nil
}
}
}

extension BoringSSLEllipticCurveGroup.CurveName {
Expand Down
251 changes: 238 additions & 13 deletions Sources/CryptoBoringWrapper/EC/EllipticCurvePoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,266 @@
//
//===----------------------------------------------------------------------===//
@_implementationOnly import CCryptoBoringSSL
@_implementationOnly import CCryptoBoringSSLShims
import struct Foundation.Data
import protocol Foundation.ContiguousBytes

/// A wrapper around BoringSSL's EC_POINT with some lifetime management.
@usableFromInline
package class EllipticCurvePoint {
package final class EllipticCurvePoint {
/* private but @usableFromInline */ @usableFromInline var _basePoint: OpaquePointer

@usableFromInline
package init(multiplying scalar: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
package init(copying pointer: OpaquePointer, on group: BoringSSLEllipticCurveGroup) throws {
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
guard let basePoint = CCryptoBoringSSL_EC_POINT_new(groupPtr) else {
guard let pointPtr = CCryptoBoringSSL_EC_POINT_dup(pointer, groupPtr) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return basePoint
return pointPtr
}
}

@usableFromInline
package convenience init(copying other: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: other._basePoint, on: group)
}

@usableFromInline
package init(_pointAtInfinityOn group: BoringSSLEllipticCurveGroup) throws {
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
guard let pointPtr = CCryptoBoringSSL_EC_POINT_new(groupPtr) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return pointPtr
}
}

@usableFromInline
package convenience init(multiplying scalar: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(_pointAtInfinityOn: group)
try group.withUnsafeGroupPointer { groupPtr in
try scalar.withUnsafeBignumPointer { bigNumPtr in
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, self._basePoint, bigNumPtr, nil, nil, nil) != 0 else {
try scalar.withUnsafeBignumPointer { scalarPtr in
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, self._basePoint, scalarPtr, nil, nil, nil) == 1 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
}

package init(copying pointer: OpaquePointer, on group: BoringSSLEllipticCurveGroup) throws {
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
guard let basePoint = CCryptoBoringSSL_EC_POINT_dup(pointer, groupPtr) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
deinit {
CCryptoBoringSSL_EC_POINT_free(self._basePoint)
}

@usableFromInline
package func multiply(by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
try self.withPointPointer { selfPtr in
try rhs.withUnsafeBignumPointer { rhsPtr in
try group.withUnsafeGroupPointer { groupPtr in
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, selfPtr, nil, selfPtr, rhsPtr, nil) != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
return basePoint
}
}

deinit {
CCryptoBoringSSL_EC_POINT_free(self._basePoint)
@usableFromInline
package convenience init(multiplying lhs: EllipticCurvePoint, by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: lhs, on: group)
try self.multiply(by: rhs, on: group)
}

@usableFromInline
package func multiplying(by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(multiplying: self, by: rhs, on: group)
}

@usableFromInline
package static func multiplying(_ lhs: EllipticCurvePoint, by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(multiplying: lhs, by: rhs, on: group)
}

@usableFromInline
package func add(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.withPointPointer { selfPtr in
try group.withUnsafeGroupPointer { groupPtr in
try rhs.withPointPointer { rhsPtr in
guard CCryptoBoringSSL_EC_POINT_add(groupPtr, selfPtr, selfPtr, rhsPtr, nil) != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
}
}

@usableFromInline
package convenience init(adding lhs: EllipticCurvePoint, _ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup ) throws {
try self.init(copying: lhs, on: group)
try self.add(rhs, on: group)
}

@usableFromInline
package func adding(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(adding: self, rhs, on: group)
}

@usableFromInline
package static func adding(_ lhs: EllipticCurvePoint, _ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(adding: lhs, rhs, on: group)
}

@usableFromInline
package func invert(on group: BoringSSLEllipticCurveGroup) throws {
try self.withPointPointer { selfPtr in
try group.withUnsafeGroupPointer { groupPtr in
guard CCryptoBoringSSL_EC_POINT_invert(groupPtr, selfPtr, nil) != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
}

@usableFromInline
package convenience init(inverting point: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: point, on: group)
try self.invert(on: group)
}

@usableFromInline
package func inverting(on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(inverting: self, on: group)
}

@usableFromInline
package static func inverting(_ point: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(inverting: point, on: group)
}

@usableFromInline
package func subtract(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.add(rhs.inverting(on: group), on: group)
}

@usableFromInline
package convenience init(subtracting rhs: EllipticCurvePoint, from lhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: lhs, on: group)
try self.subtract(rhs, on: group)
}

@usableFromInline
package func subtracting(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(subtracting: rhs, from: self, on: group)
}

@usableFromInline
package static func subtracting(_ rhs: EllipticCurvePoint, from lhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(subtracting: rhs, from: lhs, on: group)
}

@usableFromInline
package convenience init<MessageBytes: ContiguousBytes, DSTBytes: ContiguousBytes>(hashing msg: MessageBytes, to group: BoringSSLEllipticCurveGroup, domainSeparationTag: DSTBytes) throws {
let hashToCurveFunction = switch group.curveName {
case .p256: CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu
case .p384: CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu
case .p521: throw CryptoBoringWrapperError.invalidParameter // BoringSSL doesn't have a hash_to_curve API for P521.
case .none: throw CryptoBoringWrapperError.internalBoringSSLError()
}

try self.init(_pointAtInfinityOn: group)
try msg.withUnsafeBytes { msgPtr in
try group.withUnsafeGroupPointer { groupPtr in
try domainSeparationTag.withUnsafeBytes { dstPtr in
guard hashToCurveFunction(
groupPtr,
self._basePoint,
dstPtr.baseAddress,
dstPtr.count,
msgPtr.baseAddress,
msgPtr.count
) == 1 else { throw CryptoBoringWrapperError.internalBoringSSLError() }
}
}
}
}

@usableFromInline
package func isEqual(to rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) -> Bool {
self.withPointPointer { selfPtr in
group.withUnsafeGroupPointer { groupPtr in
rhs.withPointPointer { rhsPtr in
switch CCryptoBoringSSL_EC_POINT_cmp(groupPtr, selfPtr, rhsPtr, nil) {
case 0: return true
case 1: return false
default:
// EC_POINT_cmp returns an error when comparing points on different groups.
// We treat that as not equal, so we'll just clear the error and return false.
CCryptoBoringSSL_ERR_clear_error()
return false
}
}
}
}
}

@usableFromInline
package convenience init<Bytes: ContiguousBytes>(x962Representation bytes: Bytes, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(_pointAtInfinityOn: group)
guard group.withUnsafeGroupPointer({ groupPtr in
bytes.withUnsafeBytes { dataPtr in
CCryptoBoringSSL_EC_POINT_oct2point(
groupPtr,
self._basePoint,
dataPtr.baseAddress,
dataPtr.count,
nil
)
}
}) == 1 else {
throw CryptoBoringWrapperError.invalidParameter
}
}

@usableFromInline
package func x962RepresentationByteCount(compressed: Bool, on group: BoringSSLEllipticCurveGroup) throws -> Int {
let numBytesNeeded = group.withUnsafeGroupPointer { groupPtr in
CCryptoBoringSSL_EC_POINT_point2oct(
groupPtr,
self._basePoint,
compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
nil,
0,
nil
)
}
guard numBytesNeeded != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return numBytesNeeded
}

@usableFromInline
package func x962Representation(compressed: Bool, on group: BoringSSLEllipticCurveGroup) throws -> Data {
let numBytesNeeded = try self.x962RepresentationByteCount(compressed: compressed, on: group)

var buf = Data(repeating: 0, count: numBytesNeeded)

let numBytesWritten = group.withUnsafeGroupPointer { groupPtr in
buf.withUnsafeMutableBytes { bufPtr in
CCryptoBoringSSLShims_EC_POINT_point2oct(
groupPtr,
self._basePoint,
compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
bufPtr.baseAddress,
numBytesNeeded,
nil
)
}
}
guard numBytesWritten == numBytesNeeded else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}

return buf
}
}

Expand Down
Loading

0 comments on commit ff0f781

Please sign in to comment.