# RATProto DID A small library for handling atproto DIDs. Optimized for memory use and speed, especially for `did:plc`. ## Features ### Compact - The `Did` type is guaranteed to be exactly **16 bytes** (even less than a `String`!) - `did:plc` uses no heap - `did:web` allocates up to 255 bytes on the heap - Other DIDs supported (even if not compatible with atproto) - see [below](#details). ### Performant - `did:plc` encoding and decoding uses intrinsics and SIMD for ridiculous performance - If you ever wanted to parse 100M+ `did:plc` identifiers per second, look no further ### Minimal dependencies - `thiserror` and nothing else ## Example ```rust use ratproto::{Did, DidKind}; fn main() { let idents = vec![ "did:plc:c6te24qg5hx54qgegqylpqkx", // ... ]; // Parse from &str // A `did:plc` identifier is stored as 15 bytes + a discriminant let mut dids: Vec = idents .into_iter() .filter_map(|s| Did::from_str(s).ok()) .collect(); // Match on the DID method for did in &dids { match did.kind() { DidKind::Plc => { /* ... */ } DidKind::Web => { /* ... */ } DidKind::Other => { /* ... */ } } } // Ord & PartialOrd dids.sort(); // Eq & PartialEq if dids.contains(&Did::from_str("did:plc:c6te24qg5hx54qgegqylpqkx").unwrap()) { // ... } // Display & Debug for did in &dids { println!("{did}, {did:?}") } } ``` ## Details The parser follows specifications outlined in [DID syntax](https://www.w3.org/TR/did-1.1/#did-syntax) and on [the atproto website](https://atproto.com/specs/did). Atproto has two officially supported ("blessed") methods, `plc` and `web`, which go through further validation: ### General syntax - Max. length of 2048 ([atproto spec](https://atproto.com/specs/did)) - `Did` does not parse [DID URLs](https://www.w3.org/TR/did-1.1/#did-url-syntax), so no `?query`, no `#fragment` sections, and no `/path`s - The whole DID must follow [DID syntax rules](https://www.w3.org/TR/did-1.1/#did-syntax) - Percent-encoding is currently _not_ validated, but the identifier must not end in a `%` - Atproto spec currently states that methods must be `[a-z]`, but DID syntax allows digits as well. See also [GitHub issue](https://github.com/bluesky-social/atproto-website/issues/292) - DIDs that are not `did:plc` or `did:web` allocate heap space for the method and identifier as needed. ### PLC `did:plc` identifiers must be _exactly_ 24 base32 characters - The [PLC spec](https://web.plc.directory/spec/v0.1/did-plc) notes, _"It is conceivable that longer DID PLCs, with more of the SHA-256 characters, will be supported in the future."_ Due to the optimizations this library performs, only 24-character identifiers are supported. Because allowing longer `did:plc` identifiers currently feels very unlikely, and it would break the assumptions based on which this library implements optimizations for PLC identifiers, longer identifiers are currently rejected. Adding fallback support could make the API unnecessarily messy and lead to a DID variant that basically never exists anywhere, but this could be implemented in the future. ### Web `did:web` identifiers must be syntactically-valid web domains ([DID Web spec](https://w3c-ccg.github.io/did-method-web/)) - Max. 255 characters, domain segment syntax restrictions ( see [RFC1035](https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1)) - TLDs disallowed by atproto are currently NOT currently checked - Only hostname-level DIDs are supported (as per atproto spec) - i.e. no `:path` - `localhost` is the only hostname that allows a path, but it should only be used in development, and the `:` must be percent-encoded (e.g. `did:web:localhost%3A1234`) --- ## Benchmarks This crate implements benchmarks using [Criterion.rs](https://criterion-rs.github.io/book/criterion_rs.html). To get started with running benchmarks locally (assuming you have Rust installed, of course), run the following: ```shell cargo bench # Optionally for longer measurement time: cargo bench -- --measurement-time 15 ``` Benchmark results can then be found in `./target/criterion/report/index.html`. ## Miri This crate uses a bunch of `unsafe`, and is Miri-tested for soundness. To test with Miri, make sure you have it installed first: ```shell rustup +nightly component add miri ``` Then, run unit tests with: ```shell cargo +nightly miri test ``` _Note that this skips unsupported tests, such as those with SIMD, but those also include much more extensive documentation. See [plc_codec.rs](./src/plc_codec.rs)._ --- While this library is still in versions `0.0.x`, expect a few potential API breaking changes as the interface is finalized and a few remaining optimizations are implemented. For versions `0.x.x` the plan is to introduce more items from the feature wishlist before stabilizing at `1.x.x`. Version `1.x.x` should provide a feature-complete API, but patches (or further major releases) may introduce more additional features. See [TODOs](./TODOS.md) for a wishlist of "nice-to-have" features and optimizations.