发送post请求时bybit使用c#签名错误

问题描述 投票:0回答:4

我正在尝试向 bybit api 发送一个简单的 post 请求,但我一直收到 10004 符号错误。 这是回复:

{"ret_code":10004,"ret_msg":"error sign! origin_string[api_key=(my api key)\u0026symbol=BTCUSDT\u0026timestamp=1635967650768]","ext_code":"","ext_info":"","result":null,"time_now":"1635967651.397800"}

这是我用来发送请求的代码。

public async static Task<string> cancelAllOrders()
    {
        string ts = await GenerateTimeStamp();
        string paramString = "api_key=" + apiKey + "&symbol=BTCUSDT" + "timestamp=" + ts;
        string sign = CreateSignature(secretKey, paramString);
        CancelOrderContent co = new CancelOrderContent(apiKey, "BTCUSDT", ts, sign);

        var client = new RestClient(ApiUrl + "/v2/private/order/cancelAll");
        var request = new RestRequest();
        request.AddJsonBody(co);
        var response = client.Post(request);
        Trace.WriteLine(response.StatusCode.ToString() + "   " + response);
        return "";
    }

这是我正在为请求正文序列化为 JSON 的类。

public class CancelOrderContent
    {
        public string api_key;
        public string sign;
        public string symbol;
        public string timestamp;

        public CancelOrderContent(string api_key, string symbol, string timestamp,string sign)
        {
            this.api_key = api_key;
            this.symbol = symbol;
            this.timestamp = timestamp;
            this.sign = sign;
        }
    }

这是我用来创建签名的代码:

public static string CreateSignature(string secret, string message)
        {
            var signatureBytes = Hmacsha256(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(message));

            return ByteArrayToString(signatureBytes);
        }

        private static byte[] Hmacsha256(byte[] keyByte, byte[] messageBytes)
        {
            using (var hash = new HMACSHA256(keyByte))
            {
                return hash.ComputeHash(messageBytes);
            }
        }

我已经尝试了各种方法来解决这个问题,但我似乎无法摆脱它。我已经尝试了多个端点,但我仍然遇到同样的错误。

c# rest post cryptocurrency bybit
4个回答
2
投票

2022/01/17 这仍然有效。

嘿@Vexatious 我在尝试提交订单时遇到了与 bybit-api 类似的问题,并且我一直收到密钥被拒绝,权限不足的错误,即使我知道我正在正确设置我的密钥。

也许ByBit改变了什么?库过时了?谁知道。

我注意到的一件主要事情是,他们要求您 在将签名附加到请求正文之前按字母顺序排列参数。

编辑:更新是因为我认识到 GET 请求同样令人困惑。向下滚动以查看示例 POST 请求。


