1

I'm trying to create a wrapper for a bitcoin exchange API in python. However, the API requires you to generate signature with each request. I'm having difficulty generating the SHA394 signature correctly using the hmac module in python. Here's how my following code looks like so far:

# private_client.py
# Mohammad Usman
#
# A python wrapper for Gemini's public API

from cached import Cached
import requests
import json
import uuid
import hmac
import hashlib
import base64
from datetime import datetime


class PrivateClient(metaclass=Cached):
    def __init__(self, PUBLIC_API_KEY, PRIVATE_API_KEY):
        self._public_key = PUBLIC_API_KEY
        self._private_key = PRIVATE_API_KEY
        self._base_url = 'https://api.gemini.com/'

    def api_query(self, method, payload=None):
        if payload is None:
            payload = {}
        request_url = self._base_url + method

        payload['request'] = method
        payload['nonce'] = str(uuid.uuid4())

        b64_payload = base64.b64encode(json.dumps(payload).encode('utf-8'))
        signature = hmac.new(self._private_key.encode('utf-8'), msg=b64_payload, digestmod=hashlib.sha256).digest()
        headers = {
            'Content-Type': "text/plain",
            'Content-Length': "0",
            'X-GEMINI-APIKEY': self._public_key,
            'X-GEMINI-PAYLOAD': b64_payload,
            'X-GEMINI-SIGNATURE': signature,
            'Cache-Control': "no-cache"
        }

        r = requests.request('POST', request_url, headers=headers)
        return r.json()

    def new_order(self, symbol, amount, price, side, options):
        payload = {
            'symbol': symbol,
            'amount': amount,
            'price': amount,
            'side': side,
            'options': options,
            'type': 'exchange limit'
        }
        return self.api_query('/v1/order/new', payload)


a = PrivateClient('public_key', 'private_key')
b = a.new_order('BTCUSD', '20.2', '4500.2', 'buy', ["immediate-or-cancel"])
print(b)

However, I keep getting the following error:

Traceback (most recent call last):
  File "C:\Users\uzman\Documents\Python\Gemini- Python API Wrapper\gemini\private_client.py", line 58, in <module>
    b = a.new_order('BTCUSD', '20.2', '4500.2', 'buy', ["immediate-or-cancel"])
  File "C:\Users\uzman\Documents\Python\Gemini- Python API Wrapper\gemini\private_client.py", line 54, in new_order
    return self.api_query('/v1/order/new', payload)
  File "C:\Users\uzman\Documents\Python\Gemini- Python API Wrapper\gemini\private_client.py", line 42, in api_query
    r = requests.request('POST', request_url, headers=headers)
  File "C:\Users\uzman\AppData\Local\Programs\Python\Python36\lib\site-packages\requests\api.py", line 58, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\uzman\AppData\Local\Programs\Python\Python36\lib\site-packages\requests\sessions.py", line 488, in request
    prep = self.prepare_request(req)
  File "C:\Users\uzman\AppData\Local\Programs\Python\Python36\lib\site-packages\requests\sessions.py", line 431, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "C:\Users\uzman\AppData\Local\Programs\Python\Python36\lib\site-packages\requests\models.py", line 306, in prepare
    self.prepare_headers(headers)
  File "C:\Users\uzman\AppData\Local\Programs\Python\Python36\lib\site-packages\requests\models.py", line 440, in prepare_headers
    check_header_validity(header)
  File "C:\Users\uzman\AppData\Local\Programs\Python\Python36\lib\site-packages\requests\utils.py", line 869, in check_header_validity
    raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
requests.exceptions.InvalidHeader: Invalid return character or leading space in header: X-GEMINI-SIGNATURE

Any help ?

electro7912
  • 339
  • 4
  • 14
  • Yes, `self._private_key` needs to be a bytes object, not `str`. *Encode* your string. – Martijn Pieters Oct 08 '17 at 20:48
  • I've tried making `signature = hmac.new(self._private_key.encode('utf-8'), msg=b64_payload, digestmod=hashlib.sha256).digest()` but then I get back the following error `{'result': 'error', 'reason': 'InvalidSignature', 'message': 'InvalidSignature'}` – electro7912 Oct 08 '17 at 20:50
  • You will need to triple-check the documentation. Perhaps [How do I sign a POST request using HMAC-SHA512 and the Python requests library?](//stackoverflow.com/a/42100642) or [any of the other HMAC signing with `requests` posts](https://stackoverflow.com/search?q=hmac+requests+%5Bpython%5D) help? – Martijn Pieters Oct 08 '17 at 20:57
  • Oh boy, really? *Authenticated APIs do not submit their payload as POSTed data, but instead put it in the X-GEMINI-PAYLOAD header*. Tsk tsk. – Martijn Pieters Oct 08 '17 at 20:58
  • Your `request` parameter is wrong; it should be the path only: `/v1/order/new`. – Martijn Pieters Oct 08 '17 at 21:00
  • Just changed the code and corrected the parameter. However, still having problem with the signature – electro7912 Oct 08 '17 at 21:09
  • `hashlib.sha256` is not the correct SHA version, they clearly state you need SHA 384. – Martijn Pieters Oct 08 '17 at 21:11

0 Answers0