NAV
go php js java

欢迎使用 CipherBC API

接入准备

1 申请APP_ID

联系客服人员申请APP_ID,需要提供相关资质信息。 例如:公司名称,域名,IP, 第一负责人及其他商务需要信息。

2 提供需要支持的主链与合约币种清单

合约币需要合约地址、所属主链、精度、名称等基本信息给到业务对接人。

3 接口测试环境调试

接口测试环境对接,联调,演练。 接口采用 RSA2048位 双向签名验签形式确保通信可信性。 商户需要提前将公钥给到平台,用于平台对商户请求参数验签。 商户需要提前获取平台公钥,用于对平台响应结果验签。

支持公链

币名 全名称 区块链浏览器地址
btc btc https://blockchair.com/bitcoin
eth eth https://etherscan.io
bnb_bsc BSC(BNB Smart Chain) https://bscscan.com
qtum qtum https://explorer.qtum.org
trx Tron https://tronscan.io
a Vaulta(原eos升级) https://vaultascan.io/
xrp xrp https://xrpscan.com
doge doge https://dogechain.info
ltc ltc https://litecoinblockexplorer.net
dash dash https://dashblockexplorer.com
dcr dcr https://dcrdata.decred.org
bch Bitcoin Cash https://blockchair.com/bitcoin-cash
fil fil https://filscan.io
sol solana https://explorer.solana.com
pol Polygon https://polygonscan.com
avax_c avax c chain https://snowtrace.io
avax_x X chain of avax https://explorer-xp.avax.network
lunc lunc https://finder.terra.money/classic
bsv Bitcoin SV https://www.bitcoinsv.io
atom atom https://www.mintscan.io/injective
eth_optimism optimism https://optimistic.etherscan.io
eth_arbitrum arbitrum https://arbiscan.io
ada cardano https://cardanoscan.io
core core https://scan.coredao.org
eth_zksync ZKsync Era https://explorer.zksync.io
algo Algorand https://algoexplorer.io
sui Sui https://suiexplorer.com
eth_base Base https://basescan.org
ton Toncoin https://tonviewer.com
smart smart https://smartexplorer.com
tfuel_theta TFuel on Theta https://www.thetascan.io
kas Kaspa https://explorer.kaspa.org
assethub_dot AssetHub Polkadot https://assethub-polkadot.subscan.io
apt aptos https://explorer.aptoslabs.com
dag dag https://mainnet.dagexplorer.io
eth_deriw Deriw https://explorer.deriw.com/
hype Hyperliquid https://app.hyperliquid.xyz/explorer
main_bnb opBNB https://opbnbscan.com/
mnt Mantle https://mantlescan.xyz/
s Sonic https://sonicscan.org/

签名规则

<?php

$obj = new obj(); $obj->coin_list();

