Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

IOS-8332 [Casper] make casper address service #4140

Open
wants to merge 22 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
25f5c98
IOS-8316 IOS-8332 Add basic implementation blockchain
skibinalexander Oct 23, 2024
afb5596
IOS-8332 Merge branch 'develop' into blockchains/casper/IOS-8332_make…
skibinalexander Oct 24, 2024
e8fd34a
IOS-8332 Make correct flow make address for casper
skibinalexander Oct 25, 2024
72c9526
IOS-8332 Merge branch 'develop' into blockchains/casper/IOS-8332_make…
skibinalexander Oct 29, 2024
03fb7c1
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Oct 29, 2024
33199a7
IOS-8332 Make merge with last develop
skibinalexander Oct 29, 2024
cbcc56a
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Oct 30, 2024
0971033
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Oct 30, 2024
ce01238
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Oct 30, 2024
59c1614
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Oct 31, 2024
f242469
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Nov 1, 2024
a26ee8f
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Nov 2, 2024
5d52f4a
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Nov 3, 2024
0290eb3
IOS-8332 Refactor casper address tests
skibinalexander Nov 4, 2024
b4f2706
IOS-8332 Remove non actual tests
skibinalexander Nov 4, 2024
b739d08
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Nov 5, 2024
4a17d69
IOS-8332 Completed changes for review
skibinalexander Nov 5, 2024
5afc743
IOS-8332 Refactor
skibinalexander Nov 5, 2024
1cc49a8
Merge branch 'develop' into blockchains/casper/IOS-8332_make_casper_a…
skibinalexander Nov 5, 2024
67f96e8
IOS-8332 Add casper curve as parameter
skibinalexander Nov 5, 2024
2069d47
IOS-8332 Fix tests
skibinalexander Nov 5, 2024
f596878
IOS-8332 Fix tests
skibinalexander Nov 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions BlockchainSdk/Blockchains/Casper/CasperAddressService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// CasperAddressService.swift
// BlockchainSdk
//
// Created by skibinalexander on 22.10.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation
import TangemSdk

public struct CasperAddressService {
// MARK: - Private Properties

private let curve: EllipticCurve
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не стал пока упаковывать в blockchain.casper(curve) так пользоваться будем все равно secp по итогу ресерча


// MARK: - Init

init(curve: EllipticCurve) {
self.curve = curve
}
}

// MARK: - AddressProvider

extension CasperAddressService: AddressProvider {
public func makeAddress(for publicKey: Wallet.PublicKey, with addressType: AddressType) throws -> Address {
guard let prefixAddresss = CasperConstants.getAddressPrefix(curve: curve) else {
throw Error.unsupportedAddressPrefix
}

let addressBytes = Data(hexString: prefixAddresss) + publicKey.blockchainKey
let address = try CasperAddressUtils().checksum(input: addressBytes)
return PlainAddress(value: address, publicKey: publicKey, type: addressType)
}
}

// MARK: - AddressValidator

extension CasperAddressService: AddressValidator {
public func validate(_ address: String) -> Bool {
let isCorrectEd25519Address = address.count == CasperConstants.lengthED25519 && address.hasPrefix(CasperConstants.prefixED25519)
let isCorrectSecp256k1Address = address.count == CasperConstants.lengthSECP256K1 && address.hasPrefix(CasperConstants.prefixSECP256K1)
Comment on lines +42 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А у нас разве нет какого-то стандартного механизма проверки валидности адресов?
Как работают все остальные сети, там же нет такой логики
Или у каспера что-то свое специфичное?

btw проверок только на префикс + длину мне кажется мало для того, чтобы считать адрес валидным
как минимум у secp есть всякие sec bit и т.д.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я тут не очень понял, сделать проверка префиска в самом начале, дальше проверка кейсов и дальше проверка чексуммы


guard isCorrectEd25519Address || isCorrectSecp256k1Address else {
return false
}

if address.isSameCase() {
return true
}

do {
return try address == CasperAddressUtils().checksum(input: Data(hexString: address))
} catch {
return false
}
}
}

// MARK: - Constants

extension CasperAddressService {
enum Error: LocalizedError {
case unsupportedAddressPrefix
}
}

// MARK: - Helpers

fileprivate extension String {
func isSameCase() -> Bool {
lowercased() == self || uppercased() == self
}
}
83 changes: 83 additions & 0 deletions BlockchainSdk/Blockchains/Casper/CasperAddressUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// CasperAddressUtils.swift
// BlockchainSdk
//
// Created by skibinalexander on 22.10.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

/*
https://github.com/casper-ecosystem/casper-js-sdk/blob/dev/src/lib/ChecksummedHex.ts
*/

