kcode-k1-http-signature 0.1.0

Canonical K1-HTTP-1 request encoding and strict Ed25519 verification
Documentation
# K1 HTTP request signatures

This library provides stateless, HTTP-framework-independent canonical usernames, request-signing bytes, and strict Ed25519 verification. It does not decode headers, read or hash bodies, perform replay or account handling, generate keys, invoke callbacks, log, or perform I/O.

## Public API

```rust
pub struct CanonicalUsername(String);

impl CanonicalUsername {
    pub fn parse(value: &str) -> Result<Self, UsernameError>;
    pub fn as_str(&self) -> &str;
}

pub enum UsernameError {
    Empty,
    TooLong,
    NonAscii,
    ControlCharacter,
}

pub struct RequestBinding<'a> {
    pub server_id: &'a str,
    pub public_origin: &'a str,
    pub username: &'a CanonicalUsername,
    pub epoch: u64,
    pub nonce: [u8; 16],
    pub method: &'a str,
    pub target: &'a str,
    pub content_type: &'a str,
    pub body_sha256: [u8; 32],
}

pub enum SignatureError {
    MalformedPublicKey,
    VerificationFailed,
}

pub fn encode(binding: &RequestBinding<'_>) -> Vec<u8>;

pub fn verify(
    binding: &RequestBinding<'_>,
    public_key: &[u8; 32],
    signature: &[u8; 64],
) -> Result<(), SignatureError>;
```

Both error enums implement `Display` and `std::error::Error`. `CanonicalUsername` implements `Clone`, `Debug`, `Eq`, `Hash`, and `PartialEq`; both error enums implement `Clone`, `Copy`, `Debug`, `Eq`, and `PartialEq`.

## Canonical usernames

`CanonicalUsername::parse` accepts exactly 1 through 64 bytes, all in printable ASCII `0x20..=0x7e`. Validation is ordered: empty input returns `UsernameError::Empty`; more than 64 bytes returns `TooLong`; otherwise any non-ASCII byte returns `NonAscii`; otherwise an ASCII control byte (`0x00..=0x1f` or `0x7f`) returns `ControlCharacter`. Successful parsing lowercases only ASCII letters and preserves every other printable byte, including punctuation and leading, trailing, or internal spaces. `as_str` returns the owned canonical value. No other binding field is normalized.

## Request binding and encoding

The text fields are used as their exact UTF-8 bytes. `target` is the exact encoded path and query. `content_type` is the exact header value, or the empty string when absent. `nonce` and `body_sha256` are used as their exact fixed-size byte arrays.

`encode` returns one byte vector in this order:

1. the nine ASCII bytes `K1-HTTP-1`, with no prefix or terminator;
2. `server_id`, length-prefixed;
3. `public_origin`, length-prefixed;
4. the canonical `username`, length-prefixed;
5. `epoch` as an unsigned 64-bit big-endian integer;
6. the 16 `nonce` bytes;
7. `method`, length-prefixed;
8. `target`, length-prefixed;
9. `content_type`, length-prefixed;
10. the 32 `body_sha256` bytes.

Each text length prefix is exactly its UTF-8 byte length represented as eight bytes: one unsigned 64-bit big-endian integer. Empty text is therefore encoded as eight zero bytes. There are no separators, terminators, omitted fields, or implicit transformations.

## Verification and errors

`verify` decodes the supplied 32-byte public key, encodes the binding exactly as above, and performs strict Ed25519 verification of the supplied 64-byte signature over those bytes. It returns `Ok(())` only for a valid signature. A public key that cannot be decoded as an Ed25519 verification key returns `SignatureError::MalformedPublicKey`; this is checked before encoding or signature verification. Every strict signature-verification failure returns `SignatureError::VerificationFailed`, without exposing further detail.

## Dimensions, allocation, performance, and concurrency

Let `S` be the sum of the UTF-8 byte lengths of `server_id`, `public_origin`, canonical `username`, `method`, `target`, and `content_type`. The encoded output is exactly `113 + S` bytes. Username parsing is O(username bytes); encoding and verification are O(S), with fixed 16-byte nonce, 32-byte hash and public key, and 64-byte signature dimensions.

Successful username parsing owns one `String`. Encoding preallocates and returns the sole encoded `Vec<u8>`. For a decodable key, verification creates one temporary encoding and performs one strict verification; a malformed key creates no encoding. Apart from those encoded bytes and the owned username, operations use bounded local state. There is no I/O or locking. Operations are stateless and can run concurrently with independent or shared immutable inputs.

The reproducible broad performance canary on managed Linux uses an exact 16 KiB target and requires 16 iterations—each with one explicit encode and one verify—to complete in under 30 seconds. `verify` performs its own encoding in each iteration.