Encryption
Walrus stores public bytes by default — anyone with the blob_id can
download them. SEAL adds a decryption gate on top so the bytes stay
readable only to owners and allowlisted viewers.
When SEAL helps
When SEAL is overkill
- Public CDN assets — site images, JS bundles, fonts. Plaintext is cheaper (no SEAL roundtrip, smaller payload).
- Anti-piracy by itself — a viewer who decrypts once can save the plaintext locally. SEAL gates access to the key, not the bytes after.
- One-shot exchanges — for a single recipient one-time, a Noise-style handshake might be lighter weight than running a SEAL infrastructure round-trip.
What SEAL actually protects
- The blob contents
- The decryption key — gated by an on-chain Move predicate (
seal_approve)
- On-chain metadata — filename, MIME type, size, hash, sender
- Upload network metadata — the publisher knows your source IP
- Forward secrecy — a viewer who already pulled a key keeps it
If filename / MIME secrecy matters, hash or random-string them before upload. The SDK doesn't auto-encrypt metadata in v0.
How SEAL works under the hood
Three components, all already wired in the SDK:
The SDK constructs the identity as:
The marker is recorded on the BlobRef. seal_approve looks up the marker
in either the store-level viewer list OR the per-blob share table — that's
what makes per-blob sharing possible without re-encrypting.
The flow at upload time
Upload requires one wallet signature: the register PTB. The encryption step uses only public key-server data — no prompt.
The flow at read time
Decryption does require a wallet signature — the SEAL session key. After the first prompt, subsequent decrypts within ~10 minutes reuse the cached session and skip the prompt.
Marker semantics
Every encrypted upload generates a fresh 16-byte marker + 5-byte nonce. Why both?
- The marker is the share-allowlist key. Per-blob shares are stored as
Table<marker → viewer[]>on the BlobStore. Stable; recorded on chain. - The nonce keeps SEAL identities unique even if two uploads happen to share a marker by accident. Not recorded anywhere on chain.
Markers are returned in UploadBlobResult.sealMarker and stored on each
BlobRef. Use them when calling add_blob_share / remove_blob_share.
Cost overhead
SEAL adds a fixed-ish overhead, roughly:
The 400 bytes are SEAL framing (IBE ciphertext + auth tag + identity bytes). For a 142-byte plaintext you upload ~450 bytes — the per-blob cost is the same. For a 10 MiB plaintext, the overhead is invisible.
Anti-patterns
SEAL is for protecting blob contents. If you have a long-term secret (API key, master key), put it somewhere else (HSM, KMS) and reference it from inside a SEAL-encrypted blob if needed.
SEAL identities are scoped to store_id. Anyone with store-level viewer
access can decrypt every blob in that store. Use one BlobStore per
trust group; create new ones with fresh plaintext uploads.
Removing a viewer prevents future seal_approve calls, but a viewer who
already fetched the key keeps it. For real revocation, re-encrypt under a
fresh identity and upload a new blob.