ブロックチェーンを利用する場合、スケーリング等の問題から何かしらオフチェーンで承認を行い、最後の結果をブロックチェーンに登録する。というものを実装したくなることが多々あります。
今回はEthereumでオフチェーンでのやり取りを保証するための署名検証のやり方についてまとめてみました。
Ethereum(=Solidity)での署名検証の概要
- web3.eth.signを用いてmessage hashの署名を生成する。
- Contractに上記の署名とその元となったmessage hashを送る
- Contractではecrecoverを使ってmessage hashと署名から署名に使われたアドレスを取得する
- 上記で取得できたaddressと検証対象のaddressが一致しているか確認する
という流れで署名検証が可能です。
サンプルコード
- Contractでの署名検証コード
pragma solidity ^0.4.18; contract MyVerify { function ecverify(bytes32 hash, bytes signature) public pure returns(address sig_address) { require(signature.length == 65); bytes32 r; bytes32 s; uint8 v; // The signature format is a compact form of: // {bytes32 r}{bytes32 s}{uint8 v} // Compact means, uint8 is not padded to 32 bytes. assembly { r := mload(add(signature, 32)) s := mload(add(signature, 64)) // Here we are loading the last 32 bytes, including 31 bytes of 's'. v := byte(0, mload(add(signature, 96))) } // Version of signature should be 27 or 28, but 0 and 1 are also possible if (v < 27) { v += 27; } bytes memory prefix = "\x19Ethereum Signed Message:\n32"; bytes32 prefixedHash = keccak256(prefix, hash); require(v == 27 || v == 28); sig_address = ecrecover(prefixedHash, v, r, s); // ecrecover returns zero on error require(sig_address != 0x0); } }
- ローカルでの署名作成してaddressが一致しているか確認するテスト
const MyVerify = artifacts.require("MyVerify.sol") contract('MyVerify', function (accounts) { it("verify test", async () => { const instance = await MyVerify.new() const account = accounts[0] const hash = web3.sha3("Schoolbus") const sig = web3.eth.sign(account, hash) const return_address = await instance.ecverify(hash, sig) assert.equal(return_address, account) }) });
まとめ
この検証の仕組みはRaiden Networkでももちろん利用されています。
署名を作成するときに、署名の元となったhashに対してprefexをつけて再度ハッシュをするというのはRPCで定義されています。
prefixは固定文字なので、ローカルでこのprefex付きhashを作ることも可能だとは思うのですが、なぜかweb.sha3で生成したときとSolidityでkeccak256した結果が一致しないです。。。なので、今の所、prefix付きhashはContract内で生成する必要があります。
# ローカルでもprefix付きhashを生成できたらまた記事にしようかな。
追記(2018/01/21)
ethereumjs-util#hashPersonalMessageを使うことでjavascript側でprefix付きのhashを作れました。抜粋コードを載せます。
const ethUtil = require('ethereumjs-util') // ~~ snip ~~ const personalHash = ethUtil.hashPersonalMessage(Buffer.from(hash.slice(2), "hex")) const return_address = await instance.ecverify('0x'+personalHash.toString("hex"), sig)
記事にするほどでもなかったので追記で。。。
参考
- JSON RPC · ethereum/wiki Wiki · GitHub
- solidity - ecrecover from Geth and web3.eth.sign - Ethereum Stack Exchange
- transactions - workflow on signing a string with private key, followed by signature verification with public key - Ethereum Stack Exchange
- どうしてECDSA署名から公開鍵が復元できるのか? - Develop with pleasure!