Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]
}
}

Expand Down
4 changes: 2 additions & 2 deletions Packages/FeatureServices/WalletService/WalletService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
15 changes: 0 additions & 15 deletions Packages/Keystore/Package.resolved

This file was deleted.

6 changes: 4 additions & 2 deletions Packages/Keystore/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -27,7 +28,8 @@ let package = Package(
.product(name: "WalletCorePrimitives", package: "WalletCore"),
"Primitives",
"Formatters",
"Keychain"
"Keychain",
"GemstonePrimitives",
],
path: "Sources"
),
Expand Down
24 changes: 0 additions & 24 deletions Packages/Keystore/Sources/Extensions/Chain+Keystore.swift

This file was deleted.

11 changes: 5 additions & 6 deletions Packages/Keystore/Sources/LocalKeystore.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import GemstonePrimitives
import Primitives
import WalletCore

Expand Down Expand Up @@ -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
}
}

Expand Down
2 changes: 1 addition & 1 deletion Packages/Keystore/Sources/Protocols/Keystore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 5 additions & 47 deletions Packages/Keystore/Sources/Store/WalletKeyStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Packages/Keystore/TestKit/KeystoreMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
Expand Down
15 changes: 0 additions & 15 deletions Packages/Keystore/Tests/Chain+KeystoreTests.swift

This file was deleted.

22 changes: 4 additions & 18 deletions Packages/Keystore/Tests/LocalKeystoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions Packages/Keystore/Tests/WalletKeyStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ final class WalletKeyStoreTests {
#expect(address == "GADB4BDKTOE36L6QN2JLIPNNJ7EZPSY5BIVKWXLWYZLIPXNQWIRQQZKT")
}


@Test func addImportWallet() async throws {
let store = WalletKeyStore.mock()
let newWallet = try store.importWallet(
Expand Down
11 changes: 0 additions & 11 deletions Packages/Primitives/Sources/EncodingType.swift

This file was deleted.