diff --git a/Adamant/Models/DashTransaction.swift b/Adamant/Models/DashTransaction.swift
index 38d613954..1f9a5d6b1 100644
--- a/Adamant/Models/DashTransaction.swift
+++ b/Adamant/Models/DashTransaction.swift
@@ -50,8 +50,7 @@ struct DashUnspentTransaction: Decodable {
case height
}
- func asUnspentTransaction(with publicKeyHash: Data) -> UnspentTransaction {
- let lockScript = Script.buildPublicKeyHashOut(pubKeyHash: publicKeyHash)
+ func asUnspentTransaction(lockScript: Data) -> UnspentTransaction {
let txHash = Data(hex: txid).map { Data($0.reversed()) } ?? Data()
let unspentOutput = TransactionOutput(value: amount, lockingScript: lockScript)
diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift
index ce402170a..169b1b00b 100644
--- a/Adamant/SwinjectDependencies.swift
+++ b/Adamant/SwinjectDependencies.swift
@@ -7,6 +7,7 @@
//
import Swinject
+import BitcoinKit
// MARK: - Services
extension Container {
@@ -233,5 +234,10 @@ extension Container {
accountService: r.resolve(AccountService.self)!
)
}.inObjectScope(.container)
+
+ // MARK: Bitcoin AddressConverterFactory
+ self.register(AddressConverterFactory.self) { r in
+ AddressConverterFactory()
+ }.inObjectScope(.container)
}
}
diff --git a/Adamant/Wallets/Bitcoin/BtcWallet.swift b/Adamant/Wallets/Bitcoin/BtcWallet.swift
index 17674874f..09314e21d 100644
--- a/Adamant/Wallets/Bitcoin/BtcWallet.swift
+++ b/Adamant/Wallets/Bitcoin/BtcWallet.swift
@@ -9,8 +9,8 @@
import Foundation
import BitcoinKit
-class BtcWallet: WalletAccount {
- let address: String
+final class BtcWallet: WalletAccount {
+ let addressEntity: Address
let privateKey: PrivateKey
let publicKey: PublicKey
var balance: Decimal = 0.0
@@ -19,10 +19,11 @@ class BtcWallet: WalletAccount {
var minAmount: Decimal = 546e-8
var isBalanceInitialized: Bool = false
- init(privateKey: PrivateKey) {
+ var address: String { addressEntity.stringValue }
+
+ init(privateKey: PrivateKey, addressConverter: AddressConverter) throws {
self.privateKey = privateKey
self.publicKey = privateKey.publicKey()
- self.address = publicKey.toCashaddr().base58
+ self.addressEntity = try addressConverter.convert(publicKey: publicKey, type: .p2pkh)
}
-
}
diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift b/Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift
index 5d140121a..07d89f33c 100644
--- a/Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift
+++ b/Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift
@@ -29,10 +29,9 @@ extension BtcWalletService: WalletServiceTwoStepSend {
throw WalletServiceError.notLogged
}
- let changeAddress = wallet.publicKey.toCashaddr()
let key = wallet.privateKey
- guard let toAddress = try? LegacyAddress(recipient, for: self.network) else {
+ guard let toAddress = try? addressConverter.convert(address: recipient) else {
throw WalletServiceError.accountNotFound
}
@@ -56,7 +55,7 @@ extension BtcWalletService: WalletServiceTwoStepSend {
toAddress: toAddress,
amount: rawAmount,
fee: fee,
- changeAddress: changeAddress,
+ changeAddress: wallet.addressEntity,
utxos: utxos,
keys: [key]
)
@@ -133,7 +132,7 @@ extension BtcWalletService: WalletServiceTwoStepSend {
let value = NSDecimalNumber(decimal: item.value).uint64Value
- let lockScript = wallet.publicKey.toCashaddr().lockingScript
+ let lockScript = wallet.addressEntity.lockingScript
let txHash = Data(hex: item.txId).map { Data($0.reversed()) } ?? Data()
let txIndex = item.vout
diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Wallets/Bitcoin/BtcWalletService.swift
index a3a512671..6ae76e72c 100644
--- a/Adamant/Wallets/Bitcoin/BtcWalletService.swift
+++ b/Adamant/Wallets/Bitcoin/BtcWalletService.swift
@@ -111,6 +111,7 @@ class BtcWalletService: WalletService {
var dialogService: DialogService!
var router: Router!
var increaseFeeService: IncreaseFeeService!
+ var addressConverter: AddressConverter!
// MARK: - Constants
static var currencyLogo = #imageLiteral(resourceName: "bitcoin_wallet")
@@ -385,7 +386,7 @@ extension BtcWalletService: InitiatedWithPassphraseService {
let privateKeyData = passphrase.data(using: .utf8)!.sha256()
let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true)
- let eWallet = BtcWallet(privateKey: privateKey)
+ let eWallet = try BtcWallet(privateKey: privateKey, addressConverter: addressConverter)
self.btcWallet = eWallet
if !self.enabled {
@@ -444,6 +445,7 @@ extension BtcWalletService: SwinjectDependentService {
dialogService = container.resolve(DialogService.self)
router = container.resolve(Router.self)
increaseFeeService = container.resolve(IncreaseFeeService.self)
+ addressConverter = container.resolve(AddressConverterFactory.self)?.make(network: network)
}
}
diff --git a/Adamant/Wallets/Dash/DashWallet.swift b/Adamant/Wallets/Dash/DashWallet.swift
index 64d892bf4..df50db49a 100644
--- a/Adamant/Wallets/Dash/DashWallet.swift
+++ b/Adamant/Wallets/Dash/DashWallet.swift
@@ -10,7 +10,7 @@ import Foundation
import BitcoinKit
class DashWallet: WalletAccount {
- let address: String
+ let addressEntity: Address
let privateKey: PrivateKey
let publicKey: PublicKey
var balance: Decimal = 0.0
@@ -19,9 +19,15 @@ class DashWallet: WalletAccount {
var minAmount: Decimal = 0.00002
var isBalanceInitialized: Bool = false
- init(privateKey: PrivateKey) {
+ var address: String { addressEntity.stringValue }
+
+ init(privateKey: PrivateKey, addressConverter: AddressConverter) throws {
self.privateKey = privateKey
self.publicKey = privateKey.publicKey()
- self.address = publicKey.toCashaddr().base58
+
+ self.addressEntity = try addressConverter.convert(
+ publicKey: publicKey,
+ type: .p2pkh
+ )
}
}
diff --git a/Adamant/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Wallets/Dash/DashWalletService+Send.swift
index 2b899495f..dbbc91830 100644
--- a/Adamant/Wallets/Dash/DashWalletService+Send.swift
+++ b/Adamant/Wallets/Dash/DashWalletService+Send.swift
@@ -45,10 +45,9 @@ extension DashWalletService: WalletServiceTwoStepSend {
throw WalletServiceError.notLogged
}
- let changeAddress = wallet.publicKey.toCashaddr()
let key = wallet.privateKey
- guard let toAddress = try? LegacyAddress(recipient, for: self.network) else {
+ guard let toAddress = try? addressConverter.convert(address: recipient) else {
throw WalletServiceError.accountNotFound
}
@@ -66,7 +65,14 @@ extension DashWalletService: WalletServiceTwoStepSend {
}
// MARK: 4. Create local transaction
- let transaction = BitcoinKit.Transaction.createNewTransaction(toAddress: toAddress, amount: rawAmount, fee: fee, changeAddress: changeAddress, utxos: utxos, keys: [key])
+ let transaction = BitcoinKit.Transaction.createNewTransaction(
+ toAddress: toAddress,
+ amount: rawAmount,
+ fee: fee,
+ changeAddress: wallet.addressEntity,
+ utxos: utxos,
+ keys: [key]
+ )
return transaction
}
diff --git a/Adamant/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Wallets/Dash/DashWalletService+Transactions.swift
index 071643ea3..302ceac25 100644
--- a/Adamant/Wallets/Dash/DashWalletService+Transactions.swift
+++ b/Adamant/Wallets/Dash/DashWalletService+Transactions.swift
@@ -169,8 +169,9 @@ extension DashWalletService {
)
if let result = response.result {
- let transactions = result.map { $0.asUnspentTransaction(with: wallet.publicKey.toCashaddr().data) }
- return transactions
+ return result.map {
+ $0.asUnspentTransaction(lockScript: wallet.addressEntity.lockingScript)
+ }
} else if let error = response.error?.message {
throw WalletServiceError.internalError(message: error, error: nil)
}
diff --git a/Adamant/Wallets/Dash/DashWalletService.swift b/Adamant/Wallets/Dash/DashWalletService.swift
index 0d4bbacf5..6b8ed93a4 100644
--- a/Adamant/Wallets/Dash/DashWalletService.swift
+++ b/Adamant/Wallets/Dash/DashWalletService.swift
@@ -12,7 +12,7 @@ import Alamofire
import BitcoinKit
import Combine
-class DashWalletService: WalletService {
+final class DashWalletService: WalletService {
var tokenSymbol: String {
return type(of: self).currencySymbol
@@ -62,6 +62,7 @@ class DashWalletService: WalletService {
var securedStore: SecuredStore!
var dialogService: DialogService!
var router: Router!
+ var addressConverter: AddressConverter!
// MARK: - Constants
static var currencyLogo = #imageLiteral(resourceName: "dash_wallet")
@@ -239,7 +240,9 @@ class DashWalletService: WalletService {
}
func validate(address: String) -> AddressValidationResult {
- return AddressFactory.isValid(bitcoinAddress: address) ? .valid : .invalid
+ (try? addressConverter.convert(address: address)) != nil
+ ? .valid
+ : .invalid
}
}
@@ -266,7 +269,11 @@ extension DashWalletService: InitiatedWithPassphraseService {
let privateKeyData = passphrase.data(using: .utf8)!.sha256()
let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true)
- let eWallet = DashWallet(privateKey: privateKey)
+ let eWallet = try DashWallet(
+ privateKey: privateKey,
+ addressConverter: addressConverter
+ )
+
self.dashWallet = eWallet
if !self.enabled {
@@ -322,6 +329,8 @@ extension DashWalletService: SwinjectDependentService {
securedStore = container.resolve(SecuredStore.self)
dialogService = container.resolve(DialogService.self)
router = container.resolve(Router.self)
+ addressConverter = container.resolve(AddressConverterFactory.self)?
+ .make(network: network)
}
}
diff --git a/Adamant/Wallets/Doge/DogeWallet.swift b/Adamant/Wallets/Doge/DogeWallet.swift
index 329c45e7b..20451d1f5 100644
--- a/Adamant/Wallets/Doge/DogeWallet.swift
+++ b/Adamant/Wallets/Doge/DogeWallet.swift
@@ -9,8 +9,8 @@
import Foundation
import BitcoinKit
-class DogeWallet: WalletAccount {
- let address: String
+final class DogeWallet: WalletAccount {
+ let addressEntity: Address
let privateKey: PrivateKey
let publicKey: PublicKey
var balance: Decimal = 0.0
@@ -19,17 +19,24 @@ class DogeWallet: WalletAccount {
var minAmount: Decimal = 0
var isBalanceInitialized: Bool = false
- init(privateKey: PrivateKey) {
+ var address: String { addressEntity.stringValue }
+
+ init(privateKey: PrivateKey, addressConverter: AddressConverter) throws {
self.privateKey = privateKey
self.publicKey = privateKey.publicKey()
- self.address = publicKey.toCashaddr().base58
+ self.addressEntity = try addressConverter.convert(publicKey: publicKey, type: .p2pkh)
}
- init(address: String, privateKey: PrivateKey, balance: Decimal, notifications: Int) {
+ init(
+ privateKey: PrivateKey,
+ balance: Decimal,
+ notifications: Int,
+ addressConverter: AddressConverter
+ ) throws {
self.privateKey = privateKey
self.balance = balance
self.notifications = notifications
self.publicKey = privateKey.publicKey()
- self.address = publicKey.toCashaddr().base58
+ self.addressEntity = try addressConverter.convert(publicKey: publicKey, type: .p2pkh)
}
}
diff --git a/Adamant/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Wallets/Doge/DogeWalletService+Send.swift
index 737ff1351..f2247c483 100644
--- a/Adamant/Wallets/Doge/DogeWalletService+Send.swift
+++ b/Adamant/Wallets/Doge/DogeWalletService+Send.swift
@@ -35,10 +35,9 @@ extension DogeWalletService: WalletServiceTwoStepSend {
throw WalletServiceError.notLogged
}
- let changeAddress = wallet.publicKey.toCashaddr()
let key = wallet.privateKey
- guard let toAddress = try? LegacyAddress(recipient, for: self.network) else {
+ guard let toAddress = try? addressConverter.convert(address: recipient) else {
throw WalletServiceError.accountNotFound
}
@@ -56,7 +55,14 @@ extension DogeWalletService: WalletServiceTwoStepSend {
}
// Create local transaction
- let transaction = BitcoinKit.Transaction.createNewTransaction(toAddress: toAddress, amount: rawAmount, fee: fee, changeAddress: changeAddress, utxos: utxos, keys: [key])
+ let transaction = BitcoinKit.Transaction.createNewTransaction(
+ toAddress: toAddress,
+ amount: rawAmount,
+ fee: fee,
+ changeAddress: wallet.addressEntity,
+ utxos: utxos,
+ keys: [key]
+ )
return transaction
} catch {
throw WalletServiceError.notEnoughMoney
diff --git a/Adamant/Wallets/Doge/DogeWalletService.swift b/Adamant/Wallets/Doge/DogeWalletService.swift
index f44becc1d..19fb434ce 100644
--- a/Adamant/Wallets/Doge/DogeWalletService.swift
+++ b/Adamant/Wallets/Doge/DogeWalletService.swift
@@ -58,6 +58,7 @@ class DogeWalletService: WalletService {
var accountService: AccountService!
var dialogService: DialogService!
var router: Router!
+ var addressConverter: AddressConverter!
// MARK: - Constants
static var currencyLogo = #imageLiteral(resourceName: "doge_wallet")
@@ -225,7 +226,9 @@ class DogeWalletService: WalletService {
}
func validate(address: String) -> AddressValidationResult {
- return AddressFactory.isValid(bitcoinAddress: address) ? .valid : .invalid
+ (try? addressConverter.convert(address: address)) != nil
+ ? .valid
+ : .invalid
}
}
@@ -251,7 +254,7 @@ extension DogeWalletService: InitiatedWithPassphraseService {
let privateKeyData = passphrase.data(using: .utf8)!.sha256()
let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true)
- let eWallet = DogeWallet(privateKey: privateKey)
+ let eWallet = try DogeWallet(privateKey: privateKey, addressConverter: addressConverter)
self.dogeWallet = eWallet
if !self.enabled {
@@ -307,6 +310,8 @@ extension DogeWalletService: SwinjectDependentService {
apiService = container.resolve(ApiService.self)
dialogService = container.resolve(DialogService.self)
router = container.resolve(Router.self)
+ addressConverter = container.resolve(AddressConverterFactory.self)?
+ .make(network: network)
}
}
@@ -529,8 +534,8 @@ extension DogeWalletService {
}
let value = NSDecimalNumber(decimal: (amount.decimalValue * DogeWalletService.multiplier)).uint64Value
-
- let lockScript = Script.buildPublicKeyHashOut(pubKeyHash: wallet.publicKey.toCashaddr().data)
+
+ let lockScript = wallet.addressEntity.lockingScript
let txHash = Data(hex: txid).map { Data($0.reversed()) } ?? Data()
let txIndex = vout.uint32Value
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/BitcoinError.swift b/BitcoinKit/Sources/BitcoinKit/Core/BitcoinError.swift
new file mode 100644
index 000000000..c9d843a22
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/BitcoinError.swift
@@ -0,0 +1,33 @@
+//
+// BitcoinError.swift
+//
+//
+// Created by Andrey Golubenko on 06.06.2023.
+//
+
+import Foundation
+
+enum BitcoinError: LocalizedError {
+ case unknownAddressType
+ case invalidAddressLength
+ case invalidChecksum
+ case wrongAddressPrefix
+ case list(errors: [Error])
+
+ var errorDescription: String? {
+ switch self {
+ case .unknownAddressType:
+ return "Unknown address type"
+ case .invalidAddressLength:
+ return "Invalid address length"
+ case .invalidChecksum:
+ return "Invalid checksum"
+ case .wrongAddressPrefix:
+ return "Wrong address prefix"
+ case let .list(errors):
+ return errors
+ .map { $0.localizedDescription }
+ .joined(separator: ". ")
+ }
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Address.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Address.swift
deleted file mode 100644
index 7ec966c19..000000000
--- a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Address.swift
+++ /dev/null
@@ -1,268 +0,0 @@
-//
-// Address.swift
-//
-// Copyright © 2018 Kishikawa Katsumi
-// Copyright © 2018 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
-
-public protocol AddressProtocol {
- var network: Network { get }
- var type: AddressType { get }
- var data: Data { get }
- var publicKey: Data? { get }
-
- var base58: String { get }
- var cashaddr: String { get }
-}
-
-#if os(iOS) || os(tvOS) || os(watchOS)
-public typealias Address = AddressProtocol & QRCodeConvertible
-#else
-public typealias Address = AddressProtocol
-#endif
-
-public enum AddressError: Error {
- case invalid
- case invalidScheme
- case invalidVersionByte
-}
-
-public struct LegacyAddress: Address {
- public let network: Network
- public let type: AddressType
- public let data: Data
- public let base58: Base58Check
- public let cashaddr: String
- public let publicKey: Data?
-
- public typealias Base58Check = String
-
- public init(data: Data, type: AddressType, network: Network, base58: String, bech32: String, publicKey: Data?) {
- self.data = data
- self.type = type
- self.network = network
- self.base58 = base58
- self.cashaddr = bech32
- self.publicKey = publicKey
- }
-
- public init(_ base58: Base58Check) throws {
- guard let raw = Base58.decode(base58) else {
- throw AddressError.invalid
- }
- let checksum = raw.suffix(4)
- let pubKeyHash = raw.dropLast(4)
- let checksumConfirm = Crypto.sha256sha256(pubKeyHash).prefix(4)
- guard checksum == checksumConfirm else {
- throw AddressError.invalid
- }
-
- let network: Network
- let type: AddressType
- let addressPrefix = pubKeyHash[0]
- switch addressPrefix {
- case Network.mainnet.pubkeyhash:
- network = .mainnet
- type = .pubkeyHash
- case Network.testnet.pubkeyhash:
- network = .testnet
- type = .pubkeyHash
- case Network.mainnet.scripthash:
- network = .mainnet
- type = .scriptHash
- case Network.testnet.scripthash:
- network = .testnet
- type = .scriptHash
- default:
- throw AddressError.invalidVersionByte
- }
-
- self.network = network
- self.type = type
- self.publicKey = nil
- self.data = pubKeyHash.dropFirst()
- self.base58 = base58
-
- // cashaddr
- switch type {
- case .pubkeyHash, .scriptHash:
- let payload = Data([type.versionByte160]) + self.data
- self.cashaddr = Bech32.encode(payload, prefix: network.scheme)
- default:
- self.cashaddr = ""
- }
- }
-
- public init(_ base58: Base58Check, for network: Network) throws {
- guard let raw = Base58.decode(base58) else {
- throw AddressError.invalid
- }
- let checksum = raw.suffix(4)
- let pubKeyHash = raw.dropLast(4)
- let checksumConfirm = Crypto.sha256sha256(pubKeyHash).prefix(4)
- guard checksum == checksumConfirm else {
- throw AddressError.invalid
- }
-
- let type: AddressType
- let addressPrefix = pubKeyHash[0]
- switch addressPrefix {
- case network.pubkeyhash:
- type = .pubkeyHash
- case network.scripthash:
- type = .scriptHash
- default:
- throw AddressError.invalidVersionByte
- }
-
- self.network = network
- self.type = type
- self.publicKey = nil
- self.data = pubKeyHash.dropFirst()
- self.base58 = base58
-
- // cashaddr
- switch type {
- case .pubkeyHash, .scriptHash:
- let payload = Data([type.versionByte160]) + self.data
- self.cashaddr = Bech32.encode(payload, prefix: network.scheme)
- default:
- self.cashaddr = ""
- }
- }
-
- public init(data: Data, type: AddressType, network: Network) {
- let addressData: Data = [type.versionByte] + data
- self.data = data
- self.type = type
- self.network = network
- self.publicKey = nil
- self.base58 = publicKeyHashToAddress(addressData)
- self.cashaddr = Bech32.encode(addressData, prefix: network.scheme)
- }
-}
-
-extension LegacyAddress: Equatable {
- public static func == (lhs: LegacyAddress, rhs: LegacyAddress) -> Bool {
- return lhs.network == rhs.network && lhs.data == rhs.data && lhs.type == rhs.type
- }
-}
-
-extension LegacyAddress: CustomStringConvertible {
- public var description: String {
- return base58
- }
-}
-
-public struct Cashaddr: Address {
- public let network: Network
- public let type: AddressType
- public let data: Data
- public let base58: String
- public let cashaddr: CashaddrWithScheme
- public let publicKey: Data?
-
- public typealias CashaddrWithScheme = String
-
- public init(data: Data, type: AddressType, network: Network, base58: String, bech32: CashaddrWithScheme, publicKey: Data?) {
- self.data = data
- self.type = type
- self.network = network
- self.base58 = base58
- self.cashaddr = bech32
- self.publicKey = publicKey
- }
-
- public init(_ cashaddr: CashaddrWithScheme) throws {
- guard let decoded = Bech32.decode(cashaddr) else {
- throw AddressError.invalid
- }
- let (prefix, raw) = (decoded.prefix, decoded.data)
- self.cashaddr = cashaddr
- self.publicKey = nil
-
- switch prefix {
- case Network.mainnet.scheme:
- network = .mainnet
- case Network.testnet.scheme:
- network = .testnet
- default:
- throw AddressError.invalidScheme
- }
-
- let versionByte = raw[0]
- let hash = raw.dropFirst()
-
- guard hash.count == VersionByte.getSize(from: versionByte) else {
- throw AddressError.invalidVersionByte
- }
- self.data = hash
- guard let typeBits = VersionByte.TypeBits(rawValue: (versionByte & 0b01111000)) else {
- throw AddressError.invalidVersionByte
- }
-
- switch typeBits {
- case .pubkeyHash:
- type = .pubkeyHash
- base58 = publicKeyHashToAddress(Data([network.pubkeyhash]) + data)
- case .scriptHash:
- type = .scriptHash
- base58 = publicKeyHashToAddress(Data([network.scripthash]) + data)
- }
- }
- public init(data: Data, type: AddressType, network: Network) {
- let addressData: Data = [type.versionByte] + data
- self.data = data
- self.type = type
- self.network = network
- self.publicKey = nil
- self.base58 = publicKeyHashToAddress(addressData)
- self.cashaddr = Bech32.encode(addressData, prefix: network.scheme)
- }
-}
-
-extension Cashaddr: Equatable {
- public static func == (lhs: Cashaddr, rhs: Cashaddr) -> Bool {
- return lhs.network == rhs.network && lhs.data == rhs.data && lhs.type == rhs.type
- }
-}
-
-extension Cashaddr: CustomStringConvertible {
- public var description: String {
- return cashaddr
- }
-}
-
-extension AddressProtocol {
- public var lockingScript: Data {
- switch type {
- case .pubkeyHash:
- return Script.buildPublicKeyHashOut(pubKeyHash: data)
- case .scriptHash:
- return Script.buildScriptHashOut(scriptHash: data)
- default:
- assertionFailure("Unknown type")
- return .init()
- }
- }
-}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressFactory.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressFactory.swift
deleted file mode 100644
index 80c8741f9..000000000
--- a/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressFactory.swift
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// AddressFactory.swift
-//
-// Copyright © 2018 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
-
-public struct AddressFactory {
- public static func create(_ plainAddress: String) throws -> Address {
- do {
- return try Cashaddr(plainAddress)
- } catch AddressError.invalidVersionByte {
- throw AddressError.invalidVersionByte
- } catch AddressError.invalidScheme {
- throw AddressError.invalidScheme
- } catch AddressError.invalid {
- return try LegacyAddress(plainAddress)
- }
- }
-
- private static func getBase58DecodeAsBytes(address: String, length: Int) -> [UTF8.CodeUnit]? {
- let b58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
-
- var output: [UTF8.CodeUnit] = Array(repeating: 0, count: length)
-
- for i in 0..
Bool {
- guard address.count >= 26 && address.count <= 35,
- address.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil,
- let decodedAddress = getBase58DecodeAsBytes(address: address, length: 25),
- decodedAddress.count >= 4
- else { return false }
-
- let decodedAddressNoCheckSum = Array(decodedAddress.prefix(decodedAddress.count - 4))
-
- let hashedSum = Crypto.sha256sha256(Data(decodedAddressNoCheckSum))
-
- let checkSum = Array(decodedAddress.suffix(from: decodedAddress.count - 4))
- let hashedSumHeader = Array(hashedSum.prefix(4))
-
- return hashedSumHeader == checkSum
- }
-}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressType.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressType.swift
index 02fb3d68a..500b19e5c 100644
--- a/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressType.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/AddressType.swift
@@ -24,11 +24,19 @@
import Foundation
-public class AddressType {
- static let pubkeyHash: AddressType = PubkeyHash()
- static let scriptHash: AddressType = ScriptHash()
-
- var versionByte: UInt8 { return 0 }
+public enum AddressType {
+ case pubkeyHash
+ case scriptHash
+
+ var versionByte: UInt8 {
+ switch self {
+ case .pubkeyHash:
+ return .zero
+ case .scriptHash:
+ return 8
+ }
+ }
+
var versionByte160: UInt8 { return versionByte + 0 }
var versionByte192: UInt8 { return versionByte + 1 }
var versionByte224: UInt8 { return versionByte + 2 }
@@ -44,9 +52,3 @@ extension AddressType: Equatable {
return lhs.versionByte == rhs.versionByte
}
}
-public class PubkeyHash: AddressType {
- public override var versionByte: UInt8 { return 0 }
-}
-public class ScriptHash: AddressType {
- public override var versionByte: UInt8 { return 8 }
-}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/Address.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/Address.swift
new file mode 100644
index 000000000..e412f22d7
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/Address.swift
@@ -0,0 +1,21 @@
+//
+// Address.swift
+//
+//
+// Created by Andrey Golubenko on 05.06.2023.
+//
+
+import Foundation
+
+public protocol AddressProtocol {
+ var scriptType: ScriptType { get }
+ var lockingScriptPayload: Data { get }
+ var stringValue: String { get }
+ var lockingScript: Data { get }
+}
+
+#if os(iOS) || os(tvOS) || os(watchOS)
+public typealias Address = AddressProtocol & QRCodeConvertible
+#else
+public typealias Address = AddressProtocol
+#endif
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/LegacyAddress.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/LegacyAddress.swift
new file mode 100644
index 000000000..cdc74ab90
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/LegacyAddress.swift
@@ -0,0 +1,63 @@
+//
+// LegacyAddress.swift
+//
+// Copyright © 2018 Kishikawa Katsumi
+// Copyright © 2018 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
+
+public final class LegacyAddress: Address, Equatable {
+ public let type: AddressType
+ public let lockingScriptPayload: Data
+ public let stringValue: String
+
+ public var scriptType: ScriptType {
+ switch type {
+ case .pubkeyHash: return .p2pkh
+ case .scriptHash: return .p2sh
+ }
+ }
+
+ public var qrcodeString: String {
+ stringValue
+ }
+
+ public var lockingScript: Data {
+ switch type {
+ case .pubkeyHash: return OpCode.p2pkhStart + OpCode.push(lockingScriptPayload) + OpCode.p2pkhFinish
+ case .scriptHash: return OpCode.p2shStart + OpCode.push(lockingScriptPayload) + OpCode.p2shFinish
+ }
+ }
+
+ public init(type: AddressType, payload: Data, base58: String) {
+ self.type = type
+ self.lockingScriptPayload = payload
+ self.stringValue = base58
+ }
+
+ public static func ==(lhs: LegacyAddress, rhs: T) -> Bool {
+ guard let rhs = rhs as? LegacyAddress else {
+ return false
+ }
+ return lhs.type == rhs.type && lhs.lockingScriptPayload == rhs.lockingScriptPayload
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/SegWitV0Address.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/SegWitV0Address.swift
new file mode 100644
index 000000000..f8ebd58dc
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/SegWitV0Address.swift
@@ -0,0 +1,35 @@
+import Foundation
+
+public final class SegWitV0Address: Address, Equatable {
+ public let type: AddressType
+ public let lockingScriptPayload: Data
+ public let stringValue: String
+
+ public var qrcodeString: String {
+ stringValue
+ }
+
+ public var scriptType: ScriptType {
+ switch type {
+ case .pubkeyHash: return .p2wpkh
+ case .scriptHash: return .p2wsh
+ }
+ }
+
+ public var lockingScript: Data {
+ OpCode.segWitOutputScript(lockingScriptPayload, versionByte: 0)
+ }
+
+ public init(type: AddressType, payload: Data, bech32: String) {
+ self.type = type
+ self.lockingScriptPayload = payload
+ self.stringValue = bech32
+ }
+
+ static public func ==(lhs: SegWitV0Address, rhs: T) -> Bool {
+ guard let rhs = rhs as? SegWitV0Address else {
+ return false
+ }
+ return lhs.type == rhs.type && lhs.lockingScriptPayload == rhs.lockingScriptPayload
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/TaprootAddress.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/TaprootAddress.swift
new file mode 100644
index 000000000..a89047ff3
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Addresses/TaprootAddress.swift
@@ -0,0 +1,29 @@
+import Foundation
+
+public final class TaprootAddress: Address, Equatable {
+ public let lockingScriptPayload: Data
+ public let stringValue: String
+ public let version: UInt8
+ public var scriptType = ScriptType.p2tr
+
+ public var lockingScript: Data {
+ OpCode.segWitOutputScript(lockingScriptPayload, versionByte: Int(version))
+ }
+
+ public var qrcodeString: String {
+ stringValue
+ }
+
+ public init(payload: Data, bech32m: String, version: UInt8) {
+ self.lockingScriptPayload = payload
+ self.stringValue = bech32m
+ self.version = version
+ }
+
+ static public func ==(lhs: TaprootAddress, rhs: T) -> Bool {
+ guard let rhs = rhs as? TaprootAddress else {
+ return false
+ }
+ return lhs.lockingScriptPayload == rhs.lockingScriptPayload && lhs.version == rhs.version
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Bech32.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Bech32.swift
new file mode 100644
index 000000000..4d560825f
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Bech32.swift
@@ -0,0 +1,208 @@
+//
+// Bech32.swift
+//
+// Created by Evolution Group Ltd on 12.02.2018.
+// Copyright © 2018 Evolution Group Ltd. All rights reserved.
+//
+
+// Base32 address format for native v0-16 witness outputs implementation
+// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
+// Inspired by Pieter Wuille C++ implementation
+
+import Foundation
+
+/// Bech32 checksum implementation
+public final class Bech32 {
+ static let shared = Bech32()
+
+ private let gen: [UInt32] = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+ /// Bech32 checksum delimiter
+ private let checksumMarker: String = "1"
+ /// Bech32 character set for encoding
+ private let encCharset: Data = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".data(using: .utf8)!
+ /// Bech32 character set for decoding
+ private let decCharset: [Int8] = [
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
+ -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
+ 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
+ -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
+ 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
+ ]
+
+ public init() {}
+
+ /// Find the polynomial with value coefficients mod the generator as 30-bit.
+ private func polymod(_ values: Data) -> UInt32 {
+ var chk: UInt32 = 1
+ for v in values {
+ let top = (chk >> 25)
+ chk = (chk & 0x1ffffff) << 5 ^ UInt32(v)
+ for i: UInt8 in 0..<5 {
+ chk ^= ((top >> i) & 1) == 0 ? 0 : gen[Int(i)]
+ }
+ }
+ return chk
+ }
+
+ /// Expand a HRP for use in checksum computation.
+ private func expandHrp(_ hrp: String) -> Data {
+ guard let hrpBytes = hrp.data(using: .utf8) else { return Data() }
+ var result = Data(repeating: 0x00, count: hrpBytes.count*2+1)
+ for (i, c) in hrpBytes.enumerated() {
+ result[i] = c >> 5
+ result[i + hrpBytes.count + 1] = c & 0x1f
+ }
+ result[hrp.count] = 0
+ return result
+ }
+
+ private func extractChecksumWithEncoding(hrp: String, checksum: Data) -> (check: UInt32, encoding: Encoding)? {
+ var data = expandHrp(hrp)
+ data.append(checksum)
+ let check = polymod(data)
+ return Encoding.fromCheck(check).flatMap { (check, $0) }
+ }
+
+ /// Create checksum
+ private func createChecksum(hrp: String, values: Data, encoding: Encoding) -> Data {
+ var enc = expandHrp(hrp)
+ enc.append(values)
+ enc.append(Data(repeating: 0x00, count: 6))
+ let mod: UInt32 = polymod(enc) ^ encoding.checksumXorConstant
+ var ret: Data = Data(repeating: 0x00, count: 6)
+ for i in 0..<6 {
+ ret[i] = UInt8((mod >> (5 * (5 - i))) & 31)
+ }
+ return ret
+ }
+
+ /// Encode Bech32 string
+ public func encode(_ hrp: String, values: Data, encoding: Encoding) -> String {
+ let checksum = createChecksum(hrp: hrp, values: values, encoding: encoding)
+ var combined = values
+ combined.append(checksum)
+ guard let hrpBytes = hrp.data(using: .utf8) else { return "" }
+ var ret = hrpBytes
+ ret.append("1".data(using: .utf8)!)
+ for i in combined {
+ ret.append(encCharset[Int(i)])
+ }
+ return String(data: ret, encoding: .utf8) ?? ""
+ }
+
+ /// Decode Bech32 string
+ public func decode(_ str: String) throws -> (hrp: String, checksum: Data, encoding: Encoding) {
+ guard let strBytes = str.data(using: .utf8) else {
+ throw DecodingError.nonUTF8String
+ }
+ guard strBytes.count <= 90 else {
+ throw DecodingError.stringLengthExceeded
+ }
+ var lower: Bool = false
+ var upper: Bool = false
+ for c in strBytes {
+ // printable range
+ if c < 33 || c > 126 {
+ throw DecodingError.nonPrintableCharacter
+ }
+ // 'a' to 'z'
+ if c >= 97 && c <= 122 {
+ lower = true
+ }
+ // 'A' to 'Z'
+ if c >= 65 && c <= 90 {
+ upper = true
+ }
+ }
+ if lower && upper {
+ throw DecodingError.invalidCase
+ }
+ guard let pos = str.range(of: checksumMarker, options: .backwards)?.lowerBound else {
+ throw DecodingError.noChecksumMarker
+ }
+ let intPos: Int = str.distance(from: str.startIndex, to: pos)
+ guard intPos >= 1 else {
+ throw DecodingError.incorrectHrpSize
+ }
+ guard intPos + 7 <= str.count else {
+ throw DecodingError.incorrectChecksumSize
+ }
+ let vSize: Int = str.count - 1 - intPos
+ var values: Data = Data(repeating: 0x00, count: vSize)
+ for i in 0.. Encoding? {
+ switch check {
+ case Encoding.bech32.checksumXorConstant: return .bech32
+ case Encoding.bech32m.checksumXorConstant: return .bech32m
+ default: return nil
+ }
+ }
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Scripts/AddressConverter.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverter.swift
similarity index 81%
rename from BitcoinKit/Sources/BitcoinKit/Scripts/AddressConverter.swift
rename to BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverter.swift
index 1bb84f171..69b260db3 100644
--- a/BitcoinKit/Sources/BitcoinKit/Scripts/AddressConverter.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverter.swift
@@ -1,21 +1,20 @@
//
// AddressConverter.swift
-// BitcoinKit
+//
//
-// Created by Anton Boyarkin on 12/02/2019.
+// Created by Andrey Golubenko on 06.06.2023.
//
import Foundation
-class AddressConverter {
- enum ConversionError: Error {
- case invalidChecksum
- case invalidAddressLength
- case unknownAddressType
- case wrongAddressPrefix
- }
-
- public static func extract(from signatureScript: Data, with network: Network) -> Address? {
+public protocol AddressConverter {
+ func convert(address: String) throws -> Address
+ func convert(lockingScriptPayload: Data, type: ScriptType) throws -> Address
+ func convert(publicKey: PublicKey, type: ScriptType) throws -> Address
+}
+
+extension AddressConverter {
+ public func extract(from signatureScript: Data, with network: Network) -> Address? {
var payload: Data?
var validScriptType: ScriptType = ScriptType.unknown
let sigScriptCount = signatureScript.count
@@ -60,7 +59,8 @@ class AddressConverter {
}
if let payload = payload {
let keyHash = Crypto.sha256ripemd160(payload)
- if let address = try? network.convert(keyHash: keyHash, type: validScriptType) {
+ let address = try? convert(lockingScriptPayload: payload, type: validScriptType)
+ if let address = address {
outputAddress = address
}
}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverterChain.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverterChain.swift
new file mode 100644
index 000000000..ca1b56394
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverterChain.swift
@@ -0,0 +1,54 @@
+import Foundation
+
+final class AddressConverterChain: AddressConverter {
+ private let concreteConverters: [AddressConverter]
+
+ init(concreteConverters: [AddressConverter]) {
+ self.concreteConverters = concreteConverters
+ }
+
+ func convert(address: String) throws -> Address {
+ var errors = [Error]()
+
+ for converter in concreteConverters {
+ do {
+ let converted = try converter.convert(address: address)
+ return converted
+ } catch {
+ errors.append(error)
+ }
+ }
+
+ throw BitcoinError.list(errors: errors)
+ }
+
+ func convert(lockingScriptPayload: Data, type: ScriptType) throws -> Address {
+ var errors = [Error]()
+
+ for converter in concreteConverters {
+ do {
+ let converted = try converter.convert(lockingScriptPayload: lockingScriptPayload, type: type)
+ return converted
+ } catch {
+ errors.append(error)
+ }
+ }
+
+ throw BitcoinError.list(errors: errors)
+ }
+
+ public func convert(publicKey: PublicKey, type: ScriptType) throws -> Address {
+ var errors = [Error]()
+
+ for converter in concreteConverters {
+ do {
+ let converted = try converter.convert(publicKey: publicKey, type: type)
+ return converted
+ } catch {
+ errors.append(error)
+ }
+ }
+
+ throw BitcoinError.list(errors: errors)
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverterFactory.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverterFactory.swift
new file mode 100644
index 000000000..e0deec1b0
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/AddressConverterFactory.swift
@@ -0,0 +1,24 @@
+//
+// AddressConverterFactory.swift
+//
+//
+// Created by Andrey Golubenko on 06.06.2023.
+//
+
+public struct AddressConverterFactory {
+ public func make(network: Network) -> AddressConverter {
+ let segWitAddressConverter = SegWitBech32AddressConverter(prefix: "bc")
+
+ let base58AddressConverter = Base58AddressConverter(
+ addressVersion: network.pubkeyhash,
+ addressScriptVersion: network.scripthash
+ )
+
+ return AddressConverterChain(concreteConverters: [
+ segWitAddressConverter,
+ base58AddressConverter
+ ])
+ }
+
+ public init() {}
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/Base58AddressConverter.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/Base58AddressConverter.swift
new file mode 100644
index 000000000..c514e64c0
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/Base58AddressConverter.swift
@@ -0,0 +1,71 @@
+import Foundation
+
+final class Base58AddressConverter: AddressConverter {
+ private static let checkSumLength = 4
+ private let addressVersion: UInt8
+ private let addressScriptVersion: UInt8
+
+ init(addressVersion: UInt8, addressScriptVersion: UInt8) {
+ self.addressVersion = addressVersion
+ self.addressScriptVersion = addressScriptVersion
+ }
+
+ func convert(address: String) throws -> Address {
+ // check length of address to avoid wrong converting
+ guard address.count >= 26 && address.count <= 35 else {
+ throw BitcoinError.invalidAddressLength
+ }
+
+ guard let hex = Base58.decode(address) else {
+ throw BitcoinError.unknownAddressType
+ }
+
+ // check decoded length. Must be 1(version) + 20(KeyHash) + 4(CheckSum)
+ if hex.count != Base58AddressConverter.checkSumLength + 20 + 1 {
+ throw BitcoinError.invalidAddressLength
+ }
+ let givenChecksum = hex.suffix(Base58AddressConverter.checkSumLength)
+ let doubleSHA256 = Crypto.sha256sha256(hex.prefix(hex.count - Base58AddressConverter.checkSumLength))
+ let actualChecksum = doubleSHA256.prefix(Base58AddressConverter.checkSumLength)
+ guard givenChecksum == actualChecksum else {
+ throw BitcoinError.invalidChecksum
+ }
+
+ let type: AddressType
+ switch hex[0] {
+ case addressVersion: type = AddressType.pubkeyHash
+ case addressScriptVersion: type = AddressType.scriptHash
+ default: throw BitcoinError.wrongAddressPrefix
+ }
+
+ let keyHash = hex.dropFirst().dropLast(4)
+ return LegacyAddress(type: type, payload: keyHash, base58: address)
+ }
+
+ func convert(lockingScriptPayload: Data, type: ScriptType) throws -> Address {
+ let version: UInt8
+ let addressType: AddressType
+
+ switch type {
+ case .p2pkh, .p2pk:
+ version = addressVersion
+ addressType = AddressType.pubkeyHash
+ case .p2sh, .p2wpkhSh:
+ version = addressScriptVersion
+ addressType = AddressType.scriptHash
+ default: throw BitcoinError.unknownAddressType
+ }
+
+ var withVersion = (Data([version])) + lockingScriptPayload
+ let doubleSHA256 = Crypto.sha256sha256(withVersion)
+ let checksum = doubleSHA256.prefix(4)
+ withVersion += checksum
+ let base58 = Base58.encode(withVersion)
+ return LegacyAddress(type: addressType, payload: lockingScriptPayload, base58: base58)
+ }
+
+ func convert(publicKey: PublicKey, type: ScriptType) throws -> Address {
+ let keyHash = type == .p2wpkhSh ? publicKey.hashP2wpkhWrappedInP2sh : publicKey.hashP2pkh
+ return try convert(lockingScriptPayload: keyHash, type: type)
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/SegWitBech32AddressConverter.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/SegWitBech32AddressConverter.swift
new file mode 100644
index 000000000..8342fa329
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Converters/SegWitBech32AddressConverter.swift
@@ -0,0 +1,67 @@
+//
+// SegWitBech32AddressConverter.swift
+//
+//
+// Created by Andrey Golubenko on 05.06.2023.
+//
+
+import Foundation
+
+final class SegWitBech32AddressConverter: AddressConverter {
+ private let prefix: String
+ private let hasAdvanced: Bool
+
+ init(prefix: String, hasAdvanced: Bool = true) {
+ self.prefix = prefix
+ self.hasAdvanced = hasAdvanced
+ }
+
+ func convert(address: String) throws -> Address {
+ if let segWitData = try? SegWitBech32.decode(hrp: prefix, addr: address, hasAdvanced: hasAdvanced) {
+ switch segWitData.version {
+ case 0:
+ var type: AddressType = .pubkeyHash
+ switch segWitData.program.count {
+ case 20: type = .pubkeyHash
+ case 32: type = .scriptHash
+ default: break
+ }
+ return SegWitV0Address(type: type, payload: segWitData.program, bech32: address)
+ case 1:
+ guard segWitData.program.count == 32 else {
+ break
+ }
+ return TaprootAddress(payload: segWitData.program, bech32m: address, version: segWitData.version)
+ default:
+ break
+ }
+ }
+ throw BitcoinError.unknownAddressType
+ }
+
+ func convert(lockingScriptPayload: Data, type: ScriptType) throws -> Address {
+ switch type {
+ case .p2wpkh:
+ let bech32 = try SegWitBech32.encode(hrp: prefix, version: 0, program: lockingScriptPayload, encoding: .bech32)
+ return SegWitV0Address(type: AddressType.pubkeyHash, payload: lockingScriptPayload, bech32: bech32)
+ case .p2wsh:
+ let bech32 = try SegWitBech32.encode(hrp: prefix, version: 0, program: lockingScriptPayload, encoding: .bech32)
+ return SegWitV0Address(type: AddressType.scriptHash, payload: lockingScriptPayload, bech32: bech32)
+ case .p2tr:
+ let bech32 = try SegWitBech32.encode(hrp: prefix, version: 1, program: lockingScriptPayload, encoding: .bech32m)
+ return TaprootAddress(payload: lockingScriptPayload, bech32m: bech32, version: 1)
+ default: throw BitcoinError.unknownAddressType
+ }
+ }
+
+ func convert(publicKey: PublicKey, type: ScriptType) throws -> Address {
+ switch type {
+ case .p2wpkh, .p2wsh:
+ return try convert(lockingScriptPayload: publicKey.hashP2pkh, type: type)
+// case .p2tr:
+// return try convert(lockingScriptPayload: publicKey.convertedForP2tr, type: type)
+ default:
+ throw BitcoinError.unknownAddressType
+ }
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Encoding.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Encoding.swift
index 1fe920735..ee064f4f0 100644
--- a/BitcoinKit/Sources/BitcoinKit/Core/Keys/Encoding.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/Encoding.swift
@@ -161,148 +161,3 @@ extension Encoding {
return Data(repeating: 0, count: zerosCount) + Data(decodedBytes)
}
}
-
-public struct Bech32 {
- private static let base32Alphabets = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
-
- public static func encode(_ bytes: Data, prefix: String, seperator: String = ":") -> String {
- let payload = convertTo5bit(data: bytes, pad: true)
- let checksum: Data = createChecksum(prefix: prefix, payload: payload) // Data of [UInt5]
- let combined: Data = payload + checksum // Data of [UInt5]
- var base32 = ""
- for b in combined {
- let strIndex = String.Index(utf16Offset: Int(b), in: base32Alphabets)
- base32 += String(base32Alphabets[strIndex])
- }
-
- return prefix + seperator + base32
- }
-
- // string : "bitcoincash:qql8zpwglr3q5le9jnjxkmypefaku39dkygsx29fzk"
- public static func decode(_ string: String, seperator: 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: seperator)
- // 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))
- }
-
- private static func verifyChecksum(prefix: String, payload: Data) -> Bool {
- return PolyMod(expand(prefix) + payload) == 0
- }
-
- private static func expand(_ prefix: String) -> Data {
- var ret: Data = Data()
- let buf: [UInt8] = Array(prefix.utf8)
- for b in buf {
- ret += b & 0x1f
- }
- ret += Data(repeating: 0, count: 1)
- return ret
- }
-
- private 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 += UInt8((mod >> (5 * (7 - i))) & 0x1f)
- }
- return ret
- }
-
- private 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
- }
-
- private 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)
- }
-
- private enum DecodeError: Error {
- case invalidCharacter
- case invalidBits
- }
-}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Keys/PublicKey.swift b/BitcoinKit/Sources/BitcoinKit/Core/Keys/PublicKey.swift
index fff10b408..8d7deebb6 100644
--- a/BitcoinKit/Sources/BitcoinKit/Core/Keys/PublicKey.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Keys/PublicKey.swift
@@ -34,39 +34,22 @@ public struct PublicKey {
public let data: Data
@available(*, deprecated, renamed: "data")
public var raw: Data { return data }
- public var pubkeyHash: Data {
- return Crypto.sha256ripemd160(data)
- }
public let network: Network
public let isCompressed: Bool
+ public let hashP2pkh: Data
+ public let hashP2wpkhWrappedInP2sh: Data
public init(bytes data: Data, network: Network) {
self.data = data
self.network = network
let header = data[0]
self.isCompressed = (header == 0x02 || header == 0x03)
- }
-
- /// Version = 1 byte of 0 (zero); on the test network, this is 1 byte of 111
- /// Key hash = Version concatenated with RIPEMD-160(SHA-256(public key))
- /// Checksum = 1st 4 bytes of SHA-256(SHA-256(Key hash))
- /// Bitcoin Address = Base58Encode(Key hash concatenated with Checksum)
- private func base58() -> String {
- let versionByte: Data = Data([network.pubkeyhash])
- return publicKeyHashToAddress(versionByte + pubkeyHash)
- }
-
- private func bech32() -> String {
- let versionByte: Data = Data([VersionByte.pubkeyHash160])
- return Bech32.encode(versionByte + pubkeyHash, prefix: network.scheme)
- }
-
- public func toLegacy() -> LegacyAddress {
- return LegacyAddress(data: pubkeyHash, type: .pubkeyHash, network: network, base58: base58(), bech32: bech32(), publicKey: data)
- }
-
- public func toCashaddr() -> Cashaddr {
- return Cashaddr(data: pubkeyHash, type: .pubkeyHash, network: network, base58: base58(), bech32: bech32(), publicKey: data)
+ hashP2pkh = Crypto.sha256ripemd160(data)
+
+ hashP2wpkhWrappedInP2sh = Crypto.sha256ripemd160(OpCode.segWitOutputScript(
+ hashP2pkh,
+ versionByte: .zero
+ ))
}
}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/Network.swift b/BitcoinKit/Sources/BitcoinKit/Core/Network.swift
index 287123a46..e12ac73e4 100644
--- a/BitcoinKit/Sources/BitcoinKit/Core/Network.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Core/Network.swift
@@ -275,29 +275,3 @@ public class Testnet: Network {
return Data(Data(hex: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")!.reversed())
}
}
-
-extension Network {
- func convert(keyHash: Data, type: ScriptType) throws -> Address {
- let version: UInt8
- let addressType: AddressType
- switch type {
- case .p2pkh, .p2pk:
- version = self.pubkeyhash
- addressType = .pubkeyHash
- case .p2sh, .p2wpkhSh:
- version = self.scripthash
- addressType = .scriptHash
- default: throw AddressConverter.ConversionError.unknownAddressType
- }
- return try convertToLegacy(keyHash: keyHash, version: version, addressType: addressType)
- }
-
- func convertToLegacy(keyHash: Data, version: UInt8, addressType: AddressType) throws -> LegacyAddress {
- var withVersion = (Data([version])) + keyHash
- let doubleSHA256 = Crypto.sha256sha256(withVersion)
- let checksum = doubleSHA256.prefix(4)
- withVersion += checksum
- let base58 = Base58.encode(withVersion)
- return try LegacyAddress(base58)
- }
-}
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/PaymentURI.swift b/BitcoinKit/Sources/BitcoinKit/Core/PaymentURI.swift
index 90237df81..43ea160ea 100644
--- a/BitcoinKit/Sources/BitcoinKit/Core/PaymentURI.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Core/PaymentURI.swift
@@ -40,11 +40,11 @@ public struct PaymentURI {
case amount
}
- public init(_ string: String) throws {
+ public init(_ string: String, addressConverter: AddressConverter) throws {
guard let components = URLComponents(string: string), let scheme = components.scheme, scheme.lowercased() == "bitcoin" else {
throw PaymentURIError.invalid
}
- guard let address = try? AddressFactory.create(components.path) else {
+ guard let address = try? addressConverter.convert(address: components.path) else {
throw PaymentURIError.malformed(.address)
}
self.address = address
diff --git a/BitcoinKit/Sources/BitcoinKit/Core/SegWitBech32.swift b/BitcoinKit/Sources/BitcoinKit/Core/SegWitBech32.swift
new file mode 100644
index 000000000..99c0b94ba
--- /dev/null
+++ b/BitcoinKit/Sources/BitcoinKit/Core/SegWitBech32.swift
@@ -0,0 +1,116 @@
+//
+// SegWitBech32.swift
+//
+// Created by Evolution Group Ltd on 12.02.2018.
+// Copyright © 2018 Evolution Group Ltd. All rights reserved.
+//
+
+// Base32 address format for native v0-16 witness outputs implementation
+// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
+// Inspired by Pieter Wuille C++ implementation
+
+import Foundation
+
+/// Segregated Witness Address encoder/decoder
+public class SegWitBech32 {
+ /// Convert from one power-of-2 number base to another
+ private static func convertBits(from: Int, to: Int, pad: Bool, idata: Data) throws -> Data {
+ var acc: Int = 0
+ var bits: Int = 0
+ let maxv: Int = (1 << to) - 1
+ let maxAcc: Int = (1 << (from + to - 1)) - 1
+ var odata = Data()
+ for ibyte in idata {
+ acc = ((acc << from) | Int(ibyte)) & maxAcc
+ bits += from
+ while bits >= to {
+ bits -= to
+ odata.append(UInt8((acc >> bits) & maxv))
+ }
+ }
+ if pad {
+ if bits != 0 {
+ odata.append(UInt8((acc << (to - bits)) & maxv))
+ }
+ } else if (bits >= from || ((acc << (to - bits)) & maxv) != 0) {
+ throw CoderError.bitsConversionFailed
+ }
+ return odata
+ }
+
+ /// Decode segwit address
+ public static func decode(hrp: String, addr: String, hasAdvanced: Bool = true) throws -> (version: UInt8, program: Data) {
+ let dec = try Bech32.shared.decode(addr)
+ guard dec.hrp == hrp else {
+ throw CoderError.hrpMismatch(dec.hrp, hrp)
+ }
+ guard dec.checksum.count >= 1 else {
+ throw CoderError.checksumSizeTooLow
+ }
+ let idata = (hasAdvanced ? dec.checksum.advanced(by: 1) : dec.checksum)
+ let conv = try convertBits(from: 5, to: 8, pad: false, idata: idata)
+ guard conv.count >= 2 && conv.count <= 40 else {
+ throw CoderError.dataSizeMismatch(conv.count)
+ }
+ guard dec.checksum[0] <= 16 else {
+ throw CoderError.segwitVersionNotSupported(dec.checksum[0])
+ }
+ if dec.checksum[0] == 0 && conv.count != 20 && conv.count != 32 {
+ throw CoderError.segwitV0ProgramSizeMismatch(conv.count)
+ }
+ if (dec.checksum[0] == 0 && dec.encoding != .bech32) || (dec.checksum[0] != 0 && dec.encoding != .bech32m) {
+ throw CoderError.segwitVersionAndEncodingMismatch
+ }
+ return (dec.checksum[0], conv)
+ }
+
+ /// Encode segwit address
+ public static func encode(hrp: String, version: UInt8, program: Data, encoding: Bech32.Encoding) throws -> String {
+ var enc = Data([version])
+ enc.append(try convertBits(from: 8, to: 5, pad: true, idata: program))
+ let result = Bech32.shared.encode(hrp, values: enc, encoding: encoding)
+ guard let _ = try? decode(hrp: hrp, addr: result) else {
+ throw CoderError.encodingCheckFailed
+ }
+ return result
+ }
+
+ public init() {}
+
+}
+
+extension SegWitBech32 {
+ public enum CoderError: LocalizedError {
+ case bitsConversionFailed
+ case hrpMismatch(String, String)
+ case checksumSizeTooLow
+
+ case dataSizeMismatch(Int)
+ case segwitVersionNotSupported(UInt8)
+ case segwitV0ProgramSizeMismatch(Int)
+ case segwitVersionAndEncodingMismatch
+
+ case encodingCheckFailed
+
+ public var errorDescription: String? {
+ switch self {
+ case .bitsConversionFailed:
+ return "Failed to perform bits conversion"
+ case .checksumSizeTooLow:
+ return "Checksum size is too low"
+ case .dataSizeMismatch(let size):
+ return "Program size \(size) does not meet required range 2...40"
+ case .encodingCheckFailed:
+ return "Failed to check result after encoding"
+ case .hrpMismatch(let got, let expected):
+ return "Human-readable-part \"\(got)\" does not match requested \"\(expected)\""
+ case .segwitV0ProgramSizeMismatch(let size):
+ return "Segwit program size \(size) does not meet version 0 requirements"
+ case .segwitVersionNotSupported(let version):
+ return "Segwit version \(version) is not supported by this decoder"
+ case .segwitVersionAndEncodingMismatch:
+ return "Wrong encoding is used for the Segwit version being used"
+ }
+ }
+ }
+}
diff --git a/BitcoinKit/Sources/BitcoinKit/Messages/Transaction.swift b/BitcoinKit/Sources/BitcoinKit/Messages/Transaction.swift
index 2fc720c09..0658e43d7 100644
--- a/BitcoinKit/Sources/BitcoinKit/Messages/Transaction.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Messages/Transaction.swift
@@ -74,21 +74,15 @@ public struct Transaction {
return data
}
- public mutating func unpack(with network: Network) {
+ public mutating func unpack(with network: Network, addressConverter: AddressConverter) {
for index in 0.. Bool {
- let hash = address.base58
- return inputs.filter { input -> Bool in input.address == hash }.count > 0
- || outputs.filter { output -> Bool in output.address == hash }.count > 0
- }
public func isCoinbase() -> Bool {
return inputs.count == 1 && inputs[0].isCoinbase()
@@ -169,7 +163,7 @@ public struct Transaction {
for (i, utxo) in unsignedTx.utxos.enumerated() {
let pubkeyHash: Data = Script.getPublicKeyHash(from: utxo.output.lockingScript)
- let keysOfUtxo: [PrivateKey] = keys.filter { $0.publicKey().pubkeyHash == pubkeyHash }
+ let keysOfUtxo: [PrivateKey] = keys.filter { $0.publicKey().hashP2pkh == pubkeyHash }
guard let key = keysOfUtxo.first else {
print("No keys to this txout : \(utxo.output.value)")
continue
diff --git a/BitcoinKit/Sources/BitcoinKit/Messages/TransactionInput.swift b/BitcoinKit/Sources/BitcoinKit/Messages/TransactionInput.swift
index bdb695c8a..c9cfa6e92 100644
--- a/BitcoinKit/Sources/BitcoinKit/Messages/TransactionInput.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Messages/TransactionInput.swift
@@ -59,8 +59,8 @@ public struct TransactionInput {
return data
}
- public mutating func unpack(with network: Network) {
- address = AddressConverter.extract(from: self.signatureScript, with: network)?.base58
+ public mutating func unpack(with network: Network, addressConverter: AddressConverter) {
+ address = addressConverter.extract(from: signatureScript, with: network)?.stringValue
}
static func deserialize(_ byteStream: ByteStream) -> TransactionInput {
diff --git a/BitcoinKit/Sources/BitcoinKit/Mock/MockHelper.swift b/BitcoinKit/Sources/BitcoinKit/Mock/MockHelper.swift
index 541be2bd1..4180aadfe 100644
--- a/BitcoinKit/Sources/BitcoinKit/Mock/MockHelper.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Mock/MockHelper.swift
@@ -1,126 +1,126 @@
+////
+//// MockHelper.swift
+////
+//// Copyright © 2018 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.
+////
//
-// MockHelper.swift
-//
-// Copyright © 2018 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
-
-public struct MockHelper {
-
- public static func createUtxo(lockScript: Script) -> UnspentTransaction {
- let outputMock = TransactionOutput(value: 100_000_000, lockingScript: lockScript.data)
- let outpointMock = TransactionOutPoint(hash: Data(), index: 0)
- return UnspentTransaction(output: outputMock, outpoint: outpointMock)
- }
-
- public static func createTransaction(utxo: UnspentTransaction) -> Transaction {
- let toAddress: Address = try! AddressFactory.create("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx")
- let changeAddress: Address = try! AddressFactory.create("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU")
- // 1. inputs
- let unsignedInputs = [TransactionInput(previousOutput: utxo.outpoint,
- signatureScript: Data(),
- sequence: UInt32.max)]
-
- // 2. outputs
- // 2-1. amount, change, fee
- let amount: UInt64 = 10_000
- let fee: UInt64 = 1000
- let change: UInt64 = utxo.output.value - amount - fee
-
- // 2-2. Script
- let lockingScriptTo = Script(address: toAddress)!
- let lockingScriptChange = Script(address: changeAddress)!
-
- // 2-3. TransactionOutput
- let toOutput = TransactionOutput(value: amount, lockingScript: lockingScriptTo.data)
- let changeOutput = TransactionOutput(value: change, lockingScript: lockingScriptChange.data)
-
- // 3. Tx
- let tx = Transaction(version: 1, inputs: unsignedInputs, outputs: [toOutput, changeOutput], lockTime: 0)
- return tx
- }
-
- public static func updateTransaction(_ tx: Transaction, unlockScriptData: Data) -> Transaction {
- let i = 0
- var inputs = tx.inputs
-
- // Sequence may need to be updated
- let txin = inputs[i]
- inputs[i] = TransactionInput(previousOutput: txin.previousOutput,
- signatureScript: unlockScriptData,
- sequence: txin.sequence)
-
- return Transaction(version: tx.version,
- inputs: inputs,
- outputs: tx.outputs,
- lockTime: tx.lockTime)
- }
-
- public static func verifySingleKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, key: MockKey, verbose: Bool = true) throws -> Bool {
- // mocks
- let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
- let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
-
- // signature, unlockScript(scriptSig)
- let hashType = SighashType.BCH.ALL
- let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
- let sigWithHashType: Data = signature + UInt8(hashType)
- let pair: SigKeyPair = SigKeyPair(sigWithHashType, key.pubkey)
- let unlockScript: Script = unlockScriptBuilder.build(pairs: [pair])
- // signed tx
- let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)
-
- // context
- let context = ScriptExecutionContext(transaction: signedTxMock, utxoToVerify: utxoMock.output, inputIndex: 0)!
- context.verbose = verbose
-
- // script test
- return try ScriptMachine.verify(lockScript: lockScript, unlockScript: unlockScript, context: context)
- }
-
- public static func verifyMultiKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, keys: [MockKey], verbose: Bool = true) throws -> Bool {
- // mocks
- let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
- let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
-
- // signature, unlockScript(scriptSig)
- let hashType = SighashType.BCH.ALL
- var sigKeyPairs: [SigKeyPair] = []
- for key in keys {
- let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
- let sigWithHashType: Data = signature + UInt8(hashType)
- sigKeyPairs.append(SigKeyPair(sigWithHashType, key.pubkey))
- }
-
- let unlockScript: Script = unlockScriptBuilder.build(pairs: sigKeyPairs)
- // signed tx
- let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)
-
- // context
- let context = ScriptExecutionContext(transaction: signedTxMock, utxoToVerify: utxoMock.output, inputIndex: 0)!
- context.verbose = verbose
-
- // script test
- return try ScriptMachine.verify(lockScript: lockScript, unlockScript: unlockScript, context: context)
- }
-
-}
+//import Foundation
+//
+//public struct MockHelper {
+//
+// public static func createUtxo(lockScript: Script) -> UnspentTransaction {
+// let outputMock = TransactionOutput(value: 100_000_000, lockingScript: lockScript.data)
+// let outpointMock = TransactionOutPoint(hash: Data(), index: 0)
+// return UnspentTransaction(output: outputMock, outpoint: outpointMock)
+// }
+//
+// public static func createTransaction(utxo: UnspentTransaction) -> Transaction {
+// let toAddress: Address = try! AddressFactory.create("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx")
+// let changeAddress: Address = try! AddressFactory.create("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU")
+// // 1. inputs
+// let unsignedInputs = [TransactionInput(previousOutput: utxo.outpoint,
+// signatureScript: Data(),
+// sequence: UInt32.max)]
+//
+// // 2. outputs
+// // 2-1. amount, change, fee
+// let amount: UInt64 = 10_000
+// let fee: UInt64 = 1000
+// let change: UInt64 = utxo.output.value - amount - fee
+//
+// // 2-2. Script
+// let lockingScriptTo = Script(address: toAddress)!
+// let lockingScriptChange = Script(address: changeAddress)!
+//
+// // 2-3. TransactionOutput
+// let toOutput = TransactionOutput(value: amount, lockingScript: lockingScriptTo.data)
+// let changeOutput = TransactionOutput(value: change, lockingScript: lockingScriptChange.data)
+//
+// // 3. Tx
+// let tx = Transaction(version: 1, inputs: unsignedInputs, outputs: [toOutput, changeOutput], lockTime: 0)
+// return tx
+// }
+//
+// public static func updateTransaction(_ tx: Transaction, unlockScriptData: Data) -> Transaction {
+// let i = 0
+// var inputs = tx.inputs
+//
+// // Sequence may need to be updated
+// let txin = inputs[i]
+// inputs[i] = TransactionInput(previousOutput: txin.previousOutput,
+// signatureScript: unlockScriptData,
+// sequence: txin.sequence)
+//
+// return Transaction(version: tx.version,
+// inputs: inputs,
+// outputs: tx.outputs,
+// lockTime: tx.lockTime)
+// }
+//
+// public static func verifySingleKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, key: MockKey, verbose: Bool = true) throws -> Bool {
+// // mocks
+// let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
+// let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
+//
+// // signature, unlockScript(scriptSig)
+// let hashType = SighashType.BCH.ALL
+// let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
+// let sigWithHashType: Data = signature + UInt8(hashType)
+// let pair: SigKeyPair = SigKeyPair(sigWithHashType, key.pubkey)
+// let unlockScript: Script = unlockScriptBuilder.build(pairs: [pair])
+// // signed tx
+// let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)
+//
+// // context
+// let context = ScriptExecutionContext(transaction: signedTxMock, utxoToVerify: utxoMock.output, inputIndex: 0)!
+// context.verbose = verbose
+//
+// // script test
+// return try ScriptMachine.verify(lockScript: lockScript, unlockScript: unlockScript, context: context)
+// }
+//
+// public static func verifyMultiKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, keys: [MockKey], verbose: Bool = true) throws -> Bool {
+// // mocks
+// let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
+// let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
+//
+// // signature, unlockScript(scriptSig)
+// let hashType = SighashType.BCH.ALL
+// var sigKeyPairs: [SigKeyPair] = []
+// for key in keys {
+// let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
+// let sigWithHashType: Data = signature + UInt8(hashType)
+// sigKeyPairs.append(SigKeyPair(sigWithHashType, key.pubkey))
+// }
+//
+// let unlockScript: Script = unlockScriptBuilder.build(pairs: sigKeyPairs)
+// // signed tx
+// let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)
+//
+// // context
+// let context = ScriptExecutionContext(transaction: signedTxMock, utxoToVerify: utxoMock.output, inputIndex: 0)!
+// context.verbose = verbose
+//
+// // script test
+// return try ScriptMachine.verify(lockScript: lockScript, unlockScript: unlockScript, context: context)
+// }
+//
+//}
diff --git a/BitcoinKit/Sources/BitcoinKit/Mock/MockKey.swift b/BitcoinKit/Sources/BitcoinKit/Mock/MockKey.swift
index 585abc736..4f72f15cc 100644
--- a/BitcoinKit/Sources/BitcoinKit/Mock/MockKey.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Mock/MockKey.swift
@@ -38,7 +38,7 @@ public class MockKey {
return privkey.publicKey()
}
public var pubkeyHash: Data {
- return pubkey.pubkeyHash
+ return pubkey.hashP2pkh
}
private init(wif: String) {
diff --git a/BitcoinKit/Sources/BitcoinKit/Scripts/Opcode.swift b/BitcoinKit/Sources/BitcoinKit/Scripts/Opcode.swift
index 3026a47aa..336ab2d0d 100644
--- a/BitcoinKit/Sources/BitcoinKit/Scripts/Opcode.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Scripts/Opcode.swift
@@ -28,6 +28,14 @@ public enum OpCode: OpCodeProtocol {
// swiftlint:disable:next line_length
case OP_0, OP_FALSE, OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4, OP_1NEGATE, OP_RESERVED, OP_1, OP_TRUE, OP_2, OP_3, OP_4, OP_5, OP_6, OP_7, OP_8, OP_9, OP_10, OP_11, OP_12, OP_13, OP_14, OP_15, OP_16, OP_NOP, OP_VER, OP_IF, OP_NOTIF, OP_VERIF, OP_VERNOTIF, OP_ELSE, OP_ENDIF, OP_VERIFY, OP_RETURN, OP_TOALTSTACK, OP_FROMALTSTACK, OP_2DROP, OP_2DUP, OP_3DUP, OP_2OVER, OP_2ROT, OP_2SWAP, OP_IFDUP, OP_DEPTH, OP_DROP, OP_DUP, OP_NIP, OP_OVER, OP_PICK, OP_ROLL, OP_ROT, OP_SWAP, OP_TUCK, OP_CAT, OP_SIZE, OP_SPLIT, OP_NUM2BIN, OP_BIN2NUM, OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_EQUAL, OP_EQUALVERIFY, OP_RESERVED1, OP_RESERVED2, OP_1ADD, OP_1SUB, OP_2MUL, OP_2DIV, OP_NEGATE, OP_ABS, OP_NOT, OP_0NOTEQUAL, OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT, OP_BOOLAND, OP_BOOLOR, OP_NUMEQUAL, OP_NUMEQUALVERIFY, OP_NUMNOTEQUAL, OP_LESSTHAN, OP_GREATERTHAN, OP_LESSTHANOREQUAL, OP_GREATERTHANOREQUAL, OP_MIN, OP_MAX, OP_WITHIN, OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256, OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY, OP_PUBKEYHASH, OP_PUBKEY, OP_INVALIDOPCODE, OP_NOP1, OP_NOP4, OP_NOP5, OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10
+ static let p2pkhStart = Data(from: [OpCode.OP_DUP, OpCode.OP_HASH160])
+ static let p2pkhFinish = Data([OpCode.OP_EQUALVERIFY.value, OpCode.checkSig])
+ static let p2pkFinish = Data([OpCode.checkSig])
+ static let p2shStart = Data(from: [OpCode.OP_HASH160])
+ static let p2shFinish = Data(from: [OpCode.OP_EQUAL])
+ static let pushData1: UInt8 = 0x4c
+ static let pushData2: UInt8 = 0x4d
+ static let pushData4: UInt8 = 0x4e
static let pFromShCodes = [checkSig, checkSigVerify, checkMultiSig, checkMultiSigVerify]
static let checkSig: UInt8 = 0xAC
static let checkSigVerify: UInt8 = 0xAD
@@ -192,6 +200,35 @@ public enum OpCode: OpCodeProtocol {
public func mainProcess(_ context: ScriptExecutionContext) throws {
try opcode.mainProcess(context)
}
+
+ public static func push(_ value: Int) -> Data {
+ guard value != 0 else {
+ return Data([0])
+ }
+ guard value <= 16 else {
+ return Data()
+ }
+ return Data([UInt8(value + 0x50)])
+ }
+
+ public static func push(_ data: Data) -> Data {
+ let length = data.count
+ var bytes = Data()
+
+ switch length {
+ case 0x00...0x4b: bytes = Data([UInt8(length)])
+ case 0x4c...0xff: bytes = Data([OpCode.pushData1]) + UInt8(length).littleEndian
+ case 0x0100...0xffff: bytes = Data([OpCode.pushData2]) + UInt16(length).littleEndian
+ case 0x10000...0xffffffff: bytes = Data([OpCode.pushData4]) + UInt32(length).littleEndian
+ default: return data
+ }
+
+ return bytes + data
+ }
+
+ public static func segWitOutputScript(_ data: Data, versionByte: Int = 0) -> Data {
+ return OpCode.push(versionByte) + OpCode.push(data)
+ }
}
//public struct OpCode {
diff --git a/BitcoinKit/Sources/BitcoinKit/Scripts/Script.swift b/BitcoinKit/Sources/BitcoinKit/Scripts/Script.swift
index 9c885aa9f..25e0ca407 100644
--- a/BitcoinKit/Sources/BitcoinKit/Scripts/Script.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Scripts/Script.swift
@@ -54,18 +54,6 @@ public class Script {
return data.hex
}
- public func toP2SH() -> Script {
- return try! Script()
- .append(.OP_HASH160)
- .appendData(Crypto.sha256ripemd160(data))
- .append(.OP_EQUAL)
- }
-
- public func standardP2SHAddress(network: Network) -> Address {
- let scriptHash: Data = Crypto.sha256ripemd160(data)
- return Cashaddr(data: scriptHash, type: .scriptHash, network: network)
- }
-
// Multisignature script attribute.
// If multisig script is not detected, this is nil
public typealias MultisigVariables = (nSigRequired: UInt, publickeys: [PublicKey])
@@ -98,34 +86,6 @@ public class Script {
self.init(data: scriptData)
}
- public convenience init?(address: Address) {
- self.init()
- switch address.type {
- case .pubkeyHash:
- // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
- do {
- try self.append(.OP_DUP)
- .append(.OP_HASH160)
- .appendData(address.data)
- .append(.OP_EQUALVERIFY)
- .append(.OP_CHECKSIG)
- } catch {
- return nil
- }
- case .scriptHash:
- // OP_HASH160 OP_EQUAL
- do {
- try self.append(.OP_HASH160)
- .appendData(address.data)
- .append(.OP_EQUAL)
- } catch {
- return nil
- }
- default:
- return nil
- }
- }
-
// OP_ ... OP_ OP_CHECKMULTISIG
public convenience init?(publicKeys: [PublicKey], signaturesRequired: UInt) {
// First make sure the arguments make sense.
@@ -306,21 +266,6 @@ public class Script {
return chunks
}
- public func standardAddress(network: Network) -> Address? {
- if isPayToPublicKeyHashScript {
- guard let dataChunk = chunk(at: 2) as? DataChunk else {
- return nil
- }
- return Cashaddr(data: dataChunk.pushedData, type: .pubkeyHash, network: network)
- } else if isPayToScriptHashScript {
- guard let dataChunk = chunk(at: 1) as? DataChunk else {
- return nil
- }
- return Cashaddr(data: dataChunk.pushedData, type: .scriptHash, network: network)
- }
- return nil
- }
-
// MARK: - Modification
public func invalidateSerialization() {
dataCache = nil
diff --git a/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptFactory.swift b/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptFactory.swift
index f765fc3f2..cf9eab7ac 100644
--- a/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptFactory.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptFactory.swift
@@ -44,14 +44,6 @@ public extension ScriptFactory.Standard {
.append(.OP_CHECKSIG)
}
- static func buildP2PKH(address: Address) -> Script? {
- return Script(address: address)
- }
-
- static func buildP2SH(script: Script) -> Script {
- return script.toP2SH()
- }
-
static func buildMultiSig(publicKeys: [PublicKey]) -> Script? {
return Script(publicKeys: publicKeys, signaturesRequired: UInt(publicKeys.count))
}
@@ -75,22 +67,6 @@ public extension ScriptFactory.LockTime {
let lockDate = Date(timeIntervalSinceNow: lockIntervalSinceNow)
return build(script: script, lockDate: lockDate)
}
-
- // P2PKH + LockTime
- static func build(address: Address, lockIntervalSinceNow: TimeInterval) -> Script? {
- guard let p2pkh = Script(address: address) else {
- return nil
- }
- let lockDate = Date(timeIntervalSinceNow: lockIntervalSinceNow)
- return build(script: p2pkh, lockDate: lockDate)
- }
-
- static func build(address: Address, lockDate: Date) -> Script? {
- guard let p2pkh = Script(address: address) else {
- return nil
- }
- return build(script: p2pkh, lockDate: lockDate)
- }
}
// MARK: - OpReturn
@@ -172,14 +148,14 @@ public extension ScriptFactory.HashedTimeLockedContract {
.append(.OP_EQUALVERIFY)
.append(.OP_DUP)
.append(.OP_HASH160)
- .appendData(recipient.data)
+ .appendData(recipient.lockingScriptPayload)
.append(.OP_ELSE)
.appendData(lockDate.bigNumData)
.append(.OP_CHECKLOCKTIMEVERIFY)
.append(.OP_DROP)
.append(.OP_DUP)
.append(.OP_HASH160)
- .appendData(sender.data)
+ .appendData(sender.lockingScriptPayload)
.append(.OP_ENDIF)
.append(.OP_EQUALVERIFY)
.append(.OP_CHECKSIG)
diff --git a/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptType.swift b/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptType.swift
index 8483d1111..f93e3a850 100644
--- a/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptType.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Scripts/ScriptType.swift
@@ -8,7 +8,7 @@
import Foundation
public enum ScriptType: Int {
- case unknown, p2pkh, p2pk, p2multi, p2sh, p2wsh, p2wpkh, p2wpkhSh
+ case unknown, p2pkh, p2pk, p2multi, p2sh, p2wsh, p2wpkh, p2wpkhSh, p2tr
var size: Int {
switch self {
@@ -18,31 +18,12 @@ public enum ScriptType: Int {
case .p2wsh: return 34
case .p2wpkh: return 22
case .p2wpkhSh: return 23
- default: return 0
- }
- }
-
- var keyLength: UInt8 {
- switch self {
- case .p2pk: return 0x21
- case .p2pkh: return 0x14
- case .p2sh: return 0x14
- case .p2wsh: return 0x20
- case .p2wpkh: return 0x14
- case .p2wpkhSh: return 0x14
- default: return 0
- }
- }
-
- var addressType: AddressType {
- switch self {
- case .p2sh, .p2wsh: return .scriptHash
- default: return .pubkeyHash
+ case .p2tr: return 34
+ case .unknown, .p2multi: return .zero
}
}
var witness: Bool {
- return self == .p2wpkh || self == .p2wpkhSh || self == .p2wsh
+ self == .p2wpkh || self == .p2wpkhSh || self == .p2wsh || self == .p2tr
}
-
}
diff --git a/BitcoinKit/Sources/BitcoinKit/Wallet/HDWallet.swift b/BitcoinKit/Sources/BitcoinKit/Wallet/HDWallet.swift
index 1492a12a4..aa1acf837 100644
--- a/BitcoinKit/Sources/BitcoinKit/Wallet/HDWallet.swift
+++ b/BitcoinKit/Sources/BitcoinKit/Wallet/HDWallet.swift
@@ -42,6 +42,7 @@ public final class HDWallet {
private let seed: Data
private let keychain: HDKeychain
+ private let addressConverter: AddressConverter
private let purpose: UInt32
private let coinType: UInt32
@@ -85,6 +86,7 @@ public final class HDWallet {
// Public derivation is used at this level.
externalIndex = 0
internalIndex = 0
+ addressConverter = AddressConverterFactory().make(network: network)
}
// MARK: - External Addresses & Keys (Receive Addresses & Keys)
@@ -94,7 +96,7 @@ public final class HDWallet {
public func receiveAddress(index: UInt32) throws -> Address {
let key = try publicKey(index: index)
- return key.toCashaddr()
+ return try addressConverter.convert(publicKey: key, type: .p2pkh)
}
public func publicKey(index: UInt32) throws -> PublicKey {
@@ -121,7 +123,7 @@ public final class HDWallet {
public func changeAddress(index: UInt32) throws -> Address {
let key = try changePublicKey(index: index)
- return key.toCashaddr()
+ return try addressConverter.convert(publicKey: key, type: .p2pkh)
}
public func changePublicKey(index: UInt32) throws -> PublicKey {