diff --git a/Package.swift b/Package.swift index 04b795686..cb1ff6c3e 100644 --- a/Package.swift +++ b/Package.swift @@ -171,7 +171,7 @@ let package = Package( "Key Agreement/ECDH.swift.gyb", "Signatures/ECDSA.swift.gyb", "Signatures/MLDSA.swift.gyb", - "Signatures/BoringSSL/MLDSA_boring.swift.gyb", + "Signatures/BoringSSL/MLDSA+externalMu_boring.swift.gyb", "KEM/MLKEM.swift.gyb", "KEM/BoringSSL/MLKEM_boring.swift.gyb", ], @@ -188,7 +188,8 @@ let package = Package( .product(name: "SwiftASN1", package: "swift-asn1"), ], exclude: privacyManifestExclude + [ - "CMakeLists.txt" + "CMakeLists.txt", + "MLDSA/MLDSA+externalMu.swift.gyb", ], resources: privacyManifestResource, swiftSettings: swiftSettings @@ -207,7 +208,8 @@ let package = Package( "CCryptoBoringSSLShims", ], exclude: privacyManifestExclude + [ - "CMakeLists.txt" + "CMakeLists.txt", + "MLDSA/BoringSSLMLDSA.swift.gyb", ], resources: privacyManifestResource ), diff --git a/Sources/Crypto/CMakeLists.txt b/Sources/Crypto/CMakeLists.txt index 1b560e783..ed22804c9 100644 --- a/Sources/Crypto/CMakeLists.txt +++ b/Sources/Crypto/CMakeLists.txt @@ -91,7 +91,7 @@ add_library(Crypto "Signatures/BoringSSL/ECDSASignature_boring.swift" "Signatures/BoringSSL/ECDSA_boring.swift" "Signatures/BoringSSL/EdDSA_boring.swift" - "Signatures/BoringSSL/MLDSA_boring.swift" + "Signatures/BoringSSL/MLDSA+externalMu_boring.swift" "Signatures/BoringSSL/MLDSA_wrapper.swift" "Signatures/ECDSA.swift" "Signatures/Ed25519.swift" diff --git a/Sources/Crypto/Docs.docc/index.md b/Sources/Crypto/Docs.docc/index.md index 931857786..3991f7928 100644 --- a/Sources/Crypto/Docs.docc/index.md +++ b/Sources/Crypto/Docs.docc/index.md @@ -39,11 +39,25 @@ Swift Crypto is built on top of [BoringSSL](https://boringssl.googlesource.com/b - ``P256`` - ``SharedSecret`` - ``HPKE`` +- ``MLDSA65`` +- ``MLDSA87`` ### Key derivation functions - ``HKDF`` +### Key encapsulation mechanisms (KEM) + +- ``KEM`` +- ``MLKEM768`` +- ``MLKEM1024`` +- ``XWingMLKEM768X25519`` + +### KEM keys + +- ``KEMPrivateKey`` +- ``KEMPublicKey`` + ### Errors - ``CryptoKitError`` diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA+externalMu_boring.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA+externalMu_boring.swift new file mode 100644 index 000000000..73060c04d --- /dev/null +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA+externalMu_boring.swift @@ -0,0 +1,145 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + +#if (!CRYPTO_IN_SWIFTPM) || CRYPTO_IN_SWIFTPM_FORCE_BUILD_API +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA65.PublicKey { + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash_boring(for data: D) throws -> Data { + try self.boringSSLKey.prehash_boring(for: data) + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash_boring(for data: D, context: C) throws -> Data { + try self.boringSSLKey.prehash_boring(for: data, context: context) + } + + private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA65.PrivateKey { + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``MLDSA87/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.boringSSLKey.signature_boring(forPrehashedMessageRepresentative: mu) + } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA87.PublicKey { + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash_boring(for data: D) throws -> Data { + try self.boringSSLKey.prehash_boring(for: data) + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash_boring(for data: D, context: C) throws -> Data { + try self.boringSSLKey.prehash_boring(for: data, context: context) + } + + private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA87.PrivateKey { + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``MLDSA87/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.boringSSLKey.signature_boring(forPrehashedMessageRepresentative: mu) + } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif + } + } +} +#endif // Linux or !SwiftPM diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA+externalMu_boring.swift.gyb b/Sources/Crypto/Signatures/BoringSSL/MLDSA+externalMu_boring.swift.gyb new file mode 100644 index 000000000..1752561f6 --- /dev/null +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA+externalMu_boring.swift.gyb @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + +#if (!CRYPTO_IN_SWIFTPM) || CRYPTO_IN_SWIFTPM_FORCE_BUILD_API +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif +%{ + parameter_sets = ["65", "87"] +}% +% for parameter_set in parameter_sets: + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA${parameter_set}.PublicKey { + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash_boring(for data: D) throws -> Data { + try self.boringSSLKey.prehash_boring(for: data) + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash_boring(for data: D, context: C) throws -> Data { + try self.boringSSLKey.prehash_boring(for: data, context: context) + } + + private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA${parameter_set}.PrivateKey { + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``MLDSA87/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.boringSSLKey.signature_boring(forPrehashedMessageRepresentative: mu) + } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif + } + } +} +% end +#endif // Linux or !SwiftPM diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift index a02db5076..1ab91610e 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift @@ -16,6 +16,7 @@ @_exported import CryptoKit #else @_implementationOnly import CCryptoBoringSSL +import CryptoBoringWrapper #if canImport(FoundationEssentials) import FoundationEssentials #else @@ -34,6 +35,8 @@ protocol BoringSSLBackedMLDSAPrivateKey: Sendable { func signature(for data: D, context: C) throws -> Data + func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data + var publicKey: AssociatedPublicKey { get } var seedRepresentation: Data { get } @@ -48,6 +51,10 @@ protocol BoringSSLBackedMLDSAPublicKey: Sendable { func isValidSignature(_: S, for data: D, context: C) -> Bool var rawRepresentation: Data { get } + + func prehash(for data: D) throws -> Data + + func prehash(for data: D, context: C) throws -> Data } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) @@ -59,27 +66,27 @@ protocol BoringSSLBackedMLDSAParameters { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension MLDSA65: BoringSSLBackedMLDSAParameters { - typealias BackingPrivateKey = MLDSA65.InternalPrivateKey - typealias BackingPublicKey = MLDSA65.InternalPublicKey + typealias BackingPrivateKey = BoringSSLMLDSA65.InternalPrivateKey + typealias BackingPublicKey = BoringSSLMLDSA65.InternalPublicKey } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension MLDSA87: BoringSSLBackedMLDSAParameters { - typealias BackingPrivateKey = MLDSA87.InternalPrivateKey - typealias BackingPublicKey = MLDSA87.InternalPublicKey + typealias BackingPrivateKey = BoringSSLMLDSA87.InternalPrivateKey + typealias BackingPublicKey = BoringSSLMLDSA87.InternalPublicKey } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA65.InternalPrivateKey: BoringSSLBackedMLDSAPrivateKey {} +extension BoringSSLMLDSA65.InternalPrivateKey: BoringSSLBackedMLDSAPrivateKey {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA65.InternalPublicKey: BoringSSLBackedMLDSAPublicKey {} +extension BoringSSLMLDSA65.InternalPublicKey: BoringSSLBackedMLDSAPublicKey {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA87.InternalPrivateKey: BoringSSLBackedMLDSAPrivateKey {} +extension BoringSSLMLDSA87.InternalPrivateKey: BoringSSLBackedMLDSAPrivateKey {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA87.InternalPublicKey: BoringSSLBackedMLDSAPublicKey {} +extension BoringSSLMLDSA87.InternalPublicKey: BoringSSLBackedMLDSAPublicKey {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) struct OpenSSLMLDSAPrivateKeyImpl { @@ -117,6 +124,10 @@ struct OpenSSLMLDSAPrivateKeyImpl { try self.backing.signature(for: data, context: context) } + func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + var publicKey: OpenSSLMLDSAPublicKeyImpl { .init(backing: self.backing.publicKey) } @@ -135,7 +146,7 @@ struct OpenSSLMLDSAPrivateKeyImpl { } static var seedSize: Int { - MLDSA.seedByteCount + BoringSSLMLDSA.seedByteCount } } @@ -169,6 +180,14 @@ struct OpenSSLMLDSAPublicKeyImpl { var rawRepresentation: Data { self.backing.rawRepresentation } + + func prehash_boring(for data: D) throws -> Data { + try self.backing.prehash(for: data) + } + + func prehash_boring(for data: D, context: C) throws -> Data { + try self.backing.prehash(for: data, context: context) + } } #endif // CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API diff --git a/Sources/Crypto/Signatures/MLDSA.swift b/Sources/Crypto/Signatures/MLDSA.swift index b3f2cce07..17c7a78dd 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -11,6 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API @_exported import CryptoKit #else @@ -32,7 +37,6 @@ typealias MLDSAPublicKeyImpl = OpenSSLMLDSAPublicKeyImpl typealias MLDSAPrivateKeyImpl = OpenSSLMLDSAPrivateKeyImpl #endif - /// The MLDSA65 Digital Signature Algorithm @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum MLDSA65: Sendable {} @@ -47,7 +51,7 @@ extension MLDSA65 { internal init(_ impl: MLDSAPublicKeyImpl) { self.impl = impl } - + /// Parses a public key from a serialized representation. /// /// - Parameter rawRepresentation: The public key, in the FIPS 204 standard serialization format. @@ -144,7 +148,7 @@ extension MLDSA65 { public func signature(for data: D, context: C) throws -> Data { return try impl.signature(for: data, context: context) } - + /// The associated public key. public var publicKey: PublicKey { get { @@ -180,7 +184,6 @@ extension MLDSA65 { } } - /// The MLDSA87 Digital Signature Algorithm @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum MLDSA87: Sendable {} @@ -195,7 +198,7 @@ extension MLDSA87 { internal init(_ impl: MLDSAPublicKeyImpl) { self.impl = impl } - + /// Parses a public key from a serialized representation. /// /// - Parameter rawRepresentation: The public key, in the FIPS 204 standard serialization format. @@ -292,7 +295,7 @@ extension MLDSA87 { public func signature(for data: D, context: C) throws -> Data { return try impl.signature(for: data, context: context) } - + /// The associated public key. public var publicKey: PublicKey { get { @@ -328,5 +331,4 @@ extension MLDSA87 { } } - -#endif // Linux or !SwiftPM +#endif // Linux or !SwiftPM diff --git a/Sources/Crypto/Signatures/MLDSA.swift.gyb b/Sources/Crypto/Signatures/MLDSA.swift.gyb index 5378503a1..368850957 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -11,6 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API @_exported import CryptoKit #else @@ -24,10 +29,14 @@ public import Foundation }% #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLDSAPublicKeyImpl = CorecryptoMLDSAPublicKeyImpl +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLDSAPrivateKeyImpl = CorecryptoMLDSAPrivateKeyImpl #else +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLDSAPublicKeyImpl = OpenSSLMLDSAPublicKeyImpl +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLDSAPrivateKeyImpl = OpenSSLMLDSAPrivateKeyImpl #endif @@ -35,15 +44,21 @@ typealias MLDSAPrivateKeyImpl = OpenSSLMLDSAPrivateKeyImpl %{ NAME = MLDSA_VARIANT["name"] }% - /// The ${NAME} Digital Signature Algorithm +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum ${NAME}: Sendable {} +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension ${NAME} { /// The public key for ${NAME}. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct PublicKey: Sendable { var impl: MLDSAPublicKeyImpl<${NAME}> - + + internal init(_ impl: MLDSAPublicKeyImpl<${NAME}>) { + self.impl = impl + } + /// Parses a public key from a serialized representation. /// /// - Parameter rawRepresentation: The public key, in the FIPS 204 standard serialization format. @@ -82,6 +97,7 @@ extension ${NAME} { } /// The private key for ${NAME}. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct PrivateKey: Signer, Sendable { internal let impl: MLDSAPrivateKeyImpl<${NAME}> @@ -139,7 +155,7 @@ extension ${NAME} { public func signature(for data: D, context: C) throws -> Data { return try impl.signature(for: data, context: context) } - + /// The associated public key. public var publicKey: PublicKey { get { @@ -176,5 +192,4 @@ extension ${NAME} { } % end - -#endif // Linux or !SwiftPM +#endif // Linux or !SwiftPM diff --git a/Sources/CryptoBoringWrapper/CMakeLists.txt b/Sources/CryptoBoringWrapper/CMakeLists.txt index 980f33c4d..201a0357d 100644 --- a/Sources/CryptoBoringWrapper/CMakeLists.txt +++ b/Sources/CryptoBoringWrapper/CMakeLists.txt @@ -17,8 +17,10 @@ add_library(CryptoBoringWrapper STATIC "CryptoKitErrors_boring.swift" "EC/EllipticCurve.swift" "EC/EllipticCurvePoint.swift" + "MLDSA/BoringSSLMLDSA.swift" "Util/ArbitraryPrecisionInteger.swift" "Util/FiniteFieldArithmeticContext.swift" + "Util/Optional+withUnsafeBytes.swift" "Util/RandomBytes.swift" ) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift b/Sources/CryptoBoringWrapper/MLDSA/BoringSSLMLDSA.swift similarity index 59% rename from Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift rename to Sources/CryptoBoringWrapper/MLDSA/BoringSSLMLDSA.swift index 05150b49f..a342a06aa 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift +++ b/Sources/CryptoBoringWrapper/MLDSA/BoringSSLMLDSA.swift @@ -12,10 +12,8 @@ // //===----------------------------------------------------------------------===// -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -@_exported import CryptoKit -#else @_implementationOnly import CCryptoBoringSSL + #if canImport(FoundationEssentials) import FoundationEssentials #else @@ -26,21 +24,17 @@ import Foundation // any edits of this file WILL be overwritten and thus discarded // see section `gyb` in `README` for details. -@_implementationOnly import CCryptoBoringSSL -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +package enum BoringSSLMLDSA65: Sendable {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA65 { +extension BoringSSLMLDSA65 { /// A ML-DSA-65 private key. - struct InternalPrivateKey: @unchecked Sendable { + package struct InternalPrivateKey: @unchecked Sendable { private var backing: Backing /// Initialize a ML-DSA-65 private key from a random seed. - init() throws { + package init() throws { self.backing = try Backing() } @@ -48,18 +42,18 @@ extension MLDSA65 { /// /// - Parameter seedRepresentation: The seed to use to generate the private key. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. - init(seedRepresentation: some DataProtocol) throws { + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the seed is not 32 bytes long. + package init(seedRepresentation: some DataProtocol) throws { self.backing = try Backing(seedRepresentation: seedRepresentation) } /// The seed from which this private key was generated. - var seedRepresentation: Data { + package var seedRepresentation: Data { self.backing.seed } /// The public key associated with this private key. - var publicKey: InternalPublicKey { + package var publicKey: InternalPublicKey { self.backing.publicKey } @@ -68,7 +62,7 @@ extension MLDSA65 { /// - Parameter data: The message to sign. /// /// - Returns: The signature of the message. - func signature(for data: D) throws -> Data { + package func signature(for data: D) throws -> Data { let context: Data? = nil return try self.backing.signature(for: data, context: context) } @@ -80,10 +74,21 @@ extension MLDSA65 { /// - context: The context to use for the signature. /// /// - Returns: The signature of the message. - func signature(for data: D, context: C) throws -> Data { + package func signature(for data: D, context: C) throws -> Data { try self.backing.signature(for: data, context: context) } + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``BoringSSLMLDSA65/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + package func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -99,11 +104,11 @@ extension MLDSA65 { self.seed = try withUnsafeTemporaryAllocation( of: UInt8.self, - capacity: MLDSA.seedByteCount + capacity: BoringSSLMLDSA.seedByteCount ) { seedPtr in try withUnsafeTemporaryAllocation( of: UInt8.self, - capacity: MLDSA65.InternalPublicKey.Backing.byteCount + capacity: BoringSSLMLDSA65.InternalPublicKey.Backing.byteCount ) { publicKeyPtr in guard CCryptoBoringSSL_MLDSA65_generate_key( @@ -112,10 +117,10 @@ extension MLDSA65 { &self.key ) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } - return Data(bytes: seedPtr.baseAddress!, count: MLDSA.seedByteCount) + return Data(bytes: seedPtr.baseAddress!, count: BoringSSLMLDSA.seedByteCount) } } } @@ -124,10 +129,10 @@ extension MLDSA65 { /// /// - Parameter seedRepresentation: The seed to use to generate the private key. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the seed is not 32 bytes long. init(seedRepresentation: some DataProtocol) throws { - guard seedRepresentation.count == MLDSA.seedByteCount else { - throw CryptoKitError.incorrectKeySize + guard seedRepresentation.count == BoringSSLMLDSA.seedByteCount else { + throw CryptoBoringWrapperError.incorrectKeySize } self.key = .init() @@ -138,11 +143,11 @@ extension MLDSA65 { CCryptoBoringSSL_MLDSA65_private_key_from_seed( &self.key, seedPtr.baseAddress, - MLDSA.seedByteCount + BoringSSLMLDSA.seedByteCount ) }) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } } @@ -159,7 +164,7 @@ extension MLDSA65 { /// /// - Returns: The signature of the message. func signature(for data: D, context: C?) throws -> Data { - var signature = Data(repeating: 0, count: MLDSA65.signatureByteCount) + var signature = Data(repeating: 0, count: BoringSSLMLDSA65.signatureByteCount) let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in let bytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) @@ -178,7 +183,39 @@ extension MLDSA65 { } guard rc == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return signature + } + + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``BoringSSLMLDSA65/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + guard mu.count == BoringSSLMLDSA.muByteCount else { + throw CryptoBoringWrapperError.incorrectParameterSize + } + + var signature = Data(repeating: 0, count: BoringSSLMLDSA65.signatureByteCount) + + let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in + let muBytes: ContiguousBytes = mu.regions.count == 1 ? mu.regions.first! : Array(mu) + return muBytes.withUnsafeBytes { muPtr in + CCryptoBoringSSL_MLDSA65_sign_message_representative( + signaturePtr.baseAddress, + &self.key, + muPtr.baseAddress + ) + } + } + + guard rc == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() } return signature @@ -191,9 +228,9 @@ extension MLDSA65 { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA65 { +extension BoringSSLMLDSA65 { /// A ML-DSA-65 public key. - struct InternalPublicKey: @unchecked Sendable { + package struct InternalPublicKey: @unchecked Sendable { private var backing: Backing fileprivate init(privateKeyBacking: InternalPrivateKey.Backing) { @@ -204,13 +241,13 @@ extension MLDSA65 { /// /// - Parameter rawRepresentation: The public key bytes. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. - init(rawRepresentation: some DataProtocol) throws { + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the raw representation is not the correct size. + package init(rawRepresentation: some DataProtocol) throws { self.backing = try Backing(rawRepresentation: rawRepresentation) } /// The raw binary representation of the public key. - var rawRepresentation: Data { + package var rawRepresentation: Data { self.backing.rawRepresentation } @@ -221,7 +258,7 @@ extension MLDSA65 { /// - data: The message to verify the signature against. /// /// - Returns: `true` if the signature is valid, `false` otherwise. - func isValidSignature(_ signature: S, for data: D) -> Bool { + package func isValidSignature(_ signature: S, for data: D) -> Bool { let context: Data? = nil return self.backing.isValidSignature(signature, for: data, context: context) } @@ -234,7 +271,7 @@ extension MLDSA65 { /// - context: The context to use for the signature verification. /// /// - Returns: `true` if the signature is valid, `false` otherwise. - func isValidSignature( + package func isValidSignature( _ signature: S, for data: D, context: C @@ -242,6 +279,27 @@ extension MLDSA65 { self.backing.isValidSignature(signature, for: data, context: context) } + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash(for data: D) throws -> Data { + let context: Data? = nil + return try self.backing.prehash(for: data, context: context) + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash(for data: D, context: C) throws -> Data { + try self.backing.prehash(for: data, context: context) + } + /// The size of the public key in bytes. static let byteCount = Backing.byteCount @@ -257,10 +315,10 @@ extension MLDSA65 { /// /// - Parameter rawRepresentation: The public key bytes. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the raw representation is not the correct size. init(rawRepresentation: some DataProtocol) throws { - guard rawRepresentation.count == MLDSA65.InternalPublicKey.Backing.byteCount else { - throw CryptoKitError.incorrectKeySize + guard rawRepresentation.count == BoringSSLMLDSA65.InternalPublicKey.Backing.byteCount else { + throw CryptoBoringWrapperError.incorrectKeySize } self.key = .init() @@ -273,7 +331,7 @@ extension MLDSA65 { try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in var cbs = CBS(data: buffer.baseAddress, len: buffer.count) guard CCryptoBoringSSL_MLDSA65_parse_public_key(&self.key, &cbs) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } } } @@ -283,7 +341,7 @@ extension MLDSA65 { var rawRepresentation: Data { var cbb = CBB() // The following BoringSSL functions can only fail on allocation failure, which we define as impossible. - CCryptoBoringSSL_CBB_init(&cbb, MLDSA65.InternalPublicKey.Backing.byteCount) + CCryptoBoringSSL_CBB_init(&cbb, BoringSSLMLDSA65.InternalPublicKey.Backing.byteCount) defer { CCryptoBoringSSL_CBB_cleanup(&cbb) } CCryptoBoringSSL_MLDSA65_marshal_public_key(&cbb, &self.key) return Data(bytes: CCryptoBoringSSL_CBB_data(&cbb), count: CCryptoBoringSSL_CBB_len(&cbb)) @@ -323,6 +381,41 @@ extension MLDSA65 { } } + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + func prehash(for data: D, context: C?) throws -> Data { + var mu = Data(repeating: 0, count: BoringSSLMLDSA.muByteCount) + + let dataBytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) + let rc: CInt = mu.withUnsafeMutableBytes { muPtr in + dataBytes.withUnsafeBytes { dataPtr in + context.withUnsafeBytes { contextPtr in + var prehash = MLDSA65_prehash() + let rc = CCryptoBoringSSL_MLDSA65_prehash_init( + &prehash, + &key, + contextPtr.baseAddress, + contextPtr.count + ) + CCryptoBoringSSL_MLDSA65_prehash_update(&prehash, dataPtr.baseAddress, dataPtr.count) + CCryptoBoringSSL_MLDSA65_prehash_finalize(muPtr.baseAddress, &prehash) + return rc + } + } + } + + guard rc == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return mu + } + /// The size of the public key in bytes. static let byteCount = Int(MLDSA65_PUBLIC_KEY_BYTES) } @@ -330,19 +423,22 @@ extension MLDSA65 { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA65 { +extension BoringSSLMLDSA65 { /// The size of the signature in bytes. private static let signatureByteCount = Int(MLDSA65_SIGNATURE_BYTES) } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA87 { +package enum BoringSSLMLDSA87: Sendable {} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension BoringSSLMLDSA87 { /// A ML-DSA-87 private key. - struct InternalPrivateKey: @unchecked Sendable { + package struct InternalPrivateKey: @unchecked Sendable { private var backing: Backing /// Initialize a ML-DSA-87 private key from a random seed. - init() throws { + package init() throws { self.backing = try Backing() } @@ -350,18 +446,18 @@ extension MLDSA87 { /// /// - Parameter seedRepresentation: The seed to use to generate the private key. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. - init(seedRepresentation: some DataProtocol) throws { + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the seed is not 32 bytes long. + package init(seedRepresentation: some DataProtocol) throws { self.backing = try Backing(seedRepresentation: seedRepresentation) } /// The seed from which this private key was generated. - var seedRepresentation: Data { + package var seedRepresentation: Data { self.backing.seed } /// The public key associated with this private key. - var publicKey: InternalPublicKey { + package var publicKey: InternalPublicKey { self.backing.publicKey } @@ -370,7 +466,7 @@ extension MLDSA87 { /// - Parameter data: The message to sign. /// /// - Returns: The signature of the message. - func signature(for data: D) throws -> Data { + package func signature(for data: D) throws -> Data { let context: Data? = nil return try self.backing.signature(for: data, context: context) } @@ -382,10 +478,21 @@ extension MLDSA87 { /// - context: The context to use for the signature. /// /// - Returns: The signature of the message. - func signature(for data: D, context: C) throws -> Data { + package func signature(for data: D, context: C) throws -> Data { try self.backing.signature(for: data, context: context) } + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``BoringSSLMLDSA87/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + package func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -401,11 +508,11 @@ extension MLDSA87 { self.seed = try withUnsafeTemporaryAllocation( of: UInt8.self, - capacity: MLDSA.seedByteCount + capacity: BoringSSLMLDSA.seedByteCount ) { seedPtr in try withUnsafeTemporaryAllocation( of: UInt8.self, - capacity: MLDSA87.InternalPublicKey.Backing.byteCount + capacity: BoringSSLMLDSA87.InternalPublicKey.Backing.byteCount ) { publicKeyPtr in guard CCryptoBoringSSL_MLDSA87_generate_key( @@ -414,10 +521,10 @@ extension MLDSA87 { &self.key ) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } - return Data(bytes: seedPtr.baseAddress!, count: MLDSA.seedByteCount) + return Data(bytes: seedPtr.baseAddress!, count: BoringSSLMLDSA.seedByteCount) } } } @@ -426,10 +533,10 @@ extension MLDSA87 { /// /// - Parameter seedRepresentation: The seed to use to generate the private key. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the seed is not 32 bytes long. init(seedRepresentation: some DataProtocol) throws { - guard seedRepresentation.count == MLDSA.seedByteCount else { - throw CryptoKitError.incorrectKeySize + guard seedRepresentation.count == BoringSSLMLDSA.seedByteCount else { + throw CryptoBoringWrapperError.incorrectKeySize } self.key = .init() @@ -440,11 +547,11 @@ extension MLDSA87 { CCryptoBoringSSL_MLDSA87_private_key_from_seed( &self.key, seedPtr.baseAddress, - MLDSA.seedByteCount + BoringSSLMLDSA.seedByteCount ) }) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } } @@ -461,7 +568,7 @@ extension MLDSA87 { /// /// - Returns: The signature of the message. func signature(for data: D, context: C?) throws -> Data { - var signature = Data(repeating: 0, count: MLDSA87.signatureByteCount) + var signature = Data(repeating: 0, count: BoringSSLMLDSA87.signatureByteCount) let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in let bytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) @@ -480,7 +587,39 @@ extension MLDSA87 { } guard rc == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return signature + } + + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``BoringSSLMLDSA87/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + guard mu.count == BoringSSLMLDSA.muByteCount else { + throw CryptoBoringWrapperError.incorrectParameterSize + } + + var signature = Data(repeating: 0, count: BoringSSLMLDSA87.signatureByteCount) + + let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in + let muBytes: ContiguousBytes = mu.regions.count == 1 ? mu.regions.first! : Array(mu) + return muBytes.withUnsafeBytes { muPtr in + CCryptoBoringSSL_MLDSA87_sign_message_representative( + signaturePtr.baseAddress, + &self.key, + muPtr.baseAddress + ) + } + } + + guard rc == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() } return signature @@ -493,9 +632,9 @@ extension MLDSA87 { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA87 { +extension BoringSSLMLDSA87 { /// A ML-DSA-87 public key. - struct InternalPublicKey: @unchecked Sendable { + package struct InternalPublicKey: @unchecked Sendable { private var backing: Backing fileprivate init(privateKeyBacking: InternalPrivateKey.Backing) { @@ -506,13 +645,13 @@ extension MLDSA87 { /// /// - Parameter rawRepresentation: The public key bytes. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. - init(rawRepresentation: some DataProtocol) throws { + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the raw representation is not the correct size. + package init(rawRepresentation: some DataProtocol) throws { self.backing = try Backing(rawRepresentation: rawRepresentation) } /// The raw binary representation of the public key. - var rawRepresentation: Data { + package var rawRepresentation: Data { self.backing.rawRepresentation } @@ -523,7 +662,7 @@ extension MLDSA87 { /// - data: The message to verify the signature against. /// /// - Returns: `true` if the signature is valid, `false` otherwise. - func isValidSignature(_ signature: S, for data: D) -> Bool { + package func isValidSignature(_ signature: S, for data: D) -> Bool { let context: Data? = nil return self.backing.isValidSignature(signature, for: data, context: context) } @@ -536,7 +675,7 @@ extension MLDSA87 { /// - context: The context to use for the signature verification. /// /// - Returns: `true` if the signature is valid, `false` otherwise. - func isValidSignature( + package func isValidSignature( _ signature: S, for data: D, context: C @@ -544,6 +683,27 @@ extension MLDSA87 { self.backing.isValidSignature(signature, for: data, context: context) } + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash(for data: D) throws -> Data { + let context: Data? = nil + return try self.backing.prehash(for: data, context: context) + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash(for data: D, context: C) throws -> Data { + try self.backing.prehash(for: data, context: context) + } + /// The size of the public key in bytes. static let byteCount = Backing.byteCount @@ -559,10 +719,10 @@ extension MLDSA87 { /// /// - Parameter rawRepresentation: The public key bytes. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the raw representation is not the correct size. init(rawRepresentation: some DataProtocol) throws { - guard rawRepresentation.count == MLDSA87.InternalPublicKey.Backing.byteCount else { - throw CryptoKitError.incorrectKeySize + guard rawRepresentation.count == BoringSSLMLDSA87.InternalPublicKey.Backing.byteCount else { + throw CryptoBoringWrapperError.incorrectKeySize } self.key = .init() @@ -575,7 +735,7 @@ extension MLDSA87 { try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in var cbs = CBS(data: buffer.baseAddress, len: buffer.count) guard CCryptoBoringSSL_MLDSA87_parse_public_key(&self.key, &cbs) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } } } @@ -585,7 +745,7 @@ extension MLDSA87 { var rawRepresentation: Data { var cbb = CBB() // The following BoringSSL functions can only fail on allocation failure, which we define as impossible. - CCryptoBoringSSL_CBB_init(&cbb, MLDSA87.InternalPublicKey.Backing.byteCount) + CCryptoBoringSSL_CBB_init(&cbb, BoringSSLMLDSA87.InternalPublicKey.Backing.byteCount) defer { CCryptoBoringSSL_CBB_cleanup(&cbb) } CCryptoBoringSSL_MLDSA87_marshal_public_key(&cbb, &self.key) return Data(bytes: CCryptoBoringSSL_CBB_data(&cbb), count: CCryptoBoringSSL_CBB_len(&cbb)) @@ -625,6 +785,41 @@ extension MLDSA87 { } } + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + func prehash(for data: D, context: C?) throws -> Data { + var mu = Data(repeating: 0, count: BoringSSLMLDSA.muByteCount) + + let dataBytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) + let rc: CInt = mu.withUnsafeMutableBytes { muPtr in + dataBytes.withUnsafeBytes { dataPtr in + context.withUnsafeBytes { contextPtr in + var prehash = MLDSA87_prehash() + let rc = CCryptoBoringSSL_MLDSA87_prehash_init( + &prehash, + &key, + contextPtr.baseAddress, + contextPtr.count + ) + CCryptoBoringSSL_MLDSA87_prehash_update(&prehash, dataPtr.baseAddress, dataPtr.count) + CCryptoBoringSSL_MLDSA87_prehash_finalize(muPtr.baseAddress, &prehash) + return rc + } + } + } + + guard rc == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return mu + } + /// The size of the public key in bytes. static let byteCount = Int(MLDSA87_PUBLIC_KEY_BYTES) } @@ -632,14 +827,15 @@ extension MLDSA87 { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA87 { +extension BoringSSLMLDSA87 { /// The size of the signature in bytes. private static let signatureByteCount = Int(MLDSA87_SIGNATURE_BYTES) } -enum MLDSA { +package enum BoringSSLMLDSA { /// The size of the seed in bytes. - static let seedByteCount = 32 -} + package static let seedByteCount = 32 -#endif // CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + /// The size of the "mu" value in bytes. + fileprivate static let muByteCount = 64 +} diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb b/Sources/CryptoBoringWrapper/MLDSA/BoringSSLMLDSA.swift.gyb similarity index 59% rename from Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb rename to Sources/CryptoBoringWrapper/MLDSA/BoringSSLMLDSA.swift.gyb index 27aaa2396..ce60b7da5 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb +++ b/Sources/CryptoBoringWrapper/MLDSA/BoringSSLMLDSA.swift.gyb @@ -12,10 +12,8 @@ // //===----------------------------------------------------------------------===// -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -@_exported import CryptoKit -#else @_implementationOnly import CCryptoBoringSSL + #if canImport(FoundationEssentials) import FoundationEssentials #else @@ -25,26 +23,22 @@ import Foundation // MARK: - Generated file, do NOT edit // any edits of this file WILL be overwritten and thus discarded // see section `gyb` in `README` for details. - -@_implementationOnly import CCryptoBoringSSL -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif %{ parameter_sets = ["65", "87"] }% % for parameter_set in parameter_sets: @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA${parameter_set} { +package enum BoringSSLMLDSA${parameter_set}: Sendable {} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension BoringSSLMLDSA${parameter_set} { /// A ML-DSA-${parameter_set} private key. - struct InternalPrivateKey: @unchecked Sendable { + package struct InternalPrivateKey: @unchecked Sendable { private var backing: Backing /// Initialize a ML-DSA-${parameter_set} private key from a random seed. - init() throws { + package init() throws { self.backing = try Backing() } @@ -52,18 +46,18 @@ extension MLDSA${parameter_set} { /// /// - Parameter seedRepresentation: The seed to use to generate the private key. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. - init(seedRepresentation: some DataProtocol) throws { + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the seed is not 32 bytes long. + package init(seedRepresentation: some DataProtocol) throws { self.backing = try Backing(seedRepresentation: seedRepresentation) } /// The seed from which this private key was generated. - var seedRepresentation: Data { + package var seedRepresentation: Data { self.backing.seed } /// The public key associated with this private key. - var publicKey: InternalPublicKey { + package var publicKey: InternalPublicKey { self.backing.publicKey } @@ -72,7 +66,7 @@ extension MLDSA${parameter_set} { /// - Parameter data: The message to sign. /// /// - Returns: The signature of the message. - func signature(for data: D) throws -> Data { + package func signature(for data: D) throws -> Data { let context: Data? = nil return try self.backing.signature(for: data, context: context) } @@ -84,10 +78,21 @@ extension MLDSA${parameter_set} { /// - context: The context to use for the signature. /// /// - Returns: The signature of the message. - func signature(for data: D, context: C) throws -> Data { + package func signature(for data: D, context: C) throws -> Data { try self.backing.signature(for: data, context: context) } + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``BoringSSLMLDSA${parameter_set}/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + package func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -103,11 +108,11 @@ extension MLDSA${parameter_set} { self.seed = try withUnsafeTemporaryAllocation( of: UInt8.self, - capacity: MLDSA.seedByteCount + capacity: BoringSSLMLDSA.seedByteCount ) { seedPtr in try withUnsafeTemporaryAllocation( of: UInt8.self, - capacity: MLDSA${parameter_set}.InternalPublicKey.Backing.byteCount + capacity: BoringSSLMLDSA${parameter_set}.InternalPublicKey.Backing.byteCount ) { publicKeyPtr in guard CCryptoBoringSSL_MLDSA${parameter_set}_generate_key( @@ -116,10 +121,10 @@ extension MLDSA${parameter_set} { &self.key ) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } - return Data(bytes: seedPtr.baseAddress!, count: MLDSA.seedByteCount) + return Data(bytes: seedPtr.baseAddress!, count: BoringSSLMLDSA.seedByteCount) } } } @@ -128,10 +133,10 @@ extension MLDSA${parameter_set} { /// /// - Parameter seedRepresentation: The seed to use to generate the private key. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the seed is not 32 bytes long. init(seedRepresentation: some DataProtocol) throws { - guard seedRepresentation.count == MLDSA.seedByteCount else { - throw CryptoKitError.incorrectKeySize + guard seedRepresentation.count == BoringSSLMLDSA.seedByteCount else { + throw CryptoBoringWrapperError.incorrectKeySize } self.key = .init() @@ -142,11 +147,11 @@ extension MLDSA${parameter_set} { CCryptoBoringSSL_MLDSA${parameter_set}_private_key_from_seed( &self.key, seedPtr.baseAddress, - MLDSA.seedByteCount + BoringSSLMLDSA.seedByteCount ) }) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } } @@ -163,7 +168,7 @@ extension MLDSA${parameter_set} { /// /// - Returns: The signature of the message. func signature(for data: D, context: C?) throws -> Data { - var signature = Data(repeating: 0, count: MLDSA${parameter_set}.signatureByteCount) + var signature = Data(repeating: 0, count: BoringSSLMLDSA${parameter_set}.signatureByteCount) let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in let bytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) @@ -182,7 +187,39 @@ extension MLDSA${parameter_set} { } guard rc == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return signature + } + + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``BoringSSLMLDSA${parameter_set}/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + guard mu.count == BoringSSLMLDSA.muByteCount else { + throw CryptoBoringWrapperError.incorrectParameterSize + } + + var signature = Data(repeating: 0, count: BoringSSLMLDSA${parameter_set}.signatureByteCount) + + let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in + let muBytes: ContiguousBytes = mu.regions.count == 1 ? mu.regions.first! : Array(mu) + return muBytes.withUnsafeBytes { muPtr in + CCryptoBoringSSL_MLDSA${parameter_set}_sign_message_representative( + signaturePtr.baseAddress, + &self.key, + muPtr.baseAddress + ) + } + } + + guard rc == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() } return signature @@ -195,9 +232,9 @@ extension MLDSA${parameter_set} { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA${parameter_set} { +extension BoringSSLMLDSA${parameter_set} { /// A ML-DSA-${parameter_set} public key. - struct InternalPublicKey: @unchecked Sendable { + package struct InternalPublicKey: @unchecked Sendable { private var backing: Backing fileprivate init(privateKeyBacking: InternalPrivateKey.Backing) { @@ -208,13 +245,13 @@ extension MLDSA${parameter_set} { /// /// - Parameter rawRepresentation: The public key bytes. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. - init(rawRepresentation: some DataProtocol) throws { + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the raw representation is not the correct size. + package init(rawRepresentation: some DataProtocol) throws { self.backing = try Backing(rawRepresentation: rawRepresentation) } /// The raw binary representation of the public key. - var rawRepresentation: Data { + package var rawRepresentation: Data { self.backing.rawRepresentation } @@ -225,7 +262,7 @@ extension MLDSA${parameter_set} { /// - data: The message to verify the signature against. /// /// - Returns: `true` if the signature is valid, `false` otherwise. - func isValidSignature(_ signature: S, for data: D) -> Bool { + package func isValidSignature(_ signature: S, for data: D) -> Bool { let context: Data? = nil return self.backing.isValidSignature(signature, for: data, context: context) } @@ -238,7 +275,7 @@ extension MLDSA${parameter_set} { /// - context: The context to use for the signature verification. /// /// - Returns: `true` if the signature is valid, `false` otherwise. - func isValidSignature( + package func isValidSignature( _ signature: S, for data: D, context: C @@ -246,6 +283,27 @@ extension MLDSA${parameter_set} { self.backing.isValidSignature(signature, for: data, context: context) } + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash(for data: D) throws -> Data { + let context: Data? = nil + return try self.backing.prehash(for: data, context: context) + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + package func prehash(for data: D, context: C) throws -> Data { + try self.backing.prehash(for: data, context: context) + } + /// The size of the public key in bytes. static let byteCount = Backing.byteCount @@ -261,10 +319,10 @@ extension MLDSA${parameter_set} { /// /// - Parameter rawRepresentation: The public key bytes. /// - /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. + /// - Throws: `CryptoBoringWrapperError.incorrectKeySize` if the raw representation is not the correct size. init(rawRepresentation: some DataProtocol) throws { - guard rawRepresentation.count == MLDSA${parameter_set}.InternalPublicKey.Backing.byteCount else { - throw CryptoKitError.incorrectKeySize + guard rawRepresentation.count == BoringSSLMLDSA${parameter_set}.InternalPublicKey.Backing.byteCount else { + throw CryptoBoringWrapperError.incorrectKeySize } self.key = .init() @@ -277,7 +335,7 @@ extension MLDSA${parameter_set} { try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in var cbs = CBS(data: buffer.baseAddress, len: buffer.count) guard CCryptoBoringSSL_MLDSA${parameter_set}_parse_public_key(&self.key, &cbs) == 1 else { - throw CryptoKitError.internalBoringSSLError() + throw CryptoBoringWrapperError.internalBoringSSLError() } } } @@ -287,7 +345,7 @@ extension MLDSA${parameter_set} { var rawRepresentation: Data { var cbb = CBB() // The following BoringSSL functions can only fail on allocation failure, which we define as impossible. - CCryptoBoringSSL_CBB_init(&cbb, MLDSA${parameter_set}.InternalPublicKey.Backing.byteCount) + CCryptoBoringSSL_CBB_init(&cbb, BoringSSLMLDSA${parameter_set}.InternalPublicKey.Backing.byteCount) defer { CCryptoBoringSSL_CBB_cleanup(&cbb) } CCryptoBoringSSL_MLDSA${parameter_set}_marshal_public_key(&cbb, &self.key) return Data(bytes: CCryptoBoringSSL_CBB_data(&cbb), count: CCryptoBoringSSL_CBB_len(&cbb)) @@ -327,6 +385,41 @@ extension MLDSA${parameter_set} { } } + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + func prehash(for data: D, context: C?) throws -> Data { + var mu = Data(repeating: 0, count: BoringSSLMLDSA.muByteCount) + + let dataBytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) + let rc: CInt = mu.withUnsafeMutableBytes { muPtr in + dataBytes.withUnsafeBytes { dataPtr in + context.withUnsafeBytes { contextPtr in + var prehash = MLDSA${parameter_set}_prehash() + let rc = CCryptoBoringSSL_MLDSA${parameter_set}_prehash_init( + &prehash, + &key, + contextPtr.baseAddress, + contextPtr.count + ) + CCryptoBoringSSL_MLDSA${parameter_set}_prehash_update(&prehash, dataPtr.baseAddress, dataPtr.count) + CCryptoBoringSSL_MLDSA${parameter_set}_prehash_finalize(muPtr.baseAddress, &prehash) + return rc + } + } + } + + guard rc == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return mu + } + /// The size of the public key in bytes. static let byteCount = Int(MLDSA${parameter_set}_PUBLIC_KEY_BYTES) } @@ -334,15 +427,16 @@ extension MLDSA${parameter_set} { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension MLDSA${parameter_set} { +extension BoringSSLMLDSA${parameter_set} { /// The size of the signature in bytes. private static let signatureByteCount = Int(MLDSA${parameter_set}_SIGNATURE_BYTES) } % end -enum MLDSA { +package enum BoringSSLMLDSA { /// The size of the seed in bytes. - static let seedByteCount = 32 -} + package static let seedByteCount = 32 -#endif // CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + /// The size of the "mu" value in bytes. + fileprivate static let muByteCount = 64 +} diff --git a/Sources/Crypto/Util/BoringSSL/Optional+withUnsafeBytes_boring.swift b/Sources/CryptoBoringWrapper/Util/Optional+withUnsafeBytes.swift similarity index 86% rename from Sources/Crypto/Util/BoringSSL/Optional+withUnsafeBytes_boring.swift rename to Sources/CryptoBoringWrapper/Util/Optional+withUnsafeBytes.swift index 8d8aabaca..e06f884fa 100644 --- a/Sources/Crypto/Util/BoringSSL/Optional+withUnsafeBytes_boring.swift +++ b/Sources/CryptoBoringWrapper/Util/Optional+withUnsafeBytes.swift @@ -19,7 +19,9 @@ import Foundation #endif extension Optional where Wrapped: DataProtocol { - func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ReturnValue) rethrows -> ReturnValue { + package func withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws -> ReturnValue + ) rethrows -> ReturnValue { if let self { let bytes: ContiguousBytes = self.regions.count == 1 ? self.regions.first! : Array(self) return try bytes.withUnsafeBytes { try body($0) } diff --git a/Sources/CryptoExtras/CMakeLists.txt b/Sources/CryptoExtras/CMakeLists.txt index 8498dc443..b060c4313 100644 --- a/Sources/CryptoExtras/CMakeLists.txt +++ b/Sources/CryptoExtras/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(CryptoExtras "Key Derivation/PBKDF2/PBKDF2.swift" "Key Derivation/Scrypt/BoringSSL/Scrypt_boring.swift" "Key Derivation/Scrypt/Scrypt.swift" + "MLDSA/MLDSA+externalMu.swift" "OPRFs/OPRF.swift" "OPRFs/OPRFClient.swift" "OPRFs/OPRFServer.swift" diff --git a/Sources/CryptoExtras/MLDSA/MLDSA+externalMu.swift b/Sources/CryptoExtras/MLDSA/MLDSA+externalMu.swift new file mode 100644 index 000000000..ba0d555ba --- /dev/null +++ b/Sources/CryptoExtras/MLDSA/MLDSA+externalMu.swift @@ -0,0 +1,130 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + +import Crypto +import CryptoBoringWrapper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +extension MLDSA65.PrivateKey { + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``MLDSA65/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + public func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA65.InternalPrivateKey(seedRepresentation: self.seedRepresentation) + .signature(forPrehashedMessageRepresentative: mu) + #else + try self.signature_boring(forPrehashedMessageRepresentative: mu) + #endif + } +} + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +extension MLDSA65.PublicKey { + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + public func prehash(for data: D) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA65.InternalPublicKey(rawRepresentation: self.rawRepresentation) + .prehash(for: data) + #else + try self.prehash_boring(for: data) + #endif + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + public func prehash(for data: D, context: C) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA65.InternalPublicKey(rawRepresentation: self.rawRepresentation) + .prehash(for: data, context: context) + #else + try self.prehash_boring(for: data, context: context) + #endif + } +} + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +extension MLDSA87.PrivateKey { + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``MLDSA87/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + public func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA87.InternalPrivateKey(seedRepresentation: self.seedRepresentation) + .signature(forPrehashedMessageRepresentative: mu) + #else + try self.signature_boring(forPrehashedMessageRepresentative: mu) + #endif + } +} + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +extension MLDSA87.PublicKey { + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + public func prehash(for data: D) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA87.InternalPublicKey(rawRepresentation: self.rawRepresentation) + .prehash(for: data) + #else + try self.prehash_boring(for: data) + #endif + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + public func prehash(for data: D, context: C) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA87.InternalPublicKey(rawRepresentation: self.rawRepresentation) + .prehash(for: data, context: context) + #else + try self.prehash_boring(for: data, context: context) + #endif + } +} diff --git a/Sources/CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb b/Sources/CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb new file mode 100644 index 000000000..daa9a4d6a --- /dev/null +++ b/Sources/CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + +import Crypto +import CryptoBoringWrapper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif +%{ + parameter_sets = ["65", "87"] +}% +% for parameter_set in parameter_sets: + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +extension MLDSA${parameter_set}.PrivateKey { + /// Generate a signature for the prehashed message representative (a.k.a. "external mu"). + /// + /// > Note: The message representative should be obtained via calls to ``MLDSA${parameter_set}/PublicKey/prehash(for:context:)``. + /// + /// - Parameter mu: The prehashed message representative (a.k.a. "external mu"). + /// + /// - Returns: The signature of the prehashed message representative. + public func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA${parameter_set}.InternalPrivateKey(seedRepresentation: self.seedRepresentation) + .signature(forPrehashedMessageRepresentative: mu) + #else + try self.signature_boring(forPrehashedMessageRepresentative: mu) + #endif + } +} + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +extension MLDSA${parameter_set}.PublicKey { + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameter data: The message to prehash. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + public func prehash(for data: D) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA${parameter_set}.InternalPublicKey(rawRepresentation: self.rawRepresentation) + .prehash(for: data) + #else + try self.prehash_boring(for: data) + #endif + } + + /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. + /// + /// - Parameters: + /// - data: The message to prehash. + /// - context: The context of the message. + /// + /// - Returns: The prehashed message representative (a.k.a. "external mu"). + public func prehash(for data: D, context: C) throws -> Data { + #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API + try BoringSSLMLDSA${parameter_set}.InternalPublicKey(rawRepresentation: self.rawRepresentation) + .prehash(for: data, context: context) + #else + try self.prehash_boring(for: data, context: context) + #endif + } +} +% end diff --git a/Tests/CryptoExtrasTests/MLDSATests.swift b/Tests/CryptoExtrasTests/MLDSATests.swift index cbf49a809..f1394edad 100644 --- a/Tests/CryptoExtrasTests/MLDSATests.swift +++ b/Tests/CryptoExtrasTests/MLDSATests.swift @@ -12,12 +12,11 @@ // //===----------------------------------------------------------------------===// -#if !canImport(Darwin) || canImport(CryptoKit, _version: 324.0.4) - import XCTest @testable import CryptoExtras +#if !canImport(Darwin) || canImport(CryptoKit, _version: 324.0.4) final class MLDSATests: XCTestCase { func testMLDSA65Signing() throws { guard #available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.0, *) else { @@ -356,3 +355,32 @@ extension MLDSA87.PublicKey { } #endif // SDK has MLDSA + +@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *) +final class MLDSAExternalMuTests: XCTestCase { + func testMLDSA65PrehashedSigning() throws { + let message = "Hello, world!".data(using: .utf8)! + let context = "ctx".data(using: .utf8)! + + let key = try MLDSA65.PrivateKey() + let publicKey = key.publicKey + + let mu = try publicKey.prehash(for: message, context: context) + + let muSignature = try key.signature(forPrehashedMessageRepresentative: mu) + XCTAssertTrue(publicKey.isValidSignature(muSignature, for: message, context: context)) + } + + func testMLDSA87PrehashedSigning() throws { + let message = "Hello, world!".data(using: .utf8)! + let context = "ctx".data(using: .utf8)! + + let key = try MLDSA87.PrivateKey() + let publicKey = key.publicKey + + let mu = try publicKey.prehash(for: message, context: context) + + let muSignature = try key.signature(forPrehashedMessageRepresentative: mu) + XCTAssertTrue(publicKey.isValidSignature(muSignature, for: message, context: context)) + } +}