Что не так с этим coinmex API? - PullRequest
3 голосов
/ 28 марта 2019
Protected Overrides Function getJsonPrivate(method As String, otherParameters() As Tuple(Of String, String)) As String
    Dim base = "https://www.coinmex.com"
    Dim premethod = "/api/v1/spot/ccex/"
    Dim longmethod = premethod + method

    Dim timestampstring = getEstimatedTimeStamp().ToString

    Dim stringtosign = timestampstring + "GET" + longmethod + "{}" '1553784499976GET/api/v1/spot/ccex/account/assets{}

    Dim hasher = New System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(_secret1))
    Dim sighashbyte = hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringtosign))
    Dim signature = System.Convert.ToBase64String(sighashbyte) '"FIgrJFDOQctqnkOTyuv6+uTy6xw3OZiP4waC1u6P5LU="=
    Dim url = base + longmethod 'https://www.coinmex.com/api/v1/spot/ccex/account/assets

    '_apiKey1="cmx-1027e54e4723b09810576f8e7a5413**"
    '_passphrase1= 1Us6&f%*K@Qsqr**
    '
    Dim response = CookieAwareWebClient.downloadString1(url, "", {Tuple.Create("ACCESS-KEY", _apiKey1), Tuple.Create("ACCESS-SIGN", signature), Tuple.Create("ACCESS-TIMESTAMP", timestampstring), Tuple.Create("ACCESS-PASSPHRASE", _passphrase1)})

    Return response
End Function

Public Overrides Sub readbalances()
    typicalReadBalances("account/assets", "data", "currencyCode", "available", "frozen", "", {})
End Sub

Я думаю, что сделал это так, как указано здесь https://github.com/coinmex/coinmex-official-api-docs/blob/master/README_EN.md#1-access-account-information

# Request
GET /api/v1/spot/ccex/account/assets

# Response
[
    {
        "available":"0.1",
        "balance":"0.1",
        "currencyCode":"ETH",
        "frozen":"0",
        "id":1
    },
    {
        "available":"1",
        "balance":"1",
        "currencyCode":"USDT",
        "frozen":"0",
        "id":1
    }
]

А для подписи

Это руководство говорит

Заголовок ACCESS-SIGN - это вывод, сгенерированный с помощью HMAC SHA256 для создать HMAC SHA256, используя секретный ключ декодирования BASE64 в строка предварительного хэша для генерации метки времени + метод + requestPath + "?" + queryString + body (где ‘+’ представляет конкатенацию строк) и BASE64 кодированный выход. Значение метки времени совпадает с ACCESS-TIMESTAMP заголовок. Это тело является строкой тела запроса или опускается, если тела запроса нет (обычно это запрос GET). это метод должен быть написан заглавными буквами.

Помните, что перед использованием его в качестве ключа для HMAC, декодирование base64 ( результат составляет 64 байта) сначала выполняется на 64-разрядном алфавитно-цифровом строка пароля. Кроме того, дайджест-выход кодируется в base64 перед отправкой заголовка.

Пользовательские параметры должны быть подписаны, кроме знака. Во-первых, Строка для подписи упорядочена в соответствии с именем параметра (первым сравнить первую букву всех имен параметров в алфавитном порядке, если вы встретите ту же первую букву, то вы переходите ко второй письмо и т. д.).

Например, если мы подпишем следующие параметры

curl "https://www.coinmex.com/api/v1/spot/ccex/orders?limit=100"       

Timestamp = 1590000000.281
Method = "POST"
requestPath = "/api/v1/spot/ccex/orders"
queryString= "?limit=100"
body = {
            'code': 'ct_usdt',
            'side': 'buy',
            'type': 'limit',
            'size': '1',
            'price': '1',
            'funds': '',
        }

Создать строку для подписи

Message = '1590000000.281GET/api/v1/spot/ccex/orders?limit=100{"code": "ct_usdt", "side": "buy", "type": "limit", "size": "1", "price": "0.1", "funds": ""}'

Затем подписываемый символ добавляется с помощью закрытого ключа. параметры для генерации последней строки символов для подписи.

