美文网首页Dapp开发区块链研习社区块链
iOS 以太坊钱包 Trust源码解析

iOS 以太坊钱包 Trust源码解析

作者: MasonFu | 来源:发表于2018-03-27 15:53 被阅读9390次

    从2017年下半年开始,区块链技术迅速火遍技术圈,投资圈,炒币圈...背景就不多说了,自行查阅。工作需要,近几个月一直在了解学习区块链相关的技术,也尝试着做出了一个基于以太坊的钱包APP,支持Token(ERC20)及DAPP。其实大部分技术基础都来源于github上的一些优秀的开源钱包代码。这篇文章就以 Trust Wallet 这个开源项目为例,描述一下典型的钱包应用都具备哪些功能和支撑的技术点,共同学习。

    PS.区块链技术涉及到的知识点很庞杂,一些如区块链、以太坊、智能合约、钱包等基础概念就不细说了,感兴趣的可自行查阅

    Trust Wallet

    在众多开源的虚拟币钱包项目里,Trust做的算是非常完善和稳定的,代码风格、架构设计,技术栈都很新颖,并且已经在国外的AppStore上架,对应的Android版本也已开源。下面是Trust iOS版本的一些截图,四个Tab页分别为钱包、交易、Token、DAPP浏览器

    1. DAPP浏览器:基于Web浏览器,提供其支持的若干个交易网站,如加密猫游戏等,支持web在移动端进行本地钱包交易的功能
    2. 交易:展示当前钱包地址在以太坊中产生的交易记录,包括Token的交易记录,同时可收发以太币
    3. 通证:展示当前钱包地址在以太坊中,以及发生过交易的Token(ERC20)的余额状态;以太坊Token的转账功能
    4. 设置:切换钱包地址和目标网络,即连接的结点信息,Trust提供了若干主网及测试网选项;钱包地址的管理,例如创建、导入、备份、切换等
    Trust

    一、钱包(Wallet)

    钱包应用的核心功能之一,就是地址的管理:生成,导入,导出和备份,以及余额查询等功能。在Trust中,主要由EtherKeystore模块封装了以上这些功能,结构如下:

    wallet

    以太坊,或者说区块链当中的唯一标识,就是地址Address,一个地址的产生,简单来说就是一对公钥私钥的产生,以太坊的交易地址为20-byte长的字节串,来源于私钥,通过SHA3 keccak256计算可得出一串40个字符长度的EIP55串,这个串就是我们经常看到的可对外公开的以太坊地址(例如0x5E9c27156a612a2D516C74c7a80af107856F8539

    钱包的私钥相当于账户的户名及密码,私钥的处理和备份需要相当谨慎,一般常用的地址导入及备份方式有keystore、private key、mnemonic三种,不同的钱包偏重不同。Trust开发者单独封装了一个TrustKeystore的库,库中使用了很多加密算法相关的库,例如使用Apple的Security库创建密钥对,使用TrezorCrypto开源库的散列算法、助记词等算法处理私钥等。

    Tip:如果在自己的项目中使用TrustKeystore,记得将其pod工程编译优化选项置为Fast,Whole Module Optimization [-O -whole-module-optimization],否则在密钥解析时会非常慢

    令牌(ERC20 Token)

    区块链的智能合约,可以理解为双方在区块链资产上交易转账时,触发执行的一段代码(合同),我们称它为智能合约。以太坊可以创建任何智能合约,这里的令牌就属于可以表示数字资产的智能合约,而这些数字资产被称为以太坊代币(Token)。Token有许多不同类型的标准,例如ERC20、ERC721、ERC223等。

    ERC-20标准是在2015年11月份推出的,它定义了通用的Token代币所应支持的协议和接口标准,使基于以太坊众多的Token可以规范化,方便统一接入和操作。一个Token的基础属性有以下几点:

    • total supply //发行总量
    • name // 代币名称
    • decimal // 数量精度
    • symbol // 单位

    Trust当中使用开源数据库Realm,对用户手动添加或者从交易记录中提取到的Token对象进行本地存储,如名称、合约地址、单位、精度,以及余额、价格等信息。 同时启动了Timer刷新Token的余额信息。

    余额

    以太坊余额与Token余额的查询分为两种途径:

    1. 以太坊:Trust中通过开源第三方库APIKit(网络请求封装库)以及JSONRPCKit(json RPC远程调用库)来进行以太坊节点的RPC接口调用。对应的余额查询接口为“eth_getBalance”。Trust调用的是Infura免费提供的以太坊主网及测试网节点。

    Infura,以太坊开发服务平台,由于自建以太坊结点需要花比较多的时间和空间来同步区块,我们可以基于Infura可以简单很多,它免费提供公开以太坊节点和测试节点以调用,去官网只需要提供email,注册后就可得到专属的API地址。

    1. Token: Trust中对Token的余额查询方式为web3.js,在本地Native层开启一个不可见的wkwebview并且load进来index.html中的JS代码(创建web3对象、provider),通过JavaScriptKit开源库与webview配合完成以太坊智能合约接口的调用。ERC20代币合约的标准余额接口为“balanceOf”

    web3.js是以太坊提供的一个Javascript库,它封装了以太坊的JSON RPC API,提供了一系列与区块链交互的Javascript对象和函数,包括查看网络状态,查看本地账户、查看交易和区块、发送交易、编译/部署智能合约、调用智能合约等,其中最重要的就是与智能合约交互的API。

    结构图

    token

    二、转账(Transfer)

    以太坊以及Token的转账流程相对比较复杂,主要涉及到gas费用的计算、合约调用,签名,提交交易信息。每笔交易首先需要调用“eth_estimateGas”接口获取本次交易的gas费用(用户可修改,理论上gas费越高,交易成交的速度就会越快),之后调用“eth_getTransactionCount”接口获取本次交易nonce值,通过钱包私钥对交易data进行签名,最后调用“eth_sendRawTransaction”接口发送交易信息,至此交易已经提交到以太坊结节,等待矿工执行

    需要注意的是,对于Token的转账,交易的Data属性值可以看作Contract ABI的填充,这里来说就是ERC 20合约的Transfer标准方法,方法填参和编码后通过私钥签名放入transaction结构里发送给以太坊节点,在矿工成功挖矿后才会促使以太坊节点解析并执行其中的合约代码,完成Token的转账

    transfer

    三、交易(Transaction)

    交易列表及明细是钱包应用不可或缺的一部分,由于以太坊API未提供交易列表的获取接口,Trust是通过第三方服务节点拉取的指定地址的交易列表,当然我们也可以通过etherscan.io平台的API进行交易列表信息的获取,经过简单的注册即可得到专属的API Key。

    etherscan.io是 2015 年推出的一个以太坊区块探索和分析的分布式智能合同平台, 由于区块链中的交易信息等数据都是公开透明的 , Etherscan如同探索以太坊的窗口, 用户可以使用其查看自己的交易详情以及以太坊中的任何信息,开发者也可以调用其开发的API接口。

    Trust将拉取到的交易信息通过Realm存储到本地数据库中,每次以分页形式拉取最新的交易信息,同时后台运行了一个刷新线程,通过“eth_getTransactionByHash”方法更新的交易状态

    Transactions

    四、分布式应用(DAPP)

    DAPP在移动APP中的实现,简单来说,就是通过webview注入JS代码,在native端响应请求。Trust在WKWebView初始化时在WKWebViewConfiguration中加入WKUserScript自定义的JS代码以及JS代码中若干的响应方法(例如SignTransaction),native代码通过WKScriptMessageHandler协议响应网页中JS的调用,即完成了网页中点击购买,本地native代码完成支付的整个流程。

    DAPP

    开源库

    在Trust Wallet这个开源的纯Swift的项目里,用到了20多个开源三方库,APP的组织架构为MVVM,内部以Coordinator为单位进行模块之间的协作和调用,下面是我从Trust内使用的开源库中抽出比较好玩的几个项目介绍下,其实每个库都可以写一篇文章了,我只简单介绍下,深入的话请自行查阅

    APIKit

    一个轻量的类型安全的网络请求库,两个主要特点,其一是简便快速的调用方式,其二是Request/Response的类型关联。以github的https://api.github.com/rate_limitAPI接口为例,使用APIKit调用的大致流程如下:

    1. 定义Request协议基类、根url访问地址

       protocol GitHubRequest: Request {
       }
           
       extension GitHubRequest {
           var baseURL: URL {
               return URL(string: "https://api.github.com")!
           }
       }
      
    2. 定义返回类型的Model 对象

       struct RateLimit {
           let count: Int
           let resetDate: Date
       
           init?(dictionary: [String: AnyObject]) {
               guard let count = dictionary["rate"]?["limit"] as? Int else {
                   return nil
               }
       
               guard let resetDateString = dictionary["rate"]?["reset"] as? TimeInterval else {
                   return nil
               }
       
               self.count = count
               self.resetDate = Date(timeIntervalSince1970: resetDateString)
           }
      

      }

    3. 定义具体的GetRateLimitRequest对象,实现Request协议要求的方法,例如具体的path、method、response从JSON到Model的转换等:

       struct GetRateLimitRequest: GitHubRequest {
           typealias Response = RateLimit
       
           var method: HTTPMethod {
               return .get
           }
       
           var path: String {
               return "/rate_limit"
           }
       
           func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
               guard let dictionary = object as? [String: AnyObject],
                     let rateLimit = RateLimit(dictionary: dictionary) else {
                   throw ResponseError.unexpectedObject(object)
               }
       
               return rateLimit
           }
       }
      
    4. 发送请求

       let request = GetRateLimitRequest()
       Session.send(request) { result in
           switch result {
           case .success(let rateLimit):
               print("count: \(rateLimit.count)")
               print("reset: \(rateLimit.resetDate)")
       
           case .failure(let error):
               print("error: \(error)")
           }
      

      }

    是不是so easy?

    Moya

    Moya与APIKit有着类似的设计思想,都是将网络层相关的对象、参数或者数据抽象化,这样使得网络层结构更加清晰、接口之间更独立和规范,使用起来非常简单。但Moya要比APIKit更强大,它是基于Alamofire库在网络层上的完全封装,开发在应用层可以只单独依赖和调用Moya网络层即可。最大的特点是基于Moya可以很容易的构建出服务器接口组(API Service),独立性更高,方便维护、测试和移植。下面是官方文档给出的结构示意图:

    Moya

    再以github的https://api.github.com/rate_limitAPI接口为例,使用Moya调用的大致流程如下:

    1. 定义Service及其提供的所有接口

       enum GithubService {
           case rateLimit
       }
      
    2. 实现TargetType协议,明确API的调用path、parameters、method等

       // MARK: - TargetType Protocol Implementation
       extension GithubService: TargetType {
           var baseURL: URL { return URL(string: "https://api.github.com")! }
           var path: String {
               switch self {
               case . rateLimit:
                   return "/rate_limit"
               }
           }
           var method: Moya.Method {
               switch self {
               case .rateLimit:
                   return .get
               }
           }
           var task: Task {
               switch self {
               case . rateLimit: // Send no parameters
                   return .requestPlain
               }
           }
           var sampleData: Data {
               return Data()
           }
           var headers: [String: String]? {
               return ["Content-type": "application/json"]
           }
       }
      
    3. 创建Service及具体的Request,发送

       let provider = MoyaProvider<GithubService>()
       provider.request(.rateLimit) { result in
           switch result {
           case let .success(moyaResponse):
               let data = moyaResponse.data
               let statusCode = moyaResponse.statusCode
               // do something with the response data or statusCode
           case let .failure(error):
           }
       }
      

    这里的provider就是抽象的Service接口组,可以按照项目的业务划分有一个或者多个,provider是更高层次的划分,APIKit只提供了一个全局的Session服务对象,在Request对象模型上进行了不同业务的划分,这是它们最大的区别之一。

    R.swift

    App开发项目中存在大量的资源文件或者对象,例如nib、storyboard、image、file、font、color、string等等。引用这些资源基本都是以字符串类型去载入,例如UIImage.init(named: "setting_icon"),如果对象名称改变或者输入有误都将无法正确载入资源,你只能万分小心的copy/paste这些资源名称。

    R.swift就是为解决这个问题而生,具有强类型关联、编译错误检查、自动代码填充的功能,即安全又方便。基本原理就是在Xcode每次build期间自动读取解析工程目录内引用的资源文件以及创建的资源文件(例如TableViewCell.nib),将这些资源以代码的形式封装在一个动态生成的R.generated.swift的文件中。例如我在Assets.xcassets中添加了一个settings_icon图标,编译后R.generated.swift中将自动得到下面的代码段:

      /// This `R.image` struct is generated, and contains static references to 1 images.
      struct image {
        /// Image `setting_icon`.
        static let setting_icon = Rswift.ImageResource(bundle: R.hostingBundle, name: "setting_icon")
        
        /// `UIImage(named: "setting_icon", bundle: ..., traitCollection: ...)`
        static func setting_icon(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? {
          return UIKit.UIImage(resource: R.image.setting_icon, compatibleWith: traitCollection)
        }
        
        fileprivate init() {}
      }
    

    这样,在我们需要使用资源时,不再是

    let icon = UIImage(named: "settings-icon")
    let font = UIFont(name: "San Francisco", size: 42)
    let color = UIColor(named: "indictator highlight")
    let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
    let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.current, "Arthur Dent")
    

    而是

    let icon = R.image.settingsIcon()
    let font = R.font.sanFrancisco(size: 42)
    let color = R.color.indicatorHighlight()
    let viewController = CustomViewController(nib: R.nib.customView)
    let string = R.string.localizable.welcomeWithName("Arthur Dent")
    

    相关文章

      网友评论

      • 风吹PP凉_7c9c:研究dapp浏览器的 加个好友 846966684 人多就建群
        林_柏显:大神.加了你的QQ
      • 廖丹_18be:关于token余额的查询有些疑问,Trust源码里貌似并没有用web3.js,跟踪代码发现走的还是RPC api的调用,eth_getBalance,求大神指点下
        廖丹_18be:@林_柏显 是的,难道认识?
        小朴同学:不用怀疑,就是走的RPC调用,他的源码已经封装了对合约函数的调用。作者研究是6月前,可能trust在这期间更新了。而且这种方式是以太坊官方推荐的
        林_柏显:你现在也在搞区块链了?
      • 8337ea5e8883:您好,看到您的文章质量非常高,很想邀请您成为虫洞社区的首批优质内容签约作者。虫洞社区是专业的区块链技术学习社区。虫洞社区鼓励内容生产者产生高质量内容,并给予合理的回报,也希望能帮助内容消费者获得高质量的区块链内容,并让数字货币投资者获得有价值的投资洞见。同时,虫洞社区已经积累了大量的区块链深度从业者,便于作者建立个人品牌。不知道是否方便加您微信细聊?
      • 梅某:有交流群么,有好些不明白的啊:sob:
        AdhereDamon:@梅某 加下我QQ 993757654! 谢谢 一起讨论下!
        梅某:@KWK 只是能运行了,还在研究中……
        AdhereDamon:你的解决了吗 ?
      • AdhereDamon:作者您好! 我现在编译也过了,一运行项目都是语法上的错误啊! 这是什么情况啊!
        牛大发了:我也是,你解决了吗?求教
      • Wtfg:Undefined symbols for architecture arm64:
        "_random32", referenced from:
        _generate_k_random in ecdsa.o
        _random_buffer in rand.o
        _random_uniform in rand.o
        _random_permute in rand.o
        ld: symbol(s) not found for architecture arm64
        clang: error: linker command failed with exit code 1 (use -v to see invocation)

        pod库已经安装完毕,但是报以上错误,请问楼主或者其他兄弟遇到这个情况吗?
        Wtfg:成功编译的兄弟们,麻烦分享下心得:smile: :smile:
        Wtfg:@无双3 先把这个库屏蔽下,其他库有引用他。
        无双3:你好,你pod库中这个“TrezorCrypto”是怎么安装上的啊
      • 七彩麒麟:楼主:有的版本号在 github 上没有。eg:podfile 里面1.7 ,github 上1.5,实际使用有影响么???
      • 农夫_三拳:楼主 我使用Trust 导出的Keystory 和 助记词 在导入imtoken 的时候,会生成不同的钱包地址, 你有尝试过 两个钱包相互导入的功能吗?
        06acbc880629:应该是m / purpose' / coin_type' / account' / change / address_index路径不一样,你看看imtoken和trust的标准
      • 1e52e2c4b4a5:最近在做Android WebView加载Fomo3D执行JS注入,实现Android Native代码支付的功能,是否有也在做这个功能的朋友呢?一起讨论
        风吹PP凉_7c9c:能加我qq吗 846966684
      • blus_lee:有没有技术讨论群吗?
      • blus_lee:下载的trust iOS的运行不了 少东西
        妖妖零幺幺:@blus_lee 我这边 # pod 'Fabric' # pod 'Crashlytics', '~> 3.10'这两个库需要翻墙。也可以禁掉不用。
        blus_lee:@天空之城_bdfa pod install 老是不成功呢,[!] Error installing QRCodeReaderViewController 本人菜鸟,跪求赐教。
        天空之城_bdfa:少了pod导入的三方库,要先用cocoapods下载库,且必须全部下载成功为止
      • bb9a8b42c97a:老哥请问trust如何调用智能合约?求翻牌,谢谢🤝
        廖丹_18be:请问调用智能合约研究明白了吗,现在看得懵逼阶段啊。。。貌似要做不少自己组装的事情,这里对RPC api封装的很有限呢,求加qq:271013527
      • Running__:最近正在搞数字钱包,看了这篇文章,学到蛮多,谢谢!有个问题需要请教,文中这段话有点不太明白:“需要注意的是,对于Token的转账,交易的Data属性值可以看作Contract ABI的填充,这里来说就是ERC 20合约的Transfer标准方法,方法填参和编码后通过私钥签名放入transaction结构里发送给以太坊节点,在矿工成功挖矿后才会促使以太坊节点解析并执行其中的合约代码,完成Token的转账”,方便结合Trust代码解析吗?万分感谢!
        yangye_2016:你好,我也在搞数字货币这块,能否加个好友一起探讨下
        a群:Tip:如果在自己的项目中使用TrustKeystore,记得将其pod工程编译优化选项置为Fast,Whole Module Optimization [-O -whole-module-optimization],否则在密钥解析时会非常慢 这个过程的确很慢,我把他放到项目里了 具体该怎么加快速度呢 费解求教
        bb9a8b42c97a:层主请问这块你弄明白了吗?真心求教,谢谢
      • 九龙:"记得将其pod工程编译优化选项置为Fast,Whole Module Optimization [-O -whole-module-optimization]"
        这个是怎么设置的求解答
        九龙:@a群 就是在 Build Setting -> Optimization Level
        a群:你有答案了吗 求教

      本文标题:iOS 以太坊钱包 Trust源码解析

      本文链接:https://www.haomeiwen.com/subject/vbkscftx.html