Skip to content

Bip84 #310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 23, 2025
Merged

Bip84 #310

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
4 changes: 4 additions & 0 deletions BDKSwiftExampleWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
AE91CEED2C0FDB70000AAD20 /* SentAndReceivedValues+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE91CEEC2C0FDB70000AAD20 /* SentAndReceivedValues+Extensions.swift */; };
AE91CEEF2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE91CEEE2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift */; };
AE96F6622A424C400055623C /* BDKSwiftExampleWalletReceiveViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE96F6612A424C400055623C /* BDKSwiftExampleWalletReceiveViewModelTests.swift */; };
AE97E74D2E315A8F000A407D /* AddressType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE97E74C2E315A8F000A407D /* AddressType+Extensions.swift */; };
AEA0A6272E297203008A525B /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = AEA0A6262E297203008A525B /* BitcoinDevKit */; };
AEAB03112ABDDB86000C9528 /* FeeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAB03102ABDDB86000C9528 /* FeeViewModel.swift */; };
AEAB03132ABDDBF4000C9528 /* AmountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAB03122ABDDBF4000C9528 /* AmountViewModel.swift */; };
Expand Down Expand Up @@ -187,6 +188,7 @@
AE91CEEC2C0FDB70000AAD20 /* SentAndReceivedValues+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentAndReceivedValues+Extensions.swift"; sourceTree = "<group>"; };
AE91CEEE2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CanonicalTx+Extensions.swift"; sourceTree = "<group>"; };
AE96F6612A424C400055623C /* BDKSwiftExampleWalletReceiveViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BDKSwiftExampleWalletReceiveViewModelTests.swift; sourceTree = "<group>"; };
AE97E74C2E315A8F000A407D /* AddressType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressType+Extensions.swift"; sourceTree = "<group>"; };
AEAB03102ABDDB86000C9528 /* FeeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeViewModel.swift; sourceTree = "<group>"; };
AEAB03122ABDDBF4000C9528 /* AmountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountViewModel.swift; sourceTree = "<group>"; };
AEB130C82A44E4850087785B /* TransactionDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -567,6 +569,7 @@
isa = PBXGroup;
children = (
77EDA65A2E2A5B3800A5E3AD /* URL+Extensions.swift */,
AE97E74C2E315A8F000A407D /* AddressType+Extensions.swift */,
77F0FDC82DA9A93700B30E4F /* Persister+Extensions.swift */,
AEE6C74B2ABCB3E200442ADD /* Transaction+Extensions.swift */,
AE83EFDA2C9D07B200B41244 /* ChainPosition+Extensions.swift */,
Expand Down Expand Up @@ -762,6 +765,7 @@
AE783A012AB4E5E1005F0CBA /* BuildTransactionView.swift in Sources */,
AE6F34DA2AA6C1E00087E700 /* Balance+Extensions.swift in Sources */,
AED4CC0C2A1D3A9400CE1831 /* OnboardingView.swift in Sources */,
AE97E74D2E315A8F000A407D /* AddressType+Extensions.swift in Sources */,
77F0FDC92DA9A93D00B30E4F /* Persister+Extensions.swift in Sources */,
AE6716012A9AC089005C193F /* KeyServiceError.swift in Sources */,
77AD9F062DBB031D00182E65 /* ActivityHomeHeaderView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// AddressType+Extensions.swift
// BDKSwiftExampleWallet
//
// Created by Matthew Ramsden on 7/23/25.
//

import Foundation

enum AddressType: String, CaseIterable {
case bip86 = "bip86"
case bip84 = "bip84"

var description: String {
switch self {
case .bip86: return "bip86"
case .bip84: return "bip84"
}
}

var displayName: String {
switch self {
case .bip86: return "BIP86 (Taproot)"
case .bip84: return "BIP84 (SegWit)"
}
}

init?(stringValue: String) {
switch stringValue {
case "bip86": self = .bip86
case "bip84": self = .bip84
default: return nil
}
}
}
9 changes: 9 additions & 0 deletions BDKSwiftExampleWallet/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@
}
}
}
},
"Address Type" : {

},
"Amount Error" : {
"localizations" : {
Expand Down Expand Up @@ -1003,6 +1006,9 @@
}
}
}
},
"Select Address Type" : {

},
"Select Bitcoin Network" : {
"localizations" : {
Expand Down Expand Up @@ -1313,6 +1319,9 @@
}
}
}
},
"Unknown" : {

},
"Unspent" : {
"localizations" : {
Expand Down
122 changes: 106 additions & 16 deletions BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,83 @@ private class BDKService {
self.esploraClient = EsploraClient(url: self.esploraURL)
}

private func getCurrentAddressType() -> AddressType {
let storedAddressTypeString =
try? keyClient.getAddressType() ?? AddressType.bip86.description
return AddressType(stringValue: storedAddressTypeString ?? "") ?? .bip86
}

private func createDescriptors(
for addressType: AddressType,
secretKey: DescriptorSecretKey,
network: Network
) -> (descriptor: Descriptor, changeDescriptor: Descriptor) {
switch addressType {
case .bip86:
let descriptor = Descriptor.newBip86(
secretKey: secretKey,
keychainKind: .external,
network: network
)
let changeDescriptor = Descriptor.newBip86(
secretKey: secretKey,
keychainKind: .internal,
network: network
)
return (descriptor, changeDescriptor)
case .bip84:
let descriptor = Descriptor.newBip84(
secretKey: secretKey,
keychainKind: .external,
network: network
)
let changeDescriptor = Descriptor.newBip84(
secretKey: secretKey,
keychainKind: .internal,
network: network
)
return (descriptor, changeDescriptor)
}
}

private func createPublicDescriptors(
for addressType: AddressType,
publicKey: DescriptorPublicKey,
fingerprint: String,
network: Network
) -> (descriptor: Descriptor, changeDescriptor: Descriptor) {
switch addressType {
case .bip86:
let descriptor = Descriptor.newBip86Public(
publicKey: publicKey,
fingerprint: fingerprint,
keychainKind: .external,
network: network
)
let changeDescriptor = Descriptor.newBip86Public(
publicKey: publicKey,
fingerprint: fingerprint,
keychainKind: .internal,
network: network
)
return (descriptor, changeDescriptor)
case .bip84:
let descriptor = Descriptor.newBip84Public(
publicKey: publicKey,
fingerprint: fingerprint,
keychainKind: .external,
network: network
)
let changeDescriptor = Descriptor.newBip84Public(
publicKey: publicKey,
fingerprint: fingerprint,
keychainKind: .internal,
network: network
)
return (descriptor, changeDescriptor)
}
}

func getAddress() throws -> String {
guard let wallet = self.wallet else {
throw WalletError.walletNotFound
Expand Down Expand Up @@ -117,16 +194,14 @@ private class BDKService {
mnemonic: mnemonic,
password: nil
)
let descriptor = Descriptor.newBip86(
let currentAddressType = getCurrentAddressType()
let descriptors = createDescriptors(
for: currentAddressType,
secretKey: secretKey,
keychainKind: .external,
network: network
)
let changeDescriptor = Descriptor.newBip86(
secretKey: secretKey,
keychainKind: .internal,
network: network
)
let descriptor = descriptors.descriptor
let changeDescriptor = descriptors.changeDescriptor
let backupInfo = BackupInfo(
mnemonic: mnemonic.description,
descriptor: descriptor.toStringWithSecret(),
Expand Down Expand Up @@ -219,18 +294,15 @@ private class BDKService {

let descriptorPublicKey = try DescriptorPublicKey.fromString(publicKey: xpubString)
let fingerprint = descriptorPublicKey.masterFingerprint()
let descriptor = Descriptor.newBip86Public(
publicKey: descriptorPublicKey,
fingerprint: fingerprint,
keychainKind: .external,
network: network
)
let changeDescriptor = Descriptor.newBip86Public(
let currentAddressType = getCurrentAddressType()
let descriptors = createPublicDescriptors(
for: currentAddressType,
publicKey: descriptorPublicKey,
fingerprint: fingerprint,
keychainKind: .internal,
network: network
)
let descriptor = descriptors.descriptor
let changeDescriptor = descriptors.changeDescriptor

let backupInfo = BackupInfo(
mnemonic: "",
Expand Down Expand Up @@ -454,6 +526,14 @@ extension BDKService {
func setNeedsFullScan(_ value: Bool) {
needsFullScan = value
}

func getAddressType() -> AddressType {
return getCurrentAddressType()
}

func updateAddressType(_ newAddressType: AddressType) {
try? keyClient.saveAddressType(newAddressType.description)
}
}

struct BDKClient {
Expand Down Expand Up @@ -481,6 +561,8 @@ struct BDKClient {
let getEsploraURL: () -> String
let updateNetwork: (Network) -> Void
let updateEsploraURL: (String) -> Void
let getAddressType: () -> AddressType
let updateAddressType: (AddressType) -> Void
}

extension BDKClient {
Expand Down Expand Up @@ -534,6 +616,12 @@ extension BDKClient {
},
updateEsploraURL: { newURL in
BDKService.shared.updateEsploraURL(newURL)
},
getAddressType: {
BDKService.shared.getAddressType()
},
updateAddressType: { newAddressType in
BDKService.shared.updateAddressType(newAddressType)
}
)
}
Expand Down Expand Up @@ -591,7 +679,9 @@ extension BDKClient {
getNetwork: { .signet },
getEsploraURL: { Constants.Config.EsploraServerURLNetwork.Signet.mutiny },
updateNetwork: { _ in },
updateEsploraURL: { _ in }
updateEsploraURL: { _ in },
getAddressType: { .bip86 },
updateAddressType: { _ in }
)
}
#endif
25 changes: 22 additions & 3 deletions BDKSwiftExampleWallet/Service/Key Service/KeyService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ private struct KeyService {
func saveNetwork(network: String) throws {
keychain[string: "SelectedNetwork"] = network
}

func getAddressType() throws -> String? {
return keychain[string: "SelectedAddressType"]
}

func saveAddressType(addressType: String) throws {
keychain[string: "SelectedAddressType"] = addressType
}
}

struct KeyClient {
Expand All @@ -71,9 +79,11 @@ struct KeyClient {
let getBackupInfo: () throws -> BackupInfo
let getEsploraURL: () throws -> String?
let getNetwork: () throws -> String?
let getAddressType: () throws -> String?
let saveEsploraURL: (String) throws -> Void
let saveBackupInfo: (BackupInfo) throws -> Void
let saveNetwork: (String) throws -> Void
let saveAddressType: (String) throws -> Void

private init(
deleteBackupInfo: @escaping () throws -> Void,
Expand All @@ -82,19 +92,23 @@ struct KeyClient {
getBackupInfo: @escaping () throws -> BackupInfo,
getEsploraURL: @escaping () throws -> String?,
getNetwork: @escaping () throws -> String?,
getAddressType: @escaping () throws -> String?,
saveBackupInfo: @escaping (BackupInfo) throws -> Void,
saveEsploraURL: @escaping (String) throws -> Void,
saveNetwork: @escaping (String) throws -> Void
saveNetwork: @escaping (String) throws -> Void,
saveAddressType: @escaping (String) throws -> Void
) {
self.deleteBackupInfo = deleteBackupInfo
self.deleteEsplora = deleteEsplora
self.deleteNetwork = deleteNetwork
self.getBackupInfo = getBackupInfo
self.getEsploraURL = getEsploraURL
self.getNetwork = getNetwork
self.getAddressType = getAddressType
self.saveBackupInfo = saveBackupInfo
self.saveEsploraURL = saveEsploraURL
self.saveNetwork = saveNetwork
self.saveAddressType = saveAddressType
}
}

Expand All @@ -106,9 +120,12 @@ extension KeyClient {
getBackupInfo: { try KeyService().getBackupInfo() },
getEsploraURL: { try KeyService().getEsploraURL() },
getNetwork: { try KeyService().getNetwork() },
getAddressType: { try KeyService().getAddressType() },
saveBackupInfo: { backupInfo in try KeyService().saveBackupInfo(backupInfo: backupInfo) },
saveEsploraURL: { url in try KeyService().saveEsploraURL(url: url) },
saveNetwork: { network in try KeyService().saveNetwork(network: network) }
saveNetwork: { network in try KeyService().saveNetwork(network: network) },
saveAddressType: { addressType in try KeyService().saveAddressType(addressType: addressType)
}
)
}

Expand Down Expand Up @@ -146,9 +163,11 @@ extension KeyClient {
},
getEsploraURL: { nil },
getNetwork: { nil },
getAddressType: { nil },
saveBackupInfo: { _ in },
saveEsploraURL: { _ in },
saveNetwork: { _ in }
saveNetwork: { _ in },
saveAddressType: { _ in }
)
}
#endif
6 changes: 6 additions & 0 deletions BDKSwiftExampleWallet/View Model/OnboardingViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class OnboardingViewModel: ObservableObject {
bdkClient.updateEsploraURL(selectedURL)
}
}
@Published var selectedAddressType: AddressType = .bip86 {
didSet {
bdkClient.updateAddressType(selectedAddressType)
}
}
@Published var words: String = ""
var wordArray: [String] {
if words.hasPrefix("xpub") || words.hasPrefix("tpub") || words.hasPrefix("vpub") {
Expand Down Expand Up @@ -81,6 +86,7 @@ class OnboardingViewModel: ObservableObject {
self.bdkClient = bdkClient
self.selectedNetwork = bdkClient.getNetwork()
self.selectedURL = bdkClient.getEsploraURL()
self.selectedAddressType = bdkClient.getAddressType()
}

func createWallet() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SettingsViewModel: ObservableObject {
@Published var esploraURL: String?
@Published var inspectedScripts: UInt64 = 0
@Published var network: String?
@Published var addressType: AddressType?
@Published var settingsError: AppError?
@Published var showingSettingsViewErrorAlert = false
@Published var walletSyncState: WalletSyncState = .notStarted
Expand All @@ -37,6 +38,10 @@ class SettingsViewModel: ObservableObject {
self.esploraURL = bdkClient.getEsploraURL()
}

func getAddressType() {
self.addressType = bdkClient.getAddressType()
}

func delete() {
do {
try bdkClient.deleteWallet()
Expand Down
Loading