Skip to main content

Media and HTTP

Softchat validates the bounded imeta union and performs compatibility or streaming attachment transforms. The host owns file descriptors, private staging, HTTP execution, redirects, background work, and final publication.

Attachment metadata

Parse untrusted imeta tag values before using a URL, media type, size, hash, dimensions, duration, blurhash, or encryption fields. Unknown or malformed fields fail under the bounded Softchat profile. Waveform samples stay local and are not emitted in the interoperable tag.

Stream encryption

Create a stream with a 32-byte attachment key, persist its nonce with the encrypted object, feed bounded chunks, and store the final authentication tag and hashes returned by finish.

let stream = AttachmentEncryptionStream::new(key)?;
let nonce = stream.nonce();
for chunk in plaintext_chunks {
encrypted_file.write_all(&stream.update(chunk)?)?;
}
let final_value = stream.finish()?;

Authenticate before exposing plaintext

Decryption update returns staged bytes, not trusted media. Write them only to a private temporary destination. Call finish(authenticationTag) after the last ciphertext chunk. Only a successful finish permits an atomic move or UI exposure. On error or cancellation, delete the staging object.

Never stream unauthenticated plaintext directly to a decoder, gallery, media scanner, shared URL, or browser object URL.

NIP-98 request plans

createNip98Authorization binds a signed event to one normalized method, exact URL, optional payload hash, and caller timestamp. The result contains the Authorization header and explicitly says that authorization must not be forwarded across a redirect.

let plan = create_nip98_authorization(
&identity,
"PUT",
url,
Some(body.as_slice()),
now,
)?;
http.execute(plan.method, plan.url, plan.authorization_header, body)?;

Do not reuse a plan for another method, URL, payload, redirect target, or timestamp.