GETPOST 请求的处理方式不同

  1. axios(https://api-testnet.bybit.com/GET?aParam=foo&bParam=bar&sign=sign)
  2. axios(https://api-testnet.bybit.com/POST, {data: queryString})

签名

对于两者:您必须在生成签名之前按字母顺序排列参数。

  1. 在单个对象上获取查询参数 [包括'api_key',不包括 API Secret]。
  2. 按字母顺序对查询参数进行排序。
  3. 迭代对象排序键,像下面的例子一样构建一个 QueryString
  4. 使用带有十六进制摘要的 hmac sha256 来创建符号(查看底部的 ./sign.ts)

例如:“aParam=foo&bParam=bar”,

这是你的sign参数处理。


GET REQUESTS:将sign参数附加到QueryString的末尾 和发件人,可能需要使用标题

// Might still need {'Content-Type': 'application/x-www-form-urlencoded'}
// header depending on what request lib you're using.

const url = "https://api-testnet.bybit.com/GET?aParam=foo&bParam=bar&sign=" + sign

POST REQUESTS:要求对象作为 request data 发送(仍以上述 QueryString 的形式发送),而不是类似于 GET 请求的完全构建的 Http 字符串。将 sign 参数添加到您生成签名的 QueryString 的末尾,将其分配给您的请求处理程序数据参数并启动!

我确实想出了一个最小的工作版本,成功地在他们的测试网上发布了现货订单,这是开玩笑的测试。

./bybit.test.ts

test("WORKING BYBIT TRADE.", async () => {
        const serverTime:number = (await axios.get(`https://api-testnet.bybit.com/spot/v1/time`)).data

        // These need to be within 1000ms of eachother (I'm pree sure, their formula is kinda confusing)
        console.log(`Their Timestamp`, serverTime)
        console.log(`Our Timestamp`, Date.now())

        const queryParams = {
            // Alphabetically ordered
            // (Sign generation function should deal with unordered params using .sort())
            'api_key': bybitApiKey,
            qty:10,
            recvWindow: 10000,
            side:"BUY",
            symbol:"ETHUSDT",
            timestamp: Date.now(),
            type:"MARKET",
        }

        const queryString = querystring.stringify(queryParams)
        const sign = "&sign=" + getSignature(queryParams, bybitSecret)
        const fullQuery = queryString + sign

        console.log(`FullQuery example`, fullQuery) 
        // api_key=...&qty=10&recvWindow=10000&side=BUY&symbol=ETHUSDT&timestamp=1638371037593&type=MARKET&sign=...

        let result = await axios(`https://api-testnet.bybit.com/spot/v1/order`, {
            withCredentials: true,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            method: "POST",
            data: fullQuery,
        })

        console.log(`Post Status`, result.status)
        console.log(`Post Body`, result.data)
    })

    /**
        Post Status 200
        
        Post Body {
          ret_code: 0,
          ret_msg: '',
          ext_code: null,
          ext_info: null,
          result: {
            accountId: '...',
            symbol: 'ETHUSDT',
            symbolName: 'ETHUSDT',
            orderLinkId: '...',
            orderId: '...',
            transactTime: '...',
            price: '0',
            origQty: '10',
            executedQty: '0',
            status: 'FILLED',
            timeInForce: 'GTC',
            type: 'MARKET',
            side: 'BUY'
          }
    */
    }

./sign.ts

import crypto from 'crypto'

export function getSignature(parameters: any, secret: string) {
    var orderedParams = "";

    Object.keys(parameters).sort().forEach(function(key) {
        orderedParams += key + "=" + parameters[key] + "&";
    });
    orderedParams = orderedParams.substring(0, orderedParams.length - 1);

    return crypto.createHmac('sha256', secret).update(orderedParams).digest('hex');
}

希望这有帮助!


0
投票

我刚刚为 ByBit api 解决了这个问题,但是使用 Javascript 而不是 C#。但是我知道 C#,所以希望这对你有用。

使用 POST 的 ByBit API 端点需要与 GET 请求中相同的数据 HMAC 加密。而不是在查询字符串中签署参数(并附加 &sign=xxxx 到它),你:

  • 签名序列化为 JSON 对象,
  • 然后在POST前的对象上加上[object].sign = "xxxx"

这就是你的 C# 类变得棘手的地方。您有带有 sign 属性的 CancelOrderContent。这将使用空白值序列化键“符号”。但是,ByBit API 不会接受,因为签名的数据会有所不同。

你要么必须

  • 序列化一个没有'sign'键的对象(CancelOrderContentNosignkey),
  • 将属性从 CancelOrderContentNosignkey 复制到新的 CancelOrderContent
  • 将签名哈希字符串添加到 CancelOrderContent 对象.sign prop
  • 然后发布带有符号键/值对的序列化对象,

或...

  • 像现在一样序列化对象,
  • 但是然后 munge 序列化字符串以删除
    ,"sign":''
    部分。
  • 然后在那个字符串上签名,
  • 然后给对象加上符号值,
  • 再次将其序列化为 JSON,并且
  • 将其作为数据发布。

我相信这会奏效,因为我必须这样做才能让它在 JS 中工作。但是,添加符号键/值更容易,因为没有类。

这个的一个变体是使 CancelOrderContent 成为动态类型,在序列化/签名之前,您不会添加“签名”键/值。

注意,当您手动将对象序列化为 JSON(不要使用 paramString)时,理论上序列化器应该与 RestRequest.AddJsonBody() 中使用或配置的序列化器相同

抱歉,我没有 C# 代码,但这应该可以。


0
投票

谢谢@Kwuasimoto 的详细回答,它引导我朝着正确的方向前进,但是,它对我来说并不是很有效。当将 fullQuery 字符串作为 axios 数据传递时,我收到错误“缺少必需的参数'sign'”,但是当我用 URLSearchParams 替换字符串时

const postData = new URLSearchParams(queryParams);
postData.append("sign", signature);

成功了。这是我最终使用的完整代码。这主要是@Kwuasimoto 对我的一些调整的回答。

import crypto from 'crypto'
import axios from 'axios';

const getSignature(parameters: any, secret: string) => {
  let orderedParams = "";

  Object.keys(parameters).sort().forEach(function(key) {
    orderedParams += key + "=" + parameters[key] + "&";
  });
  orderedParams = orderedParams.substring(0, orderedParams.length - 1);

  return crypto.createHmac('sha256', secret).update(orderedParams).digest('hex');
}

const postSpotOrder = async () => {
  const queryParams = {
  api_key: bybitApiKey,
  qty: 10,
  recvWindow: 10000,
  side: "BUY",
  symbol: "ETHUSDT",
  timestamp: Date.now(),
  type: "MARKET",
};

  const signature = getSignature(queryParams, bybitSecret);
  const postData = new URLSearchParams(queryParams);
  postData.append("sign", signature);

  let result = await axios(`https://api-testnet.bybit.com/spot/v1/order`, {
    withCredentials: true,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    method: "POST",
    data: postData,
  });

  console.log(`Post Status`, result.status);
  console.log(`Post Body`, result.data);
};

0
投票

看来你签错了。 我将以 V5 订单创建代码为例。

internal class CreateOrderRequest
{
    public readonly string category = "linear";
    public readonly string orderLinkId;
    public readonly string orderType = "Market";
    public readonly string qty;
    public readonly string side;
    public readonly string symbol = "BTCUSDT";

    public CreateOrderRequest(string side, string qty, string orderLinkId)
    {
        this.side = side;
        this.qty = qty;
        this.orderLinkId = orderLinkId;
    }
}

internal class CreateOrderResponse
{
    public int retCode;
    public string? retMsg;
    public long time;
}


    private static readonly HttpClient client = new HttpClient();
    private static readonly Random random = new Random();

    private static async Task<CreateOrderResponse?> CreateOrder()
    {
        var time = GetTime();
        var recvWindow = 5000;
        var orderLinkId = RandomString(16);
        var payload = new CreateOrderRequest("Buy", "0.001", orderLinkId);
        var payloadStr = JsonConvert.SerializeObject(payload);
        var paramStr = $"{time}{apiKey}{recvWindow}{payloadStr}";

        var sign = CreateSignature(apiSecret, paramStr);

        using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.bybit.com/v5/order/create");
        request.Headers.Add("X-BAPI-API-KEY", apiKey);
        request.Headers.Add("X-BAPI-TIMESTAMP", time.ToString());
        request.Headers.Add("X-BAPI-SIGN", sign);
        request.Headers.Add("X-BAPI-RECV-WINDOW", recvWindow.ToString());
        request.Content = new StringContent(payloadStr, Encoding.UTF8, MediaTypeNames.Application.Json);

        using var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        var responseBody = await response.Content.ReadAsStringAsync();
        var responseObj = JsonConvert.DeserializeObject<CreateOrderResponse>(responseBody);

        return responseObj;
    }

    private static long GetTime()
    {
        DateTime utcNow = DateTime.UtcNow;
        DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        long ts = (long)((utcNow - epoch).TotalMilliseconds);
        Console.WriteLine($"Now: {ts}");
        return ts;
    }

    private static string CreateSignature(string secret, string message)
    {
        var signatureBytes = Hmacsha256(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(message));
        return ByteArrayToString(signatureBytes);
    }

    private static byte[] Hmacsha256(byte[] keyByte, byte[] messageBytes)
    {
        using var hash = new HMACSHA256(keyByte);
        return hash.ComputeHash(messageBytes);
    }

    private static string ByteArrayToString(byte[] signatureBytes)
    {
        return BitConverter.ToString(signatureBytes).Replace("-", "").ToLower();
    }

    private static string RandomString(int length)
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        return new string(Enumerable.Repeat(chars, length)
            .Select(s => s[random.Next(s.Length)]).ToArray());
    }
© www.soinside.com 2019 - 2024. All rights reserved.