-
Notifications
You must be signed in to change notification settings - Fork 194
Add SLH-DSA post-quantum signatures to CryptoExtras #278
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
fpseverino
wants to merge
63
commits into
apple:main
Choose a base branch
from
fpseverino:slh-dsa
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
63 commits
Select commit
Hold shift + click to select a range
140afbe
Include experimental primitives
fpseverino 3b41fd7
Test implementation of SPHINCS+
fpseverino 5f3d032
Update SPX implementation with more features and tests
fpseverino 52d7f6c
Add publicKey computed from privateKey and SPX.Signature
fpseverino a416c1e
Make the SPX message conform to Digest or DataProtocol
fpseverino 77a7f84
Add seed init for PublicKey
fpseverino 365eda1
Add ASN1 support to SPX
fpseverino 7925200
Set development flag to false
fpseverino 3c44463
Added SPX test vectors
fpseverino aa0b274
Merge pull request #1 from apple/main
fpseverino 1c25a05
Fix testing
fpseverino c530f73
Add constants for keys, seed and signature size
fpseverino ed0dac7
Merge pull request #3 from apple/main
fpseverino 98f7db1
Merge pull request #5 from apple/main
fpseverino dca0e4e
Initial commit
fpseverino 57c0ec8
Add some DocC
fpseverino 8ef5ee5
Fix tests
fpseverino 0369513
Remove unused test vectors
fpseverino 643d07c
Add `Backing`
fpseverino 25199fc
Properly encode and decode ASN1
fpseverino 385bba7
Add more asserts to tests
fpseverino dd451f9
Add DocC
fpseverino af0edd9
Various improvements
fpseverino 39b0b23
Remove ASN.1
fpseverino 9f394a0
Fix documentation to clarify key size error messages and adjust raw r…
fpseverino 2f05d99
Merge branch 'main' into slh-dsa
fpseverino dee8cae
Update CMake lists and license headers
fpseverino da39d26
Merge branch 'main' into slh-dsa
fpseverino 571fd4b
Merge branch 'main' into slh-dsa
fpseverino a975178
Refactor `bytesCount`
fpseverino 2e217ac
Try to remove unmodified file from git
fpseverino 4a3718d
Merge branch 'main' into slh-dsa
fpseverino b6a93d6
Swift Format
fpseverino bf40b76
Add proper namespace for SHA2_128s parameter set
fpseverino 7e89717
Swift Format
fpseverino e9d5a61
Merge branch 'main' into slh-dsa
fpseverino b6cfd8c
Merge branch 'main' into slh-dsa
fpseverino 06d74f2
Merge branch 'main' into slh-dsa
fpseverino 9f6d5b6
Merge branch 'main' into slh-dsa
fpseverino 5750f5a
Add more testing
fpseverino a2fd7f7
Remove `Signature` struct
fpseverino 5e4e825
Merge branch 'main' into slh-dsa
fpseverino e649513
Merge branch 'main' into slh-dsa
fpseverino 599ae88
Add `@available` decorators and use `withUnsafeBytes` for context
fpseverino ae02c71
Split `signature` and `isValidSignature` methods
fpseverino 92eac82
Add SLH-DSA and ML-KEM to DocC landing page
fpseverino e57e550
Merge branch 'main' into slh-dsa
fpseverino 7503dff
Remove ML-KEM from DocC
fpseverino 96347a7
Merge branch 'main' into slh-dsa
fpseverino c5390c8
Merge branch 'main' into slh-dsa
fpseverino 75742e9
Merge branch 'main' into slh-dsa
fpseverino a833fc9
Merge branch 'main' into slh-dsa
fpseverino 851571e
Merge branch 'main' into slh-dsa
fpseverino 24a9387
Merge branch 'main' into slh-dsa
fpseverino 2a9bba3
Merge branch 'main' into slh-dsa
fpseverino a9da481
Use `UnsafeMutableBufferPointer`
fpseverino 6907a6c
Merge branch 'main' into slh-dsa
fpseverino 2923f12
Formatting
fpseverino cf81d30
Move `withUnsafeBytes` to CryptoBoringWrapper
fpseverino 5551690
Merge branch 'main' into slh-dsa
fpseverino bd2e0e6
Merge remote-tracking branch 'origin/main' into slh-dsa
fpseverino 5a20cd8
Remove old `Optional+withUnsafeBytes`
fpseverino 6f18dcd
Merge branch 'main' into slh-dsa
fpseverino File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,314 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftCrypto open source project | ||
// | ||
// Copyright (c) 2024 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
@_implementationOnly import CCryptoBoringSSL | ||
import Crypto | ||
import CryptoBoringWrapper | ||
|
||
#if canImport(FoundationEssentials) | ||
import FoundationEssentials | ||
#else | ||
import Foundation | ||
#endif | ||
|
||
/// A stateless hash-based digital signature algorithm that provides security against quantum computing attacks. | ||
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) | ||
public enum SLHDSA {} | ||
|
||
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) | ||
extension SLHDSA { | ||
/// The SLH-DSA-SHA2-128s parameter set. | ||
public enum SHA2_128s {} | ||
} | ||
|
||
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) | ||
extension SLHDSA.SHA2_128s { | ||
/// A SLH-DSA-SHA2-128s private key. | ||
public struct PrivateKey: @unchecked Sendable { | ||
private var backing: Backing | ||
|
||
/// Initialize a SLH-DSA-SHA2-128s private key from a random seed. | ||
public init() { | ||
self.backing = Backing() | ||
} | ||
|
||
/// Initialize a SLH-DSA-SHA2-128s private key from a raw representation. | ||
/// | ||
/// - Parameter rawRepresentation: The private key bytes. | ||
/// | ||
/// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. | ||
public init(rawRepresentation: some DataProtocol) throws { | ||
self.backing = try Backing(rawRepresentation: rawRepresentation) | ||
} | ||
|
||
/// The raw representation of the private key. | ||
public var rawRepresentation: Data { | ||
self.backing.rawRepresentation | ||
} | ||
|
||
/// The public key associated with this private key. | ||
public var publicKey: PublicKey { | ||
self.backing.publicKey | ||
} | ||
|
||
/// Generate a signature for the given data. | ||
/// | ||
/// - Parameter data: The message to sign. | ||
/// | ||
/// - Returns: The signature of the message. | ||
public func signature<D: DataProtocol>(for data: D) throws -> Data { | ||
let context: Data? = nil | ||
return try self.backing.signature(for: data, context: context) | ||
} | ||
|
||
/// Generate a signature for the given data. | ||
/// | ||
/// - Parameters: | ||
/// - data: The message to sign. | ||
/// - context: The context to use for the signature. | ||
/// | ||
/// - Returns: The signature of the message. | ||
public func signature<D: DataProtocol, C: DataProtocol>(for data: D, context: C) throws -> Data { | ||
try self.backing.signature(for: data, context: context) | ||
} | ||
|
||
fileprivate final class Backing { | ||
private let pointer: UnsafeMutableBufferPointer<UInt8> | ||
|
||
func withUnsafePointer<T>(_ body: (UnsafePointer<UInt8>) throws -> T) rethrows -> T { | ||
try body(self.pointer.baseAddress!) | ||
} | ||
|
||
/// Initialize a SLH-DSA-SHA2-128s private key from a random seed. | ||
init() { | ||
self.pointer = UnsafeMutableBufferPointer<UInt8>.allocate( | ||
capacity: SLHDSA.SHA2_128s.PrivateKey.Backing.byteCount | ||
) | ||
|
||
withUnsafeTemporaryAllocation( | ||
of: UInt8.self, | ||
capacity: SLHDSA.SHA2_128s.PublicKey.Backing.byteCount | ||
) { publicKeyPtr in | ||
CCryptoBoringSSL_SLHDSA_SHA2_128S_generate_key(publicKeyPtr.baseAddress, self.pointer.baseAddress) | ||
} | ||
} | ||
|
||
/// Initialize a SLH-DSA-SHA2-128s private key from a raw representation. | ||
/// | ||
/// - Parameter rawRepresentation: The private key bytes. | ||
/// | ||
/// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. | ||
init(rawRepresentation: some DataProtocol) throws { | ||
guard rawRepresentation.count == SLHDSA.SHA2_128s.PrivateKey.Backing.byteCount else { | ||
throw CryptoKitError.incorrectKeySize | ||
} | ||
|
||
self.pointer = UnsafeMutableBufferPointer<UInt8>.allocate( | ||
capacity: SLHDSA.SHA2_128s.PrivateKey.Backing.byteCount | ||
) | ||
_ = self.pointer.initialize(fromContentsOf: rawRepresentation) | ||
} | ||
|
||
/// The raw representation of the private key. | ||
var rawRepresentation: Data { | ||
Data( | ||
UnsafeBufferPointer( | ||
start: self.pointer.baseAddress, | ||
count: SLHDSA.SHA2_128s.PrivateKey.Backing.byteCount | ||
) | ||
) | ||
} | ||
|
||
/// The public key associated with this private key. | ||
var publicKey: PublicKey { | ||
PublicKey(privateKeyBacking: self) | ||
} | ||
|
||
/// Generate a signature for the given data. | ||
/// | ||
/// - Parameters: | ||
/// - data: The message to sign. | ||
/// - context: The context to use for the signature. | ||
/// | ||
/// - Returns: The signature of the message. | ||
func signature<D: DataProtocol, C: DataProtocol>(for data: D, context: C?) throws -> Data { | ||
var signature = Data(repeating: 0, count: SLHDSA.SHA2_128s.signatureByteCount) | ||
|
||
let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in | ||
let bytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) | ||
return bytes.withUnsafeBytes { dataPtr in | ||
context.withUnsafeBytes { contextPtr in | ||
CCryptoBoringSSL_SLHDSA_SHA2_128S_sign( | ||
signaturePtr.baseAddress, | ||
self.pointer.baseAddress, | ||
dataPtr.baseAddress, | ||
dataPtr.count, | ||
contextPtr.baseAddress, | ||
contextPtr.count | ||
) | ||
} | ||
} | ||
} | ||
|
||
guard rc == 1 else { | ||
throw CryptoKitError.internalBoringSSLError() | ||
} | ||
|
||
return signature | ||
} | ||
|
||
/// The size of the private key in bytes. | ||
static let byteCount = 64 | ||
} | ||
} | ||
} | ||
|
||
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) | ||
extension SLHDSA.SHA2_128s { | ||
/// A SLH-DSA-SHA2-128s public key. | ||
public struct PublicKey: @unchecked Sendable { | ||
private var backing: Backing | ||
|
||
fileprivate init(privateKeyBacking: PrivateKey.Backing) { | ||
self.backing = Backing(privateKeyBacking: privateKeyBacking) | ||
} | ||
|
||
/// Initialize a SLH-DSA-SHA2-128s public key from a raw representation. | ||
/// | ||
/// - Parameter rawRepresentation: The public key bytes. | ||
/// | ||
/// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. | ||
public init(rawRepresentation: some DataProtocol) throws { | ||
self.backing = try Backing(rawRepresentation: rawRepresentation) | ||
} | ||
|
||
/// The raw representation of the public key. | ||
public var rawRepresentation: Data { | ||
self.backing.rawRepresentation | ||
} | ||
|
||
/// Verify a signature for the given data. | ||
/// | ||
/// - Parameters: | ||
/// - signature: The signature to verify. | ||
/// - data: The message to verify the signature against. | ||
/// | ||
/// - Returns: `true` if the signature is valid, `false` otherwise. | ||
public func isValidSignature<S: DataProtocol, D: DataProtocol>( | ||
_ signature: S, | ||
for data: D | ||
) -> Bool { | ||
let context: Data? = nil | ||
return self.backing.isValidSignature(signature, for: data, context: context) | ||
} | ||
|
||
/// Verify a signature for the given data. | ||
/// | ||
/// - Parameters: | ||
/// - signature: The signature to verify. | ||
/// - data: The message to verify the signature against. | ||
/// - context: The context to use for the signature verification. | ||
/// | ||
/// - Returns: `true` if the signature is valid, `false` otherwise. | ||
public func isValidSignature<S: DataProtocol, D: DataProtocol, C: DataProtocol>( | ||
_ signature: S, | ||
for data: D, | ||
context: C | ||
) -> Bool { | ||
self.backing.isValidSignature(signature, for: data, context: context) | ||
} | ||
|
||
fileprivate final class Backing { | ||
private let pointer: UnsafeMutableBufferPointer<UInt8> | ||
|
||
init(privateKeyBacking: PrivateKey.Backing) { | ||
self.pointer = UnsafeMutableBufferPointer<UInt8>.allocate( | ||
capacity: SLHDSA.SHA2_128s.PublicKey.Backing.byteCount | ||
) | ||
privateKeyBacking.withUnsafePointer { privateKeyPtr in | ||
CCryptoBoringSSL_SLHDSA_SHA2_128S_public_from_private(self.pointer.baseAddress, privateKeyPtr) | ||
} | ||
} | ||
|
||
/// Initialize a SLH-DSA-SHA2-128s public key from a raw representation. | ||
/// | ||
/// - Parameter rawRepresentation: The public key bytes. | ||
/// | ||
/// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. | ||
init(rawRepresentation: some DataProtocol) throws { | ||
guard rawRepresentation.count == SLHDSA.SHA2_128s.PublicKey.Backing.byteCount else { | ||
throw CryptoKitError.incorrectKeySize | ||
} | ||
|
||
self.pointer = UnsafeMutableBufferPointer<UInt8>.allocate( | ||
capacity: SLHDSA.SHA2_128s.PublicKey.Backing.byteCount | ||
) | ||
_ = self.pointer.initialize(fromContentsOf: rawRepresentation) | ||
} | ||
|
||
/// The raw representation of the public key. | ||
var rawRepresentation: Data { | ||
Data( | ||
UnsafeBufferPointer( | ||
start: self.pointer.baseAddress, | ||
count: SLHDSA.SHA2_128s.PublicKey.Backing.byteCount | ||
) | ||
) | ||
} | ||
|
||
/// Verify a signature for the given data. | ||
/// | ||
/// - Parameters: | ||
/// - signature: The signature to verify. | ||
/// - data: The message to verify the signature against. | ||
/// - context: The context to use for the signature verification. | ||
/// | ||
/// - Returns: `true` if the signature is valid, `false` otherwise. | ||
func isValidSignature<S: DataProtocol, D: DataProtocol, C: DataProtocol>( | ||
_ signature: S, | ||
for data: D, | ||
context: C? | ||
) -> Bool { | ||
let signatureBytes: ContiguousBytes = | ||
signature.regions.count == 1 ? signature.regions.first! : Array(signature) | ||
return signatureBytes.withUnsafeBytes { signaturePtr in | ||
let dataBytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) | ||
let rc: CInt = dataBytes.withUnsafeBytes { dataPtr in | ||
context.withUnsafeBytes { contextPtr in | ||
CCryptoBoringSSL_SLHDSA_SHA2_128S_verify( | ||
signaturePtr.baseAddress, | ||
signaturePtr.count, | ||
self.pointer.baseAddress, | ||
dataPtr.baseAddress, | ||
dataPtr.count, | ||
contextPtr.baseAddress, | ||
contextPtr.count | ||
) | ||
} | ||
} | ||
return rc == 1 | ||
} | ||
} | ||
|
||
/// The size of the public key in bytes. | ||
static let byteCount = 32 | ||
} | ||
} | ||
} | ||
|
||
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) | ||
extension SLHDSA.SHA2_128s { | ||
/// The size of the signature in bytes. | ||
private static let signatureByteCount = 7856 | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.