class obj { public $baseUrl = 'http://api.xxxxx.com/shopapi'; public $priKey = "-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8d6RGCeQkrobx +4xO1jXnHwiMU48UyOaRaNW2FMQrN9DcK/T2h58FyuA7kYN0CVNFEnaZQS9S8Nur Tl0lOVJcbwKfCSnu9nmFYDvPWqlCTqQW3F83sKNi9IWrMFFny3CX35eUHpeJahE2 R9wma2oJIFJv3ux/I3+w8fQQ6zMPIqpDx4Tcu9sCVpot+/siWJDpyiQBlbXfcxuH YOePUGx+tJ5HLXtJOUtPcgyhFTXRmW9r17DnnsjrT5Vc50IbN//JXbvr96eexQ/e /IXHZP6MZWdthjuv9y2TaxS+JMnpKLrXfgazoQEsKmchAsTLEyX6tHG5tHq+3MaK BSTFCoHHAgMBAAECggEAYodR1GPbZ+nuFpfhlF2ctIWe3G3awYz+LUrXX2ef3tBB WrmISVJBqq+TZtD+Hi254MR0SE6xxPSygu5m98zt5/EJN8d8qrckdLwIyLCnCElA s8rl5KFt6Y/YSdTq2g6bgGqePgt4QZi4At6yk0tYlgjV0DKFBTqzyPfVFvs/KEuZ o8le0XjgJA+Rnti35Gi5UDL1Tn9LcmCU/JcdpksFgPvU/wPmlNb8azM68ougXcrf hBebSRWbS0F2OXRJd7+hrTq+pwTNIH+IyzsUZHQCcH0tFe2Q304oDuVuwX5VxB3S tYG8+LoECW1nKalJbolkmdRqVxZRXG/Ho8/aRCHBcQKBgQDBmoZzUaKU2F4Sa6P+ 5vCQ5RAAiAATWao/WYfL9BBVnLzbkbzget3BNdJcJZKp1Avh6F/jeB0xUOD0aFgm rJAZjVAFu77damKWwiEPxKfdgSbBedV/KWCMASzr4m4cdYKOcmlLFsh+Du2+oZW5 3Mn35hyfbTf1OyYxrvOVrhSu+wKBgQD5NVfxWTBsugAE++d4CCy8TUL7QCqb1I1v EQ0FNdZsmXslPbCFlJ5UCFJXAF6s2x0MDKCl7y2ixlDo31H7DI6xlOP5kjYon7wr mB0U2bclGtlJ3FvCwDBFkfZykYZqPyey8A3D+kf4pnvZvn/0BgKfWCVSYnqEB4mp BI5WhywOpQKBgH4Mio8YTn8UZCMgG/UBDQ15ZbLC2THABj0aoHRkoiHrW3ala1DI DgsWbLB78gJKQbZCofOqp28NVnkqWoJLhcJtI/Wnp/vmoOvA1t+6TQTFaqM7HIqf OPHObh3iHYi/5VGzIeS2n7CbLaUY9S++lWymUGpoJK+wZAyi+IqyfVrzAoGAPKmI g75zNDQ+pvfuy2AqC5g/Esv200H1P7EF21vUm1DtEg6nNo0L0WbJLFZxtWAM/Q7h 2CKVmEbwEQ+zdAoFfiLBL9Iwjsj7VB1ODvJES6ryc7FJQOnpljXGqPLVpm1DK6KV pzv4YwWbplBh8zO3VbRuOVSxgAwZrWPnhhaXWeUCgYEAwVHYXN1qGTBS1uyrldDr OMuXyfvE7lbfJlg2EfG2A7PtTooPkh1IbRjlJ8ACbiadM0pYhnGGPk0D1WSKvRxU SmSc6PgW2weANQ8QCemo2V/zZ0DlWFZDdCV+7jMbAFBhQHcVh0qUr+uNyntjLTjj OPdMSnhKLoKtleu+NiMIogc= -----END PRIVATE KEY-----"; public $pubKey = "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7K9s+r82lofBFaj3KWp o/5E1ryDPDlLGZ4Rv68DcoCxV/bv0IvYX3cleC0WvTpwbdTpGtLjS6RHj89JREhi svv5qmk5YTfjEqd3RmtYDmOVz2Ry6E76wLh9qi1pqpneMGr3shvEzZC6gkieXRCA MOlEYbE7TSzxxDJxmovdeElowdRZcvwwPLvh/BLmCn+H1MXfdx01c/eFpTvu8BZG 1m7AqDOqnaPcR1ru/ekJoIwotF1jCPM2E5kAbQQutNaeSXhLbkfom4bxqF2/ni6k Cwnz3fbBYcXP3Ww3/AdI1dn0iLr6450qn72yBpDtQY8OZ7+Krff4Fa5jOBY7OHEN FwIDAQAB -----END PUBLIC KEY-----";

public function coin_list() { $data = $this->get_public_data(); $data["chain"] = "eth"; $data["coin"] = "eth"; $data['sign'] = Signature::encryption($data, $this->priKey); $res = self::curlByPost($this->baseUrl . '/coin/list', $data); print_r($res); // 验签 $resp = json_decode($res, true); if (Signature::checkSignature($resp["sign"], $resp, $this->pubKey)) { print_r("验签通过"); } else { print_r("验签失败"); } }

public static function curlByPost($url, $data = null, $timeout = 60) { $SSL = substr($url, 0, 8) == "https://" ? TRUE : FALSE; $ch = curl_init(); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_URL, $url); if ($SSL) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); } $data = json_encode($data);

$headers = [ 'Content-Type: application/json; charset=utf-8', 'Content-Length:' . strlen($data) ]; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $output = curl_exec($ch); return $output;

}

public function get_public_data() { $data['app_id'] = "xxxxxx";
$data['version'] = '1.0'; $data['key_version'] = 'admin'; $data['time'] = time(); return $data; } }

