美文网首页
copay钱包(5.助记词导出导入代码阅读)

copay钱包(5.助记词导出导入代码阅读)

作者: 沉寂之舟 | 来源:发表于2018-06-10 21:39 被阅读206次

    传送门
    copay钱包(1.windows环境编译运行)
    copay钱包(2.RestfulAPI初步分析)
    copay钱包(3.转账功能报文分析)
    copay钱包(4.bitcore-lib与bitcore-wallet-client类库修改)
    copay钱包(5.助记词导出导入代码阅读)


    导出入口点

    程序中,有4个地方可以导出助记词:

    1. 在最开始创建钱包时(BackupRequestPage)
    onboarding-backup.png
    1. 在主页查看交易明细中(WalletDetailsPage)
    txdetail-backup.png
    1. 在设置页面的钱包详情中(WalletSettingsPage)


      setting-backup.png
    2. 在接收到Bitcoin时(ReceivePage)

    其中,除了setting的备份外,其他的<div>都有用 *ngIf="wallet.needsBackup" 判断,只要备份过一次就不会再出现了.

    调用的形式如下:

    this.navCtrl.push(BackupWarningPage, { walletId: this.walletId, fromOnboarding: true });
    

    向BackupWarningPage传递钱包的id和是否从启动页进入(仅第1种为true).

    导出BackupGamePage处理流程

    导出流程的核心是BackupGamePage,Backup目录的其他页面都是提示性的,
    1.页面构造函数

        // 从navCtrl读取walletId参数 
        this.walletId = this.navParams.get('walletId');
        // 从navCtrl读取fromOnboarding参数 
        this.fromOnboarding = this.navParams.get('fromOnboarding');
        // 根据walletId,获取到当前需要备份的wallet对象-实际可用直接把wallet传进来,无需在获取一次.
        this.wallet = this.profileProvider.getWallet(this.walletId);
        // 判断该credential是否有加密过
        this.credentialsEncrypted = this.wallet.isPrivKeyEncrypted();
        // 判断是否手工清除过助记词?profile编辑?
        this.deleted = this.isDeletedSeed();
        if (this.deleted) {
          this.logger.debug('no mnemonics');
          return;
        }
        // 调用walletProvider服务获取到助记词列表(本身profile里面就有,但是这里还是用了异步方法.)
        this.walletProvider.getKeys(this.wallet).then((keys) => {
          if (_.isEmpty(keys)) {
            this.logger.error('Empty keys');
          }
          // 能获取到说明没有加密
          this.credentialsEncrypted = false;
          // keys包含2部分,助记词和privateKey
          this.keys = keys;
          // 流程控制.
          this.setFlow();
        }).catch((err) => {
          this.logger.error('Could not get keys: ', err);
          this.navCtrl.pop();
        });
    
    1. 流程控制函数
        // 流程控制函数 
        private setFlow(): void {
        if (!this.keys) return;
        // words为助记词
        let words = this.keys.mnemonic;
        // profile的助记词是用\u3000(unicode的空格)分割的.split后就变为数组了.
        this.mnemonicWords = words.split(/[\u3000\s]+/);
        // 打乱一下顺序.
        this.shuffledMnemonicWords = this.shuffledWords(this.mnemonicWords);
        // 始终为false
        this.mnemonicHasPassphrase = this.wallet.mnemonicHasPassphrase();
        this.useIdeograms = words.indexOf("\u3000") >= 0;
        this.password = '';
        this.customWords = [];
        this.selectComplete = false;
        this.error = false;
        // 把words擦掉,避免泄露
        words = _.repeat('x', 300);
        // 如果是第二页,就回退(说明输错了)
        if (this.currentIndex == 2) this.slidePrev();
      }
    
    1. 判断助记词游戏是否输入正确
        // 判断助记词游戏结果,返回值是一个promise
        private confirm(): Promise<any> {
        return new Promise((resolve, reject) => {
          this.error = false;
          // 把输入框的字配在一起.
          let customWordList = _.map(this.customWords, 'word');
          // 判断是否和记录的助记词一致,
          if (!_.isEqual(this.mnemonicWords, customWordList)) {
            // 调用reject()
            return reject('Mnemonic string mismatch');
          }
          // 把备份信息登记到profile中
          this.profileProvider.setBackupFlag(this.wallet.credentials.walletId);
          return resolve();
        });
      }
    

    如图这个备份信息并不是保存在credentials中,而是在profile的外面,根据backup-(钱包ID)键值保存.:


    backup-profile.png

    confirm()和setFlow()是由finalStep统一控制进行的,如果confirm成功,就转到BackupReadyModalPage完成备份;如果confirm不成功,就转到setFlow(),重新开始游戏.

    总的来说,copay的助记词导出,还是比较方便的,有适当的提示,然后还有一个随机校验去验证是否真的抄下来了,并且能有效的提示和记录导出数据的结果,确实值得借鉴的,

    导入入口点

    程序中,有2个地方可以导入助记词,使用已有钱包:

    1. 在最开始创建钱包时(此时还是更多导入形式,如多签钱包)
    onboarding-import.png
    1. 在主页钱包列表右上角
    home-import.png

    调用的形式如下:

    this.navCtrl.push(ImportWalletPage, { fromOnboarding: true });
    

    向BackupWarningPage是否从启动页进入(仅第1种为true).

    导入ImportWalletPage处理流程

    实际上,有words和file两张导入方式,但是导出并没有file呢,那这个应该是导入其他设备(如TREZOR)生成的文件.如果只是copay的使用,关注word方式即可.而代码中,因为需要兼容2种方式,增加了大量的判断分支,阅读起来就比较累了.

    1. 从助记词导入
        // 从助记词导入.
        public importFromMnemonic(): void {
        // 判断是否合法
        if (!this.importForm.valid) {
          let title = this.translate.instant('Error');
          let subtitle = this.translate.instant('There is an error in the form');
          this.popupProvider.ionicAlert(title, subtitle);
          return;
        }
    
        let opts: any = {};
        // 从页面中读取bwsURL信息,保存到opts中.
        if (this.importForm.value.bwsURL)
          opts.bwsurl = this.importForm.value.bwsURL;
        // 从页面中读取pathData信息,livenet为m/44'/0'/0',testnet为m/44'/1'/0',并调用derivationPathHelperProvider进行解析.
        let pathData: any = this.derivationPathHelperProvider.parse(this.importForm.value.derivationPath);
        // 判断解析后的衍生路径,其值是必须的,如果没有值,就报错
        if (!pathData) {
          let title = this.translate.instant('Error');
          let subtitle = this.translate.instant('Invalid derivation path');
          this.popupProvider.ionicAlert(title, subtitle);
          return;
        }
        // 从衍生路径中获取账号,网络,策略等信息
        opts.account = pathData.account;
        opts.networkName = pathData.networkName;
        opts.derivationStrategy = pathData.derivationStrategy;
        // 币种
        opts.coin = this.importForm.value.coin;
        // 解析输入的助记词
        let words: string = this.importForm.value.words || null;
    
        if (!words) {
          let title = this.translate.instant('Error');
          let subtitle = this.translate.instant('Please enter the recovery phrase');
          this.popupProvider.ionicAlert(title, subtitle);
          return;
          // 可以直接导入私钥xprv开头(livnet),tprv开头(testnet).
        } else if (words.indexOf('xprv') == 0 || words.indexOf('tprv') == 0) {
          return this.importExtendedPrivateKey(words, opts);
        } else {
          let wordList: any[] = words.split(/[\u3000\s]+/);
          // 初步判断一下是否长度符合.
          if ((wordList.length % 3) != 0) {
            let title = this.translate.instant('Error');
            let subtitle = this.translate.instant('Wrong number of recovery words:');
            this.popupProvider.ionicAlert(title, subtitle + ' ' + wordList.length);
            return;
          }
        }
    
        opts.passphrase = this.importForm.value.passphrase || null;
        // 再次调用importMnemonic完成导入
        this.importMnemonic(words, opts);
      }
    

    2.具体调入代码

        // 具体调入代码
        private importMnemonic(words: string, opts: any): void {
        // 显示等待框
        this.onGoingProcessProvider.set('importingWallet');
        // 用异步任务完成
        setTimeout(() => {
          // 实际是调用了profileProvider的importMnemonic()进行具体导入,其内部是通过walletClient客户端与bws服务通讯完成的导入.
          this.profileProvider.importMnemonic(words, opts).then((wallet: any) => {
            this.onGoingProcessProvider.clear();
            // 调用finish()函数
            this.finish(wallet);
          }).catch((err: any) => {
            // 捕捉异常状况
            if (err instanceof this.errors.NOT_AUTHORIZED) {
              this.importErr = true;
            } else {
              let title = this.translate.instant('Error');
              this.popupProvider.ionicAlert(title, err);
            }
            // 异常情况要清理等待框,
            this.onGoingProcessProvider.clear();
            return;
          });
        }, 100);
      }
    

    相关文章

      网友评论

          本文标题:copay钱包(5.助记词导出导入代码阅读)

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