Search code examples
c#httpbitcoincryptocurrency

Issue with authorization on Coinigy V2 API


I have been working on a project and i used to use the v1 api for coinigy and loved the API. The problem is now with the new v2 API, I have been having an issue with trying to work the private API with the hmac_sha256 signature. I originally contacts coinigy support, but with little luck was not able to get it fully working (they did help, but never solved the issue, which is very weird). I built a simple test project in C# to perform a private call with the documentation they had for their api.

program.cs:

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace coinigy_example
{
class Program
{

    static void Main(string[] args)
    {
        string api_key = "API_KEY";
        string api_secret = "API_SECRET";
        CoinigyApiPrivateCall call = new CoinigyApiPrivateCall(api_key, api_decret);


        string data = call.HttpPostRequest("private/exchanges/" + "BINA" + "/markets/" + "BTC" + "/" + "LTC" + "/ticker", "", null, "GET");
        string body = null;
        call.xcoinApiCall("private/exchanges/" + "BINA" + "/markets/" + "BTC" + "/" + "LTC" + "/ticker", "", ref body);


        try
        {
            JObject j = JObject.Parse(data);

            TickerDataCoinigy tt = new TickerDataCoinigy();
            tt.volume = (string)j["volume"];
            tt.last = (string)j["last"];
            tt.high = (string)j["high"];
            tt.low = (string)j["low"];
            tt.ask = (string)j["ask"];
            tt.bid = (string)j["bid"];

            Console.WriteLine("Binance, BTC-LTC ticker data:");

            Console.WriteLine("Volume: " + tt.volume);
            Console.WriteLine("Last: " + tt.last);
            Console.WriteLine("High: " + tt.high);
            Console.WriteLine("Low: " + tt.low);
            Console.WriteLine("ask: " + tt.ask);
            Console.WriteLine("bid: " + tt.bid);
        }
        catch(Exception i)
        {
            Console.WriteLine("");
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(data);
            Console.ForegroundColor = ConsoleColor.White;

        }

        Console.ReadLine();
    }


}

public class TickerDataCoinigy
{
    public string volume;
    public string last;
    public string high;
    public string low;
    public string ask;
    public string bid;
}
}

and also several versions (to see if a different way of calling the api would work) of a private call.

