From 2721361d26ac158fb3f50054f28da5a1e6c2a34a Mon Sep 17 00:00:00 2001 From: Adam Mika <88001738+amika-sq@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:23:43 -0700 Subject: [PATCH] InMemoryKeyManager implementation (#7) --- Sources/tbDEX/crypto/Crypto.swift | 94 ++++++- Sources/tbDEX/crypto/Ed25519.swift | 129 ++++++---- Sources/tbDEX/crypto/InMemoryKeyManager.swift | 49 ++++ Sources/tbDEX/crypto/JWK.swift | 38 +-- Sources/tbDEX/crypto/KeyGenerator.swift | 19 +- Sources/tbDEX/crypto/KeyManager.swift | 30 +-- Sources/tbDEX/crypto/Secp256k1.swift | 242 ++++++++++-------- Sources/tbDEX/crypto/Signer.swift | 4 +- Tests/tbDEXTests/TestVector.swift | 14 +- Tests/tbDEXTests/crypto/Ed25519Tests.swift | 26 +- .../crypto/InMemoryKeyManagerTests.swift | 36 +++ Tests/tbDEXTests/crypto/Secp256k1Tests.swift | 72 +++--- 12 files changed, 483 insertions(+), 270 deletions(-) create mode 100644 Sources/tbDEX/crypto/InMemoryKeyManager.swift create mode 100644 Tests/tbDEXTests/crypto/InMemoryKeyManagerTests.swift diff --git a/Sources/tbDEX/crypto/Crypto.swift b/Sources/tbDEX/crypto/Crypto.swift index 4c66050..e9d9987 100644 --- a/Sources/tbDEX/crypto/Crypto.swift +++ b/Sources/tbDEX/crypto/Crypto.swift @@ -1 +1,93 @@ -public enum Crypto {} +import Foundation + +enum CryptoError: Error { + case illegalArgument(description: String) +} + +enum Crypto { + + /// Generates a private key using the specified algorithm and curve, utilizing the appropriate `KeyGenerator`. + /// - Parameters: + /// - algorithm: The JWA algorithm identifier. + /// - curve: The elliptic curve. Null for algorithms that do not use elliptic curves. + /// - Returns: The generated private key as a JWK object. + static func generatePrivateKey(algorithm: Jwk.Algorithm, curve: Jwk.Curve? = nil) throws -> Jwk { + let keyGenerator = try getKeyGenerator(algorithm: algorithm, curve: curve) + return try keyGenerator.generatePrivateKey() + } + + /// Computes a public key from the given private key, utilizing relevant `KeyGenerator`. + /// - Parameter privateKey: The private key used to compute the public key. + /// - Returns: The computed public key as a JWK object. + static func computePublicKey(privateKey: Jwk) throws -> Jwk { + let keyGenerator = try getKeyGenerator(algorithm: privateKey.algorithm, curve: privateKey.curve) + return try keyGenerator.computePublicKey(privateKey: privateKey) + } + + /// Signs a payload using a private key. + /// - Parameters: + /// - privateKey: The JWK private key to be used for generating the signature. + /// - payload: The data to be signed. + /// - Returns: The digital signature as a byte array. + static func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol { + let signer = try getSigner(algorithm: privateKey.algorithm, curve: privateKey.curve) + return try signer.sign(privateKey: privateKey, payload: payload) + } + + /// Verifies a signature against a signed payload using a public key. + /// + /// - Parameters: + /// - publicKey: The JWK public key to be used for verifying the signature. + /// - signature: The signature that will be verified. + /// - signedPayload: The data that was signed. + /// - algorithm: The algorithm used for signing/verification, only used if not provided in the JWK. + /// - Returns: Boolean indicating if the publicKey and signature are valid for the given payload. + static func verify( + publicKey: Jwk, + signature: S, + signedPayload: D, + algorithm: Jwk.Algorithm? = nil + ) throws -> Bool where S: DataProtocol, D: DataProtocol { + let algorithm = publicKey.algorithm ?? algorithm + let verifier = try getVerifier(algorithm: algorithm, curve: publicKey.curve) + return try verifier.verify(publicKey: publicKey, signature: signature, signedPayload: signedPayload) + } + + /// Converts a `Jwk` public key into its byte array representation. + /// - Parameter publicKey: `Jwk` object representing the public key to be converted. + /// - Returns: Data representing the byte-level information of the provided public key + static func publicKeyToBytes(publicKey: Jwk) throws -> Data { + let keyGenerator = try getKeyGenerator(algorithm: publicKey.algorithm, curve: publicKey.curve) + return try keyGenerator.publicKeyToBytes(publicKey) + } + + // MARK: Private + + /// Retrieves a `KeyGenerator` based on the provided algorithm and curve. + /// - Parameters: + /// - algorithm: The cryptographic algorithm to find a key generator for. + /// - curve: The cryptographic curve to find a key generator for. + /// - Returns: The corresponding `KeyGenerator`. + private static func getKeyGenerator(algorithm: Jwk.Algorithm?, curve: Jwk.Curve? = nil) throws -> KeyGenerator { + switch (algorithm, curve) { + case (nil, .secp256k1), + (Secp256k1.shared.algorithm, nil), + (Secp256k1.shared.algorithm, .secp256k1): + return Secp256k1.shared + case (Ed25519.shared.algorithm, .ed25519): + return Ed25519.shared + default: + throw CryptoError.illegalArgument( + description: "Algorithm \(algorithm?.rawValue ?? "nil") not supported" + ) + } + } + + private static func getSigner(algorithm: Jwk.Algorithm?, curve: Jwk.Curve? = nil) throws -> Signer { + return try getKeyGenerator(algorithm: algorithm, curve: curve) as! Signer + } + + private static func getVerifier(algorithm: Jwk.Algorithm?, curve: Jwk.Curve? = nil) throws -> Signer { + return try getSigner(algorithm: algorithm, curve: curve) + } +} diff --git a/Sources/tbDEX/crypto/Ed25519.swift b/Sources/tbDEX/crypto/Ed25519.swift index af13d35..e841b92 100644 --- a/Sources/tbDEX/crypto/Ed25519.swift +++ b/Sources/tbDEX/crypto/Ed25519.swift @@ -6,19 +6,41 @@ import Foundation /// /// This class uses Apple's CryptoKit, specifically `Curve25519.Signing`, for it's cryptographic operations: /// https://developer.apple.com/documentation/cryptokit/curve25519/signing -public enum Ed25519: KeyGenerator, Signer { +class Ed25519 { - static let keyType: KeyType = .ed25519 + /// Shared static instance + static let shared = Ed25519() - // MARK: - Public Functions + /// Private initializer to prevent instantiation + private init() {} +} + +enum Ed25519Error: Error { + /// The privateJwk provided did not have the appropriate parameters set on it + case invalidPrivateJwk + /// The publicJwk provided did not have the appropriate parameters set on it + case invalidPublicJwk +} + +// MARK: - KeyGenerator + +extension Ed25519: KeyGenerator { + + var algorithm: Jwk.Algorithm { + .eddsa + } + + var keyType: Jwk.KeyType { + .octetKeyPair + } /// Generates an Ed25519 private key in JSON Web Key (JWK) format. - public static func generatePrivateKey() throws -> Jwk { + func generatePrivateKey() throws -> Jwk { return try generatePrivateJwk(privateKey: Curve25519.Signing.PrivateKey()) } /// Derives the public key in JSON Web Key (JWK) format from a given Ed25519 private key in JWK format. - public static func computePublicKey(privateKey: Jwk) throws -> Jwk { + func computePublicKey(privateKey: Jwk) throws -> Jwk { guard let d = privateKey.d else { throw Ed25519Error.invalidPrivateJwk } @@ -27,38 +49,69 @@ public enum Ed25519: KeyGenerator, Signer { return try generatePublicJwk(publicKey: privateKey.publicKey) } + /// Converts a private key from JSON Web Key (JWK) format to a raw bytes. + func privateKeyToBytes(_ privateKey: Jwk) throws -> Data { + guard let d = privateKey.d else { + throw Ed25519Error.invalidPrivateJwk + } + + return try d.decodeBase64Url() + } + + /// Converts a public key from JSON Web Key (JWK) format to a raw bytes. + func publicKeyToBytes(_ publicKey: Jwk) throws -> Data { + guard let x = publicKey.x else { + throw Ed25519Error.invalidPublicJwk + } + + return try x.decodeBase64Url() + } + /// Converts raw private key in bytes to its corresponding JSON Web Key (JWK) format. - public static func bytesToPrivateKey(_ bytes: Data) throws -> Jwk { + func bytesToPrivateKey(_ bytes: Data) throws -> Jwk { return try generatePrivateJwk( privateKey: try Curve25519.Signing.PrivateKey(rawRepresentation: bytes) ) } /// Converts a raw public key in bytes to its corresponding JSON Web Key (JWK) format. - public static func bytesToPublicKey(_ bytes: Data) throws -> Jwk { + func bytesToPublicKey(_ bytes: Data) throws -> Jwk { return try generatePublicJwk( publicKey: try Curve25519.Signing.PublicKey(rawRepresentation: bytes) ) } - /// Converts a private key from JSON Web Key (JWK) format to a raw bytes. - public static func privateKeyToBytes(_ privateKey: Jwk) throws -> Data { - guard let d = privateKey.d else { - throw Ed25519Error.invalidPrivateJwk - } + // MARK: Private Functions - return try d.decodeBase64Url() + private func generatePrivateJwk(privateKey: Curve25519.Signing.PrivateKey) throws -> Jwk { + var jwk = Jwk( + keyType: .octetKeyPair, + curve: .ed25519, + d: privateKey.rawRepresentation.base64UrlEncodedString(), + x: privateKey.publicKey.rawRepresentation.base64UrlEncodedString() + ) + + jwk.keyIdentifier = try jwk.thumbprint() + + return jwk } - /// Converts a public key from JSON Web Key (JWK) format to a raw bytes. - public static func publicKeyToBytes(_ publicKey: Jwk) throws -> Data { - guard let x = publicKey.x else { - throw Ed25519Error.invalidPublicJwk - } + private func generatePublicJwk(publicKey: Curve25519.Signing.PublicKey) throws -> Jwk { + var jwk = Jwk( + keyType: .octetKeyPair, + curve: .ed25519, + x: publicKey.rawRepresentation.base64UrlEncodedString() + ) - return try x.decodeBase64Url() + jwk.keyIdentifier = try jwk.thumbprint() + + return jwk } +} + +// MARK: - Signer +extension Ed25519: Signer { /// Generates an RFC8032-compliant EdDSA signature of given data using an Ed25519 private key in JSON Web Key /// (JWK) format. /// @@ -69,7 +122,7 @@ public enum Ed25519: KeyGenerator, Signer { /// See /// [Apple's documentation](https://developer.apple.com/documentation/cryptokit/curve25519/signing/privatekey/signature(for:)) /// for more information - public static func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol { + func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol { guard let d = privateKey.d else { throw Ed25519Error.invalidPrivateJwk } @@ -80,7 +133,7 @@ public enum Ed25519: KeyGenerator, Signer { /// Verifies an RFC8032-compliant EdDSA signature against given data using an Ed25519 public key in JSON Web Key /// (JWK) format. - public static func verify(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool + func verify(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool where S: DataProtocol, D: DataProtocol { guard let x = publicKey.x else { throw Ed25519Error.invalidPublicJwk @@ -89,38 +142,4 @@ public enum Ed25519: KeyGenerator, Signer { let publicKey = try Curve25519.Signing.PublicKey(rawRepresentation: try x.decodeBase64Url()) return publicKey.isValidSignature(signature, for: signedPayload) } - - // MARK: - Private Functions - - private static func generatePrivateJwk(privateKey: Curve25519.Signing.PrivateKey) throws -> Jwk { - var jwk = Jwk( - keyType: .octetKeyPair, - curve: .ed25519, - d: privateKey.rawRepresentation.base64UrlEncodedString(), - x: privateKey.publicKey.rawRepresentation.base64UrlEncodedString() - ) - - jwk.keyIdentifier = try jwk.thumbprint() - - return jwk - } - - private static func generatePublicJwk(publicKey: Curve25519.Signing.PublicKey) throws -> Jwk { - var jwk = Jwk( - keyType: .octetKeyPair, - curve: .ed25519, - x: publicKey.rawRepresentation.base64UrlEncodedString() - ) - - jwk.keyIdentifier = try jwk.thumbprint() - - return jwk - } -} - -public enum Ed25519Error: Error { - /// The privateJwk provided did not have the appropriate parameters set on it - case invalidPrivateJwk - /// The publicJwk provided did not have the appropriate parameters set on it - case invalidPublicJwk } diff --git a/Sources/tbDEX/crypto/InMemoryKeyManager.swift b/Sources/tbDEX/crypto/InMemoryKeyManager.swift new file mode 100644 index 0000000..c38a75e --- /dev/null +++ b/Sources/tbDEX/crypto/InMemoryKeyManager.swift @@ -0,0 +1,49 @@ +import Foundation + +class InMemoryKeyManager { + + /// Backing in-memory store to store generated keys. + private var keyStore = [String: Jwk]() + +} + +// MARK: - KeyManager + +extension InMemoryKeyManager: KeyManager { + + func generatePrivateKey(algorithm: Jwk.Algorithm, curve: Jwk.Curve? = nil) throws -> String { + let jwk = try Crypto.generatePrivateKey(algorithm: algorithm, curve: curve) + let alias = try getDeterministicAlias(key: jwk) + keyStore[alias] = jwk + + return alias + } + + func getPublicKey(keyAlias: String) throws -> Jwk? { + if let privateKey = keyStore[keyAlias] { + return try Crypto.computePublicKey(privateKey: privateKey) + } else { + return nil + } + } + + func sign(keyAlias: String, payload: D) throws -> Data where D: DataProtocol { + guard let privateKey = keyStore[keyAlias] else { + throw KeyManagerError.keyAliasNotFound + } + + return try Crypto.sign(privateKey: privateKey, payload: payload) + } + + func getDeterministicAlias(key: Jwk) throws -> String { + let alias: String + + if let keyIdentifier = key.keyIdentifier { + alias = keyIdentifier + } else { + alias = try key.thumbprint() + } + + return alias + } +} diff --git a/Sources/tbDEX/crypto/JWK.swift b/Sources/tbDEX/crypto/JWK.swift index df3c662..a8eb202 100644 --- a/Sources/tbDEX/crypto/JWK.swift +++ b/Sources/tbDEX/crypto/JWK.swift @@ -2,36 +2,36 @@ import CryptoKit import ExtrasBase64 import Foundation -public struct Jwk: Codable, Equatable { +struct Jwk: Codable, Equatable { // MARK: - Types /// Supported `crv` curve types. - public enum Curve: String, Codable { + enum Curve: String, Codable { case ed25519 = "Ed25519" case secp256k1 = "secp256k1" } /// Supported `kty` key types. - public enum KeyType: String, Codable { + enum KeyType: String, Codable { case elliptic = "EC" case octetKeyPair = "OKP" } /// Supported `alg` algorithms. - public enum Algorithm: String, Codable { + enum Algorithm: String, Codable { case eddsa = "EdDSA" case es256k = "ES256K" } /// Supported `use` values. - public enum PublicKeyUse: String, Codable { + enum PublicKeyUse: String, Codable { case signature = "sig" case encryption = "enc" } /// Supported `key_ops` values. - public enum KeyOperations: String, Codable { + enum KeyOperations: String, Codable { case encrypt case decrypt case sign @@ -49,53 +49,53 @@ public struct Jwk: Codable, Equatable { /// on any of these parameters. /// The `kty` (key type) parameter identifies the cyrptographic algorithm family used with the key. - public var keyType: KeyType + var keyType: KeyType /// The `use` (public key use) parameter identifies the intended use of the public key. - public var publicKeyUse: PublicKeyUse? + var publicKeyUse: PublicKeyUse? /// The "key_ops" (key operations) parameter identifies the operation(s) for which the key is intended to be used. - public var keyOperations: [KeyOperations]? + var keyOperations: [KeyOperations]? /// The `alg` (algorithm) parameter identifies the cryptographic algorithm intended for use with the key. - public var algorithm: Algorithm? + var algorithm: Algorithm? /// The "alg" (algorithm) parameter identifies the algorithm intended for use with the key. - public var keyIdentifier: String? + var keyIdentifier: String? /// The `crv` (curve) parameter identifies the cryptographic curve intended for use with the key. - public var curve: Curve? + var curve: Curve? /// The "x5u" (X.509 URL) parameter is a URI [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986) that refers /// to a resource for an X.509 public key certificate or certificate chain /// [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280). - public var x509Url: String? + var x509Url: String? /// The "x5c" (X.509 certificate chain) parameter contains a chain of one or more PKIX certificates /// [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) - public var x509CertificateChain: String? + var x509CertificateChain: String? /// The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a base64url-encoded SHA-1 thumbprint (a.k.a. digest) /// of the DER encoding of an X.509 certificate [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280). - public var x509CertificateSha1Thumbprint: String? + var x509CertificateSha1Thumbprint: String? /// The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a base64url-encoded SHA-256 thumbprint /// (a.k.a. digest) of the DER encoding of an X.509 certificate /// [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280). - public var x509CertificateSha256Thumbprint: String? + var x509CertificateSha256Thumbprint: String? // MARK: - KeyType Specific JWK Properties /// The below properties represent JWK parameters that are unique to specific JWK key types. /// `d` Private exponent. - public var d: String? + var d: String? /// The x-coordinate for the Elliptic Curve point. - public var x: String? + var x: String? /// Elliptic Curve y-coordinate. - public var y: String? + var y: String? // MARK: - Codable diff --git a/Sources/tbDEX/crypto/KeyGenerator.swift b/Sources/tbDEX/crypto/KeyGenerator.swift index ce7ad2c..6517ae1 100644 --- a/Sources/tbDEX/crypto/KeyGenerator.swift +++ b/Sources/tbDEX/crypto/KeyGenerator.swift @@ -2,24 +2,27 @@ import Foundation protocol KeyGenerator { - /// Indicates the `KeyType` intended to bse used with the key. - static var keyType: KeyType { get } + /// Indicates the algorithm intended to be used with the key. + var algorithm: Jwk.Algorithm { get } + + /// Indicates the cryptographic algorithm family used with the key. + var keyType: Jwk.KeyType { get } /// Generates a private key. - static func generatePrivateKey() throws -> Jwk + func generatePrivateKey() throws -> Jwk /// Derives a public key from the private key provided. - static func computePublicKey(privateKey: Jwk) throws -> Jwk + func computePublicKey(privateKey: Jwk) throws -> Jwk /// Converts a private key to bytes. - static func privateKeyToBytes(_ privateKey: Jwk) throws -> Data + func privateKeyToBytes(_ privateKey: Jwk) throws -> Data /// Converts a public key to bytes. - static func publicKeyToBytes(_ publicKey: Jwk) throws -> Data + func publicKeyToBytes(_ publicKey: Jwk) throws -> Data /// Converts a private key as bytes into a JWK. - static func bytesToPrivateKey(_ bytes: Data) throws -> Jwk + func bytesToPrivateKey(_ bytes: Data) throws -> Jwk /// Converts a public key as bytes into a JWK. - static func bytesToPublicKey(_ bytes: Data) throws -> Jwk + func bytesToPublicKey(_ bytes: Data) throws -> Jwk } diff --git a/Sources/tbDEX/crypto/KeyManager.swift b/Sources/tbDEX/crypto/KeyManager.swift index 4de236d..a08ed87 100644 --- a/Sources/tbDEX/crypto/KeyManager.swift +++ b/Sources/tbDEX/crypto/KeyManager.swift @@ -1,13 +1,5 @@ import Foundation -/// Enum defining all supported cryptographic key types. -public enum KeyType { - /// OctetKeyPair key along the ed25519 curve - case ed25519 - /// Elliptic key along the secp256k1 curve - case secp256k1 -} - /// A key management interface that provides functionality for generating, storing, and utilizing /// private keys and their associated public keys. Implementations of this interface should handle /// the secure generation and storage of keys, providing mechanisms for utilizing them in cryptographic @@ -16,20 +8,21 @@ public enum KeyType { /// Example implementations might provide key management through various Key Management Systems (KMS), /// such as AWS KMS, Google Cloud KMS, Hardware Security Modules (HSM), or simple in-memory storage, /// each adhering to the same consistent API for usage within applications. -public protocol KeyManager { +protocol KeyManager { /// Generates and securely stores a private key based on the provided keyType, /// returning a unique alias that can be utilized to reference the generated key for future operations. /// - /// - Parameter keyType: The `KeyType` to use for key generation - /// - Returns: A unique alias that can be used to reference the stored key. - func generatePrivateKey(keyType: KeyType) throws -> String + /// - Parameters: + /// - algorithm: The cryptographic algorithm to use for key generation. + /// - curve: The elliptic curve to use (relevant for EC algorithms). + func generatePrivateKey(algorithm: Jwk.Algorithm, curve: Jwk.Curve?) throws -> String /// Retrieves the public key associated with a previously stored private key, identified by the provided alias. /// /// - Parameter keyAlias: The alias referencing the stored private key. /// - Returns: The associated public key in JSON Web Key (JWK) format (if available). - func getPublicKey(keyAlias: String) -> Jwk? + func getPublicKey(keyAlias: String) throws -> Jwk? /// Signs the provided payload using the private key identified by the provided alias. /// @@ -41,7 +34,12 @@ public protocol KeyManager { /// Return the alias of `publicKey`, as was originally returned by `generatePrivateKey`. /// - /// - Parameter publicKey: A public key in JSON Web Key (JWK) format - /// - Returns: The alias belonging to `publicKey` - func getDeterministicAlias(publicKey: Jwk) -> String + /// - Parameter key: A key in JSON Web Key (JWK) format + /// - Returns: The alias belonging to `key` + func getDeterministicAlias(key: Jwk) throws -> String +} + +enum KeyManagerError: Error { + /// Provided `keyAlias` is not present in the target `KeyManager` + case keyAliasNotFound } diff --git a/Sources/tbDEX/crypto/Secp256k1.swift b/Sources/tbDEX/crypto/Secp256k1.swift index 7cdacb1..5d566f7 100644 --- a/Sources/tbDEX/crypto/Secp256k1.swift +++ b/Sources/tbDEX/crypto/Secp256k1.swift @@ -1,11 +1,13 @@ import Foundation import secp256k1 -public enum Secp256k1: KeyGenerator, Signer { +class Secp256k1 { - static let keyType: KeyType = .secp256k1 + /// Shared static instance + static let shared = Secp256k1() - // MARK: - Constants + /// Private initializer to prevent instantiation + private init() {} /// Uncompressed key leading byte that indicates both the X and Y coordinates are available directly within the key. static let uncompressedKeyID: UInt8 = 0x04 @@ -31,74 +33,8 @@ public enum Secp256k1: KeyGenerator, Signer { /// Size of a private key, in bytes. static let privateKeySize: Int = 32 - // MARK: - Public Functions - - /// Generates an Secp256k1 private key in JSON Web Key (JWK) format. - public static func generatePrivateKey() throws -> Jwk { - return try generatePrivateJwk( - privateKey: secp256k1.Signing.PrivateKey() - ) - } - - /// Derives the public key in JSON Web Key (JWK) format from a given Secp256k1 private key in JWK format. - public static func computePublicKey(privateKey: Jwk) throws -> Jwk { - guard let d = privateKey.d else { - throw Secpsecp256k1Error.invalidPrivateJwk - } - - let privateKeyData = try d.decodeBase64Url() - let privateKey = try secp256k1.Signing.PrivateKey(dataRepresentation: privateKeyData) - - return try generatePublicJwk(publicKey: privateKey.publicKey) - } - - /// Converts raw Secp256k1 private key in bytes to its corresponding JSON Web Key (JWK) format. - public static func bytesToPrivateKey(_ bytes: Data) throws -> Jwk { - let privateKey = try secp256k1.Signing.PrivateKey(dataRepresentation: bytes) - return try generatePrivateJwk(privateKey: privateKey) - } - - /// Converts a raw Secp256k1 public key in bytes to its corresponding JSON Web Key (JWK) format. - public static func bytesToPublicKey(_ bytes: Data) throws -> Jwk { - let publicKey = try secp256k1.Signing.PublicKey( - dataRepresentation: bytes, - format: bytes.isCompressed() ? .compressed : .uncompressed - ) - - return try generatePublicJwk(publicKey: publicKey) - } - - /// Converts a Secp256k1 private key from JSON Web Key (JWK) format to a raw bytes. - public static func privateKeyToBytes(_ privateKey: Jwk) throws -> Data { - guard let d = privateKey.d else { - throw Secpsecp256k1Error.invalidPrivateJwk - } - - return try d.decodeBase64Url() - } - - /// Converts a Secp256k1 public key from JSON Web Key (JWK) format to a raw bytes. - public static func publicKeyToBytes(_ publicKey: Jwk) throws -> Data { - guard let x = publicKey.x, - let y = publicKey.y - else { - throw Secpsecp256k1Error.invalidPublicJwk - } - - var data = Data() - data.append(Self.uncompressedKeyID) - data.append(contentsOf: try x.decodeBase64Url()) - data.append(contentsOf: try y.decodeBase64Url()) - - guard data.count == Self.uncompressedKeySize else { - throw Secpsecp256k1Error.internalError(reason: "Public Key incorrect size: \(data.count)") - } - - return data - } - /// Converts a Secp256k1 raw public key to its compressed form. - public static func compressPublicKey(publicKeyBytes: Data) throws -> Data { + func compressPublicKey(publicKeyBytes: Data) throws -> Data { guard publicKeyBytes.count == Self.uncompressedKeySize, publicKeyBytes.first == Self.uncompressedKeyID else { @@ -122,42 +58,14 @@ public enum Secp256k1: KeyGenerator, Signer { } /// Converts a Secp256k1 raw public key to its uncompressed form. - public static func decompressPublicKey(publicKeyBytes: Data) throws -> Data { + func decompressPublicKey(publicKeyBytes: Data) throws -> Data { let format: secp256k1.Format = publicKeyBytes.count == Self.compressedKeySize ? .compressed : .uncompressed let publicKey = try secp256k1.Signing.PublicKey(dataRepresentation: publicKeyBytes, format: format) return publicKey.uncompressedBytes() } - /// Generates an RFC6979-compliant ECDSA signature of given data using a Secp256k1 private key in JSON Web Key - /// (JWK) format. - public static func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol { - guard let d = privateKey.d else { - throw Secpsecp256k1Error.invalidPrivateJwk - } - - let privateKeyData = try d.decodeBase64Url() - let privateKey = try secp256k1.Signing.PrivateKey( - dataRepresentation: privateKeyData, - format: privateKeyData.isCompressed() ? .compressed : .uncompressed - ) - return try privateKey.signature(for: payload).dataRepresentation - } - - /// Verifies an RFC6979-compliant ECDSA signature against given data and a Secp256k1 public key in JSON Web Key - /// (JWK) format. - public static func verify(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool - where S: DataProtocol, D: DataProtocol { - let publicKeyBytes = try publicKeyToBytes(publicKey) - let publicKey = try secp256k1.Signing.PublicKey(dataRepresentation: publicKeyBytes, format: .uncompressed) - - let ecdsaSignature = try secp256k1.Signing.ECDSASignature(dataRepresentation: signature) - return publicKey.isValidSignature(ecdsaSignature, for: signedPayload) - } - - // MARK: - Internal Functions - /// Computes the elliptic curve points (x and y coordinates) for a given a raw Secp256k1 key - static func getCurvePoints(keyBytes: Data) throws -> (Data, Data) { + func getCurvePoints(keyBytes: Data) throws -> (Data, Data) { var keyBytes = keyBytes // If provided key bytes represent a private key, first compute the public key @@ -167,7 +75,7 @@ public enum Secp256k1: KeyGenerator, Signer { keyBytes = publicKey.dataRepresentation } - let uncompresssedBytes = try Self.decompressPublicKey(publicKeyBytes: keyBytes) + let uncompresssedBytes = try decompressPublicKey(publicKeyBytes: keyBytes) let x = uncompresssedBytes[1...32] let y = uncompresssedBytes[33...64] @@ -175,7 +83,7 @@ public enum Secp256k1: KeyGenerator, Signer { } /// Validates a given raw Secp256k1 private key to ensure its compliance with the secp256k1 curve standards. - static func validatePrivateKey(privateKeyBytes: Data) -> Bool { + func validatePrivateKey(privateKeyBytes: Data) -> Bool { do { let _ = try secp256k1.Signing.PrivateKey(dataRepresentation: privateKeyBytes) return true @@ -185,7 +93,7 @@ public enum Secp256k1: KeyGenerator, Signer { } /// Validates a given raw Secp256k1 public key to confirm its mathematical correctness on the secp256k1 curve. - static func validatePublicKey(publicKeyBytes: Data) -> Bool { + func validatePublicKey(publicKeyBytes: Data) -> Bool { do { let format: secp256k1.Format = publicKeyBytes.count == Self.compressedKeySize ? .compressed : .uncompressed let _ = try secp256k1.Signing.PublicKey(dataRepresentation: publicKeyBytes, format: format) @@ -194,10 +102,96 @@ public enum Secp256k1: KeyGenerator, Signer { return false } } +} + +enum Secpsecp256k1Error: Error { + /// The private Jwk provide did not have the appropriate parameters set on it + case invalidPrivateJwk + /// The public Jwk provide did not have the appropriate parameters set on it + case invalidPublicJwk + /// Something internally went wrong, check `reason` for more information about the exact error + case internalError(reason: String) +} + +// MARK: - KeyGenerator + +extension Secp256k1: KeyGenerator { + + var algorithm: Jwk.Algorithm { + .es256k + } + + var keyType: Jwk.KeyType { + .elliptic + } + + /// Generates an Secp256k1 private key in JSON Web Key (JWK) format. + func generatePrivateKey() throws -> Jwk { + return try generatePrivateJwk( + privateKey: secp256k1.Signing.PrivateKey() + ) + } + + /// Derives the public key in JSON Web Key (JWK) format from a given Secp256k1 private key in JWK format. + func computePublicKey(privateKey: Jwk) throws -> Jwk { + guard let d = privateKey.d else { + throw Secpsecp256k1Error.invalidPrivateJwk + } + + let privateKeyData = try d.decodeBase64Url() + let privateKey = try secp256k1.Signing.PrivateKey(dataRepresentation: privateKeyData) + + return try generatePublicJwk(publicKey: privateKey.publicKey) + } + + /// Converts a Secp256k1 private key from JSON Web Key (JWK) format to a raw bytes. + func privateKeyToBytes(_ privateKey: Jwk) throws -> Data { + guard let d = privateKey.d else { + throw Secpsecp256k1Error.invalidPrivateJwk + } + + return try d.decodeBase64Url() + } + + /// Converts a Secp256k1 public key from JSON Web Key (JWK) format to a raw bytes. + func publicKeyToBytes(_ publicKey: Jwk) throws -> Data { + guard let x = publicKey.x, + let y = publicKey.y + else { + throw Secpsecp256k1Error.invalidPublicJwk + } + + var data = Data() + data.append(Self.uncompressedKeyID) + data.append(contentsOf: try x.decodeBase64Url()) + data.append(contentsOf: try y.decodeBase64Url()) + + guard data.count == Self.uncompressedKeySize else { + throw Secpsecp256k1Error.internalError(reason: "Public Key incorrect size: \(data.count)") + } + + return data + } + + /// Converts raw Secp256k1 private key in bytes to its corresponding JSON Web Key (JWK) format. + func bytesToPrivateKey(_ bytes: Data) throws -> Jwk { + let privateKey = try secp256k1.Signing.PrivateKey(dataRepresentation: bytes) + return try generatePrivateJwk(privateKey: privateKey) + } + + /// Converts a raw Secp256k1 public key in bytes to its corresponding JSON Web Key (JWK) format. + func bytesToPublicKey(_ bytes: Data) throws -> Jwk { + let publicKey = try secp256k1.Signing.PublicKey( + dataRepresentation: bytes, + format: bytes.isCompressed() ? .compressed : .uncompressed + ) + + return try generatePublicJwk(publicKey: publicKey) + } - // MARK: - Private Functions + // MARK: Private Functions - private static func generatePrivateJwk(privateKey: secp256k1.Signing.PrivateKey) throws -> Jwk { + private func generatePrivateJwk(privateKey: secp256k1.Signing.PrivateKey) throws -> Jwk { let (x, y) = try getCurvePoints(keyBytes: privateKey.dataRepresentation) var jwk = Jwk( @@ -213,7 +207,7 @@ public enum Secp256k1: KeyGenerator, Signer { return jwk } - private static func generatePublicJwk(publicKey: secp256k1.Signing.PublicKey) throws -> Jwk { + private func generatePublicJwk(publicKey: secp256k1.Signing.PublicKey) throws -> Jwk { let (x, y) = try getCurvePoints(keyBytes: publicKey.dataRepresentation) var jwk = Jwk( @@ -229,13 +223,35 @@ public enum Secp256k1: KeyGenerator, Signer { } } -public enum Secpsecp256k1Error: Error { - /// The private Jwk provide did not have the appropriate parameters set on it - case invalidPrivateJwk - /// The public Jwk provide did not have the appropriate parameters set on it - case invalidPublicJwk - /// Something internally went wrong, check `reason` for more information about the exact error - case internalError(reason: String) +// MARK: - Signer + +extension Secp256k1: Signer { + + /// Generates an RFC6979-compliant ECDSA signature of given data using a Secp256k1 private key in JSON Web Key + /// (JWK) format. + func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol { + guard let d = privateKey.d else { + throw Secpsecp256k1Error.invalidPrivateJwk + } + + let privateKeyData = try d.decodeBase64Url() + let privateKey = try secp256k1.Signing.PrivateKey( + dataRepresentation: privateKeyData, + format: privateKeyData.isCompressed() ? .compressed : .uncompressed + ) + return try privateKey.signature(for: payload).dataRepresentation + } + + /// Verifies an RFC6979-compliant ECDSA signature against given data and a Secp256k1 public key in JSON Web Key + /// (JWK) format. + func verify(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool + where S: DataProtocol, D: DataProtocol { + let publicKeyBytes = try publicKeyToBytes(publicKey) + let publicKey = try secp256k1.Signing.PublicKey(dataRepresentation: publicKeyBytes, format: .uncompressed) + + let ecdsaSignature = try secp256k1.Signing.ECDSASignature(dataRepresentation: signature) + return publicKey.isValidSignature(ecdsaSignature, for: signedPayload) + } } // MARK: - Helper extensions diff --git a/Sources/tbDEX/crypto/Signer.swift b/Sources/tbDEX/crypto/Signer.swift index 0ffe918..cb20b54 100644 --- a/Sources/tbDEX/crypto/Signer.swift +++ b/Sources/tbDEX/crypto/Signer.swift @@ -9,7 +9,7 @@ protocol Signer { /// - privateKey: The private key in JWK format to be used for signing. /// - payload: The payload to be signed. /// - Returns: Data representing the signature - static func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol + func sign(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol /// Verify the signature of a given payload, using a public key. /// @@ -18,6 +18,6 @@ protocol Signer { /// - signature: The signature to be verified against the payload and public key. /// - signedPayload: The original payload that was signed, to be verified. /// - Returns: Boolean indicating if the publicKey and signature are valid for the given payload. - static func verify(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool + func verify(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool where S: DataProtocol, D: DataProtocol } diff --git a/Tests/tbDEXTests/TestVector.swift b/Tests/tbDEXTests/TestVector.swift index 86c9a68..3d68cae 100644 --- a/Tests/tbDEXTests/TestVector.swift +++ b/Tests/tbDEXTests/TestVector.swift @@ -1,6 +1,6 @@ import Foundation -public func loadTestVector( +func loadTestVector( fileName: String, subdirectory: String? = nil ) throws -> TestVector { @@ -19,14 +19,14 @@ public func loadTestVector( return testVector } -public struct TestVector: Codable { +struct TestVector: Codable { - public let description: String - public let vectors: [Vector] + let description: String + let vectors: [Vector] - public struct Vector: Codable { - public let input: Input - public let output: Output + struct Vector: Codable { + let input: Input + let output: Output } } diff --git a/Tests/tbDEXTests/crypto/Ed25519Tests.swift b/Tests/tbDEXTests/crypto/Ed25519Tests.swift index ec51dce..39d986f 100644 --- a/Tests/tbDEXTests/crypto/Ed25519Tests.swift +++ b/Tests/tbDEXTests/crypto/Ed25519Tests.swift @@ -5,7 +5,7 @@ import XCTest final class Ed25519Tests: XCTestCase { func test_generateKey() throws { - let privateKey = try Ed25519.generatePrivateKey() + let privateKey = try Ed25519.shared.generatePrivateKey() XCTAssertEqual(privateKey.keyType, .octetKeyPair) XCTAssertEqual(privateKey.curve, .ed25519) @@ -14,7 +14,7 @@ final class Ed25519Tests: XCTestCase { XCTAssertNotNil(privateKey.x) // Generated private key should always be 32 bytes in length - let privateKeyBytes = try Ed25519.privateKeyToBytes(privateKey) + let privateKeyBytes = try Ed25519.shared.privateKeyToBytes(privateKey) XCTAssertEqual(privateKeyBytes.count, 32) } @@ -26,7 +26,7 @@ final class Ed25519Tests: XCTestCase { for vector in testVector.vectors { let privateKeyBytes = Data.fromHexString(vector.input["privateKeyBytes"]!)! - let privateKey = try Ed25519.bytesToPrivateKey(privateKeyBytes) + let privateKey = try Ed25519.shared.bytesToPrivateKey(privateKeyBytes) XCTAssertEqual(privateKey, vector.output) } } @@ -39,7 +39,7 @@ final class Ed25519Tests: XCTestCase { for vector in testVector.vectors { let publicKeyBytes = Data.fromHexString(vector.input["publicKeyBytes"]!)! - let publicKey = try Ed25519.bytesToPublicKey(publicKeyBytes) + let publicKey = try Ed25519.shared.bytesToPublicKey(publicKeyBytes) XCTAssertEqual(publicKey, vector.output) } } @@ -51,7 +51,7 @@ final class Ed25519Tests: XCTestCase { ) for vector in testVector.vectors { - let publicKey = try Ed25519.computePublicKey(privateKey: vector.input["privateKey"]!) + let publicKey = try Ed25519.shared.computePublicKey(privateKey: vector.input["privateKey"]!) XCTAssertEqual(publicKey, vector.output) } } @@ -63,7 +63,7 @@ final class Ed25519Tests: XCTestCase { ) for vector in testVector.vectors { - let privateKeyBytes = try Ed25519.privateKeyToBytes(vector.input["privateKey"]!) + let privateKeyBytes = try Ed25519.shared.privateKeyToBytes(vector.input["privateKey"]!) XCTAssertEqual(privateKeyBytes, Data.fromHexString(vector.output)!) } } @@ -75,7 +75,7 @@ final class Ed25519Tests: XCTestCase { ) for vector in testVector.vectors { - let publicKeyBytes = try Ed25519.publicKeyToBytes(vector.input["publicKey"]!) + let publicKeyBytes = try Ed25519.shared.publicKeyToBytes(vector.input["publicKey"]!) XCTAssertEqual(publicKeyBytes, Data.fromHexString(vector.output)!) } } @@ -93,7 +93,7 @@ final class Ed25519Tests: XCTestCase { ) for vector in testVector.vectors { - let signature = try Ed25519.sign( + let signature = try Ed25519.shared.sign( privateKey: vector.input.key, payload: Data.fromHexString(vector.input.data)! ) @@ -104,14 +104,14 @@ final class Ed25519Tests: XCTestCase { // // Because of this, the signature we just generated will NOT be the same as the vector's output, // but both will be valid signatures. - let isVectorOutputSignatureValid = try Ed25519.verify( - publicKey: try Ed25519.computePublicKey(privateKey: vector.input.key), + let isVectorOutputSignatureValid = try Ed25519.shared.verify( + publicKey: try Ed25519.shared.computePublicKey(privateKey: vector.input.key), signature: Data.fromHexString(vector.output)!, signedPayload: Data.fromHexString(vector.input.data)! ) - let isGeneratedSignatureValid = try Ed25519.verify( - publicKey: try Ed25519.computePublicKey(privateKey: vector.input.key), + let isGeneratedSignatureValid = try Ed25519.shared.verify( + publicKey: try Ed25519.shared.computePublicKey(privateKey: vector.input.key), signature: signature, signedPayload: Data.fromHexString(vector.input.data)! ) @@ -136,7 +136,7 @@ final class Ed25519Tests: XCTestCase { ) for vector in testVector.vectors { - let isValid = try Ed25519.verify( + let isValid = try Ed25519.shared.verify( publicKey: vector.input.key, signature: Data.fromHexString(vector.input.signature)!, signedPayload: Data.fromHexString(vector.input.data)! diff --git a/Tests/tbDEXTests/crypto/InMemoryKeyManagerTests.swift b/Tests/tbDEXTests/crypto/InMemoryKeyManagerTests.swift new file mode 100644 index 0000000..5f4b70e --- /dev/null +++ b/Tests/tbDEXTests/crypto/InMemoryKeyManagerTests.swift @@ -0,0 +1,36 @@ +import XCTest + +@testable import tbDEX + +final class InMemoryKeyManagerTests: XCTestCase { + + let keyManager = InMemoryKeyManager() + + func test_aliasIsConsistent() throws { + let keyAlias = try keyManager.generatePrivateKey(algorithm: .es256k) + let publicKey = try XCTUnwrap(try keyManager.getPublicKey(keyAlias: keyAlias)) + let defaultAlias = try keyManager.getDeterministicAlias(key: publicKey) + + XCTAssertEqual(keyAlias, defaultAlias) + } + + func test_getPublicKey_privateKeyInStore() throws { + let keyAlias = try keyManager.generatePrivateKey(algorithm: .es256k) + XCTAssertNotNil(try keyManager.getPublicKey(keyAlias: keyAlias)) + } + + func test_getPublicKey_privateKeyNotInStore() throws { + XCTAssertNil(try keyManager.getPublicKey(keyAlias: "keyAliasNotInStore")) + } + + func test_signSucceedsWhenKeyIsInKeyManager() throws { + let keyAlias = try keyManager.generatePrivateKey(algorithm: .es256k) + let payload = "Hello, world!".data(using: .utf8)! + XCTAssertNoThrow(try keyManager.sign(keyAlias: keyAlias, payload: payload)) + } + + func test_signThrowsErrorWhenKeyIsNotInKeyManager() throws { + let payload = "Hello, world!".data(using: .utf8)! + XCTAssertThrowsError(try keyManager.sign(keyAlias: "InvalidAlias", payload: payload)) + } +} diff --git a/Tests/tbDEXTests/crypto/Secp256k1Tests.swift b/Tests/tbDEXTests/crypto/Secp256k1Tests.swift index 1d0c2d0..80d0399 100644 --- a/Tests/tbDEXTests/crypto/Secp256k1Tests.swift +++ b/Tests/tbDEXTests/crypto/Secp256k1Tests.swift @@ -6,7 +6,7 @@ import secp256k1 final class Secp256k1Tests: XCTestCase { func test_generatePrivateKey() throws { - let privateKey = try Secp256k1.generatePrivateKey() + let privateKey = try Secp256k1.shared.generatePrivateKey() XCTAssertEqual(privateKey.curve, .secp256k1) XCTAssertEqual(privateKey.keyType, .elliptic) @@ -17,8 +17,8 @@ final class Secp256k1Tests: XCTestCase { } func test_computePublicKey() throws { - let privateKey = try Secp256k1.generatePrivateKey() - let publicKey = try Secp256k1.computePublicKey(privateKey: privateKey) + let privateKey = try Secp256k1.shared.generatePrivateKey() + let publicKey = try Secp256k1.shared.computePublicKey(privateKey: privateKey) XCTAssertEqual(publicKey.curve, .secp256k1) XCTAssertEqual(publicKey.keyType, .elliptic) @@ -36,7 +36,7 @@ final class Secp256k1Tests: XCTestCase { func test_bytesToPrivateKey_returnedInJwkFormat() throws { let privateKeyBytes = Data.fromHexString("740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88")! - let privateKey = try Secp256k1.bytesToPrivateKey(privateKeyBytes) + let privateKey = try Secp256k1.shared.bytesToPrivateKey(privateKeyBytes) XCTAssertEqual(privateKey.curve, .secp256k1) XCTAssertEqual(privateKey.keyType, .elliptic) @@ -54,7 +54,7 @@ final class Secp256k1Tests: XCTestCase { for vector in testVector.vectors { let privateKeyBytes = Data.fromHexString(vector.input["privateKeyBytes"]!)! - let privateKey = try Secp256k1.bytesToPrivateKey(privateKeyBytes) + let privateKey = try Secp256k1.shared.bytesToPrivateKey(privateKeyBytes) XCTAssertEqual(privateKey, vector.output) } } @@ -63,7 +63,7 @@ final class Secp256k1Tests: XCTestCase { let publicKeyBytes = Data.fromHexString( "043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553" )! - let publicKey = try Secp256k1.bytesToPublicKey(publicKeyBytes) + let publicKey = try Secp256k1.shared.bytesToPublicKey(publicKeyBytes) XCTAssertEqual(publicKey.curve, .secp256k1) XCTAssertEqual(publicKey.keyType, .elliptic) @@ -81,7 +81,7 @@ final class Secp256k1Tests: XCTestCase { for vector in testVector.vectors { let privateKeyBytes = Data.fromHexString(vector.input["publicKeyBytes"]!)! - let publicKey = try Secp256k1.bytesToPublicKey(privateKeyBytes) + let publicKey = try Secp256k1.shared.bytesToPublicKey(privateKeyBytes) XCTAssertEqual(publicKey, vector.output) } } @@ -93,7 +93,7 @@ final class Secp256k1Tests: XCTestCase { "046bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce21465062296011dd076ae4e8ce5163ccf69d01496d3147656dcc96645b95211f3c6" )! - let output = try Secp256k1.compressPublicKey(publicKeyBytes: uncompressedPublicKeyBytes) + let output = try Secp256k1.shared.compressPublicKey(publicKeyBytes: uncompressedPublicKeyBytes) XCTAssertEqual(output.count, 33) XCTAssertEqual(output, compressedPublicKeyBytes) } @@ -104,7 +104,7 @@ final class Secp256k1Tests: XCTestCase { )! do { - let _ = try Secp256k1.compressPublicKey(publicKeyBytes: invalidUncompressedPublicKeyBytes) + let _ = try Secp256k1.shared.compressPublicKey(publicKeyBytes: invalidUncompressedPublicKeyBytes) XCTFail("Expected function to throw an error") } catch { XCTAssert(true, "Successfully threw an error") @@ -118,7 +118,7 @@ final class Secp256k1Tests: XCTestCase { "046bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce21465062296011dd076ae4e8ce5163ccf69d01496d3147656dcc96645b95211f3c6" )! - let output = try Secp256k1.decompressPublicKey(publicKeyBytes: compressedPublicKeyBytes) + let output = try Secp256k1.shared.decompressPublicKey(publicKeyBytes: compressedPublicKeyBytes) XCTAssertEqual(output.count, 65) XCTAssertEqual(output, uncompressedPublicKeyBytes) } @@ -128,7 +128,7 @@ final class Secp256k1Tests: XCTestCase { "fef0b998921eafb58f49efdeb0adc47123aa28a4042924236f08274d50c72fe7b0")! do { - let _ = try Secp256k1.decompressPublicKey(publicKeyBytes: invalidCompressedPublicKeyBytes) + let _ = try Secp256k1.shared.decompressPublicKey(publicKeyBytes: invalidCompressedPublicKeyBytes) XCTFail("Excpected function to throw an error") } catch { XCTAssert(true, "Successfully threw an error") @@ -146,7 +146,7 @@ final class Secp256k1Tests: XCTestCase { let expectedX = Data.fromHexString(vector.output["x"]!)! let expectedY = Data.fromHexString(vector.output["y"]!)! - let (x, y) = try Secp256k1.getCurvePoints(keyBytes: keyBytes) + let (x, y) = try Secp256k1.shared.getCurvePoints(keyBytes: keyBytes) XCTAssertEqual(x, expectedX) XCTAssertEqual(y, expectedY) } @@ -159,7 +159,7 @@ final class Secp256k1Tests: XCTestCase { ) for vector in testVector.vectors { - let bytes = try Secp256k1.privateKeyToBytes(vector.input["privateKey"]!) + let bytes = try Secp256k1.shared.privateKeyToBytes(vector.input["privateKey"]!) XCTAssertEqual(bytes, Data.fromHexString(vector.output)!) } } @@ -171,15 +171,15 @@ final class Secp256k1Tests: XCTestCase { ) for vector in testVector.vectors { - let bytes = try Secp256k1.publicKeyToBytes(vector.input["publicKey"]!) + let bytes = try Secp256k1.shared.publicKeyToBytes(vector.input["publicKey"]!) XCTAssertEqual(bytes, Data.fromHexString(vector.output)!) } } func test_sign_returns64ByteSignature() throws { - let privateKey = try Secp256k1.generatePrivateKey() + let privateKey = try Secp256k1.shared.generatePrivateKey() let data = Data([51, 52, 53]) - let signature = try Secp256k1.sign(privateKey: privateKey, payload: data) + let signature = try Secp256k1.shared.sign(privateKey: privateKey, payload: data) XCTAssertEqual(signature.count, 64) } @@ -191,7 +191,7 @@ final class Secp256k1Tests: XCTestCase { for vector in testVector.vectors { let privateKeyBytes = Data.fromHexString(vector.input["key"]!)! - XCTAssertEqual(Secp256k1.validatePrivateKey(privateKeyBytes: privateKeyBytes), vector.output) + XCTAssertEqual(Secp256k1.shared.validatePrivateKey(privateKeyBytes: privateKeyBytes), vector.output) } } @@ -203,28 +203,28 @@ final class Secp256k1Tests: XCTestCase { for vector in testVector.vectors { let publicKeyBytes = Data.fromHexString(vector.input["key"]!)! - XCTAssertEqual(Secp256k1.validatePublicKey(publicKeyBytes: publicKeyBytes), vector.output) + XCTAssertEqual(Secp256k1.shared.validatePublicKey(publicKeyBytes: publicKeyBytes), vector.output) } } func test_verify() throws { - let privateKey = try Secp256k1.generatePrivateKey() - let publickey = try Secp256k1.computePublicKey(privateKey: privateKey) + let privateKey = try Secp256k1.shared.generatePrivateKey() + let publickey = try Secp256k1.shared.computePublicKey(privateKey: privateKey) let data = Data([51, 52, 53]) - let signature = try Secp256k1.sign(privateKey: privateKey, payload: data) - let isValid = try Secp256k1.verify(publicKey: publickey, signature: signature, signedPayload: data) + let signature = try Secp256k1.shared.sign(privateKey: privateKey, payload: data) + let isValid = try Secp256k1.shared.verify(publicKey: publickey, signature: signature, signedPayload: data) XCTAssertTrue(isValid) } func test_verify_returnsFalseIfSignedDataWasMutated() throws { - let privateKey = try Secp256k1.generatePrivateKey() - let publickey = try Secp256k1.computePublicKey(privateKey: privateKey) + let privateKey = try Secp256k1.shared.generatePrivateKey() + let publickey = try Secp256k1.shared.computePublicKey(privateKey: privateKey) let data = Data([1, 2, 3, 4, 5, 6, 7, 8]) - let signature = try Secp256k1.sign(privateKey: privateKey, payload: data) - var isValid = try Secp256k1.verify(publicKey: publickey, signature: signature, signedPayload: data) + let signature = try Secp256k1.shared.sign(privateKey: privateKey, payload: data) + var isValid = try Secp256k1.shared.verify(publicKey: publickey, signature: signature, signedPayload: data) XCTAssertTrue(isValid) // Make a copy and flip the least significant bit of the data @@ -232,36 +232,36 @@ final class Secp256k1Tests: XCTestCase { mutatedData[0] ^= 1 << 0 // Verification should now return false, as the given data does not match the data used to generate signature - isValid = try Secp256k1.verify(publicKey: publickey, signature: signature, signedPayload: mutatedData) + isValid = try Secp256k1.shared.verify(publicKey: publickey, signature: signature, signedPayload: mutatedData) XCTAssertFalse(isValid) } func test_verify_returnsFalseIfSignatureWasMutated() throws { - let privateKey = try Secp256k1.generatePrivateKey() - let publickey = try Secp256k1.computePublicKey(privateKey: privateKey) + let privateKey = try Secp256k1.shared.generatePrivateKey() + let publickey = try Secp256k1.shared.computePublicKey(privateKey: privateKey) let data = Data([1, 2, 3, 4, 5, 6, 7, 8]) - let signature = try Secp256k1.sign(privateKey: privateKey, payload: data) + let signature = try Secp256k1.shared.sign(privateKey: privateKey, payload: data) - var isValid = try Secp256k1.verify(publicKey: publickey, signature: signature, signedPayload: data) + var isValid = try Secp256k1.shared.verify(publicKey: publickey, signature: signature, signedPayload: data) XCTAssertTrue(isValid) // Make a copy and flip the least significant bit of the signature var mutatedSignature = Data(signature) mutatedSignature[0] ^= 1 << 0 - isValid = try Secp256k1.verify(publicKey: publickey, signature: mutatedSignature, signedPayload: data) + isValid = try Secp256k1.shared.verify(publicKey: publickey, signature: mutatedSignature, signedPayload: data) XCTAssertFalse(isValid) } func test_verify_returnsFaleWithSignatureGeneratedUsingDifferentPrivateKey() throws { - let privateKeyA = try Secp256k1.generatePrivateKey() - let publicKeyB = try Secp256k1.computePublicKey(privateKey: Secp256k1.generatePrivateKey()) + let privateKeyA = try Secp256k1.shared.generatePrivateKey() + let publicKeyB = try Secp256k1.shared.computePublicKey(privateKey: Secp256k1.shared.generatePrivateKey()) let data = Data([1, 2, 3, 4, 5, 6, 7, 8]) - let signature = try Secp256k1.sign(privateKey: privateKeyA, payload: data) + let signature = try Secp256k1.shared.sign(privateKey: privateKeyA, payload: data) - let isValid = try Secp256k1.verify(publicKey: publicKeyB, signature: signature, signedPayload: data) + let isValid = try Secp256k1.shared.verify(publicKey: publicKeyB, signature: signature, signedPayload: data) XCTAssertFalse(isValid) }