Skip to content

Add "external mu" variant of ML-DSA (65 and 87) in _CryptoExtras #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: wwdc-25
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e9de693
Add an external mu variant of the ML-DSA API (65 and 87 variants)
fpseverino Jun 4, 2025
e09d149
Revert "Add an external mu variant of the ML-DSA API (65 and 87 varia…
fpseverino Jun 16, 2025
e25c941
Add an external mu variant of the ML-DSA API (65 and 87 variants)
fpseverino Jun 4, 2025
e016958
Revert "Add an external mu variant of the ML-DSA API (65 and 87 varia…
fpseverino Jun 16, 2025
2688efb
Merge branch 'mldsa-external-mu' of https://github.com/fpseverino/swi…
fpseverino Jun 16, 2025
74c97de
Add external mu variant of ML-DSA to BoringSSL implementation
fpseverino Jun 16, 2025
a6213d7
Add external mu API to wrapper with `package` level
fpseverino Jun 16, 2025
bc935ac
Expose external mu API in CryptoExtras
fpseverino Jun 16, 2025
f8ed3ee
Add tests for external mu variant
fpseverino Jun 16, 2025
64aa6ca
Small formatting fixes
fpseverino Jun 16, 2025
fdb09e2
Use computed variable to get BoringSSL implementation
fpseverino Jun 16, 2025
f8bb7ef
Add `@testable` back in tests
fpseverino Jun 16, 2025
8f06719
Make the linter happy
fpseverino Jun 16, 2025
67074c0
Update CMakeLists and use FoundationEssentials
fpseverino Jun 17, 2025
2c12b1b
Merge branch 'wwdc-25' into mldsa-external-mu
fpseverino Jun 18, 2025
794dfa6
Update DocC
fpseverino Jun 19, 2025
8e57f3e
Merge branch 'wwdc-25' into mldsa-external-mu
fpseverino Jun 20, 2025
28350a9
Merge branch 'wwdc-25' into mldsa-external-mu
fpseverino Jun 23, 2025
63d96f1
Remove some warnings
fpseverino Jun 24, 2025
0434231
Format and remove warnings from MLKEM.swift
fpseverino Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions Sources/Crypto/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down
49 changes: 27 additions & 22 deletions Sources/Crypto/KEM/MLKEM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -53,19 +52,19 @@ extension MLKEM768 {
/// A serialized representation of the public key.
public var rawRepresentation: Data {
get {
return self.impl.rawRepresentation
self.impl.rawRepresentation
}
}

/// Generates and encapsulates a shared secret.
///
/// - 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)
}
}

Expand Down Expand Up @@ -104,15 +103,18 @@ extension MLKEM768 {
if publicKey != nil {
publicKeyRawRepresentation = publicKey!.rawRepresentation
}
self.impl = try MLKEMPrivateKeyImpl<MLKEM768>(seedRepresentation: seedRepresentation, publicKeyRawRepresentation: publicKeyRawRepresentation)
self.impl = try MLKEMPrivateKeyImpl<MLKEM768>(
seedRepresentation: seedRepresentation,
publicKeyRawRepresentation: publicKeyRawRepresentation
)
}

/// The private key's seed representation.
///
/// 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
}
}

Expand All @@ -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<D: DataProtocol>(_ 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
}
}

Expand All @@ -140,7 +142,8 @@ extension MLKEM768 {
throw KEM.Errors.invalidSeed
}
let seed = Data(integrityCheckedRepresentation).subdata(in: 0..<MLKEMPrivateKeyImpl<MLKEM768>.seedSize)
let publicKeyHashData = Data(integrityCheckedRepresentation).subdata(in: MLKEMPrivateKeyImpl<MLKEM768>.seedSize..<integrityCheckedRepresentation.count)
let publicKeyHashData = Data(integrityCheckedRepresentation)
.subdata(in: MLKEMPrivateKeyImpl<MLKEM768>.seedSize..<integrityCheckedRepresentation.count)
let publicKeyHash = SHA3_256Digest(bytes: [UInt8](publicKeyHashData))

self.impl = try MLKEMPrivateKeyImpl<MLKEM768>(seedRepresentation: seed, publicKeyHash: publicKeyHash)
Expand All @@ -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 {}
Expand All @@ -178,19 +180,19 @@ extension MLKEM1024 {
/// A serialized representation of the public key.
public var rawRepresentation: Data {
get {
return self.impl.rawRepresentation
self.impl.rawRepresentation
}
}

/// Generates and encapsulates a shared secret.
///
/// - 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)
}
}

Expand Down Expand Up @@ -229,15 +231,18 @@ extension MLKEM1024 {
if publicKey != nil {
publicKeyRawRepresentation = publicKey!.rawRepresentation
}
self.impl = try MLKEMPrivateKeyImpl<MLKEM1024>(seedRepresentation: seedRepresentation, publicKeyRawRepresentation: publicKeyRawRepresentation)
self.impl = try MLKEMPrivateKeyImpl<MLKEM1024>(
seedRepresentation: seedRepresentation,
publicKeyRawRepresentation: publicKeyRawRepresentation
)
}

/// The private key's seed representation.
///
/// 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
}
}

Expand All @@ -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<D: DataProtocol>(_ 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
}
}

Expand All @@ -265,7 +270,8 @@ extension MLKEM1024 {
throw KEM.Errors.invalidSeed
}
let seed = Data(integrityCheckedRepresentation).subdata(in: 0..<MLKEMPrivateKeyImpl<MLKEM1024>.seedSize)
let publicKeyHashData = Data(integrityCheckedRepresentation).subdata(in: MLKEMPrivateKeyImpl<MLKEM1024>.seedSize..<integrityCheckedRepresentation.count)
let publicKeyHashData = Data(integrityCheckedRepresentation)
.subdata(in: MLKEMPrivateKeyImpl<MLKEM1024>.seedSize..<integrityCheckedRepresentation.count)
let publicKeyHash = SHA3_256Digest(bytes: [UInt8](publicKeyHashData))

self.impl = try MLKEMPrivateKeyImpl<MLKEM1024>(seedRepresentation: seed, publicKeyHash: publicKeyHash)
Expand All @@ -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
34 changes: 22 additions & 12 deletions Sources/Crypto/KEM/MLKEM.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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}>

Expand All @@ -54,23 +60,24 @@ extension ${NAME} {
/// A serialized representation of the public key.
public var rawRepresentation: Data {
get {
return self.impl.rawRepresentation
self.impl.rawRepresentation
}
}

/// Generates and encapsulates a shared secret.
///
/// - 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}>

Expand Down Expand Up @@ -104,15 +111,18 @@ 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.
///
/// 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
}
}

Expand All @@ -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<D: DataProtocol>(_ 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
}
}

Expand All @@ -140,7 +150,8 @@ extension ${NAME} {
throw KEM.Errors.invalidSeed
}
let seed = Data(integrityCheckedRepresentation).subdata(in: 0..<MLKEMPrivateKeyImpl<${NAME}>.seedSize)
let publicKeyHashData = Data(integrityCheckedRepresentation).subdata(in: MLKEMPrivateKeyImpl<${NAME}>.seedSize..<integrityCheckedRepresentation.count)
let publicKeyHashData = Data(integrityCheckedRepresentation)
.subdata(in: MLKEMPrivateKeyImpl<${NAME}>.seedSize..<integrityCheckedRepresentation.count)
let publicKeyHash = SHA3_256Digest(bytes: [UInt8](publicKeyHashData))

self.impl = try MLKEMPrivateKeyImpl<${NAME}>(seedRepresentation: seed, publicKeyHash: publicKeyHash)
Expand All @@ -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
Loading