class Signature { /** * 私钥签名 * * @param $data * @param $private_key */ public static function encryption($data, $private_key) { if (is_array($data)) $signString = self::getSignString($data); else $signString = $data; $privKeyId = openssl_pkey_get_private($private_key); $signature = ''; openssl_sign($signString, $signature, $privKeyId, OPENSSL_ALGO_MD5); openssl_free_key($privKeyId); return base64_encode($signature); }

/** * 使用对方的公钥验签,并且判断签名是否匹配 * * @param $sign * @param $data * @param $public_key * @return bool */ public static function checkSignature($sign, $data, $public_key) { $toSign = self::getSignString($data); $publicKeyId = openssl_pkey_get_public($public_key); $result = openssl_verify($toSign, base64_decode($sign), $publicKeyId, OPENSSL_ALGO_MD5); openssl_free_key($publicKeyId); return $result === 1 ? true : false; }

public static function getSignString($data) { unset($data['sign']); ksort($data); reset($data); $pairs = array(); foreach ($data as $k => $v) { if (is_array($v)) $v = self::arrayToString($v); $pairs[] = "$k=$v"; }

return implode('&', $pairs); }

private static function arrayToString($data) { $str = ''; foreach ($data as $list) { if (is_array($list)) { $str .= self::arrayToString($list); } else { $str .= $list; } }

return $str; } }

package main

import ( "crypto" "crypto/md5" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "github.com/imroc/req" "github.com/tidwall/gjson" "sort" "time" )

