@@ -326,6 +326,312 @@ extension MLDSA65 {
326326 private static let signatureByteCount = Int ( MLDSA65_SIGNATURE_BYTES)
327327}
328328
329+ /// A module-lattice-based digital signature algorithm that provides security against quantum computing attacks.
330+ @available ( macOS 10 . 15 , iOS 13 , watchOS 6 , tvOS 13 , macCatalyst 13 , visionOS 1 . 0 , * )
331+ public enum MLDSA87 { }
332+
333+ @available ( macOS 10 . 15 , iOS 13 , watchOS 6 , tvOS 13 , macCatalyst 13 , visionOS 1 . 0 , * )
334+ extension MLDSA87 {
335+ /// A ML-DSA-87 private key.
336+ public struct PrivateKey : Sendable {
337+ private var backing : Backing
338+
339+ /// Initialize a ML-DSA-87 private key from a random seed.
340+ public init ( ) throws {
341+ self . backing = try Backing ( )
342+ }
343+
344+ /// Initialize a ML-DSA-87 private key from a seed.
345+ ///
346+ /// - Parameter seedRepresentation: The seed to use to generate the private key.
347+ ///
348+ /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long.
349+ public init ( seedRepresentation: some DataProtocol ) throws {
350+ self . backing = try Backing ( seedRepresentation: seedRepresentation)
351+ }
352+
353+ /// The seed from which this private key was generated.
354+ public var seedRepresentation : Data {
355+ self . backing. seed
356+ }
357+
358+ /// The public key associated with this private key.
359+ public var publicKey : PublicKey {
360+ self . backing. publicKey
361+ }
362+
363+ /// Generate a signature for the given data.
364+ ///
365+ /// - Parameter data: The message to sign.
366+ ///
367+ /// - Returns: The signature of the message.
368+ public func signature< D: DataProtocol > ( for data: D ) throws -> Data {
369+ let context : Data ? = nil
370+ return try self . backing. signature ( for: data, context: context)
371+ }
372+
373+ /// Generate a signature for the given data.
374+ ///
375+ /// - Parameters:
376+ /// - data: The message to sign.
377+ /// - context: The context to use for the signature.
378+ ///
379+ /// - Returns: The signature of the message.
380+ public func signature< D: DataProtocol , C: DataProtocol > ( for data: D , context: C ) throws -> Data {
381+ try self . backing. signature ( for: data, context: context)
382+ }
383+
384+ /// The size of the private key in bytes.
385+ static let byteCount = Backing . byteCount
386+
387+ fileprivate final class Backing {
388+ fileprivate var key : MLDSA87_private_key
389+ var seed : Data
390+
391+ /// Initialize a ML-DSA-87 private key from a random seed.
392+ init ( ) throws {
393+ // We have to initialize all members before `self` is captured by the closure
394+ self . key = . init( )
395+ self . seed = Data ( )
396+
397+ self . seed = try withUnsafeTemporaryAllocation (
398+ of: UInt8 . self,
399+ capacity: MLDSA . seedByteCount
400+ ) { seedPtr in
401+ try withUnsafeTemporaryAllocation (
402+ of: UInt8 . self,
403+ capacity: MLDSA87 . PublicKey. Backing. byteCount
404+ ) { publicKeyPtr in
405+ guard
406+ CCryptoBoringSSL_MLDSA87_generate_key (
407+ publicKeyPtr. baseAddress,
408+ seedPtr. baseAddress,
409+ & self . key
410+ ) == 1
411+ else {
412+ throw CryptoKitError . internalBoringSSLError ( )
413+ }
414+
415+ return Data ( bytes: seedPtr. baseAddress!, count: MLDSA . seedByteCount)
416+ }
417+ }
418+ }
419+
420+ /// Initialize a ML-DSA-87 private key from a seed.
421+ ///
422+ /// - Parameter seedRepresentation: The seed to use to generate the private key.
423+ ///
424+ /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long.
425+ init ( seedRepresentation: some DataProtocol ) throws {
426+ guard seedRepresentation. count == MLDSA . seedByteCount else {
427+ throw CryptoKitError . incorrectKeySize
428+ }
429+
430+ self . key = . init( )
431+ self . seed = Data ( seedRepresentation)
432+
433+ guard
434+ self . seed. withUnsafeBytes ( { seedPtr in
435+ CCryptoBoringSSL_MLDSA87_private_key_from_seed (
436+ & self . key,
437+ seedPtr. baseAddress,
438+ MLDSA . seedByteCount
439+ )
440+ } ) == 1
441+ else {
442+ throw CryptoKitError . internalBoringSSLError ( )
443+ }
444+ }
445+
446+ /// The public key associated with this private key.
447+ var publicKey : PublicKey {
448+ PublicKey ( privateKeyBacking: self )
449+ }
450+
451+ /// Generate a signature for the given data.
452+ ///
453+ /// - Parameters:
454+ /// - data: The message to sign.
455+ /// - context: The context to use for the signature.
456+ ///
457+ /// - Returns: The signature of the message.
458+ func signature< D: DataProtocol , C: DataProtocol > ( for data: D , context: C ? ) throws -> Data {
459+ var signature = Data ( repeating: 0 , count: MLDSA87 . signatureByteCount)
460+
461+ let rc : CInt = signature. withUnsafeMutableBytes { signaturePtr in
462+ let bytes : ContiguousBytes = data. regions. count == 1 ? data. regions. first! : Array ( data)
463+ return bytes. withUnsafeBytes { dataPtr in
464+ context. withUnsafeBytes { contextPtr in
465+ CCryptoBoringSSL_MLDSA87_sign (
466+ signaturePtr. baseAddress,
467+ & self . key,
468+ dataPtr. baseAddress,
469+ dataPtr. count,
470+ contextPtr. baseAddress,
471+ contextPtr. count
472+ )
473+ }
474+ }
475+ }
476+
477+ guard rc == 1 else {
478+ throw CryptoKitError . internalBoringSSLError ( )
479+ }
480+
481+ return signature
482+ }
483+
484+ /// The size of the private key in bytes.
485+ static let byteCount = Int ( MLDSA87_PRIVATE_KEY_BYTES)
486+ }
487+ }
488+ }
489+
490+ @available ( macOS 10 . 15 , iOS 13 , watchOS 6 , tvOS 13 , macCatalyst 13 , visionOS 1 . 0 , * )
491+ extension MLDSA87 {
492+ /// A ML-DSA-87 public key.
493+ public struct PublicKey : Sendable {
494+ private var backing : Backing
495+
496+ fileprivate init ( privateKeyBacking: PrivateKey . Backing ) {
497+ self . backing = Backing ( privateKeyBacking: privateKeyBacking)
498+ }
499+
500+ /// Initialize a ML-DSA-87 public key from a raw representation.
501+ ///
502+ /// - Parameter rawRepresentation: The public key bytes.
503+ ///
504+ /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size.
505+ public init ( rawRepresentation: some DataProtocol ) throws {
506+ self . backing = try Backing ( rawRepresentation: rawRepresentation)
507+ }
508+
509+ /// The raw binary representation of the public key.
510+ public var rawRepresentation : Data {
511+ self . backing. rawRepresentation
512+ }
513+
514+ /// Verify a signature for the given data.
515+ ///
516+ /// - Parameters:
517+ /// - signature: The signature to verify.
518+ /// - data: The message to verify the signature against.
519+ ///
520+ /// - Returns: `true` if the signature is valid, `false` otherwise.
521+ public func isValidSignature< S: DataProtocol , D: DataProtocol > ( _ signature: S , for data: D ) -> Bool {
522+ let context : Data ? = nil
523+ return self . backing. isValidSignature ( signature, for: data, context: context)
524+ }
525+
526+ /// Verify a signature for the given data.
527+ ///
528+ /// - Parameters:
529+ /// - signature: The signature to verify.
530+ /// - data: The message to verify the signature against.
531+ /// - context: The context to use for the signature verification.
532+ ///
533+ /// - Returns: `true` if the signature is valid, `false` otherwise.
534+ public func isValidSignature< S: DataProtocol , D: DataProtocol , C: DataProtocol > (
535+ _ signature: S ,
536+ for data: D ,
537+ context: C
538+ ) -> Bool {
539+ self . backing. isValidSignature ( signature, for: data, context: context)
540+ }
541+
542+ /// The size of the public key in bytes.
543+ static let byteCount = Backing . byteCount
544+
545+ fileprivate final class Backing {
546+ private var key : MLDSA87_public_key
547+
548+ init ( privateKeyBacking: PrivateKey . Backing ) {
549+ self . key = . init( )
550+ CCryptoBoringSSL_MLDSA87_public_from_private ( & self . key, & privateKeyBacking. key)
551+ }
552+
553+ /// Initialize a ML-DSA-87 public key from a raw representation.
554+ ///
555+ /// - Parameter rawRepresentation: The public key bytes.
556+ ///
557+ /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size.
558+ init ( rawRepresentation: some DataProtocol ) throws {
559+ guard rawRepresentation. count == MLDSA87 . PublicKey. Backing. byteCount else {
560+ throw CryptoKitError . incorrectKeySize
561+ }
562+
563+ self . key = . init( )
564+
565+ let bytes : ContiguousBytes =
566+ rawRepresentation. regions. count == 1
567+ ? rawRepresentation. regions. first!
568+ : Array ( rawRepresentation)
569+ try bytes. withUnsafeBytes { rawBuffer in
570+ try rawBuffer. withMemoryRebound ( to: UInt8 . self) { buffer in
571+ var cbs = CBS ( data: buffer. baseAddress, len: buffer. count)
572+ guard CCryptoBoringSSL_MLDSA87_parse_public_key ( & self . key, & cbs) == 1 else {
573+ throw CryptoKitError . internalBoringSSLError ( )
574+ }
575+ }
576+ }
577+ }
578+
579+ /// The raw binary representation of the public key.
580+ var rawRepresentation : Data {
581+ var cbb = CBB ( )
582+ // The following BoringSSL functions can only fail on allocation failure, which we define as impossible.
583+ CCryptoBoringSSL_CBB_init ( & cbb, MLDSA87 . PublicKey. Backing. byteCount)
584+ defer { CCryptoBoringSSL_CBB_cleanup ( & cbb) }
585+ CCryptoBoringSSL_MLDSA87_marshal_public_key ( & cbb, & self . key)
586+ return Data ( bytes: CCryptoBoringSSL_CBB_data ( & cbb) , count: CCryptoBoringSSL_CBB_len ( & cbb) )
587+ }
588+
589+ /// Verify a signature for the given data.
590+ ///
591+ /// - Parameters:
592+ /// - signature: The signature to verify.
593+ /// - data: The message to verify the signature against.
594+ /// - context: The context to use for the signature verification.
595+ ///
596+ /// - Returns: `true` if the signature is valid, `false` otherwise.
597+ func isValidSignature< S: DataProtocol , D: DataProtocol , C: DataProtocol > (
598+ _ signature: S ,
599+ for data: D ,
600+ context: C ?
601+ ) -> Bool {
602+ let signatureBytes : ContiguousBytes =
603+ signature. regions. count == 1 ? signature. regions. first! : Array ( signature)
604+ return signatureBytes. withUnsafeBytes { signaturePtr in
605+ let dataBytes : ContiguousBytes = data. regions. count == 1 ? data. regions. first! : Array ( data)
606+ let rc : CInt = dataBytes. withUnsafeBytes { dataPtr in
607+ context. withUnsafeBytes { contextPtr in
608+ CCryptoBoringSSL_MLDSA87_verify (
609+ & self . key,
610+ signaturePtr. baseAddress,
611+ signaturePtr. count,
612+ dataPtr. baseAddress,
613+ dataPtr. count,
614+ contextPtr. baseAddress,
615+ contextPtr. count
616+ )
617+ }
618+ }
619+ return rc == 1
620+ }
621+ }
622+
623+ /// The size of the public key in bytes.
624+ static let byteCount = Int ( MLDSA87_PUBLIC_KEY_BYTES)
625+ }
626+ }
627+ }
628+
629+ @available ( macOS 10 . 15 , iOS 13 , watchOS 6 , tvOS 13 , macCatalyst 13 , visionOS 1 . 0 , * )
630+ extension MLDSA87 {
631+ /// The size of the signature in bytes.
632+ private static let signatureByteCount = Int ( MLDSA87_SIGNATURE_BYTES)
633+ }
634+
329635private enum MLDSA {
330636 /// The size of the seed in bytes.
331637 fileprivate static let seedByteCount = 32
0 commit comments