使用以太坊地址登录网页

前端使用 web3.js 获取用户地址并进行签名。

后端使用 go-ethereum 库校验签名是否来自指定地址,大致校验流程:

  • 通过明文的HASH和签名数据算出公钥
  • 通过公钥还原出地址
  • 校验还原出来的地方是否与用户指定地址相同

前端:

<html>

<head>
    <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        let address = '';
        $(function () {
            $('body').on('click', '#verify', function () { // 点击 验证 按钮后的事件
                let nonce = "Hello,World!"; // 假如这里是从服务端获取到的随机字符串
                // let hexNonce = window.web3.utils.utf8ToHex(nonce);
                let nonceHash = web3.utils.sha3(nonce);
                console.log('对随机数据签名:', nonceHash, ",addr:", address);
                window.web3.eth.personal.sign(nonceHash, address, function (result, signature) {
                    console.log("result:", result, ",signature:", signature);
                    $('#verify_result').append('<p>验证签名:' + signature + '</p>');
                    //TODO: 将签名发送到服务端,服务端通过公钥验证签名是否正确
                });
            });

            if (!window.web3) {
                alert('沒有找到 web3 库,请安装钱包插件');
                return;
            }
            window.web3 = new Web3(window.web3.currentProvider);
            ethereum.request({ method: 'eth_requestAccounts' }).then((result) => {
                console.log('连接钱包得到的 result:', result);
                address = result[0];
                $('#address').html(address + ' <span id="verify">点击验证</span>');

            }).catch((error) => {
                console.log("error", error);
            });

        });

    </script>
</head>

<body>
    <div id="address"></div>
    <div id="verify_result"></div>
</body>

</html>

后端:

package main

import (
	"fmt"

	"github.com/ethereum/go-ethereum/accounts"

	"github.com/ethereum/go-ethereum/common"

	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/crypto"
)

func verifySig(from, sigHex, msg string) bool {
	msgHash := crypto.Keccak256Hash([]byte(msg)).Bytes()
	fromAddr := common.HexToAddress(from)
	sig := hexutil.MustDecode(sigHex)
	if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 {
		return false
	}
	sig[crypto.RecoveryIDOffset] -= 27
	pubKey, err := crypto.SigToPub(accounts.TextHash(msgHash), sig)
	if err != nil {
		fmt.Println("crypto.SigToPub() fail:", err)
		return false
	}
	recoveredAddr := crypto.PubkeyToAddress(*pubKey)
	fmt.Println("fromAddr:", fromAddr)
	fmt.Println("recoveredAddr:", recoveredAddr)
	return fromAddr == recoveredAddr
}