美文网首页
以太坊ETH转账

以太坊ETH转账

作者: 进击的小强 | 来源:发表于2018-03-18 20:51 被阅读2046次

    本文介绍以太坊(Ethereum)的转账,依据web3j库实现。

    概念介绍

    DSA一种公开密钥算法,它不能用作加密,只用作数字签名。参考
    ECDSA椭圆曲线数字签名算法(ECDSA)是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的加密。生成的r、s签名值参考

    一、解码钱包

    也就是是根据用户密码从钱包读出keystore信息。这基本就是钱包生成的逆向流程。
    1.将用户密码根据scrypt算法重新生成derivedKey.如下图红框所示,跟create()相互对照
    2.根据derivedKey调用performCipherOperation()解密方法得到私钥。如下图蓝框所示,跟create()相互对照
    3.将私钥传给ECKeyPair::create()便可重新得到公钥。具体调用Sign::publicKeyFromPrivate():BigInteger感兴趣的可以追进去看看。
    4.根据ECKeyPair生成Credentials类,这个类主要包含ECKeyPair和钱包地址。
    这个地方需要注意的是,钱包地址是重新根据公钥生成的,而不是从文件里读取出来。
    大伙想一下这样做有什么好处?(安全呗,这不是p话么,放文件里被篡改了咋办。)

    decrypt.jpg

    具体代码

    //Wallet.java内解码钱包
    public static ECKeyPair decrypt(String password, WalletFile walletFile)throws CipherException {
        validate(walletFile);
        WalletFile.Crypto crypto = walletFile.getCrypto();
        byte[] mac = Numeric.hexStringToByteArray(crypto.getMac());
        byte[] iv = Numeric.hexStringToByteArray(crypto.getCipherparams().getIv());
        byte[] cipherText = Numeric.hexStringToByteArray(crypto.getCiphertext());
        byte[] derivedKey;
        //获得scrypt加密的相关参数,并解码用户密码。
        WalletFile.KdfParams kdfParams = crypto.getKdfparams();
        if (kdfParams instanceof WalletFile.ScryptKdfParams) {
            WalletFile.ScryptKdfParams scryptKdfParams =
                    (WalletFile.ScryptKdfParams) crypto.getKdfparams();
            int dklen = scryptKdfParams.getDklen();
            int n = scryptKdfParams.getN();
            int p = scryptKdfParams.getP();
            int r = scryptKdfParams.getR();
            byte[] salt = Numeric.hexStringToByteArray(scryptKdfParams.getSalt());
            derivedKey = generateDerivedScryptKey(
                    password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, dklen);
        } else if (kdfParams instanceof WalletFile.Aes128CtrKdfParams) {
            WalletFile.Aes128CtrKdfParams aes128CtrKdfParams =
                    (WalletFile.Aes128CtrKdfParams) crypto.getKdfparams();
            int c = aes128CtrKdfParams.getC();
            String prf = aes128CtrKdfParams.getPrf();
            byte[] salt = Numeric.hexStringToByteArray(aes128CtrKdfParams.getSalt());
            derivedKey = generateAes128CtrDerivedKey(
                    password.getBytes(Charset.forName("UTF-8")), salt, c, prf);
        } else {
            throw new CipherException("Unable to deserialize params: " + crypto.getKdf());
        }
        byte[] derivedMac = generateMac(derivedKey, cipherText);
        if (!Arrays.equals(derivedMac, mac)) {
            throw new CipherException("Invalid password provided");
        }
        byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
        //根据用户密码生成的encryptKey解码cipherText获得私钥
        byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText);
        return ECKeyPair.create(privateKey);
    }
    
    //Credentials.java内,根据ECKeyPair参数重新获取地址并保存到当前类内。
    public static Credentials create(ECKeyPair ecKeyPair) {
        String address = Numeric.prependHexPrefix(Keys.getAddress(ecKeyPair));
        return new Credentials(ecKeyPair, address);
    }
    

    经过上面的步骤我们便解码了钱包,后面就可以根据这些信息执行转账功能了。

    二、获取Nonce

    nonce:整数类型,会随着账户下的交易不断累加。作用是“防止交易的重播攻击”。
    我们通过调用ethscan的相关接口查询到上次交易的nonce值,此值从0开始。

    三、代码处理

    开始之前先介绍一个r、s、v的概念,其中r、s便是ECDSA签名值。v是chainid.
    1.根据nonce以及gasPrice、gasLimit等初始化RawTransaction类
    也就是交易描述文件。
    RawTransaction.createTransaction()
    2.根据描述文件生成byte文件。TransactionEncoder.signMessage()
    此文件为在网络上传输的文件。此步骤会根据ECDSA进行数字签名以及加密。
    3.调用api?action=eth_sendRawTransaction将描述文件发送到相关服务器。
    4.服务器将此文件广播到ETH公链。
    接口调用代码,具体见Github内TransactionService.kt类

    class TransactionService : IntentService("Transaction Service") {
    
        private var builder: NotificationCompat.Builder? = null
        internal val mNotificationId = 153
    
        override fun onHandleIntent(intent: Intent?) {
            sendNotification()
            try {
                val fromAddress = intent!!.getStringExtra("FROM_ADDRESS")
                val toAddress = intent.getStringExtra("TO_ADDRESS")
                val amount = intent.getStringExtra("AMOUNT")
                val gas_price = intent.getStringExtra("GAS_PRICE")
                val gas_limit = intent.getStringExtra("GAS_LIMIT")
                val data = intent.getStringExtra("DATA")
                val password = intent.getStringExtra("PASSWORD")
    
                val keys = WalletStorage.getInstance(applicationContext).getFullWallet(applicationContext, password, fromAddress)
    
                EtherscanAPI.INSTANCE.getNonceForAddress(fromAddress)
                        .subscribe(
                                object : SingleObserver<NonceForAddress> {
                                    override fun onSuccess(t: NonceForAddress) {
                                        if (t.result.length < 2) return
    
                                        val nonce = BigInteger(t.result.substring(2), 16)
    
                                        val tx = RawTransaction.createTransaction(
                                                nonce,
                                                BigInteger(gas_price),
                                                BigInteger(gas_limit),
                                                toAddress,
                                                BigDecimal(amount).multiply(ExchangeCalculator.ONE_ETHER).toBigInteger(),
                                                data
                                        )
    
                                        Log.d("Aaron",
                                                "Nonce: " + tx.nonce + "\n" +
                                                        "gasPrice: " + tx.gasPrice + "\n" +
                                                        "gasLimit: " + tx.gasLimit + "\n" +
                                                        "To: " + tx.to + "\n" +
                                                        "Amount: " + tx.value + "\n" +
                                                        "Data: " + tx.data
                                        )
    
                                        val signed = TransactionEncoder.signMessage(tx, 1.toByte(), keys)
    
                                        forwardTX(signed)
                                    }
    
                                    override fun onSubscribe(d: Disposable) {
                                    }
    
                                    override fun onError(e: Throwable) {
                                        error("Can't connect to network, retry it later")
                                    }
    
                                }
                        )
    
            } catch (e: Exception) {
                error("Invalid Wallet Password!")
                e.printStackTrace()
            }
    
        }
    
        @Throws(IOException::class)
        private fun forwardTX(signed: ByteArray) {
    
            EtherscanAPI.INSTANCE.forwardTransaction("0x" + Hex.toHexString(signed))
                    .subscribe(
                            object : SingleObserver<ForwardTX> {
                                override fun onSuccess(t: ForwardTX) {
                                    if (!TextUtils.isEmpty(t.result)) {
                                        suc(t.result)
                                    } else {
                                        var errormsg = t.error.message
                                        if (errormsg.indexOf(".") > 0)
                                            errormsg = errormsg.substring(0, errormsg.indexOf("."))
                                        error(errormsg) // f.E Insufficient funds
                                    }
                                }
    
                                override fun onSubscribe(d: Disposable) {
                                }
    
                                override fun onError(e: Throwable) {
                                    error("Can't connect to network, retry it later")
                                }
    
    
                            }
                    )
        }
    
        private fun suc(hash: String) {
            builder!!
                    .setContentTitle(getString(R.string.notification_transfersuc))
                    .setProgress(100, 100, false)
                    .setOngoing(false)
                    .setAutoCancel(true)
                    .setContentText("")
    
            val main = Intent(this, MainActivity::class.java)
            main.putExtra("STARTAT", 2)
            main.putExtra("TXHASH", hash)
    
            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())
        }
    
        private fun error(err: String) {
            builder!!
                    .setContentTitle(getString(R.string.notification_transferfail))
                    .setProgress(100, 100, false)
                    .setOngoing(false)
                    .setAutoCancel(true)
                    .setContentText(err)
    
            val main = Intent(this, MainActivity::class.java)
            main.putExtra("STARTAT", 2)
    
            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())
        }
    
        private fun sendNotification() {
            builder = NotificationCompat.Builder(this)
                    .setSmallIcon(R.drawable.ic_notification)
                    .setColor(0x2d435c)
                    .setTicker(getString(R.string.notification_transferingticker))
                    .setContentTitle(getString(R.string.notification_transfering_title))
                    .setContentText(getString(R.string.notification_might_take_a_minute))
                    .setOngoing(true)
                    .setProgress(0, 0, true)
            val mNotifyMgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    
            mNotifyMgr.notify(mNotificationId, builder!!.build())
        }
    
    
    }
    

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

    相关文章

      网友评论

          本文标题:以太坊ETH转账

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