検証用Contractの実装
解説は後述
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract VerifySignature {
/* 1. Get message hash to sign */
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
/* 2. Sign with message hash and */
/* 3. Sign message hash */
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
return keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
);
}
/* 4. Verify signature
_signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
_message = "coffee and donuts"
_signature = 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function verify(
address _signer,
string memory _message,
bytes memory _sig // Signature that was created using MetaMask
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_message); // Message hash
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); // Prefixed message hash
return recoverSigner(ethSignedMessageHash, _sig) == _signer; // Compare signrer addresses
}
function recoverSigner(
bytes32 _ethSignedMessageHash,
bytes memory _sig
) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_sig);
// ecrecover(<prefixed_message_hash>, r, s, v) returns signer that was used to sign the message
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) {
require(sig.length == 65, "invalid signature length");
assembly {
r := mload(add(sig, 32)) // first 32 bytes, after the length prefix
s := mload(add(sig, 64)) // second 32 bytes
v := byte(0, mload(add(sig, 96))) // final byte (first byte of the next 32 bytes)
}
}
}
"\\x19Ethereum Signed Message:\\n32"
ecrecover()
assembly{}, mload(), add()
署名と検証の流れ
Sign (署名)
- 署名に使用するメッセージ(string)の作成
- メッセージのハッシュ化
(getMessageHash()
) - MetaMaskでメッセージハッシュを署名
Verify (検証)
- メッセージからメッセージhashを作成
(getMessageHash()
,getEthSignedMessageHash()
) - signerをsignatureとhashから導き出す
(recoverSigner()
) - 2で導き出したsignerと検証対象のsignerの一致を調べる
MetaMaskを用いた署名
今回はJavaScript Consoleで実行
ethereum.enable()
const account = "<signer_address>" // your wallet address
const hash = "<message_hash>" // メッセージハッシュ
// ここでMetaMaskウィンドウが開く
ethereum.request({
method: "personal_sign",
params: [account, hash]
});
メタマスクで署名(Sign)を実行後、Promiseで署名(Signature)が返る。
Ethers.jsによる署名と検証も可能
// Sign
const sig = await signer.signMessage(msg)
// Verify
ethers.utils.verifyMessage(msg, sig)
参考