DEV Community

Sui Gn
Sui Gn

Posted on

What's a mechanism for separating data topology from cryptographic audience in a decentralized identity system?

Most storage systems quietly conflate two different things: where data physically lives and who is allowed to read it. Put a file in a folder with permission bits, and both facts are enforced by the same mechanism, at the same layer.

Move the folder to a new machine, and you've also moved — or lost — the access decision. Widen replication for durability, and you've widened, by construction, the set of processes that have to be trusted not to leak it.

Decentralized identity systems inherit this problem unless they deliberately design around it. Here's a mechanism that does, implemented in a real kernel, not just described.

Name the two variables separately
T, the topology set — which machines or processes physically hold a piece of ciphertext.
A, the audience set — which principals can actually decrypt it.

The core relation the mechanism has to guarantee:

replicate(T) ↛ change(A)
Replicating ciphertext to a backup, a CDN edge cache, a second device — none of it should, by itself, expand who can derive the key to read it. Encryption doesn't remove data from topology. It restricts membership in readability.

The actual operator
In .me, audience membership is a structural property of a path, declared at write time with a single operator, _:

me.wallet["_"]("secret");
me.wallet.income(100);

me("wallet")        // undefined — the public index excludes secret-scope roots
me("wallet.income")  // 100 — only inside a kernel/session holding "secret"
Enter fullscreen mode Exit fullscreen mode

me("wallet") isn't undefined because anything was deleted — it's excluded from the public index by construction. A different kernel instance, or the same code without the secret, gets undefined at both paths.

_("secret") doesn't grant a second person membership by itself — it defines what membership requires. Getting a real second principal into the audience is a separate, deliberate step: wrapping the scope secret to their P-256 public key with ECDH-ES + HKDF-SHA-256 + AES-256-GCM, tested against both correct and deliberately corrupted recipients — implementation in src/crypto.ts.

Breaking inheritance on purpose

Narrower paths inherit their parent's scope by default. Sometimes two secrets under the same structural path should share nothing — a second operator, ~, resets the derivation chain at a chosen boundary:

me.wallet["_"]("alpha");
me.wallet["~"]("noise");
me.wallet["_"]("beta");
// alpha-chain secret ≠ beta-chain secret, verified directly in the kernel's test suite
Enter fullscreen mode Exit fullscreen mode

Structurally, both paths sit under the same parent. Cryptographically, they belong to unrelated audiences.

decentralized identity system

The honest cost

Every scoped read runs real key derivation — not a cached policy lookup. Benchmarked, not assumed: roughly one to two orders of magnitude slower than a public read at moderate scale. In exchange: a tampered secret blob doesn't return wrong data, it fails to decrypt — there's no "the check passed and the data happened to be corrupted" state, because there's no check separate from decryption to fail.

Where the full mechanism is, source-verified
Every claim above is checked directly against .me's source — not restated from documentation. Full derivation, the _/~ operators in detail, and what this buys you versus what it costs:

The Algebra of Encrypted Audiences →

The formal closing statement — T ⊥ A as an Island whose audience is composable across multiple identities: Encrypted Semantic Island →

Top comments (0)