diff --git a/Package.swift b/Package.swift index 3d92c685..a31f1914 100644 --- a/Package.swift +++ b/Package.swift @@ -160,7 +160,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 diff --git a/Sources/Crypto/Docs.docc/index.md b/Sources/Crypto/Docs.docc/index.md index 93185778..3991f792 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/KEM/MLKEM.swift b/Sources/Crypto/KEM/MLKEM.swift index 3883fbd4..248439f8 100644 --- a/Sources/Crypto/KEM/MLKEM.swift +++ b/Sources/Crypto/KEM/MLKEM.swift @@ -32,7 +32,6 @@ typealias MLKEMPublicKeyImpl = OpenSSLMLKEMPublicKeyImpl typealias MLKEMPrivateKeyImpl = OpenSSLMLKEMPrivateKeyImpl #endif - /// The Module-Lattice key encapsulation mechanism (KEM). @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum MLKEM768: Sendable {} @@ -53,7 +52,7 @@ extension MLKEM768 { /// A serialized representation of the public key. public var rawRepresentation: Data { get { - return self.impl.rawRepresentation + self.impl.rawRepresentation } } @@ -61,11 +60,11 @@ extension MLKEM768 { /// /// - Returns: an encapsulated shared secret, that you decapsulate by calling ``MLKEM768/PrivateKey/decapsulate(_:)`` on the corresponding private key. public func encapsulate() throws -> KEM.EncapsulationResult { - return try self.impl.encapsulate() + try self.impl.encapsulate() } func encapsulateWithSeed(encapSeed: Data) throws -> KEM.EncapsulationResult { - return try self.impl.encapsulateWithSeed(encapSeed) + try self.impl.encapsulateWithSeed(encapSeed) } } @@ -104,7 +103,10 @@ extension MLKEM768 { if publicKey != nil { publicKeyRawRepresentation = publicKey!.rawRepresentation } - self.impl = try MLKEMPrivateKeyImpl(seedRepresentation: seedRepresentation, publicKeyRawRepresentation: publicKeyRawRepresentation) + self.impl = try MLKEMPrivateKeyImpl( + seedRepresentation: seedRepresentation, + publicKeyRawRepresentation: publicKeyRawRepresentation + ) } /// The private key's seed representation. @@ -112,7 +114,7 @@ extension MLKEM768 { /// The seed is `d||z`, as specified in the algorithm `ML-KEM.KeyGen_internal(d,z)` (Algorithm 16) of FIPS 203. public var seedRepresentation: Data { get { - return self.impl.seedRepresentation + self.impl.seedRepresentation } } @@ -122,13 +124,13 @@ extension MLKEM768 { /// - encapsulated: An encapsulated shared secret, that you get by calling ``MLKEM768/PublicKey/encapsulate()`` on the corresponding public key. /// - Returns: The shared secret. public func decapsulate(_ encapsulated: D) throws -> SymmetricKey { - return try impl.decapsulate(encapsulated: encapsulated) + try impl.decapsulate(encapsulated: encapsulated) } /// The corresponding public key. public var publicKey: MLKEM768.PublicKey { get { - try self.impl.publicKey + self.impl.publicKey } } @@ -140,7 +142,8 @@ extension MLKEM768 { throw KEM.Errors.invalidSeed } let seed = Data(integrityCheckedRepresentation).subdata(in: 0...seedSize) - let publicKeyHashData = Data(integrityCheckedRepresentation).subdata(in: MLKEMPrivateKeyImpl.seedSize...seedSize..(seedRepresentation: seed, publicKeyHash: publicKeyHash) @@ -151,13 +154,12 @@ extension MLKEM768 { /// This representation includes the seed value, and a hash of the corresponding public key. public var integrityCheckedRepresentation: Data { get { - return self.impl.integrityCheckedRepresentation + self.impl.integrityCheckedRepresentation } } } } - /// The Module-Lattice key encapsulation mechanism (KEM). @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum MLKEM1024: Sendable {} @@ -178,7 +180,7 @@ extension MLKEM1024 { /// A serialized representation of the public key. public var rawRepresentation: Data { get { - return self.impl.rawRepresentation + self.impl.rawRepresentation } } @@ -186,11 +188,11 @@ extension MLKEM1024 { /// /// - Returns: an encapsulated shared secret, that you decapsulate by calling ``MLKEM1024/PrivateKey/decapsulate(_:)`` on the corresponding private key. public func encapsulate() throws -> KEM.EncapsulationResult { - return try self.impl.encapsulate() + try self.impl.encapsulate() } func encapsulateWithSeed(encapSeed: Data) throws -> KEM.EncapsulationResult { - return try self.impl.encapsulateWithSeed(encapSeed) + try self.impl.encapsulateWithSeed(encapSeed) } } @@ -229,7 +231,10 @@ extension MLKEM1024 { if publicKey != nil { publicKeyRawRepresentation = publicKey!.rawRepresentation } - self.impl = try MLKEMPrivateKeyImpl(seedRepresentation: seedRepresentation, publicKeyRawRepresentation: publicKeyRawRepresentation) + self.impl = try MLKEMPrivateKeyImpl( + seedRepresentation: seedRepresentation, + publicKeyRawRepresentation: publicKeyRawRepresentation + ) } /// The private key's seed representation. @@ -237,7 +242,7 @@ extension MLKEM1024 { /// The seed is `d||z`, as specified in the algorithm `ML-KEM.KeyGen_internal(d,z)` (Algorithm 16) of FIPS 203. public var seedRepresentation: Data { get { - return self.impl.seedRepresentation + self.impl.seedRepresentation } } @@ -247,13 +252,13 @@ extension MLKEM1024 { /// - encapsulated: An encapsulated shared secret, that you get by calling ``MLKEM1024/PublicKey/encapsulate()`` on the corresponding public key. /// - Returns: The shared secret. public func decapsulate(_ encapsulated: D) throws -> SymmetricKey { - return try impl.decapsulate(encapsulated: encapsulated) + try impl.decapsulate(encapsulated: encapsulated) } /// The corresponding public key. public var publicKey: MLKEM1024.PublicKey { get { - try self.impl.publicKey + self.impl.publicKey } } @@ -265,7 +270,8 @@ extension MLKEM1024 { throw KEM.Errors.invalidSeed } let seed = Data(integrityCheckedRepresentation).subdata(in: 0...seedSize) - let publicKeyHashData = Data(integrityCheckedRepresentation).subdata(in: MLKEMPrivateKeyImpl.seedSize...seedSize..(seedRepresentation: seed, publicKeyHash: publicKeyHash) @@ -276,11 +282,10 @@ extension MLKEM1024 { /// This representation includes the seed value, and a hash of the corresponding public key. public var integrityCheckedRepresentation: Data { get { - return self.impl.integrityCheckedRepresentation + self.impl.integrityCheckedRepresentation } } } } - -#endif // Linux or !SwiftPM +#endif // Linux or !SwiftPM diff --git a/Sources/Crypto/KEM/MLKEM.swift.gyb b/Sources/Crypto/KEM/MLKEM.swift.gyb index 3a171943..a5d5417f 100644 --- a/Sources/Crypto/KEM/MLKEM.swift.gyb +++ b/Sources/Crypto/KEM/MLKEM.swift.gyb @@ -24,10 +24,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 MLKEMPublicKeyImpl = CoreCryptoMLKEMPublicKeyImpl +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLKEMPrivateKeyImpl = CoreCryptoMLKEMPrivateKeyImpl #else +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLKEMPublicKeyImpl = OpenSSLMLKEMPublicKeyImpl +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) typealias MLKEMPrivateKeyImpl = OpenSSLMLKEMPrivateKeyImpl #endif @@ -36,12 +40,14 @@ typealias MLKEMPrivateKeyImpl = OpenSSLMLKEMPrivateKeyImpl NAME = MLKEM_VARIANT["name"] INFO = MLKEM_VARIANT["ccinfo"] }% - /// The Module-Lattice key encapsulation mechanism (KEM). +@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} { /// A public key you use to encapsulate shared secrets with the Module-Lattice key encapsulation mechanism. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct PublicKey: KEMPublicKey, Sendable { var impl: MLKEMPublicKeyImpl<${NAME}> @@ -54,7 +60,7 @@ extension ${NAME} { /// A serialized representation of the public key. public var rawRepresentation: Data { get { - return self.impl.rawRepresentation + self.impl.rawRepresentation } } @@ -62,15 +68,16 @@ extension ${NAME} { /// /// - Returns: an encapsulated shared secret, that you decapsulate by calling ``${NAME}/PrivateKey/decapsulate(_:)`` on the corresponding private key. public func encapsulate() throws -> KEM.EncapsulationResult { - return try self.impl.encapsulate() + try self.impl.encapsulate() } func encapsulateWithSeed(encapSeed: Data) throws -> KEM.EncapsulationResult { - return try self.impl.encapsulateWithSeed(encapSeed) + try self.impl.encapsulateWithSeed(encapSeed) } } /// A private key you use to decapsulate shared secrets with the Module-Lattice key encapsulation mechanism. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct PrivateKey: KEMPrivateKey { internal let impl: MLKEMPrivateKeyImpl<${NAME}> @@ -104,7 +111,10 @@ extension ${NAME} { if publicKey != nil { publicKeyRawRepresentation = publicKey!.rawRepresentation } - self.impl = try MLKEMPrivateKeyImpl<${NAME}>(seedRepresentation: seedRepresentation, publicKeyRawRepresentation: publicKeyRawRepresentation) + self.impl = try MLKEMPrivateKeyImpl<${NAME}>( + seedRepresentation: seedRepresentation, + publicKeyRawRepresentation: publicKeyRawRepresentation + ) } /// The private key's seed representation. @@ -112,7 +122,7 @@ extension ${NAME} { /// The seed is `d||z`, as specified in the algorithm `ML-KEM.KeyGen_internal(d,z)` (Algorithm 16) of FIPS 203. public var seedRepresentation: Data { get { - return self.impl.seedRepresentation + self.impl.seedRepresentation } } @@ -122,13 +132,13 @@ extension ${NAME} { /// - encapsulated: An encapsulated shared secret, that you get by calling ``${NAME}/PublicKey/encapsulate()`` on the corresponding public key. /// - Returns: The shared secret. public func decapsulate(_ encapsulated: D) throws -> SymmetricKey { - return try impl.decapsulate(encapsulated: encapsulated) + try impl.decapsulate(encapsulated: encapsulated) } /// The corresponding public key. public var publicKey: ${NAME}.PublicKey { get { - try self.impl.publicKey + self.impl.publicKey } } @@ -140,7 +150,8 @@ extension ${NAME} { throw KEM.Errors.invalidSeed } let seed = Data(integrityCheckedRepresentation).subdata(in: 0...seedSize) - let publicKeyHashData = Data(integrityCheckedRepresentation).subdata(in: MLKEMPrivateKeyImpl<${NAME}>.seedSize...seedSize..(seedRepresentation: seed, publicKeyHash: publicKeyHash) @@ -151,12 +162,11 @@ extension ${NAME} { /// This representation includes the seed value, and a hash of the corresponding public key. public var integrityCheckedRepresentation: Data { get { - return self.impl.integrityCheckedRepresentation + self.impl.integrityCheckedRepresentation } } } } % end - -#endif // Linux or !SwiftPM +#endif // Linux or !SwiftPM diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift index bfba9304..a2d9ad4b 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift @@ -26,13 +26,6 @@ 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, *) extension MLDSA65 { /// A ML-DSA-65 private key. @@ -84,6 +77,17 @@ extension MLDSA65 { 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 ``MLDSA65/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 { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -184,6 +188,38 @@ extension MLDSA65 { 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 ``MLDSA65/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 == MLDSA.muByteCount else { + throw CryptoKitError.incorrectParameterSize + } + + var signature = Data(repeating: 0, count: MLDSA65.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 CryptoKitError.internalBoringSSLError() + } + + return signature + } + /// The size of the private key in bytes. static let byteCount = Int(MLDSA65_PRIVATE_KEY_BYTES) } @@ -242,6 +278,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"). + 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"). + 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 @@ -323,6 +380,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: MLDSA.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 CryptoKitError.internalBoringSSLError() + } + + return mu + } + /// The size of the public key in bytes. static let byteCount = Int(MLDSA65_PUBLIC_KEY_BYTES) } @@ -386,6 +478,17 @@ extension MLDSA87 { 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 ``MLDSA87/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 { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -486,6 +589,38 @@ extension MLDSA87 { 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 ``MLDSA87/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 == MLDSA.muByteCount else { + throw CryptoKitError.incorrectParameterSize + } + + var signature = Data(repeating: 0, count: MLDSA87.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 CryptoKitError.internalBoringSSLError() + } + + return signature + } + /// The size of the private key in bytes. static let byteCount = Int(MLDSA87_PRIVATE_KEY_BYTES) } @@ -544,6 +679,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"). + 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"). + 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 @@ -625,6 +781,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: MLDSA.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 CryptoKitError.internalBoringSSLError() + } + + return mu + } + /// The size of the public key in bytes. static let byteCount = Int(MLDSA87_PUBLIC_KEY_BYTES) } @@ -640,6 +831,9 @@ extension MLDSA87 { enum MLDSA { /// The size of the seed in bytes. fileprivate static let seedByteCount = 32 + + /// The size of the "mu" value in bytes. + fileprivate static let muByteCount = 64 } #endif // CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb index ee0b1f0c..33326f6b 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb @@ -25,13 +25,6 @@ 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"] }% @@ -88,6 +81,17 @@ extension MLDSA${parameter_set} { 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 ``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. + 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 @@ -188,6 +192,38 @@ extension MLDSA${parameter_set} { 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 ``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. + func signature(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { + guard mu.count == MLDSA.muByteCount else { + throw CryptoKitError.incorrectParameterSize + } + + var signature = Data(repeating: 0, count: MLDSA${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 CryptoKitError.internalBoringSSLError() + } + + return signature + } + /// The size of the private key in bytes. static let byteCount = Int(MLDSA${parameter_set}_PRIVATE_KEY_BYTES) } @@ -246,6 +282,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"). + 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"). + 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 @@ -327,6 +384,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: MLDSA.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 CryptoKitError.internalBoringSSLError() + } + + return mu + } + /// The size of the public key in bytes. static let byteCount = Int(MLDSA${parameter_set}_PUBLIC_KEY_BYTES) } @@ -343,6 +435,9 @@ extension MLDSA${parameter_set} { enum MLDSA { /// The size of the seed in bytes. fileprivate static let seedByteCount = 32 + + /// The size of the "mu" value in bytes. + fileprivate static let muByteCount = 64 } #endif // CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift index 671847b3..52651256 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift @@ -34,6 +34,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 +50,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, *) @@ -106,6 +112,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) } @@ -149,6 +159,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 9f5842df..b0fba688 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -30,7 +30,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 {} @@ -56,7 +55,11 @@ extension MLDSA65 { /// - data: The signed data. /// - context: Context for the signature. /// - Returns: `true` if the signature is valid in the specified context, `false` otherwise. - public func isValidSignature(signature: S, for data: D, context: C) -> Bool { + public func isValidSignature( + signature: S, + for data: D, + context: C + ) -> Bool { self.impl.isValidSignature(signature: signature, for: data, context: context) } @@ -80,6 +83,36 @@ extension MLDSA65 { fileprivate init(impl: MLDSAPublicKeyImpl) { self.impl = impl } + + /// 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 + } + } } /// The private key for MLDSA65. @@ -105,6 +138,17 @@ extension MLDSA65 { try self.impl.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 ``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) + } + /// The associated public key. public var publicKey: PublicKey { get { @@ -135,7 +179,7 @@ extension MLDSA65 { /// for the `ML-DSA.KeyGen_internal` algorithm (Algorithm 16) of FIPS 204. public var seedRepresentation: Data { get { - return self.impl.seedRepresentation + self.impl.seedRepresentation } } @@ -152,13 +196,29 @@ extension MLDSA65 { /// This representation is 64 bytes long, and contains the seed and a hash of the public key. public var integrityCheckedRepresentation: Data { get { - return self.impl.integrityCheckedRepresentation + self.impl.integrityCheckedRepresentation + } + } + + private init(impl: MLDSAPrivateKeyImpl) { + self.impl = impl + } + + 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 } } } } - /// The MLDSA87 Digital Signature Algorithm @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum MLDSA87: Sendable {} @@ -184,7 +244,11 @@ extension MLDSA87 { /// - data: The signed data. /// - context: Context for the signature. /// - Returns: `true` if the signature is valid in the specified context, `false` otherwise. - public func isValidSignature(signature: S, for data: D, context: C) -> Bool { + public func isValidSignature( + signature: S, + for data: D, + context: C + ) -> Bool { self.impl.isValidSignature(signature: signature, for: data, context: context) } @@ -208,6 +272,36 @@ extension MLDSA87 { fileprivate init(impl: MLDSAPublicKeyImpl) { self.impl = impl } + + /// 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 + } + } } /// The private key for MLDSA87. @@ -233,6 +327,17 @@ extension MLDSA87 { try self.impl.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 ``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) + } + /// The associated public key. public var publicKey: PublicKey { get { @@ -263,7 +368,7 @@ extension MLDSA87 { /// for the `ML-DSA.KeyGen_internal` algorithm (Algorithm 16) of FIPS 204. public var seedRepresentation: Data { get { - return self.impl.seedRepresentation + self.impl.seedRepresentation } } @@ -280,11 +385,27 @@ extension MLDSA87 { /// This representation is 64 bytes long, and contains the seed and a hash of the public key. public var integrityCheckedRepresentation: Data { get { - return self.impl.integrityCheckedRepresentation + self.impl.integrityCheckedRepresentation + } + } + + private init(impl: MLDSAPrivateKeyImpl) { + self.impl = impl + } + + 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 +#endif // Linux or !SwiftPM diff --git a/Sources/Crypto/Signatures/MLDSA.swift.gyb b/Sources/Crypto/Signatures/MLDSA.swift.gyb index a0cab6fe..283863b2 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -24,7 +24,9 @@ public import Foundation typealias MLDSAPublicKeyImpl = CoreCryptoMLDSAPublicKeyImpl 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 @@ -36,10 +38,11 @@ typealias MLDSAPrivateKeyImpl = OpenSSLMLDSAPrivateKeyImpl NAME = MLDSA_VARIANT["name"] INFO = MLDSA_VARIANT["ccinfo"] }% - /// 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}. public struct PublicKey: Sendable { @@ -60,7 +63,11 @@ extension ${NAME} { /// - data: The signed data. /// - context: Context for the signature. /// - Returns: `true` if the signature is valid in the specified context, `false` otherwise. - public func isValidSignature(signature: S, for data: D, context: C) -> Bool { + public func isValidSignature( + signature: S, + for data: D, + context: C + ) -> Bool { self.impl.isValidSignature(signature: signature, for: data, context: context) } @@ -84,6 +91,36 @@ extension ${NAME} { fileprivate init(impl: MLDSAPublicKeyImpl<${NAME}>) { self.impl = impl } + + /// 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<${NAME}> { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif + } + } } /// The private key for ${NAME}. @@ -109,6 +146,17 @@ extension ${NAME} { try self.impl.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 ``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) + } + /// The associated public key. public var publicKey: PublicKey { get { @@ -139,7 +187,7 @@ extension ${NAME} { /// for the `ML-DSA.KeyGen_internal` algorithm (Algorithm 16) of FIPS 204. public var seedRepresentation: Data { get { - return self.impl.seedRepresentation + self.impl.seedRepresentation } } @@ -156,12 +204,28 @@ extension ${NAME} { /// This representation is 64 bytes long, and contains the seed and a hash of the public key. public var integrityCheckedRepresentation: Data { get { - return self.impl.integrityCheckedRepresentation + self.impl.integrityCheckedRepresentation + } + } + + private init(impl: MLDSAPrivateKeyImpl<${NAME}>) { + self.impl = impl + } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl<${NAME}> { + get throws { + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl<${NAME}>( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif } } } } % end - -#endif // Linux or !SwiftPM +#endif // Linux or !SwiftPM diff --git a/Sources/_CryptoExtras/CMakeLists.txt b/Sources/_CryptoExtras/CMakeLists.txt index 2ddd0cbe..2febaf82 100644 --- a/Sources/_CryptoExtras/CMakeLists.txt +++ b/Sources/_CryptoExtras/CMakeLists.txt @@ -41,6 +41,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/Docs.docc/index.md b/Sources/_CryptoExtras/Docs.docc/index.md index b38a23dd..50b5eca5 100644 --- a/Sources/_CryptoExtras/Docs.docc/index.md +++ b/Sources/_CryptoExtras/Docs.docc/index.md @@ -15,10 +15,6 @@ Provides additional cryptographic APIs that are not available in CryptoKit (and ### Public key cryptography - ``_RSA`` -- ``MLKEM768`` -- ``MLKEM1024`` -- ``MLDSA65`` -- ``MLDSA87`` ### Key derivation functions diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift new file mode 100644 index 00000000..76ef02ee --- /dev/null +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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 { + try self.signature_boring(forPrehashedMessageRepresentative: mu) + } +} + +@available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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 { + try self.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"). + public func prehash(for data: D, context: C) throws -> Data { + try self.prehash_boring(for: data, context: context) + } +} + +@available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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 { + try self.signature_boring(forPrehashedMessageRepresentative: mu) + } +} + +@available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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 { + try self.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"). + public func prehash(for data: D, context: C) throws -> Data { + try self.prehash_boring(for: data, context: context) + } +} diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb new file mode 100644 index 00000000..899df506 --- /dev/null +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif +%{ + parameter_sets = ["65", "87"] +}% +% for parameter_set in parameter_sets: + +@available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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 { + try self.signature_boring(forPrehashedMessageRepresentative: mu) + } +} + +@available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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 { + try self.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"). + public func prehash(for data: D, context: C) throws -> Data { + try self.prehash_boring(for: data, context: context) + } +} +%end diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index 42edd033..c529bbb9 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(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.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(signature: 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(signature: muSignature, for: message, context: context)) + } +}