var ( BaseUrl = "http://api.xxxxx.com/shopapi" signPrivateKey = -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8d6RGCeQkrobx +4xO1jXnHwiMU48UyOaRaNW2FMQrN9DcK/T2h58FyuA7kYN0CVNFEnaZQS9S8Nur Tl0lOVJcbwKfCSnu9nmFYDvPWqlCTqQW3F83sKNi9IWrMFFny3CX35eUHpeJahE2 R9wma2oJIFJv3ux/I3+w8fQQ6zMPIqpDx4Tcu9sCVpot+/siWJDpyiQBlbXfcxuH YOePUGx+tJ5HLXtJOUtPcgyhFTXRmW9r17DnnsjrT5Vc50IbN//JXbvr96eexQ/e /IXHZP6MZWdthjuv9y2TaxS+JMnpKLrXfgazoQEsKmchAsTLEyX6tHG5tHq+3MaK BSTFCoHHAgMBAAECggEAYodR1GPbZ+nuFpfhlF2ctIWe3G3awYz+LUrXX2ef3tBB WrmISVJBqq+TZtD+Hi254MR0SE6xxPSygu5m98zt5/EJN8d8qrckdLwIyLCnCElA s8rl5KFt6Y/YSdTq2g6bgGqePgt4QZi4At6yk0tYlgjV0DKFBTqzyPfVFvs/KEuZ o8le0XjgJA+Rnti35Gi5UDL1Tn9LcmCU/JcdpksFgPvU/wPmlNb8azM68ougXcrf hBebSRWbS0F2OXRJd7+hrTq+pwTNIH+IyzsUZHQCcH0tFe2Q304oDuVuwX5VxB3S tYG8+LoECW1nKalJbolkmdRqVxZRXG/Ho8/aRCHBcQKBgQDBmoZzUaKU2F4Sa6P+ 5vCQ5RAAiAATWao/WYfL9BBVnLzbkbzget3BNdJcJZKp1Avh6F/jeB0xUOD0aFgm rJAZjVAFu77damKWwiEPxKfdgSbBedV/KWCMASzr4m4cdYKOcmlLFsh+Du2+oZW5 3Mn35hyfbTf1OyYxrvOVrhSu+wKBgQD5NVfxWTBsugAE++d4CCy8TUL7QCqb1I1v EQ0FNdZsmXslPbCFlJ5UCFJXAF6s2x0MDKCl7y2ixlDo31H7DI6xlOP5kjYon7wr mB0U2bclGtlJ3FvCwDBFkfZykYZqPyey8A3D+kf4pnvZvn/0BgKfWCVSYnqEB4mp BI5WhywOpQKBgH4Mio8YTn8UZCMgG/UBDQ15ZbLC2THABj0aoHRkoiHrW3ala1DI DgsWbLB78gJKQbZCofOqp28NVnkqWoJLhcJtI/Wnp/vmoOvA1t+6TQTFaqM7HIqf OPHObh3iHYi/5VGzIeS2n7CbLaUY9S++lWymUGpoJK+wZAyi+IqyfVrzAoGAPKmI g75zNDQ+pvfuy2AqC5g/Esv200H1P7EF21vUm1DtEg6nNo0L0WbJLFZxtWAM/Q7h 2CKVmEbwEQ+zdAoFfiLBL9Iwjsj7VB1ODvJES6ryc7FJQOnpljXGqPLVpm1DK6KV pzv4YwWbplBh8zO3VbRuOVSxgAwZrWPnhhaXWeUCgYEAwVHYXN1qGTBS1uyrldDr OMuXyfvE7lbfJlg2EfG2A7PtTooPkh1IbRjlJ8ACbiadM0pYhnGGPk0D1WSKvRxU SmSc6PgW2weANQ8QCemo2V/zZ0DlWFZDdCV+7jMbAFBhQHcVh0qUr+uNyntjLTjj OPdMSnhKLoKtleu+NiMIogc= -----END PRIVATE KEY----- signPublicKey = -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7K9s+r82lofBFaj3KWp o/5E1ryDPDlLGZ4Rv68DcoCxV/bv0IvYX3cleC0WvTpwbdTpGtLjS6RHj89JREhi svv5qmk5YTfjEqd3RmtYDmOVz2Ry6E76wLh9qi1pqpneMGr3shvEzZC6gkieXRCA MOlEYbE7TSzxxDJxmovdeElowdRZcvwwPLvh/BLmCn+H1MXfdx01c/eFpTvu8BZG 1m7AqDOqnaPcR1ru/ekJoIwotF1jCPM2E5kAbQQutNaeSXhLbkfom4bxqF2/ni6k Cwnz3fbBYcXP3Ww3/AdI1dn0iLr6450qn72yBpDtQY8OZ7+Krff4Fa5jOBY7OHEN FwIDAQAB -----END PUBLIC KEY----- )

func main() { pub := make(map[string]string) // 公共参数 pub["app_id"] = "xxxxxxxxx" pub["version"] = "1.0" pub["key_version"] = "admin" pub["time"] = fmt.Sprintf("%d", time.Now().Unix()) // 业务参数 pub["chain"] = "eth" pub["coin"] = "eth" pub["sign"] = getSign(pub)

url := fmt.Sprintf("%s/coin/list", BaseUrl) r := req.New() r.EnableInsecureTLS(true) post, err := r.Post(url, req.BodyJSON(&pub)) if err != nil { panic(err) } fmt.Println("返回", post.String()) // 验签 checkSign(post.String()) }

// 获取签名串 func getSign(data map[string]string) string { err, signStr := ApiSign(data, signPrivateKey) if err != nil { panic(err) } fmt.Println("签名串:", signStr) return signStr }

// 验证签名 func checkSign(resp string) { var res map[string]interface{} err := json.Unmarshal([]byte(resp), &res) if err != nil { panic(err) } sign := res["sign"].(string) data := ParseJsonToClassMap(resp) err = ApiVerifySign(data, signPublicKey, sign) if err != nil { fmt.Println("验签失败") return } fmt.Println("验签通过") }

/** * 本地私钥签名 * @param data 参数 * @param priKey 本地私钥 * @return error * @return string / func ApiSign(data map[string]string, priKey string) (error, string) { hashed := getSignString(data) // rsa block, _ := pem.Decode([]byte(priKey)) if block == nil { return errors.New("private key error"), "" } privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return err, "" } signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey.(rsa.PrivateKey), crypto.MD5, hashed) if err != nil { return err, "" } ciphertext := base64.StdEncoding.EncodeToString(signature)

return nil, ciphertext }

/** * 使用对方的公钥验签,并且判断签名是否匹配 * @param data 参数 * @param signed 签名体 * @param pubKey 公钥 * @return error / func ApiVerifySign(data map[string]string, pubKey string, signed string) error { hashed := getSignString(data) block, _ := pem.Decode([]byte(pubKey)) if block == nil { return errors.New("public key error") } // 解析公钥 pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return err } pub := pubInterface.(rsa.PublicKey)

decodeString, err := base64.StdEncoding.DecodeString(signed) if err != nil { return err } err = rsa.VerifyPKCS1v15(pub, crypto.MD5, hashed, decodeString) if err != nil { return err }

return nil }

// 拼接参数 func getSignString(data map[string]string) []byte { delete(data, "sign") var keys []string for k := range data { keys = append(keys, k) } sort.Strings(keys)

var signStr string for _, k := range keys { if signStr != "" { signStr = signStr + "&" + k + "=" + data[k] } else { signStr = k + "=" + data[k] } }

hashMd5 := md5.Sum([]byte(signStr)) hashed := hashMd5[:]

return hashed }

func ParseJsonToClassMap(jsonStr string) (m map[string]string) { m = make(map[string]string) result := gjson.Parse(jsonStr) result.ForEach(func(key, value gjson.Result) bool { if value.Type == gjson.JSON && gjson.Valid(value.String()) { m[key.String()] = JsonToString(value.String()) } else { m[key.String()] = value.String() } return true }) return }

func JsonToString(jsonStr string) string { str := "" r := gjson.Parse(jsonStr) r.ForEach(func(key, value gjson.Result) bool { if value.Type == gjson.JSON && gjson.Valid(value.String()) { str += JsonToString(value.String()) } else { str += value.String() } return true }) return str }

var crypto = require('crypto');

var priKey = -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8d6RGCeQkrobx +4xO1jXnHwiMU48UyOaRaNW2FMQrN9DcK/T2h58FyuA7kYN0CVNFEnaZQS9S8Nur Tl0lOVJcbwKfCSnu9nmFYDvPWqlCTqQW3F83sKNi9IWrMFFny3CX35eUHpeJahE2 R9wma2oJIFJv3ux/I3+w8fQQ6zMPIqpDx4Tcu9sCVpot+/siWJDpyiQBlbXfcxuH YOePUGx+tJ5HLXtJOUtPcgyhFTXRmW9r17DnnsjrT5Vc50IbN//JXbvr96eexQ/e /IXHZP6MZWdthjuv9y2TaxS+JMnpKLrXfgazoQEsKmchAsTLEyX6tHG5tHq+3MaK BSTFCoHHAgMBAAECggEAYodR1GPbZ+nuFpfhlF2ctIWe3G3awYz+LUrXX2ef3tBB WrmISVJBqq+TZtD+Hi254MR0SE6xxPSygu5m98zt5/EJN8d8qrckdLwIyLCnCElA s8rl5KFt6Y/YSdTq2g6bgGqePgt4QZi4At6yk0tYlgjV0DKFBTqzyPfVFvs/KEuZ o8le0XjgJA+Rnti35Gi5UDL1Tn9LcmCU/JcdpksFgPvU/wPmlNb8azM68ougXcrf hBebSRWbS0F2OXRJd7+hrTq+pwTNIH+IyzsUZHQCcH0tFe2Q304oDuVuwX5VxB3S tYG8+LoECW1nKalJbolkmdRqVxZRXG/Ho8/aRCHBcQKBgQDBmoZzUaKU2F4Sa6P+ 5vCQ5RAAiAATWao/WYfL9BBVnLzbkbzget3BNdJcJZKp1Avh6F/jeB0xUOD0aFgm rJAZjVAFu77damKWwiEPxKfdgSbBedV/KWCMASzr4m4cdYKOcmlLFsh+Du2+oZW5 3Mn35hyfbTf1OyYxrvOVrhSu+wKBgQD5NVfxWTBsugAE++d4CCy8TUL7QCqb1I1v EQ0FNdZsmXslPbCFlJ5UCFJXAF6s2x0MDKCl7y2ixlDo31H7DI6xlOP5kjYon7wr mB0U2bclGtlJ3FvCwDBFkfZykYZqPyey8A3D+kf4pnvZvn/0BgKfWCVSYnqEB4mp BI5WhywOpQKBgH4Mio8YTn8UZCMgG/UBDQ15ZbLC2THABj0aoHRkoiHrW3ala1DI DgsWbLB78gJKQbZCofOqp28NVnkqWoJLhcJtI/Wnp/vmoOvA1t+6TQTFaqM7HIqf OPHObh3iHYi/5VGzIeS2n7CbLaUY9S++lWymUGpoJK+wZAyi+IqyfVrzAoGAPKmI g75zNDQ+pvfuy2AqC5g/Esv200H1P7EF21vUm1DtEg6nNo0L0WbJLFZxtWAM/Q7h 2CKVmEbwEQ+zdAoFfiLBL9Iwjsj7VB1ODvJES6ryc7FJQOnpljXGqPLVpm1DK6KV pzv4YwWbplBh8zO3VbRuOVSxgAwZrWPnhhaXWeUCgYEAwVHYXN1qGTBS1uyrldDr OMuXyfvE7lbfJlg2EfG2A7PtTooPkh1IbRjlJ8ACbiadM0pYhnGGPk0D1WSKvRxU SmSc6PgW2weANQ8QCemo2V/zZ0DlWFZDdCV+7jMbAFBhQHcVh0qUr+uNyntjLTjj OPdMSnhKLoKtleu+NiMIogc= -----END PRIVATE KEY----- var pubKey = -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7K9s+r82lofBFaj3KWp o/5E1ryDPDlLGZ4Rv68DcoCxV/bv0IvYX3cleC0WvTpwbdTpGtLjS6RHj89JREhi svv5qmk5YTfjEqd3RmtYDmOVz2Ry6E76wLh9qi1pqpneMGr3shvEzZC6gkieXRCA MOlEYbE7TSzxxDJxmovdeElowdRZcvwwPLvh/BLmCn+H1MXfdx01c/eFpTvu8BZG 1m7AqDOqnaPcR1ru/ekJoIwotF1jCPM2E5kAbQQutNaeSXhLbkfom4bxqF2/ni6k Cwnz3fbBYcXP3Ww3/AdI1dn0iLr6450qn72yBpDtQY8OZ7+Krff4Fa5jOBY7OHEN FwIDAQAB -----END PUBLIC KEY-----

/** * 私钥签名 */ function encryption(data) { let signString = getSignString(data) let sign = crypto.createSign('md5WithRSAEncryption');

sign.update(signString, 'utf8'); sign.end(); let signature = sign.sign(priKey, 'base64');

return signature }

/** * 使用对方的公钥验签,并且判断签名是否匹配 * @param sign * @param data * @return bool */ function checkSignature(sign, data) { let signString = getSignString(data) let verify = crypto.createVerify('md5WithRSAEncryption'); verify.update(signString, 'utf8'); verify.end(); return verify.verify(pubKey, sign, 'base64'); }

// 拼接参数 function getSignString(data) { delete(data["sign"]) var keys = Object.keys(data) keys = keys.sort() let signStr = "" for(let k in keys) { if(typeof(data[keys[k]]) == "object") { signStr = keys[k] + "=" + arrayToString(data[keys[k]]) }else { if (signStr != "") { signStr = signStr + "&" + keys[k] + "=" + data[keys[k]] } else { signStr = keys[k] + "=" + data[keys[k]] } } } return signStr } function arrayToString(data) { let str = ''; for(let list in data) { if (typeof (data[list]) == "object") { str += arrayToString(data[list]); } else { str += data[list]; } } return str; }

//发送请求 function httpByPost(data) { let HTTP = require('http') let POST_OPTIONS = { port: 8888, host: "http://api.xxxxx.com/shopapi", path: "/coin/list", method: 'POST', headers: { "Content-Type": "application/json" } }; // 创建 http 连接 const REQUEST = HTTP.request(POST_OPTIONS, requestOnResponse) // 设置超时 REQUEST.setTimeout(6000)

// 通过连接发送数据 REQUEST.write(JSON.stringify(data), 'utf8') REQUEST.end() }

// 接受返回的数据验签 function requestOnResponse(incomingMessage){ let data = [] incomingMessage.on('data', chunk => { data.push(...chunk) })

incomingMessage.on('end', () => { let _date = JSON.parse( new TextDecoder().decode(new Uint8Array(data))) let res = checkSignature(_date["sign"], _date)

console.log(res) }) } /**测试*****/ let data = { 'app_id' : 'xxxxxxxxx', 'version' : '1.0', 'key_version' : 'admin', 'time' : Math.floor(Date.now()/1000), 'chain' :'eth', 'coin' : 'eth', } data['sign'] = encryption(data) httpByPost(data);

package com.junior.common;

import com.google.gson.*;

import java.io.; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.*; import java.util.stream.Collectors;

public class rsaSigner {

public static void main(String[] args) {

String baseUrl = "http://api.xxxxx.com/shopapi"; String priKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtdgvcfozY7fe/x6UlXR/Fff8+wX++CiG05YVRafDe90O8y5KJAMZ156TgajEUXSRyKZqASz7iVoMAcu5FqfK1KuOOdjTAg0LFe/twIZvKmAqHcCSS2pjUXk0fTpZEgQ5lmDCK12Mg07YXvr+BeHjF+kBpwRJMPMG/+DGBDYNFNeRSJqWyopt6yyF528soAykHIUQ7TTultJV5IPCrvjRQQrGdJ/QtCdE0vZg+tA09nFN7AqgraU99kIvzyS0sqysj+pP1SJRmTRSGtlVAO3mILfkXOekZl1KD60G7CF+7BIaaW83FPD55tZhAGhnbLMYyiq7OiIyU61LTRcNSaJ0tAgMBAAECggEBAIGgGKcSzxCBbMYdXLV6TPbZ/HeaRGrwyVWUu7cmc0E8CJu6iWvmb2jGzbiCwuCT5luF6ZZ8JKchvU4FlTfsE5r9TQY2IZ/Ht4s65qBaDUEts5iY3kv8HX9+ZSXDJZpV0ztqqsPmx4ZNj+NYwWXwcFiKdb5R8OmV8bgSsnPddD6wFhwewglUOBPRAjropHE4sqk1Vam6q1MlFq5kiekMn/CItyFYFFzST/ruqAtVTp7YIvazAMrfCKH+pvXWk6pYNHZmL/JZey0GglRyJ1nItiqL33X8Lji056FcbAclidq022h/KPLEJpZk6irWqYdqYZeF6YMLHtdZFDkTZ/hXY+UCgYEA5i794UTQ1Kd6K+iui/S8K5H8nGdiXb3+uZ5j5VKYfbRmzXhAdVYisYe8czeTECxhH+MeHgM4tLR4ifx9q60ylvZxl9Fav1p6EaE36YcvGVmpm7Gm3q6QDDBa4gB+X9u7CxCS4LpotV917YS1QvOIF9lnRonSj1zNzFtbXkywrN8CgYEAwOpx/mR40ZvgZxNCq9DsIdrSZYAFFKpw0xy0OtF1PpuwMaREoRabnlVw6Bb3Hofg50XA3JxemzAX6fB92ee+NfXfeL4UtC4vG1K9JyiXHtHq8p9Uw2W03ehIEeNa3xdoRRnLmD4VUpyfL19ViE2+7TM1Gd3TogjDT+AMOCSuq3MCgYBsurPH7gaq/LVT+mRAzgj4l8v4YUlwuGeTbIMJdvt7HXUWB4CDLH3U2CYnUpAQKrZyJok6ahEmIr1xiKggKP7lmmHL8eNo0icpHrtXfzi7Q8Q/PCpzs4dtioXTjaIkS5nNvzVyG/uL+RyuZmpsxrZ5dYM4KbAhchfwORMutxEZhwKBgQC5zpVw4jCEItBmNuTWO+nTScGvxTgfiXIVw+XLaQa2AJoZlhAL34yPWdffko79tv3lgweY9HsimZXO2rU8dbp8mo5c6ydhy8HPXUeWOcAkDSdv/ApWENW9jgYsRIC3swHY3Fl+Dv3Wjce8huQI3mjwaYvRmBhIToxfmHnscVhTBQKBgB9uciEHfz8hMI7ePrPo2PkyK/RbJDm3HnuWE2lFBp7MOUMlh53MXwv4dIRaYMbRrFuN1UIKEFxuKhWwTHSzZJufk/UENrha/81fpj2BoFsAKkEunKeETvaIh6fZSjec16h2+zxbzwkdS6b+yIYkSlaoAxmDYrMtae0C8G4e0YS9"; String pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2RZZISModRzproL3eTxRtvVK/nToDbeLmAVdmaVtHgNCwjXMeNCZvHySZKO7+t7XySzBd2PhhQjvAkDd8HRKB5Y0uvP6TFwvhxOMkMydTeBFcUobQWNeAI7ttRms1txSNrmyrZr0WpWfk79VIsO+yI/jFN/Pj2gxD+vh5UUKU1LD2IQq/VxgtEpf8Vs6w7SvafPwYkpdPGCKUFkRvN1FrY2+tq0j1MRVJbB2rIunWDnT8cDUh2sWLXf4yNaJVOaaTFB0zbAk/qcoicGQyW7bVG7dVW8WywKmOAPeQCCQa+SJ4BhfI1k8eEAwFVRuxz/f9Ecfog9hZJ+RgELkpS8qhQIDAQAB";

rsaSigner signer = new rsaSigner();

Map<String, Object> params = new HashMap<>(); params.put("app_id", "hxgmau9imt86qky9"); params.put("version", "1.0"); params.put("key_version", "admin"); params.put("time", Long.toString(System.currentTimeMillis() / 1000)); params.put("coin", "trx");

try { params.put("sign", signer.genSign(params, priKey)); Gson gson = new GsonBuilder().create(); System.out.println("params = " + gson.toJson(params));

String res = doPost(baseUrl + "/address/getBatch", gson.toJson(params)); if (null == res) { throw new RuntimeException("http error"); } System.out.println("response = " + res);

JsonParser jp = new JsonParser(); JsonObject resEle = jp.parse(res).getAsJsonObject(); boolean retSignOK = signer.verifySign(resEle, resEle.get("sign").getAsString(), pubKey); if (!retSignOK) { throw new RuntimeException("verify response sign fail"); }

} catch (Exception e) { e.printStackTrace(); }

System.out.println("OK"); }

public String genSign(Map<String, Object> data, String priKey) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException { Map<String, String> stringMap = toStringMap(new GsonBuilder().create().toJson(data)); return this.sign(this.genSignString(stringMap), priKey); }

public String genSignString(Map<String, String> params) { if (null == params || params.size() == 0) { return ""; }

List<String> sortedKeys = new ArrayList<>(params.keySet()); sortedKeys.remove("sign"); sortedKeys.sort(null); String sb = sortedKeys.stream().map(s -> s + "=" + params.get(s)).collect(Collectors.joining("&"));

System.out.println("genSignString = " + sb);

return sb; }

public String sign(String str, String priKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] decodedKey = Base64.getDecoder().decode(priKey.getBytes(StandardCharsets.UTF_8)); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); PrivateKey pk = keyFactory.generatePrivate(keySpec);

Signature signature = Signature.getInstance("MD5withRSA"); signature.initSign(pk); signature.update(str.getBytes(StandardCharsets.UTF_8)); return new String(Base64.getEncoder().encode(signature.sign()), StandardCharsets.UTF_8); }

