Skip to content

Commit

Permalink
♻️ Refactor Address
Browse files Browse the repository at this point in the history
- Braek down address related files into smaller files
- Introduce Address struct instead of protocol
- Add BitcoinScheme
- Add Address.VersionByte
- Add Address.HashSize
- Add Address.HashType (AddressType is renamed)
- Deprecate Cashaddr, LegacyAddress
- Add documentation for Address, Cashaddr, LegacyAddress
- Rename Network.mainnet to Network.mainnetBCH
- Rename Network.testnetBCH to Network.testnetBCH
- Modify Address.cashaddr and .legacy to be computed properties
  • Loading branch information
usatie committed Sep 21, 2019
1 parent ed4e43f commit f7adda2
Show file tree
Hide file tree
Showing 47 changed files with 979 additions and 501 deletions.
70 changes: 57 additions & 13 deletions BitcoinKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

195 changes: 19 additions & 176 deletions Sources/BitcoinKit/Core/Address/Address.swift
@@ -1,207 +1,50 @@
// //
// Address.swift // Address.swift
// //
// Copyright © 2018 Kishikawa Katsumi // Copyright © 2019 BitcoinKit developers
// Copyright © 2018 BitcoinKit developers //
//
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights // in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is // copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: // furnished to do so, subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in // The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software. // all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
// //


import Foundation import Foundation


public protocol AddressProtocol { public protocol Address: CustomStringConvertible {
var network: Network { get } var network: Network { get }
var type: AddressType { get } var hashType: BitcoinAddress.HashType { get }
var data: Data { get } var data: Data { get }
var publicKey: Data? { get } var legacy: String { get }

var base58: String { get }
var cashaddr: String { get } var cashaddr: String { get }
} }


#if os(iOS) || os(tvOS) || os(watchOS) extension Address {
public typealias Address = AddressProtocol & QRCodeConvertible @available(*, deprecated, message: "Always returns nil. If you need public key with address, please use PublicKey instead.")
#else public var publicKey: Data? {
public typealias Address = AddressProtocol return nil
#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: String
public let cashaddr: String
public let publicKey: Data?

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: String) throws { @available(*, deprecated, renamed: "legacy")
guard let pubKeyHash = Base58Check.decode(base58) else { public var base58: String {
throw AddressError.invalid return legacy
}

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(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 = Base58Check.encode(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 = Base58Check.encode([network.pubkeyhash] + data)
case .scriptHash:
type = .scriptHash
base58 = Base58Check.encode([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 = Base58Check.encode(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 { @available(*, deprecated, renamed: "hashType")
public var description: String { public var type: BitcoinAddress.HashType {
return cashaddr return hashType
} }
} }
32 changes: 32 additions & 0 deletions Sources/BitcoinKit/Core/Address/AddressError.swift
@@ -0,0 +1,32 @@
//
// AddressError.swift
//
// Copyright © 2019 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 enum AddressError: Error {
case invalid
case invalidScheme
case invalidVersionByte
case invalidDataSize
}
82 changes: 82 additions & 0 deletions Sources/BitcoinKit/Core/Address/BitcoinAddress+Cashaddr.swift
@@ -0,0 +1,82 @@
//
// BitcoinAddress+Cashaddr.swift
//
// Copyright © 2019 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

extension BitcoinAddress {
public var versionByte: VersionByte {
return VersionByte(hashType, hashSize)
}

/// Bech32 encoded bitcoincash address format
public var cashaddr: String {
let scheme: BitcoinScheme
switch network {
case .mainnetBCH: scheme = .bitcoincash
case .testnetBCH: scheme = .bchtest
case .mainnetBTC: scheme = .bitcoincash
case .testnetBTC: scheme = .bchtest
default:
fatalError("cashaddr is only supported in BitcoinCash network.")
}
return Bech32.encode([versionByte.rawValue] + data, prefix: scheme.rawValue)
}

/// Creates a new Cashaddr with the bech32 encoded address with scheme The network
/// will be .mainnetBTC or .testnetBTC. This initializer perform prefix validation,
/// bech32 decode, and hash size validation.
///
/// ```
/// let address = try BitcoinAddress(cashaddr: "bitcoincash:qpjdpjrm5zvp2al5u4uzmp36t9m0ll7gd525rss978")
/// ```
///
/// - Parameter bech32: Bech32 encoded String value to use as the source of the new
/// instance. It must come with scheme "bitcioncash:" or "bchtest:".
public init(cashaddr: String) throws {
guard let decoded = Bech32.decode(cashaddr) else {
throw AddressError.invalid
}
let payload = decoded.data

switch BitcoinScheme(scheme: decoded.prefix) {
case .some(.bitcoincash):
network = .mainnetBCH
case .some(.bchtest):
network = .testnetBCH
default:
throw AddressError.invalidScheme
}

guard let versionByte = VersionByte(payload[0]) else {
throw AddressError.invalidVersionByte
}
self.hashType = versionByte.hashType
self.hashSize = versionByte.hashSize

self.data = payload.dropFirst()
guard data.count == hashSize.sizeInBytes else {
throw AddressError.invalidVersionByte
}
}
}
@@ -1,5 +1,5 @@
// //
// AddressType.swift // BitcoinAddress+HashType.swift
// //
// Copyright © 2018 BitcoinKit developers // Copyright © 2018 BitcoinKit developers
// //
Expand All @@ -24,29 +24,9 @@


import Foundation import Foundation


public class AddressType { extension BitcoinAddress {
static let pubkeyHash: AddressType = PubkeyHash() public enum HashType: UInt8 {
static let scriptHash: AddressType = ScriptHash() case pubkeyHash = 0

case scriptHash = 8
var versionByte: UInt8 { return 0 }
var versionByte160: UInt8 { return versionByte + 0 }
var versionByte192: UInt8 { return versionByte + 1 }
var versionByte224: UInt8 { return versionByte + 2 }
var versionByte256: UInt8 { return versionByte + 3 }
var versionByte320: UInt8 { return versionByte + 4 }
var versionByte384: UInt8 { return versionByte + 5 }
var versionByte448: UInt8 { return versionByte + 6 }
var versionByte512: UInt8 { return versionByte + 7 }
}

extension AddressType: Equatable {
public static func == (lhs: AddressType, rhs: AddressType) -> Bool {
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 }
}

0 comments on commit f7adda2

Please sign in to comment.