From 0ec32de238ba1f0ebfd4e91017824e9fbc4c1a21 Mon Sep 17 00:00:00 2001 From: Simon McLoughlin Date: Mon, 18 Sep 2023 12:18:07 +0100 Subject: [PATCH] - bring test coverage up to >98% - remove Bech32 as its unused - fix issue with verify function - remove unused/old functions - add more tests --- .swiftpm/KukaiCryptoSwift.xctestplan | 32 +++ .../xcschemes/KukaiCryptoSwift.xcscheme | 97 ++++++++ Sources/KukaiCryptoSwift/Base58/Bech32.swift | 221 ------------------ Sources/KukaiCryptoSwift/PrivateKey.swift | 49 +--- Sources/KukaiCryptoSwift/PublicKey.swift | 29 +-- .../KukaiCryptoSwiftTests/EncodingTests.swift | 72 ++++++ .../KukaiCryptoSwiftTests/KeyPairTests.swift | 63 +++-- .../KukaiCryptoSwiftTests/MnemonicTests.swift | 35 ++- 8 files changed, 287 insertions(+), 311 deletions(-) create mode 100644 .swiftpm/KukaiCryptoSwift.xctestplan create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/KukaiCryptoSwift.xcscheme delete mode 100644 Sources/KukaiCryptoSwift/Base58/Bech32.swift create mode 100644 Tests/KukaiCryptoSwiftTests/EncodingTests.swift diff --git a/.swiftpm/KukaiCryptoSwift.xctestplan b/.swiftpm/KukaiCryptoSwift.xctestplan new file mode 100644 index 0000000..5a02d7b --- /dev/null +++ b/.swiftpm/KukaiCryptoSwift.xctestplan @@ -0,0 +1,32 @@ +{ + "configurations" : [ + { + "id" : "F1EB509B-4128-4914-BD49-6306AE2AF23E", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : { + "targets" : [ + { + "containerPath" : "container:", + "identifier" : "KukaiCryptoSwift", + "name" : "KukaiCryptoSwift" + } + ] + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:", + "identifier" : "KukaiCryptoSwiftTests", + "name" : "KukaiCryptoSwiftTests" + } + } + ], + "version" : 1 +} diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/KukaiCryptoSwift.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/KukaiCryptoSwift.xcscheme new file mode 100644 index 0000000..bb31e3c --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/KukaiCryptoSwift.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/KukaiCryptoSwift/Base58/Bech32.swift b/Sources/KukaiCryptoSwift/Base58/Bech32.swift deleted file mode 100644 index 32a66f7..0000000 --- a/Sources/KukaiCryptoSwift/Base58/Bech32.swift +++ /dev/null @@ -1,221 +0,0 @@ -// -// Bech32.swift -// -// Copyright © 2019 BitcoinKit developers -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Foundation - -/// A set of Bech32 coding methods. -/// -/// ``` -/// // Encode bytes to address -/// let cashaddr: String = Bech32.encode(payload: [versionByte] + pubkeyHash, -/// prefix: "bitcoincash") -/// -/// // Decode address to bytes -/// guard let payload: Data = Bech32.decode(text: address) else { -/// // Invalid checksum or Bech32 coding -/// throw SomeError() -/// } -/// let versionByte = payload[0] -/// let pubkeyHash = payload.dropFirst() -/// ``` -public struct Bech32 { - internal static let base32Alphabets = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" - - /// Encodes the data to Bech32 encoded string - /// - /// Creates checksum bytes from the prefix and the payload, and then puts the - /// checksum bytes to the original data. Then, encode the combined data to - /// Base32 string. At last, returns the combined string of prefix, separator - /// and the encoded base32 text. - /// ``` - /// let address = Base58Check.encode(payload: [versionByte] + pubkeyHash, - /// prefix: "bitcoincash") - /// ``` - /// - Parameters: - /// - payload: The data to encode - /// - prefix: The prefix of the encoded text. It is also used to create checksum. - /// - separator: separator that separates prefix and Base32 encoded text - public static func encode(payload: Data, prefix: String, separator: String = ":") -> String { - let payloadUint5 = convertTo5bit(data: payload, pad: true) - let checksumUint5: Data = createChecksum(prefix: prefix, payload: payloadUint5) // Data of [UInt5] - let combined: Data = payloadUint5 + checksumUint5 // Data of [UInt5] - var base32 = "" - for b in combined { - let index = String.Index(utf16Offset: Int(b), in: base32Alphabets) - base32 += String(base32Alphabets[index]) - } - - return prefix + separator + base32 - } - - @available(*, unavailable, renamed: "encode(payload:prefix:separator:)") - public static func encode(_ bytes: Data, prefix: String, seperator: String = ":") -> String { - return encode(payload: bytes, prefix: prefix, separator: seperator) - } - - /// Decodes the Bech32 encoded string to original payload - /// - /// ``` - /// // Decode address to bytes - /// guard let payload: Data = Bech32.decode(text: address) else { - /// // Invalid checksum or Bech32 coding - /// throw SomeError() - /// } - /// let versionByte = payload[0] - /// let pubkeyHash = payload.dropFirst() - /// ``` - /// - Parameters: - /// - string: The data to encode - /// - separator: separator that separates prefix and Base32 encoded text - public static func decode(_ string: String, separator: String = ":") -> (prefix: String, data: Data)? { - // We can't have empty string. - // Bech32 should be uppercase only / lowercase only. - guard !string.isEmpty && [string.lowercased(), string.uppercased()].contains(string) else { - return nil - } - - let components = string.components(separatedBy: separator) - // We can only handle string contains both scheme and base32 - guard components.count == 2 else { - return nil - } - let (prefix, base32) = (components[0], components[1]) - - var decodedIn5bit: [UInt8] = [UInt8]() - for c in base32.lowercased() { - // We can't have characters other than base32 alphabets. - guard let baseIndex = base32Alphabets.firstIndex(of: c)?.utf16Offset(in: base32Alphabets) else { - return nil - } - decodedIn5bit.append(UInt8(baseIndex)) - } - - // We can't have invalid checksum - let payload = Data(decodedIn5bit) - guard verifyChecksum(prefix: prefix, payload: payload) else { - return nil - } - - // Drop checksum - guard let bytes = try? convertFrom5bit(data: payload.dropLast(8)) else { - return nil - } - return (prefix, Data(bytes)) - } - @available(*, unavailable, renamed: "decode(string:separator:)") - public static func decode(_ string: String, seperator: String = ":") -> (prefix: String, data: Data)? { - return decode(string, separator: seperator) - } - - internal static func verifyChecksum(prefix: String, payload: Data) -> Bool { - return PolyMod(expand(prefix) + payload) == 0 - } - - internal static func expand(_ prefix: String) -> Data { - var ret: Data = Data() - let buf: [UInt8] = Array(prefix.utf8) - for b in buf { - ret.append(b & 0x1f) - } - ret += Data(repeating: 0, count: 1) - return ret - } - - internal static func createChecksum(prefix: String, payload: Data) -> Data { - let enc: Data = expand(prefix) + payload + Data(repeating: 0, count: 8) - let mod: UInt64 = PolyMod(enc) - var ret: Data = Data() - for i in 0..<8 { - ret.append(UInt8((mod >> (5 * (7 - i))) & 0x1f)) - } - return ret - } - - internal static func PolyMod(_ data: Data) -> UInt64 { - var c: UInt64 = 1 - for d in data { - let c0: UInt8 = UInt8(c >> 35) - c = ((c & 0x07ffffffff) << 5) ^ UInt64(d) - if c0 & 0x01 != 0 { c ^= 0x98f2bc8e61 } - if c0 & 0x02 != 0 { c ^= 0x79b76d99e2 } - if c0 & 0x04 != 0 { c ^= 0xf33e5fb3c4 } - if c0 & 0x08 != 0 { c ^= 0xae2eabe2a8 } - if c0 & 0x10 != 0 { c ^= 0x1e4f43e470 } - } - return c ^ 1 - } - - internal static func convertTo5bit(data: Data, pad: Bool) -> Data { - var acc = Int() - var bits = UInt8() - let maxv: Int = 31 // 31 = 0x1f = 00011111 - var converted: [UInt8] = [] - for d in data { - acc = (acc << 8) | Int(d) - bits += 8 - - while bits >= 5 { - bits -= 5 - converted.append(UInt8(acc >> Int(bits) & maxv)) - } - } - - let lastBits: UInt8 = UInt8(acc << (5 - bits) & maxv) - if pad && bits > 0 { - converted.append(lastBits) - } - return Data(converted) - } - - internal static func convertFrom5bit(data: Data) throws -> Data { - var acc = Int() - var bits = UInt8() - let maxv: Int = 255 // 255 = 0xff = 11111111 - var converted: [UInt8] = [] - for d in data { - guard (d >> 5) == 0 else { - throw DecodeError.invalidCharacter - } - acc = (acc << 5) | Int(d) - bits += 5 - - while bits >= 8 { - bits -= 8 - converted.append(UInt8(acc >> Int(bits) & maxv)) - } - } - - let lastBits: UInt8 = UInt8(acc << (8 - bits) & maxv) - guard bits < 5 && lastBits == 0 else { - throw DecodeError.invalidBits - } - - return Data(converted) - } - - internal enum DecodeError: Error { - case invalidCharacter - case invalidBits - } -} diff --git a/Sources/KukaiCryptoSwift/PrivateKey.swift b/Sources/KukaiCryptoSwift/PrivateKey.swift index 459cf27..9cc5034 100644 --- a/Sources/KukaiCryptoSwift/PrivateKey.swift +++ b/Sources/KukaiCryptoSwift/PrivateKey.swift @@ -37,51 +37,6 @@ public struct PrivateKey: Codable { // MARK: - Init - /** - Initialize a key with the given hex seed string. - - parameter seedString a hex encoded seed string. - - parameter signingCurve: The elliptical curve to use for the key. Defaults to ed25519. - */ - public init?(seedString: String, signingCurve: EllipticalCurve = .ed25519) { - guard let seed = Sodium.shared.utils.hex2bin(seedString), let keyPair = Sodium.shared.sign.keyPair(seed: seed) else { - return nil - } - - // Key is 64 bytes long. The first 32 bytes are the private key. Sodium, the ed25519 library expects extended - // private keys, so pass down the full 64 bytes. - let secretKeyBytes = keyPair.secretKey - switch signingCurve { - case .ed25519: - self.init(secretKeyBytes, signingCurve: signingCurve) - - case .secp256k1: - let privateKeyBytes = Array(secretKeyBytes[..<32]) - self.init(privateKeyBytes, signingCurve: signingCurve) - } - - } - - /** - Initialize a key with the given base58check encoded string. - - parameter string: A base58check encoded string. - - parameter signingCurve: The elliptical curve to use for the key. Defaults to ed25519. - */ - public init?(_ string: String, signingCurve: EllipticalCurve = .ed25519) { - switch signingCurve { - case .ed25519: - guard let bytes = Base58Check.decode(string: string, prefix: Prefix.Keys.Ed25519.secret) else { - return nil - } - self.init(bytes) - - case .secp256k1: - guard let bytes = Base58Check.decode(string: string, prefix: Prefix.Keys.Secp256k1.secret) else { - return nil - } - self.init(bytes, signingCurve: .secp256k1) - } - } - /** Initialize a key with the given bytes. - parameter bytes: Raw bytes of the private key. @@ -92,6 +47,10 @@ public struct PrivateKey: Codable { self.signingCurve = signingCurve } + + + // MARK: - Utils + /** Sign the given hex encoded string with the given key. - parameter hex: The hex string to sign. diff --git a/Sources/KukaiCryptoSwift/PublicKey.swift b/Sources/KukaiCryptoSwift/PublicKey.swift index 3c2cec4..3770cfc 100644 --- a/Sources/KukaiCryptoSwift/PublicKey.swift +++ b/Sources/KukaiCryptoSwift/PublicKey.swift @@ -58,26 +58,9 @@ public struct PublicKey: Codable { self.signingCurve = signingCurve } - /// Initialize a public key with the given base58check encoded string. - public init?(string: String, signingCurve: EllipticalCurve) { - switch signingCurve { - case .ed25519: - guard let bytes = Base58Check.decode(string: string, prefix: Prefix.Keys.Ed25519.public) else { - return nil - } - self.init(bytes, signingCurve: signingCurve) - - case .secp256k1: - guard let bytes = Base58Check.decode(string: string, prefix: Prefix.Keys.Secp256k1.public) else { - return nil - } - self.init(bytes, signingCurve: signingCurve) - } - } - - // MARK: - Crypto functions + // MARK: - Utils /** Verify that the given signature matches the given input hex. @@ -85,11 +68,11 @@ public struct PublicKey: Codable { - parameter hex: The hex to check. - Returns: True if the public key and signature match the given bytes. */ - public func verify(signature: [UInt8], hex: String) -> Bool { + public func verify(message: [UInt8], signature: [UInt8], hex: String) -> Bool { guard let bytes = Sodium.shared.utils.hex2bin(hex) else { return false } - return verify(signature: signature, bytes: bytes) + return verify(message: message, signature: signature, bytes: bytes) } /** @@ -98,10 +81,10 @@ public struct PublicKey: Codable { - parameter bytes: The bytes to check. - Returns: True if the public key and signature match the given bytes. */ - public func verify(signature: [UInt8], bytes: [UInt8]) -> Bool { + public func verify(message: [UInt8], signature: [UInt8], bytes: [UInt8]) -> Bool { switch signingCurve { case .ed25519: - return Sodium.shared.sign.verify(message: signature, publicKey: self.bytes, signature: signature) + return Sodium.shared.sign.verify(message: message, publicKey: self.bytes, signature: signature) case .secp256k1: let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY)) @@ -114,7 +97,7 @@ public struct PublicKey: Codable { secp256k1_ecdsa_signature_parse_compact(context!, &cSignature, signature) _ = secp256k1_ec_pubkey_parse(context!, &publicKey, self.bytes, self.bytes.count) - return secp256k1_ecdsa_verify(context!, &cSignature, signature, &publicKey) == 1 + return secp256k1_ecdsa_verify(context!, &cSignature, message, &publicKey) == 1 } } } diff --git a/Tests/KukaiCryptoSwiftTests/EncodingTests.swift b/Tests/KukaiCryptoSwiftTests/EncodingTests.swift new file mode 100644 index 0000000..434bd02 --- /dev/null +++ b/Tests/KukaiCryptoSwiftTests/EncodingTests.swift @@ -0,0 +1,72 @@ +// +// EncodingTests.swift +// +// +// Created by Simon Mcloughlin on 18/09/2023. +// + +import XCTest +@testable import KukaiCryptoSwift + +final class EncodingTests: XCTestCase { + + func testBase58CheckEncodeAndDecode() throws { + + let base58_1 = "edpkvCbYCa6d6g9hEcK6tvwgsY9jfB4HDzp3jZSBwfuWNSvxE5T5KR" + let base58_2 = "sppk7bXQFZLFWSLusY6gzH9NfbFWT6c61d5vb5zxNZycThMk1qMtPwk" + let base58_3 = "edpkuC1VC96abMGC9uhGi8zfAkEM3AH4bd5H6jiHeA9kZXD4gzVKCY" + let base58_4 = "sppk7b2poW37GfMQTbeRpFAKrhisSobBW6Ece49cKJzkDDfaT2maXRy" + + let decode1 = Base58Check.decode(string: base58_1, prefix: Prefix.Keys.Ed25519.public) + let decode1Hex = decode1?.hexString ?? "" + let decode2 = Base58Check.decode(string: base58_2, prefix: Prefix.Keys.Secp256k1.public) + let decode2Hex = decode2?.hexString ?? "" + let decode3 = Base58Check.decode(string: base58_3, prefix: Prefix.Keys.Ed25519.public) + let decode3Hex = decode3?.hexString ?? "" + let decode4 = Base58Check.decode(string: base58_4, prefix: Prefix.Keys.Secp256k1.public) + let decode4Hex = decode4?.hexString ?? "" + + XCTAssert(decode1Hex == "cd33a22f74d8e04977f74db15a0b1e92d21a59f351e987b9fd462bf6ef2dc253", decode1Hex) + XCTAssert(decode2Hex == "032460b1fb47abc6b64bfa313efdba92eb4313f58b90ac30b68851b4880cc9c819", decode2Hex) + XCTAssert(decode3Hex == "482c29dcbfc1f94c185e9d8da1ee7e06b16239a5d4e15a64a6f4150c298ab029", decode3Hex) + XCTAssert(decode4Hex == "02e37da4dd8966a3f6941e81f72e884e47687a79f2cfe55c903f9acb2c94c8936f", decode4Hex) + + + + let encode1 = Base58Check.encode(message: decode1 ?? [], prefix: Prefix.Keys.Ed25519.public) + let encode2 = Base58Check.encode(message: decode2 ?? [], prefix: Prefix.Keys.Secp256k1.public) + let encode3 = Base58Check.encode(message: decode3 ?? [], prefix: Prefix.Keys.Ed25519.public) + let encode4 = Base58Check.encode(message: decode4 ?? [], prefix: Prefix.Keys.Secp256k1.public) + + XCTAssert(encode1 == base58_1, encode1) + XCTAssert(encode2 == base58_2, encode2) + XCTAssert(encode3 == base58_3, encode3) + XCTAssert(encode4 == base58_4, encode4) + + + + let message1 = Base58Check.encode(message: "testing something encodeable 1".bytes, ellipticalCurve: .ed25519) + let message2 = Base58Check.encode(message: "testing something encodeable 2".bytes, ellipticalCurve: .secp256k1) + let message3 = Base58Check.encode(message: "testing something encodeable 3".bytes, ellipticalCurve: .ed25519) + let message4 = Base58Check.encode(message: "testing something encodeable 4".bytes, ellipticalCurve: .secp256k1) + + XCTAssert(message1 == "7WBtn3E9RBK4PtEoP15sYTiSgLL89fFJmQEAbd9HBgTWThM7PTcza", message1) + XCTAssert(message2 == "9nNmTC8QADXQZCcSV25iGHxJZZcMHPayRZ7dqsLxoeKYdetf3FyPT", message2) + XCTAssert(message3 == "7WBtn3E9RBK4PtEoP15sYTiSgLL89fFJmQEAbd9HBgTWThMHT1FF6", message3) + XCTAssert(message4 == "9nNmTC8QADXQZCcSV25iGHxJZZcMHPayRZ7dqsLxoeKYdetwZjsv1", message4) + + let decodedMessage1 = Base58Check.decode(string: message1, prefix: Prefix.Keys.Ed25519.signature) ?? [] + let data1 = Data(bytes: decodedMessage1, count: decodedMessage1.count) + let decodedMessage2 = Base58Check.decode(string: message2, prefix: Prefix.Keys.Secp256k1.signature) ?? [] + let data2 = Data(bytes: decodedMessage2, count: decodedMessage1.count) + let decodedMessage3 = Base58Check.decode(string: message3, prefix: Prefix.Keys.Ed25519.signature) ?? [] + let data3 = Data(bytes: decodedMessage3, count: decodedMessage1.count) + let decodedMessage4 = Base58Check.decode(string: message4, prefix: Prefix.Keys.Secp256k1.signature) ?? [] + let data4 = Data(bytes: decodedMessage4, count: decodedMessage1.count) + + XCTAssert(String(data: data1, encoding: .utf8) == "testing something encodeable 1", decodedMessage1.hexString) + XCTAssert(String(data: data2, encoding: .utf8) == "testing something encodeable 2", decodedMessage1.hexString) + XCTAssert(String(data: data3, encoding: .utf8) == "testing something encodeable 3", decodedMessage1.hexString) + XCTAssert(String(data: data4, encoding: .utf8) == "testing something encodeable 4", decodedMessage1.hexString) + } +} diff --git a/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift b/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift index 4b31b39..0ccd6e3 100644 --- a/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift +++ b/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift @@ -16,36 +16,48 @@ final class KeyPairTests: XCTestCase { let mnemonic = try Mnemonic(seedPhrase: "remember smile trip tumble era cube worry fuel bracket eight kitten inform") let keyPair1 = KeyPair.regular(fromMnemonic: mnemonic, passphrase: "", andSigningCurve: .ed25519) + let signature1 = keyPair1?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex1 = signature1.hexString XCTAssert(keyPair1?.privateKey.bytes.hexString == "80d4e52897c8e14fbfad4637373de405fa2cc7f27eb9f890db975948b0e7fdb0cd33a22f74d8e04977f74db15a0b1e92d21a59f351e987b9fd462bf6ef2dc253", keyPair1?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair1?.publicKey.bytes.hexString == "cd33a22f74d8e04977f74db15a0b1e92d21a59f351e987b9fd462bf6ef2dc253", keyPair1?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair1?.publicKey.publicKeyHash == "tz1T3QZ5w4K11RS3vy4TXiZepraV9R5GzsxG", keyPair1?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair1?.publicKey.base58CheckRepresentation == "edpkvCbYCa6d6g9hEcK6tvwgsY9jfB4HDzp3jZSBwfuWNSvxE5T5KR", keyPair1?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair1?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "c4d20c77d627d8c07e3f26ddc2e8ab9324471c65f9abd412de70a81c21ddc153dcfad1b31ab777a83c4e8a5dc021ea30d84da107dea4a192fc2ca9da9b3ede00", - keyPair1?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair1?.privateKey.base58CheckRepresentation == "edskRtsMi4AkpGSt88FzJXZtMo2HkB7BBcbN77FXgXqR1doC1eSpU7sApWaF7aBq23quKDkcYMU1eSLLXZynxA6n5pmunJaFUB", keyPair1?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex1 == "c4d20c77d627d8c07e3f26ddc2e8ab9324471c65f9abd412de70a81c21ddc153dcfad1b31ab777a83c4e8a5dc021ea30d84da107dea4a192fc2ca9da9b3ede00", signatureHex1) + XCTAssert(keyPair1?.publicKey.verify(message: watermarkedBytes, signature: signature1, hex: signatureHex1) == true) let keyPair2 = KeyPair.regular(fromMnemonic: mnemonic, passphrase: "", andSigningCurve: .secp256k1) + let signature2 = keyPair2?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex2 = signature2.hexString XCTAssert(keyPair2?.privateKey.bytes.hexString == "80d4e52897c8e14fbfad4637373de405fa2cc7f27eb9f890db975948b0e7fdb0", keyPair2?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair2?.publicKey.bytes.hexString == "032460b1fb47abc6b64bfa313efdba92eb4313f58b90ac30b68851b4880cc9c819", keyPair2?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair2?.publicKey.publicKeyHash == "tz2UiZQJwaVAKxRuYxV8Tx5k8a64gZx1ZwYJ", keyPair2?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair2?.publicKey.base58CheckRepresentation == "sppk7bXQFZLFWSLusY6gzH9NfbFWT6c61d5vb5zxNZycThMk1qMtPwk", keyPair2?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair2?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "699bc6f9f3ad5987e02b5b2dfccfa86c5583be632bd60840abd5a14c94fb7dea43e39d1f08b8d406a26bf2de337313e8dad054a26b93fec76063e24bde6b8495", - keyPair2?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair2?.privateKey.base58CheckRepresentation == "spsk2QJWfXvnhrRW7ro8f8o7hr8kmcU8pYkV6pPvJYCNvESTaua8Cc", keyPair2?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex2 == "699bc6f9f3ad5987e02b5b2dfccfa86c5583be632bd60840abd5a14c94fb7dea43e39d1f08b8d406a26bf2de337313e8dad054a26b93fec76063e24bde6b8495", signatureHex2) + XCTAssert(keyPair2?.publicKey.verify(message: watermarkedBytes, signature: signature2, hex: signatureHex2) == true) let keyPair3 = KeyPair.regular(fromMnemonic: mnemonic, passphrase: "superSecurePassphrase", andSigningCurve: .ed25519) + let signature3 = keyPair3?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex3 = signature3.hexString XCTAssert(keyPair3?.privateKey.bytes.hexString == "b17877f6b326bf75e8a5bf2bd7e457a03b103d469c869ef4e3b0473d9b9d50b1482c29dcbfc1f94c185e9d8da1ee7e06b16239a5d4e15a64a6f4150c298ab029", keyPair3?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair3?.publicKey.bytes.hexString == "482c29dcbfc1f94c185e9d8da1ee7e06b16239a5d4e15a64a6f4150c298ab029", keyPair3?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair3?.publicKey.publicKeyHash == "tz1hQ4wkVfNAh3eGeaDpoTBmQ9KjX9ZMzc6q", keyPair3?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair3?.publicKey.base58CheckRepresentation == "edpkuC1VC96abMGC9uhGi8zfAkEM3AH4bd5H6jiHeA9kZXD4gzVKCY", keyPair3?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair3?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "f83f6075f87269ae141843bda4867942e4f9f7a299289eaed7f2185ad1ad0bb71e5b976e5a3169b32756d5d87a05875d2d3fc3615cc1509ab05c46df8d30b705", - keyPair3?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair3?.privateKey.base58CheckRepresentation == "edskS1ES45UdtLknPMTYLe6KBjkuevu98jK1QHABNw26syaCNehzSAL57xoErVszznEZZZ37TsZNekeh4y9hW4Wbg9huPFTLoA", keyPair3?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex3 == "f83f6075f87269ae141843bda4867942e4f9f7a299289eaed7f2185ad1ad0bb71e5b976e5a3169b32756d5d87a05875d2d3fc3615cc1509ab05c46df8d30b705", signatureHex3) + XCTAssert(keyPair3?.publicKey.verify(message: watermarkedBytes, signature: signature3, hex: signatureHex3) == true) let keyPair4 = KeyPair.regular(fromMnemonic: mnemonic, passphrase: "superSecurePassphrase", andSigningCurve: .secp256k1) + let signature4 = keyPair4?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex4 = signature4.hexString XCTAssert(keyPair4?.privateKey.bytes.hexString == "b17877f6b326bf75e8a5bf2bd7e457a03b103d469c869ef4e3b0473d9b9d50b1", keyPair4?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair4?.publicKey.bytes.hexString == "02e37da4dd8966a3f6941e81f72e884e47687a79f2cfe55c903f9acb2c94c8936f", keyPair4?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair4?.publicKey.publicKeyHash == "tz2J2VKJaVRBwFs96hRiSAqHjJmRmqGirKv8", keyPair4?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair4?.publicKey.base58CheckRepresentation == "sppk7b2poW37GfMQTbeRpFAKrhisSobBW6Ece49cKJzkDDfaT2maXRy", keyPair4?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair4?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "1f19dd5887c4e739377f1303db66bc12863942bc969ea29055152ddf7f25d32c0e8ff3ea8ac18ab58f328b830debad43e78cbf483dad38441301f59e6af633fa", - keyPair4?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair4?.privateKey.base58CheckRepresentation == "spsk2mivr4XyTkHZ941svcQEJJMn84w6yy3H6NiRFh2QfnHZQmHT4r", keyPair4?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex4 == "1f19dd5887c4e739377f1303db66bc12863942bc969ea29055152ddf7f25d32c0e8ff3ea8ac18ab58f328b830debad43e78cbf483dad38441301f59e6af633fa", signatureHex4) + XCTAssert(keyPair4?.publicKey.verify(message: watermarkedBytes, signature: signature4, hex: signatureHex4) == true) } func testHD() throws { @@ -54,44 +66,59 @@ final class KeyPairTests: XCTestCase { let mnemonic = try Mnemonic(seedPhrase: "gym exact clown can answer hope sample mirror knife twenty powder super imitate lion churn almost shed chalk dust civil gadget pyramid helmet trade") let keyPair1 = KeyPair.hd(fromMnemonic: mnemonic, passphrase: "", andDerivationPath: "44'/1729'/0'/0'") + let signature1 = keyPair1?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex1 = signature1.hexString XCTAssert(keyPair1?.privateKey.bytes.hexString == "7b0c9fc748c9c784d50152fd2db370522a8727a8ec68fd0b7ef456330e2e089c66dc7517defa76d4355280505068b59172a568cacbd46c1f5f91a247c29426bc", keyPair1?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair1?.publicKey.bytes.hexString == "66dc7517defa76d4355280505068b59172a568cacbd46c1f5f91a247c29426bc", keyPair1?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair1?.publicKey.publicKeyHash == "tz1TyyX7U6r6tB1uSS4aUnfKX9rj3y9NCEVL", keyPair1?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair1?.publicKey.base58CheckRepresentation == "edpkuRXPQpuQyDemXE59dyYA1Eu5T94waiiL5PjcWDSkkw86ZvxR2j", keyPair1?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair1?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "d9f272a3ed8459e0b51575dc6660628e64acb231d813f04dc5c2417addd181ddf9da7fe128ed492520c77476d3db3572277b91faabd10b219a4abe6e4f83a900", - keyPair1?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair1?.privateKey.base58CheckRepresentation == "edskRt7UsfGdfmmruGVV1GY2YFHpxTDeML8ZLwSHihw6RLaXNTAEiFRaooAMCFL3BDAT5ATN5cHswXm3HKu6rsJUmF2U3n4t1z", keyPair1?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex1 == "d9f272a3ed8459e0b51575dc6660628e64acb231d813f04dc5c2417addd181ddf9da7fe128ed492520c77476d3db3572277b91faabd10b219a4abe6e4f83a900", signatureHex1) + XCTAssert(keyPair1?.publicKey.verify(message: watermarkedBytes, signature: signature1, hex: signatureHex1) == true) let keyPair2 = KeyPair.hd(fromMnemonic: mnemonic, passphrase: "", andDerivationPath: "44'/1729'/1'/0'") + let signature2 = keyPair2?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex2 = signature2.hexString XCTAssert(keyPair2?.privateKey.bytes.hexString == "bf0fc1dc57bd922369cae903710d13f966e49e8e1b0b07b7b727c4653ec5fb14865cd25e1079072c5353fb38723c606701c6e8631522738e59dab732f49b7e23", keyPair2?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair2?.publicKey.bytes.hexString == "865cd25e1079072c5353fb38723c606701c6e8631522738e59dab732f49b7e23", keyPair2?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair2?.publicKey.publicKeyHash == "tz1WCBJKr1rRivyCnN9hREpRAMqrLdmqDcym", keyPair2?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair2?.publicKey.base58CheckRepresentation == "edpkufQ3nNdMJBkgfzCgCLmk1tbfLsqK7W8AR37KiCe7tDVvmsroHh", keyPair2?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair2?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "c1d9165f9b7670bed93abb8a800bdf725b4c63c15de08f17a1d23dd6cdb5dd993e9950efec84a77c573062d82877099e1ce4c22e82982b59412874c3cec8de0a", - keyPair2?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair2?.privateKey.base58CheckRepresentation == "edskS31ZXzfzGBi1jEigpPvaWwVzwWCX3PNhx8FUpwY65SZC2oVmZ4iCHqwXCC6LBiGgdknhzJ6xAzHbpwQMEH3KKVjZ4aL4kw", keyPair2?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex2 == "c1d9165f9b7670bed93abb8a800bdf725b4c63c15de08f17a1d23dd6cdb5dd993e9950efec84a77c573062d82877099e1ce4c22e82982b59412874c3cec8de0a", signatureHex2) + XCTAssert(keyPair2?.publicKey.verify(message: watermarkedBytes, signature: signature2, hex: signatureHex2) == true) let keyPair3 = KeyPair.hd(fromMnemonic: mnemonic, passphrase: "", andDerivationPath: "44'/1729'/2147483647'/0'") + let signature3 = keyPair3?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex3 = signature3.hexString XCTAssert(keyPair3?.privateKey.bytes.hexString == "4a8ef43dfa15c30785231e1ddb2eadd13bfae7297823d42f3cd5352b981d8a993350ae690b1001d12f5b826b5bdc96a8208db3a55e32eb46309385bbf29196ad", keyPair3?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair3?.publicKey.bytes.hexString == "3350ae690b1001d12f5b826b5bdc96a8208db3a55e32eb46309385bbf29196ad", keyPair3?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair3?.publicKey.publicKeyHash == "tz1WKKg7eN7rADsFrfzZmRrEECfBcZbXKtvS", keyPair3?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair3?.publicKey.base58CheckRepresentation == "edpku2piMWnck5esnUbBXeJVK5VppMjxN85oEuPyynPqrmtC34FuKT", keyPair3?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair3?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "cc9e7d3bff6a17c8f3a6385397fb0d4e3136742e7e78f4719e7971223b3633a23c73a98d2a143d3693f480d93776506b38a9a8ec571e289b1e75b0a4bd26b200", - keyPair3?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair3?.privateKey.base58CheckRepresentation == "edskRmmXh3vqPYi3k2eVZRShEnA6u6QcYVM4iUqVw1ASxLyF58Chk5wd4MPwLSPsSALVDM8DsRCjpWtuMFzNqVTHUU8r5E8Cjx", keyPair3?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex3 == "cc9e7d3bff6a17c8f3a6385397fb0d4e3136742e7e78f4719e7971223b3633a23c73a98d2a143d3693f480d93776506b38a9a8ec571e289b1e75b0a4bd26b200", signatureHex3) + XCTAssert(keyPair3?.publicKey.verify(message: watermarkedBytes, signature: signature3, hex: signatureHex3) == true) let keyPair4 = KeyPair.hd(fromMnemonic: mnemonic, passphrase: "", andDerivationPath: "44'/1729'/1'/1'/1'") + let signature4 = keyPair4?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex4 = signature4.hexString XCTAssert(keyPair4?.privateKey.bytes.hexString == "bf7a753dde1af40df5ecc9b4f0d6471843d4c0c904f3bc8ecf627402bffdc03506b716cdb9ea32ef268de284fed61434195bc86c7d5ceb3f116ce794d6975319", keyPair4?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair4?.publicKey.bytes.hexString == "06b716cdb9ea32ef268de284fed61434195bc86c7d5ceb3f116ce794d6975319", keyPair4?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair4?.publicKey.publicKeyHash == "tz1dAgezeiGexQkgfbPm8MgP1XTqA4rJRt3C", keyPair4?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair4?.publicKey.base58CheckRepresentation == "edpkthBU4kJTd8rdbeUt3MafV4KHQEMoJ9M5idVtBrCVm5vBE2kY8K", keyPair4?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair4?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "f98074f0fc2e34b09d6ca973be900ef698718a323db5d55eb6a9633fc07659f8fcb1a22d11453ead8e36fe866de4adcbdb35be241b2828f21146f369184ced0e", - keyPair4?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair4?.privateKey.base58CheckRepresentation == "edskS34iyJpTAPGiAXmAsuraxJeujUbmqssGagUf1mZGBuoeJiXtCiBqEx4k22BPHmT5nSaY2tPucSa161Lzqi2fgt8pYqkvJQ", keyPair4?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex4 == "f98074f0fc2e34b09d6ca973be900ef698718a323db5d55eb6a9633fc07659f8fcb1a22d11453ead8e36fe866de4adcbdb35be241b2828f21146f369184ced0e", signatureHex4) + XCTAssert(keyPair4?.publicKey.verify(message: watermarkedBytes, signature: signature4, hex: signatureHex4) == true) let keyPair5 = KeyPair.hd(fromMnemonic: mnemonic, passphrase: "superSecurePassphrase", andDerivationPath: "44'/1729'/0'/0'") + let signature5 = keyPair5?.privateKey.sign(bytes: watermarkedBytes) ?? [] + let signatureHex5 = signature5.hexString XCTAssert(keyPair5?.privateKey.bytes.hexString == "ebea8d3287af11f7ab844288baaddccef75ba8a862520eca180727ece2228a3115e40e65b90e549f1f8eee368bb47d12ffdbad8322004e47c7e9a2393b4d94e3", keyPair5?.privateKey.bytes.hexString ?? "-") XCTAssert(keyPair5?.publicKey.bytes.hexString == "15e40e65b90e549f1f8eee368bb47d12ffdbad8322004e47c7e9a2393b4d94e3", keyPair5?.publicKey.bytes.hexString ?? "-") XCTAssert(keyPair5?.publicKey.publicKeyHash == "tz1TmVDbFH63shXAzmmbDkYRH3nz1RSLV463", keyPair5?.publicKey.publicKeyHash ?? "-") XCTAssert(keyPair5?.publicKey.base58CheckRepresentation == "edpktos7HPEb8SYPeZAiRQ2e96zxe12PaSiMsBxZVr12Bvi8uCEyUm", keyPair5?.publicKey.base58CheckRepresentation ?? "-") - XCTAssert(keyPair5?.privateKey.sign(bytes: watermarkedBytes)?.hexString == "45825aeb7e4cb6515d783312e527d4932029fceae883f8a0cfde5c4b938f5412a1ae86413d7b2dff41fb243cefdd4cb08cc9665968acb185e305ac1ca6ca8d0b", - keyPair5?.privateKey.sign(bytes: watermarkedBytes)?.hexString ?? "-") + XCTAssert(keyPair5?.privateKey.base58CheckRepresentation == "edskS8svPN21gfV4fhRYirkN5LY28VoFDnsUMuisWDq2PCnsydAdeQuYiRAmhxquV2mqZBiadJCVzB8tYVycWbjpUkiN9XXGGy", keyPair5?.privateKey.base58CheckRepresentation ?? "-") + XCTAssert(signatureHex5 == "45825aeb7e4cb6515d783312e527d4932029fceae883f8a0cfde5c4b938f5412a1ae86413d7b2dff41fb243cefdd4cb08cc9665968acb185e305ac1ca6ca8d0b", signatureHex5) + XCTAssert(keyPair5?.publicKey.verify(message: watermarkedBytes, signature: signature5, hex: signatureHex5) == true) } func testEllipticalCurve() { diff --git a/Tests/KukaiCryptoSwiftTests/MnemonicTests.swift b/Tests/KukaiCryptoSwiftTests/MnemonicTests.swift index f86f60d..9e18efb 100644 --- a/Tests/KukaiCryptoSwiftTests/MnemonicTests.swift +++ b/Tests/KukaiCryptoSwiftTests/MnemonicTests.swift @@ -21,13 +21,40 @@ final class MnemonicTests: XCTestCase { } func testNumberOfWords() throws { - let mnemonic = try Mnemonic(numberOfWords: .twentyFour) - XCTAssert(mnemonic.words.count == 24) + let mnemonic12 = try Mnemonic(numberOfWords: .twelve) + XCTAssert(mnemonic12.words.count == 12) + + let mnemonic15 = try Mnemonic(numberOfWords: .fifteen) + XCTAssert(mnemonic15.words.count == 15) + + let mnemonic18 = try Mnemonic(numberOfWords: .eighteen) + XCTAssert(mnemonic18.words.count == 18) + + let mnemonic21 = try Mnemonic(numberOfWords: .twentyOne) + XCTAssert(mnemonic21.words.count == 21) + + let mnemonic24 = try Mnemonic(numberOfWords: .twentyFour) + XCTAssert(mnemonic24.words.count == 24) + } + + func testChinese() throws { + let mnemonic24 = try Mnemonic(numberOfWords: .twentyFour, in: .chinese) + XCTAssert(mnemonic24.words.count == 24) + + let firstWord = mnemonic24.words.first ?? "" + let containedInEnglish = WordList.english.words.contains(firstWord) + let containedInChinese = WordList.chinese.words.contains(firstWord) + + XCTAssert(!containedInEnglish) + XCTAssert(containedInChinese) } func testEntropy() throws { - let mnemonic = try Mnemonic(entropy: Int.strongest) - XCTAssert(mnemonic.words.count == 24) + let mnemonic1 = try Mnemonic(entropy: Int.strongest) + XCTAssert(mnemonic1.words.count == 24) + + let mnemonic2 = try Mnemonic(entropy: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f") + XCTAssert(mnemonic2.words.count == 12) } func testValid() throws {