A highly-optimized library for atproto DIDs.
Rust 100.0%
73 1 0

Clone this repository

https://tangled.org/metaflame.dev/ratproto-did https://tangled.org/did:plc:c6te24qg5hx54qgegqylpqkx/ratproto-did
git@tangled.org:metaflame.dev/ratproto-did git@tangled.org:did:plc:c6te24qg5hx54qgegqylpqkx/ratproto-did

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

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.

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#

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<Did> = 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 and on the atproto website. Atproto has two officially supported ("blessed") methods, plc and web, which go through further validation:

General syntax#

  • Max. length of 2048 (atproto spec)
  • Did does not parse DID URLs, so no ?query, no #fragment sections, and no /paths
  • The whole DID must follow DID syntax rules
    • 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
  • 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 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)

  • Max. 255 characters, domain segment syntax restrictions ( see RFC1035)
  • 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.

To get started with running benchmarks locally (assuming you have Rust installed, of course), run the following:

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:

rustup +nightly component add miri

Then, run unit tests with:

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.


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 for a wishlist of "nice-to-have" features and optimizations.