ETH钱包创建

本文介绍以太坊(Ethereum)钱包的原理以及创建过程,依据web3j库实现。

名词解释:

对称加密对称加密算法也就是加密和解密用相同的密钥。这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。
非对称加密加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。
AES高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法。
PBKDF2(Password-Based Key Derivation Function)是一个用来导出密钥的函数,常用于生成加密的密码。
它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。

钱包创建

钱包创建需要一个非对称加密密钥对,以太坊选择的是椭圆曲线加密算法(ECC)中的Secp256k1(依据速度、安全性等参数)。
步骤:

  1. 根据Secp256k1生成256位公钥/密钥,然后编译成长度为64位的十六进制字符串。
  2. 生成公钥的keccak-256(一种HSA-3算法)哈希值。此时为256位二进制数字。
  3. 丢弃前面96位,即12字节。得到160位二机制数据,即20字节。
  4. 把20字节编译成十六进制的字符串。得到40字符的字符串,这就是账户地址。
    5.账户生成后被写成一个keystore文件,文件名就是钱包地址。

我们采用的是web3j库。流程图:


Eth_Wallet.png

直接上代码。

//Keys.java生成Secp256k1公钥/密钥对,并存储到ECKeyPair内
public static ECKeyPair createEcKeyPair(){
    KeyPair keyPair = createSecp256k1KeyPair();
    return ECKeyPair.create(keyPair);
}

//Keys.java根据Keccak算法加密公钥并截取最后160位
public static String getAddress(String publicKey) {
    String publicKeyNoPrefix = Numeric.toHexStringWithPrefixZeroPadded(publicKey, PUBLIC_KEY_LENGTH_IN_HEX))
    …
    //sha3即采用Keccak算法
    String hash = Hash.sha3(publicKeyNoPrefix);
    //取右方160bits,也就是40个字符的16进制
    return hash.substring(hash.length() - ADDRESS_LENGTH_IN_HEX);
}

//密码加密,具体采用的算法是“HmacSHA256”
private static byte[] generateDerivedScryptKey(
        byte[] password, byte[] salt, int n, int r, int p, int dkLen) throws CipherException {
    ...
    return SCrypt.scrypt(password, salt, n, r, p, dkLen);
    ...
}

public static WalletFile create(String password, ECKeyPair ecKeyPair, int n, int p)
        throws CipherException {
    //salt参数
    byte[] salt = generateRandomBytes(32);
    //将密码用scrypt算法加密并生成32位密码,此加密是为了防止用户输入的密码强度过低导致不安全
    byte[] derivedKey = generateDerivedScryptKey(
            password.getBytes(Charset.forName("UTF-8")), salt, n, R, p, 32);
    //截取加密后的密码前16位
    byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
    //AES加密时的16字节初始化向量
    byte[] iv = generateRandomBytes(16);
    //私钥转成Byte
    byte[] privateKeyBytes =
            Numeric.toBytesPadded(ecKeyPair.getPrivateKey(), Keys.PRIVATE_KEY_SIZE);
    //根据密码加密私钥
    byte[] cipherText = performCipherOperation(
                Cipher.ENCRYPT_MODE, iv, encryptKey, privateKeyBytes);
    //生成mac
    byte[] mac = generateMac(derivedKey, cipherText);
    return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p);
}

private static WalletFile createWalletFile(
        ECKeyPair ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac,
        int n, int p) {
    //根据公钥生成钱包地址
    WalletFile walletFile = new WalletFile();
    walletFile.setAddress(Keys.getAddress(ecKeyPair));
    ////AES加密相关参数,以及经过AES加密的密码
    WalletFile.Crypto crypto = new WalletFile.Crypto();
    crypto.setCipher(CIPHER);
    crypto.setCiphertext(Numeric.toHexStringNoPrefix(cipherText));
    walletFile.setCrypto(crypto);
    //AES加密时的16字节初始化向量
    WalletFile.CipherParams cipherParams = new WalletFile.CipherParams();
    cipherParams.setIv(Numeric.toHexStringNoPrefix(iv));
    crypto.setCipherparams(cipherParams);
    //scrypt加密的相关参数,此加密算法被用来进行‘密码’加密。
    crypto.setKdf(SCRYPT);
    WalletFile.ScryptKdfParams kdfParams = new WalletFile.ScryptKdfParams();
    kdfParams.setDklen(DKLEN);
    kdfParams.setN(n);
    kdfParams.setP(p);
    kdfParams.setR(R);
    kdfParams.setSalt(Numeric.toHexStringNoPrefix(salt));
    crypto.setKdfparams(kdfParams);
    //生成mac以及uuid,用来进行keystore信息验证
    crypto.setMac(Numeric.toHexStringNoPrefix(mac));
    walletFile.setCrypto(crypto);
    walletFile.setId(UUID.randomUUID().toString());
    walletFile.setVersion(CURRENT_VERSION);
    return walletFile;
}

到此,钱包生成,下面是生成的keystore文件内容