Например:

hmac = hmac(secretkey, Message, SHA256)
Signature = base64.encode(hmac.digest())

Я подумал, что _secret1 - это строка base64, а не utf8, поэтому я изменил на

Dim base = "https://www.coinmex.com"
Dim premethod = "/api/v1/spot/ccex/"
Dim longmethod = premethod + method

Dim timestampstring = getEstimatedTimeStamp().ToString

'Dim stringtosign = timestampstring + "GET" + longmethod + "{}" '1553784499976GET/api/v1/spot/ccex/account/assets{} also doesn't work
Dim stringtosign = timestampstring + "GET" + longmethod  '1553784499976GET/api/v1/spot/ccex/account/assets

Dim hasher = New System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(_secret1)) 'secret looks like 43a90185f5b7ab25af045e9e64bac5dc745934f359f1806fcdd2a4af80ac2
Dim sighashbyte = hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringtosign))
Dim signature = Convert.ToBase64String(sighashbyte) '"FIgrJFDOQctqnkOTyuv6+uTy6xw3OZiP4waC1u6P5LU="=
Dim url = base + longmethod 'https://www.coinmex.com/api/v1/spot/ccex/account/assets

'_apiKey1="cmx-1027e54e4723b09810576f8e7a5413**"
'_passphrase1= 1Us6&f%*K@Qsq***
'
Dim response = CookieAwareWebClient.downloadString1(url, "", {Tuple.Create("ACCESS-KEY", _apiKey1), Tuple.Create("ACCESS-SIGN", signature), Tuple.Create("ACCESS-TIMESTAMP", timestampstring), Tuple.Create("ACCESS-PASSPHRASE", _passphrase1)})

Return response

Также не работает.

Секретный ключ (я усек несколько букв) выглядит как

43a90185f5b7ab25af045e9e64bac5dc745934f359f1806fcdd2a4af80ac2

Это то, что должно быть декодировано как base 64 или utf8 или как?

Спецификация говорит, что это 64. Однако, это не похоже на 64-кодированную строку. Похоже, буквы из 0-f

Лучшие ответы будут: 1. Скажите мне, что пошло не так в коде. Я сделал изменения. Пытаться. Запустить. Работает. Потрясающе.

Хороший ответ будет 2. Пример моделирования с фальшивыми / реальными подписями / одноразовыми / парольными фразами и реальными фактическими заголовками и сигнатурами. Так что я вижу, где именно у меня неправильный результат.

Обновление: я снова изменил код. Я изменяю метку времени на секунды вместо миллисекунд. Я удаляю {}. Я использую оба пути.

    Dim base = "https://www.coinmex.com"
    Dim premethod = "/api/v1/spot/ccex/"
    Dim longmethod = premethod + method

    Dim timestampstring = (getEstimatedTimeStamp() / 1000).ToString

    Dim stringtosign = timestampstring + "GET" + longmethod  '1555154812.857GET/api/v1/spot/ccex/account/assets

    Dim hasher = New System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(_secret1)) '"43a90185f5b7ab25af045e9e64bac5dc745934f359f1806fcdd2a4af80ac2******
    Dim sighashbyte = hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringtosign))
    Dim signature = Convert.ToBase64String(sighashbyte) '"FIgrJFDOQctqnkOTyuv6+uTy6xw3OZiP4waC1u6P5LU="=
    Dim url = base + longmethod 'https://www.coinmex.com/api/v1/spot/ccex/account/assets

    '_apiKey1="cmx-1027e54e4723b09810576f8e7a5413**"
    '_passphrase1= 1Us6&f%*K@QsqrYZ
    '
    Dim response = CookieAwareWebClient.downloadString1(url, "", {Tuple.Create("ACCESS-KEY", _apiKey1), Tuple.Create("ACCESS-SIGN", signature), Tuple.Create("ACCESS-TIMESTAMP", timestampstring), Tuple.Create("ACCESS-PASSPHRASE", _passphrase1)})

    Return response

Все еще не работает.

Текущая ошибка

Message = "Удаленный сервер возвратил ошибку: (401) Unauthorized."

