DQtech Column | Learn from DEX about PlatON Application Development – Android (V)

Author Dex_DQT

In this chapter we will build the import function for the Mnemonic Phrase/key/wallet file

Create the main page for importing

This page uses ATON’s SmartTabLayout, which was previously used to support ViewPager in the ATON project, but I modified it to support ViewPager2. Please refer to the SmartTabLayout class in the com.platon.aton.widge.table package.

Then create the ImportActivity class in the activity package, which inherits from the BaseActivity class. Also modify all other Activities to inherit from this class.
The code of the BaseActivity class is as follows:

:

abstract class BaseActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        /**
         * 把标题栏颜色设置未黑色
         */
        ViewUtil.SetWindow(window)
        ViewUtil.SetStatusBarColorToBlack(window)
        /**
         * 设置UI对象
         */
        setContentView(inflateUI( LayoutInflater.from(this)))
        initUI()
        initEvent()
    }
    /**
     * 创建UI对象
     */
    protected abstract fun inflateUI(inflater: LayoutInflater): View
    /**
     * 初始化UI
     */
    protected abstract fun initUI()
    /**
     * 初始化时间
     */
    protected abstract fun initEvent()
}

This class is an abstract class. In onCreate, set the text color of the status bar to black, and then provide the following three abstract methods:
1, inflateUI instantiate the UI object
2, initUI initialize the UI object
3, initEvent initialize the event

The code of ImportActivity is as follow:

class ImportActivity : BaseActivity() {
    private lateinit var binding: ActivityImportWalletBinding

    /**
     * 创建页面对象
     */
    override fun inflateUI(inflater: LayoutInflater): View {
        binding = ActivityImportWalletBinding.inflate(inflater, null, false)
        return binding.root
    }

    /**
     *
     */
    override fun initUI() {
        // 初始化tabBar
        val indicatorThickness = DensityUtil.DP2PX(this, 2.0f)
        binding.stbBar.setIndicatorThickness(indicatorThickness)
        binding.stbBar.setIndicatorCornerRadius((indicatorThickness / 2.0f))
        // 设置tabBar的TabView创建函数
        binding.stbBar.setCustomTabView { container, title -> getTableView(container, title) }
        /**
         * 这里支持三种导入方式: 助记词, 钱包文件, 私钥
         */
        val titleList = ArrayList<String>(3)
        with(titleList) {
            add(ResourceUtil.GetString(R.string.mnemonicPhrase))
            add(ResourceUtil.GetString(R.string.keystore))
            add(ResourceUtil.GetString(R.string.privateKey))
        }
        // 创建页面的Adapter
        binding.vpContent.adapter = ImportPageAdapter()
        binding.stbBar.setViewPager(binding.vpContent, titleList)
    }

    override fun initEvent() {}
    /**
     * 创建tabView
     */
    private fun getTableView(container: ViewGroup, title: String): View? {
        val inflater = LayoutInflater.from(this)
        val tabBinding = LayoutAppTabItem1Binding.inflate(inflater, container, false)
        tabBinding.ivIcon.visibility = View.GONE
        tabBinding.tvTitle.text = title
        tabBinding.tvTitle.setTextColor(
            ContextCompat.getColorStateList(
                this,
                R.color.color_app_tab_text2
            )
        )
        return tabBinding.root
    }
}

Digging supports three methods of importing wallets: helper words, wallet files, and private keys.
The pages corresponding to these three import methods are as follows: res/layout/page_import_mnemonic_phrase.xml,
page_import_keystore.xml,
page_import_private_key.xml.
Because ViewPager2 is based on the RecyclerView implementation, create the Adapter and ViewHolder.
Create com.digquant.adapter package, then create ImportPageAdapter class, the code is as follows:

class ImportPageAdapter : RecyclerView.Adapter<BaseViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        // 对应的创建出三个页面对象
        val inflater = LayoutInflater.from(parent.context)
        if (viewType == 0) {
            val binding = PageImportMnemonicPhraseBinding.inflate(inflater, parent, false)
            return ImportMnemonicPage(binding.root)
        } else if (viewType == 1) {
            val binding = PageImportKeystoreBinding.inflate(inflater, parent, false)
            return ImportKeystorePage(binding.root)
        } else if (viewType == 2) {
            val binding = PageImportPrivateKeyBinding.inflate(inflater, parent, false)
            return ImportPrivateKeyPage(binding.root)
        }
        throw RuntimeException("无法识别页面类型")
    }
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
        // 因为页面都是静态的,因此不需要动态渲染
    }
    /**
     * 返回position作为页面类型
     */
    override fun getItemViewType(position: Int): Int {
        return position
    }
    override fun getItemCount(): Int {
        // 三个页面
        return 3
    }
}

The page effect is as follows:

FDBF44FEBC3BBE2E3F3A49DC4E652BF0
CAF56F44F8C8D62320C2ADC61D493EA2

012899E8F514572E84E4C99FC008FDB5

The logic of Introducing themnemonic words

The logic for getting this class is basically the same as the logic for creating a wallet in the ImportMnemonicPage class of com.digquant.page. Because the code is relatively large, I will not post the code here. The main logic of the class is as follows:

