以太坊ETH转账

本文介绍以太坊(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

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

推荐阅读更多精彩内容

  • 所有货币都需要一些方法来控制供应,并强制执行各种安全属性以防止作弊。在法定货币方面,像中央银行这样的组织控制货币供...
    Nutbox_Lab阅读 3,052评论 1 3
  • 一、快速术语检索 比特币地址:(例如:1DSrfJdB2AnWaFNgSbv3MZC2m74996JafV)由一串...
    不如假如阅读 15,855评论 4 88
  • 本文是对以太坊文档 Ethereum Frontier Guide 和 Ethereum Homestead 的整...
    趁风卷阅读 9,460评论 0 16
  • 常常看到听到某些人物励志演讲,或在高校站台,或在大会鼓吹,天花乱坠,说自己如何如何奋斗,如何如何经历坎坷,如何命悬...
    大陳子阅读 964评论 0 1
  • 早上刷脸打卡的时候,看见机器屏幕上去年录进去的那张脸。默默地想,要是哪天识别不出来了就好了。至少说明,要么我可以不...
    苏小白说阅读 338评论 5 6