Skip to main content

ContentAddressable

Trait ContentAddressable 

Source
pub trait ContentAddressable {
    // Required method
    fn canonical_form(&self) -> Result<Vec<u8>, ContentError>;

    // Provided methods
    fn content_id(&self) -> Result<ContentId, ContentError> { ... }
    fn verify(&self, expected: &ContentId) -> Result<bool, ContentError> { ... }
    fn ensure_content_id(
        &self,
        expected: &ContentId,
    ) -> Result<(), ContentError> { ... }
    fn from_canonical_form(bytes: &[u8]) -> Result<Self, ContentError>
       where Self: DeserializeOwned + Sized { ... }
}
Expand description

A value that can name itself by its content.

Implementors provide canonical_form; the content_id and verify methods are derived from it for free.

§Implementing

For any #[derive(serde::Serialize)] type, the canonical form is just its canonical dag-cbor encoding, so the implementation is one line:

use content_addressable::{canonical, ContentAddressable, ContentError};
use serde::Serialize;

#[derive(Serialize)]
struct Block {
    parent: Option<String>,
    payload: Vec<u8>,
}

impl ContentAddressable for Block {
    fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
        canonical::to_canonical_dagcbor(self)
    }
}

let block = Block { parent: None, payload: vec![1, 2, 3] };
let id = block.content_id().unwrap();
assert!(block.verify(&id).unwrap());

Required Methods§

Source

fn canonical_form(&self) -> Result<Vec<u8>, ContentError>

Produce the deterministic byte representation of this value.

This is the only required method. The canonical bytes must be a function of the value alone: equal values must produce equal bytes. The recommended implementation defers to to_canonical_dagcbor on a Serialize type, which guarantees that property.

§Errors

Returns ContentError if the value cannot be canonically encoded.

Provided Methods§

Source

fn content_id(&self) -> Result<ContentId, ContentError>

Compute this value’s ContentId.

Hashes the canonical_form into a BLAKE3 CIDv1. Override only if you have a faster path that is provably identical to the default.

§Errors

Propagates any error from canonical_form.

Source

fn verify(&self, expected: &ContentId) -> Result<bool, ContentError>

Check whether this value’s content id matches an expected id.

Returns Ok(true) if the recomputed id equals expected, Ok(false) otherwise. This is the integrity check at the heart of the doctrine: the value re-derives its own identity and compares it to the claim.

§Mismatch is Ok(false), not an error (FROZEN at 0.1.0)