{
  "address": "445cb5c43d9bc8eb6c6e8297452f845c2facdee9",//钱包地址
  "id": "857b3356-9d9d-455c-ae10-980214f95aa2",//UUID
  "version": 3,//钱包版本
  "crypto": {//AES加密,被用来加密私钥
    "cipher": "aes-128-ctr", //AES加密用到的算法是aes-128-ctr
    "cipherparams": {
      "iv": "fc14dee2d8188e3545df3cc210fc7264"//AES加密时的16字节初始化向量,也就是32字符
    },
    "ciphertext": "5d52d969412224c07255c940ec1258f5baae5255b4b19a5fe587135225492c71",//私钥,根据'密码'(此密码已经进行了kdf加密)进行AES加密
    "kdf": "scrypt",//PBKDF2加密用到的算法scrypt,此加密被用来进行‘密码’加密。
    "kdfparams": {//第一层加密‘密码’时的salty以及n、p、R等参数
      "dklen": 32,
      "n": 262144,
      "p": 1,
      "r": 8,
      "salt": "6d1df0eeca58c086d34dd1a13d08f796919479e78bc38f2043f05ceb3395b1c8"
    },
    "mac": "6e4de06fd9468ed04592486cbf6150ed2155610512ccdf5d6b2e5ace97cab4ba" //将ciphertext和‘密码’再次进行加密的产物,用于验证信息。
  }
}

钱包创建相关代码(具体见Github内WalletGenService.kt)

class WalletGenService : IntentService("WalletGen Service") {

    private var builder: NotificationCompat.Builder? = null
    internal val mNotificationId = 152

    private var normalMode = true

    override fun onHandleIntent(intent: Intent?) {
        val password = intent!!.getStringExtra("PASSWORD")
        var privatekey = ""

        if (intent.hasExtra("PRIVATE_KEY")) {
            normalMode = false
            privatekey = intent.getStringExtra("PRIVATE_KEY")
        }

        sendNotification()
        try {
            val walletAddress: String
            if (normalMode) { // Create new key
                walletAddress = OwnWalletUtils.generateNewWalletFile(password, File(this.filesDir, ""), true)
            } else { // Private key passed
                val keys = ECKeyPair.create(Hex.decode(privatekey))
                walletAddress = OwnWalletUtils.generateWalletFile(password, keys, File(this.filesDir, ""), true)
            }

            WalletStorage.getInstance(this).add(FullWallet("0x" + walletAddress, walletAddress), this)
            AddressNameConverter.getInstance(this).put("0x" + walletAddress, "Wallet " + ("0x" + walletAddress).substring(0, 6), this)
            Settings.walletBeingGenerated = false

            finished("0x" + walletAddress)
        } catch (e: CipherException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: InvalidAlgorithmParameterException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: NoSuchProviderException) {
            e.printStackTrace()
        }

    }

    private fun sendNotification() {
        builder = NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_notification)
                .setColor(0x2d435c)
                .setTicker(if (normalMode) getString(R.string.notification_wallgen_title) else getString(R.string.notification_wallimp_title))
                .setContentTitle(this.resources.getString(if (normalMode) R.string.wallet_gen_service_title else R.string.wallet_gen_service_title_import))
                .setOngoing(true)
                .setProgress(0, 0, true)
                .setContentText(getString(R.string.notification_wallgen_maytake))
        val mNotifyMgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        mNotifyMgr.notify(mNotificationId, builder!!.build())
    }

    private fun finished(address: String) {
        builder!!
                .setContentTitle(if (normalMode) getString(R.string.notification_wallgen_finished) else getString(R.string.notification_wallimp_finished))
                .setLargeIcon(Blockies.createIcon(address.toLowerCase()))
                .setAutoCancel(true)
                .setLights(Color.CYAN, 3000, 3000)
                .setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI)
                .setProgress(100, 100, false)
                .setOngoing(false)
                .setAutoCancel(true)
                .setContentText(getString(R.string.notification_click_to_view))

        if (android.os.Build.VERSION.SDK_INT >= 18)
        // Android bug in 4.2, just disable it for everyone then...
            builder!!.setVibrate(longArrayOf(1000, 1000))

        val main = Intent(this, MainActivity::class.java)
        main.putExtra("STARTAT", 1)

        val contentIntent = PendingIntent.getActivity(this, 0,
                main, PendingIntent.FLAG_UPDATE_CURRENT)
        builder!!.setContentIntent(contentIntent)

        val mNotifyMgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        mNotifyMgr.notify(mNotificationId, builder!!.build())
    }


}

下一篇文章我会讲关于以太坊(Ethereum)转账的原理以及实现

Git地址:https://github.com/snailflying/ETHWallet

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 小尾 文/@卢于子 虽然已经过了几天,但我内心还是稀里哗啦的,原谅我找不到可以形容的词了。毕竟那天发生的事,还是颠...
    卢于子阅读 221评论 0 0
  • 今天看完了两部电影,宁浩的《疯狂的石头》,2006年的作品,之前一直听过这部电影,终于有机会看了,黑色幽默,最大的...
    行走ing阅读 209评论 0 0
  • 如果只是一缕风 又怎会惊醒零碎的梦 和我沉睡的心 如果只是一缕风 拂过你的发梢 又怎会羞红我的脸颊 如果只是一缕风...
    北屿丿阅读 437评论 3 12