AllIsHackedOff

Just a memo, just a progress

EthereumのTransactionのRLPエンコード / デコード

Ethereumでdappを作る際にトランザクションへの署名をよしなに実装する必要があるのだが、署名前と後でRLP形式のデータがどう変化しているか一応見てみることにした

利用した便利ツール

  1. RLPコマンド (npm install -g で グローバルに入れるとCLIが入る) www.npmjs.com
rlp decode 0x...
rlp encode [] # encodeしたい構造

こんな感じで使える。達人は目デコードできると思うが、普通の人間ならCLIを使ったほうが早いだろう

  1. RLP形式のトランザクションをJavaScirptのオブジェクトに直してくれる君 www.npmjs.com
const txDecoder = require('ethereum-tx-decoder');

decoded  = txDecoder.decodeTx("0xf88a808502540be4008401c9c38094a6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b80a460fe47b100000000000000000000000000000000000000000000000000000000000003e71ca0cec088f5e937f5cfa89eca38a71f768dd66974cafae1a4dc5572acd4e664e541a01ecbdc7f1332d99fb41d170587c2b6ea45c9e85e8c43ad8fa32a22157c3597a4")
console.log(decoded)

こんな感じで使える

署名前と署名後のトランザクション

署名にはethereum-tx を使うことにして、下記のように署名を生成する

const SimpleStorage = artifacts.require("./SimpleStorage.sol")
const EthereumTx = require('ethereumjs-tx')

# ローカル環境の秘密鍵です。あしからず...
const signerAddress = "4fb1e3cd8de0392b648ec044f75d9bc58b2d7a32"
const signerPrivateKey = Buffer.from('6116d8f7e9174f1e838aec3492ac57e9a1868d579ede4b4f6b0c757519f2591a', 'hex')

// この辺は適当...
const gasLimit = 30000000
const gasPriceGwei = '10'

module.exports = async function(callback) {
    try {
        var nonce = await web3.eth.getTransactionCount(signerAddress)

        let simpleStorage = new web3.eth.Contract(SimpleStorage.abi, SimpleStorage.address, {})
        var method = simpleStorage.methods.set(999)

        var rawTx = {
            nonce: web3.utils.toHex(nonce),
            from: signerAddress,
            gasPrice: web3.utils.toHex(web3.utils.toWei(gasPriceGwei, 'gwei')),
            gasLimit: web3.utils.toHex(gasLimit), 
            to: SimpleStorage.address,
            value: new web3.utils.BN(web3.utils.toWei('0', 'ether')),
            data: method.encodeABI(),
        }
        // トランザクションオブジェクト生成
        let tx = new EthereumTx(rawTx)
        // 署名前
        console.log('0x' + tx.serialize().toString('hex')
        tx.sign(signerPrivateKey);
        // 署名後
        console.log('0x' + tx.serialize().toString('hex')
    } catch () {
    }
}

で、得られたトランザクション(のRLPエンコード)をデコードしていく

署名前のトランザクション

// raw (RLP encoded tx)
0xf84a808502540be4008401c9c38094a6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b80a460fe47b100000000000000000000000000000000000000000000000000000000000003e71c8080
// rlp decode
[ '',
  '02540be400', 
  '01c9c380',
  'a6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b',
  '',
  '60fe47b100000000000000000000000000000000000000000000000000000000000003e7',
  '1c', // 16 + 12 = 28?
  '',
  '' ]
// decoded tx as json
{ nonce: 0,
  gasPrice: BigNumber { _bn: <BN: 2540be400> },
  gasLimit: BigNumber { _bn: <BN: 1c9c380> },
  to: '0xa6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b',
  value: BigNumber { _bn: <BN: 0> },
  data:
   '0x60fe47b100000000000000000000000000000000000000000000000000000000000003e7',
  v: 28,
  r: '0x',
  s: '0x' }

署名後のトランザクション

// tx (serialized)
0xf88a808502540be4008401c9c38094a6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b80a460fe47b100000000000000000000000000000000000000000000000000000000000003e71ca0cec088f5e937f5cfa89eca38a71f768dd66974cafae1a4dc5572acd4e664e541a01ecbdc7f1332d99fb41d170587c2b6ea45c9e85e8c43ad8fa32a22157c3597a4

// rlp decoded
[ '',
  '02540be400',
  '01c9c380',
  'a6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b',
  '',
  '60fe47b100000000000000000000000000000000000000000000000000000000000003e7',
  '1c',
  'cec088f5e937f5cfa89eca38a71f768dd66974cafae1a4dc5572acd4e664e541',
  '1ecbdc7f1332d99fb41d170587c2b6ea45c9e85e8c43ad8fa32a22157c3597a4' ]

// decoded rlp as JSON
{ nonce: 0,
  gasPrice: BigNumber { _bn: <BN: 2540be400> },
  gasLimit: BigNumber { _bn: <BN: 1c9c380> },
  to: '0xa6a1b8a7015fb40e7a4cb1f30ffce4456ba30a2b',
  value: BigNumber { _bn: <BN: 0> },
  data:
   '0x60fe47b100000000000000000000000000000000000000000000000000000000000003e7',
  v: 28,
  r:
   '0xcec088f5e937f5cfa89eca38a71f768dd66974cafae1a4dc5572acd4e664e541',
  s:
   '0x1ecbdc7f1332d99fb41d170587c2b6ea45c9e85e8c43ad8fa32a22157c3597a4' }

分かりきっていたことな気がするが、署名するとv,r,s (ecrecoverなどに使うパラメタ)が入る