import { AirGapWallet , AirGapWalletStatus } from '@airgap/coinlib-core' ; import { Injectable } from '@angular/core' ; import { BIP32Interface , fromSeed } from 'bip32' ; import { entropyToMnemonic , mnemonicToSeed } from 'bip39' ; import { MnemonicSecret } from '../../models/secret' ; import { isSecretMigrated , isWalletMigrated } from '../../utils/migration' ; import { ErrorCategory , handleErrorLocal } from '../error-handler/error-handler.service' ; import { NavigationService } from '../navigation/navigation.service' ; import { SecretsService } from '../secrets/secrets.service' ; @ Injectable ( { providedIn : 'root' } ) export class MigrationService { constructor ( private readonly secretsService , private readonly navigationService ) { } public async runSecretsMigration ( secrets ) { return new Promise ( ( resolve , reject ) => { if ( secrets . every ( isSecretMigrated ) ) { resolve ( ) ; } else { this . navigateToMigrationPage ( { secrets } ) . then ( resolve ) . catch ( reject ) ; } } ) ; } public async runWalletsMigration ( wallets ) { return new Promise ( ( resolve , reject ) => { if ( wallets . every ( isWalletMigrated ) ) { resolve ( ) ; } else { this . navigateToMigrationPage ( { wallets } ) . then ( resolve ) . catch ( reject ) ; } } ) ; } private async navigateToMigrationPage ( navigationData ) { return new Promise ( ( resolve , reject ) => { this . navigationService . routeWithState ( '/migration' , { ... navigationData , onSuccess : resolve , onError : reject } ) . catch ( ( error ) => { handleErrorLocal ( ErrorCategory . IONIC_NAVIGATION ) ( error ) ; reject ( error ) ; } ) ; } ) ; } public filterMigratedSecrets ( secrets ) { if ( secrets . every ( isSecretMigrated ) ) { return [ secrets , true ] ; } return [ secrets . filter ( isSecretMigrated ) , false ] ; } public filterMigratedWallets ( wallets ) { if ( wallets . every ( isWalletMigrated ) ) { return [ wallets , true ] ; } return [ wallets . filter ( isWalletMigrated ) , false ] ; } public deepFilterMigratedSecretsAndWallets ( secrets ) { if ( secrets . every ( isSecretMigrated ) ) { return [ secrets , true ] ; } return [ secrets . map ( ( secret ) => { if ( ! secret . fingerprint ) { return undefined ; } const [ migratedWallets ] = this . filterMigratedWallets ( secret . wallets ) ; if ( migratedWallets . length === 0 ) { return undefined ; } const newSecret = MnemonicSecret . init ( secret ) ; newSecret . wallets = migratedWallets ; return newSecret ; } ) . filter ( ( secret ) => secret !== undefined ) , false ] ; } public async migrateSecret ( secret , options = { } ) { const defaultOptions = { persist : false } ; const resolvedOptions = { ... defaultOptions , ... options } ; if ( secret . fingerprint === undefined ) { let mnemonic = resolvedOptions . mnemonic ; if ( mnemonic === undefined ) { const entropy = await this . secretsService . retrieveEntropyForSecret ( secret ) ; mnemonic = entropyToMnemonic ( entropy ) ; } const seed = await mnemonicToSeed ( mnemonic ) ; const bip32Node = fromSeed ( seed ) ; const fingerprint = bip32Node . fingerprint . toString ( 'hex' ) ; secret . fingerprint = fingerprint ; } if ( resolvedOptions . persist ) { await this . secretsService . addOrUpdateSecret ( secret , { setActive : false } ) ; } } public async migrateWallet ( wallet , options ) { const defaultOptions = { bip39Passphrase : '' , persist : false } ; const resolvedOptions = { ... defaultOptions , ... options } ; if ( wallet . masterFingerprint && ! resolvedOptions . persist ) { return ; } let mnemonic = resolvedOptions . mnemonic ; let secret ; if ( mnemonic === undefined || resolvedOptions . persist ) { secret = this . secretsService . findByPublicKey ( wallet . publicKey ) ; } if ( mnemonic === undefined && secret !== undefined ) { const entropy = await this . secretsService . retrieveEntropyForSecret ( secret ) ; mnemonic = entropyToMnemonic ( entropy ) ; } else if ( mnemonic === undefined ) { return ; } const publicKey = await wallet . protocol . getPublicKeyFromMnemonic ( mnemonic , wallet . derivationPath , resolvedOptions . bip39Passphrase ) ; if ( publicKey !== wallet . publicKey ) { throw new Error ( 'Invalid BIP-39 Passphrase' ) ; } const seed = await mnemonicToSeed ( mnemonic , resolvedOptions . bip39Passphrase ) ; const bip32Node = fromSeed ( seed ) ; const fingerprint = bip32Node . fingerprint . toString ( 'hex' ) ; wallet . masterFingerprint = fingerprint ; wallet . status = AirGapWalletStatus . ACTIVE ; if ( resolvedOptions . persist && secret !== undefined ) { await this . migrateSecret ( secret , { mnemonic : resolvedOptions . mnemonic , persist : resolvedOptions . persist } ) ; } } }