CoinigyApiPrivateCall.cs

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace coinigy_example
{
public class CoinigyApiPrivateCall
{
    private string api_key;
    private string api_secret;

    public CoinigyApiPrivateCall(string api_key, string api_secret)
    {
        this.api_key = api_key;
        this.api_secret = api_secret;
    }

    public static double ConvertToUnixTimestamp(DateTime date)
    {
        DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        TimeSpan diff = date.ToUniversalTime() - origin;
        return Math.Floor(diff.TotalMilliseconds);
    }

    private string Hash_HMAC(string sKey, string sData)
    {
        byte[] rgbyKey = Encoding.ASCII.GetBytes(sKey);


        using (var hmacsha256 = new HMACSHA256(rgbyKey))
        {
            byte[] inf = hmacsha256.ComputeHash(Encoding.ASCII.GetBytes(sData));

            return (ByteToString(inf));
        }
    }

    private string ByteToString(byte[] rgbyBuff)
    {
        string sHexStr = "";


        for (int nCnt = 0; nCnt < rgbyBuff.Length; nCnt++)
        {
            sHexStr += rgbyBuff[nCnt].ToString("x2"); // Hex format
        }

        return (sHexStr);


    }

    private byte[] StringToByte(string sStr)
    {
        byte[] rgbyBuff = Encoding.ASCII.GetBytes(sStr);

        return (rgbyBuff);
    }



    public string HttpPostRequest(string url, string au, List<KeyValuePair<string, string>> postdata, string httpType)
    {
        var client = new HttpClient();

        //client.DefaultRequestHeaders.Add("User-Agent", ua);
        //client.DefaultRequestHeaders.Add("X-API-KEY", api_key);
        //client.DefaultRequestHeaders.Add("X-API-SECRET", api_secret);
        string time = Convert.ToString(ConvertToUnixTimestamp(DateTime.Now.ToUniversalTime()));
        FormUrlEncodedContent content = null;
        string data = null;
        if (postdata != null)
        {
            content = new FormUrlEncodedContent(postdata);
            var output = Newtonsoft.Json.JsonConvert.SerializeObject(postdata);
            data = api_key + time + httpType.ToUpper() + "/api/v2/" + url  + output;
        }
        else
        {
            data = api_key + time + httpType.ToUpper() + "/api/v2/" + url;
        }
        string sign = Hash_HMAC(api_secret, data);
        client.DefaultRequestHeaders.Add("X-API-SIGN", sign);
        client.DefaultRequestHeaders.Add("X-API-TIMESTAMP", time);

        HttpResponseMessage response = null;
        if (httpType.ToUpper() == "POST")
        {
            response = client.PostAsync("https://api.coinigy.com/api/v2/" + url, content).Result;
        }

        if (httpType.ToUpper() == "GET")
        {
            response = client.GetAsync("https://api.coinigy.com/api/v2/" + url).Result;
        }

        if (httpType.ToUpper() == "PUT")
        {
            response = client.PutAsync("https://api.coinigy.com/api/v2/" + url, content).Result;
        }

        if (httpType.ToUpper() == "DELETE")
        {
            response = client.DeleteAsync("https://api.coinigy.com/api/v2/" + url).Result;
        }



        return response.IsSuccessStatusCode
            ? response.Content.ReadAsStringAsync().Result
            : "ERROR:" + response.StatusCode + " " + response.ReasonPhrase + " | " + response.RequestMessage;
    }

    public JObject xcoinApiCall(string sEndPoint, string sParams, ref string sRespBodyData)
    {
        string sAPI_Sign = "";
        string sPostData = sParams;
        string sHMAC_Key = "";
        string sHMAC_Data = "";
        string sResult = "";
        double nNonce = 0;
        HttpStatusCode nCode = 0;


        sPostData += "&endpoint=" + Uri.EscapeDataString(sEndPoint);

        try
        {
            HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://api.coinigy.com/api/v2/" + sEndPoint);
            byte[] rgbyData = Encoding.ASCII.GetBytes(sPostData);


            nNonce = ConvertToUnixTimestamp(DateTime.Now.ToUniversalTime());

            sHMAC_Key = this.api_secret;
            sHMAC_Data = api_key + nNonce.ToString() + "GET" +  "/api/v2/"   + sEndPoint;
            //sHMAC_Data = sEndPoint + (char)0 + sPostData + (char)0 + nNonce.ToString();
            sResult = Hash_HMAC(sHMAC_Key, sHMAC_Data);
            //sAPI_Sign = Convert.ToBase64String(StringToByte(sResult));
            sAPI_Sign = sResult;

            Request.Headers.Add("X-API-SIGN", sAPI_Sign);
            Request.Headers.Add("X-API-TIMESTAMP", nNonce.ToString());

            Request.Method = "GET";
            Request.ContentType = "application/x-www-form-urlencoded";
            Request.UserAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36";
            /*Request.ContentLength = rgbyData.Length;

            using (var stream = Request.GetRequestStream())
            {
                stream.Write(rgbyData, 0, rgbyData.Length);
            } */

            var Response = (HttpWebResponse)Request.GetResponse();

            sRespBodyData = new StreamReader(Response.GetResponseStream()).ReadToEnd();

            return (JObject.Parse(sRespBodyData));
        }
        catch (WebException webEx)
        {
            using (HttpWebResponse Response = (HttpWebResponse)webEx.Response)
            {
                nCode = Response.StatusCode;

                using (StreamReader reader = new StreamReader(Response.GetResponseStream()))
                {
                    sRespBodyData = reader.ReadToEnd();
                    try
                    {
                        return (JObject.Parse(sRespBodyData));
                    }
                    catch(Exception i)
                    {

                    }
                }
            }
        }

        return (null);
    }



}
}