1, The length of the wallet name is 1~20 characters
2, The password of the wallet must be more than or equal to 6 characters


Then add the ImportMnemonicWords method to com/digquant/service/WalletManager.kt with the following code:

    /**
     * 导入助记词
     */
    fun ImportMnemonicWords(name: String, password: String, mnemonicWords: List<String>): Boolean {
        // 1,把助记词组合成空格隔开的字符串
        val mnemonic = mnemonicWords.joinToString(" ")
        // 2.生成种子
        val seed = JZMnemonicUtil.generateSeed(mnemonic, null)
        // 3. 生成根Keystore root private key 树顶点的master key ;bip32
        val rootPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed)
        // 4. 由根Keystore生成 第一个HD 钱包
        val dh = DeterministicHierarchy(rootPrivateKey)
        // 5. 定义父路径 H则是加强
        val parentPath = HDUtils.parsePath(PATH)
        // 6. 由父路径,派生出第一个子Keystore "new ChildNumber(0)" 表示第一个(PATH)
        val child: DeterministicKey = dh.deriveChild(parentPath, true, true, ChildNumber(0))
        val ecKeyPair = ECKeyPair.create(child.privKeyBytes)
        // 7. 删除当前创建钱包的信息
        clearCreateWalletSession()
        // 8. 存储钱包
        return storePrivateKey(name, password, ecKeyPair)
    }

Also modify the GenerateWallet method to call this method, so that the code can be reused.

The page effect is as follows:

5854E2B12A065913BA6328CA6B72AFE5

The contents of the wallet file are as follows:

{
    "address":{
        "mainnet":"lat157tqrypwa77v0nx5f2t7pqkkgrpt7n5q8p0czd",
        "testnet":"lax157tqrypwa77v0nx5f2t7pqkkgrpt7n5qgyahvz"
    },
    "id":"c1b27553-b9ba-4a40-9347-d22ab365b19e",
    "version":3,
    "crypto":{
        "cipher":"aes-128-ctr",
        "cipherparams":{
            "iv":"1bad59628b8b3bd336be6880fd222b7c"
        },
        "ciphertext":"bb31c7dd97322821f478d8496006edd55f161ba85daa8cce9bb55f16af1c1f64",
        "kdf":"scrypt",
        "kdfparams":{
            "dklen":32,
            "n":16384,
            "p":1,
            "r":8,
            "salt":"ee8e6b8d43f19ee0dfc36c799eff20af8eb480a40a963bf85d32364174df7b09"
        },
        "mac":"0536d5acaecd64e566bd90b92660d0883abea7c9b74474d708d8ccd0bd3a2f0e"
    }
}

Logic for importing wallet files

The specific code is in the com.digquant.page.ImportKeystorePage class. To import a wallet file you need to determine if the password can unlock the wallet file, so the code for ImportKeyStore in the WalletManager class is as follows:

    /**
     * 导入钱包文件
     */
    fun ImportKeyStore(
        name: String,
        password: String,
        keystore: String
    ): Status {

        try {
            val context = DiggingApplication.context

            val objectMapper = ObjectMapper()
            val walletFile: WalletFile = objectMapper.readValue(
                keystore,
                WalletFile::class.java
            )
            // 使用密码解锁钱包文件, 如果密码不正确会抛出CipherException异常
            Credentials.create(Wallet.decrypt(password, walletFile))

            FileUtil.WriteStringToFile(
                context.filesDir,
                "$WalletStorePath/${name}.json",
                keystore
            )
            return Status(0, "")

        } catch (e: CipherException) {
            return Status(1, "密码错误")
        } catch (e: Exception) {
            return Status(1, "导入失败!")
        }
    }

Use Credentials.create to try to unlock the wallet file with the entered password, if the entered password is incorrect, a CipherException will be thrown, as follows:

J{S3%{CZG}16@`YSV}{8S}1

If the password entered is correct, the import is successful..

Logic for importing secret keys

The specific code is in the com.digquant.page.ImportPrivateKeyPage class, which has the following main logic:

1, The length of the wallet name is 1~20 characters
2, The password of the wallet must be greater than or equal to 6 characters
3, Determine if the secret key is empty


Then add ImportPrivateKey to the WalletManager class, with the following code:

    /**
     * 导入密码
     */
    fun ImportPrivateKey(
        name: String,
        password: String,
        privateKey: String
    ): Boolean {

        val ecKeyPair =
            ECKeyPair.create(Numeric.toBigIntNoPrefix(Numeric.cleanHexPrefix(privateKey)))
        return storePrivateKey(name, password, ecKeyPair)
    }

Here remember to call Numeric.cleanHexPrefix method to take out the secret key starting with 0x, because some wallets export the secret key starting with 0x.

Well, that’s it for this chapter, next chapter we will start the build of main page.

The URL of github: https://github.com/DQTechnology/Platon_DevGuideProject

This article is reproduced from https://forum.latticex.foundation/t/topic/5967

Like (0)
Previous February 25, 2022 11:07
Next February 25, 2022 17:16

相关推荐

Leave a Reply

Please Login to Comment