struct CasperAddressUtils {
// Ed25519: encode([0x01]) + encode(<public key bytes>)
// or
// Secp256k1: encode([0x02]) + encode(<public key bytes>)
func checksum(input: Data) throws -> String {
let byteArray = input.bytes

guard byteArray.count > 2, let first = byteArray.first else {
throw Error.failedSizeInputChecksum
}

return try encode(input: [first]) + encode(input: Array(input.bytes[1 ..< input.count]))
}

// MARK: - Private Implementation

// Separate bytes inside ByteArray to nibbles
// E.g. [0x01, 0x55, 0xFF, ...] -> [0x00, 0x01, 0x50, 0x05, 0xF0, 0x0F, ...]
private func bytesToNibbles(bytes: [UInt8]) -> [UInt8] {
let result: [UInt8] = bytes.reduce(into: []) { partialResult, byte in
partialResult.append((byte & 0xFF) >> 4)
partialResult.append(byte & 0x0F)
}

return result
}

private func byteHash(bytes: [UInt8]) throws -> [UInt8] {
guard let hashData = Data(bytes).hashBlake2b(outputLength: 32) else {
throw Error.failedHashBlake2b
}
return hashData.bytes
}

private func encode(input: [UInt8]) throws -> String {
let inputNibbles = bytesToNibbles(bytes: input)
let hash = try byteHash(bytes: input)

// Separate bytes inside ByteArray to bits array
// E.g. [0x01, ...] -> [false, false, false, false, false, false, false, true, ...]
// E.g. [0xAA, ...] -> [true, false, true, false, true, false, true, false, ...]
let hashBits = hash.toBitArray().map { $0.boolValue }

var hashBitsValues = hashBits.makeIterator()

let result: String = inputNibbles.reduce(into: "") { partialResult, nibbleByte in
let char = String(format: "%X", nibbleByte)

if char.range(of: Constants.regexEncodeByte, options: .regularExpression) != nil, hashBitsValues.next() ?? false {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А какой смысл в этой регулярке?
В nibbleByte лежит байт, сконвертить в hex его можно любым hex экстеншеном

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А какой смысл в этой регулярке? В nibbleByte лежит байт, сконвертить в hex его можно любым hex экстеншеном

тут же не %2x а %x , реализация с официального кошелька как я понимаю

partialResult.append(char.uppercased())
} else {
partialResult.append(char.lowercased())
}
}

return result
}
}

