Skip to content

Commit

Permalink
[trello.com/c/GCrmAWok] Added SegWit BTC addresses support
Browse files Browse the repository at this point in the history
  • Loading branch information
just-software-dev committed Jun 12, 2023
1 parent 9bfccdf commit afb15f9
Show file tree
Hide file tree
Showing 40 changed files with 1,017 additions and 849 deletions.
3 changes: 1 addition & 2 deletions Adamant/Models/DashTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions Adamant/SwinjectDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import Swinject
import BitcoinKit

// MARK: - Services
extension Container {
Expand Down Expand Up @@ -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)
}
}
11 changes: 6 additions & 5 deletions Adamant/Wallets/Bitcoin/BtcWallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

}
7 changes: 3 additions & 4 deletions Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -56,7 +55,7 @@ extension BtcWalletService: WalletServiceTwoStepSend {
toAddress: toAddress,
amount: rawAmount,
fee: fee,
changeAddress: changeAddress,
changeAddress: wallet.addressEntity,
utxos: utxos,
keys: [key]
)
Expand Down Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion Adamant/Wallets/Bitcoin/BtcWalletService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand Down
12 changes: 9 additions & 3 deletions Adamant/Wallets/Dash/DashWallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
)
}
}
12 changes: 9 additions & 3 deletions Adamant/Wallets/Dash/DashWalletService+Send.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
5 changes: 3 additions & 2 deletions Adamant/Wallets/Dash/DashWalletService+Transactions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
15 changes: 12 additions & 3 deletions Adamant/Wallets/Dash/DashWalletService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
}

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

Expand Down
19 changes: 13 additions & 6 deletions Adamant/Wallets/Doge/DogeWallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}
12 changes: 9 additions & 3 deletions Adamant/Wallets/Doge/DogeWalletService+Send.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
Expand Down
13 changes: 9 additions & 4 deletions Adamant/Wallets/Doge/DogeWalletService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
}

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

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

Expand Down
33 changes: 33 additions & 0 deletions BitcoinKit/Sources/BitcoinKit/Core/BitcoinError.swift
Original file line number Diff line number Diff line change
@@ -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: ". ")
}
}
}
Loading

0 comments on commit afb15f9

Please sign in to comment.