以太坊轉賬離線簽名
首先是錢包本地將轉賬資訊進行離線簽名,然後通過以太坊JSON-RPC方法傳送到以太坊節點,其中以太坊和其他ERC20代幣的簽名是不同的.
轉賬
ETH轉賬簽名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * ETH 轉賬離線簽名 * @param to 轉入的錢包地址 * @param nonce 以太坊nonce * @param gasPrice gasPrice * @param gasLimit gasLimit * @param amount 轉賬的eth數量 * @param wallet 錢包物件 * @param password 密碼 * @return 簽名data */ public String signedEthTransactionData(String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, BigDecimal amount, HLWallet wallet, String password) throws Exception { // 把十進位制的轉換成ETH的Wei, 1ETH = 10^18 Wei BigDecimal amountInWei = Convert.toWei(amount.toString(), Convert.Unit.ETHER); RawTransaction rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, to, amountInWei.toBigInteger()); return signData(rawTransaction,wallet,password); } private String signData(RawTransaction rawTransaction, HLWallet wallet, String password) throws Exception { Credentials credentials = Credentials.create(LWallet.decrypt(password, wallet.walletFile)); byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.MAINNET, credentials); return Numeric.toHexString(signMessage); } |
nonce
為了防止交易的重播攻擊,每筆交易必須有一個nonce隨機數,針對每一個賬戶nonce都是從0開始,當nonce為0的交易處理完之後,才會處理nonce為1的交易,並依次加1的交易才會被處理.以下是nonce使用的幾條規則:
- 當nonce太小,交易會被直接拒絕。
- 當nonce太大,交易會一直處於佇列之中,這也就是導致我們上面描述的問題的原因;
- 當傳送一個比較大的nonce值,然後補齊開始nonce到那個值之間的nonce,那麼交易依舊可以被執行。
- 當交易處於queue中時停止geth客戶端,那麼交易queue中的交易會被清除掉。
ERC-20代幣轉賬簽名
ERC-20代幣與以太坊轉賬是不同的,需要走智慧合約.
以下是手動拼接的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public String signedContractTransactionData(String contractAddress, String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, BigDecimal amount, BigDecimal decimal, HLWallet wallet, String password) throws Exception { //因為每個代幣可以規定自己的小數位, 所以實際的轉賬值=數值 * 10^小數位 BigDecimal realValue = amount.multiply(decimal); //0xa9059cbb代表某個代幣的轉賬方法hex(transfer) + 對方的轉賬地址hex + 轉賬的值的hex String data = Params.Abi.transfer + // 0xa9059cbb Numeric.toHexStringNoPrefixZeroPadded(Numeric.toBigInt(to), 64) + Numeric.toHexStringNoPrefixZeroPadded(realValue.toBigInteger(), 64); RawTransaction rawTransaction = RawTransaction.createTransaction( nonce, gasPrice, gasLimit, contractAddress, data); return signData(rawTransaction, wallet, password); } |
此時我們需要將轉賬的指令資訊通過簽名時的data
傳送出去.代幣轉賬的data
組成部分有三個.
- 首先是固定頭
0xa9059cbb
- 然後是轉入錢包地址
- 再是轉賬的數量,以wei作為單位
這裡我們說明下,為什麼固定頭是0xa9059cbb
.是因為它是函式原型transfer(address,uint256)
的MethodId,相當於以太坊用來分辨指令的標識,具體轉換方法為
1 2 3 4 5 6 7 |
// transfer -> 0xa9059cbb String transfer = "transfer(address,uint256)"; byte[] bytes = transfer.getBytes(); byte[] bytes1 = org.web3j.crypto.Hash.sha3(bytes); String hex = Numeric.toHexString(bytes1, 0, 4, true); ShadowLog.i("transfer", hex); |
然後對於轉入地址及轉賬數量,我們是將其轉化為16進位制,然後左邊補0至64位.三個引數字串拼接在一起即為最後簽名時用到的data
. 那麼可能有人會問,是不是其他指令引數都是這麼拼接? 當然不是,這裡涉及到以太坊合約ABI,有興趣的童鞋可以看看.這裡因為轉入地址的型別address
和轉賬數量的型別uint256
都是靜態的,因此是上述的拼接方式,如果是動態的則不一樣了,具體可以參考ABI文件.
這裡我們提供另外一種web3j
既有的封裝實現,不用關心內部引數是如何拼接的.(推薦)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public String signContractTransaction(String contractAddress, String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, BigDecimal amount, BigDecimal decimal, HLWallet wallet, String password) throws IOException, CipherException { BigDecimal realValue = amount.multiply(decimal); Function function = new Function("transfer", Arrays.asList(new Address(to), new Uint256(realValue.toBigInteger())), Collections.emptyList()); String data = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction( nonce, gasPrice, gasLimit, contractAddress, data); return signData(rawTransaction, wallet, password); } |
傳送請求
ETH轉賬最終在以太坊瀏覽器上的轉賬結果 link ,可在頁面最下方檢視Input Data
1 |
0x3078 |
代幣轉賬最終在以太坊瀏覽器上的轉賬結果 link,Input Data為
1 2 3 4 5 |
Function: transfer(address _to, uint256 _value) MethodID: 0xa9059cbb [0]: 000000000000000000000000b7bb6c45800f4531cc1581637868373a06367b48 [1]: 000000000000000000000000000000000000000000000002d1a51c7e00500000 |
FAQ
Q: 為什麼呼叫sendRawTransaction介面後,轉賬記錄在以太坊瀏覽器或者其他錢包查詢不到?
A: 介面呼叫後會返回一個字串hash,我們通常稱為txHash
.此時是不知道結果的,因為擁堵或者其他原因以太坊不一定確認,因此查不到是正常的.在app中,一種做法是在傳送請求後,主動通知自己的中繼relay,在中繼relay中維護這個txHash
狀態,以處理中的狀態呈現給使用者.
Q:sendRawTransaction後接口報錯’invalid sender’
A: 這個報錯是以太坊環境錯誤導致的,程式碼上表現為chainId沒對應.
1 2 3 4 5 6 7 8 9 |
private String signData(byte chainId, RawTransaction rawTransaction, HDWallet wallet, String password) throws Exception { Credentials credentials; credentials = Credentials.create(Wallet.decrypt(password, wallet.getWalletFile())); byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials); return Numeric.toHexString(signMessage); } |
簽名需要制定環境chainId,若使用不帶chainId的方法,則預設是主網.
友鏈:
參考: