defmodule CCXT.Signing.HmacSha512Nonce do @moduledoc """ HMAC-SHA512 with nonce signing pattern (Kraken-style). Used by: Kraken and ~20 other exchanges. ## How it works 1. Generate incrementing nonce (microsecond timestamp) 2. Add nonce to request body 3. Hash: `sha256(nonce_string + body)` 4. Concat: `url_path_bytes + hash` 5. Sign: `hmac_sha512(concat, base64_decode(secret))` 6. Encode as Base64 ## Configuration signing: %{ pattern: :hmac_sha512_nonce, api_key_header: "API-Key", signature_header: "API-Sign", nonce_key: "nonce" } """ @behaviour CCXT.Signing.Behaviour alias CCXT.Credentials alias CCXT.Signing @impl true @spec sign(Signing.request(), Credentials.t(), Signing.config()) :: Signing.signed_request() def sign(request, credentials, config) do # Kraken requires nonces to be always-increasing per API key across BEAM restarts. # System.system_time(:microsecond) survives restarts; :erlang.unique_integer does not. nonce = Signing.nonce_from_config(config, fn -> System.system_time(:microsecond) end) nonce_key = Map.get(config, :nonce_key, "nonce") body_params = Map.put(request.params, nonce_key, nonce) body = encode_body(body_params, config) # sha256(nonce_string + body) → concat with path → hmac_sha512 with decoded secret nonce_string = to_string(nonce) hash = Signing.sha256(nonce_string <> body) message = request.path <> hash secret_decoded = Signing.decode_base64(credentials.secret) signature = message |> Signing.hmac_sha512(secret_decoded) |> Signing.encode_base64() headers = build_headers(credentials, signature, config) %{url: request.path, method: request.method, headers: headers, body: body} end defp encode_body(params, config) do case Map.get(config, :body_encoding, :urlencoded) do :urlencoded -> Signing.urlencode(params) :json -> Jason.encode!(params) end end defp build_headers(credentials, signature, config) do api_key_header = Map.get(config, :api_key_header, "API-Key") signature_header = Map.get(config, :signature_header, "API-Sign") content_type = case Map.get(config, :body_encoding, :urlencoded) do :urlencoded -> "application/x-www-form-urlencoded" :json -> "application/json" end [ {api_key_header, credentials.api_key}, {signature_header, signature}, {"Content-Type", content_type} ] end end