# hashavatar
`hashavatar` is a Rust crate for deterministic, procedural avatar generation. It is designed for services that need stable user or tenant avatars without bundled artwork, sprite sheets, external asset packs, or filesystem-side effects.
The crate starts conservative: validated avatar dimensions, bounded identity input, namespace-isolated hashing, safe Rust rendering, in-memory raster encoding, SVG string rendering, and a release process with dependency, audit, fuzz, package, SBOM, and reproducibility checks.
## Current Status
The current development version is `0.6.0`.
Implemented now:
- Pure library crate; no bundled demo server and no CLI binary.
- Deterministic avatars derived from SHA-512 identity hashes.
- Namespace-aware identity derivation for tenant isolation and visual rollouts.
- Length-prefixed hash components to avoid delimiter ambiguity.
- Avatar families: `cat`, `dog`, `robot`, `fox`, `alien`, `monster`, `ghost`, `slime`, `bird`, `wizard`, `skull`, `paws`, `planet`, `rocket`, `mushroom`, `cactus`, `frog`, `panda`, `cupcake`, `pizza`, `icecream`, `octopus`, and `knight`.
- Background modes: `themed`, `white`, `black`, `dark`, `light`, and `transparent`.
- In-memory `WebP`, `PNG`, `JPEG`, and `GIF` encoding.
- Compact SVG string rendering.
- Typed errors for invalid dimensions and oversized identity inputs.
- Private `AvatarSpec` fields so dimensions must pass construction-time validation.
- No public path-writing helpers; callers own their storage and filesystem boundary.
- `#![forbid(unsafe_code)]` in library code.
- Golden visual regression fingerprints.
- Isolated fuzz harness for avatar identities, families, backgrounds, SVG rendering, and PNG encoding.
- Local release gates for formatting, clippy, tests, docs, dependency policy, RustSec advisories, package contents, SBOM generation, reproducible build checks, and crates.io publish dry runs.
Planned or intentionally external:
- HTTP serving, rate limits, cache headers, security headers, observability, and abuse controls live in [`hashavatar-api`](https://github.com/valkyoth/hashavatar-api).
- Additional output formats such as AVIF or JPEG XL require dependency-policy review before admission.
- Larger identity inputs should be normalized or mapped by the application before calling this crate.
## Trust Dashboard
| License | `MIT OR Apache-2.0` |
| MSRV | Rust `1.95.0` |
| Crate shape | Library only |
| Runtime dependencies | `image`, `palette`, `rand`, `sha2` |
| Unsafe policy | `#![forbid(unsafe_code)]` |
| Filesystem policy | No public path-writing APIs |
| Dimension limits | `64..=2048` pixels per side |
| Identity limits | 1024 bytes per identity input |
| Namespace limits | 128 bytes per tenant/style-version component |
| Hashing posture | SHA-512 with length-prefixed domain, namespace, style, and identity components |
| SVG posture | Generated numeric markup only; caller input is not inserted into SVG fragments |
| Release evidence | fmt, clippy, tests, docs, deny, audit, fuzz harness compile, package check, SBOM, reproducibility |
Security-control details live in [docs/SECURITY_CONTROLS.md](docs/SECURITY_CONTROLS.md). Dependency policy lives in [docs/DEPENDENCIES.md](docs/DEPENDENCIES.md). Panic policy lives in [docs/PANIC_POLICY.md](docs/PANIC_POLICY.md).
Future version planning for pluggable hashing, dependency-provided SIMD, and a
possible `no_std + alloc` core lives in
[docs/VERSION_PLAN.md](docs/VERSION_PLAN.md).
## Install
```toml
[dependencies]
hashavatar = "0.6.0"
```
For a local checkout:
```toml
[dependencies]
hashavatar = { path = "../hashavatar" }
```
The crate is dual-licensed:
```toml
license = "MIT OR Apache-2.0"
```
## Limits
| Minimum width/height | `64` |
| Maximum width/height | `2048` |
| Maximum identity input | `1024` bytes |
| Maximum namespace tenant | `128` bytes |
| Maximum namespace style version | `128` bytes |
These limits are enforced by constructors and render entry points. They are intended to make the safe path the normal path for public web endpoints.
## Example: Encode WebP
```rust
use hashavatar::{
AvatarBackground, AvatarKind, AvatarOptions, AvatarOutputFormat, AvatarSpec,
encode_avatar_for_id,
};
let spec = AvatarSpec::new(256, 256, 0)?;
let bytes = encode_avatar_for_id(
spec,
"robot@hashavatar.app",
AvatarOutputFormat::WebP,
AvatarOptions::new(AvatarKind::Robot, AvatarBackground::Transparent),
)?;
assert!(!bytes.is_empty());
# Ok::<(), Box<dyn std::error::Error>>(())
```
The returned bytes can be sent as an HTTP response, uploaded to object storage, written to a caller-selected path, or cached by a CDN.
## Example: Render SVG
```rust
use hashavatar::{
AvatarBackground, AvatarKind, AvatarOptions, AvatarSpec, render_avatar_svg_for_id,
};
let spec = AvatarSpec::new(256, 256, 0)?;
let svg = render_avatar_svg_for_id(
spec,
"alien@hashavatar.app",
AvatarOptions::new(AvatarKind::Alien, AvatarBackground::Transparent),
)?;
assert!(svg.starts_with("<svg "));
assert!(svg.contains("alien avatar"));
# Ok::<(), Box<dyn std::error::Error>>(())
```
Use SVG when you need vector output, easy inspection, text storage, or post-processing by application code.
## Example: Namespaced Tenants
```rust
use hashavatar::{
AvatarBackground, AvatarKind, AvatarNamespace, AvatarOptions, AvatarOutputFormat,
AvatarSpec, encode_avatar_for_namespace,
};
let namespace = AvatarNamespace::new("customer-a", "v2")?;
let spec = AvatarSpec::new(256, 256, 0)?;
let bytes = encode_avatar_for_namespace(
spec,
namespace,
"user-123",
AvatarOutputFormat::Png,
AvatarOptions::new(AvatarKind::Cat, AvatarBackground::Themed),
)?;
assert!(!bytes.is_empty());
# Ok::<(), Box<dyn std::error::Error>>(())
```
Use namespaces when the same user identifier must not collide visually across tenants, products, or style-version rollouts.
## Example: Raw Image Buffer
```rust
use hashavatar::{
AvatarBackground, AvatarKind, AvatarOptions, AvatarSpec, render_avatar_for_id,
};
let spec = AvatarSpec::new(128, 128, 0)?;
let image = render_avatar_for_id(
spec,
"fox@hashavatar.app",
AvatarOptions::new(AvatarKind::Fox, AvatarBackground::Themed),
)?;
assert_eq!(image.width(), 128);
assert_eq!(image.height(), 128);
# Ok::<(), Box<dyn std::error::Error>>(())
```
Use raw buffers when the caller wants to composite, inspect pixels, run custom encoding, or integrate with an existing image pipeline.
## Handling Untrusted Input
```rust
use hashavatar::{
AvatarBackground, AvatarKind, AvatarOptions, AvatarOutputFormat, AvatarSpec,
encode_avatar_for_id,
};
fn avatar_response_bytes(user_id: &str, requested_size: u32) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let spec = AvatarSpec::new(requested_size, requested_size, 0)?;
let options = AvatarOptions::new(AvatarKind::Cat, AvatarBackground::Transparent);
encode_avatar_for_id(spec, user_id, AvatarOutputFormat::WebP, options)
.map_err(Into::into)
}
```
The crate rejects unsupported sizes and oversized identities. Applications should still enforce their own routing, authentication, rate limiting, cache policy, response headers, and request body limits.
## API Reference Summary
Important public entry points:
- `AvatarSpec::new(width, height, seed) -> Result<AvatarSpec, AvatarSpecError>`
- `AvatarIdentity::new(input) -> Result<AvatarIdentity, AvatarIdentityError>`
- `AvatarNamespace::new(tenant, style_version) -> Result<AvatarNamespace, AvatarIdentityError>`
- `AvatarOptions::new(kind, background)`
- `encode_avatar_for_id(...)`
- `encode_avatar_for_namespace(...)`
- `render_avatar_for_id(...)`
- `render_avatar_for_namespace(...)`
- `render_avatar_svg_for_id(...)`
- `render_avatar_svg_for_namespace(...)`
Lower-level identity-specific renderers are available for callers that want direct control over a specific avatar family.
## Output Formats
| WebP | `AvatarOutputFormat::WebP` | Recommended default for modern web delivery. |
| PNG | `AvatarOutputFormat::Png` | Lossless and broadly compatible. |
| JPEG | `AvatarOutputFormat::Jpeg` | Transparent pixels are composited over white. |
| GIF | `AvatarOutputFormat::Gif` | Legacy-compatible single-frame output. |
| SVG | `render_avatar_svg_*` | Returns a string rather than raster bytes. |
AVIF and JPEG XL are not exposed because they add dependency or encoder maturity tradeoffs that have not cleared the crate's dependency policy.
## Determinism
The output is deterministic for the tuple:
```text
namespace tenant + namespace style version + identity bytes + avatar kind + background + dimensions + seed
```
This makes the crate suitable for stable CDN-backed avatar URLs and golden regression tests. Namespace hashing uses length-prefixed components, so embedded separator bytes cannot create tenant/style-version ambiguity.
## Testing And Release Evidence
The repository includes:
- same-input stability tests
- different-input divergence tests
- raster export round-trip tests
- SVG safety and compactness tests
- enum parsing tests
- transparent background checks
- golden visual fingerprint tests
- fuzz harness compilation
- `cargo deny` policy
- RustSec advisory scanning
- reproducible package/build checks
- SBOM generation
- crates.io publish dry run
Run the standard local gate:
```bash
scripts/checks.sh
```
Run the fuller release gate:
```bash
scripts/stable_release_gate.sh check
```
## Provenance
The repository is intended to remain code-generated and asset-free. For a direct statement of how the visuals are produced, see [PROVENANCE.md](PROVENANCE.md).
## Web API And Demo
The crate is focused on reusable rendering code. The public HTTP API and demo website live in the separate [`hashavatar-api`](https://github.com/valkyoth/hashavatar-api) project.
## Changelog
See [CHANGELOG.md](CHANGELOG.md) and the release note files for version-by-version details.
## License
Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))