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

2018-06-10  本文已影响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);
  }
上一篇下一篇

猜你喜欢

热点阅读