diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/K1Tests.xcbaseline/4402FEFC-8036-409F-AFED-19F9F70B9B2E.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/K1Tests.xcbaseline/4402FEFC-8036-409F-AFED-19F9F70B9B2E.plist
new file mode 100644
index 0000000..f01771c
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcbaselines/K1Tests.xcbaseline/4402FEFC-8036-409F-AFED-19F9F70B9B2E.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ classNames
+
+ PerformanceTests
+
+ testPerformance()
+
+ com.apple.XCTPerformanceMetric_WallClockTime
+
+ baselineAverage
+ 0.029900
+ baselineIntegrationDisplayName
+ Local Baseline
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/K1Tests.xcbaseline/Info.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/K1Tests.xcbaseline/Info.plist
new file mode 100644
index 0000000..888790c
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcbaselines/K1Tests.xcbaseline/Info.plist
@@ -0,0 +1,33 @@
+
+
+
+
+ runDestinationsByUUID
+
+ 4402FEFC-8036-409F-AFED-19F9F70B9B2E
+
+ localComputer
+
+ busSpeedInMHz
+ 0
+ cpuCount
+ 1
+ cpuKind
+ Apple M1 Pro
+ cpuSpeedInMHz
+ 0
+ logicalCPUCoresPerPackage
+ 10
+ modelCode
+ MacBookPro18,3
+ physicalCPUCoresPerPackage
+ 10
+ platformIdentifier
+ com.apple.platform.macosx
+
+ targetArchitecture
+ arm64
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/K1.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/K1.xcscheme
new file mode 100644
index 0000000..d7b025a
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/K1.xcscheme
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Package.resolved b/Package.resolved
deleted file mode 100644
index ecb0362..0000000
--- a/Package.resolved
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "object": {
- "pins": [
- {
- "package": "ASN1Decoder",
- "repositoryURL": "https://github.com/filom/ASN1Decoder",
- "state": {
- "branch": null,
- "revision": "65953a42a0f039f53c73e48fd88c02809f7db607",
- "version": "1.8.0"
- }
- }
- ]
- },
- "version": 1
-}
diff --git a/Package.swift b/Package.swift
index 5642959..0adfee2 100644
--- a/Package.swift
+++ b/Package.swift
@@ -3,6 +3,21 @@
import PackageDescription
+let cSettings: [CSetting] = [
+ // Basic config values that are universal and require no dependencies.
+ // https://github.com/bitcoin-core/secp256k1/blob/master/src/basic-config.h#L12-L13
+ .define("ECMULT_WINDOW_SIZE", to: "15"),
+ .define("ECMULT_GEN_PREC_BITS", to: "4"),
+
+ // Enable modules in secp256k1.
+ // See bottom of: Sources/secp256k1/libsecp256k1/src/secp256k1.c
+ // For list
+ .define("ENABLE_MODULE_ECDH"),
+ .define("ENABLE_MODULE_RECOVERY"),
+ .define("ENABLE_MODULE_SCHNORRSIG"),
+ .define("ENABLE_MODULE_EXTRAKEYS"),
+]
+
let package = Package(
name: "K1",
platforms: [
@@ -17,10 +32,7 @@ let package = Package(
]
),
],
- dependencies: [
- // Only used by tests
- .package(url: "https://github.com/filom/ASN1Decoder", from: "1.8.0")
- ],
+ dependencies: [],
targets: [
// Target `libsecp256k1` https://github.com/bitcoin-core/secp256k1
@@ -50,37 +62,21 @@ let package = Package(
"libsecp256k1/README.md",
"libsecp256k1/SECURITY.md"
],
- cSettings: [
- // Basic config values that are universal and require no dependencies.
- // https://github.com/bitcoin-core/secp256k1/blob/master/src/basic-config.h#L12-L13
- .define("ECMULT_WINDOW_SIZE", to: "15"),
- .define("ECMULT_GEN_PREC_BITS", to: "4"),
-
- // Enable modules in secp256k1.
- // See bottom of: Sources/secp256k1/libsecp256k1/src/secp256k1.c
- // For list
- .define("ENABLE_MODULE_ECDH"),
- .define("ENABLE_MODULE_RECOVERY"),
- .define("ENABLE_MODULE_SCHNORRSIG"),
- .define("ENABLE_MODULE_EXTRAKEYS"),
- ]
+ cSettings: cSettings
),
-
.target(
name: "K1",
dependencies: [
- // ECDSA, Schnorr, ECDH etc.
"secp256k1",
+ ],
+ swiftSettings: [
+ .define("CRYPTO_IN_SWIFTPM_FORCE_BUILD_API"),
]
),
-
.testTarget(
name: "K1Tests",
dependencies: [
"K1",
-
- // DER decoding of public keys from test vectors
- "ASN1Decoder"
],
resources: [
.process("TestVectors/"),
diff --git a/README.md b/README.md
index fd047e5..a6f59f0 100644
--- a/README.md
+++ b/README.md
@@ -3,38 +3,184 @@
_K1_ is Swift wrapper around [libsecp256k1 (bitcoin-core/secp256k1)][lib], offering ECDSA, Schnorr ([BIP340][bip340]) and ECDH features.
-# Features
+# API
+The API of K1 maps almost 1:1 with Apple's [CryptoKit][ck], vendoring a set of keypairs, one per feature. E.g. in CryptoKit you have `Curve25519.KeyAgreement.PrivateKey` and `Curve25519.KeyAgreement.PublicKey` which are seperate for `Curve25519.Signing.PrivateKey` and `Curve25519.Signing.PublicKey`.
-## ECDSA Signatures
+Just like that K1 vendors these key pairs:
+- `K1.KeyAgreement.PrivateKey` / `K1.KeyAgreement.PublicKey` for key agreement (ECDH)
+- `K1.Schnorr.PrivateKey` / `K1.Schnorr.PublicKey` for sign / verify methods using Schnorr signature scheme
+- `K1.ECDSA.Recoverable.PrivateKey` / `K1.ECDSA.Recoverable.PublicKey` for sign / verify methods using ECDSA (producing/validating signature where public key is recoverable)
+- `K1.ECDSA.NonRecoverable.PrivateKey` / `K1.ECDSA.NonRecoverable.PublicKey` for sign / verify methods using ECDSA (producing/validating signature where public key is **not** recoverable)
+
+Just like you can convert between e.g. `Curve25519.KeyAgreement.PrivateKey` and `Curve25519.Signing.PrivateKey` back and forth using any of the initializers and serializer, you can convert between all PrivateKeys and all PublicKeys of all features in K1.
+
+All keys conform to `K1KeyExportable` protocol below to serialize the Private/PubliKey:
+
+```swift
+public protocol K1KeyExportable {
+ var rawRepresentation: Data { get }
+ var x963Representation: Data { get }
+ var derRepresentation: Data { get }
+ var pemRepresentation: String { get }
+}
+```
+
+All keys conform to `K1KeyImportable` protocol below to deserialize to Private/PubliKey:
```swift
-let alice = try K1.PrivateKey.generateNew()
-let message = "Send Bob 3 BTC".data(using: .utf8)!
-let signature = try alice.ecdsaSign(unhashed: message)
-let isSignatureValid = try alice.publicKey.isValidECDSASignature(signature, unhashed: message)
-assert(isSignatureValid, "Signature should be valid.")
+public protocol K1KeyImportable {
+ init(rawRepresentation: some ContiguousBytes) throws
+ init(x963Representation: some ContiguousBytes) throws
+ init(derRepresentation: some RandomAccessCollection) throws
+ init(pemRepresentation: String) throws
+}
```
+Furthermore, all PrivateKey's conform to this protocol
+
+```swift
+protocol K1PrivateKeyProtocol: K1KeyPortable {
+ associatedtype PublicKey: K1PublicKeyProtocol
+ var publicKey: PublicKey { get }
+ init()
+}
+```
+
+Furthermore, all PublicKey's conform to this protocol
+
+```swift
+public protocol K1PublicKeyProtocol: K1KeyPortable {
+ init(compressedRepresentation: some ContiguousBytes) throws
+ var compressedRepresentation: Data { get }
+}
+
+```
+
+
+# ECDSA (Elliptic Curve Digital Signature Algorithm)
+
+There exists two set of ECDSA key pairs:
+- A key pair for signatures from which you can recover the public key, specifically: `K1.ECDSA.Recoverable.PrivateKey` and `K1.ECDSA.Recoverable.PublicKey`
+- A key pair for signatures from which you can **not** recover the public key, specifically: `K1.ECDSA.NonRecoverable.PrivateKey` and `K1.ECDSA.NonRecoverable.PublicKey`
+
+For each private key there exists two different `signature:for:options` (one taking hashed data and taking `Digest` as argument) methods and one `signature:forUnhashed:options`.
+
+The `option` is a `K1.ECDSA.SigningOptions` struct, which by default specifies [`RFC6979`][rfc6979] deterministic signing, as per Bitcoin standard, however, you can change to use secure random nonce instead.
-## Schnorr Signatures
+## NonRecoverable
+### Sign
```swift
-let alice = try K1.PrivateKey.generateNew()
-let message = "Send Bob 3 BTC".data(using: .utf8)!
-let signature = try alice.schnorrSign(unhashed: message)
-let isSignatureValid = try alice.publicKey.isValidSchnorrSignature(signature, unhashed: message)
-assert(isSignatureValid, "Signature should be valid.")
+let alice = K1.ECDA.NonRecovarable.PrivateKey()
```
-### Schnorr Scheme
+#### Hashed (Data)
+
+```swift
+let hashedMessage: Data = // from somewhere
+let signature = try alice.signature(for: hashedMessage)
+```
+
+#### Digest
+
+```swift
+let message: Data = // from somewhere
+let digest = SHA256.hash(data: message)
+let signature = try alice.signature(for: digest)
+```
+
+#### Hash and Sign
+
+The `forUnhashed` will `SHA256` hash the message and then sign it.
+
+```swift
+let message: Data = // from somewhere
+let signature = try alice.signature(forUnhashed: message)
+```
+
+### Validate
+
+#### Hashed (Data)
+
+```swift
+let hashedMessage: Data = // from somewhere
+let publicKey: K1.ECDSA.NonRecoverable.PublicKey = alice.publcKey
+let signature: K1.ECDSA.NonRecoverable.Signature // from above
+
+assert(
+ publicKey.isValidSignature(signature, hashed: hashedMessage)
+) // PASS
+```
+
+#### Digest
+
+```swift
+let message: Data = // from somewhere
+let digest = SHA256.hash(data: message)
+let signature: K1.ECDSA.NonRecoverable.Signature // from above
+
+assert(
+ publicKey.isValidSignature(signature, digest: digest)
+) // PASS
+```
+
+#### Hash and Validate
+
+```swift
+let message: Data = // from somewhere
+let signature: K1.ECDSA.NonRecoverable.Signature // from above
+
+assert(
+ publicKey.isValidSignature(signature, unhashed: message)
+) // PASS
+```
+
+
+## Recoverable
+
+All signing and validation APIs are identical to the `NonRecoverable` namespace.
+
+```swift
+let alice = K1.ECDA.Recovarable.PrivateKey()
+let message: Data = // from somewhere
+let digest = SHA256.hash(data: message)
+let signature: K1.ECDSA.Recoverable.Signature = try alice.signature(for: digest)
+let publicKey: K1.ECDSA.Recoverable.PublicKey = alice.publicKey
+assert(
+ publicKey.isValidSignature(signature, digest: digest)
+) // PASS
+```
+
+
+# Schnorr Signature Scheme
+
+## Sign
+
+```swift
+let alice = K1.Schnorr.PrivateKey()
+let signature = try alice.signature(forUnhashed: message)
+```
+
+There exists other sign variants, `signature:for:options` (hashed data) and `signature:for:options` (`Digest`) if you already have a hashed message. All three variants takes a `K1.Schnorr.SigningOptions` struct where you can pass `auxiliaryRandomData` to be signed.
+
+## Validate
+
+```swift
+let publicKey: K1.Schnorr.PublicKey = alice.publicKey
+assert(publicKey.isValidSignature(signature, unhashed: message)) // PASS
+```
+
+Or alternatively `isValidSignature:digest` or `isValidSignature:hashed`.
+
+#### Schnorr Scheme
The Schnorr signature implementation is [BIP340][bip340], since we use _libsecp256k1_ which only provides the [BIP340][bip340] Schnorr scheme.
It is worth noting that some Schnorr implementations are incompatible with [BIP340][bip340] and thus this library, e.g. [Zilliqa's](https://github.com/Zilliqa/schnorr/blob/master/src/libSchnorr/src/Schnorr.cpp#L86-L242) ([kudelski report](https://docs.zilliqa.com/zilliqa-schnorr-audit-by-kudelski_public-release.pdf), [libsecp256k1 proposal](https://github.com/bitcoin-core/secp256k1/issues/1070), [Twitter thread](https://twitter.com/AmritKummer/status/1489645007699066886?s=20&t=eDgd5221qEPOVyStY0A8SA)).
-## ECDH
+# ECDH
This library vendors three different EC Diffie-Hellman (ECDH) key exchange functions:
1. `ASN1 x9.63` - No hash, return only the `X` coordinate of the point - `sharedSecretFromKeyAgreement -> SharedSecret`
@@ -42,18 +188,18 @@ This library vendors three different EC Diffie-Hellman (ECDH) key exchange funct
3. Custom - No hash, return point uncompressed - `ecdhPoint -> Data`
```swift
-let alice = try K1.PrivateKey.generateNew()
-let bob = try K1.PrivateKey.generateNew()
+let alice = try K1.KeyAgreement.PrivateKey()
+let bob = try K1.KeyAgreement.PrivateKey()
```
-### `ASN1 x9.63` ECDH
+## `ASN1 x9.63` ECDH
Returning only the `X` coordinate of the point, following [ANSI X9.63][x963] standards, embedded in a [`CryptoKit.SharedSecret`][ckss], which is useful since you can use `CryptoKit` key derivation functions on this SharedSecret, e.g. [`x963DerivedSymmetricKey`](https://developer.apple.com/documentation/cryptokit/sharedsecret/x963derivedsymmetrickey(using:sharedinfo:outputbytecount:)) or [`hkdfDerivedSymmetricKey`](https://developer.apple.com/documentation/cryptokit/sharedsecret/hkdfderivedsymmetrickey(using:salt:sharedinfo:outputbytecount:)).
You can retrieve the `X` coordinate as raw data using `withUnsafeBytes` if you need to.
```swift
let ab: CryptoKit.SharedSecret = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey)
-let ba: : CryptoKit.SharedSecret = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)
+let ba: CryptoKit.SharedSecret = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)
assert(ab == ba) // pass
@@ -62,19 +208,21 @@ ab.withUnsafeBytes {
}
```
-### `libsecp256k1` ECDH
+## `libsecp256k1` ECDH
-Using `libsecp256k1` default behaviour, returning a SHA-256 hash of the **compressed** point.
+Using `libsecp256k1` default behaviour, returning a SHA-256 hash of the **compressed** point, embedded in a [`CryptoKit.SharedSecret`][ckss], which is useful since you can use `CryptoKit` key derivation functions.
```swift
-let ab: Data = try alice.ecdh(with: bob.publicKey)
-let ba: Data = try bob.ecdh(with: alice.publicKey)
+let ab: CryptoKit.SharedSecret = try alice.ecdh(with: bob.publicKey)
+let ba: CryptoKit.SharedSecret = try bob.ecdh(with: alice.publicKey)
assert(ab == ba) // pass
-assert(ab.count == 32) // pass
+ab.withUnsafeBytes {
+ assert(Data($0).count == 32) // pass
+}
```
-### Custom ECDH
+## Custom ECDH
Returns an entire uncompresed EC point, without hashing it. Might be useful if you wanna construct your own cryptographic functions, e.g. some custom ECIES.
@@ -87,30 +235,36 @@ assert(ab.count == 65) // pass
```
-# Alternatives
-
-- [GigaBitcoin/secp256k1.swift](https://github.com/GigaBitcoin/secp256k1.swift) (also using `libsecp256k1`, ⚠️ possibly unsafe, ✅ Schnorr support)
-- [KevinVitale/WalletKit](https://github.com/KevinVitale/WalletKit/) (also using `libsecp256k1`, ❌ No Schnorr)
-- [leif-ibsen/SwiftECC](https://github.com/leif-ibsen/SwiftECC) (Custom ECC impl, ⚠️ possibly unsafe, ❌ No Schnorr)
-- [yenom/BitcoinKit](https://github.com/yenom/BitcoinKit) (💀 Discontinued, also using `libsecp256k1`, ❌ No Schnorr)
-- [oleganza/CoreBitcoin](https://github.com/oleganza/CoreBitcoin) (OpenSSL as ECC impl, ObjC + Swift, ⚠️ possibly unsafe, ❌ No Schnorr)
-- [Sajjon/EllipticCurveKit](https://github.com/Sajjon/EllipticCurveKit) (Custom ECC impl (mine), ☣️ unsafe, ✅ Schnorr support)
+# Acknowledgements
+`K1` is a Swift wrapper around [libsecp256k1][lib], so this library would not exist without the Bitcoin Core developers. Massive thank you for a wonder ful library! I've included it as a submodule, without any changes to the code, i.e. with copyright headers in files intact.
-## Non-Swift but SPM support
-[greymass/secp256k1](https://github.com/greymass/secp256k1) (Fork of `libsecp256k1`)
+`K1` uses some code from [`swift-crypto`][swc], which has been copied over with relevant copyright header. Since [`swift-crypto`][swc] is licensed under [Apache](https://github.com/apple/swift-crypto/blob/main/LICENSE.txt), so is this library.
# Development
Stand in root and run
```sh
-git submodule init
-git submodule update
+./scripts/build.sh
```
To clone the dependency [libsecp256k1][lib], using commit [427bc3cdcfbc74778070494daab1ae5108c71368](https://github.com/bitcoin-core/secp256k1/commit/427bc3cdcfbc74778070494daab1ae5108c71368) (semver 0.3.0)
+# Alternatives
+
+- [GigaBitcoin/secp256k1.swift](https://github.com/GigaBitcoin/secp256k1.swift) (also using `libsecp256k1`, ⚠️ possibly unsafe, ✅ Schnorr support)
+- [KevinVitale/WalletKit](https://github.com/KevinVitale/WalletKit/) (also using `libsecp256k1`, ❌ No Schnorr)
+- [leif-ibsen/SwiftECC](https://github.com/leif-ibsen/SwiftECC) (Custom ECC impl, ⚠️ possibly unsafe, ❌ No Schnorr)
+- [yenom/BitcoinKit](https://github.com/yenom/BitcoinKit) (💀 Discontinued, also using `libsecp256k1`, ❌ No Schnorr)
+- [oleganza/CoreBitcoin](https://github.com/oleganza/CoreBitcoin) (OpenSSL as ECC impl, ObjC + Swift, ⚠️ possibly unsafe, ❌ No Schnorr)
+- [Sajjon/EllipticCurveKit](https://github.com/Sajjon/EllipticCurveKit) (Custom ECC impl (mine), ☣️ unsafe, ✅ Schnorr support)
+
+
+[ck]: https://developer.apple.com/documentation/cryptokit
[BIP340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
[lib]: https://github.com/bitcoin-core/secp256k1
[x963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017
[ckss]: https://developer.apple.com/documentation/cryptokit/sharedsecret
+[swc]: https://github.com/apple/swift-crypto
+[rfc6979]: https://www.rfc-editor.org/rfc/rfc6979
+[mall]: https://en.bitcoin.it/wiki/Transaction_malleability
diff --git a/Sources/K1/Deprecations/ECDSASignature+Deprecated.swift b/Sources/K1/Deprecations/ECDSASignature+Deprecated.swift
deleted file mode 100644
index d9160fc..0000000
--- a/Sources/K1/Deprecations/ECDSASignature+Deprecated.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2023-03-10.
-//
-
-import Foundation
-
-@available(*, deprecated, message: "'ECDSASignature' is a deprecated typealias for 'ECDSASignatureNonRecoverable', use 'ECDSASignatureNonRecoverable' or ECDSASignatureRecovarable' instead.")
-public typealias ECDSASignature = ECDSASignatureNonRecoverable
diff --git a/Sources/K1/K1.swift b/Sources/K1/K1.swift
new file mode 100644
index 0000000..25aa707
--- /dev/null
+++ b/Sources/K1/K1.swift
@@ -0,0 +1,6 @@
+import Foundation
+
+/// The Elliptic Curve `secp256k1`
+public enum K1 {
+ // The namespace for all structs and operations vendored by this library.
+}
diff --git a/Sources/K1/K1/ECDH/KeyAgreement.swift b/Sources/K1/K1/ECDH/KeyAgreement.swift
new file mode 100644
index 0000000..b1b08f1
--- /dev/null
+++ b/Sources/K1/K1/ECDH/KeyAgreement.swift
@@ -0,0 +1,119 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-24.
+//
+
+import Foundation
+import struct CryptoKit.SharedSecret
+
+public protocol K1Feature {
+ associatedtype PublicKey: K1PublicKeyProtocol
+}
+
+extension K1 {
+
+ /// A mechanism used to create a shared secret between two users by performing `secp256k1` elliptic curve Diffie Hellman (ECDH) key exchange.
+ public enum KeyAgreement: K1Feature {
+
+ /// A `secp256k1` private key used for key agreement.
+ public typealias PrivateKey = PrivateKeyOf
+
+ /// A `secp256k1` public key used for key agreement.
+ public typealias PublicKey = PublicKeyOf
+ }
+}
+
+extension K1.KeyAgreement.PrivateKey {
+ /// Computes a shared secret with the provided public key from another party,
+ /// returning only the `X` coordinate of the point, following [ANSI X9.63][ansix963] standards.
+ ///
+ /// This is one of three ECDH functions, this library vendors, all three versions
+ /// uses different serialization of the shared EC Point, specifically:
+ /// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point <- this function
+ /// 2. `libsecp256k1`: SHA-256 hash the compressed point
+ /// 3. Custom: No hash, return point uncompressed
+ ///
+ /// This function uses 3. i.e. no hash, and returns only the `X` coordinate of the point.
+ /// This is following the [ANSI X9.63][ansix963] standard serialization of the shared point.
+ ///
+ /// Further more this function is compatible with CryptoKit, since it returns a CryptoKit
+ /// `SharedSecret` struct, thus offering you to use all of CryptoKit's Key Derivation Functions
+ /// (`KDF`s), which can be called on the `SharedSecret`.
+ ///
+ /// As seen on [StackExchange][cryptostackexchange], this version is compatible with the following
+ /// libraries:
+ /// - JS: `elliptic` (v6.4.0 in nodeJS v8.2.1)
+ /// - JS: `crypto` (builtin) - uses openssl under the hood (in nodeJS v8.2.1)
+ /// - .NET: `BouncyCastle` (BC v1.8.1.3, .NET v2.1.4)
+ /// - Python: pyca/cryptography (hazmat)
+ ///
+ /// [ansix963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017
+ /// [cryptostackexchange]: https://crypto.stackexchange.com/a/57727
+ public func sharedSecretFromKeyAgreement(
+ with publicKey: PublicKey
+ ) throws -> SharedSecret {
+ let data = try FFI.ECDH.keyExchange(
+ publicKey: publicKey.impl.wrapped,
+ privateKey: self.impl.wrapped,
+ serializeOutputFunction: .ansiX963
+ )
+ return try SharedSecret(data: data)
+ }
+
+
+ /// Computes a shared secret with the provided public key from another party,
+ /// using `libsecp256k1` default behaviour, returning a hashed of the compressed point.
+ ///
+ /// This is one of three ECDH functions, this library vendors, all three versions
+ /// uses different serialization of the shared EC Point, specifically:
+ /// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point
+ /// 2. `libsecp256k1`: SHA-256 hash the compressed point <- this function
+ /// 3. Custom: No hash, return point uncompressed
+ ///
+ /// This function uses 1. i.e.SHA-256 hash the compressed point.
+ /// This is using the [default behaviour of `libsecp256k1`][libsecp256k1], which does not adhere to any
+ /// other standard.
+ ///
+ /// As seen on [StackExchange][cryptostackexchange], this version is compatible with all
+ /// libraries which wraps `libsecp256k1`, e.g.:
+ /// - Python wrapper: secp256k1 (v0.13.2, for python 3.6.4)
+ /// - JS wrapper: secp256k1 (v3.5.0, for nodeJS v8.2.1)
+ ///
+ /// [libsecp256k1]: https://github.com/bitcoin-core/secp256k1/blob/master/src/modules/ecdh/main_impl.h#L27
+ /// [cryptostackexchange]: https://crypto.stackexchange.com/a/57727
+ ///
+ public func ecdh(
+ with publicKey: PublicKey,
+ arbitraryData: Data? = nil
+ ) throws -> SharedSecret {
+ let data = try FFI.ECDH.keyExchange(
+ publicKey: publicKey.impl.wrapped,
+ privateKey: self.impl.wrapped,
+ serializeOutputFunction: .libsecp256kDefault(arbitraryData: arbitraryData)
+ )
+ return try SharedSecret(data: data)
+ }
+
+ /// Computes a shared secret with the provided public key from another party,
+ /// returning an uncompressed public point, unhashed.
+ ///
+ /// This is one of three ECDH functions, this library vendors, all three versions
+ /// uses different serialization of the shared EC Point, specifically:
+ /// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point
+ /// 2. `libsecp256k1`: SHA-256 hash the compressed point
+ /// 3. Custom: No hash, return point uncompressed <- this function
+ ///
+ /// This function uses 2. i.e. no hash, return point uncompressed
+ /// **This is not following any standard at all**, but might be useful if you want to write your
+ /// cryptographic functions, e.g. some ECIES scheme.
+ ///
+ public func ecdhPoint(with publicKey: PublicKey) throws -> Data {
+ try FFI.ECDH.keyExchange(
+ publicKey: publicKey.impl.wrapped,
+ privateKey: self.impl.wrapped,
+ serializeOutputFunction: .noHashWholePoint
+ )
+ }
+}
diff --git a/Sources/K1/K1/ECDSA/ECDSA.swift b/Sources/K1/K1/ECDSA/ECDSA.swift
new file mode 100644
index 0000000..569be98
--- /dev/null
+++ b/Sources/K1/K1/ECDSA/ECDSA.swift
@@ -0,0 +1,80 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-24.
+//
+
+import Foundation
+
+
+extension K1 {
+
+ /// A mechanism used to create or verify a cryptographic signature using the `secp256k1` elliptic curve digital signature algorithm (ECDSA).
+ public enum ECDSA {}
+}
+
+extension K1.ECDSA {
+ public struct ValidationOptions {
+
+ public let malleabilityStrictness: MalleabilityStrictness
+
+ public init(malleabilityStrictness: MalleabilityStrictness) {
+ self.malleabilityStrictness = malleabilityStrictness
+ }
+
+ }
+}
+
+extension K1.ECDSA.ValidationOptions {
+
+ public static let `default`: Self = .init(
+ malleabilityStrictness: .rejected
+ )
+
+ /// Whether or not to consider malleable signatures valid.
+ ///
+ /// [more]: https://github.com/bitcoin-core/secp256k1/blob/2e5e4b67dfb67950563c5f0ab2a62e25eb1f35c5/include/secp256k1.h#L510-L550
+ public enum MalleabilityStrictness {
+ /// Considers all malleable signatures **invalid**.
+ case rejected
+
+ /// Accepts malleable signatures valid.
+ case accepted
+ }
+}
+
+// MARK: SigningOptions
+extension K1.ECDSA {
+ public struct SigningOptions: Sendable, Hashable {
+ public let nonceFunction: NonceFunction
+
+ public init(nonceFunction: NonceFunction) {
+ self.nonceFunction = nonceFunction
+ }
+ }
+}
+
+extension K1.ECDSA.SigningOptions {
+
+ public static let `default`: Self = .init(nonceFunction: .deterministic())
+
+ public enum NonceFunction: Sendable, Hashable {
+ case random
+
+ /// RFC6979
+ case deterministic(arbitraryData: RFC6979ArbitraryData? = nil)
+ }
+}
+
+extension K1.ECDSA.SigningOptions.NonceFunction {
+ public struct RFC6979ArbitraryData: Sendable, Hashable {
+ public let arbitraryData: [UInt8]
+ public init(arbitraryData: [UInt8]) throws {
+ guard arbitraryData.count == Curve.Field.byteCount else {
+ throw K1.Error.incorrectByteCountOfArbitraryDataForNonceFunction
+ }
+ self.arbitraryData = arbitraryData
+ }
+ }
+}
diff --git a/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable+Conversion.swift b/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable+Conversion.swift
deleted file mode 100644
index e8e1945..0000000
--- a/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable+Conversion.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-// Bridge to C
-import secp256k1
-import Foundation
-
-extension ECDSASignatureNonRecoverable {
- /// Initializes ECDSASignatureNonRecoverable from the DER representation.
- public static func `import`(fromDER derRepresentation: D) throws -> Self {
- let signatureData = try Bridge.importECDSASignature(fromDER: derRepresentation)
- return try Self(rawRepresentation: signatureData)
- }
-
- public func compactRepresentation() throws -> Data {
- try Bridge.compactRepresentationOfSignature(rawRepresentation: rawRepresentation)
- }
-
- public func derRepresentation() throws -> Data {
- try Bridge.derRepresentationOfSignature(rawRepresentation: rawRepresentation)
- }
-}
-
diff --git a/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable.swift b/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable.swift
index 6c1c824..5b756f2 100644
--- a/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable.swift
+++ b/Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable.swift
@@ -6,44 +6,235 @@
//
import Foundation
-import CryptoKit
-import secp256k1
+import protocol CryptoKit.Digest
+import struct CryptoKit.SHA256
-public struct ECDSASignatureNonRecoverable: Sendable, Hashable, ECSignature {
+extension K1.ECDSA {
- public let rawRepresentation: Data
-
- public init(rawRepresentation: D) throws {
- guard
- rawRepresentation.count == Self.byteCount
- else {
- throw K1.Error.incorrectByteCountOfRawSignature
- }
+ /// A mechanism used to create or verify a cryptographic signature using the `secp256k1` elliptic curve digital signature algorithm (ECDSA), signatures that do not offer recovery of the public key.
+ public enum NonRecoverable: K1Feature {
+
+ /// A `secp256k1` private key used to create cryptographic signatures,
+ /// more specifically ECDSA signatures, that do not offer recovery of the public key.
+ public typealias PrivateKey = PrivateKeyOf
+
+ /// A `secp256k1` public key used to verify cryptographic signatures,
+ /// more specifically ECDSA signatures, that do not offer recovery of the public key.
+ public typealias PublicKey = PublicKeyOf
+ }
+}
+
+extension K1.ECDSA.NonRecoverable {
+ public struct Signature: Sendable, Hashable, ContiguousBytes {
- self.rawRepresentation = Data(rawRepresentation)
+ typealias Wrapped = FFI.ECDSA.NonRecovery.Wrapped
+ internal let wrapped: Wrapped
+
+ init(wrapped: Wrapped) {
+ self.wrapped = wrapped
+ }
+ }
+}
+
+// MARK: Sign
+extension K1.ECDSA.NonRecoverable.PrivateKey {
+
+ /// Generates an Elliptic Curve Digital Signature Algorithm (ECDSA) signature of _hashed_ data you provide over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - hashed: The _hashed_ data to sign.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: The signature corresponding to the data. The signing algorithm uses deterministic or random nonces, dependent on `options`, thus either deterministically producing the same signature or the same data and key, or different on every call.
+ public func signature(
+ for hashed: some DataProtocol,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> K1.ECDSA.NonRecoverable.Signature {
+ try K1.ECDSA.NonRecoverable.Signature(
+ wrapped: FFI.ECDSA.NonRecovery.sign(
+ hashedMessage: [UInt8](hashed),
+ privateKey: impl.wrapped,
+ options: options
+ )
+ )
}
- public init(compactRepresentation: D) throws {
- var signature = secp256k1_ecdsa_signature()
- let compactBytes = [UInt8](compactRepresentation)
- try Bridge.call(ifFailThrow: .failedToParseSignatureFromCompactRepresentation) { context in
- secp256k1_ecdsa_signature_parse_compact(
- context,
- &signature,
- compactBytes
+ /// Generates an Elliptic Curve Digital Signature Algorithm (ECDSA) signature of the digest you provide over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - digest: The digest of the data to sign.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: The signature corresponding to the data. The signing algorithm uses deterministic or random nonces, dependent on `options`, thus either deterministically producing the same signature or the same data and key, or different on every call.
+ public func signature(
+ for digest: some Digest,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> K1.ECDSA.NonRecoverable.Signature {
+ try signature(
+ for: Data(digest),
+ options: options
+ )
+ }
+
+ /// Generates an elliptic curve digital signature algorithm (ECDSA) signature of the given data over the `secp256k1` elliptic curve, using SHA-256 as a hash function.
+ /// - Parameters:
+ /// - unhashed: The data hash and then to sign.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: The signature corresponding to the data. The signing algorithm uses deterministic or random nonces, dependent on `options`, thus either deterministically producing the same signature or the same data and key, or different on every call.
+ public func signature(
+ forUnhashed unhashed: some DataProtocol,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> K1.ECDSA.NonRecoverable.Signature {
+ try signature(
+ for: SHA256.hash(data: unhashed),
+ options: options
+ )
+ }
+}
+
+
+// MARK: Validate
+extension K1.ECDSA.NonRecoverable.PublicKey {
+ /// Verifies an elliptic curve digital signature algorithm (ECDSA) signature on some _hash_ over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - signature: The (non-recoverable) signature to check against the _hashed_ data.
+ /// - hashed: The _hashed_ data covered by the signature.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given _hashed_ data.
+ public func isValidSignature(
+ _ signature: K1.ECDSA.NonRecoverable.Signature,
+ hashed: some DataProtocol,
+ options: K1.ECDSA.ValidationOptions = .default
+ ) -> Bool {
+ do {
+ return try FFI.ECDSA.NonRecovery.isValid(
+ ecdsaSignature: signature.wrapped,
+ publicKey: self.impl.wrapped,
+ message: [UInt8](hashed),
+ options: options
)
+ } catch {
+ return false
}
+ }
+
+ /// Verifies an elliptic curve digital signature algorithm (ECDSA) signature on a digest over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - signature: The (non-recoverable) signature to check against the given digest.
+ /// - digest: The digest covered by the signature.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given digest.
+ public func isValidSignature(
+ _ signature: K1.ECDSA.NonRecoverable.Signature,
+ digest: some Digest,
+ options: K1.ECDSA.ValidationOptions = .default
+ ) -> Bool {
+ isValidSignature(
+ signature,
+ hashed: Data(digest),
+ options: options
+ )
+ }
- try self.init(rawRepresentation: Data(
- bytes: &signature.data,
- count: MemoryLayout.size(ofValue: signature.data)
- ))
+ /// Verifies an elliptic curve digital signature algorithm (ECDSA) signature on a block of data over the `secp256k1` elliptic curve.
+ ///
+ /// The function computes an SHA-256 hash from the data before verifying the signature. If you separately hash the data to be signed, use `isValidSignature(_:digest:input)` with the created digest. Or if you have access to a digest just as `some DataProtocol`, use
+ /// `isValidSignature(_:hashed:input)`.
+ ///
+ /// - Parameters:
+ /// - signature: The (non-recoverable) signature to check against the given digest.
+ /// - unhashed: The block of data covered by the signature.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given block of data.
+ public func isValidSignature(
+ _ signature: K1.ECDSA.NonRecoverable.Signature,
+ unhashed: some DataProtocol,
+ options: K1.ECDSA.ValidationOptions = .default
+ ) -> Bool {
+ isValidSignature(
+ signature,
+ digest: SHA256.hash(data: unhashed),
+ options: options
+ )
}
}
-extension ECDSASignatureNonRecoverable {
- internal static let byteCount = 2 * K1.Curve.Field.byteCount
- public typealias Scheme = ECDSA
- public static let scheme: SigningScheme = .ecdsa
+
+// MARK: Inits
+extension K1.ECDSA.NonRecoverable.Signature {
+
+ public init(compactRepresentation: some DataProtocol) throws {
+ try self.init(
+ wrapped: FFI.ECDSA.NonRecovery.from(compactBytes: [UInt8](compactRepresentation))
+ )
+ }
+
+ public init(derRepresentation: some DataProtocol) throws {
+ try self.init(
+ wrapped: FFI.ECDSA.NonRecovery.from(derRepresentation: [UInt8](derRepresentation))
+ )
+ }
}
+// MARK: ContiguousBytes
+extension K1.ECDSA.NonRecoverable.Signature {
+ public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ try wrapped.withUnsafeBytes(body)
+ }
+}
+
+// MARK: Serialize
+extension K1.ECDSA.NonRecoverable.Signature {
+
+ internal var rawRepresentation: Data {
+ Data(wrapped.bytes)
+ }
+
+ public func compactRepresentation() throws -> Data {
+ try FFI.ECDSA.NonRecovery.compact(wrapped)
+ }
+
+ public func derRepresentation() throws -> Data {
+ try FFI.ECDSA.NonRecovery.der(wrapped)
+ }
+}
+
+
+// MARK: Recover
+extension K1.ECDSA.NonRecoverable.Signature {
+ public func recoverPublicKey(
+ recoveryID: K1.ECDSA.Recoverable.Signature.RecoveryID,
+ message: some DataProtocol
+ ) throws -> K1.ECDSA.NonRecoverable.PublicKey {
+ let wrapped = try FFI.ECDSA.NonRecovery.recoverPublicKey(
+ self.wrapped,
+ recoveryID: recoveryID.recid,
+ message: [UInt8](message)
+ )
+ let impl = K1.PublicKeyImpl(wrapped: wrapped)
+ return K1.ECDSA.NonRecoverable.PublicKey(impl: impl)
+ }
+}
+
+extension K1.ECDSA.NonRecoverable.Signature {
+ internal static let byteCount = FFI.ECDSA.Recovery.byteCount
+}
+
+// MARK: Equatable
+extension K1.ECDSA.NonRecoverable.Signature {
+
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ lhs.wrapped.withUnsafeBytes { lhsBytes in
+ rhs.wrapped.withUnsafeBytes { rhsBytes in
+ safeCompare(lhsBytes, rhsBytes)
+ }
+ }
+ }
+}
+
+// MARK: Hashable
+extension K1.ECDSA.NonRecoverable.Signature {
+
+ public func hash(into hasher: inout Hasher) {
+ wrapped.withUnsafeBytes {
+ hasher.combine(bytes: $0)
+ }
+ }
+
+}
diff --git a/Sources/K1/K1/ECDSA/ECDSASignatureRecoverable.swift b/Sources/K1/K1/ECDSA/ECDSASignatureRecoverable.swift
index c120a60..aa1c45c 100644
--- a/Sources/K1/K1/ECDSA/ECDSASignatureRecoverable.swift
+++ b/Sources/K1/K1/ECDSA/ECDSASignatureRecoverable.swift
@@ -6,101 +6,343 @@
//
import Foundation
-import CryptoKit
-import secp256k1
-
-public struct ECDSASignatureRecoverable: Sendable, Hashable, ECSignature {
-
- public let rawRepresentation: Data
-
- public init(compactRepresentation: Data, recoveryID: Int32) throws {
- guard
- compactRepresentation.count == ECDSASignatureNonRecoverable.byteCount
- else {
- throw K1.Error.incorrectByteCountOfRawSignature
- }
- var recoverableSignature = secp256k1_ecdsa_recoverable_signature()
- let rs = [UInt8](compactRepresentation)
-
- try Bridge.call(ifFailThrow: .failedToParseRecoverableSignatureFromCompactRepresentation) { context in
- secp256k1_ecdsa_recoverable_signature_parse_compact(
- context,
- &recoverableSignature,
- rs,
- recoveryID
+import protocol CryptoKit.Digest
+import struct CryptoKit.SHA256
+
+extension K1.ECDSA {
+
+ /// A mechanism used to create or verify a cryptographic signature using the `secp256k1` elliptic curve digital signature algorithm (ECDSA), signatures that do offers recovery of the public key.
+ public enum Recoverable: K1Feature {
+
+ /// A `secp256k1` private key used to create cryptographic signatures,
+ /// more specifically ECDSA signatures that offers recovery of the public key.
+ public typealias PrivateKey = PrivateKeyOf
+
+ /// A `secp256k1` public key used to verify cryptographic signatures.
+ /// more specifically ECDSA signatures that offers recovery of the public key.
+ public typealias PublicKey = PublicKeyOf
+ }
+}
+
+// MARK: Sign
+extension K1.ECDSA.Recoverable.PrivateKey {
+
+ /// Generates an Elliptic Curve Digital Signature Algorithm (ECDSA) signature of _hashed_ data you provide over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - hashed: The _hashed_ data to sign.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: The signature corresponding to the data. The signing algorithm uses deterministic or random nonces, dependent on `options`, thus either deterministically producing the same signature or the same data and key, or different on every call.
+ public func signature(
+ for hashed: some DataProtocol,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> K1.ECDSA.Recoverable.Signature {
+ try K1.ECDSA.Recoverable.Signature(
+ wrapped: FFI.ECDSA.Recovery.sign(
+ hashedMessage: [UInt8](hashed),
+ privateKey: impl.wrapped,
+ options: options
+ )
+ )
+ }
+
+ /// Generates an Elliptic Curve Digital Signature Algorithm (ECDSA) signature of the digest you provide over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - digest: The digest of the data to sign.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: The signature corresponding to the data. The signing algorithm uses deterministic or random nonces, dependent on `options`, thus either deterministically producing the same signature or the same data and key, or different on every call.
+ public func signature(
+ for digest: some Digest,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> K1.ECDSA.Recoverable.Signature {
+ try signature(
+ for: Data(digest),
+ options: options
+ )
+ }
+
+ /// Generates an elliptic curve digital signature algorithm (ECDSA) signature of the given data over the `secp256k1` elliptic curve, using SHA-256 as a hash function.
+ /// - Parameters:
+ /// - unhashed: The data hash and then to sign.
+ /// - options: Whether or not to consider malleable signatures valid.
+ /// - Returns: The signature corresponding to the data. The signing algorithm uses deterministic or random nonces, dependent on `options`, thus either deterministically producing the same signature or the same data and key, or different on every call.
+ public func signature(
+ forUnhashed unhashed: some DataProtocol,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> K1.ECDSA.Recoverable.Signature {
+ try signature(
+ for: SHA256.hash(data: unhashed),
+ options: options
+ )
+ }
+
+}
+
+// MARK: Validate
+extension K1.ECDSA.Recoverable.PublicKey {
+
+ /// Verifies a recoverable ECDSA signature on some _hash_ over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - signature: The recoverable ECDSA signature to check against the given digest.
+ /// - hashed: The _hashed_ data covered by the signature.
+ /// - options: ECDSA validation options used during validation
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given _hashed_ data.
+ public func isValidSignature(
+ _ signature: K1.ECDSA.Recoverable.Signature,
+ hashed: some DataProtocol,
+ options: K1.ECDSA.ValidationOptions = .default
+ ) -> Bool {
+ do {
+ let publicKeyNonRecoverable = try K1.ECDSA.NonRecoverable.PublicKey(rawRepresentation: self.rawRepresentation)
+ let signatureNonRecoverable = try signature.nonRecoverable()
+
+ return publicKeyNonRecoverable.isValidSignature(
+ signatureNonRecoverable,
+ hashed: hashed,
+ options: options
)
+ } catch {
+ return false
}
- self.rawRepresentation = Data(
- bytes: &recoverableSignature.data,
- count: MemoryLayout.size(ofValue: recoverableSignature.data)
+ }
+
+ /// Verifies a recoverable ECDSA signature on a digest over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - signature: The recoverable ECDSA signature to check against the given digest.
+ /// - digest: The digest covered by the signature.
+ /// - options: ECDSA validation options used during validation
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given digest.
+ public func isValidSignature(
+ _ signature: K1.ECDSA.Recoverable.Signature,
+ digest: some Digest,
+ options: K1.ECDSA.ValidationOptions = .default
+ ) -> Bool {
+ isValidSignature(
+ signature,
+ hashed: Data(digest),
+ options: options
)
}
- public init(rawRepresentation: D) throws {
+ /// Verifies a recoverable ECDSA signature on a block of data over the `secp256k1` elliptic curve.
+ ///
+ /// The function computes an SHA-256 hash from the data before verifying the signature. If you separately hash the data to be signed, use `isValidSignature(_:digest:input)` with the created digest. Or if you have access to a digest just as `some DataProtocol`, use
+ /// `isValidSignature(_:hashed:input)`.
+ ///
+ /// - Parameters:
+ /// - signature: The recoverable ECDSA signature to check against the given digest.
+ /// - unhashed: The block of data covered by the signature.
+ /// - options: ECDSA validation options used during validation
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given block of data.
+ public func isValidSignature(
+ _ signature: K1.ECDSA.Recoverable.Signature,
+ unhashed: some DataProtocol,
+ options: K1.ECDSA.ValidationOptions = .default
+ ) -> Bool {
+ isValidSignature(
+ signature,
+ digest: SHA256.hash(data: unhashed),
+ options: options
+ )
+ }
+
+}
- guard
- rawRepresentation.count == ECDSASignatureNonRecoverable.byteCount + 1
- else {
- throw K1.Error.incorrectByteCountOfRawSignature
+extension K1.ECDSA.Recoverable {
+ public struct Signature: Sendable, Hashable, ContiguousBytes {
+
+ typealias Wrapped = FFI.ECDSA.Recovery.Wrapped
+ private let wrapped: Wrapped
+
+ internal init(wrapped: Wrapped) {
+ self.wrapped = wrapped
}
- self.rawRepresentation = Data(rawRepresentation)
+ }
+}
+
+
+// MARK: Init
+extension K1.ECDSA.Recoverable.Signature {
+
+ /// Compact aka `IEEE P1363` aka `R||S`.
+ public init(compact: Compact) throws {
+ try self.init(
+ wrapped: FFI.ECDSA.Recovery.deserialize(
+ compact: [UInt8](compact.compact),
+ recoveryID: compact.recoveryID.recid
+ )
+ )
}
+ /// Compact aka `IEEE P1363` aka `R||S`.
+ public init(compact: Data, recoveryID: RecoveryID) throws {
+ try self.init(compact: .init(compact: compact, recoveryID: recoveryID))
+ }
+
+ public init(
+ rawRepresentation: some DataProtocol
+ ) throws {
+ try self.init(
+ wrapped: FFI.ECDSA.Recovery.deserialize(rawRepresentation: rawRepresentation)
+ )
+ }
+}
+
+// MARK: ContiguousBytes
+extension K1.ECDSA.Recoverable.Signature {
+ public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ try wrapped.withUnsafeBytes(body)
+ }
}
-public extension ECDSASignatureRecoverable {
+// MARK: Serialize
+extension K1.ECDSA.Recoverable.Signature {
- typealias Scheme = ECDSA
- static let scheme: SigningScheme = .ecdsa
+ internal var rawRepresentation: Data {
+ Data(wrapped.bytes)
+ }
- func nonRecoverable() throws -> ECDSASignatureNonRecoverable {
- try Bridge.convertToNonRecoverable(ecdsaSignature: self)
+ /// Compact aka `IEEE P1363` aka `R||S` with `RecoveryID`
+ public func compact() throws -> Compact {
+ let (rs, recid) = try FFI.ECDSA.Recovery.serializeCompact(
+ wrapped
+ )
+ return try .init(
+ compact: Data(rs),
+ recoveryID: .init(recid: recid)
+ )
}
- func compact() throws -> (rs: Data, recoveryID: Int) {
- var rsBytes = [UInt8](repeating: 0, count: 64)
- var recoveryID: Int32 = 0
+ public struct Compact: Sendable, Hashable {
+
+ /// Compact aka `IEEE P1363` aka `R||S`.
+ public let compact: Data
- var recoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()
- withUnsafeMutableBytes(of: &recoverableBridgedToC.data) { pointer in
- pointer.copyBytes(
- from: rawRepresentation.prefix(pointer.count)
- )
+ public let recoveryID: RecoveryID
+
+ /// Compact aka `IEEE P1363` aka `R||S`.
+ public init(
+ compact: Data,
+ recoveryID: RecoveryID
+ ) throws {
+ guard compact.count == Self.byteCountRS else {
+ throw K1.Error.failedToDeserializeCompactRSRecoverableSignatureInvalidByteCount(
+ got: compact.count,
+ expected: Self.byteCountRS
+ )
+ }
+ self.compact = compact
+ self.recoveryID = recoveryID
}
- try Bridge.call(
- ifFailThrow: .failedSignatureToConvertRecoverableSignatureToCompact) { context in
- secp256k1_ecdsa_recoverable_signature_serialize_compact(
- context,
- &rsBytes,
- &recoveryID,
- &recoverableBridgedToC
+ }
+}
+
+extension K1.ECDSA.Recoverable.Signature.Compact {
+
+ public static let byteCountRS = 2 * Curve.Field.byteCount
+ public static let byteCount = Self.byteCountRS + 1
+
+ /// Takes either `R || S || V` data or `V || R || S` data, as per specification of `format`.
+ public init(
+ rawRepresentation: some DataProtocol,
+ format: SerializationFormat
+ ) throws {
+ guard rawRepresentation.count == Self.byteCount else {
+ throw K1.Error.failedToDeserializeCompactRecoverableSignatureInvalidByteCount(got: rawRepresentation.count, expected: Self.byteCount
+ )
+ }
+ switch format {
+ case .vrs:
+ try self.init(
+ compact: Data(rawRepresentation.suffix(Self.byteCountRS)),
+ recoveryID: .init(byte: rawRepresentation.first!) // force unwrap OK since we have checked length above.
+ )
+ case .rsv:
+ try self.init(
+ compact: Data(rawRepresentation.prefix(Self.byteCountRS)),
+ recoveryID: .init(byte: rawRepresentation.last!) // force unwrap OK since we have checked length above.
)
}
- return (rs: Data(rsBytes), recoveryID: Int(recoveryID))
}
-}
-
-public extension ECDSASignatureRecoverable {
- typealias ValidationMode = ECDSASignatureNonRecoverable.ValidationMode
- typealias SigningMode = ECDSASignatureNonRecoverable.SigningMode
-
- func wasSigned(by signer: K1.PublicKey, for digest: D, mode: ValidationMode) throws -> Bool where D : Digest {
- try nonRecoverable().wasSigned(by: signer, for: digest, mode: mode)
+ public enum SerializationFormat {
+
+ /// `R || S || V` - the format `libsecp256k1` v0.3.0 uses as internal representation
+ /// This is the default value of this library.
+ case rsv
+
+ /// We use `R || S || V` as default values since `libsecp256k1` v0.3.0 uses it as its internal representation.
+ public static let `default`: Self = .rsv
+
+ /// `V || R || S`.
+ case vrs
}
- func wasSigned(by signer: K1.PublicKey, hashedMessage: D, mode: ValidationMode) throws -> Bool where D : DataProtocol {
- try nonRecoverable().wasSigned(by: signer, hashedMessage: hashedMessage, mode: mode)
+ private var v: Data {
+ recoveryID.vData
+ }
+ private var rs: Data {
+ compact
}
- static func by(signing hashed: D, with privateKey: K1.PrivateKey, mode: SigningMode) throws -> Self where D : DataProtocol {
- try privateKey.ecdsaSignRecoverable(hashed: hashed, mode: mode)
+ func serialize(format: SerializationFormat) -> Data {
+ switch format {
+ case .rsv:
+ return rs + v
+ case .vrs:
+ return v + rs
+ }
}
-
- func wasSigned(by signer: K1.PublicKey, for digest: D) throws -> Bool where D : Digest {
- try nonRecoverable().wasSigned(by: signer, for: digest)
+}
+extension K1.ECDSA.Recoverable.Signature.RecoveryID {
+ var vData: Data {
+ Data(
+ [UInt8(rawValue)]
+ )
+ }
+}
+
+// MARK: Recovery
+extension K1.ECDSA.Recoverable.Signature {
+ public func recoverPublicKey(
+ message: some DataProtocol
+ ) throws -> K1.ECDSA.Recoverable.PublicKey {
+ let wrapped = try FFI.ECDSA.Recovery.recover(wrapped, message: [UInt8](message))
+ let impl = K1.PublicKeyImpl(wrapped: wrapped)
+ return K1.ECDSA.Recoverable.PublicKey(
+ impl: impl
+ )
+ }
+}
+
+
+// MARK: Conversion
+extension K1.ECDSA.Recoverable.Signature {
+ public func nonRecoverable() throws -> K1.ECDSA.NonRecoverable.Signature {
+ try K1.ECDSA.NonRecoverable.Signature(
+ wrapped: FFI.ECDSA.Recovery.nonRecoverable(self.wrapped)
+ )
+ }
+}
+
+// MARK: Equatable
+extension K1.ECDSA.Recoverable.Signature {
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ lhs.wrapped.withUnsafeBytes { lhsBytes in
+ rhs.wrapped.withUnsafeBytes { rhsBytes in
+ safeCompare(lhsBytes, rhsBytes)
+ }
+ }
}
+}
+
+// MARK: Hashable
+extension K1.ECDSA.Recoverable.Signature {
+ public func hash(into hasher: inout Hasher) {
+ wrapped.withUnsafeBytes {
+ hasher.combine(bytes: $0)
+ }
+ }
}
diff --git a/Sources/K1/K1/ECDSA/RecoveryID.swift b/Sources/K1/K1/ECDSA/RecoveryID.swift
new file mode 100644
index 0000000..00ff388
--- /dev/null
+++ b/Sources/K1/K1/ECDSA/RecoveryID.swift
@@ -0,0 +1,39 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+
+// MARK: Recovery
+extension K1.ECDSA.Recoverable.Signature {
+
+ public enum RecoveryID: UInt8, Sendable, Hashable, Codable {
+ case _0 = 0
+ case _1 = 1
+ case _2 = 2
+ case _3 = 3
+
+ internal var recid: Int32 {
+ Int32(rawValue)
+ }
+ }
+}
+
+extension K1.ECDSA.Recoverable.Signature.RecoveryID {
+ public init(byte: UInt8) throws {
+ guard let self_ = Self(rawValue: byte) else {
+ throw K1.Error.invalidRecoveryID(got: Int(byte))
+ }
+ self = self_
+ }
+
+ public init(recid: Int32) throws {
+ guard recid <= 3 && recid >= 0 else {
+ throw K1.Error.invalidRecoveryID(got: Int(recid))
+ }
+ try self.init(byte: UInt8(recid))
+ }
+}
diff --git a/Sources/K1/K1/ECDSA/Signature+Format+Conversion.swift b/Sources/K1/K1/ECDSA/Signature+Format+Conversion.swift
deleted file mode 100644
index c2151d9..0000000
--- a/Sources/K1/K1/ECDSA/Signature+Format+Conversion.swift
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2023-03-10.
-//
-
-// Bridge to C
-import secp256k1
-import Foundation
-
-extension Bridge {
- /// Initializes ECDSASignatureNonRecoverable from the DER representation.
- static func importECDSASignature(fromDER derRepresentation: D) throws -> Data {
- let derSignatureBytes = Array(derRepresentation)
- var signatureBridgedToC = secp256k1_ecdsa_signature()
-
- try Bridge.call(ifFailThrow: .failedToParseDERSignature) { context in
- secp256k1_ecdsa_signature_parse_der(
- context,
- &signatureBridgedToC,
- derSignatureBytes,
- derSignatureBytes.count
- )
- }
-
- let signatureData = Data(
- bytes: &signatureBridgedToC.data,
- count: MemoryLayout.size(ofValue: signatureBridgedToC.data)
- )
-
- return signatureData
- }
-
- static func compactRepresentationOfSignature(rawRepresentation: Data) throws -> Data {
-
- let compactSignatureLength = 64
- var signatureBridgedToC = secp256k1_ecdsa_signature()
- var compactSignature = [UInt8](repeating: 0, count: compactSignatureLength)
-
- withUnsafeMutableBytes(of: &signatureBridgedToC.data) { pointer in
- pointer.copyBytes(from: rawRepresentation.prefix(pointer.count))
- }
-
-
- try Bridge.call(ifFailThrow: .failedToSerializeCompactSignature) { context in
- secp256k1_ecdsa_signature_serialize_compact(
- context,
- &compactSignature,
- &signatureBridgedToC
- )
-
- }
-
- return Data(compactSignature)
- }
-
- static func derRepresentationOfSignature(rawRepresentation: Data) throws -> Data {
- var signatureBridgedToC = secp256k1_ecdsa_signature()
- var derMaxLength = 75 // in fact max is 73, but we can have some margin.
- var derSignature = [UInt8](repeating: 0, count: derMaxLength)
-
- withUnsafeMutableBytes(of: &signatureBridgedToC.data) { pointer in
- pointer.copyBytes(from: rawRepresentation.prefix(pointer.count))
- }
-
- try Bridge.call(ifFailThrow: .failedToSerializeDERSignature) { context in
- secp256k1_ecdsa_signature_serialize_der(
- context,
- &derSignature,
- &derMaxLength,
- &signatureBridgedToC
- )
- }
-
- return Data(bytes: &derSignature, count: derMaxLength)
- }
-}
diff --git a/Sources/K1/K1/K1/Context.swift b/Sources/K1/K1/K1/Context.swift
deleted file mode 100644
index 2d05bcf..0000000
--- a/Sources/K1/K1/K1/Context.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-
-internal extension K1 {
-
- /// Bridging type used for underlying libsecp256k1 methods:
- /// - `secp256k1_context_create`
- /// - `secp256k1_context_preallocated_size`
- /// - `secp256k1_context_preallocated_create`
- enum Context: UInt32 {
- case none, sign, verify
- }
-}
diff --git a/Sources/K1/K1/K1/Error.swift b/Sources/K1/K1/K1/Error.swift
deleted file mode 100644
index 34a8cc7..0000000
--- a/Sources/K1/K1/K1/Error.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import Foundation
-
-public extension K1 {
-
- enum Error: Swift.Error, Equatable {
-
- /// The private key scalar was either 0 or larger then the order of the
- /// curve.
- case invalidPrivateKeyMustNotBeZero
- case invalidPrivateKeyMustBeSmallerThanOrder
-
- case incorrectByteCountOfPrivateKey(got: Int, expected: Int)
-
- case incorrectByteCountOfRawSignature
- case incorrectByteCountOfSignatureNotRecoverable
-
- case failedToCreateContextForSecp256k1
- case failedToUpdateContextRandomization
- case failedToComputePublicKeyFromPrivateKey
- case incorrectByteCountOfMessageToValidate
- case failedToCompressPublicKey
- case failedToUncompressPublicKey
- case failedToProduceSharedSecret
- case incorrectByteCountOfPublicKey(got: Int, acceptableLengths: [Int])
- case failedToParsePublicKeyFromBytes
- case failedToParseDERSignature
- case failedToSerializeCompactSignature
- case failedToSerializeDERSignature
- case failedToECDSASignDigest
- case recoverPublicKeyDiscrepancyReceivedSignatureContainingRecoveryIDButFunctionSpecifiesANonMatchingOne
- case failedToParseRecoverableSignatureFromECDSASignature
- case failedToParseRecoverableSignatureFromCompactRepresentation
- case failedToParseSignatureFromCompactRepresentation
- case failedSignatureToConvertRecoverableSignatureToCompact
- case failedToConvertRecoverableSignatureToNonRecoverable
- case failedToRecoverPublicKeyFromSignature
- case failedToNormalizeECDSASignature
- case failedToSchnorrSignMessageInvalidLength
- case failedToInitializeKeyPairForSchnorrSigning
- case failedToSchnorrSignDigest
- case failedToSchnorrSignDigestProvidedRandomnessInvalidLength
- case failedToSchnorrSignErrorGettingPubKeyFromKeyPair
- case failedToSchnorrVerifyGettingXFromPubKey
- case failedToSchnorrSignDigestDidNotPassVerification
- case failedToPerformDiffieHellmanKeyExchange
- case incorrectByteCountOfAuxilaryDataForSchnorr
- case incorrectByteCountOfMessageToECDSASign
- case incorrectByteCountOfArbitraryDataForNonceFunction
- case failedToSerializePublicKeyIntoBytes
- case expectedPublicKeyToBeValidForSignatureAndMessage
- case failedToRecognizeSignatureType(onlySupportedSchemesAre: [SigningScheme])
- }
-
-}
-
-extension K1.Error {
- static func invalidSizeOfPrivateKey(providedByteCount: Int) -> Self {
- .incorrectByteCountOfPrivateKey(got: providedByteCount, expected: K1.Curve.Field.byteCount)
- }
-
- static func incorrectByteCountOfPublicKey(providedByteCount: Int) -> Self {
- .incorrectByteCountOfPublicKey(got: providedByteCount, acceptableLengths: K1.Format.allCases.map(\.length))
- }
-}
diff --git a/Sources/K1/K1/K1/Format.swift b/Sources/K1/K1/K1/Format.swift
deleted file mode 100644
index 3655c6f..0000000
--- a/Sources/K1/K1/K1/Format.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-
-public extension K1 {
-
- /// Bridging type for: `secp256k1_ec_pubkey_serialize`
- enum Format: UInt32, CaseIterable {
- case compressed, uncompressed
- }
-}
-
-public extension K1.Format {
-
- var length: Int {
- switch self {
- case .compressed: return 33
- case .uncompressed: return 65
- }
- }
-
- init(byteCount: Int) throws {
- if byteCount == Self.uncompressed.length {
- self = .uncompressed
- } else if byteCount == Self.compressed.length {
- self = .compressed
- } else {
- throw K1.Error.incorrectByteCountOfPublicKey(providedByteCount: byteCount)
- }
- }
-}
diff --git a/Sources/K1/K1/K1/K1.swift b/Sources/K1/K1/K1/K1.swift
deleted file mode 100644
index d0bba9d..0000000
--- a/Sources/K1/K1/K1/K1.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import Foundation
-
-// MARK: - `K1` (secp256k1)
-// MARK: -
-
-/// The Elliptic Curve `secp256k1`
-public enum K1 {}
-
-// MARK: - Curve
-// MARK: -
-internal extension K1 {
-
- /// Details about the elliptic curve `secp256k1`.
- enum Curve {}
-}
-
-// MARK: - FiniteField
-// MARK: -
-internal extension K1.Curve {
- /// The finite field of the secp256k1 curve.
- enum Field {}
-}
-
-internal extension K1.Curve.Field {
- /// Finite field members are 256 bits large, i.e. 32 bytes.
- static let byteCount = 32
-}
diff --git a/Sources/K1/K1/Keys/PrivateKey/PrivateKey.swift b/Sources/K1/K1/Keys/PrivateKey/PrivateKey.swift
new file mode 100644
index 0000000..da6c070
--- /dev/null
+++ b/Sources/K1/K1/Keys/PrivateKey/PrivateKey.swift
@@ -0,0 +1,181 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2022-01-27.
+//
+
+import CryptoKit
+import Foundation
+
+public protocol K1KeyExportable {
+ var rawRepresentation: Data { get }
+ var x963Representation: Data { get }
+ var derRepresentation: Data { get }
+ var pemRepresentation: String { get }
+}
+
+public protocol K1KeyImportable {
+ init(rawRepresentation: some ContiguousBytes) throws
+ init(x963Representation: some ContiguousBytes) throws
+ init(derRepresentation: some RandomAccessCollection) throws
+ init(pemRepresentation: String) throws
+}
+
+public typealias K1KeyPortable = K1KeyImportable & K1KeyExportable
+
+public protocol K1PrivateKeyProtocol: K1KeyPortable {
+ associatedtype PublicKey: K1PublicKeyProtocol
+ var publicKey: PublicKey { get }
+ init()
+}
+
+// MARK: - PrivateKey
+extension K1 {
+
+ struct PrivateKeyImpl: Sendable, Hashable, K1PrivateKeyProtocol {
+
+ typealias Wrapped = FFI.PrivateKey.Wrapped
+ internal let wrapped: Wrapped
+
+ let publicKey: PublicKeyImpl
+
+ internal init(wrapped: Wrapped) {
+ self.wrapped = wrapped
+ self.publicKey = PublicKey(wrapped: wrapped.publicKey)
+ }
+ }
+}
+
+// MARK: Inits
+extension K1.PrivateKeyImpl {
+
+ /// Creates a `secp256k1` private key from a Privacy-Enhanced Mail (PEM) representation.
+ init(
+ pemRepresentation: String
+ ) throws {
+ let pem = try ASN1.PEMDocument(pemString: pemRepresentation)
+
+ switch pem.type {
+ case "EC \(Self.pemType)":
+ let parsed = try ASN1.SEC1PrivateKey(asn1Encoded: Array(pem.derBytes))
+ self = try .init(rawRepresentation: parsed.privateKey)
+ case Self.pemType:
+ let parsed = try ASN1.PKCS8PrivateKey(asn1Encoded: Array(pem.derBytes))
+ self = try .init(rawRepresentation: parsed.privateKey.privateKey)
+ default:
+ throw K1.Error.invalidPEMDocument
+ }
+ }
+
+ init(
+ rawRepresentation: some ContiguousBytes
+ ) throws {
+ try self.init(
+ wrapped: FFI.PrivateKey.deserialize(rawRepresentation: rawRepresentation)
+ )
+ }
+
+ init(
+ x963Representation: some ContiguousBytes
+ ) throws {
+ let length = x963Representation.withUnsafeBytes { $0.count }
+ guard length == Self.x963ByteCount else {
+ throw K1.Error.incorrectByteCountOfX963PrivateKey(got: length, expected: Self.x963ByteCount)
+ }
+
+ let publicKeyX963 = x963Representation.bytes.prefix(K1.PublicKeyImpl.x963ByteCount)
+ let publicKeyFromX963 = try K1.PublicKeyImpl.init(x963Representation: publicKeyX963)
+ let privateKeyRaw = x963Representation.bytes.suffix(Self.rawByteCount)
+ try self.init(rawRepresentation: privateKeyRaw)
+ guard self.publicKey == publicKeyFromX963 else {
+ throw K1.Error.invalidPrivateX963RepresentationPublicKeyDiscrepancy
+ }
+ // All good
+ }
+
+
+ /// `DER`
+ init(derRepresentation: some RandomAccessCollection) throws {
+ let bytes = Array(derRepresentation)
+
+ // We have to try to parse this twice because we have no informaton about what kind of key this is.
+ // We try with PKCS#8 first, and then fall back to SEC.1.
+ do {
+ let key = try ASN1.PKCS8PrivateKey(asn1Encoded: bytes)
+ self = try .init(rawRepresentation: key.privateKey.privateKey)
+ } catch {
+ let key = try ASN1.SEC1PrivateKey(asn1Encoded: bytes)
+ self = try .init(rawRepresentation: key.privateKey)
+ }
+ }
+
+ init() {
+ self.init(wrapped: .init())
+ }
+}
+
+// MARK: Serialize
+extension K1.PrivateKeyImpl {
+
+ /// A raw representation of the private key.
+ var rawRepresentation: Data {
+ Data(wrapped.secureBytes.bytes)
+ }
+
+ /// A Distinguished Encoding Rules (DER) encoded representation of the private key.
+ var derRepresentation: Data {
+ let pkey = ASN1.PKCS8PrivateKey(
+ algorithm: .secp256k1,
+ privateKey: Array(self.rawRepresentation),
+ publicKey: Array(self.publicKey.x963Representation)
+ )
+ var serializer = ASN1.Serializer()
+
+ // Serializing these keys can't throw
+ try! serializer.serialize(pkey)
+ return Data(serializer.serializedBytes)
+ }
+
+ /// A Privacy-Enhanced Mail (PEM) representation of the private key.
+ var pemRepresentation: String {
+ let pemDocument = ASN1.PEMDocument(type: Self.pemType, derBytes: self.derRepresentation)
+ return pemDocument.pemString
+ }
+
+ /// An ANSI x9.63 representation of the private key.
+ var x963Representation: Data {
+ // The x9.63 private key format is a discriminator byte (0x4) concatenated with the X and Y points
+ // of the key, and the K value of the secret scalar. Let's load that in.
+ var bytes = Data()
+ bytes.reserveCapacity(Self.x963ByteCount)
+ bytes.append(contentsOf: publicKey.x963Representation)
+ bytes.append(self.rawRepresentation)
+ return bytes
+ }
+
+
+ static let rawByteCount = Curve.Field.byteCount
+ static let x963ByteCount = K1.PublicKeyImpl.x963ByteCount + K1.PrivateKeyImpl.rawByteCount
+}
+
+extension K1.PrivateKeyImpl {
+ static let pemType = "PRIVATE KEY"
+}
+
+
+// MARK: - Equatable
+extension K1.PrivateKeyImpl {
+ /// Constant-time comparision.
+ static func == (lhs: Self, rhs: Self) -> Bool {
+ lhs.wrapped.secureBytes == rhs.wrapped.secureBytes
+ }
+}
+
+// MARK: - Hashable
+extension K1.PrivateKeyImpl {
+ /// We use the key of the private key as input to hash
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(self.publicKey)
+ }
+}
diff --git a/Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey+Bridge+To+C.swift b/Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey+Bridge+To+C.swift
deleted file mode 100644
index 7bbfa02..0000000
--- a/Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey+Bridge+To+C.swift
+++ /dev/null
@@ -1,639 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import secp256k1
-import CryptoKit
-import Foundation
-
-extension Bridge {
-
- /// Produces a **recoverable** ECDSA signature.
- static func ecdsaSignRecoverable(
- message: [UInt8],
- privateKey: SecureBytes,
- mode: ECDSASignatureNonRecoverable.SigningMode
- ) throws -> Data {
-
- guard message.count == K1.Curve.Field.byteCount else {
- throw K1.Error.incorrectByteCountOfMessageToECDSASign
- }
-
- if let nonceFunctionArbitraryData = mode.nonceFunctionArbitraryData {
- guard nonceFunctionArbitraryData.count == 32 else {
- throw K1.Error.incorrectByteCountOfArbitraryDataForNonceFunction
- }
- }
-
- var nonceFunctionArbitraryBytes: [UInt8]? = nil
- if let nonceFunctionArbitraryData = mode.nonceFunctionArbitraryData {
- guard nonceFunctionArbitraryData.count == K1.Curve.Field.byteCount else {
- throw K1.Error.incorrectByteCountOfArbitraryDataForNonceFunction
- }
- nonceFunctionArbitraryBytes = [UInt8](nonceFunctionArbitraryData)
- }
-
- var signatureRecoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()
-
- try Self.call(
- ifFailThrow: .failedToECDSASignDigest
- ) { context in
- secp256k1_ecdsa_sign_recoverable(
- context,
- &signatureRecoverableBridgedToC,
- message,
- privateKey.backing.bytes,
- secp256k1_nonce_function_rfc6979,
- nonceFunctionArbitraryBytes
- )
- }
-
- return Data(
- bytes: &signatureRecoverableBridgedToC.data,
- count: MemoryLayout.size(ofValue: signatureRecoverableBridgedToC.data)
- )
- }
-
- /// Produces a **non recoverable** ECDSA signature.
- static func ecdsaSignNonRecoverable(
- message: [UInt8],
- privateKey: SecureBytes,
- mode: ECDSASignatureNonRecoverable.SigningMode
- ) throws -> Data {
-
- guard message.count == K1.Curve.Field.byteCount else {
- throw K1.Error.incorrectByteCountOfMessageToECDSASign
- }
-
- if let nonceFunctionArbitraryData = mode.nonceFunctionArbitraryData {
- guard nonceFunctionArbitraryData.count == 32 else {
- throw K1.Error.incorrectByteCountOfArbitraryDataForNonceFunction
- }
- }
-
- var nonceFunctionArbitraryBytes: [UInt8]? = nil
- if let nonceFunctionArbitraryData = mode.nonceFunctionArbitraryData {
- guard nonceFunctionArbitraryData.count == K1.Curve.Field.byteCount else {
- throw K1.Error.incorrectByteCountOfArbitraryDataForNonceFunction
- }
- nonceFunctionArbitraryBytes = [UInt8](nonceFunctionArbitraryData)
- }
-
- var signatureBridgedToC = secp256k1_ecdsa_signature()
-
- try Self.call(
- ifFailThrow: .failedToECDSASignDigest
- ) { context in
- secp256k1_ecdsa_sign(
- context,
- &signatureBridgedToC,
- message,
- privateKey.backing.bytes,
- secp256k1_nonce_function_rfc6979,
- nonceFunctionArbitraryBytes
- )
- }
-
- return Data(
- bytes: &signatureBridgedToC.data,
- count: MemoryLayout.size(ofValue: signatureBridgedToC.data)
- )
- }
-
- static func schnorrSign(
- message: [UInt8],
- privateKey: SecureBytes,
- input: SchnorrInput?
- ) throws -> Data {
- guard message.count == K1.Curve.Field.byteCount else {
- throw K1.Error.failedToSchnorrSignMessageInvalidLength
- }
- var signatureOut = [UInt8](repeating: 0, count: 64)
-
- var keyPair = secp256k1_keypair()
-
- try Self.call(
- ifFailThrow: .failedToInitializeKeyPairForSchnorrSigning
- ) { context in
- secp256k1_keypair_create(context, &keyPair, privateKey.backing.bytes)
- }
-
- var auxilaryRandomBytes: [UInt8]? = nil
- if let auxilaryRandomData = input?.auxilaryRandomData {
- guard auxilaryRandomData.count == K1.Curve.Field.byteCount else {
- throw K1.Error.failedToSchnorrSignDigestProvidedRandomnessInvalidLength
- }
- auxilaryRandomBytes = [UInt8](auxilaryRandomData)
- }
-
- try Self.call(
- ifFailThrow: .failedToSchnorrSignDigest
- ) { context in
- secp256k1_schnorrsig_sign32(
- context,
- &signatureOut,
- message,
- &keyPair,
- auxilaryRandomBytes
- )
- }
-
- var publicKey = secp256k1_xonly_pubkey()
-
- try Self.call(
- ifFailThrow: .failedToSchnorrSignErrorGettingPubKeyFromKeyPair
- ) { context in
- secp256k1_keypair_xonly_pub(context, &publicKey, nil, &keyPair)
- }
-
- try Self.call(
- ifFailThrow: .failedToSchnorrSignDigestDidNotPassVerification
- ) { context in
- secp256k1_schnorrsig_verify(context, &signatureOut, message, message.count, &publicKey)
- }
-
- return Data(signatureOut)
- }
-
- enum ECDHSerializeFunction {
-
- /// Using the `libsecp256k1` default behaviour, which is to SHA256 hash the compressed public key
- case libsecp256kDefault
-
- /// Following the [ANSI X9.63][ansix963] standard
- ///
- /// [ansix963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017
- case ansiX963
-
- /// Following no standard at all, does not hash the shared public point, and returns it in full.
- case noHashWholePoint
-
- func hashfp() -> (Optional<@convention(c) (Optional>, Optional>, Optional>, Optional) -> Int32>) {
- switch self {
- case .libsecp256kDefault: return secp256k1_ecdh_hash_function_default
- case .ansiX963: return ecdh_skip_hash_extract_only_x
- case .noHashWholePoint: return ecdh_skip_hash_extract_x_and_y
- }
- }
-
- var outputByteCount: Int {
- switch self {
- case .libsecp256kDefault: return K1.Curve.Field.byteCount
- case .ansiX963: return K1.Curve.Field.byteCount
- case .noHashWholePoint: return K1.Format.uncompressed.length
- }
- }
- }
-
- static func ecdh(
- publicKey publicKeyBytes: [UInt8],
- privateKey: SecureBytes,
- hashFp: ECDHSerializeFunction
- ) throws -> Data {
-
- var publicKeyBridgedToC = secp256k1_pubkey()
-
- try Self.call(ifFailThrow: .incorrectByteCountOfPublicKey(providedByteCount: publicKeyBytes.count)) { context in
- /* Parse a variable-length public key into the pubkey object. */
- secp256k1_ec_pubkey_parse(
- context,
- &publicKeyBridgedToC,
- publicKeyBytes,
- publicKeyBytes.count
- )
- }
-
- var sharedPublicPointBytes = [UInt8](
- repeating: 0,
- count: hashFp.outputByteCount
- )
-
- try Self.call(
- ifFailThrow: .failedToPerformDiffieHellmanKeyExchange
- ) { context in
- /** Compute an EC Diffie-Hellman secret in constant time
- */
- secp256k1_ecdh(
- context,
- &sharedPublicPointBytes, // output
- &publicKeyBridgedToC, // pubkey
- privateKey.backing.bytes, // seckey
- hashFp.hashfp(), // hashfp
- nil // arbitrary data pointer that is passed through to hashfp
- )
- }
- return Data(sharedPublicPointBytes)
- }
-
- static func convertToNonRecoverable(
- ecdsaSignature: ECDSASignatureRecoverable
- ) throws -> ECDSASignatureNonRecoverable {
- var recoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()
-
- withUnsafeMutableBytes(of: &recoverableBridgedToC.data) { pointer in
- pointer.copyBytes(
- from: ecdsaSignature.rawRepresentation.prefix(pointer.count)
- )
- }
-
- var nonRecoverableBridgedToC = secp256k1_ecdsa_signature()
-
- try Self.call(
- ifFailThrow: .failedToConvertRecoverableSignatureToNonRecoverable
- ) { context in
- secp256k1_ecdsa_recoverable_signature_convert(
- context,
- &nonRecoverableBridgedToC,
- &recoverableBridgedToC
- )
- }
-
- let signatureData = Data(
- bytes: &nonRecoverableBridgedToC.data,
- count: MemoryLayout.size(ofValue: nonRecoverableBridgedToC.data)
- )
-
- return try ECDSASignatureNonRecoverable(rawRepresentation: signatureData)
- }
-
- /// Recover an ECDSA public key from a non recoverable signature using recovery ID
- static func recoverPublicKey(
- ecdsaSignature: ECDSASignatureNonRecoverable,
- recoveryID: Int32,
- message: [UInt8]
- ) throws -> [UInt8] {
- try _recoverPublicKey(
- rs: ecdsaSignature.compactRepresentation(),
- recoveryID: recoveryID,
- message: message
- )
- }
-
- /// Recover an ECDSA public key from a signature.
- static func recoverPublicKey(
- ecdsaSignature: ECDSASignatureRecoverable,
- message: [UInt8]
- ) throws -> [UInt8] {
- var recoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()
-
- withUnsafeMutableBytes(of: &recoverableBridgedToC.data) { pointer in
- pointer.copyBytes(
- from: ecdsaSignature.rawRepresentation.prefix(pointer.count)
- )
- }
-
- return try __recoverPubKeyFrom(
- signatureBridgedToC: recoverableBridgedToC,
- message: message
- )
- }
-
- /// Recover an ECDSA public key from a signature.
- static func _recoverPublicKey(
- rs rsData: Data,
- recoveryID: Int32,
- message: [UInt8]
- ) throws -> [UInt8] {
- var signatureBridgedToC = secp256k1_ecdsa_recoverable_signature()
- let rs = [UInt8](rsData)
-
- try Self.call(
- ifFailThrow: .failedToParseRecoverableSignatureFromECDSASignature
- ) { context in
- secp256k1_ecdsa_recoverable_signature_parse_compact(
- context,
- &signatureBridgedToC,
- rs,
- recoveryID
- )
- }
-
- return try __recoverPubKeyFrom(
- signatureBridgedToC: signatureBridgedToC,
- message: message
- )
- }
-
- static func __recoverPubKeyFrom(
- signatureBridgedToC: secp256k1_ecdsa_recoverable_signature,
- message: [UInt8]
- ) throws -> [UInt8] {
- var signatureBridgedToC = signatureBridgedToC
- var publicKeyBridgedToC = secp256k1_pubkey()
- try Self.call(
- ifFailThrow: .failedToRecoverPublicKeyFromSignature
- ) { context in
- secp256k1_ecdsa_recover(
- context,
- &publicKeyBridgedToC,
- &signatureBridgedToC,
- message
- )
- }
- let publicKeyFormat = K1.Format.uncompressed
- var publicPointBytes = [UInt8](
- repeating: 0,
- count: publicKeyFormat.length
- )
- var pubkeyBytesSerializedCount = publicKeyFormat.length
- try publicPointBytes.withUnsafeMutableBytes { pubkeyBytes in
- try Self.call(
- ifFailThrow: .failedToSerializePublicKeyIntoBytes
- ) { context in
- secp256k1_ec_pubkey_serialize(
- context,
- pubkeyBytes.baseAddress!,
- &pubkeyBytesSerializedCount,
- &publicKeyBridgedToC,
- publicKeyFormat.rawValue
- )
- }
- }
- guard
- pubkeyBytesSerializedCount == K1.Format.uncompressed.length,
- publicPointBytes.count == K1.Format.uncompressed.length
- else {
- throw K1.Error.failedToSerializePublicKeyIntoBytes
- }
-
- return publicPointBytes
- }
-}
-
-
-public struct SchnorrInput {
- public let auxilaryRandomData: Data
-}
-
-public extension ECDSASignatureRecoverable {
-
- /// `recoverID` is optional since `self` can contain the recoveryID already.
- func recoverPublicKey(
- messageThatWasSigned: D
- ) throws -> K1.PublicKey {
- let uncompressedPublicKeyBytes = try Bridge.recoverPublicKey(
- ecdsaSignature: self,
- message: [UInt8](messageThatWasSigned)
- )
- let publicKey = try K1.PublicKey(
- wrapped: .init(uncompressedRaw: uncompressedPublicKeyBytes)
- )
-
- guard try publicKey.isValid(signature: self.nonRecoverable(), hashed: messageThatWasSigned) else {
- throw K1.Error.expectedPublicKeyToBeValidForSignatureAndMessage
- }
-
- return publicKey
- }
-}
-
-public extension ECDSASignatureNonRecoverable {
-
- /// `recoverID` is optional since `self` can contain the recoveryID already.
- func recoverPublicKey(
- recoveryID: Int,
- messageThatWasSigned: D
- ) throws -> K1.PublicKey {
- let uncompressedPublicKeyBytes = try Bridge.recoverPublicKey(
- ecdsaSignature: self,
- recoveryID: Int32(recoveryID),
- message: [UInt8](messageThatWasSigned)
- )
- let publicKey = try K1.PublicKey(
- wrapped: .init(uncompressedRaw: uncompressedPublicKeyBytes)
- )
-
- guard try publicKey.isValid(signature: self, hashed: messageThatWasSigned) else {
- throw K1.Error.expectedPublicKeyToBeValidForSignatureAndMessage
- }
-
- return publicKey
- }
-}
-
-public extension K1.PrivateKey {
-
- /// Produces a **recoverable** ECDSA signature.
- func ecdsaSignRecoverable(
- hashed message: D,
- mode: ECDSASignatureNonRecoverable.SigningMode = .default
- ) throws -> ECDSASignatureRecoverable {
- let messageBytes = [UInt8](message)
- let raw = try withSecureBytes {
- try Bridge.ecdsaSignRecoverable(message: messageBytes, privateKey: $0, mode: mode)
- }
-
- return try ECDSASignatureRecoverable.init(rawRepresentation: raw)
- }
-
- /// Produces a **non recoverable** ECDSA signature.
- func ecdsaSignNonRecoverable(
- hashed message: D,
- mode: ECDSASignatureNonRecoverable.SigningMode = .default
- ) throws -> ECDSASignatureNonRecoverable {
- let messageBytes = [UInt8](message)
- let signatureData = try withSecureBytes { (secureBytes: SecureBytes) -> Data in
- try Bridge.ecdsaSignNonRecoverable(message: messageBytes, privateKey: secureBytes, mode: mode)
- }
-
- return try ECDSASignatureNonRecoverable(
- rawRepresentation: signatureData
- )
- }
-
- func schnorrSign(
- hashed: D,
- input maybeInput: SchnorrInput? = nil
- ) throws -> SchnorrSignature {
- let message = [UInt8](hashed)
- let signatureData = try withSecureBytes { (secureBytes: SecureBytes) -> Data in
- try Bridge.schnorrSign(message: message, privateKey: secureBytes, input: maybeInput)
- }
-
- return try SchnorrSignature(
- rawRepresentation: signatureData
- )
- }
-
- func ecdsaSignNonRecoverable(
- digest: D,
- mode: ECDSASignatureNonRecoverable.SigningMode = .default
- ) throws -> ECDSASignatureNonRecoverable {
- try ecdsaSignNonRecoverable(hashed: Array(digest), mode: mode)
- }
-
- func ecdsaSignNonRecoverable(
- unhashed data: D,
- mode: ECDSASignatureNonRecoverable.SigningMode = .default
- ) throws -> ECDSASignatureNonRecoverable {
- try ecdsaSignNonRecoverable(digest: SHA256.hash(data: data), mode: mode)
- }
-
- func ecdsaSignRecoverable(
- digest: D,
- mode: ECDSASignatureNonRecoverable.SigningMode = .default
- ) throws -> ECDSASignatureRecoverable {
- try ecdsaSignRecoverable(hashed: Array(digest), mode: mode)
- }
-
- func ecdsaSignRecoverable(
- unhashed data: D,
- mode: ECDSASignatureNonRecoverable.SigningMode = .default
- ) throws -> ECDSASignatureRecoverable {
- try ecdsaSignRecoverable(digest: SHA256.hash(data: data), mode: mode)
- }
-
-
- func schnorrSign(
- digest: D,
- input maybeInput: SchnorrInput? = nil
- ) throws -> SchnorrSignature {
- try schnorrSign(hashed: Array(digest), input: maybeInput)
- }
-
- func schnorrSign(
- unhashed data: D,
- input maybeInput: SchnorrInput? = nil
- ) throws -> SchnorrSignature {
- try schnorrSign(digest: SHA256.hash(data: data), input: maybeInput)
- }
-
-
- func sign(
- hashed: D,
- scheme: S.Type,
- mode: S.Signature.SigningMode
- ) throws -> S.Signature {
- try S.Signature.by(signing: hashed, with: self, mode: mode)
- }
-
- func sign(
- digest: S.HashDigest,
- scheme: S.Type,
- mode: S.Signature.SigningMode
- ) throws -> S.Signature {
- try S.Signature.by(signing: Array(digest), with: self, mode: mode)
- }
-
- func sign(
- unhashed: D,
- scheme: S.Type,
- mode: S.Signature.SigningMode
- ) throws -> S.Signature {
- try sign(
- hashed: Data(S.hash(unhashed: unhashed)),
- scheme: scheme,
- mode: mode
- )
- }
-}
-
-/// MARK: ECDH
-extension K1.PrivateKey {
-
- private func _ecdh(
- publicKey: K1.PublicKey,
- serializeOutputFunction hashFp: Bridge.ECDHSerializeFunction
- ) throws -> Data {
- let sharedSecretData = try withSecureBytes { secureBytes in
- try Bridge.ecdh(
- publicKey: publicKey.uncompressedRaw,
- privateKey: secureBytes,
- hashFp: hashFp
- )
- }
- return sharedSecretData
- }
-
- /// Computes a shared secret with the provided public key from another party,
- /// returning only the `X` coordinate of the point, following [ANSI X9.63][ansix963] standards.
- ///
- /// This is one of three ECDH functions, this library vendors, all three versions
- /// uses different serialization of the shared EC Point, specifically:
- /// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point <- this function
- /// 2. `libsecp256k1`: SHA-256 hash the compressed point
- /// 3. Custom: No hash, return point uncompressed
- ///
- /// This function uses 3. i.e. no hash, and returns only the `X` coordinate of the point.
- /// This is following the [ANSI X9.63][ansix963] standard serialization of the shared point.
- ///
- /// Further more this function is compatible with CryptoKit, since it returns a CryptoKit
- /// `SharedSecret` struct, thus offering you to use all of CryptoKit's Key Derivation Functions
- /// (`KDF`s), which can be called on the `SharedSecret`.
- ///
- /// As seen on [StackExchange][cryptostackexchange], this version is compatible with the following
- /// libraries:
- /// - JS: `elliptic` (v6.4.0 in nodeJS v8.2.1)
- /// - JS: `crypto` (builtin) - uses openssl under the hood (in nodeJS v8.2.1)
- /// - .NET: `BouncyCastle` (BC v1.8.1.3, .NET v2.1.4)
- /// - Python: pyca/cryptography (hazmat)
- ///
- /// [ansix963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017
- /// [cryptostackexchange]: https://crypto.stackexchange.com/a/57727
- public func sharedSecretFromKeyAgreement(
- with publicKeyShare: K1.PublicKey
- ) throws -> SharedSecret {
- let sharedSecretData = try _ecdh(publicKey: publicKeyShare, serializeOutputFunction: .ansiX963)
- let __sharedSecret = __SharedSecret(ss: .init(bytes: sharedSecretData))
- let sharedSecret = unsafeBitCast(__sharedSecret, to: SharedSecret.self)
- guard sharedSecret.withUnsafeBytes({ Data($0).count == sharedSecretData.count }) else {
- throw K1.Error.failedToProduceSharedSecret
- }
- return sharedSecret
- }
-
- /// Computes a shared secret with the provided public key from another party,
- /// using `libsecp256k1` default behaviour, returning a hashed of the compressed point.
- ///
- /// This is one of three ECDH functions, this library vendors, all three versions
- /// uses different serialization of the shared EC Point, specifically:
- /// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point
- /// 2. `libsecp256k1`: SHA-256 hash the compressed point <- this function
- /// 3. Custom: No hash, return point uncompressed
- ///
- /// This function uses 1. i.e.SHA-256 hash the compressed point.
- /// This is using the [default behaviour of `libsecp256k1`][libsecp256k1], which does not adhere to any
- /// other standard.
- ///
- /// As seen on [StackExchange][cryptostackexchange], this version is compatible with all
- /// libraries which wraps `libsecp256k1`, e.g.:
- /// - Python wrapper: secp256k1 (v0.13.2, for python 3.6.4)
- /// - JS wrapper: secp256k1 (v3.5.0, for nodeJS v8.2.1)
- ///
- /// [libsecp256k1]: https://github.com/bitcoin-core/secp256k1/blob/master/src/modules/ecdh/main_impl.h#L27
- /// [cryptostackexchange]: https://crypto.stackexchange.com/a/57727
- ///
- public func ecdh(with publicKey: K1.PublicKey) throws -> Data {
- try _ecdh(publicKey: publicKey, serializeOutputFunction: .libsecp256kDefault)
- }
-
-
- /// Computes a shared secret with the provided public key from another party,
- /// returning an uncompressed public point, unhashed.
- ///
- /// This is one of three ECDH functions, this library vendors, all three versions
- /// uses different serialization of the shared EC Point, specifically:
- /// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point
- /// 2. `libsecp256k1`: SHA-256 hash the compressed point
- /// 3. Custom: No hash, return point uncompressed <- this function
- ///
- /// This function uses 2. i.e. no hash, return point uncompressed
- /// **This is not following any standard at all**, but might be useful if you want to write your
- /// cryptographic functions, e.g. some ECIES scheme.
- ///
- public func ecdhPoint(with publicKey: K1.PublicKey) throws -> Data {
- try _ecdh(publicKey: publicKey, serializeOutputFunction: .noHashWholePoint)
- }
-
-}
-
-// MUST match https://github.com/apple/swift-crypto/blob/main/Sources/Crypto/Key%20Agreement/DH.swift#L34
-
-/// A Key Agreement Result
-/// A SharedSecret has to go through a Key Derivation Function before being able to use by a symmetric key operation.
-public struct __SharedSecret {
- var ss: SecureBytes
-}
diff --git a/Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey.swift b/Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey.swift
deleted file mode 100644
index f94cea3..0000000
--- a/Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey.swift
+++ /dev/null
@@ -1,80 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import CryptoKit
-import Foundation
-
-// MARK: - PrivateKey
-// MARK: -
-
-public extension K1 {
-
- struct PrivateKey: Sendable, Hashable {
-
- private let wrapped: Wrapped
-
- public let publicKey: PublicKey
-
- internal init(wrapped: Wrapped) {
- self.wrapped = wrapped
- self.publicKey = PublicKey(wrapped: wrapped.publicKey)
- }
- }
-}
-public extension K1.PrivateKey {
- /// WARNING only use this if you really know what you are doing. This
- /// exposes the private key in raw form. Potentially devastatingly dangerous.
- var rawRepresentation: Data {
- withSecureBytes { Data($0) }
- }
-}
-
-internal extension K1.PrivateKey {
-
- func withSecureBytes(function: @escaping (SecureBytes) throws -> T) rethrows -> T {
- try wrapped.withSecureBytes(function: function)
- }
-}
-
-// MARK: - Conveninence Init
-// MARK: -
-public extension K1.PrivateKey {
-
- static func generateNew() throws -> Self {
- let wrapped = try Wrapped.generateNew()
- return Self(wrapped: wrapped)
- }
-
- static func `import`(
- rawRepresentation: D
- ) throws -> Self {
-
- let wrapped = try Wrapped.import(
- from: rawRepresentation
- )
-
- return Self(wrapped: wrapped)
- }
-}
-
-// MARK: - Equatable
-// MARK: -
-public extension K1.PrivateKey {
- /// Two PrivateKey are considered equal if their PublicKeys are equal
- static func == (lhs: Self, rhs: Self) -> Bool {
- lhs.publicKey == rhs.publicKey
- }
-}
-
-// MARK: - Hashable
-// MARK: -
-public extension K1.PrivateKey {
- /// We use the public key of the private key as input to hash
- func hash(into hasher: inout Hasher) {
- hasher.combine(self.publicKey)
- }
-}
diff --git a/Sources/K1/K1/Keys/PrivateKey/PrivateKeyOf+Feature.swift b/Sources/K1/K1/Keys/PrivateKey/PrivateKeyOf+Feature.swift
new file mode 100644
index 0000000..bedec43
--- /dev/null
+++ b/Sources/K1/K1/Keys/PrivateKey/PrivateKeyOf+Feature.swift
@@ -0,0 +1,62 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-24.
+//
+
+import Foundation
+
+// TODO: Replace typealiases and this single existential to use existentials generated with GYB.
+public struct PrivateKeyOf: Sendable, Hashable, K1PrivateKeyProtocol {
+ public init() {
+ self.init(impl: .init())
+ }
+
+ public init(rawRepresentation: some ContiguousBytes) throws {
+ try self.init(impl: .init(rawRepresentation: rawRepresentation))
+ }
+
+ public init(x963Representation: some ContiguousBytes) throws {
+ try self.init(impl: .init(x963Representation: x963Representation))
+ }
+
+ public init(derRepresentation: some RandomAccessCollection) throws {
+ try self.init(impl: .init(derRepresentation: derRepresentation))
+ }
+
+ public init(pemRepresentation: String) throws {
+ try self.init(impl: .init(pemRepresentation: pemRepresentation))
+ }
+
+ public var rawRepresentation: Data {
+ impl.rawRepresentation
+ }
+
+ public var x963Representation: Data {
+ impl.x963Representation
+ }
+
+ public var derRepresentation: Data {
+ impl.derRepresentation
+ }
+
+ public var pemRepresentation: String {
+ impl.pemRepresentation
+ }
+
+ typealias Impl = K1.PrivateKeyImpl
+ internal let impl: Impl
+ internal let publicKeyImpl: K1.PublicKeyImpl
+
+ public typealias PublicKey = Feature.PublicKey
+
+ public var publicKey: PublicKey {
+ try! .init(rawRepresentation: publicKeyImpl.rawRepresentation)
+ }
+
+ init(impl: Impl) {
+ self.impl = impl
+ self.publicKeyImpl = impl.publicKey
+ }
+}
diff --git a/Sources/K1/K1/Keys/PrivateKey/Wrapped/PrivateKey+Wrapped.swift b/Sources/K1/K1/Keys/PrivateKey/Wrapped/PrivateKey+Wrapped.swift
deleted file mode 100644
index f3c0709..0000000
--- a/Sources/K1/K1/Keys/PrivateKey/Wrapped/PrivateKey+Wrapped.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import Foundation
-
-
-internal extension K1.PrivateKey {
-
- @usableFromInline
- struct Wrapped: @unchecked Sendable {
-
- private let secureBytes: SecureBytes
-
- /// Allowing for "lazy" computation of public key. This is a workaround
- /// the fact that (immutable instances of) structs cannot have lazy
- /// properties.
- private let _publicKey: MemoizationBox = .init()
-
- private init(
- secureBytes: SecureBytes
- ) throws {
- do {
- let _ = try K1.PublicKey.Wrapped.derive(privateKeyBytes: secureBytes.bytes)
- self.secureBytes = secureBytes
-
- } catch {
- if secureBytes.first == 0x00 {
- throw K1.Error.invalidPrivateKeyMustNotBeZero
- } else {
- throw K1.Error.invalidPrivateKeyMustBeSmallerThanOrder
- }
- }
-
-
- }
- }
-
-}
-
-
-internal extension K1.PrivateKey.Wrapped {
-
- /// The computed public key of this private key.
- @usableFromInline
- var publicKey: K1.PublicKey.Wrapped {
- _publicKey.getOrEvaluate {
- do {
- return try K1.PublicKey.Wrapped.derive(privateKeyBytes: secureBytes.bytes)
- } catch {
- fatalError("Should always be able to derive public key from private key, but got underlying error: \(error)")
- }
- }
- }
-
- static func `import`(
- from privateKeyBytes: [UInt8]
- ) throws -> Self {
-
- guard
- privateKeyBytes.count == K1.PrivateKey.Wrapped.byteCount
- else {
- throw K1.Error.invalidSizeOfPrivateKey(providedByteCount: privateKeyBytes.count)
- }
- return try .init(secureBytes: SecureBytes(privateKeyBytes))
- }
-
- static func `import`(
- from data: D
- ) throws -> Self {
- try .import(
- from: data.bytes
- )
- }
-
- /// Generate a new private key from randomness.
- @usableFromInline
- static func generateNew() throws -> Self {
-
- var attempt = 0
-
- while attempt < 100 {
- defer { attempt += 1 }
- do {
- let secureBytes = SecureBytes(count: Self.byteCount)
- let privateKey = try Self(secureBytes: secureBytes)
- // Success => return valid private key
- return privateKey
- } catch {
- // Failure (due to unlikely scenario that the private key scalar > order of the curve) => retry
- }
- }
-
- // Probability of this happening is:
- // n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
- // (n / 2^256) ^ 100 = lim 0
- // I.e. will not happen.
- fatalError("""
- Failed to generate private key after #\(attempt) attempts.
- You are the most unlucky person in the universe.
- Or by Occam's razor: the person writing this code made some error.
- """
- )
- }
-}
-
-internal extension K1.PrivateKey.Wrapped {
-
- func withSecureBytes(function: (SecureBytes) throws -> T) rethrows -> T {
- try function(secureBytes)
- }
-}
-
-internal extension K1.PrivateKey.Wrapped {
- static let byteCount = K1.Curve.Field.byteCount
-
-}
diff --git a/Sources/K1/K1/Keys/PublicKey/PublicKey.swift b/Sources/K1/K1/Keys/PublicKey/PublicKey.swift
new file mode 100644
index 0000000..b1a3a9f
--- /dev/null
+++ b/Sources/K1/K1/Keys/PublicKey/PublicKey.swift
@@ -0,0 +1,147 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2022-01-27.
+//
+
+import Foundation
+
+public protocol K1PublicKeyProtocol: K1KeyPortable {
+ init(compressedRepresentation: some ContiguousBytes) throws
+ var compressedRepresentation: Data { get }
+}
+
+extension K1 {
+
+ struct PublicKeyImpl: Sendable, Hashable, K1PublicKeyProtocol {
+
+ typealias Wrapped = FFI.PublicKey.Wrapped
+ internal let wrapped: Wrapped
+
+ internal init(wrapped: Wrapped) {
+ self.wrapped = wrapped
+ }
+ }
+}
+
+// MARK: Init
+extension K1.PublicKeyImpl {
+
+
+ /// `04 || X || Y` (65 bytes)
+ static let x963ByteCount = FFI.PublicKey.x963ByteCount
+
+ /// `X || Y` (64 bytes)
+ static let rawByteCount = FFI.PublicKey.rawByteCount
+
+ /// `02|03 || X` (33 bytes)
+ static let compressedByteCount = FFI.PublicKey.compressedByteCount
+
+ /// `X || Y` (64 bytes)
+ init(rawRepresentation: some ContiguousBytes) throws {
+ try self.init(
+ wrapped: FFI.PublicKey.deserialize(rawRepresentation: rawRepresentation)
+ )
+ }
+
+ /// `04 || X || Y` (65 bytes)
+ init(x963Representation: some ContiguousBytes) throws {
+ try self.init(
+ wrapped: FFI.PublicKey.deserialize(x963Representation: x963Representation)
+ )
+ }
+
+ /// `DER`
+ init(derRepresentation: some RandomAccessCollection) throws {
+ let bytes = [UInt8](derRepresentation)
+ let parsed = try ASN1.SubjectPublicKeyInfo(asn1Encoded: bytes)
+ self = try .init(x963Representation: parsed.key)
+ }
+
+ /// `02|03 || X` (33 bytes)
+ init(compressedRepresentation: some ContiguousBytes) throws {
+ try self.init(
+ wrapped: FFI.PublicKey.deserialize(compressedRepresentation: compressedRepresentation)
+ )
+ }
+
+ /// Creates a `secp256k1` key from a Privacy-Enhanced Mail (PEM) representation.
+ init(pemRepresentation: String) throws {
+ let pem = try ASN1.PEMDocument(pemString: pemRepresentation)
+ guard pem.type == Self.pemType else {
+ throw K1.Error.invalidPEMDocument
+ }
+ self = try .init(derRepresentation: pem.derBytes)
+ }
+
+}
+
+extension K1.PublicKeyImpl {
+ static let pemType = "PUBLIC KEY"
+}
+
+// MARK: Serialize
+extension K1.PublicKeyImpl {
+
+ /// `X || Y` (64 bytes)
+ var rawRepresentation: Data {
+ Data(x963Representation.dropFirst())
+ }
+
+
+ /// `04 || X || Y` (65 bytes)
+ var x963Representation: Data {
+ try! FFI.PublicKey.serialize(wrapped, format: .uncompressed)
+ }
+
+ /// `02|03 || X` (33 bytes)
+ var compressedRepresentation: Data {
+ try! FFI.PublicKey.serialize(wrapped, format: .compressed)
+ }
+
+ /// `DER`
+ var derRepresentation: Data {
+ let spki = ASN1.SubjectPublicKeyInfo(
+ algorithmIdentifier: .secp256k1,
+ key: Array(self.x963Representation)
+ )
+ var serializer = ASN1.Serializer()
+
+ // Serializing these keys can't throw
+ try! serializer.serialize(spki)
+ return Data(serializer.serializedBytes)
+ }
+
+ /// A Privacy-Enhanced Mail (PEM) representation of the public key.
+ public var pemRepresentation: String {
+ let pemDocument = ASN1.PEMDocument(type: Self.pemType, derBytes: self.derRepresentation)
+ return pemDocument.pemString
+ }
+}
+
+// MARK: Equatable
+extension K1.PublicKeyImpl {
+ static func == (lhsSelf: Self, rhsSelf: Self) -> Bool {
+ let lhs = lhsSelf.wrapped
+ let rhs = rhsSelf.wrapped
+ do {
+ return try lhs.compare(to: rhs)
+ } catch {
+ return lhs.withUnsafeBytes { lhsBytes in
+ rhs.withUnsafeBytes { rhsBytes in
+ safeCompare(lhsBytes, rhsBytes)
+ }
+ }
+ }
+ }
+}
+
+// MARK: Hashable
+extension K1.PublicKeyImpl {
+ func hash(into hasher: inout Hasher) {
+ wrapped.withUnsafeBytes {
+ hasher.combine(bytes: $0)
+ }
+ }
+}
diff --git a/Sources/K1/K1/Keys/PublicKey/PublicKey/PublicKey+Bridge+To+C.swift b/Sources/K1/K1/Keys/PublicKey/PublicKey/PublicKey+Bridge+To+C.swift
deleted file mode 100644
index bcee89d..0000000
--- a/Sources/K1/K1/Keys/PublicKey/PublicKey/PublicKey+Bridge+To+C.swift
+++ /dev/null
@@ -1,423 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-28.
-//
-
-
-// Bridge to C
-import secp256k1
-import CryptoKit
-import Foundation
-
-// MARK: - Validate (Verify)
-// MARK: -
-internal extension K1.PublicKey {
-
- func isValidSchnorrSignature(
- _ signature: SchnorrSignature,
- hashed messageData: D
- ) throws -> Bool {
- let message = [UInt8](messageData)
- guard message.count == K1.Curve.Field.byteCount else {
- throw K1.Error.incorrectByteCountOfMessageToValidate
- }
- return try Bridge.toC { bridge -> Bool in
- let schnorrBytes = [UInt8](signature.rawRepresentation)
-
- var publicKeyBridgedToC = secp256k1_pubkey()
-
- try bridge.call(ifFailThrow: .failedToParsePublicKeyFromBytes) { context in
- /* Parse a variable-length public key into the pubkey object. */
- secp256k1_ec_pubkey_parse(
- context,
- &publicKeyBridgedToC,
- uncompressedRaw,
- uncompressedRaw.count
- )
- }
-
- var publicKeyX = secp256k1_xonly_pubkey()
-
- try bridge.call(ifFailThrow: .failedToSchnorrVerifyGettingXFromPubKey) { context in
- secp256k1_xonly_pubkey_from_pubkey(context, &publicKeyX, nil, &publicKeyBridgedToC)
- }
-
- return bridge.validate { context in
- secp256k1_schnorrsig_verify(
- context,
- schnorrBytes,
- message,
- message.count,
- &publicKeyX
- )
- }
- }
- }
-
- func isValidECDSASignature(
- _ signature: ECDSASignatureNonRecoverable,
- hashed messageData: D,
- mode: SignatureValidationMode = .default
- ) throws -> Bool {
- let message = [UInt8](messageData)
- guard message.count == K1.Curve.Field.byteCount else {
- throw K1.Error.incorrectByteCountOfMessageToValidate
- }
-
- return try Bridge.toC { bridge -> Bool in
-
- var publicKeyBridgedToC = secp256k1_pubkey()
-
- try bridge.call(ifFailThrow: .failedToParsePublicKeyFromBytes) { context in
- /* Parse a variable-length public key into the pubkey object. */
- secp256k1_ec_pubkey_parse(
- context,
- &publicKeyBridgedToC,
- uncompressedRaw,
- uncompressedRaw.count
- )
- }
-
- var signatureBridgedToCPotentiallyMalleable = secp256k1_ecdsa_signature()
- withUnsafeMutableBytes(of: &signatureBridgedToCPotentiallyMalleable.data) { pointer in
- pointer.copyBytes(
- from: signature.rawRepresentation.prefix(pointer.count)
- )
- }
-
- var signatureBridgedToCNonMalleable = secp256k1_ecdsa_signature()
-
- let codeForSignatureWasMalleable = 1
- let signatureWasMalleableResult = bridge.callWithResultCode { context in
- secp256k1_ecdsa_signature_normalize(
- context,
- &signatureBridgedToCNonMalleable, // out
- &signatureBridgedToCPotentiallyMalleable // in
- )
- }
-
- let signatureWasMalleable = signatureWasMalleableResult == codeForSignatureWasMalleable
-
- let isSignatureValid = bridge.validate { context in
- secp256k1_ecdsa_verify(
- context,
- &signatureBridgedToCNonMalleable,
- message,
- &publicKeyBridgedToC
- )
- }
-
- let acceptMalleableSignatures = mode == .acceptSignatureMalleability
-
- switch (isSignatureValid, signatureWasMalleable, acceptMalleableSignatures) {
- case (true, false, _):
- // Signature is valid
- return true
- case (true, true, true):
- // Signature was valid but malleable, since you specified to
- // accept malleability => considering signature valid.
- return true
- case (true, true, false):
- // Signature was valid, but not normalized which was required =>
- // considering signature invalid.
- return false
- case (false, _, _):
- // Signature is invalid.
- return false
- }
- }
- }
-}
-
-//public enum Schnorr: SignatureScheme {}
-
-// MARK: - Validate ECDSA (Verify)
-// MARK: -
-public extension K1.PublicKey {
-
- func isValidECDSASignature(
- _ signature: ECDSASignatureNonRecoverable,
- digest: D,
- mode: SignatureValidationMode = .default
- ) throws -> Bool {
- try isValidECDSASignature(signature, hashed: Data(digest), mode: mode)
- }
-
- func isValid(signature: ECSignatureBase, for digest: D) throws -> Bool {
- if let schnorrSignature = signature as? SchnorrSignature {
- return try isValidSchnorrSignature(schnorrSignature, digest: digest)
- } else if let ecdsaSignatureRecoverable = signature as? ECDSASignatureRecoverable {
- return try isValidECDSASignature(ecdsaSignatureRecoverable.nonRecoverable(), digest: digest)
- } else if let ecdsaSignatureNonRecoverable = signature as? ECDSASignatureNonRecoverable {
- return try isValidECDSASignature(ecdsaSignatureNonRecoverable, digest: digest)
- } else {
- throw K1.Error.failedToRecognizeSignatureType(onlySupportedSchemesAre: SigningScheme.allCases)
- }
- }
-
- func isValid(signature: ECSignatureBase, hashed: D) throws -> Bool {
- if let schnorrSignature = signature as? SchnorrSignature {
- return try isValidSchnorrSignature(schnorrSignature, hashed: hashed)
- } else if let ecdsaSignatureRecoverable = signature as? ECDSASignatureRecoverable {
- return try isValidECDSASignature(ecdsaSignatureRecoverable.nonRecoverable(), hashed: hashed)
- } else if let ecdsaSignatureNonRecoverable = signature as? ECDSASignatureNonRecoverable {
- return try isValidECDSASignature(ecdsaSignatureNonRecoverable, hashed: hashed)
- } else {
- throw K1.Error.failedToRecognizeSignatureType(onlySupportedSchemesAre: SigningScheme.allCases)
- }
- }
-
- func isValid(signature: ECSignatureBase, unhashed: M) throws -> Bool {
- try isValid(signature: signature, for: SHA256.hash(data: unhashed))
- }
-
- func isValidECDSASignature(
- _ signature: ECDSASignatureNonRecoverable,
- unhashed: M,
- mode: SignatureValidationMode = .default
- ) throws -> Bool {
- try isValidECDSASignature(signature, digest: SHA256.hash(data: unhashed), mode: mode)
- }
-
- func isValidECDSASignature(
- _ signature: ECDSASignatureRecoverable,
- unhashed: M,
- mode: SignatureValidationMode = .default
- ) throws -> Bool {
- try isValidECDSASignature(signature.nonRecoverable(), unhashed: unhashed, mode: mode)
- }
-}
-
-// MARK: - Validate Schnorr (Verify)
-// MARK: -
-public extension K1.PublicKey {
-
- func isValidSchnorrSignature(
- _ signature: SchnorrSignature,
- digest: D
- ) throws -> Bool {
- try isValidSchnorrSignature(signature, hashed: Data(digest))
- }
-
- func isValidSchnorrSignature(
- _ signature: SchnorrSignature,
- unhashed: M
- ) throws -> Bool {
- try isValidSchnorrSignature(signature, digest: SHA256.hash(data: unhashed))
- }
-}
-
-/// Validation mode controls whether or not signature malleability should
-/// is forbidden or allowed. Read more about it [here][more]
-///
-/// [more]: https://github.com/bitcoin-core/secp256k1/blob/2e5e4b67dfb67950563c5f0ab2a62e25eb1f35c5/include/secp256k1.h#L510-L550
-public enum SignatureValidationMode {
- case preventSignatureMalleability
- case acceptSignatureMalleability
-}
-
-public extension SignatureValidationMode {
- static let `default`: Self = .acceptSignatureMalleability
-}
-
-public protocol ECSignatureBase {
- static var scheme: SigningScheme { get }
-
- func wasSigned(
- by signer: K1.PublicKey,
- for digest: D
- ) throws -> Bool
-}
-
-public protocol ECSignature: ECSignatureBase {
- associatedtype Scheme: ECSignatureScheme
- associatedtype ValidationMode
- associatedtype SigningMode
-
- func wasSigned(
- by signer: K1.PublicKey,
- for digest: D,
- mode: ValidationMode
- ) throws -> Bool
-
- func wasSigned(
- by signer: K1.PublicKey,
- hashedMessage: D,
- mode: ValidationMode
- ) throws -> Bool
-
- static func by(
- signing hashed: D,
- with privateKey: K1.PrivateKey,
- mode: SigningMode
- ) throws -> Self
-}
-
-public extension ECSignature where ValidationMode == Void {
-
- func wasSigned(
- by signer: K1.PublicKey,
- for digest: D
- ) throws -> Bool {
- try wasSigned(by: signer, for: digest, mode: ())
- }
-}
-
-public extension ECSignature {
-
- static func bySigning(
- digest: D,
- with privateKey: K1.PrivateKey,
- mode: SigningMode
- ) throws -> Self {
- try by(signing: Array(digest), with: privateKey, mode: mode)
- }
-
- static func bySigning(
- unhashed data: D,
- with privateKey: K1.PrivateKey,
- mode: SigningMode
- ) throws -> Self {
- try bySigning(digest: SHA256.hash(data: data), with: privateKey, mode: mode)
- }
-}
-
-public extension ECDSASignatureNonRecoverable {
-
- typealias ValidationMode = SignatureValidationMode
-
- struct SigningMode {
- public let nonceFunctionArbitraryData: Data?
- public init(nonceFunctionArbitraryData: Data? = nil) {
- self.nonceFunctionArbitraryData = nonceFunctionArbitraryData
- }
- }
-
- static func by(
- signing hashed: D,
- with privateKey: K1.PrivateKey,
- mode: SigningMode
- ) throws -> Self {
- try privateKey.ecdsaSignNonRecoverable(hashed: hashed, mode: mode)
- }
-
- func wasSigned(
- by signer: K1.PublicKey,
- hashedMessage: D,
- mode: ValidationMode = .default
- ) throws -> Bool {
- try signer.isValidECDSASignature(self, hashed: hashedMessage, mode: mode)
- }
-
- func wasSigned(by signer: K1.PublicKey, for digest: D) throws -> Bool {
- try wasSigned(by: signer, for: digest, mode: .default)
- }
-
- func wasSigned(
- by signer: K1.PublicKey,
- for digest: D,
- mode: ValidationMode = .default
- ) throws -> Bool {
- try signer.isValidECDSASignature(
- self,
- digest: digest,
- mode: mode
- )
- }
-}
-
-public extension ECDSASignatureNonRecoverable.SigningMode {
- static let `default`: Self = .init()
-}
-
-public protocol ECSignatureScheme where Signature.Scheme == Self {
- associatedtype Hasher: HashFunction
- associatedtype Signature: ECSignature
- static var scheme: SigningScheme { get }
-}
-public extension ECSignatureScheme {
- typealias HashDigest = Hasher.Digest
- static func hash(unhashed: D) throws -> HashDigest {
- Hasher.hash(data: unhashed)
- }
-}
-
-
-public extension ECSignatureScheme {
- static var scheme: SigningScheme { Signature.scheme }
-}
-
-public enum ECDSA: ECSignatureScheme {
- public typealias Hasher = SHA256
- public typealias Signature = ECDSASignatureNonRecoverable
-}
-
-public enum Schnorr: ECSignatureScheme {
- public typealias Hasher = SHA256
- public typealias Signature = SchnorrSignature
-}
-
-public enum SigningScheme: String, Equatable, CaseIterable {
- case schnorr
- case ecdsa
-}
-
-public struct SchnorrSignature: ECSignature, Sendable, Hashable {
- public typealias Scheme = Schnorr
- private let _rawRepresentation: [UInt8]
- public var rawRepresentation: Data {
- Data(_rawRepresentation)
- }
-
- public init(rawRepresentation: D) throws {
- guard
- rawRepresentation.count == 2 * K1.Curve.Field.byteCount
- else {
- throw K1.Error.incorrectByteCountOfRawSignature
- }
-
- self._rawRepresentation = [UInt8](rawRepresentation)
- }
-}
-
-public extension SchnorrSignature {
- static let scheme: SigningScheme = .schnorr
- typealias ValidationMode = Void
-
- func compactRepresentation() throws -> Data {
- try Bridge.compactRepresentationOfSignature(rawRepresentation: rawRepresentation)
- }
- func derRepresentation() throws -> Data {
- try Bridge.derRepresentationOfSignature(rawRepresentation: rawRepresentation)
- }
-
- func wasSigned(
- by signer: K1.PublicKey,
- for digest: D,
- mode _: Void
- ) throws -> Bool {
- try signer.isValidSchnorrSignature(self, digest: digest)
- }
-
-
- func wasSigned(
- by signer: K1.PublicKey,
- hashedMessage: D,
- mode: ValidationMode
- ) throws -> Bool {
- try signer.isValidSchnorrSignature(self, hashed: hashedMessage)
- }
-
- typealias SigningMode = SchnorrInput
-
- static func by(
- signing hashed: D,
- with privateKey: K1.PrivateKey,
- mode: SigningMode
- ) throws -> Self {
- try privateKey.schnorrSign(hashed: hashed, input: mode)
- }
-
-}
diff --git a/Sources/K1/K1/Keys/PublicKey/PublicKey/PublicKey.swift b/Sources/K1/K1/Keys/PublicKey/PublicKey/PublicKey.swift
deleted file mode 100644
index 15841f2..0000000
--- a/Sources/K1/K1/Keys/PublicKey/PublicKey/PublicKey.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import Foundation
-
-public extension K1 {
-
- struct PublicKey: Sendable, Hashable {
-
- private let wrapped: Wrapped
-
- internal init(wrapped: Wrapped) {
- self.wrapped = wrapped
- }
- }
-}
-
-internal extension K1.PublicKey {
- var uncompressedRaw: [UInt8] {
- wrapped.uncompressedRaw
- }
-}
-
-// MARK: - Convenience Init
-// MARK: -
-public extension K1.PublicKey {
-
- static func `import`(
- from data: D
- ) throws -> Self {
- try self.init(
- wrapped: .import(from: data)
- )
- }
-
-}
-
-public extension K1.PublicKey {
-
- func rawRepresentation(format: K1.Format) throws -> [UInt8] {
- try wrapped.rawRepresentation(format: format)
- }
-
-
-}
-
diff --git a/Sources/K1/K1/Keys/PublicKey/PublicKeyOf+Feature.swift b/Sources/K1/K1/Keys/PublicKey/PublicKeyOf+Feature.swift
new file mode 100644
index 0000000..24b5613
--- /dev/null
+++ b/Sources/K1/K1/Keys/PublicKey/PublicKeyOf+Feature.swift
@@ -0,0 +1,58 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-24.
+//
+
+import Foundation
+
+// TODO: Replace typealiases and this single existential to use existentials generated with GYB.
+public struct PublicKeyOf: Sendable, Hashable, K1PublicKeyProtocol {
+
+ public init(rawRepresentation: some ContiguousBytes) throws {
+ try self.init(impl: .init(rawRepresentation: rawRepresentation))
+ }
+
+ public init(compressedRepresentation: some ContiguousBytes) throws {
+ try self.init(impl: .init(compressedRepresentation: compressedRepresentation))
+ }
+
+ public init(x963Representation: some ContiguousBytes) throws {
+ try self.init(impl: .init(x963Representation: x963Representation))
+ }
+
+ public init(derRepresentation: some RandomAccessCollection) throws {
+ try self.init(impl: .init(derRepresentation: derRepresentation))
+ }
+
+ public init(pemRepresentation: String) throws {
+ try self.init(impl: .init(pemRepresentation: pemRepresentation))
+ }
+
+ public var rawRepresentation: Data {
+ impl.rawRepresentation
+ }
+
+ public var x963Representation: Data {
+ impl.x963Representation
+ }
+
+ public var derRepresentation: Data {
+ impl.derRepresentation
+ }
+
+ public var compressedRepresentation: Data {
+ impl.compressedRepresentation
+ }
+
+ public var pemRepresentation: String {
+ impl.pemRepresentation
+ }
+
+ typealias Impl = K1.PublicKeyImpl
+ internal let impl: Impl
+ internal init(impl: Impl) {
+ self.impl = impl
+ }
+}
diff --git a/Sources/K1/K1/Keys/PublicKey/Wrapped/PublicKey+Wrapped+Bridge+To+C.swift b/Sources/K1/K1/Keys/PublicKey/Wrapped/PublicKey+Wrapped+Bridge+To+C.swift
deleted file mode 100644
index 0f4b785..0000000
--- a/Sources/K1/K1/Keys/PublicKey/Wrapped/PublicKey+Wrapped+Bridge+To+C.swift
+++ /dev/null
@@ -1,170 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import secp256k1
-import Foundation
-
-extension Bridge {
-
- static func publicKeyParse(
- raw: [UInt8]
- ) throws -> Data {
-
- guard K1.Format.allCases.map(\.length).contains(raw.count) else {
- throw K1.Error.incorrectByteCountOfPublicKey(providedByteCount: raw.count)
- }
-
- var publicKeyBytesMaybeCompressed = raw
- var publicKeyBridgedToC = secp256k1_pubkey()
-
- try Self.call(
- ifFailThrow: .failedToParsePublicKeyFromBytes
- ) { context in
- /* Parse a variable-length public key into the pubkey object. */
- secp256k1_ec_pubkey_parse(
- context,
- &publicKeyBridgedToC,
- &publicKeyBytesMaybeCompressed,
- raw.count
- )
- }
-
- if publicKeyBytesMaybeCompressed.count == K1.Format.uncompressed.length {
- return Data(publicKeyBytesMaybeCompressed)
- }
-
- // Was compressed, need to uncompress
-
- var publicKeyBytesUncompressedLength = K1.Format.uncompressed.length
- var publicKeyBytesUncompressed = [UInt8].init(repeating: 0, count: publicKeyBytesUncompressedLength)
-
- try Self.call(
- ifFailThrow: .failedToUncompressPublicKey
- ) { context in
- /* "Serialize a pubkey object into a serialized byte sequence." */
- secp256k1_ec_pubkey_serialize(
- context,
- &publicKeyBytesUncompressed,
- &publicKeyBytesUncompressedLength,
- &publicKeyBridgedToC,
- K1.Format.uncompressed.rawValue
- )
- }
-
- return Data(publicKeyBytesUncompressed)
-
- }
-
- static func compress(
- publicKey: K1.PublicKey.Wrapped
- ) throws -> Data {
-
- var publicKeyBridgedToC = secp256k1_pubkey()
-
- try Self.call(ifFailThrow: .failedToParsePublicKeyFromBytes) { context in
- /* Parse a variable-length public key into the pubkey object. */
- secp256k1_ec_pubkey_parse(
- context,
- &publicKeyBridgedToC,
- publicKey.uncompressedRaw,
- publicKey.uncompressedRaw.count
- )
- }
-
- let publicKeyFormat = K1.Format.compressed
-
- var publicKeyCompressedByteCount = publicKeyFormat.length
- var publicKeyBytes = [UInt8](
- repeating: 0,
- count: publicKeyCompressedByteCount
- )
-
- try Self.call(
- ifFailThrow: .failedToCompressPublicKey
- ) { context in
- /* "Serialize a pubkey object into a serialized byte sequence." */
- secp256k1_ec_pubkey_serialize(
- context,
- &publicKeyBytes,
- &publicKeyCompressedByteCount,
- &publicKeyBridgedToC,
- publicKeyFormat.rawValue
- )
- }
-
- return Data(publicKeyBytes)
- }
-
-
- static func publicKeyCreate(privateKeyBytes: [UInt8]) throws -> Data {
-
- guard
- privateKeyBytes.count == K1.PrivateKey.Wrapped.byteCount
- else {
- throw K1.Error.invalidSizeOfPrivateKey(providedByteCount: privateKeyBytes.count)
- }
-
- let publicKeyFormat = K1.Format.uncompressed
- var publicKeyByteCount = publicKeyFormat.length
- var publicKeyBridgedToC = secp256k1_pubkey()
-
- var publicKeyBytes = [UInt8](
- repeating: 0,
- count: publicKeyFormat.length
- )
-
- try Bridge.toC { bridge in
-
- try bridge.call(
- ifFailThrow: .failedToUpdateContextRandomization
- ) {
- secp256k1_context_randomize($0, privateKeyBytes)
- }
-
- try bridge.call(
- ifFailThrow: .failedToComputePublicKeyFromPrivateKey
- ) {
- /* "Compute the public key for a secret key." */
- secp256k1_ec_pubkey_create($0, &publicKeyBridgedToC, privateKeyBytes)
- }
-
- try bridge.call(
- ifFailThrow: .failedToSerializePublicKeyIntoBytes
- ) {
- /* "Serialize a pubkey object into a serialized byte sequence." */
- secp256k1_ec_pubkey_serialize(
- $0,
- &publicKeyBytes,
- &publicKeyByteCount,
- &publicKeyBridgedToC,
- publicKeyFormat.rawValue
- )
- }
- }
-
- assert(publicKeyByteCount == publicKeyFormat.length)
-
- return Data(publicKeyBytes)
- }
-}
-
-
-internal extension K1.PublicKey.Wrapped {
-
- static func `import`(from raw: [UInt8]) throws -> Self {
- let publicKeyBytes = try Bridge.publicKeyParse(raw: raw)
- return try Self(uncompressedRaw: publicKeyBytes.bytes)
- }
-
- static func derive(
- privateKeyBytes: [UInt8]
- ) throws -> Self {
- let publicKeyRaw = try Bridge.publicKeyCreate(privateKeyBytes: privateKeyBytes)
- return try Self(uncompressedRaw: publicKeyRaw.bytes)
- }
-
-}
diff --git a/Sources/K1/K1/Keys/PublicKey/Wrapped/PublicKey+Wrapped.swift b/Sources/K1/K1/Keys/PublicKey/Wrapped/PublicKey+Wrapped.swift
deleted file mode 100644
index 75bbc06..0000000
--- a/Sources/K1/K1/Keys/PublicKey/Wrapped/PublicKey+Wrapped.swift
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-27.
-//
-
-import Foundation
-
-internal extension K1.PublicKey {
-
- @usableFromInline
- struct Wrapped: Sendable, Hashable {
-
- internal let uncompressedRaw: [UInt8]
-
- internal init(
- uncompressedRaw: [UInt8]
- ) throws {
- guard uncompressedRaw.count == K1.Format.uncompressed.length else {
- // Only accept uncompressed public key here.
- throw K1.Error.incorrectByteCountOfPublicKey(got: uncompressedRaw.count, acceptableLengths: [K1.Format.uncompressed.length])
- }
- self.uncompressedRaw = uncompressedRaw
- }
- }
-}
-
-internal extension K1.PublicKey.Wrapped {
-
- @usableFromInline
- func rawRepresentation(format: K1.Format) throws -> [UInt8] {
- switch format {
- case .uncompressed: return uncompressedRaw
- case .compressed: return try Array(Bridge.compress(publicKey: self))
- }
- }
-
- static func `import`(
- from raw: D)
- throws -> Self {
- try .import(from: raw.bytes)
- }
-
- @usableFromInline
- static func == (lhs: Self, rhs: Self) -> Bool {
- /// We use constant time comparision to not leak any information about
- /// the private key, because `PrivateKey` is `Equatable` and checks
- /// equality using its publicKey.
- safeCompare(lhs.uncompressedRaw, rhs.uncompressedRaw)
- }
-}
diff --git a/Sources/K1/K1/Schnorr/Schnorr+Signature.swift b/Sources/K1/K1/Schnorr/Schnorr+Signature.swift
new file mode 100644
index 0000000..4c5ca37
--- /dev/null
+++ b/Sources/K1/K1/Schnorr/Schnorr+Signature.swift
@@ -0,0 +1,33 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+
+extension K1.Schnorr {
+ public struct Signature: Sendable, Hashable {
+
+ typealias Wrapped = FFI.Schnorr.Wrapped
+ internal let wrapped: Wrapped
+ internal init(wrapped: Wrapped) {
+ self.wrapped = wrapped
+ }
+ }
+}
+
+// MARK: Init
+extension K1.Schnorr.Signature {
+ public init(rawRepresentation: some DataProtocol) throws {
+ try self.init(wrapped: .init(bytes: [UInt8](rawRepresentation)))
+ }
+}
+
+// MARK: Serialize
+extension K1.Schnorr.Signature {
+ public var rawRepresentation: Data {
+ wrapped.rawRepresentation
+ }
+}
diff --git a/Sources/K1/K1/Schnorr/Schnorr.swift b/Sources/K1/K1/Schnorr/Schnorr.swift
new file mode 100644
index 0000000..c5122c5
--- /dev/null
+++ b/Sources/K1/K1/Schnorr/Schnorr.swift
@@ -0,0 +1,147 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-24.
+//
+
+import Foundation
+import protocol CryptoKit.Digest
+import struct CryptoKit.SHA256
+
+extension K1 {
+
+ /// A mechanism used to create or verify a cryptographic signature using the `secp256k1` and Schnorr signature scheme.
+ public enum Schnorr: K1Feature {
+
+ /// A `secp256k1` private key used to create cryptographic signatures,
+ /// more specifically Schnorr signatures.
+ public typealias PrivateKey = PrivateKeyOf
+
+ /// A `secp256k1` public key used to verify cryptographic signatures,
+ /// more specifically Schnorr signatures.
+ public typealias PublicKey = PublicKeyOf
+ }
+}
+
+// MARK: Sign
+extension K1.Schnorr.PrivateKey {
+ public func signature(
+ for hashed: some DataProtocol,
+ options: K1.Schnorr.SigningOptions = .default
+ ) throws -> K1.Schnorr.Signature {
+ try K1.Schnorr.Signature(
+ wrapped: FFI.Schnorr.sign(
+ hashedMessage: [UInt8](hashed),
+ privateKey: impl.wrapped,
+ options: options
+ )
+ )
+ }
+
+ public func signature(
+ for digest: some Digest,
+ options: K1.Schnorr.SigningOptions = .default
+ ) throws -> K1.Schnorr.Signature {
+ try signature(
+ for: [UInt8](digest),
+ options: options
+ )
+ }
+
+ /// SHA256 hashes `unhashed` before signing it.
+ public func signature(
+ forUnhashed unhashed: some DataProtocol,
+ options: K1.Schnorr.SigningOptions = .default
+ ) throws -> K1.Schnorr.Signature {
+ try signature(
+ for: SHA256.hash(data: unhashed),
+ options: options
+ )
+ }
+}
+
+// MARK: Validate
+extension K1.Schnorr.PublicKey {
+
+ /// Verifies a Schnorr signature on some _hash_ over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - signature: The Schnorr signature to check against the _hashed_ data.
+ /// - hashed: The _hashed_ data covered by the signature.
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given _hashed_ data.
+ public func isValidSignature(
+ _ signature: K1.Schnorr.Signature,
+ hashed: some DataProtocol
+ ) -> Bool {
+ do {
+ return try FFI.Schnorr.isValid(
+ signature: signature.wrapped,
+ publicKey: self.impl.wrapped,
+ message: [UInt8](hashed)
+ )
+ } catch {
+ return false
+ }
+ }
+
+
+ /// Verifies a Schnorr signature on a digest over the `secp256k1` elliptic curve.
+ /// - Parameters:
+ /// - signature: The Schnorr signature to check against the given digest.
+ /// - digest: The digest covered by the signature.
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given digest.
+ public func isValidSignature(
+ _ signature: K1.Schnorr.Signature,
+ digest: some Digest
+ ) -> Bool {
+ isValidSignature(
+ signature,
+ hashed: [UInt8](digest)
+ )
+ }
+
+ /// Verifies a Schnorr signature on a block of data over the `secp256k1` elliptic curve.
+ ///
+ /// The function computes an SHA-256 hash from the data before verifying the signature. If you separately hash the data to be signed, use `isValidSignature(_:digest:input)` with the created digest. Or if you have access to a digest just as `some DataProtocol`, use
+ /// `isValidSignature(_:hashed:input)`
+ /// .
+ /// - Parameters:
+ /// - signature: The Schnorr signature to check against the block of data.
+ /// - unhashed: The block of data covered by the signature.
+ /// - Returns: A Boolean value that’s true if the signature is valid for the given block of data.
+ public func isValidSignature(
+ _ signature: K1.Schnorr.Signature,
+ unhashed: some DataProtocol
+ ) -> Bool {
+ isValidSignature(
+ signature,
+ digest: SHA256.hash(data: unhashed)
+ )
+ }
+
+}
+
+extension K1.Schnorr {
+ public struct SigningOptions: Sendable, Hashable {
+ public let auxilaryRandomData: AuxilaryRandomData?
+ public init(auxilaryRandomData: AuxilaryRandomData? = nil) {
+ self.auxilaryRandomData = auxilaryRandomData
+ }
+ }
+}
+
+extension K1.Schnorr.SigningOptions {
+
+ public static let `default` = Self()
+
+ public struct AuxilaryRandomData: Sendable, Hashable {
+ public let aux: [UInt8]
+
+ public init(aux: some DataProtocol) throws {
+ guard aux.count == Curve.Field.byteCount else {
+ throw K1.Error.failedToSchnorrSignDigestProvidedRandomnessInvalidLength
+ }
+ self.aux = [UInt8](aux)
+ }
+ }
+}
diff --git a/Sources/K1/Extensions/Data+Extensions.swift b/Sources/K1/Support/Extensions/Data+Extensions.swift
similarity index 66%
rename from Sources/K1/Extensions/Data+Extensions.swift
rename to Sources/K1/Support/Extensions/Data+Extensions.swift
index d97ce8f..bac5bb4 100644
--- a/Sources/K1/Extensions/Data+Extensions.swift
+++ b/Sources/K1/Support/Extensions/Data+Extensions.swift
@@ -8,13 +8,6 @@
import Foundation
-extension Data {
- var bytes: [UInt8] {
- withUnsafeBytes { pointer in
- Array(pointer)
- }
- }
-}
extension ContiguousBytes {
diff --git a/Sources/K1/Support/FFI/API/ECDH/FFI+ECDH.swift b/Sources/K1/Support/FFI/API/ECDH/FFI+ECDH.swift
new file mode 100644
index 0000000..7860103
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/ECDH/FFI+ECDH.swift
@@ -0,0 +1,98 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+import secp256k1
+
+extension FFI {
+
+ /// Just a namespace for `FFI ECDH`
+ enum ECDH {}
+}
+
+// MARK: SerializeFunction
+extension FFI.ECDH {
+
+ enum SerializeFunction {
+
+ /// Using the `libsecp256k1` default behaviour.
+ ///
+ /// SHA256 hashes the **compressed** shared point.
+ /// Accepts arbitrary data passed through hash function as well.
+ ///
+ case libsecp256kDefault(arbitraryData: Data?)
+
+ /// Following the [ANSI X9.63][ansix963] standard
+ ///
+ /// No hash, returns `X` component of shared point only.
+ ///
+ /// [ansix963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017
+ case ansiX963
+
+ /// Following no standard at all.
+ ///
+ /// No hash, returns the whole shared point.
+ ///
+ case noHashWholePoint
+
+ func hashfp() -> (Optional<@convention(c) (Optional>, Optional>, Optional>, Optional) -> Int32>) {
+ switch self {
+ case .libsecp256kDefault: return secp256k1_ecdh_hash_function_default
+ case .ansiX963: return ecdh_asn1_x963
+ case .noHashWholePoint: return ecdh_unsafe_whole_point
+ }
+ }
+
+ var outputByteCount: Int {
+ switch self {
+ case .libsecp256kDefault: return Curve.Field.byteCount
+ case .ansiX963: return Curve.Field.byteCount
+ case .noHashWholePoint: return K1.Format.uncompressed.length
+ }
+ }
+ }
+}
+
+// MARK: ECDH
+extension FFI.ECDH {
+ static func keyExchange(
+ publicKey: FFI.PublicKey.Wrapped,
+ privateKey: FFI.PrivateKey.Wrapped,
+ serializeOutputFunction hashFp: SerializeFunction
+ ) throws -> Data {
+
+ var sharedPublicPointBytes = [UInt8](
+ repeating: 0,
+ count: hashFp.outputByteCount
+ )
+ var arbitraryData: [UInt8]? = {
+ switch hashFp {
+ case let .libsecp256kDefault(arbitraryData?): return [UInt8](arbitraryData)
+ case .libsecp256kDefault(.none): return nil
+ case .ansiX963, .noHashWholePoint: return nil
+ }
+ }()
+ var publicKeyRaw = publicKey.raw
+ try FFI.call(
+ ifFailThrow: .failedToPerformDiffieHellmanKeyExchange
+ ) { context in
+ secp256k1_ecdh(
+ context,
+ &sharedPublicPointBytes, // output
+ &publicKeyRaw, // pubkey
+ privateKey.secureBytes.backing.bytes, // seckey
+ hashFp.hashfp(), // hashfp
+ &arbitraryData // arbitrary data pointer that is passed through to hashfp
+ )
+ }
+
+ return Data(sharedPublicPointBytes)
+
+ }
+}
+
+
diff --git a/Sources/K1/Support/FFI/API/ECDSA/FFI+ECDSA.swift b/Sources/K1/Support/FFI/API/ECDSA/FFI+ECDSA.swift
new file mode 100644
index 0000000..8324f86
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/ECDSA/FFI+ECDSA.swift
@@ -0,0 +1,102 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+import secp256k1
+
+extension FFI {
+ public enum ECDSA {}
+}
+extension FFI.ECDSA {
+ public enum Recovery {}
+ public enum NonRecovery {}
+}
+
+
+protocol RawECDSASignature {
+ init()
+}
+extension secp256k1_ecdsa_recoverable_signature: RawECDSASignature {}
+extension secp256k1_ecdsa_signature: RawECDSASignature {}
+
+protocol WrappedECDSASignature {
+ associatedtype Raw: RawECDSASignature
+ init(raw: Raw)
+ var raw: Raw { get }
+ static func sign() -> (OpaquePointer, UnsafeMutablePointer, UnsafePointer, UnsafePointer, secp256k1_nonce_function?, UnsafeRawPointer?) -> Int32
+}
+
+// MARK: ECDSA Shared
+extension FFI.ECDSA {
+
+ internal static func _sign(
+ message: [UInt8],
+ privateKey: FFI.PrivateKey.Wrapped,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> WrappedSignature where WrappedSignature: WrappedECDSASignature {
+ guard message.count == Curve.Field.byteCount else {
+ throw K1.Error.unableToSignMessageHasInvalidLength(
+ got: message.count,
+ expected: Curve.Field.byteCount
+ )
+ }
+
+ var raw = WrappedSignature.Raw()
+
+ try FFI.call(
+ ifFailThrow: .failedToECDSASignDigest
+ ) { context in
+ WrappedSignature.sign()(
+ context,
+ &raw,
+ message,
+ privateKey.secureBytes.backing.bytes,
+ options.nonceFunction.function(),
+ options.arbitraryData
+ )
+ }
+
+ return .init(raw: raw)
+
+ }
+}
+extension K1.ECDSA.SigningOptions {
+ fileprivate var arbitraryData: [UInt8]? {
+ switch self.nonceFunction {
+ case .random: return nil
+ case let .deterministic(arbitraryData): return arbitraryData?.arbitraryData
+ }
+ }
+
+}
+extension K1.ECDSA.SigningOptions.NonceFunction {
+ fileprivate func function() -> Optional<@convention(c) (Optional>, Optional>, Optional>, Optional>, Optional, UInt32) -> Int32> {
+ switch self {
+ case .deterministic:
+ return secp256k1_nonce_function_rfc6979
+
+ case .random:
+ return { (
+ nonce32: UnsafeMutablePointer?, // Out: pointer to a 32-byte array to be filled by the function.
+ msg: UnsafePointer?, // In: the 32-byte message hash being verified (will not be NULL)
+ key32: UnsafePointer?, // In: pointer to a 32-byte secret key (will not be NULL)
+ algo16: UnsafePointer?, // In: pointer to a 16-byte array describing the signature algorithm (will be NULL for ECDSA for compatibility).
+ arbitraryData: UnsafeMutableRawPointer?, // In: Arbitrary data pointer that is passed through.
+ attemptIndex: UInt32 // In: how many iterations we have tried to find a nonce. This will almost always be 0, but different attempt values are required to result in a different nonce.
+ ) -> Int32 /* Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail. */ in
+
+ SecureBytes(count: Curve.Field.byteCount).withUnsafeBytes {
+ nonce32?.assign(from: $0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
+ }
+
+ // Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail.
+ return 1
+ }
+ }
+ }
+}
+
diff --git a/Sources/K1/Support/FFI/API/ECDSA/NonRecovery/ECDSA+NonRecovery+Wrapped.swift b/Sources/K1/Support/FFI/API/ECDSA/NonRecovery/ECDSA+NonRecovery+Wrapped.swift
new file mode 100644
index 0000000..6cbd0a6
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/ECDSA/NonRecovery/ECDSA+NonRecovery+Wrapped.swift
@@ -0,0 +1,34 @@
+import Foundation
+import secp256k1
+
+// MARK: Wrapped
+extension FFI.ECDSA.NonRecovery {
+
+ struct Wrapped: @unchecked Sendable, ContiguousBytes, WrappedECDSASignature {
+ typealias Raw = secp256k1_ecdsa_signature
+ let raw: Raw
+ init(raw: Raw) {
+ self.raw = raw
+ }
+ }
+}
+
+
+// MARK: Sign
+extension FFI.ECDSA.NonRecovery.Wrapped {
+ static func sign() -> (OpaquePointer, UnsafeMutablePointer, UnsafePointer, UnsafePointer, secp256k1_nonce_function?, UnsafeRawPointer?) -> Int32 {
+ secp256k1_ecdsa_sign
+ }
+}
+
+// MARK: ContiguousBytes
+extension FFI.ECDSA.NonRecovery.Wrapped {
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ var rawData = raw.data
+ return try Swift.withUnsafeBytes(of: &rawData) { pointer in
+ try body(pointer)
+ }
+ }
+}
+
+
diff --git a/Sources/K1/Support/FFI/API/ECDSA/NonRecovery/FFI+ECDSA+NonRecovery.swift b/Sources/K1/Support/FFI/API/ECDSA/NonRecovery/FFI+ECDSA+NonRecovery.swift
new file mode 100644
index 0000000..1936376
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/ECDSA/NonRecovery/FFI+ECDSA+NonRecovery.swift
@@ -0,0 +1,174 @@
+import Foundation
+import secp256k1
+
+// MARK: Deserialize
+extension FFI.ECDSA.NonRecovery {
+ static let byteCount = 2 * Curve.Field.byteCount
+
+ /// Compact aka `IEEE P1363` aka `R||S`.
+ static func from(
+ compactBytes: [UInt8]
+ ) throws -> Wrapped {
+ try Wrapped(
+ raw: Raw.nonRecoverableSignature(compactBytes: compactBytes)
+ )
+ }
+
+ static func from(
+ derRepresentation: [UInt8]
+ ) throws -> Wrapped {
+ try Wrapped(
+ raw: Raw.nonRecoverableSignature(derBytes: derRepresentation)
+ )
+ }
+}
+
+// MARK: Serialize
+extension FFI.ECDSA.NonRecovery {
+ static func compact(_ wrapped: Wrapped) throws -> Data {
+ var out = [UInt8](repeating: 0, count: Self.byteCount)
+ var rawSignature = wrapped.raw
+ try FFI.call(ifFailThrow: .failedToSerializeSignature) { context in
+ secp256k1_ecdsa_signature_serialize_compact(context, &out, &rawSignature)
+ }
+ return Data(out)
+ }
+
+ static func der(
+ _ wrapped: Wrapped
+ ) throws -> Data {
+ var derMaxLength = 75 // in fact max is 73, but we can have some margin.
+ var derSignature = [UInt8](repeating: 0, count: derMaxLength)
+ var rawSignature = wrapped.raw
+ try FFI.call(ifFailThrow: .failedToSerializeDERSignature) { context in
+ secp256k1_ecdsa_signature_serialize_der(
+ context,
+ &derSignature,
+ &derMaxLength,
+ &rawSignature
+ )
+ }
+ return Data(derSignature.prefix(derMaxLength))
+ }
+}
+
+// MARK: Recover
+extension FFI.ECDSA.NonRecovery {
+
+ static func recoverPublicKey(
+ _ wrapped: Wrapped,
+ recoveryID: Int32,
+ message: [UInt8]
+ ) throws -> FFI.PublicKey.Wrapped {
+ guard message.count == Curve.Field.byteCount else {
+ throw K1.Error.unableToRecoverMessageHasInvalidLength(got: message.count, expected: Curve.Field.byteCount)
+ }
+ let nonRecoverableCompact = try FFI.ECDSA.NonRecovery.compact(wrapped)
+ return try Self.recoverPublicKey(
+ nonRecoverableCompact: nonRecoverableCompact,
+ recoveryID: recoveryID,
+ message: message
+ )
+ }
+
+ static func recoverPublicKey(
+ nonRecoverableCompact: Data,
+ recoveryID: Int32,
+ message: [UInt8]
+ ) throws -> FFI.PublicKey.Wrapped {
+ guard message.count == Curve.Field.byteCount else {
+ throw K1.Error.unableToRecoverMessageHasInvalidLength(
+ got: message.count,
+ expected: Curve.Field.byteCount
+ )
+ }
+ var compact = [UInt8](nonRecoverableCompact)
+ var recoverable = secp256k1_ecdsa_recoverable_signature()
+ try FFI.call(ifFailThrow: .failedToParseRecoverableSignatureFromCompact) { context in
+ secp256k1_ecdsa_recoverable_signature_parse_compact(
+ context,
+ &recoverable,
+ &compact,
+ recoveryID
+ )
+ }
+ var publicKeyRaw = secp256k1_pubkey()
+ try FFI.call(ifFailThrow: .failedToRecoverPublicKey) { context in
+ secp256k1_ecdsa_recover(
+ context,
+ &publicKeyRaw,
+ &recoverable,
+ message
+ )
+ }
+ return FFI.PublicKey.Wrapped(raw: publicKeyRaw)
+ }
+}
+
+
+// MARK: Validate
+extension FFI.ECDSA.NonRecovery {
+ static func isValid(
+ ecdsaSignature: FFI.ECDSA.NonRecovery.Wrapped,
+ publicKey: FFI.PublicKey.Wrapped,
+ message: [UInt8],
+ options: K1.ECDSA.ValidationOptions = .default
+ ) throws -> Bool {
+ try FFI.toC { ffi -> Bool in
+ var publicKeyRaw = publicKey.raw
+ var maybeMalleable = ecdsaSignature.raw
+ var normalized = secp256k1_ecdsa_signature()
+
+ let codeForSignatureWasMalleable = 1
+ let signatureWasMalleableResult = ffi.callWithResultCode { context in
+ secp256k1_ecdsa_signature_normalize(context, &normalized, &maybeMalleable)
+ }
+ let signatureWasMalleable = signatureWasMalleableResult == codeForSignatureWasMalleable
+ let isSignatureValid = ffi.validate { context in
+ secp256k1_ecdsa_verify(
+ context,
+ &normalized,
+ message,
+ &publicKeyRaw
+ )
+ }
+ let acceptMalleableSignatures = options.malleabilityStrictness == .accepted
+ switch (isSignatureValid, signatureWasMalleable, acceptMalleableSignatures) {
+ case (true, false, _):
+ // Signature is valid
+ return true
+ case (true, true, true):
+ // Signature was valid but malleable, since you specified to
+ // accept malleability => considering signature valid.
+ return true
+ case (true, true, false):
+ // Signature was valid, but not normalized which was required =>
+ // considering signature invalid.
+ return false
+ case (false, _, _):
+ // Signature is invalid.
+ return false
+ }
+ }
+ }
+
+}
+
+
+// MARK: Sign
+extension FFI.ECDSA.NonRecovery {
+
+ /// Produces a **non recoverable** ECDSA signature from a hashed `message`
+ static func sign(
+ hashedMessage: [UInt8],
+ privateKey: FFI.PrivateKey.Wrapped,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> FFI.ECDSA.NonRecovery.Wrapped {
+
+ try FFI.ECDSA._sign(
+ message: hashedMessage,
+ privateKey: privateKey,
+ options: options
+ )
+ }
+}
diff --git a/Sources/K1/Support/FFI/API/ECDSA/Recovery/ECDSA+Recovery+Wrapped.swift b/Sources/K1/Support/FFI/API/ECDSA/Recovery/ECDSA+Recovery+Wrapped.swift
new file mode 100644
index 0000000..40cbe06
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/ECDSA/Recovery/ECDSA+Recovery+Wrapped.swift
@@ -0,0 +1,33 @@
+import Foundation
+import secp256k1
+
+
+// MARK: ECDSA Recovery Wrapped
+extension FFI.ECDSA.Recovery {
+ struct Wrapped: @unchecked Sendable, ContiguousBytes, WrappedECDSASignature {
+ typealias Raw = secp256k1_ecdsa_recoverable_signature
+ let raw: Raw
+ init(raw: Raw) {
+ self.raw = raw
+ }
+ }
+}
+
+// MARK: Sign
+extension FFI.ECDSA.Recovery.Wrapped {
+
+ static func sign() -> (OpaquePointer, UnsafeMutablePointer, UnsafePointer, UnsafePointer, secp256k1_nonce_function?, UnsafeRawPointer?) -> Int32 {
+ secp256k1_ecdsa_sign_recoverable
+ }
+}
+
+// MARK: ContiguousBytes
+extension FFI.ECDSA.Recovery.Wrapped {
+
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ var rawData = raw.data
+ return try Swift.withUnsafeBytes(of: &rawData) { pointer in
+ try body(pointer)
+ }
+ }
+}
diff --git a/Sources/K1/Support/FFI/API/ECDSA/Recovery/FFI+ECDSA+Recovery.swift b/Sources/K1/Support/FFI/API/ECDSA/Recovery/FFI+ECDSA+Recovery.swift
new file mode 100644
index 0000000..63c7094
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/ECDSA/Recovery/FFI+ECDSA+Recovery.swift
@@ -0,0 +1,122 @@
+import Foundation
+import secp256k1
+
+// MARK: Deserialize
+extension FFI.ECDSA.Recovery {
+ static let byteCount = FFI.ECDSA.NonRecovery.byteCount + 1
+
+ static func deserialize(
+ rawRepresentation: some DataProtocol
+ ) throws -> Wrapped {
+ try Wrapped(raw: Raw.recoverableSignature(rawRepresentation))
+ }
+
+ /// Compact aka `IEEE P1363` aka `R||S`.
+ static func deserialize(
+ compact rs: [UInt8],
+ recoveryID recid: Int32
+ ) throws -> Wrapped {
+ var raw = Wrapped.Raw()
+ try FFI.call(
+ ifFailThrow: .failedSignatureToConvertRecoverableSignatureToCompact
+ ) { context in
+ secp256k1_ecdsa_recoverable_signature_parse_compact(
+ context,
+ &raw,
+ rs,
+ recid
+ )
+ }
+ return .init(raw: raw)
+ }
+}
+
+
+// MARK: Serialize
+extension FFI.ECDSA.Recovery {
+ static func serializeCompact(
+ _ wrapped: Wrapped
+ ) throws -> (rs: [UInt8], recoveryID: Int32) {
+ var rs = [UInt8](repeating: 0, count: FFI.ECDSA.NonRecovery.byteCount)
+ var recoveryID: Int32 = 0
+ var rawSignature = wrapped.raw
+ try FFI.call(
+ ifFailThrow: .failedSignatureToConvertRecoverableSignatureToCompact
+ ) { context in
+ secp256k1_ecdsa_recoverable_signature_serialize_compact(
+ context,
+ &rs,
+ &recoveryID,
+ &rawSignature
+ )
+ }
+ return (rs, recoveryID)
+ }
+}
+
+// MARK: Convert
+extension FFI.ECDSA.Recovery {
+ static func nonRecoverable(
+ _ wrapped: Wrapped
+ ) throws -> FFI.ECDSA.NonRecovery.Wrapped {
+
+
+ var nonRecoverable = secp256k1_ecdsa_signature()
+ var recoverable = wrapped.raw
+
+ try FFI.call(
+ ifFailThrow: .failedToConvertRecoverableSignatureToNonRecoverable
+ ) { context in
+ secp256k1_ecdsa_recoverable_signature_convert(
+ context,
+ &nonRecoverable,
+ &recoverable
+ )
+ }
+
+ return .init(raw: nonRecoverable)
+ }
+}
+
+// MARK: Recover
+extension FFI.ECDSA.Recovery {
+ static func recover(
+ _ wrapped: Wrapped,
+ message: [UInt8]
+ ) throws -> FFI.PublicKey.Wrapped {
+ guard message.count == Curve.Field.byteCount else {
+ throw K1.Error.unableToRecoverMessageHasInvalidLength(got: message.count, expected: Curve.Field.byteCount)
+ }
+ var rawSignature = wrapped.raw
+ var rawPublicKey = secp256k1_pubkey()
+ try FFI.call(
+ ifFailThrow: .failedToRecoverPublicKey
+ ) { context in
+ secp256k1_ecdsa_recover(
+ context,
+ &rawPublicKey,
+ &rawSignature,
+ message
+ )
+ }
+ return FFI.PublicKey.Wrapped(raw: rawPublicKey)
+ }
+}
+
+// MARK: Sign
+extension FFI.ECDSA.Recovery {
+
+ /// Produces a **recoverable** ECDSA signature from a hashed `message`
+ static func sign(
+ hashedMessage: [UInt8],
+ privateKey: K1.PrivateKeyImpl.Wrapped,
+ options: K1.ECDSA.SigningOptions = .default
+ ) throws -> FFI.ECDSA.Recovery.Wrapped {
+
+ try FFI.ECDSA._sign(
+ message: hashedMessage,
+ privateKey: privateKey,
+ options: options
+ )
+ }
+}
diff --git a/Sources/K1/Support/FFI/API/Keys/PrivateKey/PrivateKey+Wrapped.swift b/Sources/K1/Support/FFI/API/Keys/PrivateKey/PrivateKey+Wrapped.swift
new file mode 100644
index 0000000..192073a
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/Keys/PrivateKey/PrivateKey+Wrapped.swift
@@ -0,0 +1,91 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-18.
+//
+
+import Foundation
+import secp256k1
+
+extension FFI {
+ enum PrivateKey {
+ struct Wrapped: @unchecked Sendable {
+
+ let publicKey: FFI.PublicKey.Wrapped
+ internal let secureBytes: SecureBytes
+
+ fileprivate init(secureBytes: SecureBytes) throws {
+ guard secureBytes.count == Curve.Field.byteCount else {
+ throw K1.Error.failedToInitializePrivateKeyIncorrectByteCount(
+ got: secureBytes.count,
+ expected: Curve.Field.byteCount
+ )
+ }
+
+ if secureBytes.allSatisfy({ $0 == .zero }) {
+ throw K1.Error.invalidPrivateKeyMustNotBeZero
+ }
+
+ self.secureBytes = secureBytes
+ var secureBytes = secureBytes
+ self.publicKey = try secureBytes.withUnsafeMutableBytes { seckey in
+ var raw = secp256k1_pubkey()
+
+ try FFI.call(ifFailThrow: .invalidPrivateKeyMustBeSmallerThanOrder) { context in
+ secp256k1_ec_pubkey_create(context, &raw, seckey.baseAddress!)
+ }
+
+ return FFI.PublicKey.Wrapped(raw: raw)
+ }
+ }
+ }
+ }
+}
+
+// MARK: Init
+extension FFI.PrivateKey.Wrapped {
+
+ fileprivate init(bytes: [UInt8]) throws {
+ try self.init(secureBytes: .init(bytes: bytes))
+ }
+
+ init() {
+ func generateNew() -> SecureBytes {
+ var attempt = 0
+
+ while attempt < 100 {
+ defer { attempt += 1 }
+ do {
+ let secureBytes = SecureBytes(count: Curve.Field.byteCount)
+ let _ = try FFI.PrivateKey.Wrapped(secureBytes: secureBytes)
+ return secureBytes
+ } catch {
+ // Failure (due to unlikely scenario that the private key scalar > order of the curve) => retry
+ }
+ }
+
+ // Probability of this happening is:
+ // n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
+ // (n / 2^256) ^ 100 = lim 0
+ // I.e. will not happen.
+ fatalError(
+ """
+ Failed to generate private key after #\(attempt) attempts.
+ You are the most unlucky person in the universe.
+ Or by Occam's razor: the person writing this code made some error.
+ """
+ )
+ }
+ try! self.init(secureBytes: generateNew())
+ }
+}
+
+// MARK: Deserialize
+extension FFI.PrivateKey {
+ static func deserialize(
+ rawRepresentation: some ContiguousBytes
+ ) throws -> Wrapped {
+ try Wrapped(bytes: rawRepresentation.bytes)
+ }
+}
diff --git a/Sources/K1/Support/FFI/API/Keys/PublicKey/FFI+PublicKey.swift b/Sources/K1/Support/FFI/API/Keys/PublicKey/FFI+PublicKey.swift
new file mode 100644
index 0000000..2ff6230
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/Keys/PublicKey/FFI+PublicKey.swift
@@ -0,0 +1,109 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-20.
+//
+
+import Foundation
+import secp256k1
+
+// MARK: Deserialize
+extension FFI.PublicKey {
+
+ /// `04 || X || Y` (65 bytes)
+ public static let x963ByteCount = 1 + (2 * Curve.Field.byteCount)
+
+ /// `X || Y` (64 bytes)
+ public static let rawByteCount = 2 * Curve.Field.byteCount
+
+
+ /// `02|03 || X` (33 bytes)
+ public static let compressedByteCount = 1 + Curve.Field.byteCount
+
+ /// `04 || X || Y` (65 bytes)
+ static func deserialize(
+ x963Representation contiguousBytes: some ContiguousBytes
+ ) throws -> Wrapped {
+ try contiguousBytes.withUnsafeBytes { bufferPointer throws -> Wrapped in
+ let expected = Self.x963ByteCount
+ guard bufferPointer.count == expected else {
+ throw K1.Error.incorrectByteCountOfX963PublicKey(got: bufferPointer.count, expected: expected)
+ }
+ return try Self._deserialize(bytes: [UInt8](bufferPointer))
+ }
+ }
+
+ /// `X || Y` (64 bytes)
+ static func deserialize(
+ rawRepresentation contiguousBytes: some ContiguousBytes
+ ) throws -> Wrapped {
+ try contiguousBytes.withUnsafeBytes { bufferPointer throws -> Wrapped in
+ let expected = Self.rawByteCount
+ guard bufferPointer.count == expected else {
+ throw K1.Error.incorrectByteCountOfRawPublicKey(got: bufferPointer.count, expected: expected)
+ }
+ // We can simply prepend `04` and parse as x963Representation
+ do {
+ return try Self.deserialize(x963Representation: [0x04] + [UInt8](bufferPointer))
+ } catch {
+ throw K1.Error.unableToDeserializePublicKeyFromRawRepresentation
+ }
+ }
+ }
+
+ /// `02|03 || X` (33 bytes)
+ static func deserialize(
+ compressedRepresentation contiguousBytes: some ContiguousBytes
+ ) throws -> Wrapped {
+
+ try contiguousBytes.withUnsafeBytes { bufferPointer throws -> Wrapped in
+ let expected = Self.compressedByteCount
+ guard bufferPointer.count == expected else {
+ throw K1.Error.incorrectByteCountOfCompressedPublicKey(got: bufferPointer.count, expected: expected)
+ }
+ return try Self._deserialize(bytes: [UInt8](bufferPointer))
+ }
+
+ }
+
+ private static func _deserialize(bytes: [UInt8]) throws -> Wrapped {
+ var raw = secp256k1_pubkey()
+ try FFI.call(
+ ifFailThrow: .failedToDeserializePublicKey
+ ) { context in
+ secp256k1_ec_pubkey_parse(
+ context,
+ &raw,
+ bytes,
+ bytes.count
+ )
+
+ }
+ return .init(raw: raw)
+ }
+}
+
+// MARK: Serialize
+extension FFI.PublicKey {
+
+ static func serialize(
+ _ wrapped: Wrapped,
+ format: K1.Format
+ ) throws -> Data {
+ var byteCount = format.length
+ var out = [UInt8](repeating: 0x00, count: byteCount)
+ var publicKeyRaw = wrapped.raw
+ try FFI.call(ifFailThrow: .failedToSerializePublicKey) { context in
+ secp256k1_ec_pubkey_serialize(
+ context,
+ &out,
+ &byteCount,
+ &publicKeyRaw,
+ format.rawValue
+ )
+ }
+ return Data(out.prefix(byteCount))
+ }
+
+}
diff --git a/Sources/K1/Support/FFI/API/Keys/PublicKey/PublicKey+Wrapped.swift b/Sources/K1/Support/FFI/API/Keys/PublicKey/PublicKey+Wrapped.swift
new file mode 100644
index 0000000..cb90a45
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/Keys/PublicKey/PublicKey+Wrapped.swift
@@ -0,0 +1,50 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+import secp256k1
+
+extension FFI {
+ enum PublicKey {}
+}
+
+// MARK: PublicKey Wrapped
+extension FFI.PublicKey {
+
+ struct Wrapped: @unchecked Sendable, ContiguousBytes {
+ typealias Raw = secp256k1_pubkey
+ let raw: Raw
+ init(raw: Raw) {
+ self.raw = raw
+ }
+ }
+}
+
+// MARK: ContiguousBytes
+extension FFI.PublicKey.Wrapped {
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ var rawData = raw.data
+ return try Swift.withUnsafeBytes(of: &rawData) { pointer in
+ try body(pointer)
+ }
+ }
+}
+
+// MARK: Comparison
+extension FFI.PublicKey.Wrapped {
+ func compare(to other: FFI.PublicKey.Wrapped) throws -> Bool {
+ var selfRaw = self.raw
+ var otherRaw = other.raw
+ return try FFI.toC { ffi in
+ ffi.callWithResultCode { context in
+ secp256k1_ec_pubkey_cmp(context, &selfRaw, &otherRaw)
+ } == 0
+ }
+
+ }
+}
+
diff --git a/Sources/K1/Support/FFI/API/Schnorr/FFI+Schnorr.swift b/Sources/K1/Support/FFI/API/Schnorr/FFI+Schnorr.swift
new file mode 100644
index 0000000..9f4fcd4
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/Schnorr/FFI+Schnorr.swift
@@ -0,0 +1,97 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+import secp256k1
+
+extension FFI {
+ enum Schnorr {}
+}
+
+// MARK: Schnorr Validate
+extension FFI.Schnorr {
+
+ static func isValid(
+ signature: FFI.Schnorr.Wrapped,
+ publicKey: FFI.PublicKey.Wrapped,
+ message: [UInt8]
+ ) throws -> Bool {
+
+ try FFI.toC { ffi -> Bool in
+ var publicKeyX = secp256k1_xonly_pubkey()
+ var publicKeyRaw = publicKey.raw
+ try FFI.call(ifFailThrow: .failedToSchnorrVerifyGettingXFromPubKey) { context in
+ secp256k1_xonly_pubkey_from_pubkey(
+ context,
+ &publicKeyX,
+ nil,
+ &publicKeyRaw
+ )
+ }
+
+ return ffi.validate { context in
+ secp256k1_schnorrsig_verify(
+ context,
+ signature.bytes,
+ message,
+ message.count,
+ &publicKeyX
+ )
+ }
+ }
+ }
+}
+
+// MARK: Schnorr Sign
+extension FFI.Schnorr {
+
+ static func sign(
+ hashedMessage message: [UInt8],
+ privateKey: FFI.PrivateKey.Wrapped,
+ options: K1.Schnorr.SigningOptions = .default
+ ) throws -> FFI.Schnorr.Wrapped {
+
+ guard
+ message.count == Curve.Field.byteCount
+ else {
+ throw K1.Error.unableToSignMessageHasInvalidLength(
+ got: message.count,
+ expected: Curve.Field.byteCount
+ )
+ }
+
+ var signatureOut = [UInt8](repeating: 0, count: FFI.Schnorr.Wrapped.byteCount)
+
+ var keyPair = secp256k1_keypair()
+
+ try FFI.call(
+ ifFailThrow: .failedToInitializeKeyPairForSchnorrSigning
+ ) { context in
+ secp256k1_keypair_create(
+ context,
+ &keyPair,
+ privateKey.secureBytes.backing.bytes
+ )
+ }
+
+
+ try FFI.call(
+ ifFailThrow: .failedToSchnorrSignDigest
+ ) { context in
+ secp256k1_schnorrsig_sign32(
+ context,
+ &signatureOut,
+ message,
+ &keyPair,
+ options.auxilaryRandomData?.aux
+ )
+ }
+
+ return try FFI.Schnorr.Wrapped(bytes: signatureOut)
+ }
+
+}
diff --git a/Sources/K1/Support/FFI/API/Schnorr/Schnorr+Wrapped.swift b/Sources/K1/Support/FFI/API/Schnorr/Schnorr+Wrapped.swift
new file mode 100644
index 0000000..0967bed
--- /dev/null
+++ b/Sources/K1/Support/FFI/API/Schnorr/Schnorr+Wrapped.swift
@@ -0,0 +1,47 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-20.
+//
+
+import Foundation
+
+extension FFI.Schnorr {
+ struct Wrapped: @unchecked Sendable, Hashable {
+ static let byteCount = 2 * Curve.Field.byteCount
+
+ internal let bytes: [UInt8]
+
+ init(bytes: [UInt8]) throws {
+ guard bytes.count == Self.byteCount else {
+ throw K1.Error.failedToInitSchnorrSignatureInvalidByteCount(
+ got: bytes.count,
+ expected: Self.byteCount
+ )
+ }
+ self.bytes = bytes
+ }
+ }
+}
+
+// MARK: Serialization
+extension FFI.Schnorr.Wrapped {
+ var rawRepresentation: Data {
+ Data(bytes)
+ }
+}
+
+// MARK: Equatable
+extension FFI.Schnorr.Wrapped {
+ static func == (lhs: FFI.Schnorr.Wrapped, rhs: FFI.Schnorr.Wrapped) -> Bool {
+ lhs.bytes == rhs.bytes
+ }
+}
+
+// MARK: Hashable
+extension FFI.Schnorr.Wrapped {
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(bytes)
+ }
+}
diff --git a/Sources/K1/Bridge/BridgeToC.swift b/Sources/K1/Support/FFI/Internals/FFI+Call.swift
similarity index 78%
rename from Sources/K1/Bridge/BridgeToC.swift
rename to Sources/K1/Support/FFI/Internals/FFI+Call.swift
index 0a28e6a..44ef514 100644
--- a/Sources/K1/Bridge/BridgeToC.swift
+++ b/Sources/K1/Support/FFI/Internals/FFI+Call.swift
@@ -9,13 +9,13 @@
import CryptoKit
import secp256k1
-internal final class Bridge {
+final class FFI {
let context: OpaquePointer
init() throws {
guard
/* "Create a secp256k1 context object." */
- let context = secp256k1_context_create(K1.Context.sign.rawValue | K1.Context.verify.rawValue)
+ let context = secp256k1_context_create(Context.sign.rawValue | Context.verify.rawValue)
else {
throw K1.Error.failedToCreateContextForSecp256k1
}
@@ -29,13 +29,13 @@ internal final class Bridge {
}
}
-extension Bridge {
+extension FFI {
static func toC(
- _ closure: (Bridge) throws -> T
+ _ closure: (FFI) throws -> T
) throws -> T {
- let bridge = try Bridge()
- return try closure(bridge)
+ let ffi = try FFI()
+ return try closure(ffi)
}
/// Returns `true` iff result code is `1`
@@ -67,8 +67,8 @@ extension Bridge {
ifFailThrow error: K1.Error,
_ method: (OpaquePointer) -> Int32
) throws {
- try toC { bridge in
- try bridge.call(ifFailThrow: error, method)
+ try toC { ffi in
+ try ffi.call(ifFailThrow: error, method)
}
}
}
diff --git a/Sources/K1/K1/K1/K1+Bridge+To+C.swift b/Sources/K1/Support/FFI/Internals/FFI+Context.swift
similarity index 55%
rename from Sources/K1/K1/K1/K1+Bridge+To+C.swift
rename to Sources/K1/Support/FFI/Internals/FFI+Context.swift
index d91312e..980c9bf 100644
--- a/Sources/K1/K1/K1/K1+Bridge+To+C.swift
+++ b/Sources/K1/Support/FFI/Internals/FFI+Context.swift
@@ -5,12 +5,24 @@
// Created by Alexander Cyon on 2022-01-27.
//
-
-// Bridge to C
+// FFI to C
import secp256k1
+extension FFI {
+
+ /// Bridging type used for underlying libsecp256k1 methods:
+ /// - `secp256k1_context_create`
+ /// - `secp256k1_context_preallocated_size`
+ /// - `secp256k1_context_preallocated_create`
+ enum Context: UInt32 {
+ case none, sign, verify
+ }
+
+}
+
+
// MARK: Context
-extension K1.Context {
+extension FFI.Context {
/// Bridging value used by libsecp256k1 methods that requires info about
/// how the context is used, e.g. for signing or verification (validate).
@@ -26,19 +38,3 @@ extension K1.Context {
return UInt32(value)
}
}
-
-// MARK: Format
-public extension K1.Format {
-
- /// Bridging value used by libsecp256k1 public key specifying the format
- /// of the imported public key, i.e. how many bytes.
- var rawValue: UInt32 {
- let value: Int32
- switch self {
- case .compressed: value = SECP256K1_EC_COMPRESSED
- case .uncompressed: value = SECP256K1_EC_UNCOMPRESSED
- }
-
- return UInt32(value)
- }
-}
diff --git a/Sources/K1/Support/FFI/Internals/FFI+Format.swift b/Sources/K1/Support/FFI/Internals/FFI+Format.swift
new file mode 100644
index 0000000..7634cd5
--- /dev/null
+++ b/Sources/K1/Support/FFI/Internals/FFI+Format.swift
@@ -0,0 +1,53 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-17.
+//
+
+import Foundation
+import secp256k1
+
+// MARK: Format
+extension K1.Format {
+
+ /// Bridging value used by libsecp256k1 key specifying the format
+ /// of the imported key, i.e. how many bytes.
+ public var rawValue: UInt32 {
+ let value: Int32
+ switch self {
+ case .compressed: value = SECP256K1_EC_COMPRESSED
+ case .uncompressed: value = SECP256K1_EC_UNCOMPRESSED
+ }
+
+ return UInt32(value)
+ }
+}
+
+extension K1 {
+
+ // Bridging type for: `secp256k1_ec_pubkey_serialize`
+ public enum Format: UInt32, CaseIterable {
+ case compressed, uncompressed
+ }
+}
+
+extension K1.Format {
+
+ var length: Int {
+ switch self {
+ case .compressed: return 33
+ case .uncompressed: return 65
+ }
+ }
+
+ internal init(byteCount: Int) throws {
+ if byteCount == Self.uncompressed.length {
+ self = .uncompressed
+ } else if byteCount == Self.compressed.length {
+ self = .compressed
+ } else {
+ fatalError("invalid byte count: \(byteCount)")
+ }
+ }
+}
diff --git a/Sources/K1/Support/FFI/Internals/FFI+Raw.swift b/Sources/K1/Support/FFI/Internals/FFI+Raw.swift
new file mode 100644
index 0000000..d329378
--- /dev/null
+++ b/Sources/K1/Support/FFI/Internals/FFI+Raw.swift
@@ -0,0 +1,70 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-17.
+//
+
+import Foundation
+import secp256k1
+
+enum Raw {}
+
+extension Raw {
+
+ static func recoverableSignature(
+ _ rawRepresentation: some DataProtocol
+ ) throws -> secp256k1_ecdsa_recoverable_signature {
+ let expected = K1.ECDSA.Recoverable.Signature.Compact.byteCount
+ guard
+ rawRepresentation.count == expected
+ else {
+ throw K1.Error.incorrectByteCountOfRawRecoverableSignature(
+ got: rawRepresentation.count,
+ expected: expected
+ )
+ }
+ var raw = secp256k1_ecdsa_recoverable_signature()
+ withUnsafeMutableBytes(of: &raw.data) { pointer in
+ pointer.copyBytes(
+ from: rawRepresentation.prefix(pointer.count)
+ )
+ }
+ return raw
+ }
+
+ static func nonRecoverableSignature(
+ compactBytes: [UInt8]
+ ) throws -> secp256k1_ecdsa_signature {
+
+ var raw = secp256k1_ecdsa_signature()
+
+ try FFI.call(ifFailThrow: .failedToParseNonRecoverableSignatureFromCompactRepresentation) { context in
+ secp256k1_ecdsa_signature_parse_compact(
+ context,
+ &raw,
+ compactBytes
+ )
+ }
+
+ return raw
+ }
+
+ static func nonRecoverableSignature(
+ derBytes: [UInt8]
+ ) throws -> secp256k1_ecdsa_signature {
+
+ var raw = secp256k1_ecdsa_signature()
+
+ try FFI.call(ifFailThrow: .failedToParseNonRecoverableSignatureFromCompactRepresentation) { context in
+ secp256k1_ecdsa_signature_parse_der(
+ context,
+ &raw,
+ derBytes,
+ derBytes.count
+ )
+ }
+
+ return raw
+ }
+}
diff --git a/Sources/K1/Support/Misc/Curve.swift b/Sources/K1/Support/Misc/Curve.swift
new file mode 100644
index 0000000..ac2bac9
--- /dev/null
+++ b/Sources/K1/Support/Misc/Curve.swift
@@ -0,0 +1,24 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+
+// MARK: - Curve
+
+/// Details about the elliptic curve `secp256k1`.
+enum Curve {}
+
+// MARK: - FiniteField
+extension Curve {
+ /// The finite field of the secp256k1 curve.
+ enum Field {}
+}
+
+extension Curve.Field {
+ /// Finite field members are 256 bits large, i.e. 32 bytes.
+ static let byteCount = 32
+}
diff --git a/Sources/K1/Support/Misc/K1+Error.swift b/Sources/K1/Support/Misc/K1+Error.swift
new file mode 100644
index 0000000..d2ca959
--- /dev/null
+++ b/Sources/K1/Support/Misc/K1+Error.swift
@@ -0,0 +1,57 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-17.
+//
+
+import Foundation
+
+extension K1 {
+ enum Error: Sendable, Swift.Error, Hashable {
+ case invalidPEMDocument
+ case invalidPrivateKeyMustNotBeZero
+ case invalidPrivateKeyMustBeSmallerThanOrder
+ case failedToProduceSharedSecret
+ case failedToComputePublicKeyFromPrivateKey
+ case failedToPerformDiffieHellmanKeyExchange
+ case failedToInitializePrivateKeyIncorrectByteCount(got: Int, expected: Int)
+ case failedToCreateContextForSecp256k1
+ case failedToDeserializePublicKey
+ case failedToSerializePublicKey
+ case failedToSchnorrVerifyGettingXFromPubKey
+ case incorrectByteCountOfX963PrivateKey(got: Int, expected: Int)
+
+ /// The public key encoded in the x963 privateKey representation does not match the derived public key from the private key.
+ case invalidPrivateX963RepresentationPublicKeyDiscrepancy
+
+ case incorrectByteCountOfX963PublicKey(got: Int, expected: Int)
+ case incorrectByteCountOfRawPublicKey(got: Int, expected: Int)
+ case unableToDeserializePublicKeyFromRawRepresentation
+ case incorrectByteCountOfCompressedPublicKey(got: Int, expected: Int)
+ case failedSignatureToConvertRecoverableSignatureToCompact
+ case failedToConvertRecoverableSignatureToNonRecoverable
+ case failedToRecoverPublicKey
+ case unableToSignMessageHasInvalidLength(got: Int, expected: Int)
+ case failedToInitializeKeyPairForSchnorrSigning
+ case failedToSchnorrSignDigestProvidedRandomnessInvalidLength
+ case failedToSchnorrSignDigest
+ case failedToInitSchnorrSignatureInvalidByteCount(got: Int, expected: Int)
+ case failedToSchnorrSignMessageInvalidLength
+ case incorrectByteCountOfArbitraryDataForNonceFunction
+ case invalidByteCount
+ case failedToECDSASignDigest
+ case unableToRecoverMessageHasInvalidLength(got: Int, expected: Int)
+ case failedToComparePublicKeys
+ case failedToSerializeDERSignature
+ case failedToSerializeSignature
+ case failedToParseRecoverableSignatureFromCompact
+ case incorrectByteCountOfRawSignature
+ case incorrectByteCountOfRawRecoverableSignature(got: Int, expected: Int)
+ case failedToParseNonRecoverableSignatureFromCompactRepresentation
+ case failedToDeserializeCompactRecoverableSignatureInvalidByteCount(got: Int, expected: Int)
+ case failedToDeserializeCompactRSRecoverableSignatureInvalidByteCount(got: Int, expected: Int)
+ case invalidRecoveryID(got: Int)
+ }
+}
+
diff --git a/Sources/K1/Support/Misc/SharedSecret.swift b/Sources/K1/Support/Misc/SharedSecret.swift
new file mode 100644
index 0000000..30c0b1f
--- /dev/null
+++ b/Sources/K1/Support/Misc/SharedSecret.swift
@@ -0,0 +1,34 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-17.
+//
+
+import Foundation
+
+// MUST match https://github.com/apple/swift-crypto/blob/main/Sources/Crypto/Key%20Agreement/DH.swift#L34
+import struct CryptoKit.SharedSecret
+
+/// A Key Agreement Result
+/// A SharedSecret has to go through a Key Derivation Function before being able to use by a symmetric key operation.
+struct __SharedSecret {
+ var ss: SecureBytes
+
+ internal init(ss: SecureBytes) {
+ self.ss = ss
+ }
+}
+
+
+extension CryptoKit.SharedSecret {
+ init(data: Data) throws {
+ let __sharedSecret = __SharedSecret(ss: .init(bytes: data))
+ let sharedSecret = unsafeBitCast(__sharedSecret, to: SharedSecret.self)
+ guard sharedSecret.withUnsafeBytes({ Data($0).count == data.count }) else {
+ throw K1.Error.failedToProduceSharedSecret
+ }
+
+ self = sharedSecret
+ }
+}
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/ASN1.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/ASN1.swift
new file mode 100644
index 0000000..b14b324
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/ASN1.swift
@@ -0,0 +1,728 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+// This module implements "just enough" ASN.1. Specifically, we implement exactly enough ASN.1 DER parsing to handle
+// the following use-cases:
+//
+// 1. Being able to parse the SPKI format for EC public keys
+// 2. Being able to parse the PKCS#8 format for EC private keys
+// 3. Being able to parse the SEC 1 format for EC private keys (produced by `openssl ec`)
+//
+// Let's talk about the DER encoding of ASN.1. DER is fundamentally a TLV (type length value) encoding. Each element is
+// made of up some bytes that identify its type, some bytes that identify the length, and then the contents. In the full
+// scheme of ASN.1 we care about a lot of things about its structure, but for our case we only care about a few kinds of
+// tag. To work out the tag we need, we can look at the X.509 representation of an EC key public key, from RFC 5480 (for case 1), as
+// well as the SEC 1 format for private keys and the PKCS#8 format for private keys.
+//
+// ### RFC 5480 SPKI:
+//
+// SubjectPublicKeyInfo ::= SEQUENCE {
+// algorithm AlgorithmIdentifier,
+// subjectPublicKey BIT STRING
+// }
+//
+// ### SEC 1
+//
+// For private keys, SEC 1 uses:
+//
+// ECPrivateKey ::= SEQUENCE {
+// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+// privateKey OCTET STRING,
+// parameters [0] EXPLICIT ECDomainParameters OPTIONAL,
+// publicKey [1] EXPLICIT BIT STRING OPTIONAL
+// }
+//
+// ### PKCS#8
+//
+// For PKCS#8 we need the following for the private key:
+//
+// PrivateKeyInfo ::= SEQUENCE {
+// version Version,
+// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+// privateKey PrivateKey,
+// attributes [0] IMPLICIT Attributes OPTIONAL }
+//
+// Version ::= INTEGER
+//
+// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+//
+// PrivateKey ::= OCTET STRING
+//
+// Attributes ::= SET OF Attribute
+//
+// ### Common
+//
+// Several of the above use formats defined here:
+//
+// AlgorithmIdentifier ::= SEQUENCE {
+// algorithm OBJECT IDENTIFIER,
+// parameters ANY DEFINED BY algorithm OPTIONAL
+// }
+//
+// ECParameters ::= CHOICE {
+// namedCurve OBJECT IDENTIFIER
+// -- implicitCurve NULL
+// -- specifiedCurve SpecifiedECDomain
+// }
+//
+// For us, we expect the ECParameters structure to be using the namedCurve representation only: as we support only the NIST curves with ASN.1
+// there is no reason for the curve to ever not be named.
+//
+// Conveniently, this requires only a few data types from us: SEQUENCE, BIT STRING, OCTET STRING, and OBJECT IDENTIFIER. All three are
+// universal objects for ASN.1. Their relevant characteristics are:
+//
+// ┌───────────────────┬────────────┬────────────────────────────────────────────────┬────────────────┬───────────┐
+// │ Name │ Tag Number │ Primitive │ Encoding Class │ Tag Bytes │
+// ├───────────────────┼────────────┼────────────────────────────────────────────────┼────────────────┼───────────┤
+// │ SEQUENCE │ 0x10 │ N │ Universal │ 0x30 │
+// │ BIT STRING │ 0x03 │ Y (we don't support constructed bit strings) │ Universal │ 0x03 │
+// │ OBJECT IDENTIFIER │ 0x06 │ Y │ Universal │ 0x06 │
+// | OCTET STRING | 0x04 | Y (we don't support constructed octet strings) | Universal | 0x04 |
+// | INTEGER | 0x02 | Y | Universal | 0x02 |
+// └───────────────────┴────────────┴────────────────────────────────────────────────┴────────────────┴───────────┘
+//
+// The subjectPublicKey is required to be in x9.62 format, either compressed or uncompressed, so we can pass it directly to the
+// initializers for CryptoKit once we've done the extraction.
+//
+// This is the complete set of things we need to be able to parse. To make our lives easier we try to parse this set of things somewhat
+// generally: that is, we don't hard-code special knowledge of these formats as part of the parsing process. Instead we have written a
+// parser that can divide the world of ASN.1 into parseable chunks, and then we try to decode specific formats from those chunks. This
+// allows us to extend things in the future without too much pain.
+internal enum ASN1 { }
+
+// MARK: - Parser Node
+extension ASN1 {
+ /// An `ASN1ParserNode` is a representation of a parsed ASN.1 TLV section. An `ASN1ParserNode` may be primitive, or may be composed of other `ASN1ParserNode`s.
+ /// In our representation, we keep track of this by storing a node "depth", which allows rapid forward and backward scans to hop over sections
+ /// we're uninterested in.
+ ///
+ /// This type is not exposed to users of the API: it is only used internally for implementation of the user-level API.
+ fileprivate struct ASN1ParserNode {
+ /// The identifier.
+ var identifier: ASN1Identifier
+
+ /// The depth of this node.
+ var depth: Int
+
+ /// The data bytes for this node, if it is primitive.
+ var dataBytes: ArraySlice?
+ }
+}
+
+extension ASN1.ASN1ParserNode: Hashable { }
+
+extension ASN1.ASN1ParserNode: CustomStringConvertible {
+ var description: String {
+ return "ASN1.ASN1ParserNode(identifier: \(self.identifier), depth: \(self.depth), dataBytes: \(self.dataBytes?.count ?? 0))"
+ }
+}
+
+// MARK: - Sequence, SequenceOf, and Set
+extension ASN1 {
+ /// Parse the node as an ASN.1 sequence.
+ internal static func sequence(_ node: ASN1Node, identifier: ASN1.ASN1Identifier, _ builder: (inout ASN1.ASN1NodeCollection.Iterator) throws -> T) throws -> T {
+ guard node.identifier == identifier, case .constructed(let nodes) = node.content else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ var iterator = nodes.makeIterator()
+
+ let result = try builder(&iterator)
+
+ guard iterator.next() == nil else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return result
+ }
+
+ internal static func sequence(of: T.Type = T.self, identifier: ASN1.ASN1Identifier, rootNode: ASN1Node) throws -> [T] {
+ guard rootNode.identifier == identifier, case .constructed(let nodes) = rootNode.content else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ return try nodes.map { try T(asn1Encoded: $0) }
+ }
+
+ internal static func sequence(of: T.Type = T.self, identifier: ASN1.ASN1Identifier, nodes: inout ASN1.ASN1NodeCollection.Iterator) throws -> [T] {
+ guard let node = nodes.next() else {
+ // Not present, throw.
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return try sequence(of: T.self, identifier: identifier, rootNode: node)
+ }
+
+ /// Parse the node as an ASN.1 set.
+ internal static func set(_ node: ASN1Node, identifier: ASN1.ASN1Identifier, _ builder: (inout ASN1.ASN1NodeCollection.Iterator) throws -> T) throws -> T {
+ // Shhhh these two are secretly the same with identifier.
+ return try sequence(node, identifier: identifier, builder)
+ }
+}
+
+// MARK: - Optional explicitly tagged
+extension ASN1 {
+ /// Parses an optional explicitly tagged element. Throws on a tag mismatch, returns nil if the element simply isn't there.
+ ///
+ /// Expects to be used with the `ASN1.sequence` helper function.
+ internal static func optionalExplicitlyTagged(_ nodes: inout ASN1.ASN1NodeCollection.Iterator, tagNumber: Int, tagClass: ASN1.ASN1Identifier.TagClass, _ builder: (ASN1Node) throws -> T) throws -> T? {
+ var localNodesCopy = nodes
+ guard let node = localNodesCopy.next() else {
+ // Node not present, return nil.
+ return nil
+ }
+
+ let expectedNodeID = ASN1.ASN1Identifier(explicitTagWithNumber: tagNumber, tagClass: tagClass)
+ assert(expectedNodeID.constructed)
+ guard node.identifier == expectedNodeID else {
+ // Node is a mismatch, with the wrong tag. Our optional isn't present.
+ return nil
+ }
+
+ // We have the right optional, so let's consume it.
+ nodes = localNodesCopy
+
+ // We expect a single child.
+ guard case .constructed(let nodes) = node.content else {
+ // This error is an internal parser error: the tag above is always constructed.
+ preconditionFailure("Explicit tags are always constructed")
+ }
+
+ var nodeIterator = nodes.makeIterator()
+ guard let child = nodeIterator.next(), nodeIterator.next() == nil else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return try builder(child)
+ }
+}
+
+// MARK: - DEFAULT
+extension ASN1 {
+ /// Parses a value that is encoded with a DEFAULT. Such a value is optional, and if absent will
+ /// be replaced with its default.
+ ///
+ /// Expects to be used with the `ASN1.sequence` helper function.
+ internal static func decodeDefault(_ nodes: inout ASN1.ASN1NodeCollection.Iterator, identifier: ASN1.ASN1Identifier, defaultValue: T, _ builder: (ASN1Node) throws -> T) throws -> T {
+ // A weird trick here: we only want to consume the next node _if_ it has the right tag. To achieve that,
+ // we work on a copy.
+ var localNodesCopy = nodes
+ guard let node = localNodesCopy.next() else {
+ // Whoops, nothing here.
+ return defaultValue
+ }
+
+ guard node.identifier == identifier else {
+ // Node is a mismatch, with the wrong identifier. Our optional isn't present.
+ return defaultValue
+ }
+
+ // We have the right optional, so let's consume it.
+ nodes = localNodesCopy
+ let parsed = try builder(node)
+
+ // DER forbids encoding DEFAULT values at their default state.
+ // We can lift this in BER.
+ guard parsed != defaultValue else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return parsed
+ }
+
+ internal static func decodeDefaultExplicitlyTagged(_ nodes: inout ASN1.ASN1NodeCollection.Iterator, tagNumber: Int, tagClass: ASN1.ASN1Identifier.TagClass, defaultValue: T, _ builder: (ASN1Node) throws -> T) throws -> T {
+ if let result = try optionalExplicitlyTagged(&nodes, tagNumber: tagNumber, tagClass: tagClass, builder) {
+ guard result != defaultValue else {
+ // DER forbids encoding DEFAULT values at their default state.
+ // We can lift this in BER.
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return result
+ } else {
+ return defaultValue
+ }
+ }
+}
+
+// MARK: - Parsing
+extension ASN1 {
+ /// A parsed representation of ASN.1.
+ fileprivate struct ASN1ParseResult {
+ private static let maximumNodeDepth = 10
+
+ var nodes: ArraySlice
+
+ private init(_ nodes: ArraySlice) {
+ self.nodes = nodes
+ }
+
+ fileprivate static func parse(_ data: ArraySlice) throws -> ASN1ParseResult {
+ var data = data
+ var nodes = [ASN1ParserNode]()
+ nodes.reserveCapacity(16)
+
+ try parseNode(from: &data, depth: 1, into: &nodes)
+ guard data.count == 0 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+ return ASN1ParseResult(nodes[...])
+ }
+
+ /// Parses a single ASN.1 node from the data and appends it to the buffer. This may recursively
+ /// call itself when there are child nodes for constructed nodes.
+ private static func parseNode(from data: inout ArraySlice, depth: Int, into nodes: inout [ASN1ParserNode]) throws {
+ guard depth <= ASN1.ASN1ParseResult.maximumNodeDepth else {
+ // We defend ourselves against stack overflow by refusing to allocate more than 10 stack frames to
+ // the parsing.
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ guard let rawIdentifier = data.popFirst() else {
+ throw CryptoKitASN1Error.truncatedASN1Field
+ }
+
+ let identifier = try ASN1Identifier(rawIdentifier: rawIdentifier)
+ guard let wideLength = try data.readASN1Length() else {
+ throw CryptoKitASN1Error.truncatedASN1Field
+ }
+
+ // UInt is sometimes too large for us!
+ guard let length = Int(exactly: wideLength) else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ var subData = data.prefix(length)
+ data = data.dropFirst(length)
+
+ guard subData.count == length else {
+ throw CryptoKitASN1Error.truncatedASN1Field
+ }
+
+ if identifier.constructed {
+ nodes.append(ASN1ParserNode(identifier: identifier, depth: depth, dataBytes: nil))
+ while subData.count > 0 {
+ try parseNode(from: &subData, depth: depth + 1, into: &nodes)
+ }
+ } else {
+ nodes.append(ASN1ParserNode(identifier: identifier, depth: depth, dataBytes: subData))
+ }
+ }
+ }
+}
+
+extension ASN1.ASN1ParseResult: Hashable { }
+
+extension ASN1 {
+ static func parse(_ data: [UInt8]) throws -> ASN1Node {
+ return try parse(data[...])
+ }
+
+ static func parse(_ data: ArraySlice) throws -> ASN1Node {
+ var result = try ASN1ParseResult.parse(data)
+
+ // There will always be at least one node if the above didn't throw, so we can safely just removeFirst here.
+ let firstNode = result.nodes.removeFirst()
+
+ let rootNode: ASN1Node
+ if firstNode.identifier.constructed {
+ // We need to feed it the next set of nodes.
+ let nodeCollection = result.nodes.prefix { $0.depth > firstNode.depth }
+ result.nodes = result.nodes.dropFirst(nodeCollection.count)
+ rootNode = ASN1.ASN1Node(identifier: firstNode.identifier, content: .constructed(.init(nodes: nodeCollection, depth: firstNode.depth)))
+ } else {
+ rootNode = ASN1.ASN1Node(identifier: firstNode.identifier, content: .primitive(firstNode.dataBytes!))
+ }
+
+ precondition(result.nodes.count == 0, "ASN1ParseResult unexpectedly allowed multiple root nodes")
+
+ return rootNode
+ }
+}
+
+// MARK: - ASN1NodeCollection
+extension ASN1 {
+ /// Represents a collection of ASN.1 nodes contained in a constructed ASN.1 node.
+ ///
+ /// Constructed ASN.1 nodes are made up of multiple child nodes. This object represents the collection of those child nodes.
+ /// It allows us to lazily construct the child nodes, potentially skipping over them when we don't care about them.
+ internal struct ASN1NodeCollection {
+ private var nodes: ArraySlice
+
+ private var depth: Int
+
+ fileprivate init(nodes: ArraySlice, depth: Int) {
+ self.nodes = nodes
+ self.depth = depth
+
+ precondition(self.nodes.allSatisfy({ $0.depth > depth }))
+ if let firstDepth = self.nodes.first?.depth {
+ precondition(firstDepth == depth + 1)
+ }
+ }
+ }
+}
+
+extension ASN1.ASN1NodeCollection: Sequence {
+ struct Iterator: IteratorProtocol {
+ private var nodes: ArraySlice
+ private var depth: Int
+
+ fileprivate init(nodes: ArraySlice, depth: Int) {
+ self.nodes = nodes
+ self.depth = depth
+ }
+
+ mutating func next() -> ASN1.ASN1Node? {
+ guard let nextNode = self.nodes.popFirst() else {
+ return nil
+ }
+
+ assert(nextNode.depth == self.depth + 1)
+ if nextNode.identifier.constructed {
+ // We need to feed it the next set of nodes.
+ let nodeCollection = self.nodes.prefix { $0.depth > nextNode.depth }
+ self.nodes = self.nodes.dropFirst(nodeCollection.count)
+ return ASN1.ASN1Node(identifier: nextNode.identifier, content: .constructed(.init(nodes: nodeCollection, depth: nextNode.depth)))
+ } else {
+ // There must be data bytes here, even if they're empty.
+ return ASN1.ASN1Node(identifier: nextNode.identifier, content: .primitive(nextNode.dataBytes!))
+ }
+ }
+ }
+
+ func makeIterator() -> Iterator {
+ return Iterator(nodes: self.nodes, depth: self.depth)
+ }
+}
+
+// MARK: - ASN1Node
+extension ASN1 {
+ /// An `ASN1Node` is a single entry in the ASN.1 representation of a data structure.
+ ///
+ /// Conceptually, an ASN.1 data structure is rooted in a single node, which may itself contain zero or more
+ /// other nodes. ASN.1 nodes are either "constructed", meaning they contain other nodes, or "primitive", meaning they
+ /// store a base data type of some kind.
+ ///
+ /// In this way, ASN.1 objects tend to form a "tree", where each object is represented by a single top-level constructed
+ /// node that contains other objects and primitives, eventually reaching the bottom which is made up of primitive objects.
+ internal struct ASN1Node {
+ internal var identifier: ASN1Identifier
+
+ internal var content: Content
+ }
+}
+
+// MARK: - ASN1Node.Content
+extension ASN1.ASN1Node {
+ /// The content of a single ASN1Node.
+ enum Content {
+ case constructed(ASN1.ASN1NodeCollection)
+ case primitive(ArraySlice)
+ }
+}
+
+// MARK: - Serializing
+extension ASN1 {
+ struct Serializer {
+ var serializedBytes: [UInt8]
+
+ init() {
+ // We allocate a 1kB array because that should cover us most of the time.
+ self.serializedBytes = []
+ self.serializedBytes.reserveCapacity(1024)
+ }
+
+ /// Appends a single, non-constructed node to the content.
+ mutating func appendPrimitiveNode(identifier: ASN1.ASN1Identifier, _ contentWriter: (inout [UInt8]) throws -> Void) rethrows {
+ assert(identifier.primitive)
+ try self._appendNode(identifier: identifier) { try contentWriter(&$0.serializedBytes) }
+ }
+
+ mutating func appendConstructedNode(identifier: ASN1.ASN1Identifier, _ contentWriter: (inout Serializer) throws -> Void) rethrows {
+ assert(identifier.constructed)
+ try self._appendNode(identifier: identifier, contentWriter)
+ }
+
+ mutating func serialize(_ node: T) throws {
+ try node.serialize(into: &self)
+ }
+
+ mutating func serialize(_ node: T, explicitlyTaggedWithTagNumber tagNumber: Int, tagClass: ASN1.ASN1Identifier.TagClass) throws {
+ return try self.serialize(explicitlyTaggedWithTagNumber: tagNumber, tagClass: tagClass) { coder in
+ try coder.serialize(node)
+ }
+ }
+
+ mutating func serialize(explicitlyTaggedWithTagNumber tagNumber: Int, tagClass: ASN1.ASN1Identifier.TagClass, _ block: (inout Serializer) throws -> Void) rethrows {
+ let identifier = ASN1Identifier(explicitTagWithNumber: tagNumber, tagClass: tagClass)
+ try self.appendConstructedNode(identifier: identifier) { coder in
+ try block(&coder)
+ }
+ }
+
+ mutating func serializeSequenceOf(_ elements: Elements, identifier: ASN1.ASN1Identifier = .sequence) throws where Elements.Element: ASN1Serializable {
+ try self.appendConstructedNode(identifier: identifier) { coder in
+ for element in elements {
+ try coder.serialize(element)
+ }
+ }
+ }
+
+ mutating func serialize(_ node: ASN1.ASN1Node) {
+ let identifier = node.identifier
+ self._appendNode(identifier: identifier) { coder in
+ switch node.content {
+ case .constructed(let nodes):
+ for node in nodes {
+ coder.serialize(node)
+ }
+ case .primitive(let baseData):
+ coder.serializedBytes.append(contentsOf: baseData)
+ }
+ }
+ }
+
+ // This is the base logical function that all other append methods are built on. This one has most of the logic, and doesn't
+ // police what we expect to happen in the content writer.
+ private mutating func _appendNode(identifier: ASN1.ASN1Identifier, _ contentWriter: (inout Serializer) throws -> Void) rethrows {
+ // This is a tricky game to play. We want to write the identifier and the length, but we don't know what the
+ // length is here. To get around that, we _assume_ the length will be one byte, and let the writer write their content.
+ // If it turns out to have been longer, we recalculate how many bytes we need and shuffle them in the buffer,
+ // before updating the length. Most of the time we'll be right: occasionally we'll be wrong and have to shuffle.
+ self.serializedBytes.writeIdentifier(identifier)
+
+ // Write a zero for the length.
+ self.serializedBytes.append(0)
+
+ // Save the indices and write.
+ let originalEndIndex = self.serializedBytes.endIndex
+ let lengthIndex = self.serializedBytes.index(before: originalEndIndex)
+
+ try contentWriter(&self)
+
+ let contentLength = self.serializedBytes.distance(from: originalEndIndex, to: self.serializedBytes.endIndex)
+ let lengthBytesNeeded = contentLength.bytesNeededToEncode
+ if lengthBytesNeeded == 1 {
+ // We can just set this at the top, and we're done!
+ assert(contentLength <= 0x7F)
+ self.serializedBytes[lengthIndex] = UInt8(contentLength)
+ return
+ }
+
+ // Whoops, we need more than one byte to represent the length. That's annoying!
+ // To sort this out we want to "move" the memory to the right.
+ self.serializedBytes.moveRange(offset: lengthBytesNeeded - 1, range: originalEndIndex..> (shift * 8)))
+ }
+
+ assert(writeIndex == self.serializedBytes.index(lengthIndex, offsetBy: lengthBytesNeeded - 1))
+ }
+ }
+}
+
+// MARK: - Helpers
+internal protocol ASN1Parseable {
+ init(asn1Encoded: ASN1.ASN1Node) throws
+}
+
+extension ASN1Parseable {
+ internal init(asn1Encoded sequenceNodeIterator: inout ASN1.ASN1NodeCollection.Iterator) throws {
+ guard let node = sequenceNodeIterator.next() else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ self = try .init(asn1Encoded: node)
+ }
+
+ internal init(asn1Encoded: [UInt8]) throws {
+ self = try .init(asn1Encoded: ASN1.parse(asn1Encoded))
+ }
+
+ internal init(asn1Encoded: ArraySlice) throws {
+ self = try .init(asn1Encoded: ASN1.parse(asn1Encoded))
+ }
+}
+
+internal protocol ASN1Serializable {
+ func serialize(into coder: inout ASN1.Serializer) throws
+}
+
+/// Covers ASN.1 types that may be implicitly tagged. Not all nodes can be!
+internal protocol ASN1ImplicitlyTaggable: ASN1Parseable, ASN1Serializable {
+ /// The tag that the first node will use "by default" if the grammar omits
+ /// any more specific tag definition.
+ static var defaultIdentifier: ASN1.ASN1Identifier { get }
+
+ init(asn1Encoded: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws
+}
+
+extension ASN1ImplicitlyTaggable {
+ internal init(asn1Encoded sequenceNodeIterator: inout ASN1.ASN1NodeCollection.Iterator,
+ withIdentifier identifier: ASN1.ASN1Identifier = Self.defaultIdentifier) throws {
+ guard let node = sequenceNodeIterator.next() else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ self = try .init(asn1Encoded: node, withIdentifier: identifier)
+ }
+
+ internal init(asn1Encoded: [UInt8], withIdentifier identifier: ASN1.ASN1Identifier = Self.defaultIdentifier) throws {
+ self = try .init(asn1Encoded: ASN1.parse(asn1Encoded), withIdentifier: identifier)
+ }
+
+ internal init(asn1Encoded: ArraySlice, withIdentifier identifier: ASN1.ASN1Identifier = Self.defaultIdentifier) throws {
+ self = try .init(asn1Encoded: ASN1.parse(asn1Encoded), withIdentifier: identifier)
+ }
+
+ init(asn1Encoded: ASN1.ASN1Node) throws {
+ try self.init(asn1Encoded: asn1Encoded, withIdentifier: Self.defaultIdentifier)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer) throws {
+ try self.serialize(into: &coder, withIdentifier: Self.defaultIdentifier)
+ }
+}
+
+extension ArraySlice where Element == UInt8 {
+ fileprivate mutating func readASN1Length() throws -> UInt? {
+ guard let firstByte = self.popFirst() else {
+ return nil
+ }
+
+ switch firstByte {
+ case 0x80:
+ // Indefinite form. Unsupported.
+ throw CryptoKitASN1Error.unsupportedFieldLength
+ case let val where val & 0x80 == 0x80:
+ // Top bit is set, this is the long form. The remaining 7 bits of this octet
+ // determine how long the length field is.
+ let fieldLength = Int(val & 0x7F)
+ guard self.count >= fieldLength else {
+ return nil
+ }
+
+ // We need to read the length bytes
+ let lengthBytes = self.prefix(fieldLength)
+ self = self.dropFirst(fieldLength)
+ let length = try UInt(bigEndianBytes: lengthBytes)
+
+ // DER requires that we enforce that the length field was encoded in the minimum number of octets necessary.
+ let requiredBits = UInt.bitWidth - length.leadingZeroBitCount
+ switch requiredBits {
+ case 0...7:
+ // For 0 to 7 bits, the long form is unnacceptable and we require the short.
+ throw CryptoKitASN1Error.unsupportedFieldLength
+ case 8...:
+ // For 8 or more bits, fieldLength should be the minimum required.
+ let requiredBytes = (requiredBits + 7) / 8
+ if fieldLength > requiredBytes {
+ throw CryptoKitASN1Error.unsupportedFieldLength
+ }
+ default:
+ // This is not reachable, but we'll error anyway.
+ throw CryptoKitASN1Error.unsupportedFieldLength
+ }
+
+ return length
+ case let val:
+ // Short form, the length is only one 7-bit integer.
+ return UInt(val)
+ }
+ }
+}
+
+extension FixedWidthInteger {
+ internal init(bigEndianBytes bytes: Bytes) throws where Bytes.Element == UInt8 {
+ guard bytes.count <= (Self.bitWidth / 8) else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ self = 0
+ let shiftSizes = stride(from: 0, to: bytes.count * 8, by: 8).reversed()
+
+ var index = bytes.startIndex
+ for shift in shiftSizes {
+ self |= Self(truncatingIfNeeded: bytes[index]) << shift
+ bytes.formIndex(after: &index)
+ }
+ }
+}
+
+extension Array where Element == UInt8 {
+ fileprivate mutating func writeIdentifier(_ identifier: ASN1.ASN1Identifier) {
+ self.append(identifier.baseTag)
+ }
+
+ fileprivate mutating func moveRange(offset: Int, range: Range) {
+ // We only bothered to implement this for positive offsets for now, the algorithm
+ // generalises.
+ precondition(offset > 0)
+
+ let distanceFromEndOfRangeToEndOfSelf = self.distance(from: range.endIndex, to: self.endIndex)
+ if distanceFromEndOfRangeToEndOfSelf < offset {
+ // We begin by writing some zeroes out to the size we need.
+ for _ in 0..<(offset - distanceFromEndOfRangeToEndOfSelf) {
+ self.append(0)
+ }
+ }
+
+ // Now we walk the range backwards, moving the elements.
+ for index in range.reversed() {
+ self[index + offset] = self[index]
+ }
+ }
+}
+
+extension Int {
+ fileprivate var bytesNeededToEncode: Int {
+ // ASN.1 lengths are in two forms. If we can store the length in 7 bits, we should:
+ // that requires only one byte. Otherwise, we need multiple bytes: work out how many,
+ // plus one for the length of the length bytes.
+ if self <= 0x7F {
+ return 1
+ } else {
+ // We need to work out how many bytes we need. There are many fancy bit-twiddling
+ // ways of doing this, but honestly we don't do this enough to need them, so we'll
+ // do it the easy way. This math is done on UInt because it makes the shift semantics clean.
+ // We save a branch here because we can never overflow this addition.
+ return UInt(self).neededBytes &+ 1
+ }
+ }
+}
+
+extension FixedWidthInteger {
+ // Bytes needed to store a given integer.
+ internal var neededBytes: Int {
+ let neededBits = self.bitWidth - self.leadingZeroBitCount
+ return (neededBits + 7) / 8
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Any.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Any.swift
new file mode 100644
index 0000000..2228f24
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Any.swift
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// An ASN1 ANY represents...well, anything.
+ ///
+ /// In this case we store the ASN.1 ANY as a serialized representation. This is a bit annoying,
+ /// but it's the only safe way to manage this data, as we cannot arbitrarily parse it.
+ ///
+ /// The only things we allow users to do with ASN.1 ANYs is to try to decode them as something else,
+ /// to create them from something else, or to serialize them.
+ struct ASN1Any: ASN1Parseable, ASN1Serializable, Hashable {
+ fileprivate var serializedBytes: ArraySlice
+
+ init(erasing: ASN1Type) throws {
+ var serializer = ASN1.Serializer()
+ try erasing.serialize(into: &serializer)
+ self.serializedBytes = ArraySlice(serializer.serializedBytes)
+ }
+
+ init(erasing: ASN1Type, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ var serializer = ASN1.Serializer()
+ try erasing.serialize(into: &serializer, withIdentifier: identifier)
+ self.serializedBytes = ArraySlice(serializer.serializedBytes)
+ }
+
+ init(asn1Encoded rootNode: ASN1.ASN1Node) {
+ // This is a bit sad: we just re-serialize this data. In an ideal world
+ // we'd update the parse representation so that all nodes can point at their
+ // complete backing storage, but for now this is better.
+ var serializer = ASN1.Serializer()
+ serializer.serialize(rootNode)
+ self.serializedBytes = ArraySlice(serializer.serializedBytes)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer) throws {
+ // Dangerous to just reach in there like this, but it's the right way to serialize this.
+ coder.serializedBytes.append(contentsOf: self.serializedBytes)
+ }
+ }
+}
+
+extension ASN1Parseable {
+ init(asn1Any: ASN1.ASN1Any) throws {
+ try self.init(asn1Encoded: asn1Any.serializedBytes)
+ }
+}
+
+extension ASN1ImplicitlyTaggable {
+ init(asn1Any: ASN1.ASN1Any, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ try self.init(asn1Encoded: asn1Any.serializedBytes, withIdentifier: identifier)
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift
new file mode 100644
index 0000000..80e694d
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// A bitstring is a representation of...well...some bits.
+ struct ASN1BitString: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitiveBitString
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ guard case .primitive(let content) = node.content else {
+ preconditionFailure("ASN.1 parser generated primitive node with constructed content")
+ }
+
+ // The initial octet explains how many of the bits in the _final_ octet are not part of the bitstring.
+ // The only value we support here is 0.
+ guard content.first == 0 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ self.bytes = content.dropFirst()
+ }
+
+ init(bytes: ArraySlice) {
+ self.bytes = bytes
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ coder.appendPrimitiveNode(identifier: identifier) { bytes in
+ bytes.append(0)
+ bytes.append(contentsOf: self.bytes)
+ }
+ }
+ }
+}
+
+extension ASN1.ASN1BitString: Hashable { }
+
+extension ASN1.ASN1BitString: ContiguousBytes {
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift
new file mode 100644
index 0000000..3fd156f
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension Bool: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .boolean
+ }
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ guard case .primitive(let bytes) = node.content, bytes.count == 1 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ switch bytes[bytes.startIndex] {
+ case 0:
+ // Boolean false
+ self = false
+ case 0xff:
+ // Boolean true in DER
+ self = true
+ default:
+ // If we come to support BER then these values are all "true" as well.
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ coder.appendPrimitiveNode(identifier: identifier) { bytes in
+ if self {
+ bytes.append(0xff)
+ } else {
+ bytes.append(0)
+ }
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift
new file mode 100644
index 0000000..a350846
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift
@@ -0,0 +1,146 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// An `ASN1Identifier` is a representation of the abstract notion of an ASN.1 identifier. Identifiers have a number of properties that relate to both the specific
+ /// tag number as well as the properties of the identifier in the stream.
+ internal struct ASN1Identifier {
+ /// The base tag. In a general ASN.1 implementation we'd need an arbitrary precision integer here as the tag number can be arbitrarily large, but
+ /// we don't need the full generality here.
+ private(set) var baseTag: UInt8
+
+ /// Whether this tag is primitive.
+ var primitive: Bool {
+ return self.baseTag & 0x20 == 0
+ }
+
+ /// Whether this tag is constructed.
+ var constructed: Bool {
+ return !self.primitive
+ }
+
+ enum TagClass {
+ case universal
+ case application
+ case contextSpecific
+ case `private`
+ }
+
+ /// The class of this tag.
+ var tagClass: TagClass {
+ switch self.baseTag >> 6 {
+ case 0x00:
+ return .universal
+ case 0x01:
+ return .application
+ case 0x02:
+ return .contextSpecific
+ case 0x03:
+ return .private
+ default:
+ fatalError("Unreachable")
+ }
+ }
+
+ init(rawIdentifier: UInt8) throws {
+ // We don't support multibyte identifiers, which are signalled when the bottom 5 bits are all 1.
+ guard rawIdentifier & 0x1F != 0x1F else {
+ throw CryptoKitASN1Error.invalidFieldIdentifier
+ }
+
+ self.baseTag = rawIdentifier
+ }
+
+ init(explicitTagWithNumber number: Int, tagClass: TagClass) {
+ precondition(number >= 0)
+ precondition(number < 0x1F)
+
+ self.baseTag = UInt8(number)
+
+ switch tagClass {
+ case .universal:
+ preconditionFailure("Explicit tags may not be universal")
+ case .application:
+ self.baseTag |= 1 << 6
+ case .contextSpecific:
+ self.baseTag |= 2 << 6
+ case .private:
+ self.baseTag |= 3 << 6
+ }
+
+ // Explicit tags are always constructed.
+ self.baseTag |= 0x20
+ }
+
+ init(tagWithNumber number: Int, tagClass: TagClass, constructed: Bool) {
+ precondition(number >= 0)
+ precondition(number < 0x1F)
+
+ self.baseTag = UInt8(number)
+
+ switch tagClass {
+ case .universal:
+ preconditionFailure("Custom tags may not be universal")
+ case .application:
+ self.baseTag |= 1 << 6
+ case .contextSpecific:
+ self.baseTag |= 2 << 6
+ case .private:
+ self.baseTag |= 3 << 6
+ }
+
+ if constructed {
+ self.baseTag |= 0x20
+ }
+ }
+ }
+}
+
+extension ASN1.ASN1Identifier {
+ internal static let objectIdentifier = try! ASN1.ASN1Identifier(rawIdentifier: 0x06)
+ internal static let primitiveBitString = try! ASN1.ASN1Identifier(rawIdentifier: 0x03)
+ internal static let primitiveOctetString = try! ASN1.ASN1Identifier(rawIdentifier: 0x04)
+ internal static let integer = try! ASN1.ASN1Identifier(rawIdentifier: 0x02)
+ internal static let sequence = try! ASN1.ASN1Identifier(rawIdentifier: 0x30)
+ internal static let set = try! ASN1.ASN1Identifier(rawIdentifier: 0x31)
+ internal static let null = try! ASN1.ASN1Identifier(rawIdentifier: 0x05)
+ internal static let boolean = try! ASN1.ASN1Identifier(rawIdentifier: 0x01)
+ internal static let enumerated = try! ASN1.ASN1Identifier(rawIdentifier: 0x0a)
+ internal static let primitiveUTF8String = try! ASN1.ASN1Identifier(rawIdentifier: 0x0c)
+ internal static let primitiveNumericString = try! ASN1.ASN1Identifier(rawIdentifier: 0x12)
+ internal static let primitivePrintableString = try! ASN1.ASN1Identifier(rawIdentifier: 0x13)
+ internal static let primitiveTeletexString = try! ASN1.ASN1Identifier(rawIdentifier: 0x14)
+ internal static let primitiveVideotexString = try! ASN1.ASN1Identifier(rawIdentifier: 0x15)
+ internal static let primitiveIA5String = try! ASN1.ASN1Identifier(rawIdentifier: 0x16)
+ internal static let primitiveGraphicString = try! ASN1.ASN1Identifier(rawIdentifier: 0x19)
+ internal static let primitiveVisibleString = try! ASN1.ASN1Identifier(rawIdentifier: 0x1a)
+ internal static let primitiveGeneralString = try! ASN1.ASN1Identifier(rawIdentifier: 0x1b)
+ internal static let primitiveUniversalString = try! ASN1.ASN1Identifier(rawIdentifier: 0x1c)
+ internal static let primitiveBMPString = try! ASN1.ASN1Identifier(rawIdentifier: 0x1e)
+ internal static let generalizedTime = try! ASN1.ASN1Identifier(rawIdentifier: 0x18)
+}
+
+extension ASN1.ASN1Identifier: Hashable { }
+
+extension ASN1.ASN1Identifier: CustomStringConvertible {
+ var description: String {
+ return "ASN1Identifier(\(self.baseTag))"
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift
new file mode 100644
index 0000000..3ef9cf6
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift
@@ -0,0 +1,272 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+/// A protocol that represents any internal object that can present itself as a INTEGER, or be parsed from
+/// a INTEGER.
+///
+/// This is not a very good solution for a fully-fledged ASN.1 library: we'd rather have a better numerics
+/// protocol that could both initialize from and serialize to either bytes or words. However, no such
+/// protocol exists today.
+protocol ASN1IntegerRepresentable: ASN1ImplicitlyTaggable {
+ associatedtype IntegerBytes: RandomAccessCollection where IntegerBytes.Element == UInt8
+
+ /// Whether this type can represent signed integers. If this is set to false, the serializer and
+ /// parser will automatically handle padding with leading zero bytes as needed.
+ static var isSigned: Bool { get }
+
+ init(asn1IntegerBytes: ArraySlice) throws
+
+ func withBigEndianIntegerBytes(_ body: (IntegerBytes) throws -> ReturnType) rethrows -> ReturnType
+}
+
+extension ASN1IntegerRepresentable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .integer
+ }
+
+ internal init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ guard case .primitive(var dataBytes) = node.content else {
+ preconditionFailure("ASN.1 parser generated primitive node with constructed content")
+ }
+
+ // Zero bytes of integer is not an acceptable encoding.
+ guard dataBytes.count > 0 else {
+ throw CryptoKitASN1Error.invalidASN1IntegerEncoding
+ }
+
+ // 8.3.2 If the contents octets of an integer value encoding consist of more than one octet, then the bits of the first octet and bit 8 of the second octet:
+ //
+ // a) shall not all be ones; and
+ // b) shall not all be zero.
+ //
+ // NOTE – These rules ensure that an integer value is always encoded in the smallest possible number of octets.
+ if let first = dataBytes.first, let second = dataBytes.dropFirst().first {
+ if (first == 0xFF) && second.topBitSet ||
+ (first == 0x00) && !second.topBitSet {
+ throw CryptoKitASN1Error.invalidASN1IntegerEncoding
+ }
+ }
+
+ // If the type we're trying to decode is unsigned, and the top byte is zero, we should strip it.
+ // If the top bit is set, however, this is an invalid conversion: the number needs to be positive!
+ if !Self.isSigned, let first = dataBytes.first {
+ if first == 0x00 {
+ dataBytes = dataBytes.dropFirst()
+ } else if first & 0x80 == 0x80 {
+ throw CryptoKitASN1Error.invalidASN1IntegerEncoding
+ }
+ }
+
+ self = try Self(asn1IntegerBytes: dataBytes)
+ }
+
+ internal func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ coder.appendPrimitiveNode(identifier: identifier) { bytes in
+ self.withBigEndianIntegerBytes { integerBytes in
+ // If the number of bytes is 0, we're encoding a zero. That actually _does_ require one byte.
+ if integerBytes.count == 0 {
+ bytes.append(0)
+ return
+ }
+
+ // If self is unsigned and the first byte has the top bit set, we need to prepend a 0 byte.
+ if !Self.isSigned, let topByte = integerBytes.first, topByte.topBitSet {
+ bytes.append(0)
+ bytes.append(contentsOf: integerBytes)
+ } else {
+ // Either self is signed, or the top bit isn't set. Either way, trim to make sure the representation is minimal.
+ bytes.append(contentsOf: integerBytes.trimLeadingExcessBytes())
+ }
+ }
+ }
+ }
+}
+
+// MARK: - Auto-conformance for FixedWidthInteger with fixed width magnitude.
+extension ASN1IntegerRepresentable where Self: FixedWidthInteger {
+ init(asn1IntegerBytes bytes: ArraySlice) throws {
+ // Defer to the FixedWidthInteger constructor.
+ // There's a wrinkle here: if this is a signed integer, and the top bit of the data bytes was set,
+ // then we need to 1-extend the bytes. This is because ASN.1 tries to delete redundant bytes that
+ // are all 1.
+ self = try Self(bigEndianBytes: bytes)
+
+ if Self.isSigned, let first = bytes.first, first.topBitSet {
+ for shift in stride(from: self.bitWidth - self.leadingZeroBitCount, to: self.bitWidth, by: 8) {
+ self |= 0xFF << shift
+ }
+ }
+ }
+
+ func withBigEndianIntegerBytes(_ body: (IntegerBytesCollection) throws -> ReturnType) rethrows -> ReturnType {
+ return try body(IntegerBytesCollection(self))
+ }
+}
+
+struct IntegerBytesCollection {
+ private var integer: Integer
+
+ init(_ integer: Integer) {
+ self.integer = integer
+ }
+}
+
+extension IntegerBytesCollection: RandomAccessCollection {
+ struct Index {
+ fileprivate var byteNumber: Int
+
+ fileprivate init(byteNumber: Int) {
+ self.byteNumber = byteNumber
+ }
+
+ fileprivate var shift: Integer {
+ // As byte number 0 is the end index, the byte number is one byte too large for the shift.
+ return Integer((self.byteNumber - 1) * 8)
+ }
+ }
+
+ var startIndex: Index {
+ return Index(byteNumber: Int(self.integer.neededBytes))
+ }
+
+ var endIndex: Index {
+ return Index(byteNumber: 0)
+ }
+
+ var count: Int {
+ return Int(self.integer.neededBytes)
+ }
+
+ subscript(index: Index) -> UInt8 {
+ // We perform the bitwise operations in magnitude space.
+ let shifted = Integer.Magnitude(truncatingIfNeeded: self.integer) >> index.shift
+ let masked = shifted & 0xFF
+ return UInt8(masked)
+ }
+}
+
+extension IntegerBytesCollection.Index: Equatable { }
+
+extension IntegerBytesCollection.Index: Comparable {
+ // Comparable here is backwards to the original ordering.
+ static func <(lhs: Self, rhs: Self) -> Bool {
+ return lhs.byteNumber > rhs.byteNumber
+ }
+
+ static func >(lhs: Self, rhs: Self) -> Bool {
+ return lhs.byteNumber < rhs.byteNumber
+ }
+
+ static func <=(lhs: Self, rhs: Self) -> Bool {
+ return lhs.byteNumber >= rhs.byteNumber
+ }
+
+ static func >=(lhs: Self, rhs: Self) -> Bool {
+ return lhs.byteNumber <= rhs.byteNumber
+ }
+}
+
+extension IntegerBytesCollection.Index: Strideable {
+ func advanced(by n: Int) -> IntegerBytesCollection.Index {
+ return IntegerBytesCollection.Index(byteNumber: self.byteNumber - n)
+ }
+
+ func distance(to other: IntegerBytesCollection.Index) -> Int {
+ // Remember that early indices have high byte numbers and later indices have low ones.
+ return self.byteNumber - other.byteNumber
+ }
+}
+
+extension Int8: ASN1IntegerRepresentable { }
+
+extension UInt8: ASN1IntegerRepresentable { }
+
+extension Int16: ASN1IntegerRepresentable { }
+
+extension UInt16: ASN1IntegerRepresentable { }
+
+extension Int32: ASN1IntegerRepresentable { }
+
+extension UInt32: ASN1IntegerRepresentable { }
+
+extension Int64: ASN1IntegerRepresentable { }
+
+extension UInt64: ASN1IntegerRepresentable { }
+
+extension Int: ASN1IntegerRepresentable { }
+
+extension UInt: ASN1IntegerRepresentable { }
+
+extension RandomAccessCollection where Element == UInt8 {
+ fileprivate func trimLeadingExcessBytes() -> SubSequence {
+ var slice = self[...]
+ guard let first = slice.first else {
+ // Easy case, empty.
+ return slice
+ }
+
+ let wholeByte: UInt8
+
+ switch first {
+ case 0:
+ wholeByte = 0
+ case 0xFF:
+ wholeByte = 0xFF
+ default:
+ // We're already fine, this is maximally compact. We need the whole thing.
+ return slice
+ }
+
+ // We never trim this to less than one byte, as that's always the smallest representation.
+ while slice.count > 1 {
+ // If the first byte is equal to our original first byte, and the top bit
+ // of the next byte is also equal to that, then we need to drop the byte and
+ // go again.
+ if slice.first != wholeByte {
+ break
+ }
+
+ guard let second = slice.dropFirst().first else {
+ preconditionFailure("Loop condition violated: must be at least two bytes left")
+ }
+
+ if second & 0x80 != wholeByte & 0x80 {
+ // Different top bit, we need the leading byte.
+ break
+ }
+
+ // Both the first byte and the top bit of the next are all zero or all 1, drop the leading
+ // byte.
+ slice = slice.dropFirst()
+ }
+
+ return slice
+ }
+}
+
+extension UInt8 {
+ fileprivate var topBitSet: Bool {
+ return (self & 0x80) != 0
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Null.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Null.swift
new file mode 100644
index 0000000..0dcb788
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Null.swift
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// An ASN1 NULL represents nothing.
+ struct ASN1Null: ASN1ImplicitlyTaggable, Hashable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .null
+ }
+
+ init() { }
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier, case .primitive(let content) = node.content else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ guard content.count == 0 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) {
+ coder.appendPrimitiveNode(identifier: identifier, { _ in })
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift
new file mode 100644
index 0000000..d1a6d49
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// An octet string is a representation of a string of octets.
+ struct ASN1OctetString: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitiveOctetString
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ guard case .primitive(let content) = node.content else {
+ preconditionFailure("ASN.1 parser generated primitive node with constructed content")
+ }
+
+ self.bytes = content
+ }
+
+ init(contentBytes: ArraySlice) {
+ self.bytes = contentBytes
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ coder.appendPrimitiveNode(identifier: identifier) { bytes in
+ bytes.append(contentsOf: self.bytes)
+ }
+ }
+ }
+}
+
+extension ASN1.ASN1OctetString: Hashable { }
+
+extension ASN1.ASN1OctetString: ContiguousBytes {
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift
new file mode 100644
index 0000000..c57dc2e
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift
@@ -0,0 +1,196 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// A UTF8String is roughly what it sounds like. We note that all the string types are encoded as implicitly tagged
+ /// octet strings, and so for now we just piggyback on the decoder and encoder for that type.
+ struct ASN1UTF8String: ASN1ImplicitlyTaggable, Hashable, ContiguousBytes, ExpressibleByStringLiteral {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitiveUTF8String
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self.bytes = try ASN1OctetString(asn1Encoded: node, withIdentifier: identifier).bytes
+ }
+
+ init(contentBytes: ArraySlice) {
+ self.bytes = contentBytes
+ }
+
+ init(stringLiteral value: StringLiteralType) {
+ self.bytes = ArraySlice(value.utf8)
+ }
+
+ init(_ string: String) {
+ self.bytes = ArraySlice(string.utf8)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ let octet = ASN1OctetString(contentBytes: self.bytes)
+ try octet.serialize(into: &coder, withIdentifier: identifier)
+ }
+
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+ }
+
+ /// We note that all the string types are encoded as implicitly tagged
+ /// octet strings, and so for now we just piggyback on the decoder and encoder for that type.
+ struct ASN1TeletexString: ASN1ImplicitlyTaggable, Hashable, ContiguousBytes, ExpressibleByStringLiteral {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitiveTeletexString
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self.bytes = try ASN1OctetString(asn1Encoded: node, withIdentifier: identifier).bytes
+ }
+
+ init(contentBytes: ArraySlice) {
+ self.bytes = contentBytes
+ }
+
+ init(stringLiteral value: StringLiteralType) {
+ self.bytes = ArraySlice(value.utf8)
+ }
+
+ init(_ string: String) {
+ self.bytes = ArraySlice(string.utf8)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ let octet = ASN1OctetString(contentBytes: self.bytes)
+ try octet.serialize(into: &coder, withIdentifier: identifier)
+ }
+
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+ }
+
+ /// We note that all the string types are encoded as implicitly tagged
+ /// octet strings, and so for now we just piggyback on the decoder and encoder for that type.
+ struct ASN1PrintableString: ASN1ImplicitlyTaggable, Hashable, ContiguousBytes, ExpressibleByStringLiteral {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitivePrintableString
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self.bytes = try ASN1OctetString(asn1Encoded: node, withIdentifier: identifier).bytes
+ }
+
+ init(contentBytes: ArraySlice) {
+ self.bytes = contentBytes
+ }
+
+ init(stringLiteral value: StringLiteralType) {
+ self.bytes = ArraySlice(value.utf8)
+ }
+
+ init(_ string: String) {
+ self.bytes = ArraySlice(string.utf8)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ let octet = ASN1OctetString(contentBytes: self.bytes)
+ try octet.serialize(into: &coder, withIdentifier: identifier)
+ }
+
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+ }
+
+ /// We note that all the string types are encoded as implicitly tagged
+ /// octet strings, and so for now we just piggyback on the decoder and encoder for that type.
+ struct ASN1UniversalString: ASN1ImplicitlyTaggable, Hashable, ContiguousBytes, ExpressibleByStringLiteral {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitiveUniversalString
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self.bytes = try ASN1OctetString(asn1Encoded: node, withIdentifier: identifier).bytes
+ }
+
+ init(contentBytes: ArraySlice) {
+ self.bytes = contentBytes
+ }
+
+ init(stringLiteral value: StringLiteralType) {
+ self.bytes = ArraySlice(value.utf8)
+ }
+
+ init(_ string: String) {
+ self.bytes = ArraySlice(string.utf8)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ let octet = ASN1OctetString(contentBytes: self.bytes)
+ try octet.serialize(into: &coder, withIdentifier: identifier)
+ }
+
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+ }
+
+ /// We note that all the string types are encoded as implicitly tagged
+ /// octet strings, and so for now we just piggyback on the decoder and encoder for that type.
+ struct ASN1BMPString: ASN1ImplicitlyTaggable, Hashable, ContiguousBytes, ExpressibleByStringLiteral {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .primitiveBMPString
+ }
+
+ var bytes: ArraySlice
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self.bytes = try ASN1OctetString(asn1Encoded: node, withIdentifier: identifier).bytes
+ }
+
+ init(contentBytes: ArraySlice) {
+ self.bytes = contentBytes
+ }
+
+ init(stringLiteral value: StringLiteralType) {
+ self.bytes = ArraySlice(value.utf8)
+ }
+
+ init(_ string: String) {
+ self.bytes = ArraySlice(string.utf8)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ let octet = ASN1OctetString(contentBytes: self.bytes)
+ try octet.serialize(into: &coder, withIdentifier: identifier)
+ }
+
+ func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+ return try self.bytes.withUnsafeBytes(body)
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift
new file mode 100644
index 0000000..22cab28
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+
+// For temporary purposes we pretend that ArraySlice is our "bigint" type. We don't really need anything else.
+extension ArraySlice: ASN1Serializable where Element == UInt8 { }
+
+extension ArraySlice: ASN1Parseable where Element == UInt8 { }
+
+extension ArraySlice: ASN1ImplicitlyTaggable where Element == UInt8 { }
+
+extension ArraySlice: ASN1IntegerRepresentable where Element == UInt8 {
+ // We only use unsigned "bigint"s
+ static var isSigned: Bool {
+ return false
+ }
+
+ init(asn1IntegerBytes: ArraySlice) throws {
+ self = asn1IntegerBytes
+ }
+
+ func withBigEndianIntegerBytes(_ body: (ArraySlice) throws -> ReturnType) rethrows -> ReturnType {
+ return try body(self)
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift
new file mode 100644
index 0000000..cb423a4
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift
@@ -0,0 +1,384 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ struct GeneralizedTime: ASN1ImplicitlyTaggable, Hashable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .generalizedTime
+ }
+
+ var year: Int {
+ get {
+ return self._year
+ }
+ set {
+ self._year = newValue
+ try! self.validate()
+ }
+ }
+
+ var month: Int {
+ get {
+ return self._month
+ }
+ set {
+ self._month = newValue
+ try! self.validate()
+ }
+ }
+
+ var day: Int {
+ get {
+ return self._day
+ }
+ set {
+ self._day = newValue
+ try! self.validate()
+ }
+ }
+
+ var hours: Int {
+ get {
+ return self._hours
+ }
+ set {
+ self._hours = newValue
+ try! self.validate()
+ }
+ }
+
+ var minutes: Int {
+ get {
+ return self._minutes
+ }
+ set {
+ self._minutes = newValue
+ try! self.validate()
+ }
+ }
+
+ var seconds: Int {
+ get {
+ return self._seconds
+ }
+ set {
+ self._seconds = newValue
+ try! self.validate()
+ }
+ }
+
+ var fractionalSeconds: Double {
+ get {
+ return self._fractionalSeconds
+ }
+ set {
+ self._fractionalSeconds = newValue
+ try! self.validate()
+ }
+ }
+
+ private var _year: Int
+ private var _month: Int
+ private var _day: Int
+ private var _hours: Int
+ private var _minutes: Int
+ private var _seconds: Int
+ private var _fractionalSeconds: Double
+
+ init(year: Int, month: Int, day: Int, hours: Int, minutes: Int, seconds: Int, fractionalSeconds: Double) throws {
+ self._year = year
+ self._month = month
+ self._day = day
+ self._hours = hours
+ self._minutes = minutes
+ self._seconds = seconds
+ self._fractionalSeconds = fractionalSeconds
+
+ try self.validate()
+ }
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ guard case .primitive(let content) = node.content else {
+ preconditionFailure("ASN.1 parser generated primitive node with constructed content")
+ }
+
+ self = try .parseDateBytes(content)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ coder.appendPrimitiveNode(identifier: identifier) { bytes in
+ bytes.append(self)
+ }
+ }
+
+ private func validate() throws {
+ // Validate that the structure is well-formed.
+ guard self._year >= 0 && self._year <= 9999 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ // This also validates the month.
+ guard let daysInMonth = ASN1.GeneralizedTime.daysInMonth(self._month, ofYear: self._year) else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ guard self._day >= 1 && self._day <= daysInMonth else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ guard self._hours >= 0 && self._hours < 24 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ guard self._minutes >= 0 && self._minutes < 60 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ // We allow leap seconds here, but don't validate it.
+ // This exposes us to potential confusion if we naively implement
+ // comparison here. We should consider whether this needs to be transformable
+ // to `Date` or similar.
+ guard self._seconds >= 0 && self._seconds <= 61 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ // Fractional seconds may not be negative and may not be 1 or more.
+ guard self._fractionalSeconds >= 0 && self._fractionalSeconds < 1 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+ }
+ }
+}
+
+extension ASN1.GeneralizedTime {
+ fileprivate static func parseDateBytes(_ bytes: ArraySlice) throws -> ASN1.GeneralizedTime {
+ var bytes = bytes
+
+ // First, there must always be a calendar date. No separators, 4
+ // digits for the year, 2 digits for the month, 2 digits for the day.
+ guard let rawYear = bytes.readFourDigitDecimalInteger(),
+ let rawMonth = bytes.readTwoDigitDecimalInteger(),
+ let rawDay = bytes.readTwoDigitDecimalInteger() else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ // Next there must be a _time_. Per DER rules, this time must always go
+ // to at least seconds, there are no separators, there is no time-zone (but there must be a 'Z'),
+ // and there may be fractional seconds but they must not have trailing zeros.
+ guard let rawHour = bytes.readTwoDigitDecimalInteger(),
+ let rawMinutes = bytes.readTwoDigitDecimalInteger(),
+ let rawSeconds = bytes.readTwoDigitDecimalInteger() else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ // There may be some fractional seconds.
+ var fractionalSeconds: Double = 0
+ if bytes.first == UInt8(ascii: ".") {
+ fractionalSeconds = try bytes.readFractionalSeconds()
+ }
+
+ // The next character _must_ be Z, or the encoding is invalid.
+ guard bytes.popFirst() == UInt8(ascii: "Z") else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ // Great! There better not be anything left.
+ guard bytes.count == 0 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return try ASN1.GeneralizedTime(year: rawYear,
+ month: rawMonth,
+ day: rawDay,
+ hours: rawHour,
+ minutes: rawMinutes,
+ seconds: rawSeconds,
+ fractionalSeconds: fractionalSeconds)
+ }
+
+ static func daysInMonth(_ month: Int, ofYear year: Int) -> Int? {
+ switch month {
+ case 1:
+ return 31
+ case 2:
+ // This one has a dependency on the year!
+ // A leap year occurs in any year divisible by 4, except when that year is divisible by 100,
+ // unless the year is divisible by 400.
+ let isLeapYear = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
+ return isLeapYear ? 29 : 28
+ case 3:
+ return 31
+ case 4:
+ return 30
+ case 5:
+ return 31
+ case 6:
+ return 30
+ case 7:
+ return 31
+ case 8:
+ return 31
+ case 9:
+ return 30
+ case 10:
+ return 31
+ case 11:
+ return 30
+ case 12:
+ return 31
+ default:
+ return nil
+ }
+ }
+}
+
+extension ArraySlice where Element == UInt8 {
+ fileprivate mutating func readFourDigitDecimalInteger() -> Int? {
+ guard let first = self.readTwoDigitDecimalInteger(),
+ let second = self.readTwoDigitDecimalInteger() else {
+ return nil
+ }
+
+ // Unchecked math is still safe here: we're in Int32 space, and this number cannot
+ // get any larger than 9999.
+ return (first &* 100) &+ second
+ }
+
+ fileprivate mutating func readTwoDigitDecimalInteger() -> Int? {
+ guard let firstASCII = self.popFirst(),
+ let secondASCII = self.popFirst() else {
+ return nil
+ }
+
+ guard let first = Int(fromDecimalASCII: firstASCII),
+ let second = Int(fromDecimalASCII: secondASCII) else {
+ return nil
+ }
+
+ // Unchecked math is safe here: we're in Int32 space at the very least, and this number cannot
+ // possibly be smaller than zero or larger than 99.
+ return (first &* 10) &+ (second)
+ }
+
+ /// This may only be called if there's a leading period: we precondition on this fact.
+ fileprivate mutating func readFractionalSeconds() throws -> Double {
+ precondition(self.popFirst() == UInt8(ascii: "."))
+
+ var numerator = 0
+ var denominator = 1
+
+ while let nextASCII = self.first, let next = Int(fromDecimalASCII: nextASCII) {
+ self = self.dropFirst()
+
+ let (newNumerator, multiplyOverflow) = numerator.multipliedReportingOverflow(by: 10)
+ let (newDenominator, secondMultiplyOverflow) = denominator.multipliedReportingOverflow(by: 10)
+ let (newNumeratorWithAdded, addingOverflow) = newNumerator.addingReportingOverflow(next)
+
+ // If the new denominator overflows, we just cap to the old value.
+ if !secondMultiplyOverflow {
+ denominator = newDenominator
+ }
+
+ // If the numerator overflows, we don't support the result.
+ if multiplyOverflow || addingOverflow {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ numerator = newNumeratorWithAdded
+ }
+
+ // Ok, we're either at the end or the next character is a Z. One final check: there may not have
+ // been any trailing zeros here. This means the number may not be 0 mod 10.
+ if numerator % 10 == 0 {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return Double(numerator) / Double(denominator)
+ }
+}
+
+extension Array where Element == UInt8 {
+ fileprivate mutating func append(_ generalizedTime: ASN1.GeneralizedTime) {
+ self.appendFourDigitDecimal(generalizedTime.year)
+ self.appendTwoDigitDecimal(generalizedTime.month)
+ self.appendTwoDigitDecimal(generalizedTime.day)
+ self.appendTwoDigitDecimal(generalizedTime.hours)
+ self.appendTwoDigitDecimal(generalizedTime.minutes)
+ self.appendTwoDigitDecimal(generalizedTime.seconds)
+
+ // Ok, tricky moment here. Is the fractional part non-zero? If it is, we need to write it out as well.
+ if generalizedTime.fractionalSeconds != 0 {
+ let stringified = String(generalizedTime.fractionalSeconds)
+ assert(stringified.starts(with: "0."))
+
+ self.append(contentsOf: stringified.utf8.dropFirst(1))
+ // Remove any trailing zeros from self, they are forbidden.
+ while self.last == 0 {
+ self = self.dropLast()
+ }
+ }
+
+ self.append(UInt8(ascii: "Z"))
+ }
+
+ fileprivate mutating func appendFourDigitDecimal(_ number: Int) {
+ assert(number >= 0 && number <= 9999)
+
+ // Each digit can be isolated by dividing by the place and then taking the result modulo 10.
+ // This is annoyingly division heavy. There may be a better algorithm floating around.
+ // Unchecked math is fine, there cannot be an overflow here.
+ let asciiZero = UInt8(ascii: "0")
+ self.append(UInt8(truncatingIfNeeded: (number / 1000) % 10) &+ asciiZero)
+ self.append(UInt8(truncatingIfNeeded: (number / 100) % 10) &+ asciiZero)
+ self.append(UInt8(truncatingIfNeeded: (number / 10) % 10) &+ asciiZero)
+ self.append(UInt8(truncatingIfNeeded: number % 10) &+ asciiZero)
+ }
+
+ fileprivate mutating func appendTwoDigitDecimal(_ number: Int) {
+ assert(number >= 0 && number <= 99)
+
+ // Each digit can be isolated by dividing by the place and then taking the result modulo 10.
+ // This is annoyingly division heavy. There may be a better algorithm floating around.
+ // Unchecked math is fine, there cannot be an overflow here.
+ let asciiZero = UInt8(ascii: "0")
+ self.append(UInt8(truncatingIfNeeded: (number / 10) % 10) &+ asciiZero)
+ self.append(UInt8(truncatingIfNeeded: number % 10) &+ asciiZero)
+ }
+}
+
+extension Int {
+ fileprivate init?(fromDecimalASCII ascii: UInt8) {
+ let asciiZero = UInt8(ascii: "0")
+ let zeroToNine = 0...9
+
+ // These are all coming from UInt8space, the subtraction cannot overflow.
+ let converted = Int(ascii) &- Int(asciiZero)
+
+ guard zeroToNine.contains(converted) else {
+ return nil
+ }
+
+ self = converted
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ObjectIdentifier.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ObjectIdentifier.swift
new file mode 100644
index 0000000..850d999
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/Basic ASN1 Types/ObjectIdentifier.swift
@@ -0,0 +1,222 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// Changes by Alexander Cyon:
+// * Removed ASN1 identifiers for swift-crypto curves
+// * Added ASN1 identifier for curve `secp256k1`
+
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// An Object Identifier is a representation of some kind of object: really any kind of object.
+ ///
+ /// It represents a node in an OID hierarchy, and is usually represented as an ordered sequence of numbers.
+ ///
+ /// We mostly don't care about the semantics of the thing, we just care about being able to store and compare them.
+ struct ASN1ObjectIdentifier: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .objectIdentifier
+ }
+
+ private var oidComponents: [UInt]
+
+ init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ guard node.identifier == identifier else {
+ throw CryptoKitASN1Error.unexpectedFieldType
+ }
+
+ guard case .primitive(var content) = node.content else {
+ preconditionFailure("ASN.1 parser generated primitive node with constructed content")
+ }
+
+ // We have to parse the content. From the spec:
+ //
+ // > Each subidentifier is represented as a series of (one or more) octets. Bit 8 of each octet indicates whether it
+ // > is the last in the series: bit 8 of the last octet is zero, bit 8 of each preceding octet is one. Bits 7 to 1 of
+ // > the octets in the series collectively encode the subidentifier. Conceptually, these groups of bits are concatenated
+ // > to form an unsigned binary number whose most significant bit is bit 7 of the first octet and whose least significant
+ // > bit is bit 1 of the last octet. The subidentifier shall be encoded in the fewest possible octets[...].
+ // >
+ // > The number of subidentifiers (N) shall be one less than the number of object identifier components in the object identifier
+ // > value being encoded.
+ // >
+ // > The numerical value of the first subidentifier is derived from the values of the first _two_ object identifier components
+ // > in the object identifier value being encoded, using the formula:
+ // >
+ // > (X*40) + Y
+ // >
+ // > where X is the value of the first object identifier component and Y is the value of the second object identifier component.
+ //
+ // Yeah, this is a bit bananas, but basically there are only 3 first OID components (0, 1, 2) and there are no more than 39 children
+ // of nodes 0 or 1. In my view this is too clever by half, but the ITU.T didn't ask for my opinion when they were coming up with this
+ // scheme, likely because I was in middle school at the time.
+ var subcomponents = [UInt]()
+ while content.count > 0 {
+ subcomponents.append(try content.readOIDSubidentifier())
+ }
+
+ guard subcomponents.count >= 2 else {
+ throw CryptoKitASN1Error.invalidObjectIdentifier
+ }
+
+ // Now we need to expand the subcomponents out. This means we need to undo the step above. The first component will be in the range 0..<40
+ // when the first oidComponent is 0, 40..<80 when the first oidComponent is 1, and 80+ when the first oidComponent is 2.
+ var oidComponents = [UInt]()
+ oidComponents.reserveCapacity(subcomponents.count + 1)
+
+ switch subcomponents.first! {
+ case ..<40:
+ oidComponents.append(0)
+ oidComponents.append(subcomponents.first!)
+ case 40 ..< 80:
+ oidComponents.append(1)
+ oidComponents.append(subcomponents.first! - 40)
+ default:
+ oidComponents.append(2)
+ oidComponents.append(subcomponents.first! - 80)
+ }
+
+ oidComponents.append(contentsOf: subcomponents.dropFirst())
+
+ self.oidComponents = oidComponents
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ coder.appendPrimitiveNode(identifier: identifier) { bytes in
+ var components = self.oidComponents[...]
+ guard let firstComponent = components.popFirst(), let secondComponent = components.popFirst() else {
+ preconditionFailure("Invalid number of OID components: must be at least two!")
+ }
+
+ let serializedFirstComponent = (firstComponent * 40) + secondComponent
+ ASN1ObjectIdentifier.writeOIDSubidentifier(serializedFirstComponent, into: &bytes)
+
+ while let component = components.popFirst() {
+ ASN1ObjectIdentifier.writeOIDSubidentifier(component, into: &bytes)
+ }
+ }
+ }
+
+ private static func writeOIDSubidentifier(_ identifier: UInt, into array: inout [UInt8]) {
+ // An OID subidentifier is written as an integer over 7-bit bytes, where the last byte has the top bit unset.
+ // The first thing we need is to know how many bits we need to write
+ let bitsToWrite = UInt.bitWidth - identifier.leadingZeroBitCount
+ let bytesToWrite = (bitsToWrite + 6) / 7
+
+ guard bytesToWrite > 0 else {
+ // Just a zero.
+ array.append(0)
+ return
+ }
+
+ for byteNumber in (1..> shift) & 0x7f) | 0x80
+ array.append(byte)
+ }
+
+ // Last byte to append here, we must unset the top bit.
+ let byte = UInt8((identifier & 0x7F))
+ array.append(byte)
+ }
+ }
+}
+
+extension ASN1.ASN1ObjectIdentifier: Hashable {}
+
+extension ASN1.ASN1ObjectIdentifier: ExpressibleByArrayLiteral {
+ init(arrayLiteral elements: UInt...) {
+ self.oidComponents = elements
+ }
+ }
+
+extension ASN1.ASN1ObjectIdentifier {
+ enum NamedCurves {
+
+ /// ASN1 identifier for `secp256k1`
+ /// https://oidref.com/1.3.132.0.10
+ static let secp256k1: ASN1.ASN1ObjectIdentifier = [1, 3, 132, 0, 10]
+
+ }
+
+ enum HashFunctions {
+ static let sha256: ASN1.ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 1]
+ static let sha384: ASN1.ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 2]
+ static let sha512: ASN1.ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 3]
+ }
+
+ enum AlgorithmIdentifier {
+ static let idEcPublicKey: ASN1.ASN1ObjectIdentifier = [1, 2, 840, 10_045, 2, 1]
+ }
+
+ enum NameAttributes {
+ static let name: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 41]
+ static let surname: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 4]
+ static let givenName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 42]
+ static let initials: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 43]
+ static let generationQualifier: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 44]
+ static let commonName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 3]
+ static let localityName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 7]
+ static let stateOrProvinceName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 8]
+ static let organizationName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 10]
+ static let organizationalUnitName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 11]
+ static let title: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 12]
+ static let dnQualifier: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 46]
+ static let countryName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 6]
+ static let serialNumber: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 5]
+ static let pseudonym: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 65]
+ static let domainComponent: ASN1.ASN1ObjectIdentifier = [0, 9, 2342, 19_200_300, 100, 1, 25]
+ static let emailAddress: ASN1.ASN1ObjectIdentifier = [1, 2, 840, 113_549, 1, 9, 1]
+ }
+
+}
+
+extension ArraySlice where Element == UInt8 {
+ mutating fileprivate func readOIDSubidentifier() throws -> UInt {
+ // In principle OID subidentifiers can be too large to fit into a UInt. We are choosing to not care about that
+ // because for us it shouldn't matter.
+ guard let subidentifierEndIndex = self.firstIndex(where: { $0 & 0x80 == 0x00 }) else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ let oidSlice = self[self.startIndex ... subidentifierEndIndex]
+ self = self[self.index(after: subidentifierEndIndex)...]
+
+ // We need to compact the bits. These are 7-bit integers, which is really awkward.
+ return try UInt(sevenBitBigEndianBytes: oidSlice)
+ }
+}
+
+extension UInt {
+ fileprivate init(sevenBitBigEndianBytes bytes: Bytes) throws where Bytes.Element == UInt8 {
+ // We need to know how many bytes we _need_ to store this "int".
+ guard ((bytes.count * 7) + 7) / 8 <= MemoryLayout.size else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ self = 0
+ let shiftSizes = stride(from: 0, to: bytes.count * 7, by: 7).reversed()
+
+ var index = bytes.startIndex
+ for shift in shiftSizes {
+ self |= UInt(bytes[index] & 0x7F) << shift
+ bytes.formIndex(after: &index)
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/ECDSASignature.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/ECDSASignature.swift
new file mode 100644
index 0000000..a89f40d
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/ECDSASignature.swift
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// An ECDSA signature is laid out as follows:
+ ///
+ /// ECDSASignature ::= SEQUENCE {
+ /// r INTEGER,
+ /// s INTEGER
+ /// }
+ ///
+ /// This type is generic because our different backends want to use different bignum representations.
+ struct ECDSASignature: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .sequence
+ }
+
+ var r: IntegerType
+ var s: IntegerType
+
+ init(r: IntegerType, s: IntegerType) {
+ self.r = r
+ self.s = s
+ }
+
+ init(asn1Encoded rootNode: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self = try ASN1.sequence(rootNode, identifier: identifier) { nodes in
+ let r = try IntegerType(asn1Encoded: &nodes)
+ let s = try IntegerType(asn1Encoded: &nodes)
+
+ return ECDSASignature(r: r, s: s)
+ }
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.r)
+ try coder.serialize(self.s)
+ }
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/PEMDocument.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/PEMDocument.swift
new file mode 100644
index 0000000..f2f8d7e
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/PEMDocument.swift
@@ -0,0 +1,118 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ /// A PEM document is some data, and a discriminator type that is used to advertise the content.
+ struct PEMDocument {
+ private static let lineLength = 64
+
+ var type: String
+
+ var derBytes: Data
+
+ init(pemString: String) throws {
+ // A PEM document looks like this:
+ //
+ // -----BEGIN -----
+ //
+ // -----END -----
+ //
+ // This function attempts to parse this string as a PEM document, and returns the discriminator type
+ // and the base64 decoded bytes.
+ var lines = pemString.split { $0.isNewline }[...]
+ guard let first = lines.first, let last = lines.last else {
+ throw CryptoKitASN1Error.invalidPEMDocument
+ }
+
+ guard let discriminator = first.pemStartDiscriminator, discriminator == last.pemEndDiscriminator else {
+ throw CryptoKitASN1Error.invalidPEMDocument
+ }
+
+ // All but the last line must be 64 bytes. The force unwrap is safe because we require the lines to be
+ // greater than zero.
+ lines = lines.dropFirst().dropLast()
+ guard lines.count > 0,
+ lines.dropLast().allSatisfy({ $0.utf8.count == PEMDocument.lineLength }),
+ lines.last!.utf8.count <= PEMDocument.lineLength else {
+ throw CryptoKitASN1Error.invalidPEMDocument
+ }
+
+ guard let derBytes = Data(base64Encoded: lines.joined()) else {
+ throw CryptoKitASN1Error.invalidPEMDocument
+ }
+
+ self.type = discriminator
+ self.derBytes = derBytes
+ }
+
+ init(type: String, derBytes: Data) {
+ self.type = type
+ self.derBytes = derBytes
+ }
+
+ var pemString: String {
+ var encoded = self.derBytes.base64EncodedString()[...]
+ let pemLineCount = (encoded.utf8.count + PEMDocument.lineLength) / PEMDocument.lineLength
+ var pemLines = [Substring]()
+ pemLines.reserveCapacity(pemLineCount + 2)
+
+ pemLines.append("-----BEGIN \(self.type)-----")
+
+ while encoded.count > 0 {
+ let prefixIndex = encoded.index(encoded.startIndex, offsetBy: PEMDocument.lineLength, limitedBy: encoded.endIndex) ?? encoded.endIndex
+ pemLines.append(encoded[.. String? {
+ var utf8Bytes = self.utf8[...]
+
+ // We want to split this sequence into three parts: the prefix, the middle, and the end
+ let prefixSize = expectedPrefix.utf8.count
+ let suffixSize = expectedSuffix.utf8.count
+
+ let prefix = utf8Bytes.prefix(prefixSize)
+ utf8Bytes = utf8Bytes.dropFirst(prefixSize)
+ let suffix = utf8Bytes.suffix(suffixSize)
+ utf8Bytes = utf8Bytes.dropLast(suffixSize)
+
+ guard prefix.elementsEqual(expectedPrefix.utf8), suffix.elementsEqual(expectedSuffix.utf8) else {
+ return nil
+ }
+
+ return String(utf8Bytes)
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/PKCS8PrivateKey.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/PKCS8PrivateKey.swift
new file mode 100644
index 0000000..c35f762
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/PKCS8PrivateKey.swift
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ // A PKCS#8 private key is one of two formats, depending on the version:
+ //
+ // For PKCS#8 we need the following for the private key:
+ //
+ // PrivateKeyInfo ::= SEQUENCE {
+ // version Version,
+ // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+ // privateKey PrivateKey,
+ // attributes [0] IMPLICIT Attributes OPTIONAL }
+ //
+ // Version ::= INTEGER
+ //
+ // PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ //
+ // PrivateKey ::= OCTET STRING
+ //
+ // Attributes ::= SET OF Attribute
+ //
+ // We disregard the attributes because we don't support them anyway.
+ //
+ // The private key octet string contains (surprise!) a SEC1-encoded private key! So we recursively invoke the
+ // ASN.1 parser and go again.
+ struct PKCS8PrivateKey: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ return .sequence
+ }
+
+ var algorithm: RFC5480AlgorithmIdentifier
+
+ var privateKey: ASN1.SEC1PrivateKey
+
+ init(asn1Encoded rootNode: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self = try ASN1.sequence(rootNode, identifier: identifier) { nodes in
+ let version = try Int(asn1Encoded: &nodes)
+ guard version == 0 else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ let algorithm = try ASN1.RFC5480AlgorithmIdentifier(asn1Encoded: &nodes)
+ let privateKeyBytes = try ASN1.ASN1OctetString(asn1Encoded: &nodes)
+
+ // We ignore the attributes
+ _ = try ASN1.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { _ in }
+
+ let sec1PrivateKeyNode = try ASN1.parse(privateKeyBytes.bytes)
+ let sec1PrivateKey = try ASN1.SEC1PrivateKey(asn1Encoded: sec1PrivateKeyNode)
+ if let innerAlgorithm = sec1PrivateKey.algorithm, innerAlgorithm != algorithm {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ return try .init(algorithm: algorithm, privateKey: sec1PrivateKey)
+ }
+ }
+
+ private init(algorithm: ASN1.RFC5480AlgorithmIdentifier, privateKey: ASN1.SEC1PrivateKey) throws {
+ self.privateKey = privateKey
+ self.algorithm = algorithm
+ }
+
+ init(algorithm: ASN1.RFC5480AlgorithmIdentifier, privateKey: [UInt8], publicKey: [UInt8]) {
+ self.algorithm = algorithm
+
+ // We nil out the private key here. I don't really know why we do this, but OpenSSL does, and it seems
+ // safe enough to do: it certainly avoids the possibility of disagreeing on what it is!
+ self.privateKey = ASN1.SEC1PrivateKey(privateKey: privateKey, algorithm: nil, publicKey: publicKey)
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(0) // version
+ try coder.serialize(self.algorithm)
+
+ // Here's a weird one: we recursively serialize the private key, and then turn the bytes into an octet string.
+ var subCoder = ASN1.Serializer()
+ try subCoder.serialize(self.privateKey)
+ let serializedKey = ASN1.ASN1OctetString(contentBytes: subCoder.serializedBytes[...])
+
+ try coder.serialize(serializedKey)
+ }
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/SEC1PrivateKey.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/SEC1PrivateKey.swift
new file mode 100644
index 0000000..5cc1b76
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/SEC1PrivateKey.swift
@@ -0,0 +1,108 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// Changes by Alexander Cyon:
+// * Removed support for swift-crypto curves
+// * Added support for curve `secp256k1`
+
+
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ // For private keys, SEC 1 uses:
+ //
+ // ECPrivateKey ::= SEQUENCE {
+ // version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ // privateKey OCTET STRING,
+ // parameters [0] EXPLICIT ECDomainParameters OPTIONAL,
+ // publicKey [1] EXPLICIT BIT STRING OPTIONAL
+ // }
+ struct SEC1PrivateKey: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ return .sequence
+ }
+
+ var algorithm: ASN1.RFC5480AlgorithmIdentifier?
+
+ var privateKey: ASN1.ASN1OctetString
+
+ var publicKey: ASN1.ASN1BitString?
+
+ init(asn1Encoded rootNode: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ self = try ASN1.sequence(rootNode, identifier: identifier) { nodes in
+ let version = try Int(asn1Encoded: &nodes)
+ guard 1 == version else {
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ let privateKey = try ASN1OctetString(asn1Encoded: &nodes)
+ let parameters = try ASN1.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { node in
+ return try ASN1.ASN1ObjectIdentifier(asn1Encoded: node)
+ }
+ let publicKey = try ASN1.optionalExplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) { node in
+ return try ASN1.ASN1BitString(asn1Encoded: node)
+ }
+
+ return try .init(privateKey: privateKey, algorithm: parameters, publicKey: publicKey)
+ }
+ }
+
+ private init(privateKey: ASN1.ASN1OctetString, algorithm: ASN1.ASN1ObjectIdentifier?, publicKey: ASN1.ASN1BitString?) throws {
+ self.privateKey = privateKey
+ self.publicKey = publicKey
+ self.algorithm = try algorithm.map { algorithmOID in
+ switch algorithmOID {
+ case ASN1ObjectIdentifier.NamedCurves.secp256k1:
+ return .secp256k1
+ default:
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+ }
+ }
+
+ init(privateKey: [UInt8], algorithm: RFC5480AlgorithmIdentifier?, publicKey: [UInt8]) {
+ self.privateKey = ASN1OctetString(contentBytes: privateKey[...])
+ self.algorithm = algorithm
+ self.publicKey = ASN1BitString(bytes: publicKey[...])
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(1) // version
+ try coder.serialize(self.privateKey)
+
+ if let algorithm = self.algorithm {
+ let oid: ASN1.ASN1ObjectIdentifier
+ switch algorithm {
+ case .secp256k1:
+ oid = ASN1ObjectIdentifier.NamedCurves.secp256k1
+ default:
+ throw CryptoKitASN1Error.invalidASN1Object
+ }
+
+ try coder.serialize(oid, explicitlyTaggedWithTagNumber: 0, tagClass: .contextSpecific)
+ }
+
+ if let publicKey = self.publicKey {
+ try coder.serialize(publicKey, explicitlyTaggedWithTagNumber: 1, tagClass: .contextSpecific)
+ }
+ }
+ }
+ }
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/SubjectPublicKeyInfo.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/SubjectPublicKeyInfo.swift
new file mode 100644
index 0000000..f87f396
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1/SubjectPublicKeyInfo.swift
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// Changes by Alexander Cyon:
+// * Removed RFC5480AlgorithmIdentifier for swift-crypto curves
+// * Added RFC5480AlgorithmIdentifier for curve `secp256k1`
+
+
+#if (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
+@_exported import CryptoKit
+#else
+import Foundation
+
+extension ASN1 {
+ struct SubjectPublicKeyInfo: ASN1ImplicitlyTaggable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .sequence
+ }
+
+ var algorithmIdentifier: RFC5480AlgorithmIdentifier
+
+ var key: ASN1.ASN1BitString
+
+ init(asn1Encoded rootNode: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ // The SPKI block looks like this:
+ //
+ // SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING
+ // }
+ self = try ASN1.sequence(rootNode, identifier: identifier) { nodes in
+ let algorithmIdentifier = try ASN1.RFC5480AlgorithmIdentifier(asn1Encoded: &nodes)
+ let key = try ASN1.ASN1BitString(asn1Encoded: &nodes)
+
+ return SubjectPublicKeyInfo(algorithmIdentifier: algorithmIdentifier, key: key)
+ }
+ }
+
+ private init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: ASN1.ASN1BitString) {
+ self.algorithmIdentifier = algorithmIdentifier
+ self.key = key
+ }
+
+ internal init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: [UInt8]) {
+ self.algorithmIdentifier = algorithmIdentifier
+ self.key = ASN1BitString(bytes: key[...])
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.algorithmIdentifier)
+ try coder.serialize(self.key)
+ }
+ }
+ }
+
+ struct RFC5480AlgorithmIdentifier: ASN1ImplicitlyTaggable, Hashable {
+ static var defaultIdentifier: ASN1.ASN1Identifier {
+ .sequence
+ }
+
+ var algorithm: ASN1.ASN1ObjectIdentifier
+
+ var parameters: ASN1.ASN1Any?
+
+ init(algorithm: ASN1.ASN1ObjectIdentifier, parameters: ASN1.ASN1Any?) {
+ self.algorithm = algorithm
+ self.parameters = parameters
+ }
+
+ init(asn1Encoded rootNode: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ // The AlgorithmIdentifier block looks like this.
+ //
+ // AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // }
+ //
+ // ECParameters ::= CHOICE {
+ // namedCurve OBJECT IDENTIFIER
+ // -- implicitCurve NULL
+ // -- specifiedCurve SpecifiedECDomain
+ // }
+ //
+ // We don't bother with helpers: we just try to decode it directly.
+ self = try ASN1.sequence(rootNode, identifier: identifier) { nodes in
+ let algorithmOID = try ASN1.ASN1ObjectIdentifier(asn1Encoded: &nodes)
+
+ let parameters = nodes.next().map { ASN1.ASN1Any(asn1Encoded: $0) }
+
+ return .init(algorithm: algorithmOID, parameters: parameters)
+ }
+ }
+
+ func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.algorithm)
+ if let parameters = self.parameters {
+ try coder.serialize(parameters)
+ }
+ }
+ }
+ }
+}
+
+// MARK: Algorithm Identifier Statics
+extension ASN1.RFC5480AlgorithmIdentifier {
+ static let secp256k1 = ASN1.RFC5480AlgorithmIdentifier(
+ algorithm: .AlgorithmIdentifier.idEcPublicKey,
+ parameters: try! .init(erasing: ASN1.ASN1ObjectIdentifier.NamedCurves.secp256k1)
+ )
+}
+
+#endif // Linux or !SwiftPM
diff --git a/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1Error.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1Error.swift
new file mode 100644
index 0000000..9c258e4
--- /dev/null
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/ASN1Error.swift
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCrypto open source project
+//
+// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// FROM: https://github.com/apple/swift-crypto/blob/5d8b14d22701c8394ad8cd5b297f8ffd9a6d3d4a/Sources/Crypto/CryptoKitErrors.swift#L34
+
+/// Errors encountered when parsing ASN.1 formatted keys.
+public enum CryptoKitASN1Error: Error {
+ /// The ASN.1 tag for this field is invalid or unsupported.
+ case invalidFieldIdentifier
+
+ /// The ASN.1 tag for the parsed field does not match the required format.
+ case unexpectedFieldType
+
+ /// An invalid ASN.1 object identifier was encountered.
+ case invalidObjectIdentifier
+
+ /// The format of the parsed ASN.1 object does not match the format required for the data type
+ /// being decoded.
+ case invalidASN1Object
+
+ /// An ASN.1 integer was decoded that does not use the minimum number of bytes for its encoding.
+ case invalidASN1IntegerEncoding
+
+ /// An ASN.1 field was truncated and could not be decoded.
+ case truncatedASN1Field
+
+ /// The encoding used for the field length is not supported.
+ case unsupportedFieldLength
+
+ /// It was not possible to parse a string as a PEM document.
+ case invalidPEMDocument
+}
diff --git a/Sources/K1/ThirdyParty/swift-crypto/BytesUtil.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/BytesUtil.swift
similarity index 96%
rename from Sources/K1/ThirdyParty/swift-crypto/BytesUtil.swift
rename to Sources/K1/Support/ThirdyParty/swift-crypto/BytesUtil.swift
index 46fa4ed..a8e6d9c 100644
--- a/Sources/K1/ThirdyParty/swift-crypto/BytesUtil.swift
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/BytesUtil.swift
@@ -52,17 +52,17 @@ extension Array where Element == UInt8 {
guard hex.count.isMultiple(of: 2), !hex.isEmpty else {
throw ByteHexEncodingErrors.incorrectString
}
-
+
let stringBytes: [UInt8] = Array(hex.data(using: String.Encoding.utf8)!)
-
+
for i in 0...((hex.count / 2) - 1) {
let char1 = stringBytes[2 * i]
let char2 = stringBytes[2 * i + 1]
-
+
try self.append(htoi(char1) << 4 + htoi(char2))
}
}
-
+
}
extension DataProtocol {
@@ -71,7 +71,7 @@ extension DataProtocol {
let hexLen = self.count * 2
let ptr = UnsafeMutablePointer.allocate(capacity: hexLen)
var offset = 0
-
+
self.regions.forEach { (_) in
for i in self {
ptr[Int(offset * 2)] = itoh((i >> 4) & 0xF)
@@ -79,7 +79,7 @@ extension DataProtocol {
offset += 1
}
}
-
+
return String(bytesNoCopy: ptr, length: hexLen, encoding: .utf8, freeWhenDone: true)!
}
}
@@ -88,19 +88,19 @@ extension DataProtocol {
extension Data {
init(hex: String) throws {
self.init()
-
+
if hex.count % 2 != 0 || hex.count == 0 {
throw ByteHexEncodingErrors.incorrectString
}
-
+
let stringBytes: [UInt8] = Array(hex.lowercased().data(using: String.Encoding.utf8)!)
-
+
for i in 0...((hex.count / 2) - 1) {
let char1 = stringBytes[2 * i]
let char2 = stringBytes[2 * i + 1]
-
+
try self.append(htoi(char1) << 4 + htoi(char2))
}
}
-
+
}
diff --git a/Sources/K1/ThirdyParty/swift-crypto/RNG_boring.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/RNG_boring.swift
similarity index 100%
rename from Sources/K1/ThirdyParty/swift-crypto/RNG_boring.swift
rename to Sources/K1/Support/ThirdyParty/swift-crypto/RNG_boring.swift
diff --git a/Sources/K1/ThirdyParty/swift-crypto/SafeCompare_boring.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/SafeCompare_boring.swift
similarity index 92%
rename from Sources/K1/ThirdyParty/swift-crypto/SafeCompare_boring.swift
rename to Sources/K1/Support/ThirdyParty/swift-crypto/SafeCompare_boring.swift
index 60c6221..cc85fe5 100644
--- a/Sources/K1/ThirdyParty/swift-crypto/SafeCompare_boring.swift
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/SafeCompare_boring.swift
@@ -20,7 +20,7 @@ import Foundation
/// This function performs a safe comparison between two buffers of bytes. It exists as a temporary shim until we refactor
/// some of the usage sites to pass better data structures to us.
@inlinable
-internal func openSSLSafeCompare(_ lhs: LHS, _ rhs: RHS) -> Bool {
+func safeCompare(_ lhs: LHS, _ rhs: RHS) -> Bool {
lhs.withUnsafeBytes { lhsPtr in
rhs.withUnsafeBytes { rhsPtr in
constantTimeCompare(lhsPtr, rhsPtr)
diff --git a/Sources/K1/ThirdyParty/swift-crypto/SecureBytes.swift b/Sources/K1/Support/ThirdyParty/swift-crypto/SecureBytes.swift
similarity index 99%
rename from Sources/K1/ThirdyParty/swift-crypto/SecureBytes.swift
rename to Sources/K1/Support/ThirdyParty/swift-crypto/SecureBytes.swift
index d5da6f3..1c3dace 100644
--- a/Sources/K1/ThirdyParty/swift-crypto/SecureBytes.swift
+++ b/Sources/K1/Support/ThirdyParty/swift-crypto/SecureBytes.swift
@@ -76,7 +76,7 @@ extension SecureBytes {
// MARK: - Equatable conformance, constant-time
extension SecureBytes: Equatable {
- public static func == (lhs: SecureBytes, rhs: SecureBytes) -> Bool {
+ static func == (lhs: SecureBytes, rhs: SecureBytes) -> Bool {
return safeCompare(lhs, rhs)
}
}
diff --git a/Sources/K1/ThirdyParty/swift-crypto/SafeCompare.swift b/Sources/K1/ThirdyParty/swift-crypto/SafeCompare.swift
deleted file mode 100644
index afd22c5..0000000
--- a/Sources/K1/ThirdyParty/swift-crypto/SafeCompare.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-// from: https://github.com/apple/swift-crypto/blob/794901c991bf3fa0431ba3c0927ba078799c6911/Sources/Crypto/Util/SafeCompare.swift
-// commit: 794901c991bf3fa0431ba3c0927ba078799c6911
-// editing done in compliance with Apache License
-
-//===----------------------------------------------------------------------===//
-//
-// This source file is part of the SwiftCrypto open source project
-//
-// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
-// Licensed under Apache License v2.0
-//
-// See LICENSE.txt for license information
-// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-//===----------------------------------------------------------------------===//
-
-import Foundation
-
-internal func safeCompare(_ lhs: LHS, _ rhs: RHS) -> Bool {
- return openSSLSafeCompare(lhs, rhs)
-}
-
diff --git a/Sources/K1/Util/MemoizationBox.swift b/Sources/K1/Util/MemoizationBox.swift
deleted file mode 100644
index cb7f572..0000000
--- a/Sources/K1/Util/MemoizationBox.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-28.
-//
-
-import Foundation
-
-// https://forums.swift.org/t/proposal-property-behaviors/594
-internal final class MemoizationBox {
- private let lock = NSLock()
- private var value: T? = nil
-
- internal init() {}
-
- func getOrEvaluate(fn: () -> T) -> T {
- if let value = value { return value }
- // Perform initialization in a thread-safe way.
- return lock.with {
- let initialValue = fn()
- value = initialValue
- return initialValue
- }
- }
-}
-
-extension NSLock {
-
- @discardableResult
- func with(_ block: () throws -> T) rethrows -> T {
- lock()
- defer { unlock() }
- return try block()
- }
-}
diff --git a/Sources/secp256k1/include/secp256k1.h b/Sources/secp256k1/include/secp256k1.h
index 6c2d66c..1d6d15a 100644
--- a/Sources/secp256k1/include/secp256k1.h
+++ b/Sources/secp256k1/include/secp256k1.h
@@ -5,4 +5,4 @@
#include "../libsecp256k1/include/secp256k1_schnorrsig.h"
#include "../libsecp256k1/include/secp256k1.h"
-#include "../src/ecdh_no_hashfp.h"
+#include "../src/ecdh_variants.h"
diff --git a/Sources/secp256k1/src/ecdh_no_hashfp.h b/Sources/secp256k1/src/ecdh_no_hashfp.h
deleted file mode 100644
index 7c95bbc..0000000
--- a/Sources/secp256k1/src/ecdh_no_hashfp.h
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-// ecdh_no_hashfp.h
-//
-//
-// Created by Alexander Cyon on 2022-01-31.
-//
-
-#ifndef ecdh_no_hashfp_h
-#define ecdh_no_hashfp_h
-
-#include "../libsecp256k1/include/secp256k1_ecdh.h"
-
-int ecdh_skip_hash_extract_x_and_y(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data);
-
-
-int ecdh_skip_hash_extract_only_x(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data);
-
-
-
-
-#endif /* ecdh_no_hashfp_h */
diff --git a/Sources/secp256k1/src/ecdh_no_hashfp.c b/Sources/secp256k1/src/ecdh_variants.c
similarity index 51%
rename from Sources/secp256k1/src/ecdh_no_hashfp.c
rename to Sources/secp256k1/src/ecdh_variants.c
index 8a2863e..6b4df1e 100644
--- a/Sources/secp256k1/src/ecdh_no_hashfp.c
+++ b/Sources/secp256k1/src/ecdh_variants.c
@@ -1,14 +1,14 @@
//
-// ecdh_no_hashfp.c
+// ecdh_variants.c
//
//
// Created by Alexander Cyon on 2022-01-31.
//
-#include "./ecdh_no_hashfp.h"
+#include "./ecdh_variants.h"
#include
-int ecdh_skip_hash_extract_x_and_y(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
+int ecdh_unsafe_whole_point(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
(void)data;
/* Save x and y as uncompressed public key */
output[0] = 0x04;
@@ -17,7 +17,7 @@ int ecdh_skip_hash_extract_x_and_y(unsigned char *output, const unsigned char *x
return 1;
}
-int ecdh_skip_hash_extract_only_x(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
+int ecdh_asn1_x963(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
(void)data;
memcpy(output, x32, 32);
return 1;
diff --git a/Sources/secp256k1/src/ecdh_variants.h b/Sources/secp256k1/src/ecdh_variants.h
new file mode 100644
index 0000000..32c5d26
--- /dev/null
+++ b/Sources/secp256k1/src/ecdh_variants.h
@@ -0,0 +1,39 @@
+//
+// ecdh_variants.h
+//
+//
+// Created by Alexander Cyon on 2022-01-31.
+//
+
+#ifndef ecdh_variants_h
+#define ecdh_variants_h
+
+#include "../libsecp256k1/include/secp256k1_ecdh.h"
+
+/** An unsafe ECDH variant which returns the whole ECDH secret (uncompressed point, unhashed)
+ *
+ * Returns: 1 if the point was successfully hashed.
+ * 0 will cause secp256k1_ecdh to fail and return 0.
+ * Other return values are not allowed, and the behaviour of
+ * secp256k1_ecdh is undefined for other return values.
+ * Out: output: pointer to an array to be filled by the function
+ * In: x32: pointer to a 32-byte x coordinate
+ * y32: pointer to a 32-byte y coordinate
+ * data: arbitrary data pointer that is passed through
+ */
+int ecdh_unsafe_whole_point(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data);
+
+/** The ASN1 X9.63 ECDH variant which returns only the X component of ECDH secret (unhashed).
+ *
+ * Returns: 1 if the point was successfully hashed.
+ * 0 will cause secp256k1_ecdh to fail and return 0.
+ * Other return values are not allowed, and the behaviour of
+ * secp256k1_ecdh is undefined for other return values.
+ * Out: output: pointer to an array to be filled by the function
+ * In: x32: pointer to a 32-byte x coordinate
+ * y32: pointer to a 32-byte y coordinate
+ * data: arbitrary data pointer that is passed through
+ */
+int ecdh_asn1_x963(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data);
+
+#endif /* ecdh_variants_h */
diff --git a/Tests/K1Tests/TestCases/ECDH/ECDHTests.swift b/Tests/K1Tests/TestCases/ECDH/ECDHTests.swift
index 7f47e51..145e796 100644
--- a/Tests/K1Tests/TestCases/ECDH/ECDHTests.swift
+++ b/Tests/K1Tests/TestCases/ECDH/ECDHTests.swift
@@ -12,9 +12,9 @@ import XCTest
final class ECDHTests: XCTestCase {
func testECDHX963() throws {
- let alice = try K1.PrivateKey.generateNew()
- let bob = try K1.PrivateKey.generateNew()
-
+ let alice = K1.KeyAgreement.PrivateKey()
+ let bob = K1.KeyAgreement.PrivateKey()
+ XCTAssertNotEqual(alice, bob)
let ab = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey)
let ba = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)
ab.withUnsafeBytes {
@@ -24,19 +24,21 @@ final class ECDHTests: XCTestCase {
}
func testECDHLibsecp256k1() throws {
- let alice = try K1.PrivateKey.generateNew()
- let bob = try K1.PrivateKey.generateNew()
-
+ let alice = K1.KeyAgreement.PrivateKey()
+ let bob = K1.KeyAgreement.PrivateKey()
+ XCTAssertNotEqual(alice, bob)
let ab = try alice.ecdh(with: bob.publicKey)
let ba = try bob.ecdh(with: alice.publicKey)
+ ab.withUnsafeBytes {
+ XCTAssertEqual(Data($0).count, 32)
+ }
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
- XCTAssertEqual(ab.count, 32)
}
func testECDHPoint() throws {
- let alice = try K1.PrivateKey.generateNew()
- let bob = try K1.PrivateKey.generateNew()
-
+ let alice = K1.KeyAgreement.PrivateKey()
+ let bob = K1.KeyAgreement.PrivateKey()
+ XCTAssertNotEqual(alice, bob)
let ab = try alice.ecdhPoint(with: bob.publicKey)
let ba = try bob.ecdhPoint(with: alice.publicKey)
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
@@ -45,21 +47,24 @@ final class ECDHTests: XCTestCase {
/// Test vectors from: https://crypto.stackexchange.com/q/57695
func test_crypto_stackexchange_vector() throws {
- let privateKey1 = try PrivateKey(hex: "82fc9947e878fc7ed01c6c310688603f0a41c8e8704e5b990e8388343b0fd465")
- let privateKey2 = try PrivateKey(hex: "5f706787ac72c1080275c1f398640fb07e9da0b124ae9734b28b8d0f01eda586")
+ let privateKey1 = try K1.KeyAgreement.PrivateKey(hex: "82fc9947e878fc7ed01c6c310688603f0a41c8e8704e5b990e8388343b0fd465")
+ let privateKey2 = try K1.KeyAgreement.PrivateKey(hex: "5f706787ac72c1080275c1f398640fb07e9da0b124ae9734b28b8d0f01eda586")
let libsecp256k1 = try privateKey1.ecdh(with: privateKey2.publicKey)
let ans1X963 = try privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey).withUnsafeBytes({ Data($0) })
- XCTAssertEqual(libsecp256k1.hex, "5935d0476af9df2998efb60383adf2ff23bc928322cfbb738fca88e49d557d7e")
+ libsecp256k1.withUnsafeBytes {
+ XCTAssertEqual(Data($0).hex, "5935d0476af9df2998efb60383adf2ff23bc928322cfbb738fca88e49d557d7e")
+ }
+
XCTAssertEqual(ans1X963.hex, "3a17fe5fa33c4f2c7e61799a65061214913f39bfcbee178ab351493d5ee17b2f")
}
/// Assert we do not introduce any regression bugs for the custom ECDh `ecdhPoint`
func testECDHCustom() throws {
- let alice = try K1.PrivateKey.import(rawRepresentation: Data(repeating: 0xAA, count: 32))
- let bob = try K1.PrivateKey.import(rawRepresentation: Data(repeating: 0xBB, count: 32))
+ let alice = try K1.KeyAgreement.PrivateKey(rawRepresentation: Data(repeating: 0xAA, count: 32))
+ let bob = try K1.KeyAgreement.PrivateKey(rawRepresentation: Data(repeating: 0xBB, count: 32))
let ab = try alice.ecdhPoint(with: bob.publicKey)
let ba = try bob.ecdhPoint(with: alice.publicKey)
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
@@ -68,8 +73,8 @@ final class ECDHTests: XCTestCase {
}
}
-extension K1.PrivateKey {
+extension K1.KeyAgreement.PrivateKey {
init(hex: String) throws {
- self = try Self.import(rawRepresentation: Data(hex: hex))
+ self = try Self(rawRepresentation: Data(hex: hex))
}
}
diff --git a/Tests/K1Tests/TestCases/ECDH/ECDHWychoproofTests.swift b/Tests/K1Tests/TestCases/ECDH/ECDHWychoproofTests.swift
index c159801..0698e03 100644
--- a/Tests/K1Tests/TestCases/ECDH/ECDHWychoproofTests.swift
+++ b/Tests/K1Tests/TestCases/ECDH/ECDHWychoproofTests.swift
@@ -22,7 +22,7 @@ final class ECDHWychoproofTests: XCTestCase {
func testECDHWycheproof() throws {
let result = try testSuite(
- jsonName: "ecdh_secp256k1_test",
+ jsonName: "wycheproof_ecdh_ASN1x963",
testFunction: { (group: ECDHTestGroup) in
testGroup(group: group)
})
@@ -45,7 +45,7 @@ private extension ECDHWychoproofTests {
//
// Second, Wycheproof inputs may be too short or too long with
// leading zeros.
- let curveSize = K1.Curve.Field.byteCount
+ let curveSize = 32 //K1.Curve.Field.byteCount
var privateBytes = [UInt8](repeating: 0, count: curveSize)
let hexFromVector = (vector.count % 2 == 0) ? vector : "0\(vector)"
@@ -74,10 +74,10 @@ private extension ECDHWychoproofTests {
}
numberOfTestsRun += 1
do {
- let publicKey = try PublicKey.import(der: Data(hex: testVector.publicKey))
+ let publicKey = try K1.KeyAgreement.PublicKey(derRepresentation: Data(hex: testVector.publicKey))
var privateBytes = [UInt8]()
privateBytes = try padKeyIfNecessary(vector: testVector.privateKey)
- let privateKey = try PrivateKey.import(rawRepresentation: privateBytes)
+ let privateKey = try K1.KeyAgreement.PrivateKey(rawRepresentation: privateBytes)
/// ANS1 X9.63 serialization of shared secret, returning a `CryptoKit.SharedSecret`
let sharedPublicKeyPoint = try privateKey.sharedSecretFromKeyAgreement(with: publicKey)
diff --git a/Tests/K1Tests/TestCases/ECDH/TwoVariantsOfECDHWithKDFTests.swift b/Tests/K1Tests/TestCases/ECDH/TwoVariantsOfECDHWithKDFTests.swift
index 44dd647..af223b0 100644
--- a/Tests/K1Tests/TestCases/ECDH/TwoVariantsOfECDHWithKDFTests.swift
+++ b/Tests/K1Tests/TestCases/ECDH/TwoVariantsOfECDHWithKDFTests.swift
@@ -47,9 +47,12 @@ private struct Vector: Decodable {
final class TwoVariantsOfECDHWithKDFTests: XCTestCase {
-
+ override func setUp() {
+ super.setUp()
+ continueAfterFailure = false
+ }
func testTwoVariantsOfECDHWithKDF_vectors() throws {
- let fileURL = Bundle.module.url(forResource: "ecdh_secp256k1_two_variants_with_kdf_test", withExtension: ".json")
+ let fileURL = Bundle.module.url(forResource: "cyon_ecdh_two_variants_with_kdf", withExtension: ".json")
let data = try Data(contentsOf: fileURL!)
let suite = try JSONDecoder().decode(ECDHX963Suite.self, from: data)
try suite.vectors.forEach(doTest)
@@ -63,10 +66,10 @@ extension TwoVariantsOfECDHWithKDFTests {
let outputByteCount = 32
let hash = SHA256.self
- let alice = try PrivateKey(hex: vector.alicePrivateKey)
- let bob = try PrivateKey(hex: vector.bobPrivateKey)
- try XCTAssertEqual(alice.publicKey.rawRepresentation(format: .uncompressed).hex, vector.alicePublicKeyUncompressed)
- try XCTAssertEqual(bob.publicKey.rawRepresentation(format: .uncompressed).hex, vector.bobPublicKeyUncompressed)
+ let alice = try K1.KeyAgreement.PrivateKey(hex: vector.alicePrivateKey)
+ let bob = try K1.KeyAgreement.PrivateKey(hex: vector.bobPrivateKey)
+ XCTAssertEqual(alice.publicKey.x963Representation.hex, vector.alicePublicKeyUncompressed)
+ XCTAssertEqual(bob.publicKey.x963Representation.hex, vector.bobPublicKeyUncompressed)
for outcome in vector.outcomes {
switch outcome.ecdhVariant {
@@ -104,11 +107,11 @@ extension TwoVariantsOfECDHWithKDFTests {
for derivedKeys in outcome.derivedKeys {
let info = try XCTUnwrap(derivedKeys.info.data(using: .utf8))
let salt = try Data(hex: derivedKeys.salt)
- let x963 = x963DerivedSymmetricKey(secret: sharedSecretAB, using: hash, sharedInfo: info, outputByteCount: outputByteCount)
+ let x963 = sharedSecretAB.x963DerivedSymmetricKey(using: hash, sharedInfo: info, outputByteCount: outputByteCount)
x963.withUnsafeBytes {
XCTAssertEqual(Data($0).hex, derivedKeys.x963, "Wrong X963 KDF result, mismatched expected from vector.")
}
- let hkdf = hkdfDerivedSymmetricKey(secret: sharedSecretAB, using: hash, salt: salt, sharedInfo: info, outputByteCount: outputByteCount)
+ let hkdf = sharedSecretAB.hkdfDerivedSymmetricKey(using: hash, salt: salt, sharedInfo: info, outputByteCount: outputByteCount)
hkdf.withUnsafeBytes {
XCTAssertEqual(Data($0).hex, derivedKeys.hkdf, "Wrong HKDF result, mismatched expected from vector.")
}
@@ -122,108 +125,3 @@ extension TwoVariantsOfECDHWithKDFTests {
}
-
-//===----------------------------------------------------------------------===//
-//
-// This source file is part of the SwiftCrypto open source project
-//
-// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
-// Licensed under Apache License v2.0
-//
-// See LICENSE.txt for license information
-// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-//===----------------------------------------------------------------------===//
-
-// Copy pasted over from https://github.com/apple/swift-crypto/blob/main/Sources/Crypto/Key%20Agreement/DH.swift#L48
-
-/// Derives a symmetric encryption key using X9.63 key derivation.
-///
-/// - Parameters:
-/// - hashFunction: The Hash Function to use for key derivation.
-/// - sharedInfo: The Shared Info to use for key derivation.
-/// - outputByteCount: The length in bytes of resulting symmetric key.
-/// - Returns: The derived symmetric key
-public func x963DerivedSymmetricKey(
- secret: Data,
- using hashFunction: H.Type, sharedInfo: SI, outputByteCount: Int
-) -> SymmetricKey {
- // SEC1 defines 3 inputs to the KDF:
- //
- // 1. An octet string Z which is the shared secret value. That's `self` here.
- // 2. An integer `keydatalen` which is the length in octets of the keying data to be generated. Here that's `outputByteCount`.
- // 3. An optional octet string `SharedInfo` which consists of other shared data. Here, that's `sharedInfo`.
- //
- // We then need to perform the following steps:
- //
- // 1. Check that keydatalen < hashlen × (2³² − 1). If keydatalen ≥ hashlen × (2³² − 1), fail.
- // 2. Initiate a 4 octet, big-endian octet string Counter as 0x00000001.
- // 3. For i = 1 to ⌈keydatalen/hashlen⌉, do the following:
- // 1. Compute: Ki = Hash(Z || Counter || [SharedInfo]).
- // 2. Increment Counter.
- // 3. Increment i.
- // 4. Set K to be the leftmost keydatalen octets of: K1 || K2 || . . . || K⌈keydatalen/hashlen⌉.
- // 5. Output K.
- //
- // The loop in step 3 is not very Swifty, so instead we generate the counter directly.
- // Step 1: Check that keydatalen < hashlen × (2³² − 1).
- // We do this math in UInt64-space, because we'll overflow 32-bit integers.
- guard UInt64(outputByteCount) < (UInt64(H.Digest.byteCount) * UInt64(UInt32.max)) else {
- fatalError("Invalid parameter size")
- }
-
- var key = SecureBytes()
- key.reserveCapacity(outputByteCount)
-
- var remainingBytes = outputByteCount
- var counter = UInt32(1)
-
- while remainingBytes > 0 {
- // 1. Compute: Ki = Hash(Z || Counter || [SharedInfo]).
- var hasher = H()
- hasher.update(data: secret)
- hasher.update(counter.bigEndian)
- hasher.update(data: sharedInfo)
- let digest = hasher.finalize()
-
- // 2. Increment Counter.
- counter += 1
-
- // Append the bytes of the digest. We don't want to append more than the remaining number of bytes.
- let bytesToAppend = min(remainingBytes, H.Digest.byteCount)
- digest.withUnsafeBytes { digestPtr in
- key.append(digestPtr.prefix(bytesToAppend))
- }
- remainingBytes -= bytesToAppend
- }
-
- precondition(key.count == outputByteCount)
- return SymmetricKey(data: key)
-}
-
-extension HashFunction {
- mutating func update(_ counter: UInt32) {
- withUnsafeBytes(of: counter) {
- self.update(bufferPointer: $0)
- }
- }
-}
-
-
-/// From: https://github.com/apple/swift-crypto/blob/main/Sources/Crypto/Key%20Agreement/DH.swift#L110
-/// Derives a symmetric encryption key using HKDF key derivation.
-///
-/// - Parameters:
-/// - hashFunction: The Hash Function to use for key derivation.
-/// - salt: The salt to use for key derivation.
-/// - sharedInfo: The Shared Info to use for key derivation.
-/// - outputByteCount: The length in bytes of resulting symmetric key.
-/// - Returns: The derived symmetric key
-public func hkdfDerivedSymmetricKey(
- secret: Data,
- using hashFunction: H.Type, salt: Salt, sharedInfo: SI, outputByteCount: Int
-) -> SymmetricKey {
- HKDF.deriveKey(inputKeyMaterial: SymmetricKey(data: secret), salt: salt, info: sharedInfo, outputByteCount: outputByteCount)
-}
diff --git a/Tests/K1Tests/TestCases/ECDSA/ECDSARecoverableSignatureRoundtripTests.swift b/Tests/K1Tests/TestCases/ECDSA/ECDSARecoverableSignatureRoundtripTests.swift
index b97df30..ce4d8bc 100644
--- a/Tests/K1Tests/TestCases/ECDSA/ECDSARecoverableSignatureRoundtripTests.swift
+++ b/Tests/K1Tests/TestCases/ECDSA/ECDSARecoverableSignatureRoundtripTests.swift
@@ -11,10 +11,10 @@ import XCTest
final class ECDSARecoverableSignatureRoundtripTests: XCTestCase {
func testECDSARecoverable() throws {
- let alice = try K1.PrivateKey.generateNew()
+ let alice = K1.ECDSA.Recoverable.PrivateKey()
let message = "Send Bob 3 BTC".data(using: .utf8)!
- let signature = try alice.ecdsaSignRecoverable(unhashed: message)
- let isSignatureValid = try alice.publicKey.isValid(signature: signature, unhashed: message)
+ let signature = try alice.signature(forUnhashed: message)
+ let isSignatureValid = alice.publicKey.isValidSignature(signature, unhashed: message)
XCTAssertTrue(isSignatureValid, "Signature should be valid.")
}
diff --git a/Tests/K1Tests/TestCases/ECDSA/ECDSASignatureTests.swift b/Tests/K1Tests/TestCases/ECDSA/ECDSASignatureTests.swift
index a00fd6b..c2d1ccd 100644
--- a/Tests/K1Tests/TestCases/ECDSA/ECDSASignatureTests.swift
+++ b/Tests/K1Tests/TestCases/ECDSA/ECDSASignatureTests.swift
@@ -8,14 +8,40 @@
import Foundation
import K1
import XCTest
+import CryptoKit
final class ECDSASignatureTests: XCTestCase {
- func testECDSA() throws {
- let alice = try K1.PrivateKey.generateNew()
+ func testECDSADeterministic() throws {
+ let alice = K1.ECDSA.NonRecoverable.PrivateKey()
let message = "Send Bob 3 BTC".data(using: .utf8)!
- let signature = try alice.ecdsaSignNonRecoverable(unhashed: message)
- let isSignatureValid = try alice.publicKey.isValidECDSASignature(signature, unhashed: message)
+
+ let signature = try alice.signature(forUnhashed: message)
+
+ let isSignatureValid = alice.publicKey.isValidSignature(signature, unhashed: message)
XCTAssertTrue(isSignatureValid, "Signature should be valid.")
}
+ func testECDSARandom() throws {
+ continueAfterFailure = false
+ let alice = K1.ECDSA.NonRecoverable.PrivateKey()
+ let message = "Send Bob 3 BTC".data(using: .utf8)!
+
+ let requestedNumberOfSignatures = 1000
+ var signatures = Set()
+
+ for i in 0.. ResultOfTestGroup {
var numberOfTestsRun = 0
for vector in group.tests {
- let privateKey = try K1.PrivateKey.import(rawRepresentation: Data(hex: vector.privateKey))
- let publicKey = privateKey.publicKey
+ let privateKey = try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: Data(hex: vector.privateKey))
+ let publicKey: K1.ECDSA.NonRecoverable.PublicKey = privateKey.publicKey
let expectedSignature = try vector.expectedSignature()
let messageDigest = try vector.messageDigest()
- XCTAssertTrue(try publicKey.isValidECDSASignature(expectedSignature, digest: messageDigest))
+ XCTAssertTrue(publicKey.isValidSignature(expectedSignature, digest: messageDigest))
- let signatureFromMessage = try privateKey.ecdsaSignNonRecoverable(digest: messageDigest)
- let signatureRecoverableFromMessage = try privateKey.ecdsaSignRecoverable(digest: messageDigest)
+ let signatureFromMessage = try privateKey.signature(for: messageDigest)
XCTAssertEqual(signatureFromMessage, expectedSignature)
+
+ let signatureRandom = try privateKey.signature(
+ for: messageDigest,
+ options: .init(nonceFunction: .random)
+ )
+
+ XCTAssertNotEqual(signatureRandom, expectedSignature)
+ XCTAssertTrue(publicKey.isValidSignature(signatureRandom, digest: messageDigest))
+
+ let privateKeyRecoverable = try K1.ECDSA.Recoverable.PrivateKey(rawRepresentation: privateKey.rawRepresentation)
+ let signatureRecoverableFromMessage = try privateKeyRecoverable.signature(for: messageDigest)
try XCTAssertEqual(signatureRecoverableFromMessage.nonRecoverable(), expectedSignature)
let recid = try signatureRecoverableFromMessage.compact().recoveryID
- XCTAssertEqual(signatureRecoverableFromMessage.rawRepresentation.hex, expectedSignature.rawRepresentation.hex + "\(Data([UInt8(recid)]).hex)")
+ XCTAssertEqual(signatureRecoverableFromMessage.rawRepresentation.hex, expectedSignature.rawRepresentation.hex + "\(Data([UInt8(recid.rawValue)]).hex)")
numberOfTestsRun += 1
}
return .init(numberOfTestsRun: numberOfTestsRun, idsOmittedTests: [])
@@ -60,7 +71,7 @@ private extension XCTestCase {
private struct SignatureTrezorTestVector: SignatureTestVector {
typealias MessageDigest = SHA256.Digest
- typealias Signature = ECDSASignatureNonRecoverable
+ typealias Signature = K1.ECDSA.NonRecoverable.Signature
let msg: String
let privateKey: String
@@ -79,7 +90,7 @@ private struct SignatureTrezorTestVector: SignatureTestVector {
}
func expectedSignature() throws -> Signature {
let derData = try Data(hex: expected.der)
- let signature = try ECDSASignatureNonRecoverable.import(fromDER: derData)
+ let signature = try K1.ECDSA.NonRecoverable.Signature(derRepresentation: derData)
try XCTAssertEqual(signature.derRepresentation().hex, expected.der)
try XCTAssertEqual(
signature.compactRepresentation().hex,
@@ -89,6 +100,11 @@ private struct SignatureTrezorTestVector: SignatureTestVector {
].joined(separator: "")
)
+ try XCTAssertEqual(
+ K1.ECDSA.NonRecoverable.Signature(compactRepresentation: Data(hex: expected.r + expected.s)),
+ signature
+ )
+
return signature
}
diff --git a/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofASNDEREncodedSignaturesTests.swift b/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofASNDEREncodedSignaturesTests.swift
index 2b62344..0a3ddcb 100644
--- a/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofASNDEREncodedSignaturesTests.swift
+++ b/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofASNDEREncodedSignaturesTests.swift
@@ -24,12 +24,12 @@ final class ECDSA_Wycheproof_ASN_DER_EncodedSignaturesTests: XCTestCase {
func testWycheProofSecp256k1_DER() throws {
let result: TestResult = try testSuite(
/* https://github.com/google/wycheproof/blob/master/testvectors/ecdsa_secp256k1_sha256_test.json */
- jsonName: "ecdsa_secp256k1_sha256_der_test",
+ jsonName: "wycheproof_ecdsa_verify_der",
testFunction: { (group: ECDSAWycheTestGroup) in
try doTestGroup(
group: group,
- signatureValidationMode: .acceptSignatureMalleability,
+ signatureValidationMode: .init(malleabilityStrictness: .accepted),
hashFunction: SHA256.self,
skipIfContainsFlags: .init(["MissingZero", "BER"])
)
@@ -42,7 +42,7 @@ final class ECDSA_Wycheproof_ASN_DER_EncodedSignaturesTests: XCTestCase {
private struct SignatureWycheproofDERTestVector: WycheproofTestVector {
typealias MessageDigest = SHA256.Digest
- typealias Signature = ECDSASignatureNonRecoverable
+ typealias Signature = K1.ECDSA.NonRecoverable.Signature
let comment: String
let msg: String
@@ -57,7 +57,7 @@ private struct SignatureWycheproofDERTestVector: WycheproofTestVector {
}
func expectedSignature() throws -> Signature {
let derData = try Data(hex: sig)
- let signature = try ECDSASignatureNonRecoverable.import(fromDER: derData)
+ let signature = try K1.ECDSA.NonRecoverable.Signature(derRepresentation: derData)
if self.result == "valid" {
try XCTAssertEqual(sig, signature.derRepresentation().hex)
}
diff --git a/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofIEEEP1364RSEncodedSignaturesTests.swift b/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofIEEEP1364RSEncodedSignaturesTests.swift
index 9e66b0b..04c2a3c 100644
--- a/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofIEEEP1364RSEncodedSignaturesTests.swift
+++ b/Tests/K1Tests/TestCases/ECDSA/ECDSAWycheproofIEEEP1364RSEncodedSignaturesTests.swift
@@ -9,11 +9,11 @@ final class ECDSA_Wycheproof_IEEE_P1364_RS_EncodedSignaturesTests: XCTestCase {
let result: TestResult =
try testSuite(
/* https://github.com/google/wycheproof/blob/master/testvectors/ecdsa_secp256k1_sha256_test.json */
- jsonName: "ecdsa_secp256k1_sha256_p1363_RS_test",
+ jsonName: "wycheproof_ecdsa_verify_p1363",
testFunction: { (group: ECDSAWycheTestGroup) in
try doTestGroup(
group: group,
- signatureValidationMode: .acceptSignatureMalleability,
+ signatureValidationMode: .init(malleabilityStrictness: .accepted),
hashFunction: SHA256.self,
skipIfContainsFlags: .init(["MissingZero", "BER", "SigSize"]),
skipIfContainsComment: ["r too large"]
@@ -29,7 +29,7 @@ final class ECDSA_Wycheproof_IEEE_P1364_RS_EncodedSignaturesTests: XCTestCase {
private struct SignatureWycheproofP1364TestVector: WycheproofTestVector {
typealias MessageDigest = SHA256.Digest
- typealias Signature = ECDSASignatureNonRecoverable
+ typealias Signature = K1.ECDSA.NonRecoverable.Signature
let comment: String
let msg: String
diff --git a/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyEncodingTests.swift b/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyEncodingTests.swift
new file mode 100644
index 0000000..c416a2e
--- /dev/null
+++ b/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyEncodingTests.swift
@@ -0,0 +1,54 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-21.
+//
+import Foundation
+@testable import K1
+import XCTest
+
+
+final class PrivateKeyEncodingTests: XCTestCase {
+
+ func testRawRoundtrip() throws {
+ try doTest(
+ serialize: \.rawRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PrivateKey.init(rawRepresentation:)
+ )
+ }
+
+ func testx963Roundtrip() throws {
+ try doTest(
+ serialize: \.x963Representation,
+ deserialize: K1.ECDSA.NonRecoverable.PrivateKey.init(x963Representation:)
+ )
+ }
+
+ func testDERRoundtrip() throws {
+ try doTest(
+ serialize: \.derRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PrivateKey.init(derRepresentation:)
+ )
+ }
+
+ func testPEMRoundtrip() throws {
+ try doTest(
+ serialize: \.pemRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PrivateKey.init(pemRepresentation:)
+ )
+ }
+}
+
+private extension PrivateKeyEncodingTests {
+ func doTest(
+ serialize: KeyPath,
+ deserialize: (Enc) throws -> K1.ECDSA.NonRecoverable.PrivateKey
+ ) throws {
+ try doTestSerializationRoundtrip(
+ original: K1.ECDSA.NonRecoverable.PrivateKey(),
+ serialize: serialize,
+ deserialize: deserialize
+ )
+ }
+}
diff --git a/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyGenerationTests.swift b/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyGenerationTests.swift
index cf2cfd2..9f93c98 100644
--- a/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyGenerationTests.swift
+++ b/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyGenerationTests.swift
@@ -12,12 +12,12 @@ import XCTest
final class PrivateKeyGenerationTests: XCTestCase {
func testGenerationWorks() throws {
- XCTAssertNoThrow(try PrivateKey.generateNew())
+ XCTAssertNoThrow(K1.ECDSA.NonRecoverable.PrivateKey())
}
func testRandom() throws {
// The probability of two keys being identical is approximately: 1/2^256
- XCTAssertNotEqual(try PrivateKey.generateNew(), try PrivateKey.generateNew())
+ XCTAssertNotEqual(K1.ECDSA.NonRecoverable.PrivateKey(), K1.ECDSA.NonRecoverable.PrivateKey())
}
}
diff --git a/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyImportTests.swift b/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyImportTests.swift
index e5c3ede..f9d08f3 100644
--- a/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyImportTests.swift
+++ b/Tests/K1Tests/TestCases/Keys/PrivateKey/PrivateKeyImportTests.swift
@@ -14,23 +14,23 @@ final class PrivateKeyImportTests: XCTestCase {
func testAssertImportingPrivateKeyWithTooFewBytesThrowsError() throws {
let raw = try Data(hex: "deadbeef")
assert(
- try PrivateKey.import(rawRepresentation: raw),
- throws: K1.Error.invalidSizeOfPrivateKey(providedByteCount: raw.count)
+ try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: raw),
+ throws: K1.Error.failedToInitializePrivateKeyIncorrectByteCount(got: 4, expected: 32)
)
}
func testAssertImportingPrivateKeyWithTooManyBytesThrowsError() throws {
let raw = Data(repeating: 0xba, count: 33)
assert(
- try PrivateKey.import(rawRepresentation: raw),
- throws: K1.Error.invalidSizeOfPrivateKey(providedByteCount: raw.count)
+ try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: raw),
+ throws: K1.Error.failedToInitializePrivateKeyIncorrectByteCount(got: 33, expected: 32)
)
}
func testAssertImportingPrivateKeyZeroThrowsError() throws {
let raw = Data(repeating: 0x00, count: 32)
assert(
- try PrivateKey.import(rawRepresentation: raw),
+ try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: raw),
throws: K1.Error.invalidPrivateKeyMustNotBeZero
)
}
@@ -38,7 +38,7 @@ final class PrivateKeyImportTests: XCTestCase {
func testAssertImportingPrivateKeyCurveOrderThrowsError() throws {
let raw = try Data(hex: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
assert(
- try PrivateKey.import(rawRepresentation: raw),
+ try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: raw),
throws: K1.Error.invalidPrivateKeyMustBeSmallerThanOrder
)
}
@@ -46,17 +46,17 @@ final class PrivateKeyImportTests: XCTestCase {
func testAssertImportingPrivateKeyLargerThanCurveOrderThrowsError() throws {
let raw = Data(repeating: 0xff, count: 32)
assert(
- try PrivateKey.import(rawRepresentation: raw),
+ try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: raw),
throws: K1.Error.invalidPrivateKeyMustBeSmallerThanOrder
)
}
func testAssertPublicKeyOfImportedPrivateKey1() throws {
let privateKeyRaw = try Data(hex: "0000000000000000000000000000000000000000000000000000000000000001")
- let privateKey = try K1.PrivateKey.import(rawRepresentation: privateKeyRaw)
+ let privateKey = try K1.ECDSA.NonRecoverable.PrivateKey(rawRepresentation: privateKeyRaw)
// Easily verified by: https://bitaddress.org/
// Pretty well known key pair
- let expectedPublicKey = try K1.PublicKey.import(from: Data(hex: "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"))
+ let expectedPublicKey = try K1.ECDSA.NonRecoverable.PublicKey(x963Representation: Data(hex: "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"))
XCTAssertEqual(privateKey.publicKey, expectedPublicKey)
}
diff --git a/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyEncodingTests.swift b/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyEncodingTests.swift
new file mode 100644
index 0000000..3337af5
--- /dev/null
+++ b/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyEncodingTests.swift
@@ -0,0 +1,91 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-21.
+//
+
+import Foundation
+@testable import K1
+import XCTest
+
+
+final class PublicKeyEncodingTests: XCTestCase {
+
+ func test_pubkey_raw_is_x963_minus_prefix() throws {
+ let privateKey = K1.ECDSA.NonRecoverable.PrivateKey()
+ let publicKey = privateKey.publicKey
+
+ XCTAssertEqual(publicKey.rawRepresentation.hex, Data(publicKey.x963Representation.dropFirst()).hex)
+ }
+
+ func testRawRoundtrip() throws {
+ try doTest(
+ serialize: \.rawRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PublicKey.init(rawRepresentation:)
+ )
+ }
+
+ func testCompressedRoundtrip() throws {
+ try doTest(
+ serialize: \.compressedRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PublicKey.init(compressedRepresentation:)
+ )
+ }
+
+ func testx963Roundtrip() throws {
+ try doTest(
+ serialize: \.x963Representation,
+ deserialize: K1.ECDSA.NonRecoverable.PublicKey.init(x963Representation:)
+ )
+ }
+
+ func testDERRoundtrip() throws {
+ try doTest(
+ serialize: \.derRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PublicKey.init(derRepresentation:)
+ )
+ }
+
+ func testPEMRoundtrip() throws {
+ try doTest(
+ serialize: \.pemRepresentation,
+ deserialize: K1.ECDSA.NonRecoverable.PublicKey.init(pemRepresentation:)
+ )
+ }
+}
+
+private extension PublicKeyEncodingTests {
+ func doTest(
+ serialize: KeyPath,
+ deserialize: (Enc) throws -> K1.ECDSA.NonRecoverable.PublicKey
+ ) throws {
+ try doTestSerializationRoundtrip(
+ original: K1.ECDSA.NonRecoverable.PublicKey.generateNew(),
+ serialize: serialize,
+ deserialize: deserialize
+ )
+ }
+}
+
+extension K1.ECDSA.NonRecoverable.PublicKey {
+ static func generateNew() -> Self {
+ K1.ECDSA.NonRecoverable.PrivateKey().publicKey
+ }
+}
+
+public func doTestSerializationRoundtrip(
+ original makeOriginal: @autoclosure () -> T,
+ serialize: KeyPath,
+ deserialize: (Enc) throws -> T
+) throws where T: Equatable, Enc: Equatable {
+ for _ in 0 ..< 100 {
+ let original = makeOriginal()
+ let serialized = original[keyPath: serialize]
+ let deserialized = try deserialize(serialized)
+ XCTAssertEqual(deserialized, original)
+ let reserialized = deserialized[keyPath: serialize]
+ XCTAssertEqual(reserialized, serialized)
+
+ }
+}
diff --git a/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyImportTests.swift b/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyImportTests.swift
index 0f1251e..929d6cf 100644
--- a/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyImportTests.swift
+++ b/Tests/K1Tests/TestCases/Keys/PublicKey/PublicKeyImportTests.swift
@@ -13,46 +13,47 @@ final class PublicKeyImportTests: XCTestCase {
func testAssertImportingPublicKeyWithTooFewBytesThrowsError() throws {
let raw = try Data(hex: "deadbeef")
assert(
- try PublicKey.import(from: raw),
- throws: K1.Error.incorrectByteCountOfPublicKey(providedByteCount: raw.count)
+ try K1.ECDSA.NonRecoverable.PublicKey.init(x963Representation: raw),
+ throws: K1.Error.incorrectByteCountOfX963PublicKey(got: 4, expected: 65)
)
}
func testAssertImportingPublicKeyWithTooManyBytesThrowsError() throws {
let raw = Data(repeating: 0xde, count: 66)
assert(
- try PublicKey.import(from: raw),
- throws: K1.Error.incorrectByteCountOfPublicKey(providedByteCount: raw.count)
+ try K1.ECDSA.NonRecoverable.PublicKey(x963Representation: raw),
+ throws: K1.Error.incorrectByteCountOfX963PublicKey(got: 66, expected: 65)
)
}
func testAssertImportingInvalidUncompressedPublicKeyThrowsError() throws {
let raw = Data(repeating: 0x04, count: 65)
assert(
- try PublicKey.import(from: raw),
- throws: K1.Error.failedToParsePublicKeyFromBytes
+ try K1.ECDSA.NonRecoverable.PublicKey(x963Representation: raw),
+ throws: K1.Error.failedToDeserializePublicKey
)
}
func testAssertImportingInvalidCompressedPublicKeyThrowsError() throws {
let raw = Data(repeating: 0x03, count: 33)
assert(
- try PublicKey.import(from: raw),
- throws: K1.Error.failedToParsePublicKeyFromBytes
+ try K1.ECDSA.NonRecoverable.PublicKey(compressedRepresentation: raw),
+ throws: K1.Error.failedToDeserializePublicKey
)
}
func testAssertImportValidPublicKeyWorks() throws {
let raw = Data(repeating: 0x02, count: 33)
- let publicKey = try PublicKey.import(from: raw)
- XCTAssertEqual(publicKey.uncompressedRaw.hex, "040202020202020202020202020202020202020202020202020202020202020202415456f0fc01d66476251cab4525d9db70bfec652b2d8130608675674cde64b2")
+ let publicKey = try K1.ECDSA.NonRecoverable.PublicKey(compressedRepresentation: raw)
+ XCTAssertEqual(publicKey.compressedRepresentation.hex, "020202020202020202020202020202020202020202020202020202020202020202")
+ XCTAssertEqual(publicKey.x963Representation.hex, "040202020202020202020202020202020202020202020202020202020202020202415456f0fc01d66476251cab4525d9db70bfec652b2d8130608675674cde64b2")
}
func test_compress_pubkey() throws {
let raw = Data(repeating: 0x02, count: 33)
- let publicKey = try PublicKey.import(from: raw)
- try XCTAssertEqual(publicKey.rawRepresentation(format: .compressed).hex, "020202020202020202020202020202020202020202020202020202020202020202")
- try XCTAssertEqual(publicKey.rawRepresentation(format: .uncompressed).hex, "040202020202020202020202020202020202020202020202020202020202020202415456f0fc01d66476251cab4525d9db70bfec652b2d8130608675674cde64b2")
+ let publicKey = try K1.ECDSA.NonRecoverable.PublicKey(compressedRepresentation: raw)
+ XCTAssertEqual(publicKey.compressedRepresentation.hex, "020202020202020202020202020202020202020202020202020202020202020202")
+ XCTAssertEqual(publicKey.x963Representation.hex, "040202020202020202020202020202020202020202020202020202020202020202415456f0fc01d66476251cab4525d9db70bfec652b2d8130608675674cde64b2")
}
func testNotOnCurve() throws {
@@ -63,8 +64,8 @@ final class PublicKeyImportTests: XCTestCase {
let raw = try Data(hex: "040000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
assert(
- try PublicKey.import(from: raw),
- throws: K1.Error.failedToParsePublicKeyFromBytes
+ try K1.ECDSA.NonRecoverable.PublicKey(x963Representation: raw),
+ throws: K1.Error.failedToDeserializePublicKey
)
}
}
diff --git a/Tests/K1Tests/TestCases/Performance/PerformanceTests.swift b/Tests/K1Tests/TestCases/Performance/PerformanceTests.swift
new file mode 100644
index 0000000..dbe5abf
--- /dev/null
+++ b/Tests/K1Tests/TestCases/Performance/PerformanceTests.swift
@@ -0,0 +1,88 @@
+import Foundation
+import K1
+import XCTest
+
+final class PerformanceTests: XCTestCase {
+
+ func testPerformance() {
+ let message = [UInt8](repeating: 0xab, count: 32)
+ measure {
+ do {
+ for _ in 0 ..< 10 {
+ let schnorrPrivateKey = K1.Schnorr.PrivateKey()
+ let schnorrPublicKey = schnorrPrivateKey.publicKey
+
+ try XCTAssertEqual(
+ K1.Schnorr.PublicKey(compressedRepresentation: schnorrPublicKey.compressedRepresentation),
+ schnorrPublicKey
+ )
+ try XCTAssertEqual(
+ K1.Schnorr.PublicKey(x963Representation: schnorrPublicKey.x963Representation),
+ schnorrPublicKey
+ )
+
+ let ecdsaPrivateKey = K1.ECDSA.Recoverable.PrivateKey()
+ let ecdsaPublicKey = ecdsaPrivateKey.publicKey
+
+ try XCTAssertEqual(
+ K1.ECDSA.Recoverable.PublicKey(compressedRepresentation: ecdsaPublicKey.compressedRepresentation),
+ ecdsaPublicKey
+ )
+ try XCTAssertEqual(
+ K1.ECDSA.Recoverable.PublicKey(x963Representation: ecdsaPublicKey.x963Representation),
+ ecdsaPublicKey
+ )
+
+ let ecdsa = try ecdsaPrivateKey.signature(for: message)
+ XCTAssertTrue(
+ ecdsaPublicKey.isValidSignature(
+ ecdsa,
+ hashed: message
+ )
+ )
+ try XCTAssertEqual(
+ K1.ECDSA.Recoverable.Signature(compact: ecdsa.compact()),
+ ecdsa
+ )
+ try XCTAssertEqual(
+ K1.ECDSA.NonRecoverable.Signature(compactRepresentation: ecdsa.nonRecoverable().compactRepresentation()),
+ ecdsa.nonRecoverable()
+ )
+ try XCTAssertEqual(
+ K1.ECDSA.NonRecoverable.Signature(derRepresentation: ecdsa.nonRecoverable().derRepresentation()),
+ ecdsa.nonRecoverable()
+ )
+
+ let schnorr = try schnorrPrivateKey.signature(for: message)
+ XCTAssertTrue(
+ schnorrPublicKey.isValidSignature(
+ schnorr,
+ hashed: message
+ )
+ )
+ try XCTAssertEqual(
+ K1.Schnorr.Signature(rawRepresentation: schnorr.rawRepresentation),
+ schnorr
+ )
+
+ let alicePrivateKey = try K1.KeyAgreement.PrivateKey(x963Representation: ecdsaPrivateKey.x963Representation)
+ let alicePublicKey = alicePrivateKey.publicKey
+ let bobPrivateKey = K1.KeyAgreement.PrivateKey()
+ let bobPublicKey = bobPrivateKey.publicKey
+
+ var ab = try alicePrivateKey.sharedSecretFromKeyAgreement(with: bobPublicKey)
+ var ba = try bobPrivateKey.sharedSecretFromKeyAgreement(with: alicePublicKey)
+ XCTAssertEqual(ab, ba)
+ ab = try alicePrivateKey.ecdh(with: bobPublicKey)
+ ba = try bobPrivateKey.ecdh(with: alicePublicKey)
+ XCTAssertEqual(ab, ba)
+ }
+ } catch {
+ XCTFail("abort")
+ }
+ }
+
+ }
+
+}
+
diff --git a/Tests/K1Tests/TestCases/PublicKeyRecovery/ECDASignaturePublicKeyRecoveryTests.swift b/Tests/K1Tests/TestCases/PublicKeyRecovery/ECDASignaturePublicKeyRecoveryTests.swift
index 992a9e2..35a7c23 100644
--- a/Tests/K1Tests/TestCases/PublicKeyRecovery/ECDASignaturePublicKeyRecoveryTests.swift
+++ b/Tests/K1Tests/TestCases/PublicKeyRecovery/ECDASignaturePublicKeyRecoveryTests.swift
@@ -9,6 +9,7 @@ import Foundation
@testable import K1
import XCTest
+
/// Test vectors:
/// https://gist.github.com/webmaster128/130b628d83621a33579751846699ed15
final class ECDASignaturePublicKeyRecoveryTests: XCTestCase {
@@ -20,7 +21,7 @@ final class ECDASignaturePublicKeyRecoveryTests: XCTestCase {
func test_recovery_test_vectors() throws {
let result: TestResult = try testSuite(
- jsonName: "publickey_recovery",
+ jsonName: "warta_cyon_publickey_recovery",
testFunction: { (group: RecoveryTestGroup) in
try doTestGroup(group: group)
}
@@ -32,16 +33,41 @@ final class ECDASignaturePublicKeyRecoveryTests: XCTestCase {
func test_conversionRoundtrips() throws {
let recoverySignatureHex = "acf9e195e094f2f40eb619b9878817ff951b9b11fac37cf0d7290098bbefb574f8606281a2231a3fc781045f2ea4df086936263bbfa8d15ca17fe70e0c3d6e5601"
let recoverableSigRaw = try Data(hex: recoverySignatureHex)
- let recoverableSig = try ECDSASignatureRecoverable(rawRepresentation: recoverableSigRaw)
+ let recoverableSig = try K1.ECDSA.Recoverable.Signature(rawRepresentation: recoverableSigRaw)
+ XCTAssertEqual(recoverableSig.rawRepresentation.hex, recoverySignatureHex)
+
+ let compactRSV = "74b5efbb980029d7f07cc3fa119b1b95ff178887b919b60ef4f294e095e1f9ac566e3d0c0ee77fa15cd1a8bf3b26366908dfa42e5f0481c73f1a23a2816260f801"
+ try XCTAssertEqual(recoverableSig.compact().serialize(format: .rsv).hex, compactRSV)
+ let compactVRS = "0174b5efbb980029d7f07cc3fa119b1b95ff178887b919b60ef4f294e095e1f9ac566e3d0c0ee77fa15cd1a8bf3b26366908dfa42e5f0481c73f1a23a2816260f8"
+ try XCTAssertEqual(recoverableSig.compact().serialize(format: .vrs).hex, compactVRS)
+
+ try XCTAssertEqual(
+ recoverableSig.rawRepresentation.hex,
+ K1.ECDSA.Recoverable.Signature(compact: .init(rawRepresentation: Data(hex: compactVRS), format: .vrs)).rawRepresentation.hex
+ )
+
let compactRecoverableSig = try recoverableSig.compact()
- XCTAssertEqual(compactRecoverableSig.rs.hex, "74b5efbb980029d7f07cc3fa119b1b95ff178887b919b60ef4f294e095e1f9ac566e3d0c0ee77fa15cd1a8bf3b26366908dfa42e5f0481c73f1a23a2816260f8")
- XCTAssertEqual(compactRecoverableSig.recoveryID, 1)
- let nonRecoverable = try ECDSASignatureNonRecoverable(compactRepresentation: compactRecoverableSig.rs)
+ let compactRecoverableSigRSHex = "74b5efbb980029d7f07cc3fa119b1b95ff178887b919b60ef4f294e095e1f9ac566e3d0c0ee77fa15cd1a8bf3b26366908dfa42e5f0481c73f1a23a2816260f8"
+ let recid = try K1.ECDSA.Recoverable.Signature.RecoveryID(recid: 1)
+ XCTAssertEqual(compactRecoverableSig.compact.hex, compactRecoverableSigRSHex)
+ XCTAssertEqual(compactRecoverableSig.recoveryID, recid)
+
+ let compactRecoverableSigRS = try Data(hex: compactRecoverableSigRSHex)
+ try XCTAssertEqual(K1.ECDSA.Recoverable.Signature(compact: compactRecoverableSigRS, recoveryID: recid), K1.ECDSA.Recoverable.Signature(compact: compactRecoverableSig))
+ try XCTAssertEqual(K1.ECDSA.Recoverable.Signature.Compact.init(compact: compactRecoverableSigRS, recoveryID: recid), compactRecoverableSig)
+
+ let nonRecoverable = try K1.ECDSA.NonRecoverable.Signature(compactRepresentation: compactRecoverableSig.compact)
try XCTAssertEqual(nonRecoverable, recoverableSig.nonRecoverable())
let nonRecovDer = try nonRecoverable.derRepresentation()
- XCTAssertEqual(nonRecovDer.hex, "3044022074b5efbb980029d7f07cc3fa119b1b95ff178887b919b60ef4f294e095e1f9ac0220566e3d0c0ee77fa15cd1a8bf3b26366908dfa42e5f0481c73f1a23a2816260f8")
+ let nonRecoveryDERHex = "3044022074b5efbb980029d7f07cc3fa119b1b95ff178887b919b60ef4f294e095e1f9ac0220566e3d0c0ee77fa15cd1a8bf3b26366908dfa42e5f0481c73f1a23a2816260f8"
+ XCTAssertEqual(nonRecovDer.hex, nonRecoveryDERHex)
+
+ try XCTAssertEqual(K1.ECDSA.NonRecoverable.Signature(derRepresentation: Data(hex: nonRecoveryDERHex)), nonRecoverable)
+
+
+
}
}
@@ -54,12 +80,11 @@ private extension ECDASignaturePublicKeyRecoveryTests {
var numberOfTestsRun = 0
for vector in group.tests {
let publicKeyUncompressed = try [UInt8](hex: vector.publicKeyUncompressed)
- let expectedPublicKey = try K1.PublicKey(
- wrapped: .init(uncompressedRaw: publicKeyUncompressed)
- )
+ let expectedPublicKey = try K1.ECDSA.Recoverable.PublicKey.init(x963Representation: publicKeyUncompressed)
+
XCTAssertEqual(
- try [UInt8](hex: vector.publicKeyCompressed),
- try expectedPublicKey.rawRepresentation(format: .compressed)
+ try Data(hex: vector.publicKeyCompressed),
+ expectedPublicKey.compressedRepresentation
)
@@ -67,25 +92,22 @@ private extension ECDASignaturePublicKeyRecoveryTests {
try XCTAssertEqual(recoverableSig.compact().recoveryID, vector.recoveryID)
let hashedMessage = try Data(hex: vector.hashMessage)
- XCTAssertTrue(try expectedPublicKey.isValid(signature: recoverableSig, hashed: hashedMessage))
- XCTAssertTrue(try expectedPublicKey.isValid(signature: recoverableSig.nonRecoverable(), hashed: hashedMessage))
+ XCTAssertTrue(expectedPublicKey.isValidSignature(recoverableSig, hashed: hashedMessage))
try XCTAssertEqual(vector.recoveryID, recoverableSig.compact().recoveryID)
-
+
let recoveredPublicKey = try recoverableSig.recoverPublicKey(
- messageThatWasSigned: hashedMessage
+ message: hashedMessage
)
-
+
XCTAssertEqual(expectedPublicKey, recoveredPublicKey)
-
- XCTAssertTrue(try recoveredPublicKey.isValid(signature: recoverableSig, hashed: hashedMessage))
- XCTAssertTrue(try recoveredPublicKey.isValid(signature: recoverableSig.nonRecoverable(), hashed: hashedMessage))
-
+
+ XCTAssertTrue(recoveredPublicKey.isValidSignature(recoverableSig, hashed: hashedMessage))
+
let recoveredWithID = try recoverableSig.nonRecoverable().recoverPublicKey(
recoveryID: vector.recoveryID,
- messageThatWasSigned: hashedMessage
+ message: hashedMessage
)
- XCTAssertEqual(expectedPublicKey, recoveredWithID)
-
+ try XCTAssertEqual(expectedPublicKey, .init(x963Representation: recoveredWithID.x963Representation))
numberOfTestsRun += 1
}
@@ -99,19 +121,25 @@ private struct RecoveryTestGroup: Decodable {
struct IncorrectByteCount: Swift.Error {}
struct RecoveryTestVector: Decodable, Equatable {
- let recoveryID: Int
+ let recoveryID: K1.ECDSA.Recoverable.Signature.RecoveryID
let message: String
let hashMessage: String
private let signature: String
- func recoverableSignature() throws -> ECDSASignatureRecoverable {
- let raw = try Data(hex: self.signature)
- let signature = try ECDSASignatureRecoverable(rawRepresentation: raw)
- XCTAssertEqual(signature.rawRepresentation, raw)
- return signature
+ func recoverableSignature() throws -> K1.ECDSA.Recoverable.Signature {
+ try K1.ECDSA.Recoverable.Signature(
+ rawRepresentation: Data(hex: signature)
+ )
}
let publicKeyUncompressed: String
let publicKeyCompressed: String
}
+
+extension K1.ECDSA.Recoverable.Signature.RecoveryID: ExpressibleByIntegerLiteral {
+ public init(integerLiteral value: UInt8) {
+ self.init(rawValue: value)!
+ }
+}
+
diff --git a/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureBitcoinCoreTests.swift b/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureBitcoinCoreTests.swift
index ad2a7fe..67b1bff 100644
--- a/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureBitcoinCoreTests.swift
+++ b/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureBitcoinCoreTests.swift
@@ -60,7 +60,7 @@ final class SchnorrSignatureBitcoinCoreTests: XCTestCase {
func testSchnorrSignBitcoinVectors() throws {
let result: TestResult = try testSuite(
/* https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv */
- jsonName: "schnorr_secp256k1_sign_sha256_test",
+ jsonName: "bip340_schnorr_sign",
testFunction: { (group: SchnorrTestGroup) in
var numberOfTestsRun = 0
try group.tests.forEach(doTestSchnorrSign)
@@ -75,7 +75,7 @@ final class SchnorrSignatureBitcoinCoreTests: XCTestCase {
let result: TestResult =
try testSuite(
/* https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv */
- jsonName: "schnorr_secp256k1_verify_sha256_test",
+ jsonName: "bip340_schnorr_verify",
testFunction: { (group: SchnorrTestGroup) in
var numberOfTestsRun = 0
try group.tests.forEach(doTestSchnorrVerify)
@@ -90,43 +90,43 @@ final class SchnorrSignatureBitcoinCoreTests: XCTestCase {
private extension SchnorrSignatureBitcoinCoreTests {
func doTestSchnorrSign(vector: SchnorrTestSignVector) throws {
- let privateKey = try K1.PrivateKey.import(
+ let privateKey = try K1.Schnorr.PrivateKey(
rawRepresentation: Data(hex: vector.privateKeyHex)
)
let publicKey = privateKey.publicKey
- let expectedPublicKey = try K1.PublicKey.import(from: Data(hex: vector.publicKeyHex))
+ let expectedPublicKey = try K1.Schnorr.PublicKey(compressedRepresentation: Data(hex: vector.publicKeyHex))
XCTAssertEqual(publicKey, expectedPublicKey)
XCTAssertEqual(
- try! publicKey.rawRepresentation(format: .compressed).hex.lowercased(),
- try! expectedPublicKey.rawRepresentation(format: .compressed).hex.lowercased()
- )
+ publicKey.compressedRepresentation,
+ expectedPublicKey.compressedRepresentation
+ )
let message = try Data(hex: vector.messageHex)
- let signature = try privateKey.schnorrSign(
- hashed: message,
- input: .init(auxilaryRandomData: Data(hex: vector.auxDataHex))
+ let signature = try privateKey.signature(
+ for: message,
+ options: .init(auxilaryRandomData: .init(aux: Data(hex: vector.auxDataHex)))
)
- let expectedSig = try SchnorrSignature(rawRepresentation: Data(hex: vector.signatureCompact))
+ let expectedSig = try K1.Schnorr.Signature(rawRepresentation: Data(hex: vector.signatureCompact))
- XCTAssertEqual(signature, expectedSig)
+ XCTAssertEqual(signature.rawRepresentation.hex, vector.signatureCompact)
XCTAssertEqual(
- try publicKey.isValidSchnorrSignature(expectedSig, hashed: message),
+ publicKey.isValidSignature(expectedSig, hashed: message),
vector.isValid
)
}
func doTestSchnorrVerify(vector: SchnorrTestVector) throws {
- func parsePublicKey() throws -> PublicKey {
- try PublicKey.import(from: Data(hex: vector.publicKeyHex))
+ func parsePublicKey() throws -> K1.Schnorr.PublicKey {
+ try .init(compressedRepresentation: Data(hex: vector.publicKeyHex))
}
guard !vector.invalidPublicKey else {
XCTAssertThrowsError(try parsePublicKey(), "") { anyError in
if let error = anyError as? K1.Error {
- XCTAssertEqual(error, K1.Error.failedToParsePublicKeyFromBytes)
+ XCTAssertEqual(error, K1.Error.failedToDeserializePublicKey)
} else {
XCTFail("Failed to cast error")
}
@@ -135,9 +135,9 @@ private extension SchnorrSignatureBitcoinCoreTests {
}
let publicKey = try parsePublicKey()
- let signature = try SchnorrSignature(rawRepresentation: Data(hex: vector.signatureCompact))
+ let signature = try K1.Schnorr.Signature(rawRepresentation: Data(hex: vector.signatureCompact))
- let validSignature = try publicKey.isValidSchnorrSignature(
+ let validSignature = try publicKey.isValidSignature(
signature, hashed: Data(hex: vector.messageHex)
)
diff --git a/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureTests.swift b/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureTests.swift
index 87b9ec4..e401a37 100644
--- a/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureTests.swift
+++ b/Tests/K1Tests/TestCases/Schnorr/SchnorrSignatureTests.swift
@@ -8,14 +8,15 @@
import Foundation
import K1
import XCTest
+import CryptoKit
final class SchnorrSignatureTests: XCTestCase {
func testSchnorr() throws {
- let alice = try K1.PrivateKey.generateNew()
+ let alice = K1.Schnorr.PrivateKey()
let message = "Send Bob 3 BTC".data(using: .utf8)!
- let signature = try alice.schnorrSign(unhashed: message)
- let isSignatureValid = try alice.publicKey.isValidSchnorrSignature(signature, unhashed: message)
+ let signature = try alice.signature(forUnhashed: message)
+ let isSignatureValid = alice.publicKey.isValidSignature(signature, unhashed: message)
XCTAssertTrue(isSignatureValid, "Signature should be valid.")
}
diff --git a/Tests/K1Tests/TestVectors/schnorr_secp256k1_sign_sha256_test.json b/Tests/K1Tests/TestVectors/bip340_schnorr_sign.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/schnorr_secp256k1_sign_sha256_test.json
rename to Tests/K1Tests/TestVectors/bip340_schnorr_sign.json
diff --git a/Tests/K1Tests/TestVectors/schnorr_secp256k1_verify_sha256_test.json b/Tests/K1Tests/TestVectors/bip340_schnorr_verify.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/schnorr_secp256k1_verify_sha256_test.json
rename to Tests/K1Tests/TestVectors/bip340_schnorr_verify.json
diff --git a/Tests/K1Tests/TestVectors/ecdh_secp256k1_two_variants_with_kdf_test.json b/Tests/K1Tests/TestVectors/cyon_ecdh_two_variants_with_kdf.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/ecdh_secp256k1_two_variants_with_kdf_test.json
rename to Tests/K1Tests/TestVectors/cyon_ecdh_two_variants_with_kdf.json
diff --git a/Tests/K1Tests/TestVectors/ecdsa_secp256k1_sha256_rfc6979_trezor_test.json b/Tests/K1Tests/TestVectors/trezor_ecdsa_sign_rfc6979.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/ecdsa_secp256k1_sha256_rfc6979_trezor_test.json
rename to Tests/K1Tests/TestVectors/trezor_ecdsa_sign_rfc6979.json
diff --git a/Tests/K1Tests/TestVectors/publickey_recovery.json b/Tests/K1Tests/TestVectors/warta_cyon_publickey_recovery.json
similarity index 99%
rename from Tests/K1Tests/TestVectors/publickey_recovery.json
rename to Tests/K1Tests/TestVectors/warta_cyon_publickey_recovery.json
index 083358e..3f411cf 100644
--- a/Tests/K1Tests/TestVectors/publickey_recovery.json
+++ b/Tests/K1Tests/TestVectors/warta_cyon_publickey_recovery.json
@@ -1,5 +1,7 @@
{
"algorithm": "ecdsa public key recovery",
+ "authors": ["Simon Warta", "Alexander Cyon"],
+ "origin": "",
"numberOfTests": 10,
"testGroups": [
{
diff --git a/Tests/K1Tests/TestVectors/ecdh_secp256k1_test.json b/Tests/K1Tests/TestVectors/wycheproof_ecdh_ASN1x963.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/ecdh_secp256k1_test.json
rename to Tests/K1Tests/TestVectors/wycheproof_ecdh_ASN1x963.json
diff --git a/Tests/K1Tests/TestVectors/ecdsa_secp256k1_sha256_der_test.json b/Tests/K1Tests/TestVectors/wycheproof_ecdsa_verify_der.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/ecdsa_secp256k1_sha256_der_test.json
rename to Tests/K1Tests/TestVectors/wycheproof_ecdsa_verify_der.json
diff --git a/Tests/K1Tests/TestVectors/ecdsa_secp256k1_sha256_p1363_RS_test.json b/Tests/K1Tests/TestVectors/wycheproof_ecdsa_verify_p1363.json
similarity index 100%
rename from Tests/K1Tests/TestVectors/ecdsa_secp256k1_sha256_p1363_RS_test.json
rename to Tests/K1Tests/TestVectors/wycheproof_ecdsa_verify_p1363.json
diff --git a/Tests/K1Tests/Util/Data+Hex.swift b/Tests/K1Tests/Util/Data+Hex.swift
deleted file mode 100644
index aca0755..0000000
--- a/Tests/K1Tests/Util/Data+Hex.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-02-01.
-//
-
-import Foundation
-@testable import K1
-
-public extension Data {
- init(hex: String) throws {
- try self.init(Array(hex: hex))
- }
-
-}
diff --git a/Tests/K1Tests/Util/ECSignature.swift b/Tests/K1Tests/Util/ECSignature.swift
new file mode 100644
index 0000000..df0c297
--- /dev/null
+++ b/Tests/K1Tests/Util/ECSignature.swift
@@ -0,0 +1,13 @@
+//
+// File.swift
+//
+//
+// Created by Alexander Cyon on 2023-03-19.
+//
+
+import Foundation
+import K1
+
+public protocol ECSignature {}
+extension K1.ECDSA.NonRecoverable.Signature: ECSignature {}
+extension K1.ECDSA.Recoverable.Signature: ECSignature {}
diff --git a/Tests/K1Tests/Util/PublicKey+DERDecode.swift b/Tests/K1Tests/Util/PublicKey+DERDecode.swift
deleted file mode 100644
index e5e4942..0000000
--- a/Tests/K1Tests/Util/PublicKey+DERDecode.swift
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexander Cyon on 2022-01-31.
-//
-
-import Foundation
-@testable import K1
-
-import ASN1Decoder
-
-extension K1.PublicKey {
- enum WycheproofDERDecodeError: Swift.Error {
- case expectedFirstElementToBeSequence
- case expectedTopLevelObjectToHaveSeqeuenceTag
- case expectedTopLevelSequenceWithElementCount(of: Int, butGot: Int)
- case elementLacksIdentifier(elementIndex: Int)
- case expectedBitString(atIndex: Int)
- case failedToCastBitstringToData
- }
-
- private static func importPublicKeyDataFromASN1Object(der: Data) throws -> Data {
-
- let asn1Objects = try ASN1DERDecoder.decode(data: der)
-
- guard asn1Objects.count == 1 else {
- throw WycheproofDERDecodeError.expectedTopLevelSequenceWithElementCount(of: 1, butGot: asn1Objects.count)
- }
-
- guard
- let topLevelObjectIdentifier = asn1Objects[0].identifier,
- case let topLevelObjectTag = topLevelObjectIdentifier.tagNumber(),
- topLevelObjectTag == .sequence else {
- throw WycheproofDERDecodeError.expectedTopLevelObjectToHaveSeqeuenceTag
- }
-
-
- let bitstringObjectElementIndex = 1
- let expectedElementCount = bitstringObjectElementIndex + 1
-
- guard asn1Objects[0].subCount() == expectedElementCount else {
- throw WycheproofDERDecodeError.expectedTopLevelSequenceWithElementCount(of: expectedElementCount, butGot: asn1Objects.count)
- }
-
- guard let bitstringObject = asn1Objects[0].sub(bitstringObjectElementIndex) else {
- throw WycheproofDERDecodeError.expectedBitString(atIndex: bitstringObjectElementIndex)
- }
-
- guard let bitstringObjectIdentifier = bitstringObject.identifier else {
- throw WycheproofDERDecodeError.elementLacksIdentifier(elementIndex: bitstringObjectElementIndex)
- }
-
- guard
- case let bitstringObjectTag = bitstringObjectIdentifier.tagNumber(),
- bitstringObjectTag == .bitString
- else {
- throw WycheproofDERDecodeError.expectedBitString(atIndex: bitstringObjectElementIndex)
- }
-
- guard let bitstringData = bitstringObject.value as? Data else {
- throw WycheproofDERDecodeError.failedToCastBitstringToData
- }
-
- return bitstringData
-
- }
-
-
- static func `import`(der: Data) throws -> Self {
- let publicKeyData = try importPublicKeyDataFromASN1Object(der: der)
- return try Self.import(from: publicKeyData)
- }
-}
diff --git a/Tests/K1Tests/Util/Wycheproof.swift b/Tests/K1Tests/Util/Wycheproof.swift
index cdef77e..568d83f 100644
--- a/Tests/K1Tests/Util/Wycheproof.swift
+++ b/Tests/K1Tests/Util/Wycheproof.swift
@@ -16,6 +16,7 @@
//===----------------------------------------------------------------------===//
import XCTest
import CryptoKit
+
@testable import K1
struct TestSuite: Decodable {
@@ -65,13 +66,11 @@ struct ResultOfTestGroup {
let idsOmittedTests: [Int]
}
-
-
extension XCTestCase {
func doTestGroup(
group: ECDSAWycheTestGroup,
- signatureValidationMode: SignatureValidationMode,
+ signatureValidationMode: K1.ECDSA.ValidationOptions = .default,
hashFunction: HF.Type,
skipIfContainsFlags: [String] = [],
skipIfContainsComment: [String] = [],
@@ -83,7 +82,42 @@ extension XCTestCase {
throw ECDSASignatureTestError(description: errorMessage)
}
let keyBytes = try Array(hex: group.key.uncompressed)
- let key = try PublicKey(x963Representation: keyBytes)
+ let key = try K1.ECDSA.NonRecoverable.PublicKey(x963Representation: keyBytes)
+
+ let keyFromDER = try K1.ECDSA.NonRecoverable.PublicKey(derRepresentation: Data(hex: group.keyDer))
+ XCTAssertEqual(key.derRepresentation.hex, group.keyDer)
+ XCTAssertEqual(keyFromDER, key)
+
+ let keyFromPEM = try K1.ECDSA.NonRecoverable.PublicKey(pemRepresentation: group.keyPem)
+ XCTAssertEqual(key.pemRepresentation, group.keyPem)
+ XCTAssertEqual(keyFromPEM, key)
+
+
+ let compactXRaw = try Data(hex: group.key.wx)
+ let compactYRaw = try Data(hex: group.key.wy)
+ func ensure32Bytes(_ compactComponent: Data) throws -> Data {
+ if compactComponent.count == Curve.Field.byteCount {
+ return compactComponent
+ }
+
+ var compactComponent = [UInt8](compactComponent)
+ while compactComponent.count < Curve.Field.byteCount {
+ compactComponent = [0x00] + compactComponent
+ }
+ while compactComponent.count > Curve.Field.byteCount {
+ guard compactComponent.first == 0x00 else {
+ throw BadKeyComponent()
+ }
+ compactComponent = [UInt8](compactComponent.dropFirst())
+ }
+ return Data(compactComponent)
+ }
+ let xOnly = try ensure32Bytes(compactXRaw)
+ let yOnly = try ensure32Bytes(compactYRaw)
+
+ let fromRaw = try K1.ECDSA.NonRecoverable.PublicKey(rawRepresentation: xOnly + yOnly)
+ XCTAssertEqual(fromRaw, key)
+
var numberOfTestsRun = 0
var idsOfOmittedTests = Array()
outerloop: for testVector in group.tests {
@@ -106,10 +140,10 @@ extension XCTestCase {
let signature = try testVector.expectedSignature()
let messageDigest = try testVector.messageDigest()
- isValid = try key.isValidECDSASignature(
+ isValid = key.isValidSignature(
signature,
digest: messageDigest,
- mode: signatureValidationMode
+ options: signatureValidationMode
)
} catch {
let expectedFailure = testVector.result == "invalid" || testVector.result == "acceptable"
@@ -152,21 +186,26 @@ struct ECDSATestGroup: Codable {
struct ECDSAWycheTestGroup: Codable {
let tests: [TV]
let key: ECDSAKey
+ let keyDer: String
+ let keyPem: String
}
struct ECDSAKey: Codable {
let uncompressed: String
+ let wx: String
+ let wy: String
let curve: String
}
+
protocol SignatureTestVector: Codable {
associatedtype MessageDigest: Digest
associatedtype Signature: ECSignature
func messageDigest() throws -> MessageDigest
func expectedSignature() throws -> Signature
}
-protocol WycheproofTestVector: SignatureTestVector where Signature == ECDSASignatureNonRecoverable {
+protocol WycheproofTestVector: SignatureTestVector where Signature == K1.ECDSA.NonRecoverable.Signature {
var flags: [String] { get }
var tcId: Int { get }
var result: String { get }
@@ -175,15 +214,8 @@ protocol WycheproofTestVector: SignatureTestVector where Signature == ECDSASigna
}
-typealias PublicKey = K1.PublicKey
-extension PublicKey {
- init(x963Representation: [UInt8]) throws {
- self = try Self.import(from: x963Representation)
- }
-}
-typealias PrivateKey = K1.PrivateKey
-
struct ECDSASignatureTestError: Swift.Error, CustomStringConvertible {
let description: String
}
+struct BadKeyComponent: Swift.Error {}
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
deleted file mode 100644
index 86364e5..0000000
--- a/Tests/LinuxMain.swift
+++ /dev/null
@@ -1 +0,0 @@
-fatalError("Please use --enable-test-discovery to run the tests instead")
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..daf5136
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+
+set -eu
+LIB="Sources/secp256k1/libsecp256k1"
+here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd "$here"/..
+rm -rf $LIB
+
+git submodule init
+git submodule update