验签代码示例

MYKEY使用账号的Reserved Key签名。服务端验签,需要先从链上找出Reserved Key,再验证。

ETH:

查询用的AccountStorage合约文件为:

第三方应用传递timestamp, account, uuID, ref给MYKEY,MYKEY按如下规则进行签名: let message = hex(timestamp + account + uuID + ref)

let unsignedData = "\x19Ethereum Signed Message:\n" + message.length + message

认证时,MYKEY还会返回mykeyUID和mykeyUIDSignature字段,其签名规则如下:

let messageForMykeyUID = hex(timestamp + account + uuID + ref + mykeyUID)

let unsignedDataForMykeyUID = "\x19Ethereum Signed Message:\n" + message.length + message

GO
JS
Node
Java
GO
package main
import (
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"log"
"strings"
"testing"
)
const (
signFormat = "\x19Ethereum Signed Message:\n%d%s"
)
func TestVerifty(t *testing.T) {
ETHPrv := "87f9bc3c6cd57f18a0143b6863d6b47d6364f1a1ba86eae0ce411097704d5d0d"
ETHAdd := "0x296Fd341F90508B6742701F4341D2027CdF5c86d"
//待签名的数据
data := "0x1234"
prvKey, _ := hex.DecodeString(ETHPrv)
sig, _ := SignWithEthWeb3(data, prvKey)
log.Println("sig:", sig)
unsignDataHashByte := crypto.Keccak256Hash(common.FromHex(data)).Bytes()
// 开始服务端签名验证
log.Println("verify sign:", verifySig(strings.ToLower(ETHAdd), sig, unsignDataHashByte))
}
func SignWithEthWeb3(unsignData string, privateByte []byte) (signature string, err error) {
// 需要先做一次Hash
unsignDataHashByte := crypto.Keccak256Hash(common.FromHex(unsignData)).Bytes()
return SignWithEth(unsignDataHashByte, privateByte)
}
func SignWithEth(unsignData, privateKeyByte []byte) (signature string, err error) {
// web3签名需要加盐后做Hash,对hash数据进行签名
newUnsignData := fmt.Sprintf(signFormat, len(unsignData), unsignData)
unsignDataHash := crypto.Keccak256([]byte(newUnsignData))
key, err := crypto.ToECDSA(privateKeyByte)
if err != nil {
log.Println("sign ToECDSA err:", err.Error())
return "", err
}
signatureByte, err := crypto.Sign(unsignDataHash, key)
if err != nil {
log.Println("sign Sign err:", err.Error())
return "", err
}
// web逻辑
signatureByte[64] += 27
return hexutil.Encode(signatureByte), nil
}
// 验证签名
func verifySig(from, sigHex string, msg []byte) bool {
fromAddr := common.HexToAddress(from)
sig := hexutil.MustDecode(sigHex)
if sig[64] != 1 && sig[64] != 0 && sig[64] != 27 && sig[64] != 28 {
log.Println("in hexutil.MustDecode error.")
return false
}
if sig[64] != 1 && sig[64] != 0 {
sig[64] -= 27
}
pubKey, err := crypto.SigToPub(createSignHash(msg), sig)
if err != nil {
log.Println("in crypto.SigToPub error:", err.Error())
return false
}
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
log.Println("recoveredAddr:", recoveredAddr.String())
return fromAddr == recoveredAddr
}
// 待签名数据生成符合格式的hash
func createSignHash(data []byte) []byte {
msg := fmt.Sprintf(signFormat, len(data), data)
return crypto.Keccak256([]byte(msg))
}

JS

