MYKEY Docs
中文English
中文
中文
  • 简介
  • 基于MYKEY的第三方授权登录
    • 验签代码示例
    • 更多MYKEY登录的技术原理和流程
  • 接入MYKEY
    • 多种方式接入MYKEY
    • Mobile H5页面接入
      • ETH
      • EOS
      • JS功能扩展
    • MYKEY Android SDK接入
      • 环境准备
      • 初始化SDK
      • 认证
      • 签名
      • 转账
      • 合约调用
    • MYKEY iOS SDK接入
      • 环境准备
      • 初始化SDK
      • 认证
      • 签名
      • 转账
      • 合约调用
    • Web应用扫码接入
    • SimpleWallet协议接入
    • Deeplink协议接入
  • 深入MYKEY
    • 深入MYKEY账户
    • 类和方法定义
      • Android类
      • iOS类
    • 错误码
    • 识别MYKEY合约充值
      • ETH充值
      • EOS充值
    • MYKEY白皮书
  • KEY ID
    • KEYID ETH合约介绍
      • 创建账户模块
      • 数据存储模块
      • 逻辑模块
      • 逻辑管理模块
    • 账户恢复机制
    • KEYID合约升级流程
    • KEYID合约升级记录
      • ETH
        • KEY ID以太坊合约升级保护期参数调整公告
        • KEY ID以太坊逻辑合约模块升级公告
        • KEY ID以太坊合约升级保护期参数调整公告
        • KEY ID以太坊 AccountLogic/DualsigsLogic 合约升级公告
        • KEY ID以太坊 DappLogic 合约升级公示
  • 开发资源
    • ETH
    • EOS
  • 加入我们
    • 项目提交上架
    • 开发者社区
Powered by GitBook
On this page

Was this helpful?

  1. 基于MYKEY的第三方授权登录

验签代码示例

Previous基于MYKEY的第三方授权登录Next更多MYKEY登录的技术原理和流程

Last updated 4 years ago

Was this helpful?

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

ETH:

查询用的AccountStorage合约文件为:

第三方应用传递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

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))
}


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. 签名消息。使用mykey时,直接调用web3.personal.sign签名即可。以下提供私钥签名,仅为了测试签名和验签。
web3.personal.sign("hello", web3.eth.coinbase, console.log); 
let message = 'hello' 
let messageHash = web3.utils.soliditySha3(message);
let privKeyHex = '78e22x19400da88318b74649ec8ff0d6aa9x7f8062950276f28xc65b9d569f32f84' // prvkey of '0xd2F9b4652D80FA870207C2b421B8437d7D54a484' 
let privKey = Buffer.from(privKeyHex, 'hex') 
let msgParams = { data: messageHash }
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())



// 验证签名:message是待处理的data;sig是已签名的数据;expectedKey是期望的ReservedKey
async function recoverOnly() {
    // recover
    let message = "xxxxx"
    let messageHash = web3.utils.soliditySha3(message);
    let sig = '0x1dc17f7413edaa2d696a40a2298cca98cfff59f489af7d7e82a7e5184b65036b6a6147734b17728796a5932aa0c1f1455cafc80e208d55adeb2a8400777c1fa01'
    let recovedKey = await web3.eth.accounts.recover(messageHash, sig)
    let expectedKey = "0x0b2144B2c8430ecde7d4ED7xxxxxx"
    console.log( expectedKey.toLowerCase() == recovedKey.toLowerCase())
}

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)
    })

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

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)
   }

/**
 * 获取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

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()

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);
    }
}

Reserved Key
13KB
AccountStorage.abi.json