nautechsystems/nautilus_trader · error · anyhow::Error

Failed to decode API secret: {e}

Error message

Failed to decode API secret: {e}

What it means

Kraken API secrets are base64-encoded; sign_spot decodes the stored api_secret with STANDARD base64 before computing the HMAC signature. This error wraps the base64 decode failure when the configured API secret is not valid base64.

Source

Thrown at crates/adapters/kraken/src/common/credential.rs:174

    }

    /// Sign a request for Kraken Spot REST API.
    ///
    /// Kraken Spot uses HMAC-SHA512 with the following message:
    /// - path + SHA256(nonce + POST data)
    /// - The secret is base64 decoded before signing
    ///
    /// Note: "nonce + POST data" means the nonce value string is prepended
    /// to the URL-encoded POST body, e.g., "1234567890nonce=1234567890&param=value".
    pub fn sign_spot(
        &self,
        path: &str,
        nonce: u64,
        params: &HashMap<String, String>,
    ) -> anyhow::Result<(String, String)> {
        let secret = STANDARD
            .decode(&self.api_secret)
            .map_err(|e| anyhow::anyhow!("Failed to decode API secret: {e}"))?;

        let nonce_str = nonce.to_string();
        let mut post_data = format!("nonce={nonce_str}");

        if !params.is_empty() {
            let encoded = serde_urlencoded::to_string(params)
                .map_err(|e| anyhow::anyhow!("Failed to encode params: {e}"))?;
            post_data.push('&');
            post_data.push_str(&encoded);
        }

        let sha_input = format!("{nonce_str}{post_data}");
        let hash = digest::digest(&digest::SHA256, sha_input.as_bytes());
        let mut message = path.as_bytes().to_vec();
        message.extend_from_slice(hash.as_ref());
        let key = hmac::Key::new(hmac::HMAC_SHA512, &secret);
        let signature = hmac::sign(&key, &message);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Paste the API secret exactly as provided by Kraken (it is base64 already) without decoding or modifying it
  2. Strip whitespace/newlines from the secret string before constructing credentials
  3. Verify you are not swapping the API key and secret fields
  4. Validate with: python -c "import base64; base64.b64decode('<secret>')"

Example fix

// before
let creds = KrakenCredentials::new(api_key, my_decoded_hex_secret);
// after
let secret = my_raw_kraken_secret.trim().to_string();
let creds = KrakenCredentials::new(api_key, secret);
Defensive patterns

Strategy: validation

Validate before calling

import base64
base64.b64decode(secret, validate=True)  # raises if not valid standard base64

Type guard

function isValidBase64(s) { return /^[A-Za-z0-9+/]+={0,2}$/.test(s) && Buffer.from(s, 'base64').length > 0; }

Try / catch

match creds.sign_spot(path, nonce, &params) {
    Ok(sig) => sig,
    Err(e) if e.to_string().contains("decode API secret") => {
        eprintln!("api_secret is not valid base64; re-copy from Kraken: {e}");
        return Err(SignError::BadSecret);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Creating/using KrakenCredentials with an api_secret that is the raw (already-decoded) hex/plain secret, contains whitespace, uses a URL-safe alphabet, or is otherwise invalid standard base64.

Common situations: Copying the API secret from Kraken and accidentally decoding it first; trailing newline/space from a copy-paste or env var; misconfiguring secret vs key fields; reading the secret from a file with encoding issues.

Understand the failure class

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/fe2233a4c20524c6. Report an issue: GitHub.