Я бы хотел дать какой-нибудь API-ключ только для чтения. Подожди. Или создайте пустую учетную запись и получите ключ API только для чтения

Ответы [ 2 ]

2 голосов
/ 13 апреля 2019

Документация гласит

Это тело является строкой тела запроса или опущено, если тела запроса нет (обычно это запрос GET)

Примечание: выделение

но вы включаете пустой объект JSON в запрос GET

Dim stringtosign = timestampstring + "GET" + longmethod + "{}" '1553784499976GET/api/v1/spot/ccex/account/assets{}

То, что {} не должно бытьвключены в запрос GET.

'1553784499976GET/api/v1/spot/ccex/account/assets
Dim stringtosign = timestampstring + "GET" + longmethod

Таким образом, похоже, что вы не правильно создавали подпись согласно документации.

Заметили, что документы

КореньURL для доступа к REST: https://www.coinmex.pro

при попытке вызова "https://www.coinmex.com"

Отметка времени

Если не указано иное, все отметки времени в API-интерфейсахвозвращаются в микросекундах.

Заголовок ACCESS-TIMESTAMP должен быть числом секунд, прошедших с начала UIX Unix Epoch.Десятичные значения допускаются. Ваша временная метка должна быть в пределах 30 секунд от времени обслуживания API, в противном случае ваш запрос будет считаться истекшим и отклоненным. Если вы считаете, что между вашим сервером и сервером API существует большая разница во времени, мы рекомендуемВы используете момент времени для проверки времени сервера API.

примечание: акцент шахты

Следующий метод расширения был использован для вычисления метки времени

private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

/// <summary>
/// Converts the value of the current <see cref="System.DateTime"/> object to Unix Time.
/// </summary>
/// <param name="dateTime"></param>
/// <remarks>
/// </remarks>
/// This method first converts the current instance to UTC before returning its Unix time.
/// <returns> 
/// A <see cref="System.Int64"/> defined as the number of seconds that have elapsed since midnight Coordinated Universal Time (UTC), January 1, 1970, not counting leap seconds.
/// </returns>
public static long ToUnixTimeSeconds(this DateTime dateTime) {
    if (dateTime.ToUniversalTime() < Epoch) {
        return 0;
    }

    var totalSeconds = dateTime.ToUniversalTime().Subtract(Epoch).TotalSeconds;
    var timestamp = Convert.ToInt64(totalSeconds);

    return timestamp;
}

Я выполнил следующий тест, чтобы посмотреть, смогу ли я вызвать API в соответствии с документацией, и, похоже, он работает.

Я использовал c #, однако

[TestClass]
public class CoinMaxAPITests {
    const string apiKey1 = "cmx-1027e54e4723b09810576f8e7a5413**";
    const string fakeSecret = "43a90185f5b7ab25af045e9e64bac5dc745934f359f1806fcdd2a4af80ac23==";
    const string passphrase1 = "1Us6&f%*K@QsqrYZ";

    Lazy<HttpClient> http = new Lazy<HttpClient>(() => {
        var rootUrl = "https://www.coinmex.pro";

        CookieContainer cookies = new CookieContainer();
        HttpClientHandler handler = new HttpClientHandler {
            CookieContainer = cookies,
            UseCookies = true,

        };
        var client = new HttpClient() {
            BaseAddress = new Uri(rootUrl)
        };
        client.DefaultRequestHeaders.TryAddWithoutValidation("ACCESS-KEY", apiKey1);
        client.DefaultRequestHeaders.TryAddWithoutValidation("ACCESS-PASSPHRASE", passphrase1);
        return client;
    });

