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.