From e9de693b7f32c82ef36b22d2aefb5177c3ff43fa Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Wed, 4 Jun 2025 17:36:56 +0200 Subject: [PATCH 01/17] Add an external mu variant of the ML-DSA API (65 and 87 variants) --- .../_CryptoExtras/MLDSA/MLDSA_boring.swift | 201 ++++++++++++++++++ .../MLDSA/MLDSA_boring.swift.gyb | 102 +++++++++ Tests/_CryptoExtrasTests/MLDSATests.swift | 26 +++ 3 files changed, 329 insertions(+) diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift index 51941f563..329b90ae9 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift +++ b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift @@ -75,6 +75,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. + public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -175,6 +186,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: M) 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) } @@ -233,6 +276,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"). + public 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"). + public 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 @@ -314,6 +378,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) } @@ -381,6 +480,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. + public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -481,6 +591,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: M) 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) } @@ -539,6 +681,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"). + public 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"). + public 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 @@ -620,6 +783,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) } @@ -635,4 +833,7 @@ extension MLDSA87 { private 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 } diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb index 91a86c18d..a5ddd98ee 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb +++ b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb @@ -79,6 +79,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. + public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -179,6 +190,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: M) 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) } @@ -237,6 +280,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"). + public 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"). + public 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 @@ -318,6 +382,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) } @@ -334,4 +433,7 @@ extension MLDSA${parameter_set} { private 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 } diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index e19582dc5..24ca3bb52 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -157,6 +157,32 @@ final class MLDSATests: XCTestCase { XCTAssertThrowsError(try MLDSA87.PublicKey(rawRepresentation: encodedPublicKey)) } + 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)) + } + func testMLDSA65NISTKeyGenFile() throws { try nistTest(jsonName: "mldsa_nist_keygen_65_tests") { (testVector: NISTKeyGenTestVector) in let seed = try Data(hexString: testVector.seed) From e09d1499dd245d2ce4234ab5366be68ad702b46c Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 16:01:33 +0200 Subject: [PATCH 02/17] Revert "Add an external mu variant of the ML-DSA API (65 and 87 variants)" This reverts commit e9de693b7f32c82ef36b22d2aefb5177c3ff43fa. --- .../_CryptoExtras/MLDSA/MLDSA_boring.swift | 201 ------------------ .../MLDSA/MLDSA_boring.swift.gyb | 102 --------- Tests/_CryptoExtrasTests/MLDSATests.swift | 26 --- 3 files changed, 329 deletions(-) diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift index 329b90ae9..51941f563 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift +++ b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift @@ -75,17 +75,6 @@ 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. - public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { - try self.backing.signature(forPrehashedMessageRepresentative: mu) - } - /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -186,38 +175,6 @@ 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: M) 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) } @@ -276,27 +233,6 @@ 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"). - public 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"). - public 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 @@ -378,41 +314,6 @@ 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) } @@ -480,17 +381,6 @@ 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. - public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { - try self.backing.signature(forPrehashedMessageRepresentative: mu) - } - /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -591,38 +481,6 @@ 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: M) 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) } @@ -681,27 +539,6 @@ 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"). - public 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"). - public 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 @@ -783,41 +620,6 @@ 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) } @@ -833,7 +635,4 @@ extension MLDSA87 { private 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 } diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb index a5ddd98ee..91a86c18d 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb +++ b/Sources/_CryptoExtras/MLDSA/MLDSA_boring.swift.gyb @@ -79,17 +79,6 @@ 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. - public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { - try self.backing.signature(forPrehashedMessageRepresentative: mu) - } - /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -190,38 +179,6 @@ 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: M) 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) } @@ -280,27 +237,6 @@ 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"). - public 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"). - public 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 @@ -382,41 +318,6 @@ 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) } @@ -433,7 +334,4 @@ extension MLDSA${parameter_set} { private 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 } diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index 24ca3bb52..e19582dc5 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -157,32 +157,6 @@ final class MLDSATests: XCTestCase { XCTAssertThrowsError(try MLDSA87.PublicKey(rawRepresentation: encodedPublicKey)) } - 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)) - } - func testMLDSA65NISTKeyGenFile() throws { try nistTest(jsonName: "mldsa_nist_keygen_65_tests") { (testVector: NISTKeyGenTestVector) in let seed = try Data(hexString: testVector.seed) From e25c9419c572235d798e10c39b16792d07b7883d Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Wed, 4 Jun 2025 17:36:56 +0200 Subject: [PATCH 03/17] Add an external mu variant of the ML-DSA API (65 and 87 variants) --- .../Signatures/BoringSSL/MLDSA_boring.swift | 201 ++++++++++++++++++ .../BoringSSL/MLDSA_boring.swift.gyb | 102 +++++++++ Tests/_CryptoExtrasTests/MLDSATests.swift | 26 +++ 3 files changed, 329 insertions(+) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift index f784d36f2..6967cfa97 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift @@ -76,6 +76,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. + public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -176,6 +187,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: M) 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) } @@ -234,6 +277,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"). + public 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"). + public 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 @@ -315,6 +379,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) } @@ -378,6 +477,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. + public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -478,6 +588,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: M) 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) } @@ -536,6 +678,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"). + public 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"). + public 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 @@ -617,6 +780,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) } @@ -632,6 +830,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 ed70dd236..c5b705e91 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb @@ -80,6 +80,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. + public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { + try self.backing.signature(forPrehashedMessageRepresentative: mu) + } + /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -180,6 +191,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: M) 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) } @@ -238,6 +281,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"). + public 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"). + public 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 @@ -319,6 +383,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) } @@ -335,6 +434,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/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index 01ddbd743..1a6265d47 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -185,6 +185,32 @@ final class MLDSATests: XCTestCase { XCTAssertThrowsError(try MLDSA87.PublicKey(rawRepresentation: encodedPublicKey)) } + 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)) + } + func testMLDSA65NISTKeyGenFile() throws { guard #available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.0, *) else { throw XCTSkip("MLDSA is only available on iOS 19.0+, macOS 16.0+, watchOS 12.0+, tvOS 19.0+, visionOS 3.0+") From e016958bcd9ac07e96a16163941416b3d3137b8d Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 16:01:33 +0200 Subject: [PATCH 04/17] Revert "Add an external mu variant of the ML-DSA API (65 and 87 variants)" This reverts commit e9de693b7f32c82ef36b22d2aefb5177c3ff43fa. --- .../Signatures/BoringSSL/MLDSA_boring.swift | 201 ------------------ .../BoringSSL/MLDSA_boring.swift.gyb | 102 --------- Tests/_CryptoExtrasTests/MLDSATests.swift | 26 --- 3 files changed, 329 deletions(-) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift index 6967cfa97..f784d36f2 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift @@ -76,17 +76,6 @@ 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. - public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { - try self.backing.signature(forPrehashedMessageRepresentative: mu) - } - /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -187,38 +176,6 @@ 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: M) 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) } @@ -277,27 +234,6 @@ 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"). - public 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"). - public 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 @@ -379,41 +315,6 @@ 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) } @@ -477,17 +378,6 @@ 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. - public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { - try self.backing.signature(forPrehashedMessageRepresentative: mu) - } - /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -588,38 +478,6 @@ 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: M) 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) } @@ -678,27 +536,6 @@ 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"). - public 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"). - public 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 @@ -780,41 +617,6 @@ 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) } @@ -830,9 +632,6 @@ 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 c5b705e91..ed70dd236 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb @@ -80,17 +80,6 @@ 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. - public func signature(forPrehashedMessageRepresentative mu: M) throws -> Data { - try self.backing.signature(forPrehashedMessageRepresentative: mu) - } - /// The size of the private key in bytes. static let byteCount = Backing.byteCount @@ -191,38 +180,6 @@ 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: M) 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) } @@ -281,27 +238,6 @@ 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"). - public 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"). - public 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 @@ -383,41 +319,6 @@ 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) } @@ -434,9 +335,6 @@ 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/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index 1a6265d47..01ddbd743 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -185,32 +185,6 @@ final class MLDSATests: XCTestCase { XCTAssertThrowsError(try MLDSA87.PublicKey(rawRepresentation: encodedPublicKey)) } - 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)) - } - func testMLDSA65NISTKeyGenFile() throws { guard #available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, visionOS 3.0, *) else { throw XCTSkip("MLDSA is only available on iOS 19.0+, macOS 16.0+, watchOS 12.0+, tvOS 19.0+, visionOS 3.0+") From 74c97decf69e9e8f7289aca2c5f6a7f92d0fa9b1 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 20:23:39 +0200 Subject: [PATCH 05/17] Add external mu variant of ML-DSA to BoringSSL implementation --- .../Signatures/BoringSSL/MLDSA_boring.swift | 201 ++++++++++++++++++ .../BoringSSL/MLDSA_boring.swift.gyb | 102 +++++++++ 2 files changed, 303 insertions(+) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift index f784d36f2..634c758de 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift @@ -76,6 +76,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 @@ -176,6 +187,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) } @@ -234,6 +277,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 @@ -315,6 +379,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) } @@ -378,6 +477,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 @@ -478,6 +588,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) } @@ -536,6 +678,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 @@ -617,6 +780,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) } @@ -632,6 +830,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 ed70dd236..2a7a62d01 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb @@ -80,6 +80,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 @@ -180,6 +191,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) } @@ -238,6 +281,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 @@ -319,6 +383,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) } @@ -335,6 +434,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 From a6213d750d0b56e8b53bed14663d116074d1560c Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 20:30:31 +0200 Subject: [PATCH 06/17] Add external mu API to wrapper with `package` level --- .../Signatures/BoringSSL/MLDSA_wrapper.swift | 18 ++++ Sources/Crypto/Signatures/MLDSA.swift | 88 +++++++++++++++++++ Sources/Crypto/Signatures/MLDSA.swift.gyb | 48 ++++++++++ 3 files changed, 154 insertions(+) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift index 5d18b7d74..ddacae21f 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift @@ -30,6 +30,8 @@ protocol BoringSSLBackedMLDSAPrivateKey { 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 } @@ -44,6 +46,10 @@ protocol BoringSSLBackedMLDSAPublicKey { 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, *) @@ -102,6 +108,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) } @@ -145,6 +155,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 0a079ca45..4d16b1281 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -76,6 +76,32 @@ 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 { + let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + return try implementation.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 { + let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + return try implementation.prehash_boring(for: data, context: context) + } } /// The private key for MLDSA65. @@ -101,6 +127,20 @@ 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 { + let implementation = self.impl is OpenSSLMLDSAPrivateKeyImpl + ? self.impl + : try OpenSSLMLDSAPrivateKeyImpl(seedRepresentation: self.seedRepresentation, publicKey: nil) + return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) + } + /// The associated public key. public var publicKey: PublicKey { get { @@ -151,6 +191,10 @@ extension MLDSA65 { return self.impl.integrityCheckedRepresentation } } + + fileprivate init(impl: MLDSAPrivateKeyImpl) { + self.impl = impl + } } } @@ -204,6 +248,32 @@ 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 { + let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + return try implementation.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 { + let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + return try implementation.prehash_boring(for: data, context: context) + } } /// The private key for MLDSA87. @@ -229,6 +299,20 @@ 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 { + let implementation = self.impl is OpenSSLMLDSAPrivateKeyImpl + ? self.impl + : try OpenSSLMLDSAPrivateKeyImpl(seedRepresentation: self.seedRepresentation, publicKey: nil) + return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) + } + /// The associated public key. public var publicKey: PublicKey { get { @@ -279,6 +363,10 @@ extension MLDSA87 { return self.impl.integrityCheckedRepresentation } } + + fileprivate init(impl: MLDSAPrivateKeyImpl) { + self.impl = impl + } } } diff --git a/Sources/Crypto/Signatures/MLDSA.swift.gyb b/Sources/Crypto/Signatures/MLDSA.swift.gyb index 9481fc670..3d136b541 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -20,7 +20,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 @@ -34,8 +36,10 @@ typealias MLDSAPrivateKeyImpl = OpenSSLMLDSAPrivateKeyImpl }% /// 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 { @@ -80,6 +84,32 @@ 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 { + let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) + return try implementation.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 { + let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) + return try implementation.prehash_boring(for: data, context: context) + } } /// The private key for ${NAME}. @@ -105,6 +135,20 @@ 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 { + let implementation = self.impl is OpenSSLMLDSAPrivateKeyImpl<${NAME}> + ? self.impl + : try OpenSSLMLDSAPrivateKeyImpl<${NAME}>(seedRepresentation: self.seedRepresentation, publicKey: nil) + return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) + } + /// The associated public key. public var publicKey: PublicKey { get { @@ -155,6 +199,10 @@ extension ${NAME} { return self.impl.integrityCheckedRepresentation } } + + fileprivate init(impl: MLDSAPrivateKeyImpl<${NAME}>) { + self.impl = impl + } } } From bc935acaf387e6aae79b73daac2a241d66851727 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 20:31:03 +0200 Subject: [PATCH 07/17] Expose external mu API in CryptoExtras --- .../MLDSA/MLDSA+externalMu.swift | 94 +++++++++++++++++++ .../MLDSA/MLDSA+externalMu.swift.gyb | 62 ++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift create mode 100644 Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift new file mode 100644 index 000000000..9f1c4ad90 --- /dev/null +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift @@ -0,0 +1,94 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation + +@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 { + return 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 { + return 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 000000000..01103c26c --- /dev/null +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation +%{ + 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 { + return 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 From f8ed3ee344fe72f2dc4a23aa49842ec4be352ca7 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 20:31:21 +0200 Subject: [PATCH 08/17] Add tests for external mu variant --- Tests/_CryptoExtrasTests/MLDSATests.swift | 34 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index 01ddbd743..b1e518595 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 +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)) + } +} \ No newline at end of file From 64aa6ca40aa2dbbb8882862af20fb1a178d08b74 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 20:37:34 +0200 Subject: [PATCH 09/17] Small formatting fixes --- .../Signatures/BoringSSL/MLDSA_boring.swift | 9 ++-- .../BoringSSL/MLDSA_boring.swift.gyb | 9 ++-- Sources/Crypto/Signatures/MLDSA.swift | 43 ++++++++++++------- Sources/Crypto/Signatures/MLDSA.swift.gyb | 23 ++++++---- 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift index 634c758de..ebc3f3362 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift @@ -12,16 +12,13 @@ // //===----------------------------------------------------------------------===// -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -@_exported import CryptoKit -#else -@_implementationOnly import CCryptoBoringSSL -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. +#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API +@_exported import CryptoKit +#else @_implementationOnly import CCryptoBoringSSL import Foundation diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb index 2a7a62d01..25170f1b2 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_boring.swift.gyb @@ -12,16 +12,13 @@ // //===----------------------------------------------------------------------===// -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -@_exported import CryptoKit -#else -@_implementationOnly import CCryptoBoringSSL -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. +#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API +@_exported import CryptoKit +#else @_implementationOnly import CCryptoBoringSSL import Foundation %{ diff --git a/Sources/Crypto/Signatures/MLDSA.swift b/Sources/Crypto/Signatures/MLDSA.swift index 4d16b1281..a88ff05ce 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -26,7 +26,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 {} @@ -52,7 +51,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) } @@ -83,7 +86,8 @@ extension MLDSA65 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + let implementation = + self.impl is OpenSSLMLDSAPublicKeyImpl ? self.impl : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) return try implementation.prehash_boring(for: data) @@ -97,7 +101,8 @@ extension MLDSA65 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D, context: C) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + let implementation = + self.impl is OpenSSLMLDSAPublicKeyImpl ? self.impl : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) return try implementation.prehash_boring(for: data, context: context) @@ -135,7 +140,8 @@ extension MLDSA65 { /// /// - Returns: The signature of the prehashed message representative. package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPrivateKeyImpl + let implementation = + self.impl is OpenSSLMLDSAPrivateKeyImpl ? self.impl : try OpenSSLMLDSAPrivateKeyImpl(seedRepresentation: self.seedRepresentation, publicKey: nil) return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) @@ -171,7 +177,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 } } @@ -188,7 +194,7 @@ 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 } } @@ -198,7 +204,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 {} @@ -224,7 +229,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) } @@ -255,7 +264,8 @@ extension MLDSA87 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + let implementation = + self.impl is OpenSSLMLDSAPublicKeyImpl ? self.impl : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) return try implementation.prehash_boring(for: data) @@ -269,7 +279,8 @@ extension MLDSA87 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D, context: C) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl + let implementation = + self.impl is OpenSSLMLDSAPublicKeyImpl ? self.impl : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) return try implementation.prehash_boring(for: data, context: context) @@ -307,7 +318,8 @@ extension MLDSA87 { /// /// - Returns: The signature of the prehashed message representative. package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPrivateKeyImpl + let implementation = + self.impl is OpenSSLMLDSAPrivateKeyImpl ? self.impl : try OpenSSLMLDSAPrivateKeyImpl(seedRepresentation: self.seedRepresentation, publicKey: nil) return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) @@ -343,7 +355,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 } } @@ -360,7 +372,7 @@ 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 } } @@ -370,5 +382,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 3d136b541..0c8707ec1 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -34,7 +34,6 @@ 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 {} @@ -60,7 +59,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) } @@ -91,7 +94,8 @@ extension ${NAME} { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> + let implementation = + self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> ? self.impl : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) return try implementation.prehash_boring(for: data) @@ -105,7 +109,8 @@ extension ${NAME} { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D, context: C) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> + let implementation = + self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> ? self.impl : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) return try implementation.prehash_boring(for: data, context: context) @@ -143,7 +148,8 @@ extension ${NAME} { /// /// - Returns: The signature of the prehashed message representative. package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { - let implementation = self.impl is OpenSSLMLDSAPrivateKeyImpl<${NAME}> + let implementation = + self.impl is OpenSSLMLDSAPrivateKeyImpl<${NAME}> ? self.impl : try OpenSSLMLDSAPrivateKeyImpl<${NAME}>(seedRepresentation: self.seedRepresentation, publicKey: nil) return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) @@ -179,7 +185,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 } } @@ -196,7 +202,7 @@ 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 } } @@ -207,5 +213,4 @@ extension ${NAME} { } % end - -#endif // Linux or !SwiftPM +#endif // Linux or !SwiftPM From fdb09e23ce8772d72021935d7398a62dc47adb8f Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 20:45:56 +0200 Subject: [PATCH 10/17] Use computed variable to get BoringSSL implementation --- Sources/Crypto/Signatures/MLDSA.swift | 74 ++++++++++++++--------- Sources/Crypto/Signatures/MLDSA.swift.gyb | 37 +++++++----- 2 files changed, 66 insertions(+), 45 deletions(-) diff --git a/Sources/Crypto/Signatures/MLDSA.swift b/Sources/Crypto/Signatures/MLDSA.swift index a88ff05ce..adaa6aa91 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -86,11 +86,7 @@ extension MLDSA65 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D) throws -> Data { - let implementation = - self.impl is OpenSSLMLDSAPublicKeyImpl - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) - return try implementation.prehash_boring(for: data) + try self.boringSSLKey.prehash_boring(for: data) } /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. @@ -101,11 +97,15 @@ extension MLDSA65 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D, context: C) throws -> Data { - let implementation = + try self.boringSSLKey.prehash_boring(for: data, context: context) + } + + private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { + get throws { self.impl is OpenSSLMLDSAPublicKeyImpl - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) - return try implementation.prehash_boring(for: data, context: context) + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + } } } @@ -140,11 +140,7 @@ extension MLDSA65 { /// /// - Returns: The signature of the prehashed message representative. package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { - let implementation = - self.impl is OpenSSLMLDSAPrivateKeyImpl - ? self.impl - : try OpenSSLMLDSAPrivateKeyImpl(seedRepresentation: self.seedRepresentation, publicKey: nil) - return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) + try self.boringSSLKey.signature_boring(forPrehashedMessageRepresentative: mu) } /// The associated public key. @@ -198,9 +194,20 @@ extension MLDSA65 { } } - fileprivate init(impl: MLDSAPrivateKeyImpl) { + private init(impl: MLDSAPrivateKeyImpl) { self.impl = impl } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { + get throws { + self.impl is OpenSSLMLDSAPrivateKeyImpl + ? self.impl + : try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + } + } } } @@ -264,11 +271,7 @@ extension MLDSA87 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D) throws -> Data { - let implementation = - self.impl is OpenSSLMLDSAPublicKeyImpl - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) - return try implementation.prehash_boring(for: data) + try self.boringSSLKey.prehash_boring(for: data) } /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. @@ -279,11 +282,15 @@ extension MLDSA87 { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D, context: C) throws -> Data { - let implementation = + try self.boringSSLKey.prehash_boring(for: data, context: context) + } + + private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { + get throws { self.impl is OpenSSLMLDSAPublicKeyImpl - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) - return try implementation.prehash_boring(for: data, context: context) + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + } } } @@ -318,11 +325,7 @@ extension MLDSA87 { /// /// - Returns: The signature of the prehashed message representative. package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { - let implementation = - self.impl is OpenSSLMLDSAPrivateKeyImpl - ? self.impl - : try OpenSSLMLDSAPrivateKeyImpl(seedRepresentation: self.seedRepresentation, publicKey: nil) - return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) + try self.boringSSLKey.signature_boring(forPrehashedMessageRepresentative: mu) } /// The associated public key. @@ -376,9 +379,20 @@ extension MLDSA87 { } } - fileprivate init(impl: MLDSAPrivateKeyImpl) { + private init(impl: MLDSAPrivateKeyImpl) { self.impl = impl } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { + get throws { + self.impl is OpenSSLMLDSAPrivateKeyImpl + ? self.impl + : try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + } + } } } diff --git a/Sources/Crypto/Signatures/MLDSA.swift.gyb b/Sources/Crypto/Signatures/MLDSA.swift.gyb index 0c8707ec1..1b220eb29 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -94,11 +94,7 @@ extension ${NAME} { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D) throws -> Data { - let implementation = - self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) - return try implementation.prehash_boring(for: data) + try self.boringSSLKey.prehash_boring(for: data) } /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. @@ -109,11 +105,15 @@ extension ${NAME} { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). package func prehash_boring(for data: D, context: C) throws -> Data { - let implementation = + try self.boringSSLKey.prehash_boring(for: data, context: context) + } + + private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl<${NAME}> { + get throws { self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) - return try implementation.prehash_boring(for: data, context: context) + ? self.impl + : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) + } } } @@ -148,11 +148,7 @@ extension ${NAME} { /// /// - Returns: The signature of the prehashed message representative. package func signature_boring(forPrehashedMessageRepresentative mu: some DataProtocol) throws -> Data { - let implementation = - self.impl is OpenSSLMLDSAPrivateKeyImpl<${NAME}> - ? self.impl - : try OpenSSLMLDSAPrivateKeyImpl<${NAME}>(seedRepresentation: self.seedRepresentation, publicKey: nil) - return try implementation.signature_boring(forPrehashedMessageRepresentative: mu) + try self.boringSSLKey.signature_boring(forPrehashedMessageRepresentative: mu) } /// The associated public key. @@ -206,9 +202,20 @@ extension ${NAME} { } } - fileprivate init(impl: MLDSAPrivateKeyImpl<${NAME}>) { + private init(impl: MLDSAPrivateKeyImpl<${NAME}>) { self.impl = impl } + + private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl<${NAME}> { + get throws { + self.impl is OpenSSLMLDSAPrivateKeyImpl<${NAME}> + ? self.impl + : try OpenSSLMLDSAPrivateKeyImpl<${NAME}>( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + } + } } } From f8bb7ef5cfbb4e5fa14bc276ae8cb9bf94e84eaa Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 23:30:15 +0200 Subject: [PATCH 11/17] Add `@testable` back in tests --- Tests/_CryptoExtrasTests/MLDSATests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index b1e518595..1e9892bb5 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -14,7 +14,7 @@ import XCTest -import _CryptoExtras +@testable import _CryptoExtras #if !canImport(Darwin) || canImport(CryptoKit, _version: 324.0.4) final class MLDSATests: XCTestCase { From 8f0671989dadfb4a813c051e62c9a2af9351a707 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Mon, 16 Jun 2025 23:41:07 +0200 Subject: [PATCH 12/17] Make the linter happy --- Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift | 4 ++-- Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb | 2 +- Tests/_CryptoExtrasTests/MLDSATests.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift index 9f1c4ad90..2566d37a4 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift @@ -41,7 +41,7 @@ extension MLDSA65.PublicKey { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). public func prehash(for data: D) throws -> Data { - return try self.prehash_boring(for: data) + try self.prehash_boring(for: data) } /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. @@ -78,7 +78,7 @@ extension MLDSA87.PublicKey { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). public func prehash(for data: D) throws -> Data { - return try self.prehash_boring(for: data) + try self.prehash_boring(for: data) } /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb index 01103c26c..0edb300af 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb @@ -45,7 +45,7 @@ extension MLDSA${parameter_set}.PublicKey { /// /// - Returns: The prehashed message representative (a.k.a. "external mu"). public func prehash(for data: D) throws -> Data { - return try self.prehash_boring(for: data) + try self.prehash_boring(for: data) } /// Generate a prehashed message representative (a.k.a. "external mu") for the given message. diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index 1e9892bb5..a30362c6d 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -383,4 +383,4 @@ final class MLDSAExternalMuTests: XCTestCase { let muSignature = try key.signature(forPrehashedMessageRepresentative: mu) XCTAssertTrue(publicKey.isValidSignature(signature: muSignature, for: message, context: context)) } -} \ No newline at end of file +} From 67074c0bf452d34d756c1b6cc906aa87bf7bca80 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Tue, 17 Jun 2025 12:03:42 +0200 Subject: [PATCH 13/17] Update CMakeLists and use FoundationEssentials --- Sources/_CryptoExtras/CMakeLists.txt | 1 + Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift | 4 ++++ Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/Sources/_CryptoExtras/CMakeLists.txt b/Sources/_CryptoExtras/CMakeLists.txt index 2ddd0cbe7..2febaf829 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/MLDSA/MLDSA+externalMu.swift b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift index 2566d37a4..76ef02ee7 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift @@ -17,7 +17,11 @@ // 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 { diff --git a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb index 0edb300af..899df5063 100644 --- a/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb +++ b/Sources/_CryptoExtras/MLDSA/MLDSA+externalMu.swift.gyb @@ -17,7 +17,11 @@ // see section `gyb` in `README` for details. import Crypto +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif %{ parameter_sets = ["65", "87"] }% From 794dfa6020c26820f8cab69585ad80b4fd303785 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 19 Jun 2025 18:59:56 +0200 Subject: [PATCH 14/17] Update DocC --- Sources/Crypto/Docs.docc/index.md | 14 ++++++++++++++ Sources/_CryptoExtras/Docs.docc/index.md | 4 ---- 2 files changed, 14 insertions(+), 4 deletions(-) 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/_CryptoExtras/Docs.docc/index.md b/Sources/_CryptoExtras/Docs.docc/index.md index b38a23dd7..50b5eca56 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 From 63d96f141c441f641a19a1daf7bafff5ca77495e Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Tue, 24 Jun 2025 19:01:10 +0200 Subject: [PATCH 15/17] Remove some warnings --- Package.swift | 3 +- Sources/Crypto/Signatures/MLDSA.swift | 44 +++++++++++++---------- Sources/Crypto/Signatures/MLDSA.swift.gyb | 22 +++++++----- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/Package.swift b/Package.swift index 3d92c6858..a31f1914b 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/Signatures/MLDSA.swift b/Sources/Crypto/Signatures/MLDSA.swift index e22ff27dd..b0fba688e 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -106,9 +106,11 @@ extension MLDSA65 { private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { get throws { - self.impl is OpenSSLMLDSAPublicKeyImpl - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif } } } @@ -204,12 +206,14 @@ extension MLDSA65 { private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { get throws { - self.impl is OpenSSLMLDSAPrivateKeyImpl - ? self.impl - : try OpenSSLMLDSAPrivateKeyImpl( - seedRepresentation: self.seedRepresentation, - publicKey: nil - ) + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif } } } @@ -291,9 +295,11 @@ extension MLDSA87 { private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl { get throws { - self.impl is OpenSSLMLDSAPublicKeyImpl - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif } } } @@ -389,12 +395,14 @@ extension MLDSA87 { private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl { get throws { - self.impl is OpenSSLMLDSAPrivateKeyImpl - ? self.impl - : try OpenSSLMLDSAPrivateKeyImpl( - seedRepresentation: self.seedRepresentation, - publicKey: nil - ) + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif } } } diff --git a/Sources/Crypto/Signatures/MLDSA.swift.gyb b/Sources/Crypto/Signatures/MLDSA.swift.gyb index 9204878b4..283863b22 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -114,9 +114,11 @@ extension ${NAME} { private var boringSSLKey: OpenSSLMLDSAPublicKeyImpl<${NAME}> { get throws { - self.impl is OpenSSLMLDSAPublicKeyImpl<${NAME}> - ? self.impl - : try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPublicKeyImpl<${NAME}>(rawRepresentation: self.rawRepresentation) + #else + self.impl + #endif } } } @@ -212,12 +214,14 @@ extension ${NAME} { private var boringSSLKey: OpenSSLMLDSAPrivateKeyImpl<${NAME}> { get throws { - self.impl is OpenSSLMLDSAPrivateKeyImpl<${NAME}> - ? self.impl - : try OpenSSLMLDSAPrivateKeyImpl<${NAME}>( - seedRepresentation: self.seedRepresentation, - publicKey: nil - ) + #if (!CRYPTO_IN_SWIFTPM_FORCE_BUILD_API) || CRYPTOKIT_NO_ACCESS_TO_FOUNDATION + try OpenSSLMLDSAPrivateKeyImpl<${NAME}>( + seedRepresentation: self.seedRepresentation, + publicKey: nil + ) + #else + self.impl + #endif } } } From 04342312716617b45262d011e4304a3514e45a88 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Tue, 24 Jun 2025 19:01:34 +0200 Subject: [PATCH 16/17] Format and remove warnings from MLKEM.swift --- Sources/Crypto/KEM/MLKEM.swift | 49 ++++++++++++++++-------------- Sources/Crypto/KEM/MLKEM.swift.gyb | 34 +++++++++++++-------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/Sources/Crypto/KEM/MLKEM.swift b/Sources/Crypto/KEM/MLKEM.swift index 3883fbd40..248439f88 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 3a1719437..a5d5417f0 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 From 66b120296336b02cad1854f46bdacb74f4023d18 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Wed, 6 Aug 2025 11:58:37 +0200 Subject: [PATCH 17/17] Remove `signature:` parameter label from `isValidSignature` --- .../Signatures/BoringSSL/MLDSA_wrapper.swift | 4 +-- Sources/Crypto/Signatures/MLDSA.swift | 16 ++++----- Sources/Crypto/Signatures/MLDSA.swift.gyb | 8 ++--- .../Signatures/MLDSA/MLDSATests.swift | 16 ++++----- Tests/_CryptoExtrasTests/MLDSATests.swift | 36 +++++++++---------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift index 52651256a..5a3ab1186 100644 --- a/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift +++ b/Sources/Crypto/Signatures/BoringSSL/MLDSA_wrapper.swift @@ -142,14 +142,14 @@ struct OpenSSLMLDSAPublicKeyImpl { } func isValidSignature( - signature: S, + _ signature: S, for data: D ) -> Bool { self.backing.isValidSignature(signature, for: data) } func isValidSignature( - signature: S, + _ signature: S, for data: D, context: C ) -> Bool { diff --git a/Sources/Crypto/Signatures/MLDSA.swift b/Sources/Crypto/Signatures/MLDSA.swift index b0fba688e..485e1087f 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift +++ b/Sources/Crypto/Signatures/MLDSA.swift @@ -45,8 +45,8 @@ extension MLDSA65 { /// - signature: The MLDSA65 signature to verify. /// - data: The signed data. /// - Returns: `true` if the signature is valid, `false` otherwise. - public func isValidSignature(signature: S, for data: D) -> Bool { - self.impl.isValidSignature(signature: signature, for: data) + public func isValidSignature(_ signature: S, for data: D) -> Bool { + self.impl.isValidSignature(signature, for: data) } /// Verifies a MLDSA65 signature, in a specific context. @@ -56,11 +56,11 @@ extension MLDSA65 { /// - context: Context for the signature. /// - Returns: `true` if the signature is valid in the specified context, `false` otherwise. public func isValidSignature( - signature: S, + _ signature: S, for data: D, context: C ) -> Bool { - self.impl.isValidSignature(signature: signature, for: data, context: context) + self.impl.isValidSignature(signature, for: data, context: context) } /// Parses a public key from a serialized representation. @@ -234,8 +234,8 @@ extension MLDSA87 { /// - signature: The MLDSA87 signature to verify. /// - data: The signed data. /// - Returns: `true` if the signature is valid, `false` otherwise. - public func isValidSignature(signature: S, for data: D) -> Bool { - self.impl.isValidSignature(signature: signature, for: data) + public func isValidSignature(_ signature: S, for data: D) -> Bool { + self.impl.isValidSignature(signature, for: data) } /// Verifies a MLDSA87 signature, in a specific context. @@ -245,11 +245,11 @@ extension MLDSA87 { /// - context: Context for the signature. /// - Returns: `true` if the signature is valid in the specified context, `false` otherwise. public func isValidSignature( - signature: S, + _ signature: S, for data: D, context: C ) -> Bool { - self.impl.isValidSignature(signature: signature, for: data, context: context) + self.impl.isValidSignature(signature, for: data, context: context) } /// Parses a public key from a serialized representation. diff --git a/Sources/Crypto/Signatures/MLDSA.swift.gyb b/Sources/Crypto/Signatures/MLDSA.swift.gyb index 283863b22..14d31e014 100644 --- a/Sources/Crypto/Signatures/MLDSA.swift.gyb +++ b/Sources/Crypto/Signatures/MLDSA.swift.gyb @@ -53,8 +53,8 @@ extension ${NAME} { /// - signature: The ${NAME} signature to verify. /// - data: The signed data. /// - Returns: `true` if the signature is valid, `false` otherwise. - public func isValidSignature(signature: S, for data: D) -> Bool { - self.impl.isValidSignature(signature: signature, for: data) + public func isValidSignature(_ signature: S, for data: D) -> Bool { + self.impl.isValidSignature(signature, for: data) } /// Verifies a ${NAME} signature, in a specific context. @@ -64,11 +64,11 @@ extension ${NAME} { /// - context: Context for the signature. /// - Returns: `true` if the signature is valid in the specified context, `false` otherwise. public func isValidSignature( - signature: S, + _ signature: S, for data: D, context: C ) -> Bool { - self.impl.isValidSignature(signature: signature, for: data, context: context) + self.impl.isValidSignature(signature, for: data, context: context) } /// Parses a public key from a serialized representation. diff --git a/Tests/CryptoTests/Signatures/MLDSA/MLDSATests.swift b/Tests/CryptoTests/Signatures/MLDSA/MLDSATests.swift index f833e3355..61dd8e7d0 100644 --- a/Tests/CryptoTests/Signatures/MLDSA/MLDSATests.swift +++ b/Tests/CryptoTests/Signatures/MLDSA/MLDSATests.swift @@ -40,18 +40,18 @@ final class MLDSATests: XCTestCase { let message = Data("ML-DSA test message".utf8) let signature = try privateKey.signature(for: message) XCTAssertNotNil(signature) - let isValid = publicKey.isValidSignature(signature: signature, for: message) + let isValid = publicKey.isValidSignature(signature, for: message) XCTAssertTrue(isValid) // Test signing with a context let context = Data("ML-DSA test context".utf8) let signatureWithContext = try privateKey.signature(for: message, context: context) - let isValidWithContext = publicKey.isValidSignature(signature: signatureWithContext, for: message, context: context) + let isValidWithContext = publicKey.isValidSignature(signatureWithContext, for: message, context: context) XCTAssertTrue(isValidWithContext) // Check that invalid signatures (mismatching contexts) fail - XCTAssertFalse(publicKey.isValidSignature(signature: signature, for: message, context: context)) - XCTAssertFalse(publicKey.isValidSignature(signature: signatureWithContext, for: message)) + XCTAssertFalse(publicKey.isValidSignature(signature, for: message, context: context)) + XCTAssertFalse(publicKey.isValidSignature(signatureWithContext, for: message)) } func testMLDSA87() throws { @@ -71,18 +71,18 @@ final class MLDSATests: XCTestCase { let message = Data("ML-DSA test message".utf8) let signature = try privateKey.signature(for: message) XCTAssertNotNil(signature) - let isValid = publicKey.isValidSignature(signature: signature, for: message) + let isValid = publicKey.isValidSignature(signature, for: message) XCTAssertTrue(isValid) // Test signing with a context let context = Data("ML-DSA test context".utf8) let signatureWithContext = try privateKey.signature(for: message, context: context) - let isValidWithContext = publicKey.isValidSignature(signature: signatureWithContext, for: message, context: context) + let isValidWithContext = publicKey.isValidSignature(signatureWithContext, for: message, context: context) XCTAssertTrue(isValidWithContext) // Check that invalid signatures (mismatching contexts) fail - XCTAssertFalse(publicKey.isValidSignature(signature: signature, for: message, context: context)) - XCTAssertFalse(publicKey.isValidSignature(signature: signatureWithContext, for: message)) + XCTAssertFalse(publicKey.isValidSignature(signature, for: message, context: context)) + XCTAssertFalse(publicKey.isValidSignature(signatureWithContext, for: message)) } } diff --git a/Tests/_CryptoExtrasTests/MLDSATests.swift b/Tests/_CryptoExtrasTests/MLDSATests.swift index c529bbb94..034e6be00 100644 --- a/Tests/_CryptoExtrasTests/MLDSATests.swift +++ b/Tests/_CryptoExtrasTests/MLDSATests.swift @@ -32,7 +32,7 @@ final class MLDSATests: XCTestCase { let test = "Hello, world!".data(using: .utf8)! try XCTAssertTrue( key.publicKey.isValidSignature( - signature: key.signature(for: test), + key.signature(for: test), for: test ) ) @@ -40,7 +40,7 @@ final class MLDSATests: XCTestCase { let context = "ctx".data(using: .utf8)! try XCTAssertTrue( key.publicKey.isValidSignature( - signature: key.signature(for: test, context: context), + key.signature(for: test, context: context), for: test, context: context ) @@ -61,7 +61,7 @@ final class MLDSATests: XCTestCase { let test = "Hello, world!".data(using: .utf8)! try XCTAssertTrue( key.publicKey.isValidSignature( - signature: key.signature(for: test), + key.signature(for: test), for: test ) ) @@ -69,7 +69,7 @@ final class MLDSATests: XCTestCase { let context = "ctx".data(using: .utf8)! try XCTAssertTrue( key.publicKey.isValidSignature( - signature: key.signature(for: test, context: context), + key.signature(for: test, context: context), for: test, context: context ) @@ -114,8 +114,8 @@ final class MLDSATests: XCTestCase { XCTAssertNotEqual(signature1, signature2) // Even though the signatures are different, they both verify. - XCTAssertTrue(publicKey.isValidSignature(signature: signature1, for: message)) - XCTAssertTrue(publicKey.isValidSignature(signature: signature2, for: message)) + XCTAssertTrue(publicKey.isValidSignature(signature1, for: message)) + XCTAssertTrue(publicKey.isValidSignature(signature2, for: message)) } func testMLDSA87SignatureIsRandomized() throws { @@ -134,8 +134,8 @@ final class MLDSATests: XCTestCase { XCTAssertNotEqual(signature1, signature2) // Even though the signatures are different, they both verify. - XCTAssertTrue(publicKey.isValidSignature(signature: signature1, for: message)) - XCTAssertTrue(publicKey.isValidSignature(signature: signature2, for: message)) + XCTAssertTrue(publicKey.isValidSignature(signature1, for: message)) + XCTAssertTrue(publicKey.isValidSignature(signature2, for: message)) } func testInvalidMLDSA65PublicKeyEncodingLength() throws { @@ -263,15 +263,15 @@ final class MLDSATests: XCTestCase { switch test.result { case .valid: if let context { - XCTAssertTrue(publicKey.isValidSignature(signature: signature, for: message, context: context)) + XCTAssertTrue(publicKey.isValidSignature(signature, for: message, context: context)) } else { - XCTAssertTrue(publicKey.isValidSignature(signature: signature, for: message)) + XCTAssertTrue(publicKey.isValidSignature(signature, for: message)) } case .invalid: if let context { - XCTAssertFalse(publicKey.isValidSignature(signature: signature, for: message, context: context)) + XCTAssertFalse(publicKey.isValidSignature(signature, for: message, context: context)) } else { - XCTAssertFalse(publicKey.isValidSignature(signature: signature, for: message)) + XCTAssertFalse(publicKey.isValidSignature(signature, for: message)) } } } @@ -298,15 +298,15 @@ final class MLDSATests: XCTestCase { switch test.result { case .valid: if let context { - XCTAssertTrue(publicKey.isValidSignature(signature: signature, for: message, context: context)) + XCTAssertTrue(publicKey.isValidSignature(signature, for: message, context: context)) } else { - XCTAssertTrue(publicKey.isValidSignature(signature: signature, for: message)) + XCTAssertTrue(publicKey.isValidSignature(signature, for: message)) } case .invalid: if let context { - XCTAssertFalse(publicKey.isValidSignature(signature: signature, for: message, context: context)) + XCTAssertFalse(publicKey.isValidSignature(signature, for: message, context: context)) } else { - XCTAssertFalse(publicKey.isValidSignature(signature: signature, for: message)) + XCTAssertFalse(publicKey.isValidSignature(signature, for: message)) } } } @@ -368,7 +368,7 @@ final class MLDSAExternalMuTests: XCTestCase { 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)) + XCTAssertTrue(publicKey.isValidSignature(muSignature, for: message, context: context)) } func testMLDSA87PrehashedSigning() throws { @@ -381,6 +381,6 @@ final class MLDSAExternalMuTests: XCTestCase { 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)) + XCTAssertTrue(publicKey.isValidSignature(muSignature, for: message, context: context)) } }