One Seed, Many Identities: How Pairwise DIDs Work
The unlinkability implementation described here is unaudited.
Start with the assumption most people carry into decentralized identity: if you have one seed, everything derived from it must trace back to you. It doesn't have to work that way. One seed can derive many DIDs, each used with a different relying party, each looking, from the outside, like it belongs to a different person. Show one identifier to one service and a different one to the next, and there is nothing in either identifier for those two services to compare notes on, even if they wanted to.
This is a pairwise DID: an identifier a wallet generates for one relationship at a time, not one identifier reused everywhere.
Where this comes from
Solidus did not invent per-relationship identifiers. The pattern traces to the Hyperledger Indy/Aries and Sovrin Foundation community's work on pairwise-pseudonymous DIDs, from around 2017–2019, later formalized in DIF's did:peer method specification, and discussed as a core privacy pattern in W3C DID Core itself. Solidus is a DIF Associate Member (a body it has joined, not just borrowed vocabulary from) and what Solidus wrote is its own derivation scheme: an HKDF-based key derivation over a wallet's existing seed, domain-separated per verifier, rather than adopting did:peer or a newer pseudonym draft wholesale. The idea of deriving instead of storing a separate identity per relationship predates Solidus by years. The full attribution, and the honest tradeoffs of Solidus's specific implementation, are in the Lexicon: Pairwise DID.
Run the same derivation twice with the same seed and the same verifier name, and you get the identical identifier back, no state to store, no separate backup process, because every pairwise identity is just recomputed from the same seed the wallet already backs up.
The negative proof
A pairwise DID's defining property is that it is never anchored on-chain, never submitted as a chain transaction, never written into the state tree the way a registered identity anchor is. That's not an oversight; it's the entire point. A pairwise identifier that was published to a public, resolvable registry would defeat its own purpose the moment it existed there, because "resolvable by anyone" and "known only to one relying party" are contradictory properties for the same string to hold.
Which means the proof that a pairwise DID stays private isn't something you can screenshot as a success state, it's an absence. Take any syntactically valid did:solidus string that has never been anchored and resolve it against the public RPC:
curl -X POST https://rpc.solidus.network \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "solidus_didResolve",
"params": ["did:solidus:testnet:1111111111111111111111zz"],
"id": 1
}'
{"jsonrpc":"2.0","id":1,"result":null}
result: null. Not an error, not a malformed-request response: the chain's honest answer to "does this identifier exist in my state tree," and the answer is no. This is the same behavior DID Resolution documents for any never-anchored identifier: a made-up DID and a genuinely private, never-published pairwise DID look identical from the outside, because both are, structurally, nothing to the chain. That's not a limitation of the resolver. It's the mechanism working exactly as designed: a pairwise DID's privacy doesn't depend on the chain politely declining to publish something it holds; it depends on the chain never having been asked to hold it in the first place. Try the same call through explorer.solidus.network/lookup with any identifier you make up, and you'll get the same nothing back: there's no separate, friendlier path that would tell you more.
What this actually buys the user
The practical translation: if two services each hold a pairwise DID for the same person, they cannot compare those two DIDs and conclude they're looking at the same user, because there is no shared, public identifier between them to compare in the first place. Each service still recognizes the same person on repeat visits, because the derivation is deterministic and returns the same identifier every time for that specific verifier. What breaks is cross-service correlation, not single-service recognition. Two companies wanting to pool notes on a shared customer would need the person's cooperation, or some other shared piece of data entirely outside this mechanism: the pairwise DID itself gives them nothing to compare.
Pairwise-DID unlinkability is about identifiers across relationships, derived before any credential is ever presented. The two compose, but they're not the same claim, and this page isn't the place to make the other one.
Where to go next
This is the sibling case to why an identity anchor can never hold or send value, both pages are about what's structurally absent by design, one for value, one for correlation. For the contrast case: a DID that is anchored, resolving to a real document instead of null, see resolve a DID yourself. Read together, the two resolution outcomes are the honest map of when a lookup finds something and when, on purpose, it never will.

