MYKEY Docs
中文English
English
English
  • Introduction
  • Sign in with MYKEY
    • Verify signature at backend server
    • More detailed tech and process of MYKEY login
  • Integrate with MYKEY
    • Multiple ways to integration with MYKEY
    • Mobile Dapp with H5 pages
      • ETH
      • EOS
      • JS Extensions
    • Integration via MYKEY Android SDK
      • Preconditions
      • Initiate SDK
      • Authorize
      • Sign
      • Transfer
      • Call contracts
    • Integration via MYKEY iOS SDK
      • Preconditions
      • Initiate SDK
      • Authorize
      • Sign
      • Transfer
      • Call contracts
    • Web application with scanning qrcode
    • SimpleWallet Protocol Compatible
    • Deeplink Protocol
  • Dive into MYKEY
    • Dive into MYKEY account
    • Classes and methods
      • Android Classes
      • iOS Classes
    • Error Code
    • Identify MYKEY deposit transaction
      • ETH deposit
      • EOS deposit
    • MYKEY Whitepaper
  • KEY ID
    • KEYID ETH Contracts introduction
      • Account Module
      • Account Storage Module
      • Logic Management Module
      • Logic Module
    • Account recovery mechanism
    • KEYID contract upgrade process
    • KEYID contract upgrade records
      • ETH
        • KEY ID Ethereum Contracts Upgrade Pending Time Adjustment
        • KEY ID Ethereum logic contract module upgrade announcement
        • KEY ID Ethereum Contracts Upgrade Pending Time Adjustment
        • KEY ID Ethereum AccountLogic/DualsigsLogic Contracts Upgrade
        • KEY ID Ethereum DappLogic Contract Upgrade
  • Development Resources
    • Ethereum
    • EOS
  • Join Us
    • DApp submit
    • Developers Community
Powered by GitBook
On this page

Was this helpful?

  1. Sign in with MYKEY

Verify signature at backend server

PreviousSign in with MYKEYNextMore detailed tech and process of MYKEY login

Last updated 4 years ago

Was this helpful?

MYKEY uses the which index is 3 to sign data. Server side of dapps need find the Reserved Key first, then verify the signature.

ETH:

The contract Abi for query Reserved Key is file AccountStorage:

Dapp pass parameters timestamp, account, uuID, ref to MYKEY, MYKEY will sign message with below rules: let message = hex(timestamp + account + uuID + ref)

let hashedMessage = crypto.Keccak256Hash(common.FromHex(unsignData)).Bytes()

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

For authorize method, MYKEY will return mykeyUID and mykeyUIDSignature fields, the signature rule for mykeyUID is:

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

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

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"
	//unsigned data
	data := "0x1234"

	prvKey, _ := hex.DecodeString(ETHPrv)
	sig, _ := SignWithEthWeb3(data, prvKey)
	log.Println("sig:", sig)

	unsignDataHashByte := crypto.Keccak256Hash(common.FromHex(data)).Bytes()
	// verify signature at server backend
	log.Println("verify sign:", verifySig(strings.ToLower(ETHAdd), sig, unsignDataHashByte))
}

func SignWithEthWeb3(unsignData string, privateByte []byte) (signature string, err error) {
	// need to Hash first
	unsignDataHashByte := crypto.Keccak256Hash(common.FromHex(unsignData)).Bytes()
	return SignWithEth(unsignDataHashByte, privateByte)
}

func SignWithEth(unsignData, privateKeyByte []byte) (signature string, err error) {
	// web3 signature need hash with salt, then sign the hashed data
	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
	}
	
	signatureByte[64] += 27
	return hexutil.Encode(signatureByte), nil
}

// verify signature
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
}

// generate hashed data for signing
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/v/English/dive-into-mykey/mykey-on-eos#mykey-account-structure
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. Method 'web3.personal.sign' should be used when using MYKEY to sign. Here it's only for test that we provide private key to sign message and verify message.
web3.personal.sign("hello", web3.eth.coinbase, console.log); 
let message = 'hello' 
let messageHash = web3.utils.soliditySha3(message);
let privKeyHex = '78e2219400da88378b746499ec8ff0d6aa97f806950276f28c65b9d569f32f84' // 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())


// Try recover only
async function recoverOnly() {
    // recover
    let message = "raw message"
    let messageHash = web3.utils.soliditySha3(message);
    let sig = '0x1dc17f7413edaa2d69f59f489af7d7e82a7e5184b65036b6a6147734b17728796a5932aa0c1f1455cafc80e208d55adeb2a8400777c1fa01'
    let recovedKey = await web3.eth.accounts.recover(messageHash, sig)
    let expectedKey = "0x0b2144B2c8430ecde7d4"  //expected reserved key
    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)
    })

Add dependencies to pom.xml:

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

Sample Code:

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 from MYKEY or ETH blockchain
        String unsignedData = "";  //unsigned data, the rule is: timestamp + account + uuID + ref
        byte[] signature = Numeric.hexStringToByteArray("Input Signature Here");  
        boolean isTrue = ETHEccUtil.verifyPrefixedMessage(Hash.sha3(unsignedData.getBytes()), signature, Numeric.cleanHexPrefix(pubKey).toLowerCase());
        System.out.println("verify sig " + isTrue);
    }
}

EOS:

Dapp pass parameters timestamp, account, uuID, ref to MYKEY, MYKEY will sign message with below rules: let unsignedData = timestamp + account + uuID + ref

For authorize method, MYKEY will return mykeyUID and mykeyUIDSignature fields, the signature rule for mykeyUID is:

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

/**
 * Get ReservedKey in mykey account which is for other actions without specific operation keys, details in
 * https://docs.mykey.org/v/English/dive-into-mykey/mykey-on-eos#mykey-account-structure
 * @param  {String}  name account name
 * @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;
}

/**
 * Verify signed data
 * 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()

Add dependencies to pom.xml:

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

Sample Code:

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 from MYKEY or EOS blockchain
        String data = "";     //unsigned data, the rule is: timestamp + account + uuID + ref
        String signature = ""; //Signature
        boolean isTrue = EOSEccUtil.verify(pubkey, data, signature);
        System.out.println("verify result " + isTrue);
    }
}

13KB
AccountStorage.abi.json
Reserved Key