napplet.
← All NAPs

NAP-BLOSSOM / PR #71

Low-Level Blossom Blob Access

Open PR

Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.

NAP-BLOSSOM

Low-Level Blossom Blob Access

draft

NAP ID: NAP-BLOSSOM Domain: blossom Depends:

  • identity -- capability · optional -- authenticated Blossom requests and BUD-03 server-list mutations use the current shell-user identity.
  • relay -- capability · optional -- BUD-03 server-list helpers read and publish kind 10063 events through the runtime relay surface.
  • relay -- wire · optional -- imports RelayEventResult for BUD-03 server-list event returns.
  • resource -- wire · optional -- imported RelayEventResult.sidecar.resources? carries ResourceSidecarEntry[], a type owned by the resource domain. Web binding (NIP-5D): window.napplet.blossom; domain presence signals availability.

Description

NAP-BLOSSOM gives napplets a low-level, shell-mediated interface to Blossom blob servers. A napplet names the server, blob hash, bytes, and endpoint intent. The shell performs the HTTP request, applies runtime network policy, constructs and signs any required Blossom authorization token, and returns endpoint-shaped results.

This is not a generic network escape hatch. It only covers Blossom server endpoints and Blossom URI helpers. NAP-RESOURCE remains the generic byte-fetch surface. NAP-UPLOAD remains the high-level "store these bytes somewhere" surface where the shell chooses NIP-96, Blossom, or another rail.

API Surface

Operation Parameters Result Wire
check request (BlossomBlobCheckRequest) BlossomBlobCheckResult blossom.check / blossom.check.result
get request (BlossomGetRequest) BlossomBytesResult blossom.get / blossom.get.result
checkUpload request (BlossomCheckRequest) BlossomCheckResult blossom.checkUpload / blossom.checkUpload.result
upload request (BlossomUploadRequest) BlossomDescriptorResult blossom.upload / blossom.upload.result
mirror request (BlossomMirrorRequest) BlossomDescriptorResult blossom.mirror / blossom.mirror.result
checkMedia request (BlossomCheckRequest) BlossomCheckResult blossom.checkMedia / blossom.checkMedia.result
media request (BlossomMediaRequest) BlossomDescriptorResult blossom.media / blossom.media.result
list request (BlossomListRequest) BlossomListResult blossom.list / blossom.list.result
delete request (BlossomDeleteRequest) BlossomDeleteResult blossom.delete / blossom.delete.result
servers none BlossomServerListResult blossom.servers / blossom.servers.result
addServer request (BlossomAddServerRequest) BlossomServerListResult blossom.addServer / blossom.addServer.result
removeServer server (ServerUrl) BlossomServerListResult blossom.removeServer / blossom.removeServer.result
reorderServers servers (list of ServerUrl) BlossomServerListResult blossom.reorderServers / blossom.reorderServers.result
parseUri uri (tstr) BlossomUri local helper; no wire message
formatUri uri (BlossomUri) tstr local helper; no wire message

Schemas

RelayEventResult is owned by NAP-RELAY. NAP-BLOSSOM imports it by name for kind 10063 server-list events returned by the server-list helpers.

Primitive aliases and enums:

Name Definition
HexSha256 SHA-256 digest encoded as lowercase hex
HexPubkey 32-byte public key encoded as lowercase hex
ServerUrl Blossom server URL
BlobExtension blob filename extension text
MimeType MIME type text
ErrorCode machine-readable error text
NostrTag list of text tag fields
RelayEventResult external type owned by NAP-RELAY
BlossomAuthMode auto, required, none

Request records:

Record Required fields Optional fields
BlossomBlobCheckRequest server: ServerUrl, sha256: HexSha256 extension: BlobExtension, auth: BlossomAuthMode, purpose: tstr
BlossomGetRequest server: ServerUrl, sha256: HexSha256 extension: BlobExtension, range: tstr, auth: BlossomAuthMode, purpose: tstr
BlossomCheckRequest server: ServerUrl, sha256: HexSha256, size: uint, type: MimeType auth: BlossomAuthMode, purpose: tstr
BlossomUploadRequest server: ServerUrl, data: bstr sha256: HexSha256, type: MimeType, size: uint, auth: BlossomAuthMode, purpose: tstr
BlossomMirrorRequest server: ServerUrl, url: tstr sha256: HexSha256, auth: BlossomAuthMode, purpose: tstr
BlossomMediaRequest server: ServerUrl, data: bstr sha256: HexSha256, type: MimeType, size: uint, auth: BlossomAuthMode, purpose: tstr
BlossomListRequest server: ServerUrl, pubkey: HexPubkey cursor: HexSha256, limit: uint, auth: BlossomAuthMode, purpose: tstr
BlossomDeleteRequest server: ServerUrl, sha256: HexSha256 extension: BlobExtension, auth: BlossomAuthMode, purpose: tstr

