What Happens When You Tap Share: the Key-Binding JWT
The unlinkability implementation described here is unaudited.
What actually leaves your device
An SD-JWT presentation is a single string with tilde-separated segments: the issuer-signed credential, then the individual disclosures you chose to reveal, and, when the verifier asked for one, a final segment that is the key-binding token.
That last segment is an ordinary three-part JWT. You can decode it with any JOSE tool. No Solidus software is required, and we would rather you check it that way than trust a description of it.
$ npm i @solidus-network/sdk # 0.6.3
# call presentSdJwtVc, split the compact result on '~',
# take the FINAL segment, and decode it as a normal JWT.
Its payload carries exactly four claims:
aud, who this presentation is for. The verifier's identifier.nonce, a fresh, single-use value the verifier supplies.iat, when it was signed.sd_hash, a hash binding this token to the exact set of disclosures you sent.
Four claims, and each one is doing a specific job. aud stops a presentation being replayed at a
different verifier. nonce stops it being replayed at the same verifier later. sd_hash stops
someone taking your valid token and attaching it to a different, larger set of disclosures.
Remove any one of them and a specific attack comes back. That is why the payload is small rather than because it is unfinished.
The design choice we want you to see
Our SDK always emits a key-binding token whenever a verifier requests one, by supplying an expected audience, or a nonce, or both. There is no partial mode where a token is present but goes unchecked. Present-and-ignore is a whole class of vulnerability, and it is not reachable here.
And the part that is not up to us
If a verifier supplies neither an expected audience nor an expected nonce, the presentation is
accepted with no key-binding token at all. A bearer presentation. The package's own documentation
says so in as many words: such presentations "stay on the Phase-1 verify path with no aud/nonce."
So the binding is only as strict as the verifier chooses to demand, and a verifier that asks for nothing gets a credential anyone holding a copy could have presented.
We are not going to describe that as a flexible integration option. It is the weakest configuration our software permits, it is reachable by writing less code rather than more, and a relying party that has not deliberately chosen otherwise is probably in it.
If you are integrating: supply a fresh nonce and an expected audience on every presentation. Generate the nonce yourself, use it once, and reject anything that does not carry it back.
What this does and does not prove
Does: the presenter controlled the private key bound into the credential, at this moment, for this verifier, over exactly these disclosures.
Does not:
- Unlinkability. A key-binding token is signed with the same key every time; two verifiers comparing notes can match on it. If unlinkable presentation is the requirement, that is BBS+'s holder binding for the same boundary stated from the other side.
- Truth of the claim. No signature check establishes that the issuer was right.
- That the verifier stored nothing. What a relying party keeps after a successful presentation is its business, not something the protocol constrains. The BBS+ implementation referenced here is unaudited.
One implementation note, since this page is for people who read code
The compact-JWS parsing that verifies this token is hand-rolled, and it is in the published
package, dist/sdjwt/verify.js, shipped in the tarball you install. It is not hidden behind a
dependency, which means it can be read and attacked by anybody who cares to.
That is a deliberate trade and it cuts both ways: hand-rolled parsing of a security-critical format is precisely the kind of code an audit exists to examine, and no audit has examined it.