public boolean verifySign(JsonElement data, String sign, String pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { Map<String, String> resMap = toStringMap(data); String signString = this.genSignString(resMap);

byte[] keyBytes = Base64.getDecoder().decode(pubKey.getBytes(StandardCharsets.UTF_8)); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey key = keyFactory.generatePublic(keySpec); Signature signature = Signature.getInstance("MD5withRSA"); signature.initVerify(key); signature.update(signString.getBytes(StandardCharsets.UTF_8)); return signature.verify(Base64.getDecoder().decode(sign.getBytes(StandardCharsets.UTF_8))); }

public static Map<String, String> toStringMap(String str) { JsonParser jp = new JsonParser(); JsonElement jEle = jp.parse(str); return toStringMap(jEle); }

public static Map<String, String> toStringMap(JsonElement jEle) { Map<String, String> resMap = new HashMap<>(); if (null != jEle && jEle.isJsonObject()) { for (Map.Entry<String, JsonElement> entry : jEle.getAsJsonObject().entrySet()) { resMap.put(entry.getKey(), valueAsString(entry.getValue())); } } return resMap; }

public static String valueAsString(JsonElement element) { if (element.isJsonNull()) { return ""; } else if (element.isJsonPrimitive()) { return element.getAsJsonPrimitive().getAsString(); } else if (element.isJsonObject()) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, JsonElement> entry : element.getAsJsonObject().entrySet()) { sb.append(valueAsString(entry.getValue())); } return sb.toString(); } else if (element.isJsonArray()) { StringBuilder sb = new StringBuilder(); for (JsonElement vv : element.getAsJsonArray()) { sb.append(valueAsString(vv)); } return sb.toString(); } return ""; }