A mismatch is not an error — it returns Ok(false). A negative answer to “do these match?” is a successful, expected result, not a failure to check; forcing it through the Err channel would conflate “I checked, the answer is no” with “I couldn’t check”. The ?-friendly Result<bool> also composes cleanly in boolean logic (if a.verify(&id1)? && b.verify(&id2)? { … }). This return contract is a frozen part of the 0.1.0 API surface (README gate item #8) — flipping Ok(false) to an Err arm later would be a breaking change.

If you want a mismatch to short-circuit via ?, use the strict helper ensure_content_id, which returns Err(ContentError::VerificationFailed) on mismatch — don’t hand-roll it.

§Errors

Propagates any error from content_id only (i.e. an encoding failure in canonical_form); never an Err on a clean mismatch.

Source

fn ensure_content_id(&self, expected: &ContentId) -> Result<(), ContentError>

Verify, returning an error on mismatch instead of Ok(false).

Like verify, but a mismatch is reported as Err(ContentError::VerificationFailed) carrying both ids (as their Display strings, multibase base32-lower b…), rather than Ok(false). On a match it returns Ok(()). Use this when a mismatch should short-circuit through ?; use verify when you want the boolean to compose in further logic.

This is the strict form the crate’s doctrine promises: it makes ContentError::VerificationFailed a real, reachable, tested error path rather than a name callers must construct by hand. It is a defaulted trait method, so every implementor gets it for free. Both verify and this helper are frozen for the 0.1.0 API surface (README gate item #8).

§Errors
Source

fn from_canonical_form(bytes: &[u8]) -> Result<Self, ContentError>
where Self: DeserializeOwned + Sized,

Checked ingress: decode a value from its canonical form, proving the bytes reproduce it — a checked partial inverse of canonical_form.

This is the door for bytes you did not produce: foreign, stored, or off the wire.

§The enforced invariant

On success, and unconditionally:

let value = T::from_canonical_form(bytes)?;
assert_eq!(value.canonical_form()?, bytes);

That is the whole guarantee, and it is a claim about bytes, not about the codec: it is established by re-encoding the decoded value and comparing, not by appealing to dag-cbor being canonical. Exact byte equality is also strictly stronger than comparing two ContentIds — it assumes no collision resistance.

§Corollary for a lawful implementation

Call an implementation lawful when its content_id agrees with the trait law — content_id() == ContentId::from_canonical_bytes(&canonical_form()?), which the provided default satisfies by construction. For a lawful implementation the invariant above gives:

value.content_id()? == ContentId::from_canonical_bytes(bytes)

This is a corollary, not a guarantee. content_id is overridable, and an override that is not provably identical to the default breaks the equation. Nothing here calls content_id, so the crate cannot detect that. A caller who needs the equation held rather than assumed checks it, with ensure_content_id:

value.ensure_content_id(&ContentId::from_canonical_bytes(bytes))?;
§Partial: the domain, and which prerequisite failed

It succeeds on exactly those bytes that are

  1. canonical dag-cbor — otherwise ContentError::DecodingError (not dag-cbor at all) or ContentError::NonCanonical (dag-cbor, but not the canonical encoding). Established generically, before any Self is constructed, so the result does not lean on this type’s serde impl being well behaved;
  2. directly deserializable as Self — otherwise ContentError::DecodingError;
  3. reproduced byte-for-byte by the decoded value’s canonical_form — otherwise ContentError::LossyDecode.

Every failure names the prerequisite that was not met.

§What “inverse” does and does not mean here

On its accepted byte domain, decoding and re-encoding reproduces the original canonical bytes. The returned value therefore has the same canonical representation and content identity; it is not necessarily equal to a previously encoded in-memory value unless the type separately guarantees a value-preserving serde round trip.

Serialize + DeserializeOwned does not establish that. A #[serde(skip)] field is the clean counterexample: it is absent from the encoding entirely, so two values differing only in it share identical canonical bytes, and decoding returns the default rather than what was encoded. This door correctly accepts that — those bytes really are that value’s canonical representation. Identity is preserved; the in-memory value need not be.

A plain #[serde(default)] field is not such a case. It only supplies a value when one is missing on the way in; ordinary derived serialization writes it back out, so the re-encoding differs from the input and stage 3 reports ContentError::LossyDecode. It joins #[serde(skip)] here only if it is also omitted when serializing.

A second, separate limit applies to the shape of the type:

A type may define a canonical_form that is deliberately not its serde representation — an envelope, a versioned framing, a projection — and still be perfectly lawful for content addressing: its identity is well defined and reproducible. But its canonical bytes are then not directly deserializable as Self unless it also supplies a matching Deserialize, so from_canonical_form on its own output fails prerequisite 2 with ContentError::DecodingError.

That is not a defect in the type and does not make its canonical_form unlawful. It means this method is checked ingress for the common representation, not a universal inverse: a custom canonical form needs a custom decoder to match it. What the method never does is guess — it refuses rather than return a value the bytes do not reproduce.

§Why stage 3 re-encodes through canonical_form

Deliberately, rather than through to_canonical_dagcbor: canonical_form is the function that defines this type’s identity, so it is the only re-encode whose equality proves the invariant. For the recommended one-line implementation the two coincide; for a type with its own canonical form they do not, and only this one is sound. It is also why the bound is DeserializeOwned + Sized and not also Serialize.

For a plain Serialize + Deserialize value that is not ContentAddressable, use canonical::from_canonical_dagcbor_checked, whose stage 3 is to_canonical_dagcbor instead.

§Overriding

Like content_id, override this only if you have a path that is provably identical. An override that skips a stage does not weaken the store seam — NodeStoreExt::get_node (feature unstable-store) deliberately runs the shared body rather than this method, so a T cannot hand itself a pass — but it does weaken every direct caller of T::from_canonical_form.

§Availability

Added in 0.1.2 (issue #90) as a defaulted associated function, so every implementor gets it for free; it carries where Self: Sized, so the trait stays dyn-compatible. It is the one deliberate exception 0.1.2 makes to the otherwise frozen 0.1.x Rust API — see docs/STABILITY.md.

§Errors

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§