When auth is omitted, shells use auto. purpose is shell-owned diagnostic prompt text only.

BlossomHeaders fields:

Field Required Type
contentType no MimeType
contentLength no unsigned integer
acceptRanges no text
sunset no text
reason no text

BlossomDescriptor fields:

Field Required Type
url yes text
sha256 yes HexSha256
size yes unsigned integer
type yes MimeType
uploaded yes unsigned integer
nip94 no list of NostrTag
additional fields no any

Result records:

Record Required fields Optional fields
BlossomBlobCheckResult ok: bool, status: uint, headers: BlossomHeaders error: ErrorCode
BlossomBytesResult ok: bool, status: uint blob: bstr, headers: BlossomHeaders, error: ErrorCode
BlossomCheckResult ok: bool, status: uint headers: BlossomHeaders, error: ErrorCode
BlossomDescriptorResult ok: bool, status: uint descriptor: BlossomDescriptor, error: ErrorCode
BlossomListResult ok: bool, status: uint, blobs: list of BlossomDescriptor cursor: HexSha256, error: ErrorCode
BlossomDeleteResult ok: bool, status: uint error: ErrorCode

BlossomAddServerRequest fields:

Field Required Type
server yes ServerUrl
position no unsigned integer

BlossomServerListResult fields:

Field Required Type
ok yes boolean
servers yes list of ServerUrl
eventId no text
result no RelayEventResult
error no ErrorCode

BlossomUri fields:

Field Required Type
sha256 yes HexSha256
extension yes BlobExtension
servers yes list of ServerUrl
authors yes list of HexPubkey
size no unsigned integer

check(request) -- Checks whether server has the blob identified by sha256, using the BUD-01 HEAD /<sha256> endpoint under the hood. Returns status and safe metadata headers. It never returns bytes.

get(request) -- Performs BUD-01 GET /<sha256>. The shell MUST verify that returned bytes hash to request.sha256 before delivering blob. When range is provided, the result MAY be a partial byte range; the shell MUST NOT claim whole-blob verification for partial content.

checkUpload(request) -- Performs BUD-06 HEAD /upload using X-SHA-256, X-Content-Type, and X-Content-Length. The result is advisory. A later upload can still fail.

upload(request) -- Performs BUD-02 PUT /upload. The shell sends the exact supplied bytes. It MUST NOT transform them. When sha256 is supplied, the shell MUST verify it against data before sending and use it for preflight or authorization scoping.

mirror(request) -- Performs BUD-04 PUT /mirror. The shell submits the remote URL to the target server. When sha256 is supplied, the shell MUST scope authorization to that hash and reject a returned descriptor with a different hash.

checkMedia(request) -- Performs BUD-05 HEAD /media. The result is advisory. A later media can still fail.

media(request) -- Performs BUD-05 PUT /media. The Blossom server may optimize or transform media. The returned descriptor describes the stored result, not necessarily the original bytes.

list(request) -- Performs BUD-12 GET /list/<pubkey>. This endpoint is optional and unrecommended by Blossom. Shells MUST expose server failure as a normal result, not as proof that the user has no blobs.

delete(request) -- Performs BUD-12 DELETE /<sha256>. A delete request acts on exactly one hash. Multiple authorization x tags, if the shell creates them, MUST NOT be treated as a multi-delete request.

servers() -- Returns the shell-user's ordered BUD-03 server list from the latest kind 10063 event. When the source event is returned, it is returned as RelayEventResult. Returns an empty list when no list exists. This is a Blossom-specific helper for the user's preferred upload/retrieval servers, not a generic list editor.

addServer(request) -- Adds server to the shell-user's BUD-03 list and publishes a replacement kind 10063 event. If server already exists, the shell MAY treat the request as idempotent or move it to position when supplied. The server URL MUST include http:// or https://, matching BUD-03.

removeServer(server) -- Removes server from the shell-user's BUD-03 list and publishes the replacement kind 10063 event. Removing an absent server is idempotent. Removing the final server MUST be rejected with empty-server-list, because BUD-03 events require at least one server tag.