let sigUtil = require('eth-sig-util')
let Web3 = require('web3');
let web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/56444e75b6a24070a374f791bd25f811"));
let json = require('./AccountStorage.abi.json');
let AccountStorageABI = json.abi
let AccountStorageAddr = '0xADc92d1fD878580579716d944eF3460E241604b7'
let AccountStorageIns = new web3.eth.Contract(AccountStorageABI, AccountStorageAddr);
// 1. get mykey account Reserved key
// 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
let account = '0x67913A00a459fCd41CbF4124a887e8d8dE0742c0'
// account proxy
let reservedKeyAddr = await AccountStorageIns.methods.getKeyData(account, 3).call();
console.log(account, "reserved key:", reservedKeyAddr) // should be 0xd2F9b4652D80FA870207C2b421B8437d7D54a484
// 2. sign message,
web3.personal.sign("hello", web3.eth.coinbase, console.log);
let message = 'hello'
let privKeyHex = '78e2219400da88378b746499ec8ff0d6aa97f806950276f28c65b9d569f32f84' // prvkey of '0xd2F9b4652D80FA870207C2b421B8437d7D54a484'
let privKey = Buffer.from(privKeyHex, 'hex')
let msgParams = { data: message }
let signed = sigUtil.personalSign(privKey, msgParams)
console.log("signature:", signed)
// 3. recover
msgParams.sig = signed
let recovered = sigUtil.recoverPersonalSignature(msgParams)
console.log("recovered:", recovered)
console.log(reservedKeyAddr.toLowerCase() === recovered.toLowerCase())
Node

var Web3 = require('web3')
// var Contract = require('web3-eth-contract')
var Accounts = require('web3-eth-accounts');
var accounts = new Accounts('ws://localhost:8546');
var assert = require("assert")
var message = "test string"
var unsignedData = "\x19Ethereum Signed Message:\n" + message.length + message
var pk = "0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
var address = accounts.privateKeyToAccount(pk).address
// sign
var signature = accounts.sign(unsignedData, pk).signature;
console.log("signature ", signature)
// recover
var recoveredAddress = accounts.recover(unsignedData, signature)
if ( address == recoveredAddress ) {
console.log("the signature is correct")
}
var web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/9dced93dfa714682b768ce813867a4d3"));
var account = '0x67913A00a459fCd41CbF4124a887e8d8dE0742c0' //test account
var json = require('./AccountStorage.abi.json')
var addr = '0xADc92d1fD878580579716d944eF3460E241604b7'
var mykeyAccountsContract = new web3.eth.Contract(json.abi, addr)
var keyDataCall = mykeyAccountsContract.methods.getKeyData(account, 3).call();
keyDataCall
.then(console.log)
.catch((error) => {
throw new Error(error)
})

Java

POM加入依赖包:

<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.5.12</version>
</dependency>

示例代码:

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.SignatureException;
import java.util.Arrays;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.utils.Numeric;
public class ETHEccUtil {
public static byte[] signPrefixedMessage(byte[] data, String privateKey) {
BigInteger priKeyBI = Numeric.toBigInt(privateKey);
ECKeyPair pair = ECKeyPair.create(priKeyBI);
SignatureData signatureData = Sign.signPrefixedMessage(data, pair);
ByteBuffer buf = ByteBuffer.allocate(signatureData.getR().length + signatureData.getS().length + signatureData.getV().length);
buf.put(signatureData.getR());
buf.put(signatureData.getS());
buf.put(signatureData.getV());
return buf.array();
}
public static boolean verifyPrefixedMessage(byte[] data, byte[] sig, String pubKeyAddress) throws SignatureException {
byte[] r = Arrays.copyOfRange(sig, 0, 32);
byte[] s = Arrays.copyOfRange(sig, 32, 64);
byte[] v = Arrays.copyOfRange(sig, 64, sig.length);
SignatureData signatureData = new SignatureData(v, r, s);
BigInteger recoveredPubKey = Sign.signedPrefixedMessageToKey(data, signatureData);
return pubKeyAddress.equals(Keys.getAddress(recoveredPubKey));
}
public static void main(String[] args) throws SignatureException {
String pubKey = ""; //ReservedKey, mykey返回,或者从ETH链上查询
String unsignedData = ""; //未签名数据,格式为:timestamp + account + uuID + ref
byte[] signature = Numeric.hexStringToByteArray("这里是签名");
boolean isTrue = ETHEccUtil.verifyPrefixedMessage(Hash.sha3(unsignedData.getBytes()), signature, Numeric.cleanHexPrefix(pubKey).toLowerCase());
System.out.println("verify sig " + isTrue);
}
}

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
GO
package main
import (
"crypto/sha256"
"fmt"
"github.com/eoscanada/eos-go/ecc"
"testing"
)
func TestSignWithEOS(t *testing.T) {
EOSPrv := "5JrAtoHd6uQnX4nowRzEzuykb81uaLDzwvAvkgV61ACk1tam5K8"
EOSPub := "EOS5f7NU3vMKt2mSrZtDpiJKRPFXNKZfjsP1V79eEXdZKf62LfvhU"
sig, _ := SignWithEos(EOSPrv, []byte("hello"))
fmt.Println("sig:", sig)
publicTest, _ := ecc.NewPublicKey(EOSPub)
fmt.Println("content:", len(publicTest.Content))
signature, err := ecc.NewSignature(sig)
if err != nil {
return
}
verifyResult := signature.Verify(sigDigest([]byte("hello")), publicTest)
fmt.Println("verify result:", verifyResult)
}
func SignWithEos(privateKeyHex string, unsignData []byte) (signedData string, err error) {
privatekey, err := ecc.NewPrivateKey(privateKeyHex)
if err != nil {
return "", err
}
signature, err := privatekey.Sign(sigDigest(unsignData))
if err != nil {
return "", err
}
return signature.String(), nil
}
func sigDigest(contextFreeData []byte) []byte {
h := sha256.New()
if len(contextFreeData) > 0 {
h2 := sha256.New()
_, _ = h2.Write(contextFreeData)
_, _ = h.Write(h2.Sum(nil))
} else {
_, _ = h.Write(make([]byte, 32, 32))
}
return h.Sum(nil)
}