public static String doPost(String httpUrl, String param) { //send http post request } }

创建私钥

openssl genrsa -out rsa_private_key.pem 2048

根据私钥创建公钥

openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout

签名算法详解

生成签名字符串, 对于如下的参数数组: user_id = 1 coin = eth address = 0x038B8E7406dED2Be112B6c7E4681Df5316957cad amount = 10.001 trade_id = 20220131012030274786

对数组里的每一个键从a到z的顺序排序,若遇到相同首字母,则看第二个字母,以此类推。排序完成之后,再把所有数组值以“&”字符连接起来,如$dataString:

address=0x038B8E7406dED2Be112B6c7E4681Df5316957cad&amount=10.001&coin=eth&trade_id=20220131012030274786&user_id=1
这串字符串便是拼接好的字符串。

公共信息说明

以下信息对所有接口均通用、后面每个接口不重复描述

生产环境

API接口地址请联系客服获得

测试环境

API接口地址请联系客服获得

请求公共参数

参数名 必选 类型 约束 案列 说明
app_id 是 string 系统生成 A6vZLiqyRYxu 商户号具体对接时候系统分配
version 是 string 固定 1.0 接口版本号-根据接入时候约定为准固定不变
sign 是 string 长度344位 Dr7MnhPYh… RSA-2048 位秘钥模式
time 是 string 长度 10 位 1578829811 时间戳-忽略时区以秒为单位的timestamp
key_version 否 string 动态枚举型 默认:admin 秘钥版本号:admin / read / arbitrary-可以针对任意1-n个接口授权

公共信息须知

名称 类型 案列 描述
时间1 timeStamp 1578829811 UTC 时间统一时间不带时区
时间2 DateTime 2020-01-09 15:48:42 全局系统如果出现这种格式默认为北京时间
请求方式 POST — 所有请求均为POST
编码 UTF-8 — 其他编码可能会产生乱码和错误
通信协议 HTTPS https 目前仅支持HTTPS
签名类型 RSA-2048位 Dr7MnhPYh… 点击查看规则详情
请求与返回 JSON — 所有请求和响应参数都是JSON格式
金额参数注意 string — 小数点后超8位,小数点前超12位无法通过,最后无效0要删除