lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

blossom-media.md (12099B)


      1 # Blossom Media Foundation
      2 
      3 Status: active implementation contract
      4 
      5 Scope: public, runtime-independent Radroots primitives for Blossom BUD-01 hash paths, BUD-02
      6 descriptors, and local descriptor-versus-byte verification.
      7 
      8 ## Protocol Basis
      9 
     10 This contract is based on the Blossom repository at commit
     11 `b5bd2801d1763aa635fc8fea7a76597e0eb18990`:
     12 
     13 - [BUD-01: Server requirements and blob retrieval](https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/01.md)
     14 - [BUD-02: Blob upload and descriptor](https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/02.md)
     15 
     16 The upstream BUD rules and the Radroots application profile are distinct layers. A URL can be a
     17 structurally valid Blossom URL without being an approved Radroots reference. Callers must not treat
     18 structural parsing as approval, byte verification, network reachability, or proof that a server
     19 implements Blossom.
     20 
     21 ## Ownership And Dependency Boundary
     22 
     23 `radroots_blossom` owns these primitives as a public `no_std + alloc` protocol leaf. It may use pure
     24 hashing and serialization dependencies, but it must not depend on Radroots event models, event
     25 codecs, Nostr, an HTTP client or server runtime, platform policy, or private code.
     26 
     27 This contract does not implement BUD-03 server discovery, BUD-11 authorization, BUD-12 listing,
     28 upload entitlement, object-storage policy, network I/O, redirects, retries, or availability checks.
     29 BUD-11 is added as a separate foundation slice.
     30 
     31 ## SHA-256
     32 
     33 A `Sha256` is exactly 32 bytes. Its wire and display form is exactly 64 lowercase
     34 ASCII hexadecimal characters.
     35 
     36 - Parsing rejects uppercase hexadecimal, prefixes, separators, whitespace, non-hexadecimal bytes,
     37   and every length other than 64 characters.
     38 - Serialization and display always emit lowercase hexadecimal.
     39 - Digest construction hashes the exact supplied bytes without text conversion or normalization.
     40 - The empty byte sequence is valid and has its ordinary SHA-256 digest.
     41 
     42 ## Root Hash Paths
     43 
     44 A `HashPath` represents one BUD-01 root path:
     45 
     46 ```text
     47 /<64-lowercase-hex-sha256>[.<extension>]
     48 ```
     49 
     50 The leading slash is required by the root-path parser. The path has exactly one segment. It has no
     51 query, fragment, percent decoding, dot segment, or trailing slash.
     52 
     53 An extension is returned without its leading period. It consists of one or more nonempty
     54 dot-separated components. Each component contains only ASCII letters, digits, `-`, or `_`.
     55 Extension case is preserved because the remote path may be case-sensitive. Examples include `png`,
     56 `JPG`, and `tar.gz`; empty components, whitespace, path separators, percent escapes, and `..` are
     57 invalid.
     58 
     59 BUD-01 retrieval paths may omit the extension. A URL carried by a BUD-02 blob descriptor must
     60 include one. The extension is not used to infer or validate the descriptor MIME type.
     61 
     62 ## Structural Blob URLs
     63 
     64 A `BlobUrl` is an absolute structural BUD-01 URL with these invariants:
     65 
     66 - the scheme is `http` or `https`, compared case-insensitively and exposed canonically in lowercase
     67 - the authority contains one nonempty DNS hostname, canonical dotted-decimal IPv4 address, or
     68   bracketed IPv6 address and may contain a valid decimal port
     69 - raw DNS hostnames are ASCII, at most 253 bytes, and consist of dot-separated 1-through-63-byte
     70   labels containing only ASCII letters, digits, or interior hyphens; URL-parser-valid explicit
     71   ASCII punycode is accepted, while implicit IDNA conversion of a raw Unicode hostname is rejected
     72 - user information is forbidden
     73 - the path is exactly one root hash path
     74 - query and fragment components are forbidden
     75 - the path digest is exposed as a typed `Sha256`
     76 - an optional safe extension is parsed independently from the digest
     77 
     78 Scheme and DNS host case are canonicalized to lowercase by the URL parser. Extension case is
     79 preserved. Before parsing, the complete raw URL rejects whitespace plus Unicode general categories
     80 `Cc` and `Cf`. After structural parsing, the preserved raw authority is checked before a typed URL
     81 is returned: DNS labels must begin and end with an ASCII alphanumeric character, and noncanonical,
     82 shortened, octal, hexadecimal, or otherwise ambiguous IPv4 spellings are rejected. These checks
     83 prevent approval behavior from changing after a serialization round trip. Raw user-information
     84 delimiters, invalid or implicit-IDNA DNS names, percent-encoded path data, malformed or zero ports,
     85 unbracketed IPv6 addresses, and additional path segments are also rejected before parser
     86 normalization can enter a typed value.
     87 
     88 Structural validity proves only that the URL has a BUD-01-compatible shape. It does not approve the
     89 transport scheme, perform DNS resolution, follow a redirect, issue `HEAD` or `GET`, or establish
     90 that bytes exist at the URL.
     91 
     92 ## Approved Reference Policy
     93 
     94 Radroots reference approval is a second, explicit validation step over a structurally valid blob
     95 URL:
     96 
     97 - every structurally valid `https` URL is transport-approved
     98 - `http` is approved only for a syntactic loopback host
     99 - the loopback hostname set is case-insensitive exact `localhost`, or a hostname with at least one
    100   nonempty label ending in `.localhost`
    101 - the loopback IPv4 set is canonical dotted-decimal `127.0.0.0/8`
    102 - the loopback IPv6 set is the address `::1`, including equivalent expanded IPv6 spelling
    103 
    104 The policy does not use DNS resolution. A public hostname that resolves to loopback is not accepted
    105 as loopback. `localhost.`, `localhost.example`, IPv4-mapped IPv6 addresses, private or link-local
    106 addresses, unspecified addresses, and arbitrary public hosts are not approved for `http`. Expanded
    107 IPv6 spellings that parse exactly to `::1` remain structurally valid and approved.
    108 
    109 HTTPS approval is a reference-transport rule, not an SSRF or egress policy. Network runtimes remain
    110 responsible for their own destination, redirect, DNS-rebinding, response-size, and timeout controls.
    111 
    112 ## Media Types
    113 
    114 A `MediaType` is a syntactically valid MIME media type parsed by the repository's
    115 `mediatype` dependency. MIME parameters are supported. The canonical representation lowercases the
    116 type, subtype, suffix, and parameter names, sorts parameters by name, and preserves parameter
    117 values. Duplicate parameter names are rejected. Semantic equality is case-insensitive for those
    118 names, while parameter values retain the media-type library's case-sensitive semantics.
    119 
    120 Empty values, missing type/subtype structure, wildcards, invalid parameter syntax, leading or
    121 parser-truncated trailing whitespace, control characters, and non-ASCII token characters are
    122 rejected.
    123 
    124 Parsing never substitutes a default for empty or malformed input. A caller that intends
    125 `application/octet-stream` must provide it explicitly.
    126 
    127 The media type used for byte verification is selected and approved by the caller. This foundation
    128 does not sniff or infer content type from file bytes or the URL extension.
    129 
    130 ## BUD-02 Descriptors
    131 
    132 A structural `BlobDescriptor` contains the five required BUD-02 fields:
    133 
    134 | Field | Contract |
    135 | --- | --- |
    136 | `url` | structurally valid absolute root blob URL with an extension |
    137 | `sha256` | typed lowercase SHA-256 equal to the digest in `url` |
    138 | `size` | unsigned 64-bit byte count |
    139 | `type` | parsed `MediaType` |
    140 | `uploaded` | unsigned 64-bit Unix timestamp |
    141 
    142 Unknown descriptor fields are tolerated on input for forward compatibility. They need not be
    143 retained when the typed descriptor is serialized. Required fields, field types, URL structure, URL
    144 extension presence, URL/hash equality, and MIME syntax are validated during construction and
    145 deserialization; public fields must not permit bypassing those invariants.
    146 
    147 Descriptor parsing is structural. It does not apply the approved-reference scheme policy. An
    148 authored publication flow must explicitly require both a structurally valid descriptor and an
    149 approved descriptor URL.
    150 
    151 The `uploaded` value is parsed as protocol data. Structural parsing does not compare it to a wall
    152 clock or treat it as server attestation.
    153 
    154 ## Byte Commitments And Byte-Verified Descriptors
    155 
    156 A structural descriptor advances to `ApprovedDescriptor` only after its URL passes
    157 the explicit HTTPS-or-loopback reference policy. `ByteCommitment::from_bytes`
    158 computes the SHA-256 and exact `u64` size of a final byte slice and binds them to a caller-approved
    159 `MediaType`.
    160 
    161 An approved descriptor advances to `ByteVerifiedDescriptor` only through
    162 `verify_commitment`, or through the `verify_bytes` convenience path that constructs the same byte
    163 commitment. Verification succeeds only when all three values match:
    164 
    165 1. computed byte SHA-256 equals descriptor `sha256`
    166 2. exact byte length equals descriptor `size`
    167 3. caller-approved media type is semantically equal to descriptor `type`
    168 
    169 These comparisons are independent. URL extension does not participate in MIME verification, and
    170 no byte sniffing occurs.
    171 
    172 The resulting byte-verified state proves only that the reference policy approved the descriptor URL,
    173 the supplied descriptor and approved media type describe the supplied bytes, and the descriptor URL
    174 carries the same digest. It is deliberately not named or modeled as an upload receipt. It does not
    175 prove that an upload occurred, the descriptor was issued by the named server, the server is
    176 authorized or reachable, the blob is currently available, a redirect preserves the hash, or a later
    177 retrieval returns those bytes. An HTTP-capable runtime must separately require a successful BUD-02
    178 upload response before publication and must perform bounded retrieval checks at their owning layer.
    179 
    180 ## Stable Conformance Operations
    181 
    182 `contracts/conformance/vectors/blossom/hash_path_and_descriptor.v1.json` is executable evidence for
    183 this contract. Integration tests dispatch its vectors by `kind`:
    184 
    185 - `blossom.sha256.digest`
    186 - `blossom.sha256.parse.valid` and `blossom.sha256.parse.invalid`
    187 - `blossom.hash_path.parse.valid` and `blossom.hash_path.parse.invalid`
    188 - `blossom.blob_url.parse.valid` and `blossom.blob_url.parse.invalid`
    189 - `blossom.reference_policy.valid` and `blossom.reference_policy.invalid`
    190 - `blossom.media_type.parse.valid` and `blossom.media_type.parse.invalid`
    191 - `blossom.descriptor.parse.valid` and `blossom.descriptor.parse.invalid`
    192 - `blossom.descriptor.approve_reference.valid` and
    193   `blossom.descriptor.approve_reference.invalid`
    194 - `blossom.descriptor.verify_bytes.valid` and
    195   `blossom.descriptor.verify_bytes.invalid`
    196 
    197 `bytes_hex` values are exact bytes encoded as lowercase hexadecimal. A `null` extension or port
    198 means absence. Invalid typed-operation vectors use the exact stable identifier returned by
    199 `Error::code()`.
    200 
    201 ## Stable Error Identifiers
    202 
    203 The public typed API exposes these stable semantic identifiers:
    204 
    205 - `invalid_sha256`
    206 - `invalid_hash_path`
    207 - `invalid_file_extension`
    208 - `invalid_blob_url`
    209 - `unsupported_blob_url_scheme`
    210 - `blob_url_credentials_forbidden`
    211 - `blob_url_query_forbidden`
    212 - `blob_url_fragment_forbidden`
    213 - `insecure_blob_url`
    214 - `invalid_media_type`
    215 - `descriptor_extension_required`
    216 - `descriptor_hash_mismatch`
    217 - `blob_hash_mismatch`
    218 - `blob_size_mismatch`
    219 - `blob_media_type_mismatch`
    220 
    221 Malformed descriptor JSON can fail in serde before a `Error` exists. The executable
    222 descriptor harness uses three additional wire-shape classifications for those cases:
    223 
    224 - `missing_descriptor_field`, with the missing field named separately
    225 - `invalid_descriptor_size`
    226 - `invalid_descriptor_uploaded`
    227 
    228 Nested descriptor values that reach a Radroots parser, including invalid SHA-256, URL, MIME, URL
    229 extension, and URL/hash combinations, retain the applicable `Error::code()` value.
    230 
    231 The canonical vector file is mirrored under `crates/blossom/tests/fixtures/` so the published crate
    232 can execute the same conformance suite. Source-workspace tests require the canonical file and assert
    233 that the packaged mirror is byte-for-byte current.
    234 
    235 The public error enum is non-exhaustive so later BUD slices can add typed failures without breaking
    236 consumers. Adding error detail is allowed, but it must not collapse protocol structure, reference
    237 approval, and byte verification into one indistinguishable state.