2

I am implementing Coinbase's exchange API using custom auth in requests-python. The following code works with all the (authenticated) GET-based calls, but fails for all the authenticated POST-based calls (I haven't tried with DELETE or UPDATE verbs). I don't understand why the signature wouldn't work for both, because the payload is timestamp + method + path for GETs and timestamp + method + path + body for PUTs, so custom auth seems correct. Something is going wrong with adding the body and changing GET to POST. Thanks!

You can get your API keys for trying it out here: https://gdax.com/settings

import json, hmac, hashlib, time, requests, base64
from requests.auth import AuthBase

class CoinbaseAuth(AuthBase):
    SIGNATURE_HTTP_HEADER = 'CB-ACCESS-SIGN'
    TIMESTAMP_HTTP_HEADER = 'CB-ACCESS-TIMESTAMP'
    KEY_HTTP_HEADER = 'CB-ACCESS-KEY'
    PASSPHRASE_HTTP_HEADER = 'CB-ACCESS-PASSPHRASE'

    def __init__(self, api_key, secret_key, passphrase):
        self.api_key = api_key
        self.secret_key = secret_key
        self.passphrase = passphrase

    def __call__(self, request):
        #Add headers        
        request.headers[CoinbaseAuth.KEY_HTTP_HEADER] = self.api_key
        request.headers[CoinbaseAuth.PASSPHRASE_HTTP_HEADER] = self.passphrase
        timestamp = str(time.time())        
        request.headers[CoinbaseAuth.TIMESTAMP_HTTP_HEADER] = timestamp

        #add signature
        method = request.method
        path = request.path_url
        content = request.body
        message = timestamp + method + path        
        if content:
            message += content 
        hmac_key = base64.b64decode(self.secret_key)
        sig = hmac.new(hmac_key, message, hashlib.sha256)
        sig_b64 = sig.digest().encode("base64").rstrip("\n")

        #Add signature header
        request.headers[CoinbaseAuth.SIGNATURE_HTTP_HEADER] = sig_b64
        return request

#Get your keys here: https://gdax.com/settings
key = 'KEY GOES HERE'
secret = 'SECRET GOES HERE'
passphrase = 'PASSPHRASE GOES HERE'

api_url = 'https://api.gdax.com:443/'
auth = CoinbaseAuth(API_KEY, API_SECRET, API_PASS)

#GETs work, shows account balances
r = requests.get(api_url + 'accounts', auth=auth)
print r.json()

#POSTs fail: {message: 'invalid signature'}
order = {}        
order['size'] = 0.01
order['price'] = 100
order['side'] = 'buy'
order['product_id'] = 'BTC-USD'
r = requests.post(api_url + 'orders', data=json.dumps(order), auth=auth)
print r.json()

And the output:

GET call: 200: [{u'available': .......}]

POST call: 400: {u'message': u'invalid signature'}

EDIT: POSTing 'a' instead of valid JSON-encoded data results in the same signature error (rather than a JSON decoding error from the server), so I don't think it is the way I'm forming the data. Notably, if I omit the body -- request.post(..., data='',...) --- the server responds appropriately with {u'message': u'Missing product_id'}.

4

3 回答 3

3

我不知道为什么,但是如果我将data关键字参数更改requests.post()json它可以:

r = requests.post(api_url + 'orders', json=order, auth=auth)

编辑:唯一改变的是,AFAICT,标头中的内容类型从文本更改为 JSON。所以很可能是unicode vs ASCII编码问题。这是最近添加此功能的库的问题:https ://github.com/kennethreitz/requests/issues/2025#issuecomment-46337236

于 2015-01-27T08:23:20.327 回答
0

I believe the content needs to be a json string with no spaces (this is what the node example does anyway). Maybe try this:

message += json.dumps(content).replace(' ', '')
于 2015-01-27T06:50:20.337 回答
0

在查看 nodeJS 的公共 gdax API 并发现他们正在使用 GDAX API 文档中未提及的一些附加标头之前,我遇到了同样的问题。我添加了它们,然后它开始工作。请参阅我对以下问题的回答:GDAX API Always Returns Http 400 "Invalid Signature" 即使我的做法与 API Doc 中的完全一样

于 2018-01-24T08:23:11.767 回答