How Buzz Implements Blossom Media Uploads with S3/MinIO Storage
Buzz stores binary assets in S3-compatible object storage using the Blossom protocol, which requires Nostr kind 24242 authorization events with SHA-256 content binding and tenant-aware server tags.
The Buzz Nostr relay (developed by Block) handles media through a cryptographically secured pipeline that implements the Blossom specification. This system binds upload tokens to specific file hashes and tenant hosts, then streams verified content to configurable S3-compatible backends such as MinIO or AWS S3.
The Blossom Authentication Flow (Kind 24242 Events)
All media operations in Buzz require a signed Nostr event of kind 24242 (the Blossom authorization kind). This event acts as a cryptographically verifiable capability token.
Constructing the Authorization Event
Clients must generate an event containing four mandatory tags:
t— The verb describing the intended action (uploadorget)expiration— A Unix timestamp in the future after which the token becomes invalidx— The SHA-256 hash of the file body (required for uploads per BUD-11 §6)server— The hostname of the relay handling the request (enables multi-tenant isolation)
The signed event JSON is then serialized and transmitted in the HTTP Authorization header as Bearer <event-json>.
Server-Side Verification
In crates/buzz-relay/src/api/media.rs, the extract_blossom_auth function extracts the header and delegates validation to the buzz-media crate. The verification chain involves two critical functions in crates/buzz-media/src/auth.rs:
-
verify_blossom_auth_event_for_— Validates the signature, ensures the kind is 24242, checks required tags are present, verifies the expiration timestamp has not passed, ensures the event was created recently (within the last 600 seconds by default), and confirms theservertag matches the tenant host. -
verify_blossom_upload_auth— Performs all standard checks plus validates that thextag matches the SHA-256 hash computed from the request body being uploaded.
// Server-side verification flow from crates/buzz-media/src/auth.rs
pub fn verify_blossom_upload_auth(
event: &Event,
expected_hash: &str,
server: Option<&str>,
max_age: u64,
) -> Result<(), AuthError> {
verify_blossom_auth_event_for_verb(event, "upload", server, max_age)?;
// Verify SHA-256 binding
let x_tags: Vec<_> = event.tags.iter()
.filter(|t| t.kind() == TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::X)))
.collect();
if !x_tags.iter().any(|t| t.content() == Some(expected_hash)) {
return Err(AuthError::HashMismatch);
}
Ok(())
}
S3/MinIO Storage Architecture
Once authorization passes, the system streams bytes to object storage through a thin abstraction layer.
Upload Handling and Streaming
The entry point buzz_media::upload::handle in crates/buzz-media/src/upload.rs receives the verified request body and delegates to the storage layer. The put_object function in crates/buzz-media/src/storage.rs utilizes the AWS SDK for S3 (or compatible alternatives like rusoto_s3) to:
- Generate a unique object key (typically the SHA-256 hash from the
xtag) - Write the byte stream to the configured bucket
- Store metadata (MIME type, content size) in Postgres via
buzz_media::types
// Streaming upload implementation reference
async fn handle_upload(
auth_event: Event,
body: impl AsyncRead,
sha256: &str,
bucket_cfg: &BucketConfig,
) -> Result<MediaMetadata, MediaError> {
// Cryptographic verification first
verify_blossom_upload_auth(&auth_event, sha256, None, 600)?;
// Stream to S3/MinIO
buzz_media::storage::put_object(bucket_cfg, sha256, body).await
}
Configuration and Bucket Setup
Storage targets are defined through environment variables and the BucketConfig struct. The repository includes a MinIO container configuration for local development (see deployment compose files), while production deployments set the BLOB_BUCKET environment variable to target AWS S3, DigitalOcean Spaces, or other S3-compatible services.
// Configuration structure reference
pub struct BucketConfig {
pub name: String,
pub endpoint: String,
pub region: String,
pub access_key: String,
pub secret_key: String,
}
Integration Points
The media system spans three architectural layers: the relay API, the shared media crate, and the desktop client bridge.
HTTP Endpoints in buzz-relay
The relay exposes REST endpoints at POST /media and GET /media/<id> in crates/buzz-relay/src/api/media.rs. These handlers extract the Blossom auth header, verify tenant isolation via the server tag, and call into the buzz-media crate. The multi-tenant design ensures an auth token generated for relay-a.example.com cannot be replayed against relay-b.example.com even within the same process.
Tauri Desktop Bridge
Desktop clients interact with the system through Tauri commands defined in desktop/src-tauri/src/commands/media.rs. This bridge signs the kind 24242 event using the user's Nostr private key, attaches the Bearer token to an HTTP request, and returns the resulting media URL to the frontend UI.
// Client-side upload flow
async fn upload_media(client: &Client, file_bytes: Vec<u8>, relay_host: &str, secret_key: &SecretKey) -> Result<String> {
let sha256 = compute_sha256(&file_bytes);
// 1. Create Blossom auth event
let auth_event = Event::new(
Kind::Custom(24242),
secret_key,
None,
vec![
Tag::custom("t", "upload"),
Tag::custom("expiration", (now() + 600).to_string()),
Tag::custom("x", &sha256),
Tag::custom("server", relay_host),
],
"upload token".to_string(),
);
// 2. Send with Authorization header
let response = client
.post(format!("https://{}/media", relay_host))
.header("Authorization", format!("Bearer {}", auth_event.as_json()))
.body(file_bytes)
.send()
.await?;
Ok(response.json::<MediaResponse>().await?.url)
}
Security Guarantees and Replay Protection
The Blossom implementation in Buzz provides three cryptographic guarantees:
- Verb-specific authorization — Tokens minted for
uploadcannot be replayed forgetoperations, preventing privilege escalation - Server-tag enforcement — The
servertag binds tokens to specific tenant hostnames, eliminating cross-tenant token leakage in multi-community deployments - Content-addressed integrity — The SHA-256
xtag ensures the uploaded bytes match the authorized hash, preventing tampering between auth and storage
Summary
- Buzz implements the Blossom protocol using Nostr kind 24242 events as authorization tokens for all media operations
- The
buzz-mediacrate handles verification incrates/buzz-media/src/auth.rsusingverify_blossom_upload_authandverify_blossom_auth_event_for_verb - Verified uploads stream to S3-compatible storage (MinIO or AWS S3) via
put_objectincrates/buzz-media/src/storage.rs - The
servertag and verb-specific checks prevent cross-tenant replay attacks and privilege escalation - Desktop clients use the Tauri bridge in
desktop/src-tauri/src/commands/media.rsto sign events locally before transmission
Frequently Asked Questions
What is the Blossom protocol in Buzz?
Blossom is a decentralized media hosting specification that uses Nostr events for authorization. In Buzz, it refers specifically to the kind 24242 event flow where clients cryptographically sign upload or download intents, and relays verify these signatures before serving or storing binary data. This eliminates the need for traditional bearer tokens or API keys while maintaining user sovereignty over media access.
How does Buzz verify media upload authorization?
Buzz verifies uploads through a three-stage check defined in crates/buzz-media/src/auth.rs. First, extract_blossom_auth parses the Authorization header. Then verify_blossom_auth_event_for_verb validates the signature, checks the event kind is 24242, verifies the expiration timestamp, and ensures the server tag matches the request host. Finally, verify_blossom_upload_auth confirms the x tag matches the SHA-256 hash of the request body.
Can Buzz use AWS S3 instead of MinIO?
Yes, Buzz works with any S3-compatible storage provider. The BucketConfig struct in crates/buzz-media/src/storage.rs accepts endpoint, region, and credential configurations via environment variables. While the development environment uses MinIO (configured in the Docker compose files), production deployments can target AWS S3, Cloudflare R2, or DigitalOcean Spaces by setting the appropriate BLOB_BUCKET and endpoint parameters.
How does the SHA-256 x-tag prevent tampering?
The x tag binds the authorization token to specific content. When a client requests an upload token, they must include the SHA-256 hash of the file they intend to upload in the x tag. The server recomputes the hash from the actual request body in verify_blossom_upload_auth and rejects the request if the values diverge. This prevents attackers from obtaining a token for one file and uploading different content.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →