> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anchorage.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing requests

> Create Ed25519 signatures for sensitive Anchorage Digital API requests.

Certain Anchorage endpoints require an [Ed25519 signature](https://en.wikipedia.org/wiki/EdDSA#Ed25519) in the request headers alongside the API key.
Signatures are optional unless explicitly required, but are encouraged for all requests for maximum security. The `Api-Signature` is valid for 60 seconds.

## Signature input

Create the signature by concatenating these values:

```text theme={null}
timestamp + uppercase HTTP method + request path including query + request body
```

For requests without a body, omit the body from the signature input. For
requests with a JSON body, sign the **exact bytes sent over the wire** —
that is, `JSON.stringify(body)` with no extra whitespace. Any difference
(stray spaces, trailing newlines, key reordering) will produce a signature
the server rejects.

| Value          | Notes                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------ |
| `timestamp`    | Seconds since the Unix Epoch in UTC. It must be within one minute of the API service time. |
| `method`       | Uppercase HTTP method, such as `GET`, `POST`, or `DELETE`.                                 |
| `request path` | Path and query string, such as `/v2/transfers?foo=bar&baz=bang`.                           |
| `body`         | `JSON.stringify(body)` for requests that include one. Empty for `GET`.                     |

## Sign a request

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    package com.anchorage.api.client;

    import okio.Buffer;
    import org.apache.commons.codec.DecoderException;
    import org.apache.commons.codec.binary.Hex;
    import org.bouncycastle.crypto.CryptoException;
    import org.bouncycastle.crypto.Signer;
    import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
    import org.bouncycastle.crypto.signers.Ed25519Signer;
    import org.json.JSONObject;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.http.client.SimpleClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;

    import java.nio.charset.StandardCharsets;
    import java.time.Instant;

    /**
     * A sample java API client to connect to Anchorage API v2
     *
     * Required dependencies:
     *   org.json:json:20210307
     *   org.bouncycastle:bcpkix-jdk15on:1.69
     *   commons-codec:commons-codec:1.15
     *   com.squareup.okio:okio:1.9.0
     */
    public class RestClientWithSigning {

        private RestTemplate restTemplate;

        public static void main(String[] args){
            RestClientWithSigning api = new RestClientWithSigning();
            api.init();
            api.apiCall();
        }

        private void init(){
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(2000);
            factory.setReadTimeout(2000);
            restTemplate = new RestTemplate(factory);
        }

        private void apiCall() {
            try {
                String url = "https://api.anchorage-staging.com/v2/transfers";
                String body = createRequestBody();

                HttpEntity<String> request = new HttpEntity<>(body, createHeaders("/v2/transfers", HttpMethod.POST, body));
                ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
                if (responseEntity != null && HttpStatus.CREATED == responseEntity.getStatusCode()) {
                    System.out.println(String.format("Getting data from ( %s ) response: %s", url, responseEntity.getBody()));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private String createRequestBody(){
            JSONObject source = new JSONObject();
            source.put("id", "1c920f4241b78a1d483a29f3c24b6c4c");
            source.put("type", "VAULT");

            JSONObject destination = new JSONObject();
            destination.put("id", "55e89d4a644d736b01533a2ea9b32a20");
            destination.put("type", "VAULT");

            JSONObject body = new JSONObject();
            body.put("source", source);
            body.put("assetType", "ETH");
            body.put("destination", destination);
            body.put("amount", "1000.00000000");
            return body.toString();
        }

        private HttpHeaders createHeaders(String requestPath, HttpMethod httpmethod, String body) throws DecoderException, CryptoException {
            String api_key = "your API Key";
            String signing_key_hex = "Your signing key";

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.set("Api-Access-Key", api_key);

            String timestamp = String.valueOf(Instant.now().getEpochSecond());
            byte[] toSign = decodeMessage(timestamp, httpmethod, requestPath, body);

            String signature = bc_sign(Hex.decodeHex(signing_key_hex), toSign);
            headers.add("Api-Signature", signature);
            headers.add("Api-Timestamp", timestamp);
            return headers;
        }

        private byte[] decodeMessage(String timestamp, HttpMethod httpmethod, String url_path, String body){
            Buffer buffer = new Buffer();
            buffer.write(timestamp.getBytes(StandardCharsets.UTF_8))
                    .write(httpmethod.name().getBytes(StandardCharsets.UTF_8))
                    .write(url_path.getBytes(StandardCharsets.UTF_8))
                    .write(body.getBytes(StandardCharsets.UTF_8));
            return buffer.readByteArray();
        }

        private String bc_sign(byte[] signing_key, byte[] toSign) throws CryptoException {
            Ed25519PrivateKeyParameters privateKeyParameters = new Ed25519PrivateKeyParameters(signing_key);
            Signer signer = new Ed25519Signer();
            signer.init(true, privateKeyParameters);
            signer.update(toSign, 0, toSign.length);
            return Hex.encodeHexString(signer.generateSignature());
        }
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using Sodium;

    // Dependencies: Sodium.Core 1.3.1
    // Replace privateKey, publicKey, and Api-Access-Key with your keys

    namespace WebAPIClient
    {
        class Program
        {
            private static readonly HttpClient client = new HttpClient();

            static async Task Main(string[] args)
            {
                await ProcessAnchorageAPISign();
            }

            private static async Task<Object> ProcessAnchorageAPISign()
            {
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                string privateKey = "your private key here";
                string publicKey = "your public key here";
                string signingKey = $"{privateKey}{publicKey}";

                string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
                string path = "/v2/transfers";
                string method = "POST";
                string payload = "{\"source\":{\"id\":\"1c920f4241b78a1d483a29f3c24b6c4c\",\"type\":\"VAULT\"},\"assetType\":\"ETH\",\"destination\":{\"id\":\"55e89d4a644d736b01533a2ea9b32a20\",\"type\":\"VAULT\"},\"amount\":\"1000.00000000\"}";
                string msgToSign = $"{timestamp}{method}{path}{payload}";
                var signature = ComputeSignature(Utilities.HexToBinary(signingKey), Encoding.UTF8.GetBytes(msgToSign));

                client.DefaultRequestHeaders.Add("Api-Access-Key", "your api access key");
                client.DefaultRequestHeaders.Add("Api-Timestamp", timestamp);
                client.DefaultRequestHeaders.Add("Api-Signature", signature);

                Uri u = new Uri("https://api.anchorage-staging.com/v2/transfers");
                HttpContent c = new StringContent(payload, Encoding.UTF8, "application/json");
                HttpResponseMessage result = await client.PostAsync(u, c);

                var response = await result.Content.ReadAsStringAsync();
                Console.WriteLine(response);
                return response;
            }

            static string ComputeSignature(byte[] privateKey, byte[] toSign)
            {
                byte[] signature = PublicKeyAuth.SignDetached(toSign, privateKey);
                return Utilities.BinaryToHex(signature);
            }
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    ```ruby theme={null}
    require "ed25519"
    require "net/http"
    require "time"

    def hex_to_bin(s)
      [s].pack('H*')
    end

    def bin_to_hex(s)
      s.unpack('H*').first
    end

    private_key_seed_hex = '0101010101010101010101010101010101010101010101010101010101010101'
    public_key_hex = '8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c'
    key_pair_hex = private_key_seed_hex + public_key_hex

    key_pair = hex_to_bin(key_pair_hex)
    signing_key = Ed25519::SigningKey.from_keypair(key_pair)

    timestamp = '1577880000' # Time.now.to_i.to_s

    req = Net::HTTP::Post.new('/v2/transfers?foo=bar&baz=bang')
    req.body = '{"source": {"id": "1c920f4241b78a1d483a29f3c24b6c4c", "type": "VAULT"},
    "assetType": "ETH", "destination": {"id": "55e89d4a644d736b01533a2ea9b32a20", "type": "VAULT"}, "amount": "1000.00000000"}'

    signature = signing_key.sign(timestamp + req.method + req.path + req.body)

    req['Api-Access-Key'] = 'YOUR_ACCESS_KEY'
    req['Api-Timestamp'] = timestamp
    req['Api-Signature'] = bin_to_hex(signature)

    puts bin_to_hex(signature)
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import time
    import requests
    from nacl import signing


    class AnchorageAuth(requests.auth.AuthBase):
        ACCESS_KEY_HEADER = "Api-Access-Key"
        SIGNATURE_HEADER = "Api-Signature"
        TIMESTAMP_HEADER = "Api-Timestamp"

        def __init__(self, access_key: str, signing_key_seed: bytes):
            self.access_key = access_key
            self.signing_key = signing.SigningKey(signing_key_seed)

        def __call__(self, request: requests.PreparedRequest):
            request.headers[self.ACCESS_KEY_HEADER] = self.access_key

            timestamp = str(int(time.time()))
            method = request.method.upper() if request.method else "GET"
            body = request.body or b""
            if isinstance(body, str):
                body = body.encode("utf-8")

            message = b"".join([
                timestamp.encode("utf-8"),
                method.encode("utf-8"),
                request.path_url.encode("utf-8"),
                body,
            ])

            request.headers[self.SIGNATURE_HEADER] = self.signing_key.sign(message).signature.hex()
            request.headers[self.TIMESTAMP_HEADER] = timestamp
            return request
    ```
  </Tab>

  <Tab title="Node.js">
    ```js theme={null}
    const { sign } = require("@noble/ed25519");
    const axios = require("axios");

    class AnchorageClient {
      constructor(accessKey, signingKeySeed) {
        this.accessKey = accessKey;
        this.signingKeySeed = signingKeySeed; // 32-byte hex seed
        this.basePath = "https://api.anchorage-staging.com";
      }

      async sendSignedRequest(method, endpoint, body) {
        const timestamp = Math.floor(Date.now() / 1000);
        const serializedBody = body ? JSON.stringify(body) : "";
        const signatureInput = `${timestamp}${method}${endpoint}${serializedBody}`;
        const messageHex = Buffer.from(signatureInput, "utf8").toString("hex");
        const signature = await sign(messageHex, this.signingKeySeed);
        const signatureHex = Buffer.from(signature).toString("hex");

        return axios({
          method,
          url: this.basePath + endpoint,
          data: body,
          headers: {
            "Api-Access-Key": this.accessKey,
            "Api-Signature": signatureHex,
            "Api-Timestamp": String(timestamp),
            "Content-Type": "application/json",
          },
        }).then((res) => res.data);
      }
    }

    // Example: create a transfer
    (async () => {
      const client = new AnchorageClient("YOUR_ACCESS_KEY", "YOUR_SIGNING_KEY_SEED");

      const transfer = await client.sendSignedRequest("POST", "/v2/transfers", {
        source: { id: "1c920f4241b78a1d483a29f3c24b6c4c", type: "VAULT" },
        assetType: "ETH",
        destination: { id: "55e89d4a644d736b01533a2ea9b32a20", type: "VAULT" },
        amount: "1000.00000000",
      });
    })();
    ```
  </Tab>
</Tabs>

<Note>
  `JSON.stringify(body)` produces the same compact bytes the server signs
  against, so you don't need to pre-serialize the request manually. The live
  signer above applies the same canonicalization to anything you paste into
  the body field.
</Note>
