# Signature

HMAC-SHA256 over a deterministic canonical string. Cross-SDK identical — the JS, Rust, and Go SDKs all share `tests/fixtures/signature-vectors.json`.

```rust
use akira_billing::{
    canonical, sign, new_nonce,
    HEADER_PRODUCT, HEADER_TIMESTAMP, HEADER_NONCE, HEADER_SIGNATURE,
};
```

`Client` wraps the whole flow. Use these primitives when signing requests from outside the crate (e.g. a custom HTTP layer or a service mesh integration).

## Headers

| Constant | Value |
|----------|-------|
| `HEADER_PRODUCT` | `"X-Akira-Product"` |
| `HEADER_TIMESTAMP` | `"X-Akira-Timestamp"` |
| `HEADER_NONCE` | `"X-Akira-Nonce"` |
| `HEADER_SIGNATURE` | `"X-Akira-Signature"` |

## Canonical string

```rust
pub fn canonical(
    product: &str,
    timestamp: i64,
    nonce: &str,
    method: &str,
    path: &str,
    body: &[u8],
) -> String;
```

Returns:

```
{product}\n{timestamp}\n{nonce}\n{METHOD}\n{path}\n{sha256_hex_of_body}
```

Rules:

- `method` is uppercased.
- `path` includes the query string when present.
- `body` is the request body bytes; pass `&[]` for `GET`/`DELETE`.
- `sha256_hex_of_body` is lowercase hex (no `0x` prefix). Empty body → `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.

## Signing

```rust
pub fn sign(secret: &str, canonical: &str) -> String;
```

Computes `hex(hmac_sha256(secret, canonical))`. The secret is consumed as raw bytes (UTF-8 of the `&str`).

## Nonce

```rust
pub fn new_nonce() -> String;
```

16 bytes from `rand::thread_rng()` → 32 lowercase hex chars.

## Replay window

The server checks `X-Akira-Timestamp` against its wall clock. Outside the 300-second window the request is rejected with `timestamp_skew`. Nonces are cached for 600 seconds; a replayed `(timestamp, nonce)` pair is rejected with `nonce_replay`.

## Worked example — sign from a Tower middleware

```rust
use akira_billing::{canonical, new_nonce, sign};
use http::{HeaderMap, HeaderValue, Method, Request};
use std::time::{SystemTime, UNIX_EPOCH};

async fn sign_request(
    secret: &str,
    product: &str,
    req: &mut Request<Vec<u8>>,
) -> anyhow::Result<()> {
    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)?
        .as_secs() as i64;
    let nonce = new_nonce();
    let body = req.body();
    let path = req.uri().path_and_query()
        .map(|p| p.as_str())
        .unwrap_or("/");

    let c = canonical(product, ts, &nonce, req.method().as_str(), path, body);
    let signature = sign(secret, &c);

    let headers = req.headers_mut();
    headers.insert("x-akira-product", HeaderValue::from_str(product)?);
    headers.insert("x-akira-timestamp", HeaderValue::from_str(&ts.to_string())?);
    headers.insert("x-akira-nonce", HeaderValue::from_str(&nonce)?);
    headers.insert("x-akira-signature", HeaderValue::from_str(&signature)?);

    Ok(())
}
```

## Cross-SDK fixtures

`tests/fixtures/signature-vectors.json` defines the cross-SDK contract:

```json
[
  {
    "name": "get-no-body",
    "secret": "7d1c…",
    "product": "spectra",
    "timestamp": 1714532400,
    "nonce": "0123456789abcdef0123456789abcdef",
    "method": "GET",
    "path": "/api/v1/me/products/spectra/license",
    "body": "",
    "expected_signature": "999833bb…"
  },
  …
]
```

The Rust signature integration test (`tests/signature.rs`) loads this fixture and asserts byte-equal hex output against `sign(secret, canonical(...))`. The Go SDK's `signature_test.go` does the same. Any change to canonical layout requires both crates to roll together.

---

Navigation: [← Client](10-client.md) · **Signature** · [Errors →](12-errors.md)
