soliditySha3

最近在使用Java写程序,来与ETH合约进行交互。但是EthereumjWeb3j 的使用体验都不是很好,与其他语言的版本有很大的差距。其中有一个Solidity中的函数encodePackedJava中没有找到任何已有的实现,最后只能是自己摸索了。

原函数探查

首先搜索了Solidity相关的文档,发现encodePacked 函数已经转为 soliditySha3。搜索后还是没有发现Java版本。所以还是先找到了功能实现比较齐全的JavaScript版本来作为对照。

JavaScript中的使用如下:

1
2
let result = Web3.utils.soliditySha3({t: 'string', v:'233'})
console.log(result)

其内部根据传入参数的不同类型,首先对各个参数进行转换,转为byte array,然后直接将byte array进行拼接,最后转为Hex字符串结束。

Address

直接使用ethereumj中的ByteUtil.hexStringToBytes进行转化即可;

uint(uint256)

使用ByteUtil.bigIntegerToBytes进行转化。这里需要注意的是,要显示的指明 numBytes参数为32,这里最开始没有进行制定,计算总是不一致,还是翻看了JavaScript中的代码才找到了此问题;

string

直接使用 String.getBytes() 即可;

最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static String soliditySha3(SParams... params) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (SParams param : params) {
byte[] bytes = solidityType2Byte(param);
try {
bos.write(bytes);
} catch (IOException e) {
log.error("write byte error", e);
throw new HBCExceiption("solidity sha3 calc error");
}
}

return ByteUtil.toHexString(HashUtil.sha3(bos.toByteArray()));
}

public static byte[] solidityType2Byte(SParams param) {
switch (param.type) {
case "address":
return ByteUtil.hexStringToBytes((String) param.value);
case "uint":
if (param.value instanceof Integer) {
return ByteUtil.bigIntegerToBytes(BigInteger.valueOf((int) param.value), 32);
} else if (param.value instanceof BigInteger) {
return ByteUtil.bigIntegerToBytes((BigInteger) param.value, 32);
} else {
return ByteUtil.bigIntegerToBytes(BigInteger.valueOf((long) param.value), 32);
}
case "string":
return ((String) param.value).getBytes();
default:
throw new HBCExceiption(String.format("unknown solidity type:%s", param.type));
}
}

public static class SParams {
public String type;
public Object value;

public SParams(String type, Object value) {
this.type = type;
this.value = value;
}