extension CasperAddressUtils {
enum Constants {
static let regexEncodeByte = "^[a-zA-Z()]+"
}

enum Error: LocalizedError {
case failedSizeInputChecksum
case failedHashBlake2b
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// CasperExternalLinkProvider.swift
// BlockchainSdk
//
// Created by skibinalexander on 22.10.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

struct CasperExternalLinkProvider: ExternalLinkProvider {
var testnetFaucetURL: URL? {
URL(string: "https://testnet.cspr.live/tools/faucet")
}

private let isTestnet: Bool

private var baseExplorerUrl: String {
return isTestnet ? "https://testnet.cspr.live" : "https://cspr.live"
}

init(isTestnet: Bool) {
self.isTestnet = isTestnet
}

func url(address: String, contractAddress: String?) -> URL? {
return URL(string: "\(baseExplorerUrl)/account/\(address)")
}

func url(transaction hash: String) -> URL? {
return URL(string: "\(baseExplorerUrl)/deploy/\(hash)")
}
}
15 changes: 15 additions & 0 deletions BlockchainSdk/Blockchains/Casper/CasperWalletAssembly.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// CasperWalletAssembly.swift
// BlockchainSdk
//
// Created by skibinalexander on 22.10.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

struct CasperWalletAssembly: WalletManagerAssembly {
func make(with input: WalletManagerAssemblyInput) throws -> WalletManager {
CasperWalletManager(wallet: input.wallet)
}
}
35 changes: 35 additions & 0 deletions BlockchainSdk/Blockchains/Casper/CasperWalletManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// CasperWalletManager.swift
// BlockchainSdk
//
// Created by skibinalexander on 22.10.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation
import Combine

class CasperWalletManager: BaseManager, WalletManager {
var currentHost: String {
// TODO: - https://tangem.atlassian.net/browse/IOS-8316
""
}

var allowsFeeSelection: Bool {
false
}

override func update(completion: @escaping (Result<Void, any Error>) -> Void) {
// TODO: - https://tangem.atlassian.net/browse/IOS-8316
}

func getFee(amount: Amount, destination: String) -> AnyPublisher<[Fee], any Error> {
// TODO: - https://tangem.atlassian.net/browse/IOS-8316
return .anyFail(error: WalletError.empty)
}

func send(_ transaction: Transaction, signer: any TransactionSigner) -> AnyPublisher<TransactionSendResult, SendTxError> {
// TODO: - https://tangem.atlassian.net/browse/IOS-8316
return .anyFail(error: SendTxError(error: WalletError.empty))
}
}
32 changes: 32 additions & 0 deletions BlockchainSdk/Blockchains/Casper/Common/CasperConstants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// CasperConstants.swift
// BlockchainSdk
//
// Created by Alexander Skibin on 29.10.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation
import TangemSdk

enum CasperConstants {
// ED25519
static let prefixED25519 = "01"
static let lengthED25519 = 66

// SECP256K1
static let prefixSECP256K1 = "02"
static let lengthSECP256K1 = 68

static func getAddressPrefix(curve: EllipticCurve) -> String? {
switch curve {
case .ed25519, .ed25519_slip0010:
return CasperConstants.prefixED25519
case .secp256k1:
return CasperConstants.prefixSECP256K1
default:
// Any curves not supported or will be added in the future
return nil
}
}
}
4 changes: 4 additions & 0 deletions BlockchainSdk/Common/API/TestnetAPINodeInfoProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ struct TestnetAPINodeInfoProvider {
return [
.init(url: URL(string: "https://rpc.test.btcs.network")!),
]
case .casper:
return [
.init(url: URL(string: "https://testnet.phantom-rpc.com/rpc")!),
]
// TODO: Refactor in IOS-6639
case .bitcoin, .litecoin, .disChain, .rsk, .bitcoinCash, .binance, .cardano,
.xrp, .ducatus, .tezos, .dogecoin, .solana, .kusama, .dash, .gnosis,
Expand Down
3 changes: 2 additions & 1 deletion BlockchainSdk/Common/Address/AddressTypesConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ struct AddressTypesConfig {
.energyWebEVM,
.energyWebX,
.core,
.canxium:
.canxium,
.casper:
return [.default]
}
}
Expand Down
14 changes: 12 additions & 2 deletions BlockchainSdk/Common/Blockchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public indirect enum Blockchain: Equatable, Hashable {
case energyWebX(curve: EllipticCurve)
case core(testnet: Bool)
case canxium
case casper(testnet: Bool)

public var isTestnet: Bool {
switch self {
Expand Down Expand Up @@ -135,7 +136,8 @@ public indirect enum Blockchain: Equatable, Hashable {
.sei(let testnet),
.kaspa(let testnet),
.energyWebEVM(let testnet),
.core(let testnet):
.core(let testnet),
.casper(let testnet):
return testnet
case .litecoin,
.ducatus,
Expand Down Expand Up @@ -307,7 +309,7 @@ public indirect enum Blockchain: Equatable, Hashable {
return 6
case .stellar:
return 7
case .solana, .ton, .bittensor, .sui:
case .solana, .ton, .bittensor, .sui, .casper:
return 9
case .polkadot(_, let testnet):
return testnet ? 12 : 10
Expand Down Expand Up @@ -465,6 +467,8 @@ public indirect enum Blockchain: Equatable, Hashable {
return isTestnet ? "tCORE" : "CORE"
case .canxium:
return "CAU"
case .casper:
return "CSPR"
}
}

Expand Down Expand Up @@ -963,6 +967,7 @@ extension Blockchain: Codable {
case .energyWebX: return "energyWebX"
case .core: return "core"
case .canxium: return "canxium"
case .casper: return "casper-network"
}
}

Expand Down Expand Up @@ -1060,6 +1065,7 @@ extension Blockchain: Codable {
case "energyWebX": self = .energyWebX(curve: curve)
case "core": self = .core(testnet: isTestnet)
case "canxium": self = .canxium
case "casper-network": self = .casper(testnet: isTestnet)
default:
throw BlockchainSdkError.decodingFailed
}
Expand Down Expand Up @@ -1302,6 +1308,8 @@ private extension Blockchain {
}
case .canxium:
return "canxium"
case .casper:
return "casper-network"
}
}

Expand Down Expand Up @@ -1417,6 +1425,8 @@ extension Blockchain {
return SuiWalletAssembly()
case .filecoin:
return FilecoinWalletAssembly()
case .casper:
return CasperWalletAssembly()
}
}
}
2 changes: 2 additions & 0 deletions BlockchainSdk/Common/Blockchain/Blockchain+AllCases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public extension Blockchain {
case .energyWebX: break
case .core: break
case .canxium: break
case .casper: break
// READ BELOW:
//
// Did you get a compilation error here? If so, add your new blockchain to the array below
Expand Down Expand Up @@ -167,6 +168,7 @@ public extension Blockchain {
.energyWebX(curve: .ed25519_slip0010),
.core(testnet: false),
.canxium,
.casper(testnet: false),
]
}
}
2 changes: 2 additions & 0 deletions BlockchainSdk/Common/Derivations/DerivationConfigV1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ struct DerivationConfigV1: DerivationConfig {
return "m/44'/246'/0'/0'/0'"
case .core:
return "m/44'/1116'/0'/0/0"
case .casper:
return "m/44'/506'/0'/0/0"
}
}
}
Loading
Loading