
The problem
Encrypting a secret for one person is solved. .me already does it by wrapping a key to that person's public key.
Real audiences are rarely one person, though. Sometimes "anyone of us" can open a secret, and sometimes it takes "all of us together." Sometimes it's a mix, like "alice and bob together, or carol alone." People join and leave. Whatever the audience says has to be enforced by the ciphertext itself, not by a server that promises to check.
The model
An audience is a formula over identities built from two operators, OR and AND, which can nest.
(alice AND bob) OR carol
OR wraps the same key once for each member.
AND splits the key into XOR shares, one per member, so no single member can rebuild it alone.
Every audience reduces to its minimal coalitions, meaning the smallest groups of people who can open it. Equivalent formulas get the same id.
There is no NOT and no XOR operator. Encryption can only grant access, never take it away from someone who already holds a key.
Removing someone happens at seal time. Revoking means sealing a new version with a new key.
Groups are immutable values identified by the hash of their members. Adding a member creates a new group.
const A = [{ and: [alice, bob] }, carol];
const env = await sealAudience({ budget: "120k" }, A);
await openAudience(env, [carolKey]); // opens
await openAudience(env, [aliceKey, bobKey]); // opens
await openAudience(env, [aliceKey]); // throws
What's tested
There are 16 passing tests.
The laws: one test per law, L1 through L9.
alice AND bob: neither alice nor bob can open alone, and together they can.
(alice AND bob) OR carol: carol opens alone, alice and bob open together, and alice alone can't.
AND of three: all three open, and no pair can.
Canonical ids: equivalent formulas produce the same id.
Empty audiences: if removing members leaves nobody, seal refuses.
Revocation: after carol is removed and the secret is resealed, she can't open the new version, though she can still open the old one.
Tampering: changing any byte of the ciphertext, the header or a share makes open fail.
Everything is built on the kernel's existing wrapping and encryption, with no new crypto dependencies.
Status
This is on a branch and not yet on npm. It hasn't been audited yet. Declaring audiences directly in the tree comes next.





