本文介紹以太坊(Ethereum)的轉(zhuǎn)賬,依據(jù)web3j庫(kù)實(shí)現(xiàn)。
概念介紹
DSA一種公開密鑰算法团秽,它不能用作加密,只用作數(shù)字簽名钾腺。參考
ECDSA橢圓曲線數(shù)字簽名算法(ECDSA)是使用橢圓曲線密碼(ECC)對(duì)數(shù)字簽名算法(DSA)的加密徙垫。生成的r、s簽名值參考
一放棒、解碼錢包
也就是是根據(jù)用戶密碼從錢包讀出keystore信息姻报。這基本就是錢包生成的逆向流程。
1.將用戶密碼根據(jù)scrypt算法重新生成derivedKey.如下圖紅框所示间螟,跟create()相互對(duì)照
2.根據(jù)derivedKey調(diào)用performCipherOperation()解密方法得到私鑰吴旋。如下圖藍(lán)框所示,跟create()相互對(duì)照
3.將私鑰傳給ECKeyPair::create()便可重新得到公鑰厢破。具體調(diào)用Sign::publicKeyFromPrivate():BigInteger
感興趣的可以追進(jìn)去看看荣瑟。
4.根據(jù)ECKeyPair生成Credentials類,這個(gè)類主要包含ECKeyPair和錢包地址摩泪。
這個(gè)地方需要注意的是笆焰,錢包地址是重新根據(jù)公鑰生成的,而不是從文件里讀取出來见坑。
大伙想一下這樣做有什么好處嚷掠?(安全唄,這不是p話么荞驴,放文件里被篡改了咋辦不皆。)
具體代碼
//Wallet.java內(nèi)解碼錢包
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加密的相關(guān)參數(shù),并解碼用戶密碼熊楼。
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);
//根據(jù)用戶密碼生成的encryptKey解碼cipherText獲得私鑰
byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText);
return ECKeyPair.create(privateKey);
}
//Credentials.java內(nèi)霹娄,根據(jù)ECKeyPair參數(shù)重新獲取地址并保存到當(dāng)前類內(nèi)。
public static Credentials create(ECKeyPair ecKeyPair) {
String address = Numeric.prependHexPrefix(Keys.getAddress(ecKeyPair));
return new Credentials(ecKeyPair, address);
}
經(jīng)過上面的步驟我們便解碼了錢包,后面就可以根據(jù)這些信息執(zhí)行轉(zhuǎn)賬功能了犬耻。
二踩晶、獲取Nonce
nonce:整數(shù)類型,會(huì)隨著賬戶下的交易不斷累加枕磁。作用是“防止交易的重播攻擊”合瓢。
我們通過調(diào)用ethscan的相關(guān)接口查詢到上次交易的nonce值,此值從0開始透典。
三晴楔、代碼處理
開始之前先介紹一個(gè)r、s峭咒、v的概念税弃,其中r、s便是ECDSA簽名值凑队。v是chainid.
1.根據(jù)nonce以及gasPrice则果、gasLimit等初始化RawTransaction類
也就是交易描述文件。
RawTransaction.createTransaction()
2.根據(jù)描述文件生成byte文件漩氨。TransactionEncoder.signMessage()
此文件為在網(wǎng)絡(luò)上傳輸?shù)奈募髯场4瞬襟E會(huì)根據(jù)ECDSA進(jìn)行數(shù)字簽名以及加密。
3.調(diào)用api?action=eth_sendRawTransaction將描述文件發(fā)送到相關(guān)服務(wù)器叫惊。
4.服務(wù)器將此文件廣播到ETH公鏈款青。
接口調(diào)用代碼,具體見Github內(nèi)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())
}
}