Tagged content digests over RFC 8785 canonical bytes.
The wire form is the self-identifying tagged string
"sha-256:<43-char unpadded base64url>" — a consumer never guesses the
algorithm or encoding from length. Preimages are domain-separated —
SHA-256(separator || <<0>> || jcs_bytes) — under the registered
separator for the hashing domain (base §8.2), so a digest transplanted
across domains never verifies.
Canonicality is the caller's contract. hash/2 and verify_content/3
treat the covered bytes as opaque: callers MUST pass canonical JCS bytes.
The enforcement point is where received bytes enter the package — the
artifact decode path verifies canonicality first (digests are
computed only over exact received bytes). This module does not re-verify,
so hashing never double-parses a document nor leaks the JSON reason set
into this vocabulary.
The algorithm set is closed (:sha256); algorithm succession is a data
change plus a protocol revision, never a format change — an unknown tag
denies :digest_algorithm_unsupported and any malformed body denies
:digest_encoding_invalid, uniformly. SHA-256 conformance (FIPS 180-4
known-answer vectors) is pinned in the conformance suite.
A digest is an identity fact about bytes; it never authorizes anything.
A digest is an identity fact about bytes; it never authorizes anything.
Summary
Functions
Constant-time equality over the digest bytes: the comparison accumulates the XOR of every byte pair and tests once at the end, so no early exit leaks the position of a difference. Length and algorithm equality are checked first and may short-circuit — both are public shape, carried in the tagged wire form itself. This is defense-in-depth: a digest is public once tagged; the guarantee matters where one side is not yet public.
Parse a tagged digest string. A string with no colon is not a tagged
digest (:digest_encoding_invalid). A tag segment that does not exactly
match a registered algorithm tag denies :digest_algorithm_unsupported —
case and hyphenation variants and the empty tag included. Every body
defect denies :digest_encoding_invalid uniformly: wrong length (a
sha-256 body decodes to exactly 32 bytes), non-alphabet characters,
padding, non-canonical pad bits, extra colons.
Domain-separated digest over canonical JCS bytes: the base §8.2 preimage
separator || <<0>> || jcs_bytes under the registered separator for
domain. An unknown domain is an internal invariant and raises loudly.
SHA-256 of data as a tagged digest value.
The wire form "sha-256:<43-char unpadded base64url>". The clause matches
the 256-bit t() byte shape, so a hand-built struct with wrong-size bytes
fails loud (a FunctionClauseError invariant) rather than emitting a
non-conforming wire string.
Verify that tagged is the honest hash(domain, jcs_bytes): parse first
(a malformed tagged string denies with its from_tagged/1 reason, before
any comparison), then constant-time equality — a well-formed but
divergent digest denies :digest_mismatch. jcs_bytes are the caller's
canonical covered bytes (see the moduledoc contract).
Types
@type algorithm() :: :sha256
@type domain() ::
:blueprint_content
| :deployment_content
| :federation_envelope
| :signature
| :extension_schema
| :extension_registry
| :conformance_report
| :corpus_index
@type reason() ::
:digest_algorithm_unsupported | :digest_encoding_invalid | :digest_mismatch
@type t() :: %AgentBlueprintProtocol.Digest{algorithm: algorithm(), bytes: <<_::256>>}
Functions
Constant-time equality over the digest bytes: the comparison accumulates the XOR of every byte pair and tests once at the end, so no early exit leaks the position of a difference. Length and algorithm equality are checked first and may short-circuit — both are public shape, carried in the tagged wire form itself. This is defense-in-depth: a digest is public once tagged; the guarantee matters where one side is not yet public.
Parse a tagged digest string. A string with no colon is not a tagged
digest (:digest_encoding_invalid). A tag segment that does not exactly
match a registered algorithm tag denies :digest_algorithm_unsupported —
case and hyphenation variants and the empty tag included. Every body
defect denies :digest_encoding_invalid uniformly: wrong length (a
sha-256 body decodes to exactly 32 bytes), non-alphabet characters,
padding, non-canonical pad bits, extra colons.
Domain-separated digest over canonical JCS bytes: the base §8.2 preimage
separator || <<0>> || jcs_bytes under the registered separator for
domain. An unknown domain is an internal invariant and raises loudly.
SHA-256 of data as a tagged digest value.
The wire form "sha-256:<43-char unpadded base64url>". The clause matches
the 256-bit t() byte shape, so a hand-built struct with wrong-size bytes
fails loud (a FunctionClauseError invariant) rather than emitting a
non-conforming wire string.
Verify that tagged is the honest hash(domain, jcs_bytes): parse first
(a malformed tagged string denies with its from_tagged/1 reason, before
any comparison), then constant-time equality — a well-formed but
divergent digest denies :digest_mismatch. jcs_bytes are the caller's
canonical covered bytes (see the moduledoc contract).