验签代码示例
MYKEY使用账号的Reserved Key签名。服务端验签,需要先从链上找出Reserved Key,再验证。
ETH:
查询用的AccountStorage合约文件为:
AccountStorage.abi.json
13KB
Code
第三方应用传递timestamp, account, uuID, ref给MYKEY,MYKEY按如下规则进行签名: let message = hex(timestamp + account + uuID + ref)
let hashedMessage = crypto.Keccak256Hash(common.FromHex(unsignData)).Bytes()
let unsignedData = "\x19Ethereum Signed Message:\n" + hashedMessage.length + hashedMessage
认证时,MYKEY还会返回mykeyUID和mykeyUIDSignature字段,其签名规则如下:
let messageForMykeyUID = hex(timestamp + account + uuID + ref + mykeyUID)
let unsignedDataForMykeyUID = "\x19Ethereum Signed Message:\n" + message.length + message
GO
JS
Node
Java
1
package main
2
3
import (
4
"encoding/hex"
5
"fmt"
6
"github.com/ethereum/go-ethereum/common"
7
"github.com/ethereum/go-ethereum/common/hexutil"
8
"github.com/ethereum/go-ethereum/crypto"
9
"log"
10
"strings"
11
"testing"
12
)
13
14
const (
15
signFormat = "\x19Ethereum Signed Message:\n%d%s"
16
)
17
18
func TestVerifty(t *testing.T) {
19
ETHPrv := "87f9bc3c6cd57f18a0143b6863d6b47d6364f1a1ba86eae0ce411097704d5d0d"
20
ETHAdd := "0x296Fd341F90508B6742701F4341D2027CdF5c86d"
21
//待签名的数据
22
data := "0x1234"
23
24
prvKey, _ := hex.DecodeString(ETHPrv)
25
sig, _ := SignWithEthWeb3(data, prvKey)
26
log.Println("sig:", sig)
27
28
unsignDataHashByte := crypto.Keccak256Hash(common.FromHex(data)).Bytes()
29
// 开始服务端签名验证
30
log.Println("verify sign:", verifySig(strings.ToLower(ETHAdd), sig, unsignDataHashByte))
31
}
32
33
func SignWithEthWeb3(unsignData string, privateByte []byte) (signature string, err error) {
34
// 需要先做一次Hash
35
unsignDataHashByte := crypto.Keccak256Hash(common.FromHex(unsignData)).Bytes()
36
return SignWithEth(unsignDataHashByte, privateByte)
37
}
38
39
func SignWithEth(unsignData, privateKeyByte []byte) (signature string, err error) {
40
// web3签名需要加盐后做Hash,对hash数据进行签名
41
newUnsignData := fmt.Sprintf(signFormat, len(unsignData), unsignData)
42
unsignDataHash := crypto.Keccak256([]byte(newUnsignData))
43
key, err := crypto.ToECDSA(privateKeyByte)
44
if err != nil {
45
log.Println("sign ToECDSA err:", err.Error())
46
return "", err
47
}
48
signatureByte, err := crypto.Sign(unsignDataHash, key)
49
if err != nil {
50
log.Println("sign Sign err:", err.Error())
51
return "", err
52
}
53
// web逻辑
54
signatureByte[64] += 27
55
return hexutil.Encode(signatureByte), nil
56
}
57
58
// 验证签名
59
func verifySig(from, sigHex string, msg []byte) bool {
60
fromAddr := common.HexToAddress(from)
61
62
sig := hexutil.MustDecode(sigHex)
63
if sig[64] != 1 && sig[64] != 0 && sig[64] != 27 && sig[64] != 28 {
64
log.Println("in hexutil.MustDecode error.")
65
return false
66
}
67
68
if sig[64] != 1 && sig[64] != 0 {
69
sig[64] -= 27
70
}
71
pubKey, err := crypto.SigToPub(createSignHash(msg), sig)
72
if err != nil {
73
log.Println("in crypto.SigToPub error:", err.Error())
74
return false
75
}
76
77
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
78
log.Println("recoveredAddr:", recoveredAddr.String())
79
return fromAddr == recoveredAddr
80
}
81
82
// 待签名数据生成符合格式的hash
83
func createSignHash(data []byte) []byte {
84
msg := fmt.Sprintf(signFormat, len(data), data)
85
return crypto.Keccak256([]byte(msg))
86
}
87
88
Copied!
1
2
let sigUtil = require('eth-sig-util')
3
let Web3 = require('web3');
4
let web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/56444e75b6a24070a374f791bd25f811"));
5
let json = require('./AccountStorage.abi.json');
6
let AccountStorageABI = json.abi
7
let AccountStorageAddr = '0xADc92d1fD878580579716d944eF3460E241604b7'
8
let AccountStorageIns = new web3.eth.Contract(AccountStorageABI, AccountStorageAddr);
9
10
// 1. get mykey account Reserved key
11
// https://docs.mykey.org/dive-into-mykey/mykey-on-eos#keydata%E8%A1%A8%E4%B8%AD%E7%9A%84%E5%AF%86%E9%92%A5
12
let account = '0x67913A00a459fCd41CbF4124a887e8d8dE0742c0'
13
14
// account proxy
15
let reservedKeyAddr = await AccountStorageIns.methods.getKeyData(account, 3).call();
16
console.log(account, "reserved key:", reservedKeyAddr) // should be 0xd2F9b4652D80FA870207C2b421B8437d7D54a484
17
18
// 2. 签名消息。使用mykey时,直接调用web3.personal.sign签名即可。以下提供私钥签名,仅为了测试签名和验签。
19
web3.personal.sign("hello", web3.eth.coinbase, console.log);
20
let message = 'hello'
21
let messageHash = web3.utils.soliditySha3(message);
22
let privKeyHex = '78e22x19400da88318b74649ec8ff0d6aa9x7f8062950276f28xc65b9d569f32f84' // prvkey of '0xd2F9b4652D80FA870207C2b421B8437d7D54a484'
23
let privKey = Buffer.from(privKeyHex, 'hex')
24
let msgParams = { data: messageHash }
25
let signed = sigUtil.personalSign(privKey, msgParams)
26
console.log("signature:", signed)
27
28
// 3. recover
29
msgParams.sig = signed
30
let recovered = sigUtil.recoverPersonalSignature(msgParams)
31
console.log("recovered:", recovered)
32
console.log(reservedKeyAddr.toLowerCase() === recovered.toLowerCase())
33
34
35
36
// 验证签名:message是待处理的data;sig是已签名的数据;expectedKey是期望的ReservedKey
37
async function recoverOnly() {
38
// recover
39
let message = "xxxxx"
40
let messageHash = web3.utils.soliditySha3(message);
41
let sig = '0x1dc17f7413edaa2d696a40a2298cca98cfff59f489af7d7e82a7e5184b65036b6a6147734b17728796a5932aa0c1f1455cafc80e208d55adeb2a8400777c1fa01'
42
let recovedKey = await web3.eth.accounts.recover(messageHash, sig)
43
let expectedKey = "0x0b2144B2c8430ecde7d4ED7xxxxxx"
44
console.log( expectedKey.toLowerCase() == recovedKey.toLowerCase())
45
}
Copied!
1
var Web3 = require('web3')
2
// var Contract = require('web3-eth-contract')
3
var Accounts = require('web3-eth-accounts');
4
var accounts = new Accounts('ws://localhost:8546');
5
var assert = require("assert")
6
7
var message = "test string"
8
var unsignedData = "\x19Ethereum Signed Message:\n" + message.length + message
9
var pk = "0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
10
11
var address = accounts.privateKeyToAccount(pk).address
12
13
// sign
14
var signature = accounts.sign(unsignedData, pk).signature;
15
console.log("signature ", signature)
16
17
// recover
18
var recoveredAddress = accounts.recover(unsignedData, signature)
19
20
if ( address == recoveredAddress ) {
21
console.log("the signature is correct")
22
}
23
24
25
var web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/9dced93dfa714682b768ce813867a4d3"));
26
27
var account = '0x67913A00a459fCd41CbF4124a887e8d8dE0742c0' //test account
28
var json = require('./AccountStorage.abi.json')
29
var addr = '0xADc92d1fD878580579716d944eF3460E241604b7'
30
var mykeyAccountsContract = new web3.eth.Contract(json.abi, addr)
31
32
var keyDataCall = mykeyAccountsContract.methods.getKeyData(account, 3).call();
33
keyDataCall
34
.then(console.log)
35
.catch((error) => {
36
throw new Error(error)
37
})
Copied!
POM加入依赖包:
1
<dependency>
2
<groupId>org.web3j</groupId>
3
<artifactId>core</artifactId>
4
<version>4.5.12</version>
5
</dependency>
Copied!
示例代码:
1
import java.math.BigInteger;
2
import java.nio.ByteBuffer;
3
import java.security.SignatureException;
4
import java.util.Arrays;
5
import org.web3j.crypto.ECKeyPair;
6
import org.web3j.crypto.Hash;
7
import org.web3j.crypto.Keys;
8
import org.web3j.crypto.Sign;
9
import org.web3j.crypto.Sign.SignatureData;
10
import org.web3j.utils.Numeric;
11
12
public class ETHEccUtil {
13
14
public static byte[] signPrefixedMessage(byte[] data, String privateKey) {
15
BigInteger priKeyBI = Numeric.toBigInt(privateKey);
16
ECKeyPair pair = ECKeyPair.create(priKeyBI);
17
SignatureData signatureData = Sign.signPrefixedMessage(data, pair);
18
ByteBuffer buf = ByteBuffer.allocate(signatureData.getR().length + signatureData.getS().length + signatureData.getV().length);
19
buf.put(signatureData.getR());
20
buf.put(signatureData.getS());
21
buf.put(signatureData.getV());
22
return buf.array();
23
}
24
25
public static boolean verifyPrefixedMessage(byte[] data, byte[] sig, String pubKeyAddress) throws SignatureException {
26
byte[] r = Arrays.copyOfRange(sig, 0, 32);
27
byte[] s = Arrays.copyOfRange(sig, 32, 64);
28
byte[] v = Arrays.copyOfRange(sig, 64, sig.length);
29
SignatureData signatureData = new SignatureData(v, r, s);
30
BigInteger recoveredPubKey = Sign.signedPrefixedMessageToKey(data, signatureData);
31
return pubKeyAddress.equals(Keys.getAddress(recoveredPubKey));
32
}
33
34
35
public static void main(String[] args) throws SignatureException {
36
String pubKey = ""; //ReservedKey, mykey返回,或者从ETH链上查询
37
String unsignedData = ""; //未签名数据,格式为:timestamp + account + uuID + ref
38
byte[] signature = Numeric.hexStringToByteArray("这里是签名");
39
boolean isTrue = ETHEccUtil.verifyPrefixedMessage(Hash.sha3(unsignedData.getBytes()), signature, Numeric.cleanHexPrefix(pubKey).toLowerCase());
40
System.out.println("verify sig " + isTrue);
41
}
42
}
Copied!
EOS:
第三方应用传递timestamp, account, uuID, ref给MYKEY,MYKEY按如下规则进行签名: let unsignedData = timestamp + account + uuID + ref
认证时,MYKEY还会返回mykeyUID和mykeyUIDSignature字段,其签名规则如下:
let unsignedDataForMykeyUID = timestamp + account + uuID + ref + mykeyUID
GO
JS
Node
Java
1
package main
2
3
import (
4
"crypto/sha256"
5
"fmt"
6
"github.com/eoscanada/eos-go/ecc"
7
"testing"
8
)
9
10
func TestSignWithEOS(t *testing.T) {
11
EOSPrv := "5JrAtoHd6uQnX4nowRzEzuykb81uaLDzwvAvkgV61ACk1tam5K8"
12
EOSPub := "EOS5f7NU3vMKt2mSrZtDpiJKRPFXNKZfjsP1V79eEXdZKf62LfvhU"
13
14
sig, _ := SignWithEos(EOSPrv, []byte("hello"))
15
fmt.Println("sig:", sig)
16
17
publicTest, _ := ecc.NewPublicKey(EOSPub)
18
fmt.Println("content:", len(publicTest.Content))
19
signature, err := ecc.NewSignature(sig)
20
if err != nil {
21
return
22
}
23
verifyResult := signature.Verify(sigDigest([]byte("hello")), publicTest)
24
fmt.Println("verify result:", verifyResult)
25
}
26
27
func SignWithEos(privateKeyHex string, unsignData []byte) (signedData string, err error) {
28
privatekey, err := ecc.NewPrivateKey(privateKeyHex)
29
if err != nil {
30
return "", err
31
}
32
signature, err := privatekey.Sign(sigDigest(unsignData))
33
if err != nil {
34
return "", err
35
}
36
return signature.String(), nil
37
}
38
39
func sigDigest(contextFreeData []byte) []byte {
40
h := sha256.New()
41
if len(contextFreeData) > 0 {
42
h2 := sha256.New()
43
_, _ = h2.Write(contextFreeData)
44
_, _ = h.Write(h2.Sum(nil))
45
} else {
46
_, _ = h.Write(make([]byte, 32, 32))
47
}
48
return h.Sum(nil)
49
}
50
Copied!
1
/**
2
* 获取ReservedKey
3
* 详情参考 https://docs.mykey.org/v/English/dive-into-mykey/mykey-on-eos#mykey-account-structure
4
* @param {String} name MYKEY账号
5
* @return {String} ReservedKey/the 3rd Operation Key
6
*/
7
async getReservedKey(name) {
8
const mgrcontract = await this.getMykeyMgr(name)
9
const mykey_signkey_index = 3
10
const keydata = await this.eosJsonRpc.get_table_rows({json:true, code:mgrcontract, scope:name, table:'keydata', lower_bound: mykey_signkey_index, limit:1})
11
if(!keydata) return '';
12
return keydata.rows[0].key.pubkey;
13
}
14
15
/**
16
* 验证已签名的数据
17
* Parameters
18
* signature (string | Buffer) buffer or hex string
19
* data (string | Buffer)
20
* pubkey (pubkey | PublicKey)
21
* encoding (optional, default 'utf8')
22
* hashData boolean sha256 hash data before verify (optional, default true)
23
*/
24
ecc.verify(signature, data, pubkey) === true
25
Copied!
1
var ecc = require('eosjs-ecc')
2
var eos = require('eosjs')
3
var fetch = require('node-fetch');
4
5
var priv = '5JbrPk2h9kNtsmKTauKar5PtmE5nPhtF8BcVUSzGrZhFV7UvccK' //only for test
6
var data = 'test string'
7
8
function SignWithEOS() {
9
var signature = ecc.sign(data, priv)
10
console.log(signature)
11
return signature
12
}
13
14
var signature = SignWithEOS()
15
16
function VerifyWithEOS() {
17
var pub = ecc.privateToPublic(priv)
18
var verified = ecc.verify(signature, data, pub, 'utf8', false)
19
console.log(verified)
20
return verified
21
}
22
23
VerifyWithEOS()
24
25
// 获取Rerserve Key
26
function GetReserveKey() {
27
var rpc = new eos.JsonRpc('https://mainnet.eoscannon.io', { fetch })
28
var resp = rpc.get_table_rows({
29
json: true,
30
code: 'mykeymanager',
31
scope: 'mykeyhulu521', //test account
32
table: 'keydata',
33
limit: 10,
34
reverse: false,
35
show_payer: false
36
});
37
resp.then(function (data) {
38
console.log("Reserved public key: ", data.rows[3].key.pubkey)
39
})
40
}
41
42
GetReserveKey()
Copied!
POM加入依赖包:
1
<dependency>
2
<groupId>org.bouncycastle</groupId>
3
<artifactId>bcprov-jdk15on</artifactId>
4
<version>1.59</version>
5
</dependency>
6
<dependency>
7
<groupId>com.google</groupId>
8
<artifactId>bitcoinj</artifactId>
9
<version>0.11.3</version>
10
</dependency>
Copied!
示例代码:
1
import com.google.bitcoin.core.AddressFormatException;
2
import com.google.bitcoin.core.Base58;
3
4
import org.bouncycastle.crypto.params.ECDomainParameters;
5
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
6
import org.bouncycastle.crypto.signers.ECDSASigner;
7
import org.bouncycastle.jce.ECNamedCurveTable;
8
import org.bouncycastle.jce.provider.BouncyCastleProvider;
9
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
10
11
import java.math.BigInteger;
12
import java.security.*;
13
import java.util.Arrays;
14
15
16
public class EOSEccUtil {
17
public static final String LEGACY_SIG_PREFIX = "EOS";
18
public static final String ECC_CURVE_NAME = "secp256k1";
19
public static final String SIG_PREFIX = "SIG_K1_";
20
21
static {
22
Security.addProvider(new BouncyCastleProvider());
23
}
24
25
//验签
26
public static boolean verify(String publKey, String data, String sign) throws Exception {
27
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
28
byte[] digest = sha256.digest(data.getBytes());
29
30
Boolean verified = verifySignature(digest,sign,publKey);
31
return verified;
32
}
33
34
private static boolean verifySignature(byte[] digest, String signature, String publicKeyWif) throws KeyException, AddressFormatException {
35
SignatureComponents signatureComponents = checkAndDecodeSignature(signature);
36
37
byte[] xBytes = Base58.decode(publicKeyWif.substring(3));
38
xBytes = Arrays.copyOfRange(xBytes, 0, xBytes.length - 4);
39
40
ECNamedCurveParameterSpec paramsSpec = ECNamedCurveTable.getParameterSpec(ECC_CURVE_NAME);
41
ECDomainParameters curve = new ECDomainParameters(
42
paramsSpec.getCurve(),
43
paramsSpec.getG(),
44
paramsSpec.getN(),
45
paramsSpec.getH());
46
47
boolean verified = false;
48
49
BigInteger r = signatureComponents.r;
50
BigInteger s = signatureComponents.s;
51
52
ECDSASigner signer = new ECDSASigner();
53
ECPublicKeyParameters params = new ECPublicKeyParameters(curve.getCurve().decodePoint(xBytes), curve);
54
signer.init(false, params);
55
try {
56
verified = signer.verifySignature(digest, r, s);
57
} catch (NullPointerException ex) {
58
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures
59
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
60
throw new KeyException("verify error");
61
}
62
63
return verified;
64
}
65
66
public static SignatureComponents checkAndDecodeSignature(
67
final String signatureString)
68
throws KeyException, IllegalArgumentException, AddressFormatException {
69
SignatureComponents components = null;
70
71
try {
72
// Verify the private key string is properly formatted
73
if (!signatureString.startsWith(LEGACY_SIG_PREFIX)
74
&& !signatureString.startsWith(SIG_PREFIX)) {
75
throw new IllegalArgumentException("Unrecognized Signature format");
76
}
77
78
// Check the encoding of the Signature (e.g. EOS/WIF, SIG_K1)
79
boolean legacy = signatureString.startsWith(LEGACY_SIG_PREFIX);
80
81
// Remove the prefix
82
String trimmedPrivateKeyString;
83
if (legacy) {
84
trimmedPrivateKeyString = signatureString.replace(LEGACY_SIG_PREFIX, "");
85
} else {
86
trimmedPrivateKeyString = signatureString.replace(SIG_PREFIX, "");
87
}
88
89
// Decode the string and extract its various components (i.e. R, S, i)
90
byte[] decodedBytes = Base58.decode(trimmedPrivateKeyString);
91
byte i = decodedBytes[0];
92
byte[] rBytes = Arrays.copyOfRange(decodedBytes, 1, 33);
93
byte[] sBytes = Arrays.copyOfRange(decodedBytes, 33, 65);
94
byte[] checksum = Arrays.copyOfRange(decodedBytes, 65, 69);
95
96
// Verify the checksum is correct
97
byte[] calculatedChecksum = ripemd160(
98
new byte[]{i},
99
rBytes,
100
sBytes,
101
"K1".getBytes());
102
calculatedChecksum = Arrays.copyOfRange(calculatedChecksum, 0, 4);
103
if (!Arrays.equals(checksum, calculatedChecksum)) {
104
throw new KeyException("Signature Checksum failed");
105
}
106
107
// Construct a SignatureComponents object from the components
108
components = new SignatureComponents();
109
components.r = new BigInteger(1, rBytes);
110
components.s = new BigInteger(1, sBytes);
111
components.i = i;
112
} catch (NoSuchAlgorithmException e) {
113
throw new KeyException("Failed to decode Signature", e);
114
}
115
116
return components;
117
}
118
119
private static byte[] ripemd160(byte[]... inputs) throws NoSuchAlgorithmException {
120
byte[] hash = null;
121
122
MessageDigest ripemd160 = MessageDigest.getInstance("RIPEMD160");
123
for (byte[] input : inputs) {
124
ripemd160.update(input);
125
}
126
hash = ripemd160.digest();
127
128
return hash;
129
}
130
131
/**
132
*
133
*/
134
public static class SignatureComponents {
135
public BigInteger r;
136
public BigInteger s;
137
public byte i;
138
139
@Override
140
public String toString() {
141
return "SignatureComponents{\n" +
142
" r=" + r + "\n" +
143
" s=" + s + "\n" +
144
" i=" + i +
145
'}';
146
}
147
}
148
149
public static void main(String[] args) throws Exception {
150
String pubkey = ""; //ReservedKey, mykey返回,或者从EOS链上查询
151
String data = ""; //未签名数据,格式为:timestamp + account + uuID + ref
152
String signature = ""; //签名
153
boolean isTrue = EOSEccUtil.verify(pubkey, data, signature);
154
System.out.println("verify result " + isTrue);
155
}
156
}
Copied!
Last modified 1yr ago
Copy link