JS

/**
* 获取ReservedKey
* 详情参考 https://docs.mykey.org/v/English/dive-into-mykey/mykey-on-eos#mykey-account-structure
* @param {String} name MYKEY账号
* @return {String} ReservedKey/the 3rd Operation Key
*/
async getReservedKey(name) {
const mgrcontract = await this.getMykeyMgr(name)
const mykey_signkey_index = 3
const keydata = await this.eosJsonRpc.get_table_rows({json:true, code:mgrcontract, scope:name, table:'keydata', lower_bound: mykey_signkey_index, limit:1})
if(!keydata) return '';
return keydata.rows[0].key.pubkey;
}
/**
* 验证已签名的数据
* Parameters
* signature (string | Buffer) buffer or hex string
* data (string | Buffer)
* pubkey (pubkey | PublicKey)
* encoding (optional, default 'utf8')
* hashData boolean sha256 hash data before verify (optional, default true)
*/
ecc.verify(signature, data, pubkey) === true
Node

var ecc = require('eosjs-ecc')
var eos = require('eosjs')
var fetch = require('node-fetch');
var priv = '5JbrPk2h9kNtsmKTauKar5PtmE5nPhtF8BcVUSzGrZhFV7UvccK' //only for test
var data = 'test string'
function SignWithEOS() {
var signature = ecc.sign(data, priv)
console.log(signature)
return signature
}
var signature = SignWithEOS()
function VerifyWithEOS() {
var pub = ecc.privateToPublic(priv)
var verified = ecc.verify(signature, data, pub, 'utf8', false)
console.log(verified)
return verified
}
VerifyWithEOS()
// 获取Rerserve Key
function GetReserveKey() {
var rpc = new eos.JsonRpc('https://mainnet.eoscannon.io', { fetch })
var resp = rpc.get_table_rows({
json: true,
code: 'mykeymanager',
scope: 'mykeyhulu521', //test account
table: 'keydata',
limit: 10,
reverse: false,
show_payer: false
});
resp.then(function (data) {
console.log("Reserved public key: ", data.rows[3].key.pubkey)
})
}
GetReserveKey()

Java

POM加入依赖包:

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.59</version>
</dependency>
<dependency>
<groupId>com.google</groupId>
<artifactId>bitcoinj</artifactId>
<version>0.11.3</version>
</dependency>

示例代码:

import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.Base58;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import java.math.BigInteger;
import java.security.*;
import java.util.Arrays;
public class EOSEccUtil {
public static final String LEGACY_SIG_PREFIX = "EOS";
public static final String ECC_CURVE_NAME = "secp256k1";
public static final String SIG_PREFIX = "SIG_K1_";
static {
Security.addProvider(new BouncyCastleProvider());
}
//验签
public static boolean verify(String publKey, String data, String sign) throws Exception {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] digest = sha256.digest(data.getBytes());
Boolean verified = verifySignature(digest,sign,publKey);
return verified;
}
private static boolean verifySignature(byte[] digest, String signature, String publicKeyWif) throws KeyException, AddressFormatException {
SignatureComponents signatureComponents = checkAndDecodeSignature(signature);
byte[] xBytes = Base58.decode(publicKeyWif.substring(3));
xBytes = Arrays.copyOfRange(xBytes, 0, xBytes.length - 4);
ECNamedCurveParameterSpec paramsSpec = ECNamedCurveTable.getParameterSpec(ECC_CURVE_NAME);
ECDomainParameters curve = new ECDomainParameters(
paramsSpec.getCurve(),
paramsSpec.getG(),
paramsSpec.getN(),
paramsSpec.getH());
boolean verified = false;
BigInteger r = signatureComponents.r;
BigInteger s = signatureComponents.s;
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(curve.getCurve().decodePoint(xBytes), curve);
signer.init(false, params);
try {
verified = signer.verifySignature(digest, r, s);
} catch (NullPointerException ex) {
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
throw new KeyException("verify error");
}
return verified;
}
public static SignatureComponents checkAndDecodeSignature(
final String signatureString)
throws KeyException, IllegalArgumentException, AddressFormatException {
SignatureComponents components = null;
try {
// Verify the private key string is properly formatted
if (!signatureString.startsWith(LEGACY_SIG_PREFIX)
&& !signatureString.startsWith(SIG_PREFIX)) {
throw new IllegalArgumentException("Unrecognized Signature format");
}
// Check the encoding of the Signature (e.g. EOS/WIF, SIG_K1)
boolean legacy = signatureString.startsWith(LEGACY_SIG_PREFIX);
// Remove the prefix
String trimmedPrivateKeyString;
if (legacy) {
trimmedPrivateKeyString = signatureString.replace(LEGACY_SIG_PREFIX, "");
} else {
trimmedPrivateKeyString = signatureString.replace(SIG_PREFIX, "");
}
// Decode the string and extract its various components (i.e. R, S, i)
byte[] decodedBytes = Base58.decode(trimmedPrivateKeyString);
byte i = decodedBytes[0];
byte[] rBytes = Arrays.copyOfRange(decodedBytes, 1, 33);
byte[] sBytes = Arrays.copyOfRange(decodedBytes, 33, 65);
byte[] checksum = Arrays.copyOfRange(decodedBytes, 65, 69);
// Verify the checksum is correct
byte[] calculatedChecksum = ripemd160(
new byte[]{i},
rBytes,
sBytes,
"K1".getBytes());
calculatedChecksum = Arrays.copyOfRange(calculatedChecksum, 0, 4);
if (!Arrays.equals(checksum, calculatedChecksum)) {
throw new KeyException("Signature Checksum failed");
}
// Construct a SignatureComponents object from the components
components = new SignatureComponents();
components.r = new BigInteger(1, rBytes);
components.s = new BigInteger(1, sBytes);
components.i = i;
} catch (NoSuchAlgorithmException e) {
throw new KeyException("Failed to decode Signature", e);
}
return components;
}
private static byte[] ripemd160(byte[]... inputs) throws NoSuchAlgorithmException {
byte[] hash = null;
MessageDigest ripemd160 = MessageDigest.getInstance("RIPEMD160");
for (byte[] input : inputs) {
ripemd160.update(input);
}
hash = ripemd160.digest();
return hash;
}
/**
*
*/
public static class SignatureComponents {
public BigInteger r;
public BigInteger s;
public byte i;
@Override
public String toString() {
return "SignatureComponents{\n" +
" r=" + r + "\n" +
" s=" + s + "\n" +
" i=" + i +
'}';
}
}
public static void main(String[] args) throws Exception {
String pubkey = ""; //ReservedKey, mykey返回,或者从EOS链上查询
String data = ""; //未签名数据,格式为:timestamp + account + uuID + ref
String signature = ""; //签名
boolean isTrue = EOSEccUtil.verify(pubkey, data, signature);
System.out.println("verify result " + isTrue);
}
}