If anyone that has any knowledge on this that could help, I would be very grateful.

Edit: I used the code from Joe to see if the unauthorized issue could be fixed and I got this result:

{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: 
System.Net.Http.StreamContent, Headers:
{
  Date: Sun, 29 Jul 2018 19:19:36 GMT
  Content-Length: 0
}}

I am using this code to perform the http call:

   using System;
   using System.Collections.Generic;
   using System.Linq;
   using System.Text;
   using System.Threading.Tasks;
   using Coinigy_v2_api_2018.Functions;
   using System.Net.Http;

   namespace Coinigy_v2_api_test
   {
   class Program
   {
    static void Main(string[] args)
    {
        ApiRequest req = new ApiRequest();
        req.BaseUrl = "https://api.coinigy.com";
        req.Body = "";
        req.Method = "GET";
        req.Secret = "secret";
        req.Key = "key";
        req.Endpoint = "/api/v2/private/exchanges";

        string signature = req.Signature;
        HttpResponseMessage response = null;
        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("X-API-SIGN", signature);
            client.DefaultRequestHeaders.Add("X-API-TIMESTAMP", req.Timestamp);
            response = client.GetAsync(req.BaseUrl + req.Endpoint).Result;
        }
        string r = null;
        if (response.IsSuccessStatusCode)
        {
            r = response.Content.ReadAsStringAsync().Result;
        }

        Console.WriteLine(r);
        Console.ReadLine();


    }
}
}

again thank you in advance!


Solution

  • API documentation can be found here: https://api.coinigy.com/api/v2/docs/#/

    2019-08-28: Latest update is that the endpoint must require the query parameter unencoded when generating the signature.

    The very first thing to check is to make sure you are using a V2 api key/secret. The old V1 keys will not work with the V2 api. If you upgrade your subscription, you will likely need to generate a new key for it to take effect.

    Here is a class that should help out if that doesn't solve the problem:

    public class ApiRequest
    {
        public string BaseUrl { get; set; }
        public string Endpoint { get; set; }
        public string Key { get; set; }
        public string Secret { get; set; }
        public string Method { get; set; }
        public string Body { get; set; }
        public string Timestamp { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
    
    
        /// <summary>
        /// Each API request needs to be signed so the sender can be identified.
        /// This property generates the signature based on the current request.
        /// </summary>
        public string Signature
        {
            get
            {
                var asciiEncoding = new ASCIIEncoding();
                var hmac = new HMACSHA256(asciiEncoding.GetBytes(Secret));
                string signature = Key + Timestamp + Method.ToUpper() + Endpoint + (Body ?? string.Empty);
                byte[] signatureBytes = asciiEncoding.GetBytes(signature);
                byte[] hashedSignatureBytes = hmac.ComputeHash(signatureBytes);
                string hexSignature = string.Join(string.Empty, Array.ConvertAll(hashedSignatureBytes, hb => hb.ToString("X2")));
    
                return hexSignature;
            }
        }
    }
    

    If you set those values properly, you should be able to generate the proper signature.

    For the parameter values, start with something like this and compare to the signature I've produced to make sure your signature function is working properly:

    BaseUrl   : https://api.coinigy.com
    Endpoint  : /api/v2/private/exchanges?pythagoreanTheorem=a%5E2%2Bb%5E2%3Dc%5E2
    Key       : keykeykeykeykeykeykeykeykeykeyke
    Secret    : secretsecretsecretsecretsecretse
    Method    : GET
    Timestamp : 1532718830 (which is 2018-07-27T19:13:50.6694555Z)
    Body      : (empty string)
    
    Signature : B618C0B3C92632C701D7CEFC00AC9C8A0771989B21E00D61D4945F79239D2F87
    

    Here is a bonus python3 version that will perform the entire process: https://gist.github.com/phillijw/1f78c8bafdce3a71a0b2ef9d4f5942a1