Skip to content

Latest commit

 

History

History
156 lines (113 loc) · 5.15 KB

EthereumTransaction.md

File metadata and controls

156 lines (113 loc) · 5.15 KB

Ethereum transaction

一般交易由以下幾個部分組成

From
To
Amount
Fee
Note

Ethereum 的交易由以下幾個部份組成

nonce
From
To
Amount
gasPrice
gasUsed/gasLimit
Note

針對其中幾個部分進行說明:

nonce: prevent double spending[1: what is nonce] 可以透過API得到,也可以透過自己錢包的交易數量推得。

gasPrice:每單位 Gas 願意付出多少 ETH,一般使用 Gwei(1e9 to wei) 作為單位。gasPrice * gasUsed 即為此次交易的手續費 Fee。

gasUsed:此次交易使用的 gas 數量。

gasLimit: 你願意花費在交易上的最大數量的 Gas 單位。

進行ETH交易

  1. 取得 chainId, chainId是取決於Ethereum交易的網路
ropsten rinkeby mordor Eth mainnet Eth based mainnet
3 4 63 1 61
  1. 將上述 ETH 交易內容進行 rlp 編碼的格式排列
class Transaction{
    int nonce;
    BigInt gasPrice;
    int gasLimit;
    String to;
    BigInt amount;
    String note;
    
    Transaction(this.nonce, this.gasPrice, this.gasLimit, this.to, this.amount, this.note);
}

var tx = Transaction(0, 49 * 1e9 ,21000, '0x15b1a87b648384033fdca1b7656cb534b91ffe56', 0.002 * 1e18, 'This is a transaction on ropsten');

var v = chainId;
var r = BigInt.zero;
var s = BigInt.zero;

var rlpData = [nonce, gasPrice, gasLimit, to, amount, note, v, r, s];
  1. 將rlpData 使用rlpEncode function進行rlp 編碼,得到 encodeRlpData,再進行rlp 編碼之前會將 rlpData裡面的各元素根據其型別轉為Buffer,可參考 toBuffer function。
Uint8List toBuffer(dynamic data) {
  if (data is Uint8List) return data;

  if (data is String) {
    if (isHexString(data)) {
      return Uint8List.fromList(hex.decode(padToEven(stripHexPrefix(data))));
    } else {
      return Uint8List.fromList(utf8.encode(data));
    }
  } else if (data is int) {
    return Uint8List.fromList(intToBuffer(data));
  } else if (data is BigInt) {
    return Uint8List.fromList(encodeBigInt(data));
  } else if (data is List<int>) {
    return Uint8List.fromList(data);
  }

  throw TypeError();
}
  1. var hash = keccak256(encodeRlpData)
  2. [r,s,v] = ECDSA(hash)
  3. 最後將簽名後得到的r,s,v 替換上述的rlpData,再將進行rlp encode後得到的Buffer轉為HEX,加上前綴{0x}, 即為可用於Ethereum HEX。

RLP encode:

rlp 是 recursive length prefix 的縮寫,目的是用於對巢狀的陣列進行編碼[eth wiki](中文)RLP 編碼規則

規則一: 對於值在[0, 127]之間(因為基於ASCII標準表,所以超過128就處理不了啦)的單個字元,其編碼是其ASCII編碼。 比如,單個字元‘0’在ASCII標準表裡是48(十六進位制是0x30),所以在RLP這裡也是48。

規則二: 如果字串長度是0-55個位元組,那麼在前面加上(128+字串長度)作為字首。 比如,空字串編碼就是128,即128 = 128 + 0; “0”這個字串只有1個字元,編碼就是12948; “0123”在ASCII標準表是48495051,長度是4個位元組,字首為132,那麼RLP 轉換後就是13248495051。

規則三: 如果字串長度大於55, 那麼在前面加上(183+字串長度的二進位制編碼的長度)和(字串長度)做字首。(為什麼是183?因為在規則二里,最大就是183(128+55)。)

規則四: 如果列表中所有字串的總長度按規則1到3編碼後,小於等於55,那麼在前面加上(192+該長度)做字首。列表裡的字串編碼參照規則1到3(183和192之間差了9個位元組,9個位元組用來表示長度。

規則五: 如果列表中所有字串的總長度按規則1到3編碼後,超過55,那麼在前面加上(247+該長度的二進位制編碼的長度)和(該長度)做字首。列表裡的字串編碼參照規則1到3(為什麼是247?因為192+55=247)。

Uint8List encode(dynamic input) {
  if (input is List && !(input is Uint8List)) {
    final output = <Uint8List>[];
    for (var data in input) {
      output.add(encode(data));
    }

    final data = _concat(output);
    return _concat([encodeLength(data.length, 192), data]);
  } else {
    final data = toBuffer(input);
    // 對於值在[0x00,0x7f]範圍內的單個字節, 
    if (data.length == 1 && data[0] < 128) {
      return data;
    } else {
      return _concat([encodeLength(data.length, 128), data]);
    }
  }
}

Uint8List encodeLength(int length, int offset) {
  if (length < 56) {
    return Uint8List.fromList([length + offset]);
  } else {
    final String hexLen = _intToHex(length);
    final int lLength = hexLen.length ~/ 2;

    return _concat([
      Uint8List.fromList([offset + 55 + lLength]),
      Uint8List.fromList(hex.decode(hexLen))
    ]);
  }
}

Uint8List _concat(List<Uint8List> lists) {
  final list = <int>[];

  lists.forEach(list.addAll);

  return Uint8List.fromList(list);
}