    [TestMethod]
    public async Task Should_Accept_Signature() {
        //Arrange
        var requestPath = "/api/v1/spot/public/time";
        var method = "GET";
        var timeStamp = getEstimatedTimeStamp().ToString(); //"1555253371"

        var message = timeStamp + method + requestPath; //"1555253371GET/api/v1/spot/public/time"

        var secretKey = Convert.FromBase64String(fakeSecret);
        var hmac = new HMACSHA256(secretKey);
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
        var signature = Convert.ToBase64String(hash);//Jzui/eO3iyLTD6L9qVkUO0EBpZP/lFhx1HlsbuSNt/8=

        var request = new HttpRequestMessage(HttpMethod.Get, requestPath);
        request.Headers.TryAddWithoutValidation("ACCESS-TIMESTAMP", timeStamp);
        request.Headers.TryAddWithoutValidation("ACCESS-SIGN", signature);

        //Act
        var response = await http.Value.SendAsync(request);

        //Assert
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        //"{\"epoch\":\"1555253501.225\",\"iso\":\"2019-04-14T14:51:41.225Z\",\"timestamp\":1555253501225}"
        var server = JsonConvert.DeserializeObject<ServerTime>(json);

        server.Should().NotBeNull();
        server.Iso.Date.Should().Be(DateTime.Today);
    }

    long getEstimatedTimeStamp() {
        return DateTime.Now.ToUnixTimeSeconds(); //custom extension method
    }
}


public partial class ServerTime {
    [JsonProperty("epoch")]
    public string Epoch { get; set; }

    [JsonProperty("iso")]
    public DateTime Iso { get; set; }

    [JsonProperty("timestamp")]
    public long Timestamp { get; set; }
}

И смогчтобы получить действительный ответ JSON, вызывающий /api/v1/spot/public/time, который я смог десериализовать для своего утверждения, даже с поддельными ключами.Вероятно, так как это публичный API.Это доказывает, что вызываемый URL-адрес правильный.

Когда путь запроса изменяется на

"/api/v1/spot/ccex/account/assets"

И проверяется на наличие более безопасных личных данных из API, ответ равен 400 Bad Request сследующее содержание в теле ответа

{"message":"Encrypted key does not exist"}

, как и ожидалось, учитывая, что ключи, которые я использовал, были поддельными.

Это дает мне все признаки того, что API на самом деле работает какожидается, что вы будете следовать тому, что предложено в связанной документации.

0 голосов
/ 14 апреля 2019

Интересно, что я должен выбрать в качестве ответа из-за этого конфликта интересов.

То, что произошло, он приложил дополнительные усилия, чтобы попробовать API. Я решил создать новый ключ API и опубликовать его здесь. Это только для чтения и не может пойти не так, как надо. Прежде чем я разместил здесь, я пытаюсь запустить его еще раз, ожидая, что он не будет работать, как обычно.

Оказывается, мой код просто работает. Похоже, что есть ошибка в ключе API, секрете или пароле.

Вот код, который, наконец, работает

Protected Overrides Function getJsonPrivate(method As String, otherParameters() As Tuple(Of String, String)) As String
    Dim base = "https://www.coinmex.pro"
    Dim premethod = "/api/v1/spot/ccex/"
    Dim longmethod = premethod + method

    Dim timestampstring = (getEstimatedTimeStamp() / 1000).ToString

    Dim stringtosign = timestampstring + "GET" + longmethod  '1555154812.857GET/api/v1/spot/ccex/account/assets

    Dim hasher = New System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(_secret1)) '"43a90185f5b7ab25af045e9e64bac5dc745934f359f1806fcdd2a4af80ac2******
    Dim sighashbyte = hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringtosign))
    Dim signature = Convert.ToBase64String(sighashbyte) '"FIgrJFDOQctqnkOTyuv6+uTy6xw3OZiP4waC1u6P5LU="=
    Dim url = base + longmethod 'https://www.coinmex.com/api/v1/spot/ccex/account/assets

    '_apiKey1="cmx-1027e54e4723b09810576f8e7a5413**"
    '_passphrase1= 1Us6&f%*K@QsqrYZ
    '
    Dim response = CookieAwareWebClient.downloadString1(url, "", {Tuple.Create("ACCESS-KEY", _apiKey1), Tuple.Create("ACCESS-SIGN", signature), Tuple.Create("ACCESS-TIMESTAMP", timestampstring), Tuple.Create("ACCESS-PASSPHRASE", _passphrase1)})

    Return response
End Function
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...