From 4c77c7384768acf1093d66ccaacf298d322b10b7 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 21 Dec 2023 15:27:01 -0800 Subject: [PATCH] Initial Swift API for PEM format (#453) * Initial Swift API for PEM format * Created PEM keys from OpenSSL for unit tests * Updated readme --- .swiftlint.yml | 12 + README.md | 19 +- Sources/secp256k1/ASN1/ASN1.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Any.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1BitString.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Boolean.swift | 1 + .../Basic ASN1 Types/ASN1Identifier.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Integer.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Null.swift | 1 + .../Basic ASN1 Types/ASN1OctetString.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Strings.swift | 1 + .../Basic ASN1 Types/ArraySliceBigint.swift | 1 + .../Basic ASN1 Types/GeneralizedTime.swift | 1 + .../Basic ASN1 Types/ObjectIdentifier.swift | 1 + Sources/secp256k1/ASN1/ECDSASignature.swift | 1 + Sources/secp256k1/ASN1/PEMDocument.swift | 1 + Sources/secp256k1/ASN1/PKCS8PrivateKey.swift | 1 + Sources/secp256k1/ASN1/SEC1PrivateKey.swift | 1 + .../secp256k1/ASN1/SubjectPublicKeyInfo.swift | 1 + Sources/secp256k1/CryptoKitErrors.swift | 1 + Sources/zkp/ASN1/ASN1.swift | 1 + .../zkp/ASN1/Basic ASN1 Types/ASN1Any.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1BitString.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Boolean.swift | 1 + .../Basic ASN1 Types/ASN1Identifier.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Integer.swift | 1 + .../zkp/ASN1/Basic ASN1 Types/ASN1Null.swift | 1 + .../Basic ASN1 Types/ASN1OctetString.swift | 1 + .../ASN1/Basic ASN1 Types/ASN1Strings.swift | 1 + .../Basic ASN1 Types/ArraySliceBigint.swift | 1 + .../Basic ASN1 Types/GeneralizedTime.swift | 1 + .../Basic ASN1 Types/ObjectIdentifier.swift | 231 ++++++++++++++++++ Sources/zkp/ASN1/ECDSASignature.swift | 1 + Sources/zkp/ASN1/PEMDocument.swift | 1 + Sources/zkp/ASN1/PKCS8PrivateKey.swift | 1 + Sources/zkp/ASN1/SEC1PrivateKey.swift | 121 +++++++++ Sources/zkp/ASN1/SubjectPublicKeyInfo.swift | 137 +++++++++++ Sources/zkp/Asymmetric.swift | 78 ++++++ Sources/zkp/CryptoKitErrors.swift | 1 + Sources/zkp/Errors.swift | 10 +- Tests/zkpTests/secp256k1Tests.swift | 75 +++++- 41 files changed, 708 insertions(+), 8 deletions(-) create mode 120000 Sources/secp256k1/ASN1/ASN1.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Any.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1BitString.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Boolean.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Identifier.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Integer.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Null.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1OctetString.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Strings.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ArraySliceBigint.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/GeneralizedTime.swift create mode 120000 Sources/secp256k1/ASN1/Basic ASN1 Types/ObjectIdentifier.swift create mode 120000 Sources/secp256k1/ASN1/ECDSASignature.swift create mode 120000 Sources/secp256k1/ASN1/PEMDocument.swift create mode 120000 Sources/secp256k1/ASN1/PKCS8PrivateKey.swift create mode 120000 Sources/secp256k1/ASN1/SEC1PrivateKey.swift create mode 120000 Sources/secp256k1/ASN1/SubjectPublicKeyInfo.swift create mode 120000 Sources/secp256k1/CryptoKitErrors.swift create mode 120000 Sources/zkp/ASN1/ASN1.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1Any.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1BitString.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1Boolean.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1Identifier.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1Integer.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1Null.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1OctetString.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ASN1Strings.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/ArraySliceBigint.swift create mode 120000 Sources/zkp/ASN1/Basic ASN1 Types/GeneralizedTime.swift create mode 100644 Sources/zkp/ASN1/Basic ASN1 Types/ObjectIdentifier.swift create mode 120000 Sources/zkp/ASN1/ECDSASignature.swift create mode 120000 Sources/zkp/ASN1/PEMDocument.swift create mode 120000 Sources/zkp/ASN1/PKCS8PrivateKey.swift create mode 100644 Sources/zkp/ASN1/SEC1PrivateKey.swift create mode 100644 Sources/zkp/ASN1/SubjectPublicKeyInfo.swift create mode 120000 Sources/zkp/CryptoKitErrors.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 25229e0..2a34a2e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -132,7 +132,19 @@ included: # paths to include during linting. `--path` is ignored if present. - Sources - Tests excluded: # paths to ignore during linting. Takes precedence over `included`. + - Sources/**/ArraySliceBigint.swift + - Sources/**/ASN1.swift + - Sources/**/ASN1BitString.swift + - Sources/**/ASN1Boolean.swift + - Sources/**/ASN1Identifier.swift + - Sources/**/ASN1Integer.swift + - Sources/**/ASN1OctetString.swift + - Sources/**/ASN1Strings.swift - Sources/**/Digest.swift + - Sources/**/ECDSASignature.swift + - Sources/**/GeneralizedTime.swift + - Sources/**/PEMDocument.swift + - Sources/**/PKCS8PrivateKey.swift - Sources/**/PrettyBytes.swift - Sources/**/RNG_boring.swift - Sources/**/SecureBytes.swift diff --git a/README.md b/README.md index 1a79c75..478f2cf 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Long-term goals are: This repository primarily uses Swift package manager as its build tool, so we recommend using that as well. Xcode comes with [built-in support](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) for Swift packages. From the menu bar, goto: `File > Add Packages...` If you manage packages via a `Package.swift` file, simply add `secp256k1.swift` as a dependencies' clause in your Swift manifest: ```swift -.package(name: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.13.0"), +.package(name: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.15.0"), ``` Include `secp256k1` as a dependency for your executable target: @@ -136,7 +136,7 @@ let publicKey = try! secp256k1.Recovery.PublicKey(messageData, signature: recove let signature = try! recoverySignature.normalize ``` -# Combine Public Keys +## Combine Public Keys ```swift let privateKey = try! secp256k1.Signing.PrivateKey() @@ -146,6 +146,21 @@ let publicKey = try! secp256k1.Signing.PrivateKey().public publicKey.combine([privateKey.publicKey], format: .uncompressed) ``` +## PEM Key Format + +```swift +let privateKeyString = """ +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK +oUQDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMAcjHIrTDS6HEELgguOatmFBOp +2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g== +-----END EC PRIVATE KEY----- +""" + +// Import keys generated from OpenSSL +let privateKey = try! secp256k1.Signing.PrivateKey(pemRepresentation: privateKeyString) +``` + # Danger These APIs should not be considered stable and may change at any time. diff --git a/Sources/secp256k1/ASN1/ASN1.swift b/Sources/secp256k1/ASN1/ASN1.swift new file mode 120000 index 0000000..38150aa --- /dev/null +++ b/Sources/secp256k1/ASN1/ASN1.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ASN1.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Any.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Any.swift new file mode 120000 index 0000000..49089c2 --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Any.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Any.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1BitString.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1BitString.swift new file mode 120000 index 0000000..117c474 --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1BitString.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Boolean.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Boolean.swift new file mode 120000 index 0000000..7e29f7a --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Boolean.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Identifier.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Identifier.swift new file mode 120000 index 0000000..bac2e57 --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Identifier.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Integer.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Integer.swift new file mode 120000 index 0000000..4f10111 --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Integer.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Null.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Null.swift new file mode 120000 index 0000000..17c6ee4 --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Null.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Null.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1OctetString.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1OctetString.swift new file mode 120000 index 0000000..0044c8c --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1OctetString.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Strings.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Strings.swift new file mode 120000 index 0000000..998da6f --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ASN1Strings.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ArraySliceBigint.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ArraySliceBigint.swift new file mode 120000 index 0000000..a3b2cee --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ArraySliceBigint.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/GeneralizedTime.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/GeneralizedTime.swift new file mode 120000 index 0000000..5b54f7c --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/GeneralizedTime.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/Basic ASN1 Types/ObjectIdentifier.swift b/Sources/secp256k1/ASN1/Basic ASN1 Types/ObjectIdentifier.swift new file mode 120000 index 0000000..74978e2 --- /dev/null +++ b/Sources/secp256k1/ASN1/Basic ASN1 Types/ObjectIdentifier.swift @@ -0,0 +1 @@ +../../../zkp/ASN1/Basic ASN1 Types/ObjectIdentifier.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/ECDSASignature.swift b/Sources/secp256k1/ASN1/ECDSASignature.swift new file mode 120000 index 0000000..1d9bb92 --- /dev/null +++ b/Sources/secp256k1/ASN1/ECDSASignature.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ECDSASignature.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/PEMDocument.swift b/Sources/secp256k1/ASN1/PEMDocument.swift new file mode 120000 index 0000000..889039c --- /dev/null +++ b/Sources/secp256k1/ASN1/PEMDocument.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PEMDocument.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/PKCS8PrivateKey.swift b/Sources/secp256k1/ASN1/PKCS8PrivateKey.swift new file mode 120000 index 0000000..e040588 --- /dev/null +++ b/Sources/secp256k1/ASN1/PKCS8PrivateKey.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PKCS8PrivateKey.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/SEC1PrivateKey.swift b/Sources/secp256k1/ASN1/SEC1PrivateKey.swift new file mode 120000 index 0000000..00a4759 --- /dev/null +++ b/Sources/secp256k1/ASN1/SEC1PrivateKey.swift @@ -0,0 +1 @@ +../../zkp/ASN1/SEC1PrivateKey.swift \ No newline at end of file diff --git a/Sources/secp256k1/ASN1/SubjectPublicKeyInfo.swift b/Sources/secp256k1/ASN1/SubjectPublicKeyInfo.swift new file mode 120000 index 0000000..ef5d73d --- /dev/null +++ b/Sources/secp256k1/ASN1/SubjectPublicKeyInfo.swift @@ -0,0 +1 @@ +../../zkp/ASN1/SubjectPublicKeyInfo.swift \ No newline at end of file diff --git a/Sources/secp256k1/CryptoKitErrors.swift b/Sources/secp256k1/CryptoKitErrors.swift new file mode 120000 index 0000000..8bd86c8 --- /dev/null +++ b/Sources/secp256k1/CryptoKitErrors.swift @@ -0,0 +1 @@ +../../Submodules/swift-crypto/Sources/Crypto/CryptoKitErrors.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/ASN1.swift b/Sources/zkp/ASN1/ASN1.swift new file mode 120000 index 0000000..38150aa --- /dev/null +++ b/Sources/zkp/ASN1/ASN1.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ASN1.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Any.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Any.swift new file mode 120000 index 0000000..49089c2 --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Any.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Any.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1BitString.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1BitString.swift new file mode 120000 index 0000000..117c474 --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1BitString.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Boolean.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Boolean.swift new file mode 120000 index 0000000..7e29f7a --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Boolean.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Identifier.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Identifier.swift new file mode 120000 index 0000000..bac2e57 --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Identifier.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Integer.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Integer.swift new file mode 120000 index 0000000..4f10111 --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Integer.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Null.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Null.swift new file mode 120000 index 0000000..17c6ee4 --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Null.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Null.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1OctetString.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1OctetString.swift new file mode 120000 index 0000000..0044c8c --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1OctetString.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Strings.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Strings.swift new file mode 120000 index 0000000..998da6f --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ASN1Strings.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ArraySliceBigint.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ArraySliceBigint.swift new file mode 120000 index 0000000..a3b2cee --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ArraySliceBigint.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/GeneralizedTime.swift b/Sources/zkp/ASN1/Basic ASN1 Types/GeneralizedTime.swift new file mode 120000 index 0000000..5b54f7c --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/GeneralizedTime.swift @@ -0,0 +1 @@ +../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/Basic ASN1 Types/ObjectIdentifier.swift b/Sources/zkp/ASN1/Basic ASN1 Types/ObjectIdentifier.swift new file mode 100644 index 0000000..feacba9 --- /dev/null +++ b/Sources/zkp/ASN1/Basic ASN1 Types/ObjectIdentifier.swift @@ -0,0 +1,231 @@ +// +// ObjectIdentifier.swift +// GigaBitcoin/secp256k1.swift +// +// Modifications Copyright (c) 2023 GigaBitcoin LLC +// Distributed under the MIT software license +// +// See the accompanying file LICENSE for information +// +// +// NOTICE: THIS FILE HAS BEEN MODIFIED BY GigaBitcoin LLC +// UNDER COMPLIANCE WITH THE APACHE 2.0 LICENSE FROM THE +// ORIGINAL WORK OF THE COMPANY Apple Inc. +// +// THE FOLLOWING IS THE COPYRIGHT OF THE ORIGINAL DOCUMENT: +// +// +//===----------------------------------------------------------------------===// +// +// 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 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 var .primitive(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 { + try subcomponents.append(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 + Self.writeOIDSubidentifier(serializedFirstComponent, into: &bytes) + + while let component = components.popFirst() { + Self.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 { + 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, 10045, 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, 19200300, 100, 1, 25] + static let emailAddress: ASN1.ASN1ObjectIdentifier = [1, 2, 840, 113549, 1, 9, 1] + } + } + + fileprivate extension ArraySlice where Element == UInt8 { + mutating 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 = firstIndex(where: { $0 & 0x80 == 0x00 }) else { + throw CryptoKitASN1Error.invalidASN1Object + } + + let oidSlice = self[startIndex...subidentifierEndIndex] + 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) + } + } + + fileprivate extension UInt { + 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/zkp/ASN1/ECDSASignature.swift b/Sources/zkp/ASN1/ECDSASignature.swift new file mode 120000 index 0000000..1d9bb92 --- /dev/null +++ b/Sources/zkp/ASN1/ECDSASignature.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ECDSASignature.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/PEMDocument.swift b/Sources/zkp/ASN1/PEMDocument.swift new file mode 120000 index 0000000..889039c --- /dev/null +++ b/Sources/zkp/ASN1/PEMDocument.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PEMDocument.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/PKCS8PrivateKey.swift b/Sources/zkp/ASN1/PKCS8PrivateKey.swift new file mode 120000 index 0000000..e040588 --- /dev/null +++ b/Sources/zkp/ASN1/PKCS8PrivateKey.swift @@ -0,0 +1 @@ +../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PKCS8PrivateKey.swift \ No newline at end of file diff --git a/Sources/zkp/ASN1/SEC1PrivateKey.swift b/Sources/zkp/ASN1/SEC1PrivateKey.swift new file mode 100644 index 0000000..2fb333e --- /dev/null +++ b/Sources/zkp/ASN1/SEC1PrivateKey.swift @@ -0,0 +1,121 @@ +// +// SEC1PrivateKey.swift +// GigaBitcoin/secp256k1.swift +// +// Modifications Copyright (c) 2023 GigaBitcoin LLC +// Distributed under the MIT software license +// +// See the accompanying file LICENSE for information +// +// +// NOTICE: THIS FILE HAS BEEN MODIFIED BY GigaBitcoin LLC +// UNDER COMPLIANCE WITH THE APACHE 2.0 LICENSE FROM THE +// ORIGINAL WORK OF THE COMPANY Apple Inc. +// +// THE FOLLOWING IS THE COPYRIGHT OF THE ORIGINAL DOCUMENT: +// +// +//===----------------------------------------------------------------------===// +// +// 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 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 { + .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 version == 1 else { + throw CryptoKitASN1Error.invalidASN1Object + } + + let privateKey = try ASN1OctetString(asn1Encoded: &nodes) + let parameters = try ASN1.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { node in + try ASN1.ASN1ObjectIdentifier(asn1Encoded: node) + } + let publicKey = try ASN1.optionalExplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) { node in + 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 .ecdsaP256K1 + + 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 .ecdsaP256K1: + 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/zkp/ASN1/SubjectPublicKeyInfo.swift b/Sources/zkp/ASN1/SubjectPublicKeyInfo.swift new file mode 100644 index 0000000..c7f9df9 --- /dev/null +++ b/Sources/zkp/ASN1/SubjectPublicKeyInfo.swift @@ -0,0 +1,137 @@ +// +// SubjectPublicKeyInfo.swift +// GigaBitcoin/secp256k1.swift +// +// Modifications Copyright (c) 2023 GigaBitcoin LLC +// Distributed under the MIT software license +// +// See the accompanying file LICENSE for information +// +// +// NOTICE: THIS FILE HAS BEEN MODIFIED BY GigaBitcoin LLC +// UNDER COMPLIANCE WITH THE APACHE 2.0 LICENSE FROM THE +// ORIGINAL WORK OF THE COMPANY Apple Inc. +// +// THE FOLLOWING IS THE COPYRIGHT OF THE ORIGINAL DOCUMENT: +// +// +//===----------------------------------------------------------------------===// +// +// 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 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 Self(algorithmIdentifier: algorithmIdentifier, key: key) + } + } + + private init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: ASN1.ASN1BitString) { + self.algorithmIdentifier = algorithmIdentifier + self.key = key + } + + 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 ecdsaP256K1 = ASN1.RFC5480AlgorithmIdentifier( + algorithm: .AlgorithmIdentifier.idEcPublicKey, + parameters: try! .init(erasing: ASN1.ASN1ObjectIdentifier.NamedCurves.secp256k1) + ) + } + +#endif // Linux or !SwiftPM diff --git a/Sources/zkp/Asymmetric.swift b/Sources/zkp/Asymmetric.swift index 2418ff4..662bf53 100644 --- a/Sources/zkp/Asymmetric.swift +++ b/Sources/zkp/Asymmetric.swift @@ -62,6 +62,45 @@ public extension secp256k1 { self.baseKey = try PrivateKeyImplementation(dataRepresentation: data, format: format) } + /// Creates a secp256k1 private key for signing from a Privacy-Enhanced Mail (PEM) representation. + /// + /// - Parameters: + /// - pemRepresentation: A PEM representation of the key. + public init(pemRepresentation: String) throws { + let pem = try ASN1.PEMDocument(pemString: pemRepresentation) + + switch pem.type { + case "EC PRIVATE KEY": + let parsed = try ASN1.SEC1PrivateKey(asn1Encoded: Array(pem.derBytes)) + self = try .init(dataRepresentation: parsed.privateKey) + case "PRIVATE KEY": + let parsed = try ASN1.PKCS8PrivateKey(asn1Encoded: Array(pem.derBytes)) + self = try .init(dataRepresentation: parsed.privateKey.privateKey) + + default: + throw CryptoKitASN1Error.invalidPEMDocument + } + } + + /// Creates a secp256k1 private key for signing from a Distinguished Encoding Rules (DER) encoded representation. + /// + /// - Parameters: + /// - derRepresentation: A DER-encoded representation of the key. + public init(derRepresentation: Bytes) throws where Bytes.Element == UInt8 { + let bytes = Array(derRepresentation) + + // We have to try to parse this twice because we have no information 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(dataRepresentation: key.privateKey.privateKey) + } catch { + let key = try ASN1.SEC1PrivateKey(asn1Encoded: bytes) + self = try .init(dataRepresentation: key.privateKey) + } + } + /// Determines if two private keys are equal. /// /// - Parameters: @@ -139,6 +178,45 @@ public extension secp256k1 { public init(dataRepresentation data: D, format: secp256k1.Format) throws { self.baseKey = try PublicKeyImplementation(dataRepresentation: data, format: format) } + + /// Creates a secp256k1 public key for signing from a Privacy-Enhanced Mail (PEM) representation. + /// + /// - Parameters: + /// - pemRepresentation: A PEM representation of the key. + public init(pemRepresentation: String) throws { + let pem = try ASN1.PEMDocument(pemString: pemRepresentation) + guard pem.type == "PUBLIC KEY" else { + throw CryptoKitASN1Error.invalidPEMDocument + } + self = try .init(derRepresentation: pem.derBytes) + } + + /// Creates a secp256k1 public key for signing from a Distinguished Encoding Rules (DER) encoded representation. + /// + /// - Parameters: + /// - derRepresentation: A DER-encoded representation of the key. + public init(derRepresentation: Bytes) throws where Bytes.Element == UInt8 { + let bytes = Array(derRepresentation) + let parsed = try ASN1.SubjectPublicKeyInfo(asn1Encoded: bytes) + self = try .init(x963Representation: parsed.key) + } + + /// Creates a secp256k1 public key for signing from an ANSI x9.63 representation. + /// + /// - Parameters: + /// - x963Representation: An ANSI x9.63 representation of the key. + public init(x963Representation: Bytes) throws { + // Before we do anything, we validate that the x963 representation has the right number of bytes. + let length = x963Representation.withUnsafeBytes { $0.count } + + switch length { + case (2 * secp256k1.ByteLength.dimension) + 1: + self.baseKey = try PublicKeyImplementation(dataRepresentation: x963Representation, format: .uncompressed) + + default: + throw CryptoKitError.incorrectParameterSize + } + } } /// The corresponding x-only public key for the secp256k1 curve. diff --git a/Sources/zkp/CryptoKitErrors.swift b/Sources/zkp/CryptoKitErrors.swift new file mode 120000 index 0000000..8bd86c8 --- /dev/null +++ b/Sources/zkp/CryptoKitErrors.swift @@ -0,0 +1 @@ +../../Submodules/swift-crypto/Sources/Crypto/CryptoKitErrors.swift \ No newline at end of file diff --git a/Sources/zkp/Errors.swift b/Sources/zkp/Errors.swift index 0c123eb..dad21ef 100644 --- a/Sources/zkp/Errors.swift +++ b/Sources/zkp/Errors.swift @@ -8,14 +8,14 @@ // See the accompanying file LICENSE for information // -import Foundation - /// Errors thrown for secp256k1 -/// - incorrectKeySize: A key is being deserialized with an incorrect key size. -/// - incorrectParameterSize: The number of bytes passed for a given argument is incorrect. -/// - underlyingCryptoError: An unexpected error at a lower-level occurred. public enum secp256k1Error: Error { + /// A key is being deserialized with an incorrect key size. case incorrectKeySize + + /// The number of bytes passed for a given argument is incorrect. case incorrectParameterSize + + /// An unexpected error at a lower-level occurred. case underlyingCryptoError } diff --git a/Tests/zkpTests/secp256k1Tests.swift b/Tests/zkpTests/secp256k1Tests.swift index 09872c1..02a357e 100644 --- a/Tests/zkpTests/secp256k1Tests.swift +++ b/Tests/zkpTests/secp256k1Tests.swift @@ -649,6 +649,75 @@ final class secp256k1Tests: XCTestCase { XCTAssertEqual(combinedKeyBytes, expectedCombinedKey) } + func testPrivateKeyPEM() { + let privateKeyString = """ + -----BEGIN EC PRIVATE KEY----- + MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK + oUQDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMAcjHIrTDS6HEELgguOatmFBOp + 2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g== + -----END EC PRIVATE KEY----- + """ + + let privateKey = try! secp256k1.Signing.PrivateKey(pemRepresentation: privateKeyString) + let expectedPrivateKey = "15f01cf0e979ce9bd3b19e2db9f07ad4f476f5b3a749d5dcc47ee293ca5c873b" + + // Verify the keys matches the expected keys output + XCTAssertEqual(expectedPrivateKey, String(bytes: privateKey.dataRepresentation)) + } + + func testPublicKeyPEM() { + let publicKeyString = """ + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMA + cjHIrTDS6HEELgguOatmFBOp2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g== + -----END PUBLIC KEY----- + """ + + let privateKeyBytes = try! "15f01cf0e979ce9bd3b19e2db9f07ad4f476f5b3a749d5dcc47ee293ca5c873b".bytes + let privateKey = try! secp256k1.Signing.PrivateKey(dataRepresentation: privateKeyBytes, format: .uncompressed) + let publicKey = try! secp256k1.Signing.PublicKey(pemRepresentation: publicKeyString) + + // Verify the keys matches the expected keys output + XCTAssertEqual(privateKey.publicKey.dataRepresentation, publicKey.dataRepresentation) + } + + func testSigningPEM() { + let privateKeyString = """ + -----BEGIN EC PRIVATE KEY----- + MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK + oUQDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMAcjHIrTDS6HEELgguOatmFBOp + 2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g== + -----END EC PRIVATE KEY----- + """ + + let expectedDerSignature = "MEQCIC8k5whKPsPg7XtWTInvhGL4iEU6lP6yPdpEXXZ2mOhFAiAZ3Po9tEDV8mQ8LDzwF0nhPmAn9VLYG8bkuY6PKruZNQ==" + let privateKey = try! secp256k1.Signing.PrivateKey(pemRepresentation: privateKeyString) + let messageData = "We're all Satoshi Nakamoto and a bit of Harold Thomas Finney II.".data(using: .utf8)! + + let signature = try! privateKey.signature(for: messageData) + + // Verify the signature matches the expected output + XCTAssertEqual(expectedDerSignature, try! signature.derRepresentation.base64EncodedString()) + } + + func testVerifyingPEM() { + let publicKeyString = """ + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMA + cjHIrTDS6HEELgguOatmFBOp2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g== + -----END PUBLIC KEY----- + """ + + let expectedSignature = "MEQCIEwVxXLE/mwaRzxLvz9VIcMtHaa/Wf1WRxiBJ6NEuWHeAiAQWf2oqqBqEtBABbmwsXqjCJFvsaPt8o+VaOthto1kWQ==" + let expectedDerSignature = Data(base64Encoded: expectedSignature, options: .ignoreUnknownCharacters)! + + let messageData = "We're all Satoshi Nakamoto and a bit of Harold Thomas Finney II.".data(using: .utf8)! + let signature = try! secp256k1.Signing.ECDSASignature(derRepresentation: expectedDerSignature) + let publicKey = try! secp256k1.Signing.PublicKey(pemRepresentation: publicKeyString) + + XCTAssertTrue(publicKey.isValidSignature(signature, for: SHA256.hash(data: messageData))) + } + static var allTests = [ ("testUncompressedKeypairCreation", testUncompressedKeypairCreation), ("testCompressedKeypairCreation", testCompressedKeypairCreation), @@ -687,6 +756,10 @@ final class secp256k1Tests: XCTestCase { ("testSchnorrNegating", testSchnorrNegating), ("testTaprootDerivation", testTaprootDerivation), ("testPubkeyCombine", testPubkeyCombine), - ("testPubkeyCombineBindings", testPubkeyCombineBindings) + ("testPubkeyCombineBindings", testPubkeyCombineBindings), + ("testPrivateKeyPEM", testPrivateKeyPEM), + ("testPublicKeyPEM", testPublicKeyPEM), + ("testSigningPEM", testSigningPEM), + ("testVerifyingPEM", testVerifyingPEM) ] }