Week7-8.15
Day1 CIP23与EIP712的异同
在用钱包为结构化的数据签名时,用户看到的只是一串处理后的hex字符串,无法得知具体签名的内容,这对信息、财产等安全造成了极大的威胁。为了解决该问题,以太坊引入了EIP712。与此相对的,conflux引入了CIP23来作为解决方案。CIP23与EIP712的差异总结如下:
-
当签署一条非结构化的message时,conflux为message增加前缀
\x19Conflux Signed Message:\n
而不是以太坊中的\x19Ethereum Signed Message:\n
-
将
EIP712domain
重命名为CIP23domain
-
在CIP23domain中,
chainId
字段必须包含在其中。否则应该拒绝签署该结构化的数据。
Day2 如何基于CIP23签署消息
-
当签名消息为非结构化消息时,为:
encode(message : 𝔹⁸ⁿ) = "\x19Conflux Signed Message:\n" ‖ len(message) ‖ message where len(message) is the non-zero-padded ascii-decimal encoding of the number of bytes in message.
-
当签名消息为结构化消息时,为:
encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message) where domainSeparator and hashStruct(message) are defined below.
具体的,conflux-sdk为我们提供了相应的sdk简化签署消息的过程。以java-sdk为例:
package conflux.web3j.crypto;
import org.junit.jupiter.api.Test;
import org.web3j.utils.Numeric;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SignDataTests {
//cfx_signTypedData_v4
@Test
public void testSignValidStructure() throws IOException {
StructuredDataTests t = new StructuredDataTests();
//获取TypedData
String msg = t.getResource(
"build/resources/test/"
+ "structured_data_json_files/ValidStructuredData.json");
//根据msg创建编码器
StructuredDataEncoder dataEncoder = new StructuredDataEncoder(msg);
//将数据进行编码后得到msghash,再进行签名
org.web3j.crypto.Sign.SignatureData sign = org.web3j.crypto.Sign.signMessage(dataEncoder.hashStructuredData(), SampleKeys.KEY_PAIR, false);
assertEquals(
"0x371ef48d63082d3875fee13b392c5b6a7449aa638921cb9f3d419f5b6a817ba754d085965fb3a041c3b178d3ae3798ea322ae74cb687dd699b5f6045c7fe47a91c",
Numeric.toHexString(sign.getR()) + Numeric.toHexStringNoPrefix(sign.getS()) + Numeric.toHexStringNoPrefix(sign.getV()));
}
//personal_sign
@Test
public void testSignAnyMessage() throws IOException {
String message = "v0G9u7huK4mJb2K1";
//将msg加上前缀后进行编码后得到msghash,再进行签名
org.web3j.crypto.Sign.SignatureData sign = Sign.signPrefixedMessage(message.getBytes(), SampleKeys.KEY_PAIR);
assertEquals(
"0xbb0ee8492623f2ef6ed461ea638f8b5060b191a1c8830c93d84245f3fb27e20a755e24ff60fe76482dd4377a0aef036937ef88537b2d0fdd834a54e76ecafadc1c",
Numeric.toHexString(sign.getR()) + Numeric.toHexStringNoPrefix(sign.getS()) + Numeric.toHexStringNoPrefix(sign.getV()));
}
}
Day3 如何基于cip23去验证签名的真伪
基于cip23签署的消息有两种验证方式,分别是基于sdk与合约验签。
以java-sdk为例,基于sdk的验签方法为
package conflux.web3j.crypto;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.*;
import org.web3j.crypto.Sign;
import org.web3j.utils.Numeric;
import java.io.IOException;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ECRecoverTest {
private String getAddress() {
return Numeric.prependHexPrefix(Keys.getAddress(getPubKey()));
}
private String getPubKey() {
return SampleKeys.KEY_PAIR.getPublicKey().toString();
}
@Test
public void testSignAndRecoverMessage() {
String message = "v0G9u7huK4mJb2K1";
byte[] msgHash = conflux.web3j.crypto.Sign.getConfluxMessageHash(message.getBytes());
Sign.SignatureData sign = conflux.web3j.crypto.Sign.signPrefixedMessage(message.getBytes(), SampleKeys.KEY_PAIR);
//根据签名与地址进行recover,得到签名的地址
String recoverAddress = conflux.web3j.crypto.Sign.recoverSignature(sign, msgHash, getAddress());
assertEquals(recoverAddress, getAddress());
}
//在fluent中进行签名得到签名后,可以参考以下例程来进行recover
@Test
public void testRecoverTyped() throws IOException {
StructuredDataTests t = new StructuredDataTests();
String msg = t.getResource(
"build/resources/test/"
+ "structured_data_json_files/ValidStructuredData.json");
StructuredDataEncoder dataEncoder = new StructuredDataEncoder(msg);
//以下的签名可以根据上文中的fluent签名来获取
String signature =
"0x371ef48d63082d3875fee13b392c5b6a7449aa638921cb9f3d419f5b6a817ba754d085965fb3a041c3b178d3ae3798ea322ae74cb687dd699b5f6045c7fe47a91c";
//根据签名创建签名实例
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));
//getAddress()中的私钥应换为在fluent钱包中的账户私钥
String recoverAddress = conflux.web3j.crypto.Sign.recoverSignature(sd, dataEncoder.hashStructuredData(), getAddress());
assertEquals(recoverAddress, getAddress());
}
//以下实现与testSignAndRecoverMessage()一样,只是签名方式有区别
@Test
public void testSignAndRecoverTyped() throws IOException {
StructuredDataTests t = new StructuredDataTests();
String msg = t.getResource(
"build/resources/test/"
+ "structured_data_json_files/ValidStructuredData.json");
StructuredDataEncoder dataEncoder = new StructuredDataEncoder(msg);
Sign.SignatureData sign = Sign.signMessage(dataEncoder.hashStructuredData(), SampleKeys.KEY_PAIR, false);
String recoverAddress = conflux.web3j.crypto.Sign.recoverSignature(sign, dataEncoder.hashStructuredData(), getAddress());
assertEquals(recoverAddress, getAddress());
}
}
这其中最主要的是recover()函数,我们来看看该函数的实现:
public static String recoverSignature(SignatureData sd, byte[] data, String address) {
String addressRecovered = null;
// Iterate for each possible key to recover
for (int i = 0; i < 4; i++) {
BigInteger publicKey =
org.web3j.crypto.Sign.recoverFromSignature(
(byte) i,
new ECDSASignature(
new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())),
data);
if (publicKey != null) {
addressRecovered = Numeric.prependHexPrefix(Keys.getAddress(publicKey.toString()));
if (addressRecovered.equals(address)) {
break;
}
}
}
return addressRecovered;
}
以太坊的加密算法中提供了四种签名模式,在四种签名模式下,从签名中去还原公钥,再用公钥去得到地址,与目标地址进行匹配。具体的四种签名模式可参考https://github.com/web3j/web3j/blob/7dea3d99c5bdbfcc03aaeaa8575fb0c9a9a771ab/crypto/src/main/java/org/web3j/crypto/Sign.java#:~:text=public%20static%20BigInteger%20recoverFromSignature(int%20recId%2C%20ECDSASignature%20sig%2C%20byte%5B%5D%20message)%20%7B
对于合约验签,我们需要在合约当中去实现结构化数据类型,具体可见: https://wiki.conflux123.xyz/link/60#bkmrk-%E5%90%88%E7%BA%A6%E9%AA%8C%E7%AD%BE
Day4 如何用fluent钱包去签名
java-sdk的test例程中提供了两种方式去签名,一种是未知签名,用sdk签名后再用sdk进行recover,另一种是已知签名。那么如何得到这个已知签名呢?可以用fluent等方式来获得。具体的fluent钱包为我们提供了相当的provider rpc doc。fluent钱包不仅支持cfx下的签名,还支持eth环境下的签名。
为了实现personal_sign
方法,我们需要在浏览器的console中输入:
conflux
.request({
method: 'personal_sign',
params: [
'v0G9u7huK4mJb2K1', '<your_address>']})
此时页面会弹出来自fluent的签名消息
同样的,为了实现cfx_signTypedData_v4
方法,在console中输入
conflux
.request({
method: 'cfx_signTypedData_v4',
params: [
'<your_address>',
`{
"types": {
"CIP23Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "string"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}`]
})
弹出的消息框中有消息的message字段
在签署后可以得到同样的签名
Day5 目前支持cip23的sdk
目前java-sdk与js-sdk支持cip23,对应的例程分别在java-sdk cip23例程和js-sdk cip23例程
后续go-sdk与python-sdk也会支持上cip23,敬请期待!
No Comments