-
Notifications
You must be signed in to change notification settings - Fork 36
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
base: develop
Are you sure you want to change the base?
Changes from 19 commits
25f5c98
afb5596
e8fd34a
72c9526
03fb7c1
33199a7
cbcc56a
0971033
ce01238
59c1614
f242469
a26ee8f
5d52f4a
0290eb3
b4f2706
b739d08
4a17d69
5afc743
1cc49a8
67f96e8
2069d47
f596878
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
// 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А у нас разве нет какого-то стандартного механизма проверки валидности адресов? btw проверок только на префикс + длину мне кажется мало для того, чтобы считать адрес валидным There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А какой смысл в этой регулярке? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
тут же не %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)") | ||
} | ||
} |
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) | ||
} | ||
} |
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)) | ||
} | ||
} |
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 | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Не стал пока упаковывать в blockchain.casper(curve) так пользоваться будем все равно secp по итогу ресерча