1

I have been struggling with signing requests for private GDAX endpoints for a while. Everything I have tried results in a 400 response with a message of "invalid signature." I have read their documentation on the matter several times, which can be found here. My current code is below. I'm using clj-http for making requests. I'm using their /time endpoint response for the timestamp, and I'm using pandect for the sha256 HMAC generation. I've tried converting the secret-decoded to a string using String. before passing it to sha256-hmac. I've also examined the request using clj-http's debug flag. It looks to me that I am following their directions precisely, but something must be wrong. I've done a lot of online searching before posting here. Any help would be greatly appreciated.

(defn get-time
  []
  (-> (str (:api-base-url config) "/time")
      (http/get {:as :json})
      :body))

(defn- create-signature
  ([timestamp method path]
   (create-signature timestamp method path ""))
  ([timestamp method path body]
   (let [secret-decoded (b64/decode (.getBytes (:api-secret config)))
         prehash-string (str timestamp (clojure.string/upper-case method) path body)
         hmac (sha256-hmac prehash-string secret-decoded)]
      (-> hmac
          .getBytes
          b64/encode
          String.))))

(defn- send-signed-request 
  [method path & [opts]]
  (let [url (str (:api-base-url config) path)
        timestamp (long (:epoch (get-time)))
        signature (create-signature timestamp method path (:body opts))]
    (http/request
      (merge {:method method
              :url url
              :as :json
              :headers {"CB-ACCESS-KEY" (:api-key config)
                        "CB-ACCESS-SIGN" signature
                        "CB-ACCESS-TIMESTAMP" timestamp
                        "CB-ACCESS-PASSPHRASE" (:api-passphrase config)
                        "Content-Type" "application/json"}
              :debug true}
             opts))))

(defn get-accounts []
  (send-signed-request "GET" "/accounts"))

(send-signed-request "GET" "/accounts")))
Brandon
  • 91
  • 1
  • 6
  • I noticed they have Python (& other) sample code available here: https://docs.gdax.com/?python#creating-a-request I'd suggest getting that version to work first, then transfer the working stuff to clojure/jvm. – Alan Thompson Jan 17 '18 at 19:27

1 Answers1

0

I've figured out the issue. Just in case anyone happens to have this very specific problem, I'm posting the solution. My error was that I was using the sha256-hmac function from pandect, which returns a string hmac, then I was converting that to a byte array, base64 encoding it, and converting it back to a string. Somewhere in those conversions, or perhaps in the pandect function's conversion, the value is altered in an erroneous way.

What works is using the sha256-hmac* function (note the asterisk) from pandect, which returns a raw byte array hmac, then base64 encoding that result directly, and converting it to a string. Below is the corrected, working code snippet, with which I was able to make a request to a private GDAX endpoint.

(defn create-signature
  ([timestamp method path]
    (create-signature timestamp method path ""))
  ([timestamp method path body]
    (let [secret-decoded (b64/decode (.getBytes (:api-secret config)))
          prehash-string (str timestamp (clojure.string/upper-case method) path body)
          hmac (sha256-hmac* prehash-string secret-decoded)]
      (-> hmac
          b64/encode
          String.))))
Brandon
  • 91
  • 1
  • 6