LoginSignup
1
0

More than 1 year has passed since last update.

署名の検証のスマートコントラクト実装

Last updated at Posted at 2023-06-14

検証用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 (署名)

  1. 署名に使用するメッセージ(string)の作成
  2. メッセージのハッシュ化
    (getMessageHash())
  3. MetaMaskでメッセージハッシュを署名

Verify (検証)

  1. メッセージからメッセージhashを作成
    (getMessageHash(), getEthSignedMessageHash())
  2. signerをsignatureとhashから導き出す
    (recoverSigner())
  3. 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)

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0