diff --git a/Features/ManageWallets/Sources/ViewModels/WalletIDetailViewModel.swift b/Features/ManageWallets/Sources/ViewModels/WalletIDetailViewModel.swift index a4d718c82..65c69f36e 100644 --- a/Features/ManageWallets/Sources/ViewModels/WalletIDetailViewModel.swift +++ b/Features/ManageWallets/Sources/ViewModels/WalletIDetailViewModel.swift @@ -96,11 +96,7 @@ extension WalletDetailViewModel { func getPrivateKey() async throws -> String { let chain = wallet.accounts[0].chain - return try await walletService.getPrivateKey( - wallet: wallet, - chain: chain, - encoding: chain.defaultKeyEncodingType - ) + return try await walletService.getPrivateKeyEncoded(wallet: wallet, chain: chain) } func delete() async throws { diff --git a/Features/Onboarding/Sources/ViewModels/ImportWalletSceneViewModel.swift b/Features/Onboarding/Sources/ViewModels/ImportWalletSceneViewModel.swift index a6767f2af..ca2ac4a02 100644 --- a/Features/Onboarding/Sources/ViewModels/ImportWalletSceneViewModel.swift +++ b/Features/Onboarding/Sources/ViewModels/ImportWalletSceneViewModel.swift @@ -8,6 +8,7 @@ import PrimitivesComponents import SwiftUI import Components import GemstonePrimitives +internal import func Gemstone.supportsPrivateKeyImport import enum Keystore.KeystoreImportType import struct Keystore.Mnemonic @@ -63,18 +64,17 @@ final class ImportWalletSceneViewModel { case .chain(let chain): chain } } - var showImportTypes: Bool { importTypes.count > 1 } var importTypes: [WalletImportType] { switch type { case .multicoin: return [.phrase] - case .chain: - if chain?.keyEncodingTypes.isEmpty ?? true { - return [.phrase, .address] + case .chain(let chain): + if supportsPrivateKeyImport(chain: chain.rawValue) { + return [.phrase, .privateKey, .address] } - return [.phrase, .privateKey, .address] + return [.phrase, .address] } } diff --git a/Packages/FeatureServices/WalletService/WalletService.swift b/Packages/FeatureServices/WalletService/WalletService.swift index cf0a45b4e..0630f4655 100644 --- a/Packages/FeatureServices/WalletService/WalletService.swift +++ b/Packages/FeatureServices/WalletService/WalletService.swift @@ -145,7 +145,7 @@ public struct WalletService: Sendable { try await keystore.getMnemonic(wallet: wallet) } - public func getPrivateKey(wallet: Primitives.Wallet, chain: Chain, encoding: EncodingType) async throws -> String { - try await keystore.getPrivateKey(wallet: wallet, chain: chain, encoding: encoding) + public func getPrivateKeyEncoded(wallet: Primitives.Wallet, chain: Chain) async throws -> String { + try await keystore.getPrivateKeyEncoded(wallet: wallet, chain: chain) } } diff --git a/Packages/Keystore/Package.resolved b/Packages/Keystore/Package.resolved deleted file mode 100644 index 6d8bde1fb..000000000 --- a/Packages/Keystore/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "6a214e7c1717bde07d22f26ce45795884958bb64b342b7bbfe1e5253a517350f", - "pins" : [ - { - "identity" : "bigint", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gemwalletcom/BigInt.git", - "state" : { - "revision" : "e07e00fa1fd435143a2dcf8b7eec9a7710b2fdfe", - "version" : "5.7.0" - } - } - ], - "version" : 3 -} diff --git a/Packages/Keystore/Package.swift b/Packages/Keystore/Package.swift index 1d24b0945..3ade00414 100644 --- a/Packages/Keystore/Package.swift +++ b/Packages/Keystore/Package.swift @@ -17,7 +17,8 @@ let package = Package( .package(name: "Primitives", path: "../Primitives"), .package(name: "Formatters", path: "../Formatters"), .package(name: "WalletCore", path: "../WalletCore"), - .package(name: "Keychain", path: "../Keychain") + .package(name: "Keychain", path: "../Keychain"), + .package(name: "GemstonePrimitives", path: "../GemstonePrimitives"), ], targets: [ .target( @@ -27,7 +28,8 @@ let package = Package( .product(name: "WalletCorePrimitives", package: "WalletCore"), "Primitives", "Formatters", - "Keychain" + "Keychain", + "GemstonePrimitives", ], path: "Sources" ), diff --git a/Packages/Keystore/Sources/Extensions/Chain+Keystore.swift b/Packages/Keystore/Sources/Extensions/Chain+Keystore.swift deleted file mode 100644 index 41353863c..000000000 --- a/Packages/Keystore/Sources/Extensions/Chain+Keystore.swift +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c). Gem Wallet. All rights reserved. - -import Foundation -import Primitives -import WalletCore - -public extension Chain { - - var defaultKeyEncodingType: EncodingType { - keyEncodingTypes.first! - } - - var keyEncodingTypes: [EncodingType] { - //TODO: Use chain type in the future - switch self { - case .solana: - [.base58, .hex] - case .stellar: - [.base32, .hex] - default: - [.hex] - } - } -} diff --git a/Packages/Keystore/Sources/LocalKeystore.swift b/Packages/Keystore/Sources/LocalKeystore.swift index 621495f12..77a692e15 100644 --- a/Packages/Keystore/Sources/LocalKeystore.swift +++ b/Packages/Keystore/Sources/LocalKeystore.swift @@ -1,4 +1,5 @@ import Foundation +import GemstonePrimitives import Primitives import WalletCore @@ -112,16 +113,14 @@ public final class LocalKeystore: Keystore, @unchecked Sendable { } } - public func getPrivateKey(wallet: Primitives.Wallet, chain: Chain, encoding: EncodingType) async throws -> String { + public func getPrivateKeyEncoded(wallet: Primitives.Wallet, chain: Chain) async throws -> String { var data = try await getPrivateKey(wallet: wallet, chain: chain) defer { data.zeroize() } - switch encoding { - case .base58: + switch chain.type { + case .bitcoin, .solana: return Base58.encodeNoCheck(data: data) - case .hex: + default: return data.hexString.append0x - case .base32: - throw KeystoreError.invalidPrivateKeyEncoding } } diff --git a/Packages/Keystore/Sources/Protocols/Keystore.swift b/Packages/Keystore/Sources/Protocols/Keystore.swift index 8870c926b..a49355613 100644 --- a/Packages/Keystore/Sources/Protocols/Keystore.swift +++ b/Packages/Keystore/Sources/Protocols/Keystore.swift @@ -12,7 +12,7 @@ public protocol Keystore: Sendable { func setupChains(chains: [Chain], for wallets: [Wallet]) throws -> [Wallet] func deleteKey(for wallet: Wallet) async throws func getPrivateKey(wallet: Wallet, chain: Chain) async throws -> Data - func getPrivateKey(wallet: Wallet, chain: Chain, encoding: EncodingType) async throws -> String + func getPrivateKeyEncoded(wallet: Wallet, chain: Chain) async throws -> String func getMnemonic(wallet: Wallet) async throws -> [String] func getPasswordAuthentication() throws -> KeystoreAuthentication func sign(hash: Data, wallet: Wallet, chain: Chain) async throws -> Data diff --git a/Packages/Keystore/Sources/Store/WalletKeyStore.swift b/Packages/Keystore/Sources/Store/WalletKeyStore.swift index b69cbecb3..17e19b9dd 100644 --- a/Packages/Keystore/Sources/Store/WalletKeyStore.swift +++ b/Packages/Keystore/Sources/Store/WalletKeyStore.swift @@ -6,6 +6,7 @@ import Primitives internal import struct Formatters.MnemonicFormatter internal import WalletCorePrimitives +internal import func Gemstone.decodePrivateKey struct WalletKeyStore: Sendable { private let keyStore: WalletCore.KeyStore @@ -41,54 +42,11 @@ struct WalletKeyStore: Sendable { } static func decodeKey(_ key: String, chain: Chain) throws -> PrivateKey { - var data: Data? - for encoding in chain.keyEncodingTypes { - if data != nil { - break - } - switch encoding { - case .base58: - if let decoded = Base58.decodeNoCheck(string: key), decoded.count % 32 == 0 { - data = decoded.prefix(32) - } - case .hex: - data = Data(hexString: key) - case .base32: - if let decoded = try? decodeBase32Key(string: key, chain: chain) { - data = decoded - } - } - } - - guard - let data = data, - PrivateKey.isValid(data: data, curve: chain.coinType.curve) == true, - let key = PrivateKey(data: data) - else { - throw AnyError("Invalid private key format") - } - return key - } - - static func decodeBase32Key(string: String, chain: Chain) throws -> Data { - switch chain { - case .stellar: - // test against https://lab.stellar.org/account/create - guard - string.count == 56, - string.hasPrefix("S"), - let decoded = Base32.decode(string: string), - decoded.count == 35, - decoded[0] == 0x90 // Mainnet - else { - throw KeystoreError.invalidPrivateKeyEncoding - } - // 35-byte format: [1 version] + [32 payload] + [2 checksum/padding] - return Data(decoded[1 ..< 33]) - - default: - throw KeystoreError.invalidPrivateKeyEncoding + let bytes = try decodePrivateKey(chain: chain.rawValue, value: key) + guard let privateKey = PrivateKey(data: bytes) else { + throw AnyError("Invalid private key") } + return privateKey } func importPrivateKey(id: WalletIdentifier, name: String, key: String, chain: Chain, password: String, source: WalletSource) throws -> Primitives.Wallet { diff --git a/Packages/Keystore/TestKit/KeystoreMock.swift b/Packages/Keystore/TestKit/KeystoreMock.swift index 90a733d26..5a5c4798d 100644 --- a/Packages/Keystore/TestKit/KeystoreMock.swift +++ b/Packages/Keystore/TestKit/KeystoreMock.swift @@ -12,7 +12,7 @@ public struct KeystoreMock: Keystore { public func setupChains(chains: [Primitives.Chain], for wallets: [Primitives.Wallet]) throws -> [Wallet] { [.mock()] } public func deleteKey(for wallet: Primitives.Wallet) throws {} public func getPrivateKey(wallet: Primitives.Wallet, chain: Primitives.Chain) throws -> Data { Data() } - public func getPrivateKey(wallet: Primitives.Wallet, chain: Primitives.Chain, encoding: Primitives.EncodingType) throws -> String { .empty } + public func getPrivateKeyEncoded(wallet: Primitives.Wallet, chain: Primitives.Chain) throws -> String { .empty } public func getMnemonic(wallet: Primitives.Wallet) throws -> [String] { LocalKeystore.words } public func getPasswordAuthentication() throws -> KeystoreAuthentication { .none } public func sign(hash: Data, wallet: Primitives.Wallet, chain: Primitives.Chain) throws -> Data { Data() } diff --git a/Packages/Keystore/Tests/Chain+KeystoreTests.swift b/Packages/Keystore/Tests/Chain+KeystoreTests.swift deleted file mode 100644 index 5980ad6fa..000000000 --- a/Packages/Keystore/Tests/Chain+KeystoreTests.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c). Gem Wallet. All rights reserved. - -import Foundation -import Testing -import Primitives -import PrimitivesTestKit - -final class Chain_KeystoreTests { - @Test - func testHasEncodingTypes() { - for chain in Chain.allCases { - #expect(!chain.keyEncodingTypes.isEmpty) - } - } -} diff --git a/Packages/Keystore/Tests/LocalKeystoreTests.swift b/Packages/Keystore/Tests/LocalKeystoreTests.swift index abadec5b2..3ca4cdd91 100644 --- a/Packages/Keystore/Tests/LocalKeystoreTests.swift +++ b/Packages/Keystore/Tests/LocalKeystoreTests.swift @@ -83,22 +83,19 @@ struct LocalKeystoreTests { source: .import ) - let exportedHex = try await keystore.getPrivateKey(wallet: wallet, chain: .solana, encoding: .hex) - let exportedBase58 = try await keystore.getPrivateKey(wallet: wallet, chain: .solana, encoding: .base58) - - #expect(exportedHex == hex) - #expect(exportedBase58 == "DTJi5pMtSKZHdkLX4wxwvjGjf2xwXx1LSuuUZhugYWDV") + let exported = try await keystore.getPrivateKeyEncoded(wallet: wallet, chain: .solana) + #expect(exported == "DTJi5pMtSKZHdkLX4wxwvjGjf2xwXx1LSuuUZhugYWDV") let keystore2 = LocalKeystore.mock() let wallet2 = try await keystore2.importWallet( name: "Test Solana 2", - type: .privateKey(text: exportedBase58, chain: .solana), + type: .privateKey(text: exported, chain: .solana), isWalletsEmpty: true, source: .import ) let exportedKey = try await keystore2.getPrivateKey(wallet: wallet2, chain: .solana) - #expect(Base58.encodeNoCheck(data: exportedKey) == exportedBase58) + #expect(Base58.encodeNoCheck(data: exportedKey) == exported) } } @@ -120,17 +117,6 @@ struct LocalKeystoreTests { #expect(encoded == "5ZRaXVuDePowJjZmKaMjfcuqBVZet6e8QiCjTkGXBn7xhCvoEswUKXiGs2wmPxcqTfJUH28eCC91J1vLSjANNM9v") } - @Test - func importWIF() { - #expect(throws: Never.self) { - let wif = "L1NGZutRxaVotZSfRzGnFYUj42LjEL66ZdAeSDA8CbyASZWizHLA" - let decoded = Base58.decode(string: wif)! - #expect(decoded.count == 34) - - let key = decoded[1 ... 32] - #expect(PrivateKey.isValid(data: key, curve: .secp256k1)) - } - } @Test func deriveAddress() async { diff --git a/Packages/Keystore/Tests/WalletKeyStoreTests.swift b/Packages/Keystore/Tests/WalletKeyStoreTests.swift index 86c0d5a73..68f3c6131 100644 --- a/Packages/Keystore/Tests/WalletKeyStoreTests.swift +++ b/Packages/Keystore/Tests/WalletKeyStoreTests.swift @@ -81,6 +81,7 @@ final class WalletKeyStoreTests { #expect(address == "GADB4BDKTOE36L6QN2JLIPNNJ7EZPSY5BIVKWXLWYZLIPXNQWIRQQZKT") } + @Test func addImportWallet() async throws { let store = WalletKeyStore.mock() let newWallet = try store.importWallet( diff --git a/Packages/Primitives/Sources/EncodingType.swift b/Packages/Primitives/Sources/EncodingType.swift deleted file mode 100644 index adb96cbd8..000000000 --- a/Packages/Primitives/Sources/EncodingType.swift +++ /dev/null @@ -1,11 +0,0 @@ -/* - Generated by typeshare 1.13.3 - */ - -import Foundation - -public enum EncodingType: String, Codable, CaseIterable, Equatable, Sendable { - case hex = "Hex" - case base58 = "Base58" - case base32 = "Base32" -} diff --git a/core b/core index 4734eee8a..e6cf373f2 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 4734eee8a4cffc38397242c0ec91a34f72a49b44 +Subproject commit e6cf373f23d3a9875bbaafa860a7b234e51b82d3