reorderServers(servers) -- Replaces the ordering of the shell-user's BUD-03 list. The supplied list MUST contain the same server set as the current list; callers use addServer or removeServer to change membership. Empty lists MUST be rejected with empty-server-list.

parseUri(uri) -- Parses a BUD-10 blossom: URI into hash, extension, server hints, author hints, and size. It performs no network I/O.

formatUri(uri) -- Produces a BUD-10 blossom: URI. It MUST include the hash and extension, defaulting the extension to .bin when no better extension is known.

Wire Protocol

blossom.* messages use the NIP-5D wire format ({ "type": "domain.action", ...payload }).

Type Direction Payload fields
blossom.check napplet -> shell id, request
blossom.check.result shell -> napplet id, result, error?
blossom.get napplet -> shell id, request
blossom.get.result shell -> napplet id, result, error?
blossom.checkUpload napplet -> shell id, request
blossom.checkUpload.result shell -> napplet id, result, error?
blossom.upload napplet -> shell id, request
blossom.upload.result shell -> napplet id, result, error?
blossom.mirror napplet -> shell id, request
blossom.mirror.result shell -> napplet id, result, error?
blossom.checkMedia napplet -> shell id, request
blossom.checkMedia.result shell -> napplet id, result, error?
blossom.media napplet -> shell id, request
blossom.media.result shell -> napplet id, result, error?
blossom.list napplet -> shell id, request
blossom.list.result shell -> napplet id, result, error?
blossom.delete napplet -> shell id, request
blossom.delete.result shell -> napplet id, result, error?
blossom.servers napplet -> shell id
blossom.servers.result shell -> napplet id, result, error?
blossom.addServer napplet -> shell id, request
blossom.addServer.result shell -> napplet id, result, error?
blossom.removeServer napplet -> shell id, server
blossom.removeServer.result shell -> napplet id, result, error?
blossom.reorderServers napplet -> shell id, servers
blossom.reorderServers.result shell -> napplet id, result, error?

Key design notes:

  • Request/result pairs use id for correlation.
  • Binary data and blob fields cross the boundary as projection-native byte objects. In the web projection that means Blob or ArrayBuffer, not base64-encoded strings.
  • Result status is the upstream HTTP status after shell policy checks. Shell policy failures MAY use status: 0 with a stable error.
  • headers.reason carries BUD X-Reason diagnostics only. Napplets MUST NOT branch on it.
  • BUD-03 server-list helpers are shell-owned read/replace mutations over kind 10063. Returned server-list events use RelayEventResult so resource sidecars and relay hints follow the same shape as other raw event returns. Runtimes MAY implement them using the same internal machinery as a generic list API, but NAP-BLOSSOM exposes the Blossom-specific verbs directly.
  • Local helpers (parseUri, formatUri) do not send wire messages.

Examples

Check whether a server has a blob:

-> { "type": "blossom.check", "id": "c1",
     "request": { "server": "https://cdn.example.com", "sha256": "b167...", "extension": ".pdf" } }
<- { "type": "blossom.check.result", "id": "c1",
     "result": { "ok": true, "status": 200, "headers": { "contentType": "application/pdf", "contentLength": 184292 } } }

Check, then upload exact bytes:

-> { "type": "blossom.checkUpload", "id": "b1",
     "request": { "server": "https://cdn.example.com", "sha256": "b167...", "size": 184292, "type": "application/pdf" } }
<- { "type": "blossom.checkUpload.result", "id": "b1",
     "result": { "ok": true, "status": 200, "headers": {} } }
-> { "type": "blossom.upload", "id": "b2",
     "request": { "server": "https://cdn.example.com", "data": "<bytes>", "sha256": "b167...", "type": "application/pdf", "size": 184292 } }
<- { "type": "blossom.upload.result", "id": "b2",
     "result": { "ok": true, "status": 201, "descriptor": { "url": "https://cdn.example.com/b167....pdf", "sha256": "b167...", "size": 184292, "type": "application/pdf", "uploaded": 1725105921 } } }

Add and reorder preferred servers:

-> { "type": "blossom.addServer", "id": "s1",
     "request": { "server": "https://cdn.example.com", "position": 0 } }
<- { "type": "blossom.addServer.result", "id": "s1",
     "result": { "ok": true, "servers": ["https://cdn.example.com"], "eventId": "ev1..." } }
-> { "type": "blossom.addServer", "id": "s2",
     "request": { "server": "https://cdn2.example.com", "position": 1 } }
