2

I am trying to do a request to Gate.io cryptocurrency market and getting this error. {"label":"INVALID_SIGNATURE","message":"Signature mismatch"}

C++ code:

std::string hmac_sha512(const std::string& key, const std::string& data) {
 unsigned char* digest = HMAC(EVP_sha512(), key.c_str(), key.length(),
 reinterpret_cast<const unsigned char*>(data.c_str()), data.length(), nullptr, nullptr);
 std::stringstream ss;
 for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) {
 ss << std::hex << std::setw(2) << std::setfill('0') << (int)digest[i];
 }
 return ss.str();
}
CreateOrder(){
std::string host = "api.gateio.ws";
std::string prefix = "/api/v4";
std::string path = "/spot/orders";
std::string method = "POST";
//https://api.gateio.ws/api/v4/spot/orders
std::string payload = "{\"text\":\"t-123456\",\"currency_pair\":\"ETH_BTC\",\"type\":\"limit\",\"account\":\"spot\",\"side\":\"buy\",\"iceberg\":\"0\",\"amount\":\"1\",\"price\":\"5.00032\",\"time_in_force\":\"gtc\",\"auto_borrow\":false,\"stp_act\":\"cn\"}";
std::string hashJsonPayload = hmac_sha512(secretKey, payload);
std::string timestamp_seconds= std::to_string(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
std::string queryParam = "";
std::string sign_string = method + "\n" + "/api/v4/spot/orders" + "\n" + queryParam + "\n" + hashJsonPayload + "\n" + timestamp_seconds;
std::string signHash = hmac_sha512(secretKey, sign_string);
req_.method(boost::beast::http::verb::post);
req_.target(r.target);
req_.version(11);
req_.set(boost::beast::http::field::host, r.host);
req_.set(boost::beast::http::field::accept, "application/json");
req_.set(boost::beast::http::field::content_type, "application/json");
req_.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req_.set("KEY", api_key); // API anahtarı ekleniyor
req_.set("Timestamp", timestamp_seconds);
req_.set("SIGN", signHash);
..
}
 

I am trying change payload but not working std::string payload1 = "{"text":"t-123456","currency_pair":"ETH_BTC","type":"limit","account":"spot","side":"buy","iceberg":"0","amount":"1","price":"5.00032","time_in_force":"gtc","auto_borrow":false,"stp_act":"cn"}";

std::string payload2 = R"(
{
 "text": "t-123456",
 "currency_pair": "ETH_BTC",
 "type": "limit",
 "account": "spot",
 "side": "buy",
 "iceberg": "0",
 "amount": "1",
 "price": "5.00032",
 "time_in_force": "gtc",
 "auto_borrow": false,
 "stp_act": "cn"
}
)";

std::string payload3= "{"account":"spot","currency_pair":"" + currencyPair + "","type":"market","side":"" + side + "","amount":"" + amount + ""}";

asked Jan 2, 2024 at 13:07
1
  • I've just verified that payload1, payload2 are exactly identical to payload in the code snippet? What is the precise question? Commented Jan 2, 2024 at 13:34

2 Answers 2

2

The documentation here:

HexEncode(SHA512(Request Payload))

Hash the request body with SHA512 and output its Hex encoded form. If no request body, use empty string's hashed result, i.e. cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e

Note that you need HexEncode(SHA512(Request Payload)), you used hmac_sha512 which is a key-based encryption based on SHA512, but not the same. This is easily proven by the fact that the hash for the empty payload is given.

DEMO

Attempted fix:

Live On Coliru

#include <chrono>
#include <iostream>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <span>
using namespace std::chrono_literals;
static constexpr auto now = std::chrono::system_clock::now;
using Bytes = std::span<unsigned char>;
static std::string hex_digest(Bytes hash) {
 std::string result(hash.size() * 2, '0');
 for (auto out = result.begin(); int b : hash) {
 static constexpr char digits[] = "0123456789abcdef";
 *out++ = digits[b >> 4];
 *out++ = digits[b & 0xF];
 }
 return result;
}
static std::string sha512(std::string const& data) {
 std::array<unsigned char, SHA512_DIGEST_LENGTH> md;
 unsigned n = md.size();
 if (!EVP_Digest(reinterpret_cast<unsigned char const*>(data.data()), data.length(), //
 md.data(), &n, EVP_sha512(), nullptr))
 throw std::runtime_error(__FUNCTION__);
 return hex_digest(md);
}
static std::string hmac_sha512(std::string const& key, std::string const& data) {
 unsigned char* digest =
 HMAC(EVP_sha512(), key.c_str(), key.length(), reinterpret_cast<unsigned char const*>(data.c_str()),
 data.length(), nullptr, nullptr);
 return hex_digest(Bytes(digest, SHA512_DIGEST_LENGTH));
}
int main() {
 struct {
 std::string target = "/", host = "host";
 } r;
 std::string host = "api.gateio.ws", prefix = "/api/v4", path = "/spot/orders", method = "POST";
 r = {prefix + path, host};
 std::string payload =
 "{\"text\":\"t-123456\",\"currency_pair\":\"ETH_BTC\",\"type\":\"limit\",\"account\":\"spot\","
 "\"side\":\"buy\",\"iceberg\":\"0\",\"amount\":\"1\",\"price\":\"5.00032\",\"time_in_force\":"
 "\"gtc\",\"auto_borrow\":false,\"stp_act\":\"cn\"}";
 std::string timestamp_seconds = std::to_string(now().time_since_epoch() / 1s);
 std::string queryParam = "";
 std::string sign_string =
 method + "\n" + r.target + "\n" + queryParam + "\n" + sha512(payload) + "\n" + timestamp_seconds;
 std::cout << sign_string << "\n\n" << hmac_sha512("secret", sign_string) << "\n";
}

Demo:

enter image description here

answered Jan 2, 2024 at 13:49
Sign up to request clarification or add additional context in comments.

1 Comment

Added fix with minimalized live demo
0

Thank you very much, the point I missed was encrypting the payload with hmac_sha512, when I encrypted it with sha512, the signature error was resolved.

std::string sha512(const std::string& input) {
unsigned char hash[SHA512_DIGEST_LENGTH];
SHA512_CTX shaContext;
SHA512_Init(&shaContext);
SHA512_Update(&shaContext, input.c_str(), input.length());
SHA512_Final(hash, &shaContext);
std::stringstream ss;
for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
 ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
}
return ss.str(); }
std::string hmac_sha512(const std::string& key, const std::string& data) {
unsigned char* digest = HMAC(EVP_sha512(), key.c_str(), key.length(),
 reinterpret_cast<const unsigned char*>(data.c_str()), data.length(), nullptr, nullptr);
std::stringstream ss;
for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) {
 ss << std::hex << std::setw(2) << std::setfill('0') << (int)digest[i];
}

I use two functions this way.

answered Jan 2, 2024 at 18:57

1 Comment

Note that the _Init/_Update/_Final interface per-algorithm is documented as deprecated (which is what made me choose the simpler high level interface)

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.