Skip to content

feat(sea): add SEA.share/SEA.unshare - zero-knowledge multi-user encryption - #1424

Open
ABsUP wants to merge 1 commit into
amark:masterfrom
OpenCodeWEB:feat/sea-share
Open

ABsUP wants to merge 1 commit into
amark:masterfrom
OpenCodeWEB:feat/sea-share

Conversation

@ABsUP

@ABsUP ABsUP commented Aug 15, 2026

Copy link
Copy Markdown

feat(sea): add SEA.share / SEA.unshare — zero-knowledge multi-user encryption

Adds a first-class primitive for sharing encrypted data with multiple specific recipients, built entirely on the existing SEA ECDH machinery (SEA.secret).

How it works

  1. SEA.share(data, sender, recipients) encrypts data with a fresh random session key (AES-256-GCM via SEA.encrypt).
  2. For each recipient, the session key is wrapped (encrypted) using the unique ECDH shared secret derived from sender.epriv + recipient.epub (SEA.secret).
  3. The result is a JSON-safe capsule:
{ e: sender.epub,                     // sender's ECDH pub (needed by recipients to derive the secret)
  s: { [recipient.epub]: 'SEA{...}' }, // per-recipient wrapped session key
  c: 'SEA{...}' }                      // payload ciphertext
  1. SEA.unshare(capsule, recipient) derives the same shared secret from capsule.e + recipient.epriv, unwraps the session key, and decrypts the payload.

Properties

  • Zero-knowledge: only the sender and listed recipients can ever derive the session key. Non-recipients have no key slot → undefined. Relays/graphs see only ciphertext + public keys.
  • Multi-recipient: one capsule, N recipients — no N² ciphertexts.
  • Storable: capsule is plain JSON, safe to put in a Gun graph, relay, or file.
  • Flexible input: recipients may be full pairs, {epub} objects, bare epub strings, or arrays thereof.
  • Self-share works (sender can be their own recipient).
  • Tamper-evident: any modification of c (or a slot) fails AES-GCM authentication → undefined.
  • Supports both promise and callback styles, matching the rest of SEA.

Example

const capsule = await SEA.share('top secret', alice, [bob, carol]);
gun.get('inbox').get(bob.pub).put(capsule); // store anywhere

// bob:
const data = await SEA.unshare(capsule, bob); // 'top secret'
// eve (not a recipient):
const leak = await SEA.unshare(capsule, eve); // undefined

Tests

7 new tests in test/sea/sea.js (multi-recipient, objects, bare-epub recipients, non-recipient blocking, tamper blocking, callback style, self-share). Full SEA suite: 42 passing / 1 pending / 0 failing.

Also in this PR

  • sea/index.js synced with the canonical sea.js bundle via npm run unbuildSea: the existing certificant membership check data.c.indexOf('*' || certificant) always evaluated to '*' (JS || short-circuit), so the explicit certificant check never ran. The bundle already contained the fix; the extracted module was stale.

…yption

ECDH-based multi-recipient sharing: data is encrypted with a fresh random
session key, which is then wrapped separately for each recipient using the
unique ECDH shared secret derived from the sender's epriv and the
recipient's epub. The resulting capsule { e, s, c } is JSON-safe and can
be stored anywhere (Gun graph, relay, file); only the sender and listed
recipients can ever derive the session key. Non-recipients have no key
slot and tampered capsules fail decryption.

Also syncs sea/index.js with the canonical bundle (certificant membership
check fix: 'indexOf('*' || certificant)' always evaluated to '*', so the
explicit certificant check never ran).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant