一、智能合约Hash代码
语法:
keccak256(abi.encodePacked(text1,text));
返回byte32类型的hash值
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HashContract {
function hashAbiEncode(string memory text1,string memory text) external pure returns(bytes32){
return keccak256(abi.encodePacked(text1,text));
}
function hashAbiEncodePacked(string memory text1,string memory text2) external pure returns (bytes32) {
return keccak256(abi.encode(text1,text2));
}
function hashAbiEncodePackedNum(string memory text1,uint num,string memory text2) external pure returns (bytes32) {
return keccak256(abi.encodePacked(text1,num,text2));
}
// @dev calc hash value
function hash(string memory text,uint num,string memory text1) external pure returns(bytes32){
return keccak256(abi.encodePacked(text,num,text1));
}
function encodeTest(string memory text,string memory text1) external pure returns(bytes memory){
return abi.encode(text,text1);
}
function encodePackedTest(string memory text,string memory text1) external pure returns(bytes memory) {
return abi.encodePacked(text,text1);
}
}
说明:
1.abi.encode(param1,param2) 和 abi.encodePacked(param1,param2) 的区别
abi.encode(“AAA”,“BB”): 返回的结果为:
0x4141414242
abi.encodePacked(“AAA”,“BB”): 返回结果为:
0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003414141000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024242000000000000000000000000000000000000000000000000000000000000
encodePacked会填充0,这样abi.encodePacked(“AAA”,“BB”) 和 abi.encodePacked(“AA”,“ABB”)计算出来的hash值是不一样的
2.使用abi.encode(param1,param2) 会存在hash碰撞的可能
比如abi.encode(“AAA”,“BB”) 的返回结果为:
0x4141414242
abi.encode(“AA”,“ABB”) 的返回结果为:
0x4141414242
貌似进行预算的时候是直接组合两个参数的字符串,这样就导致这两次计算的都相当于同一个字符串"AAABB"
二、通过Hash验证签名
可以使用的场景:
验证签名的过程:
下面有合约代码,我将合约代码部署到Conflux Test网络上进行上述流程的测试
ConfluxScan Test 测试网上合约地址:
cfxtest:accu714t11k574edkeh8txnr63ubfb2dtpsj2gvmgf
1.首先使用Fluent Wallet钱包对需要签名的字符串进行签名
待签名的字符串:
我祝愿浏览该帖子的各位,一夜暴富!!!功成名就。
打开Chrome F12的控制台输入
account = "cfxtest:aam81t80tu6f5zamuk0hycy14urec6xc8aepw5cmt2"
hashStr = "我祝愿浏览该帖子的各位,一夜暴富!!!功成名就。"
conflux.request({method: "personal_sign", params: [hashStr,account],from :account})
account : 要签名的地址
hashStr: 要签名的内容
签名过后生成的字符串:
0x23d1df64cba9773cfedb798344bd52ccd77c979fab6a52f28748a52b855b1cad689801d7aaffeffbb00f9291dec68e54e80724b8855f4ee30022e114bb715f0a00
之后就可以上部署的合约上进行验证了
代码实现:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SignVaild{
// return string hash value
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
// int convert to string.
function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len;
while (_i != 0) {
k = k-1;
uint8 temp = (48 + uint8(_i - _i / 10 * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}
// return string length that type is uint256
function strLen(string memory s) public pure returns ( uint256) {
return bytes(s).length;
}
// join string.
function append(string memory a, string memory b) internal pure returns (string memory) {
return string(abi.encodePacked(a, b));
}
// 测试拼接字符串
function testConcat(string memory _message) public pure returns (string memory){
string memory messageLen = uint2str(strLen(_message));
return append("\x19Conflux Signed Message:\n",messageLen);
}
// 将待检查的文本内容转换成hash
function getCfxSignedMessageHash(string memory _message) public pure returns (bytes32){
string memory messageLen = uint2str(strLen(_message));
return keccak256(abi.encodePacked(
append("\x19Conflux Signed Message:\n",messageLen),
_message
));
}
// 传递 待检验的字符串hash值和 Fluent钱包签名生成的字符串
function recover(bytes32 _cfxSignedMessageHash,bytes memory _sig) public pure returns (address){
(bytes32 r, bytes32 s, uint8 v) = _split(_sig);
return ecrecover(_cfxSignedMessageHash,v,r,s);
}
// 通过签名字符串提取出 r,s,v
function _split(bytes memory _sig) public pure returns (bytes32 r,bytes32 s, uint8 v){
require(_sig.length ==65 , "invalid sign");
assembly {
r := mload(add(_sig,32))
s := mload(add(_sig,64))
v := byte(0,mload(add(_sig,96)))
}
return (r,s,v);
}
// 校验签名该签名是否由 指定字符串和指定地址签名出来的
function verfiy(address _signer,string memory _message,bytes memory _sig) external pure returns(bool){
// 传递待检验的字符串 生成hash字符串
bytes32 cfxSignerMessageHash = getCfxSignedMessageHash(_message);
return recover(cfxSignerMessageHash,_sig) == _signer;
}
}
Java 代码验证:
这个是使用 brander 大佬的代码,多亏大佬的代码让我断点调试然后对比智能合约计算出来的结果,才找到合约中的代码问题
如果有幸让大佬看见了,不懂就问 \u0019 这个有什么用?
import conflux.web3j.types.AddressType;
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;
public class ConfluxFluentDemo {
public static String recoverFluentAddressFromSignature(String signature, String message) throws SignatureException {
String PERSONAL_MESSAGE_PREFIX = "\u0019Conflux Signed Message:\n";
String prefix = PERSONAL_MESSAGE_PREFIX + message.length();
byte[] msgHash = (prefix + message).getBytes();
byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
byte v = signatureBytes[64];
if (v < 27) {
v += 27;
}
Sign.SignatureData sd = new Sign.SignatureData(
v,
(byte[]) Arrays.copyOfRange(signatureBytes, 0, 32),
(byte[]) Arrays.copyOfRange(signatureBytes, 32, 64));
boolean match = false;
BigInteger pubkey = Sign.signedMessageToKey(msgHash, sd);
String addressRecovered = "0x" + Keys.getAddress(pubkey);
System.out.println("-22-->" + addressRecovered);
String hexAddress = AddressType.User.normalize(addressRecovered);
System.out.println("--> " + hexAddress);
return hexAddress;
}
public static void main(String[] args) {
try {
System.out.println(recoverFluentAddressFromSignature("0x0e730f15d5df13b6dedccc2f2df5c4924d2efde4f5f76908d35b79a7407bb12206b1d6e2ff0e9c0205fc74b327de840d878604b732506a085ccf963dfb0f4b4600",
"secret message"));
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
Maven POM 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.plumblossom.javase</groupId>
<artifactId>java-knowledge</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>java-collection</module>
</modules>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.3.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
参考大佬的签名验证地址: JAVA 版本 personal_sign 签名验证2 (personal_sign portal与fluent的不同点)