diff --git a/Sources/KukaiCryptoSwift/KeyPair.swift b/Sources/KukaiCryptoSwift/KeyPair.swift index 060f0ad..5703c26 100644 --- a/Sources/KukaiCryptoSwift/KeyPair.swift +++ b/Sources/KukaiCryptoSwift/KeyPair.swift @@ -22,6 +22,14 @@ public enum EllipticalCurve: String, Codable { else if prefix == "tz2" { return .secp256k1 } else { return nil } } + + public static func fromBase58Key(_ key: String) -> EllipticalCurve? { + let prefix = key.lowercased().prefix(4) + + if prefix == "edpk" { return .ed25519 } + else if prefix == "sppk" { return .secp256k1 } + else { return nil } + } } /// A struct representing a both a `PrivateKey` and `PublicKey` with helper methods to create various kinds diff --git a/Sources/KukaiCryptoSwift/PublicKey.swift b/Sources/KukaiCryptoSwift/PublicKey.swift index 8601c1e..ad30ba9 100644 --- a/Sources/KukaiCryptoSwift/PublicKey.swift +++ b/Sources/KukaiCryptoSwift/PublicKey.swift @@ -92,6 +92,25 @@ public struct PublicKey: Codable { return secp256k1_ecdsa_verify(context, &cSignature, message, &publicKey) == 1 } } + + /** + Take a base58Encoded public key (with a prefix) and convert it to a tzX public key hash + */ + public static func publicKeyHash(fromBase58EncodedKey key: String) -> String? { + guard let algo = EllipticalCurve.fromBase58Key(key), + let decoded = Base58Check.decode(string: key, prefix: algo == .ed25519 ? Prefix.Keys.Ed25519.public : Prefix.Keys.Secp256k1.public), + let hash = Sodium.shared.genericHash.hash(message: decoded, outputLength: 20) else { + return nil + } + + switch algo { + case .ed25519: + return Base58Check.encode(message: hash, prefix: Prefix.Address.tz1) + + case .secp256k1: + return Base58Check.encode(message: hash, prefix: Prefix.Address.tz2) + } + } } extension PublicKey: CustomStringConvertible { diff --git a/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift b/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift index 35e7720..6ebcd48 100644 --- a/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift +++ b/Tests/KukaiCryptoSwiftTests/KeyPairTests.swift @@ -175,4 +175,18 @@ final class KeyPairTests: XCTestCase { let pubKeySafety = KeyPair.secp256k1PublicKey(fromPrivateKeyBytes: signatureBytes) XCTAssert(pubKeySafety == nil, (pubKeySafety?.bytes.count ?? 0).description) } + + func testKeyToHash() throws { + let hash1 = PublicKey.publicKeyHash(fromBase58EncodedKey: "edpkuLshcvrn2x7c2QtCCMv8XFNEM2gHkPDGb3paKt2hBvnBRfepR4") + XCTAssert(hash1 == "tz1Xx4vxaUCkgxfaUhr1EV1kvTE2Rt3BkEdm", hash1 ?? "-") + + let hash2 = PublicKey.publicKeyHash(fromBase58EncodedKey: "edpkuaUnRZQzwP1QYHFFXzbhN919wg17KHm7vHH86pxxgSSkqT7U4a") + XCTAssert(hash2 == "tz1ZYoRJ2iouRi5r6CT83Ptp9Bof7RMRkxXe", hash2 ?? "-") + + let hash3 = PublicKey.publicKeyHash(fromBase58EncodedKey: "edpkufQ3nNdMJBkgfzCgCLmk1tbfLsqK7W8AR37KiCe7tDVvmsroHh") + XCTAssert(hash3 == "tz1WCBJKr1rRivyCnN9hREpRAMqrLdmqDcym", hash3 ?? "-") + + let hash4 = PublicKey.publicKeyHash(fromBase58EncodedKey: "sppk7Zzqz2AjP4yXqr5ys99gZkaPLFKfGKnUxn3u1T1xfNSArZ5CKX6") + XCTAssert(hash4 == "tz2HpbGQcmU3UyusJ78Sbqeg9fYteamSMDGo", hash4 ?? "-") + } }