<- { "type": "blossom.addServer.result", "id": "s2",
     "result": { "ok": true, "servers": ["https://cdn.example.com", "https://cdn2.example.com"], "eventId": "ev2..." } }
-> { "type": "blossom.reorderServers", "id": "s3",
     "servers": ["https://cdn2.example.com", "https://cdn.example.com"] }
<- { "type": "blossom.reorderServers.result", "id": "s3",
     "result": { "ok": true, "servers": ["https://cdn2.example.com", "https://cdn.example.com"], "eventId": "ev3..." } }

Retrieve by hash:

-> { "type": "blossom.get", "id": "b3",
     "request": { "server": "https://cdn.example.com", "sha256": "b167...", "extension": ".pdf" } }
<- { "type": "blossom.get.result", "id": "b3",
     "result": { "ok": true, "status": 200, "blob": "<bytes>", "headers": { "contentType": "application/pdf", "contentLength": 184292 } } }

Error Handling

All result messages use result.ok: false plus result.error for programmatic failure. The top-level error? field is reserved for malformed envelopes where no typed result can be constructed.

Common error codes: invalid-request, invalid-server, invalid-hash, not-signed-in, user-denied, blocked-by-policy, not-found, auth-required, payment-required, too-large, unsupported-media-type, hash-mismatch, server-list-unavailable, invalid-server-list, empty-server-list, network-error, timeout, server-error, unsupported.

Shell Behavior

  • The shell MUST treat server, url, hashes, MIME strings, URI hints, and descriptors as untrusted.
  • The shell MUST respond to every request with a result message carrying the same id.
  • The shell MUST restrict requests to Blossom endpoint shapes. It MUST NOT turn this interface into arbitrary HTTP method, path, or header access.
  • The shell MUST apply its normal network policy before connecting to any Blossom server or mirror origin.
  • The shell MUST perform all BUD-11 authorization token construction, signing, encoding, and header attachment. It MUST NOT expose authorization tokens or signing keys to the napplet.
  • The shell MUST scope authorization tokens with server tags derived from the lowercase Blossom server domain, not from a full URL.
  • The shell MUST scope authorization tokens with x tags whenever the target endpoint implies a hash.
  • The shell MUST reject auth: "required" requests and server-authenticated operations with not-signed-in when no shell-user signer is connected.
  • The shell MUST publish BUD-03 server-list mutations as kind 10063 events with empty content and ordered server tags.
  • The shell MUST reject BUD-03 server-list mutation requests that would publish zero server tags.
  • The shell MUST preserve the caller-visible BUD-03 server ordering exactly in servers, addServer, removeServer, and reorderServers results.
  • The shell SHOULD merge observed BUD-03 relay URLs into RelayEventResult.sidecar.relayHints when it can disclose them.
  • The shell MUST reject server-list mutation requests with not-signed-in when no shell-user signer is connected.
  • The shell MAY prompt, deny, rate-limit, cache, redact diagnostics, or route by napplet policy.
  • The shell MAY try unsigned requests for public endpoints when auth is "auto" and no shell-user signer is connected.
  • The shell MAY send unsigned requests when auth: "none" is supplied, except when runtime policy requires user consent.

Security Considerations

  • Blossom server access is a network boundary. Shells MUST enforce SSRF protections, redirect limits, size limits, and per-napplet rate limits before delivering results.
  • BUD-11 tokens are bearer credentials for their lifetime. Shells SHOULD keep expirations short, scope tokens to exact server domains, and scope hash-based operations with x tags.
  • mirror asks a server to fetch a remote URL. Shells SHOULD apply policy to the origin URL before forwarding it and SHOULD make the destination server visible in consent UI.
  • media may transform bytes. Napplets that require exact byte preservation MUST use upload, not media.
  • list can reveal a user's stored blobs. Shells SHOULD require explicit consent when listing a pubkey that belongs to the shell-user.
  • BUD-03 server lists are user preference and routing state. Shells SHOULD show the requesting napplet, server URL, and resulting order before publishing a mutation.
  • Successful upload, mirror, delete, or media results prove only what the Blossom server reported. They do not prove future availability or deletion across other servers.

References

Implementations

  • (none yet)

Changelog

  • 03ea0db - Introduced NAP-BLOSSOM as a constrained runtime capability for Blossom blob access.

  • b6c1737 - Added runtime-owned Blossom server-list operations and ownership rules.

  • d48052f - Changed Blossom server-list events to use relay-owned result shape and declared the relay dependency.

  • e92fb0b - Adopted injected-domain availability and linked the current upstream web binding.