Waldrop SDK · Guide

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

01
Personal data
Financial records, health files, ID scans — anything you'd refuse to leak even with no auth.
02
Paid / gated content
Courses, datasets, art drops — sell access via the per-blob viewer ACL.
03
Team-private docs
Internal reports, drafts. Add teammate addresses as viewers; revoke when someone leaves.
04
Compliance-required encryption-at-rest
HIPAA / SOC2 — the auditor wants encryption, SEAL provides it.

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

Protected
  • The blob contents
  • The decryption key — gated by an on-chain Move predicate (seal_approve)
Not protected
  • 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:

1. Key servers (k-of-n threshold)
   Two verified independent servers run on testnet. Threshold = 1
   (any one server's response decrypts).

2. Identity-based encryption (IBE)
   The "identity" string IS the public key. No key registration
   neededencrypt against anything, decrypt requires the matching
   private key from the key servers.

3. seal_approve Move function
   On-chain predicate the key servers dry-run before releasing
   the private key. Returns true if the caller is the BlobStore
   owner or an allowlisted viewer.

The SDK constructs the identity as:

identity = store_id (32 bytes)
         || marker  (16 bytes)
         || nonce   (5 bytes)

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

[SHA-256]of original bytes (for BlobRef.contentHash)
[SEAL encrypt]against identity; NO wallet signature
[PUT]publisher uploads CIPHERTEXT
[register_blob]BlobRef.encrypted=true, BlobRef.sealMarker=<hex>

Upload requires one wallet signature: the register PTB. The encryption step uses only public key-server data — no prompt.

The flow at read time

[fetch]publisher returns ciphertext bytes
[seal session]SDK prompts wallet to sign a personal message (cached ~10 min)
[seal_approve]key server dry-runs the Move call; if allowed, releases key
[decrypt]SDK decrypts client-side

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:

ciphertext_sizeplaintext_size + 400 bytes

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

Don't encrypt secrets larger than the blob

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.

Don't reuse a BlobStore across security domains

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.

Don't rely on revocation for compliance

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.

Edit this page on GitHub ↗
Waldrop · 2026cryptokarigar