引言
相信做过一些dapp项目的小伙伴都知道,当dapp需要和中心化的业务系统交互时,怎么验证登录成了一个问题。要dapp输入登录账户密码就很奇怪,违背了设计初衷,不登录吧,中心化系统又没有安全可言。
故此需要一个特定的动作。只有当钱包持有人授权登录(连接钱包),前端js通过调用特定api加密得到算法,从而传到后台验证签名,从而达到登录效果。
前端js 加密代码(web.js)
// 这里一下没找到前端代码,从别人哪里拿过的-见谅 import {ethers,providers} from 'ethers'; class WalletHolder{ provider:providers.Provider; signer:providers.JsonRpcSigner; accounts:Array<string>; constructor(_provider:providers.Provider,_signer:providers.JsonRpcSigner,_accounts:Array<string>) { this.provider = _provider; this.signer = _signer; this.accounts = _accounts; } } export default class WalletUtils{ public static async metamask() : Promise<Array<any>> { try { var provider = new ethers.providers.Web3Provider(window['ethereum']); var accounts = await provider.send("eth_requestAccounts", []); var signer = await provider.getSigner(); console.log("Account:", await signer.getAddress()); } catch (error) { return [null,error] } return [new WalletHolder(provider,signer,accounts),null]; } static holder:WalletHolder; public static get signer() : providers.JsonRpcSigner { return WalletUtils.holder.signer; } public static get provider() : providers.Provider { return WalletUtils.holder.provider; } public static get accounts() : Array<string> { return WalletUtils.holder.accounts; } public static async init(){ const[holder,error] = await WalletUtils.metamask(); if(error){ console.error('init metamask error',error) return; } if(holder instanceof WalletHolder){ WalletUtils.holder = holder; console.log('init success') } } } if(!WalletUtils.holder){ await WalletUtils.init(); } const defaultSinger = WalletUtils.signer; //使用签名及逆行 const message = await defaultSinger.signMessage("areyouok!") //content = "hgqZgU7IuZM8y1rWUQOaMCbbb2QS39EtNWvLGu9FyDvTTQD/cOiLkgYNy+xGl/oEh/idTY2xNh9Kdpmg+ljgfFnd8R8bOEZ2JH38c4Jlhhm6ypntKdVKgrm9dquk8En4sZw1R/mEh8O7sSQ7kLOBv6Epzme0ZGyXJVWA4accjBo="; // sing= "0xcb0a1688018b8cd8f0f5dd66647de8bb251772bc4aa64c9ac8ffc37af29299462f0ee7715646b8172e381bdf44ac5970180bbc10d31de1b6ec59a09d620fa8e21b"; //walletAddress = "0x37949e80Aedc7d72CFB3667d092161EA8729Ba49";
java验签
引入pom
<!--web3j--> <dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>org.web3j</groupId> <artifactId>geth</artifactId> <version>5.0.0</version> <exclusions> <exclusion> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.web3j</groupId> <artifactId>abi</artifactId> <version>5.0.0</version> </dependency>
为了方便java同学测试,我这里附上 验签代码和 加密代码,线上只需要验签代码,因为加密代码由前端同学完成了。
import org.web3j.crypto.Credentials; import org.web3j.crypto.Keys; import org.web3j.crypto.Sign; import org.web3j.utils.Numeric; import java.math.BigInteger; import java.security.SignatureException; import java.util.Arrays; /** * @Description: Web3j签名验签 */ public class ECRecoverUtil { public static void main(String[] args) throws SignatureException { String content = "hgqZgU7IuZM8y1rWUQOaMCbbb2QS39EtNWvLGu9FyDvTTQD/cOiLkgYNy+xGl/oEh/idTY2xNh9Kdpmg+ljgfFnd8R8bOEZ2JH38c4Jlhhm6ypntKdVKgrm9dquk8En4sZw1R/mEh8O7sSQ7kLOBv6Epzme0ZGyXJVWA4accjBo="; String sing= "0xcb0a1688018b8cd8f0f5dd66647de8bb251772bc4aa64c9ac8ffc37af29299462f0ee7715646b8172e381bdf44ac5970180bbc10d31de1b6ec59a09d620fa8e21b"; String walletAddress = "0x37949e80Aedc7d72CFB3667d092161EA8729Ba49"; boolean mark = validate(sing,content,walletAddress); System.out.println(sing); System.out.println(mark); } /** * 签名 * * @param content 原文信息 * @param privateKey 私钥 */ public static String signPrefixedMessage(String content, String privateKey) { // todo 如果验签不成功,就不需要Hash.sha3 直接content.getBytes()就可以了 // 原文信息字节数组 // byte[] contentHashBytes = Hash.sha3(content.getBytes()); byte[] contentHashBytes = content.getBytes(); // 根据私钥获取凭证对象 Credentials credentials = Credentials.create(privateKey); // Sign.SignatureData signMessage = Sign.signPrefixedMessage(contentHashBytes, credentials.getEcKeyPair()); byte[] r = signMessage.getR(); byte[] s = signMessage.getS(); byte[] v = signMessage.getV(); byte[] signByte = Arrays.copyOf(r, v.length + r.length + s.length); System.arraycopy(s, 0, signByte, r.length, s.length); System.arraycopy(v, 0, signByte, r.length + s.length, v.length); return Numeric.toHexString(signByte); } /** * 验证签名 * * @param signature 验签数据 * @param content 原文数据 * @param walletAddress 钱包地址 * @return 结果 */ public static Boolean validate(String signature, String content, String walletAddress) throws SignatureException { if (content == null) { return false; } // todo 如果验签不成功,就不需要Hash.sha3 直接content.getBytes()就可以了 // 原文字节数组 // byte[] msgHash = Hash.sha3(content.getBytes()); byte[] msgHash = content.getBytes(); // 签名数据 byte[] signatureBytes = Numeric.hexStringToByteArray(signature); byte v = signatureBytes[64]; if (v < 27) { v += 27; } //通过摘要和签名后的数据,还原公钥 Sign.SignatureData signatureData = new Sign.SignatureData( v, Arrays.copyOfRange(signatureBytes, 0, 32), Arrays.copyOfRange(signatureBytes, 32, 64)); // 签名的前缀消息到密钥 BigInteger publicKey = Sign.signedPrefixedMessageToKey(msgHash, signatureData); // 得到公钥(私钥对应的钱包地址) String parseAddress = "0x" + Keys.getAddress(publicKey); // 将钱包地址进行比对 return parseAddress.equalsIgnoreCase(walletAddress); } }