Eindhoven
Eindhoven

Reputation: 21

Coinbase API standard python example returns "invalid signature"

I am trying to use the Coinbase API with Python, starting of from the standard example on the Coinbase Developer API page: https://developers.coinbase.com/docs/wallet/api-key-authentication#

This piece of code gave me only errors, like:

TypeError: key: expected bytes or bytearray, but got 'str'

After some googling I made some adjustments that got me a little further, but I'm still not there. Could it be that the API documentation page gives an outdated example?

The main message that is returned to me now is this: {"id":"authentication_error","message":"invalid signature"}

I am using the following code (replacing my keys with xxxxxx):

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

# Before implementation, set environmental variables with the names API_KEY and API_SECRET
API_KEY = 'xxxxxxx'
API_SECRET = b'xxxxxxx'

def get_timestamp():
    return int(time.time() * 1000)


# Create custom authentication for Coinbase API
class CoinbaseWalletAuth(AuthBase):
    def __init__(self, api_key, secret_key):
        self.api_key = api_key
        self.secret_key = secret_key

    def __call__(self, request):
        timestamp = str(int(time.time()))

        print(timestamp)

        message = timestamp + request.method + request.path_url + (request.body or b'').decode()
        hmac_key = base64.b64decode(self.secret_key)
        signature = hmac.new(hmac_key, message.encode(), hashlib.sha256)
        signature_b64 = base64.b64encode(signature.digest()).decode()
        
        request.headers.update({
            'CB-ACCESS-SIGN': signature_b64,
            'CB-ACCESS-TIMESTAMP': timestamp,
            'CB-ACCESS-KEY': self.api_key,
            'Content-Type': 'application/json'
        })
        return request


api_url = 'https://api.coinbase.com/v2/'
auth = CoinbaseWalletAuth(API_KEY, API_SECRET)

# Get current user
r = requests.get(api_url + 'user', auth=auth)
print(r.json())
# {u'data': {u'username': None, u'resource': u'user', u'name': u'User'...

Hope you can help me fix this. It seems like this is just getting the basics right...

Upvotes: 2

Views: 1022

Answers (1)

Nachobacanful
Nachobacanful

Reputation: 33

The first thing that is important to mention, the code in their page that you linked is python2 and if you run their code with it it works off the bat.

I had the same issue today and realized that your code has multiple problems (because I assume you tried everything to fix it) so I fixed those for you and that the requests library in python3 or the API as of today (04/28/21) has an issue. I came up with a fix.

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

# Before implementation, set environmental variables with the names API_KEY and API_SECRET
API_KEY = 'xxxxxxx'
API_SECRET = 'xxxxxxx'


# Create custom authentication for Coinbase API
class CoinbaseWalletAuth(AuthBase):
    def __init__(self, api_key, secret_key):
        self.api_key = api_key
        self.secret_key = secret_key

    def __call__(self, request):
        timestamp = str(int(time.time()))

        # the following try statement will fix the bug
        try:
            body = request.body.decode()
            if body == "{}":
                request.body = b""
                body = ''
         except AttributeError:
             request.body = b""
             body = ''

        message = timestamp + request.method + request.path_url + body
        signature = hmac.new(self.secret_key.encode(), message.encode(), hashlib.sha256).hexdigest()
        request.headers.update({
                'CB-ACCESS-SIGN': signature,
                'CB-ACCESS-TIMESTAMP': timestamp,
                'CB-ACCESS-KEY': self.api_key,
        })
        return request


api_url = 'https://api.coinbase.com/v2/'
auth = CoinbaseWalletAuth(API_KEY, API_SECRET)

# Get current user
r = requests.get(api_url + 'user', auth=auth)
print(r.json())
# {u'data': {u'username': None, u'resource': u'user', u'name': u'User'...

now save this as test.py and run with python3 test.py, given that you have the libraries installed in your environment/system.

as of why there is a bug?
troubleshooting I found out that the request.body must be empty for the API to like it (by empty I mean '') as it expects no body for that specific endpoint. This is an assumption about the request library, when you either specify content type set to JSON or use the json= in the call and it sees that you have an empty body it will automatically populate the body with {}. This makes sense since you don't not want to troll the server by saying you are going to send json but did not send anything, so request in python3 is just being nice to the server. In python2 the request library does not automatically do that. This becomes an issue for us since the API likes the body completely empty if the endpoint does not take a body.

Why this fixes it?
I have no idea, as this is not a sign authentication issue. I checked both the working python2 version and python3 version and the signatures come out the same, therefore not an authentication issue. It seems like the server is not flexible with their request body being an empty JSON object.

So as a recap to solve this we must clear the body of the request when we see the body set to {}.

TLDR:
The server misleads into believing it's a sign/auth issue when in reality is a bad request. look at the code.

Upvotes: 2

Related Questions