@solidus-network/auth
v0.6.2
Published
DID-based authentication and DID-less BBS+ selective-disclosure verification for the Solidus Network protocol — Ed25519 challenge/response, unlinkable presentation envelopes, and the relying-party issuer-key resolver.
Maintainers
Readme
@solidus-network/auth
DID-based authentication and DID-less BBS+ selective-disclosure verification for the Solidus Network protocol.
Two independent surfaces, one package:
- DID-bound auth — Ed25519 challenge / signature / verification, for backends that authenticate the holder of a known DID without operating a user database.
- DID-less presentations — the relying-party surface for Solidus's unlinkable credential presentations: verify "a valid KYC credential from this issuer, disclosing exactly these claims" without ever learning who presented it.
Install
npm install @solidus-network/auth
# or
pnpm add @solidus-network/authQuick start — DID-bound challenge
import { createChallenge, verifyPresentation } from '@solidus-network/auth'
// Server: issue a challenge for a known DID (TTL in seconds, default 300)
const challenge = createChallenge('did:solidus:testnet:abc123', 300)
// Client answers with a W3C Verifiable Presentation whose proof signs
// the challenge nonce; the server verifies:
const result = await verifyPresentation(challenge, presentation, getPublicKey)
if (result.valid) {
// authenticated — result.checks itemizes expiry/holder/nonce/signature
}DID-less selective-disclosure presentations (BBS+)
The verifier surface for Solidus's unlinkable presentations: a wallet proves it holds a valid KYC credential from a trusted issuer and disclosed exactly the claims you asked for — without revealing the holder's DID, a credential ID, or anything two relying parties could later compare to discover they served the same person.
How it works
- The issuer signs an 8-message KYC vector with BBS+ under a header that is constant per (issuer, credential-type, epoch) — no per-credential entropy, so the header places a holder in a cohort, never identifies one.
- The issuer publishes its per-epoch public keys at
https://verify.solidus.network/.well-known/solidus-bbs-issuer.json. Credentials live as long as their epoch (+ a grace window); revocation is bounded-lifetime — a lapsed epoch's key disappears from the document and every verifier fails closed. - The wallet answers a verifier's challenge with a randomized proof bound to that verifier's domain + nonce. Replaying it under any other challenge fails cryptographically.
- The envelope (
BbsSelectiveDisclosurePresentation) has noholderfield by construction, and the verifier rejects any envelope that disclosessubject_did,verification_id, ordocument_hasheven when the proof itself is valid.
Verifier quickstart (a complete relying party)
import {
createBbsChallenge,
verifyBbsPresentation,
createIssuerKeyResolver,
parseBbsPresentation,
decodeDisclosedKycFields,
} from '@solidus-network/auth'
// 1. Issue a DID-less challenge (store it; enforce single use).
const challenge = createBbsChallenge({
domain: 'your-app.example.com', // your authenticated origin
credentialType: 'kyc',
requiredIndices: [4, 5], // kyc_level, country
})
// 2. The wallet answers with a serialized envelope.
const presentation = parseBbsPresentation(envelopeJson)
// 3. Verify — stateless, against the issuer's published epoch keys.
const resolveIssuerKey = createIssuerKeyResolver({
issuerBaseUrl: 'https://verify.solidus.network',
})
const result = await verifyBbsPresentation(challenge, presentation, {
issuerDid: 'did:solidus:testnet:2FDp7gH5qyb66jjsXAbFQYwLygqQ',
resolveIssuerKey,
used: false, // your storage-side single-use flag
})
if (result.valid) {
const fields = decodeDisclosedKycFields(presentation.disclosed)
// e.g. { issuer_did: '…', kyc_level: '2', country: 'TR' } — and nothing else
}The resolver is fail-closed: an unknown or lapsed epoch, an unreachable
issuer, or a malformed key document all resolve to null and the
presentation is rejected — never accepted on error.
Verify the unlinkability claim yourself
Don't take our word for it — the demonstration ships in this package and runs in under a minute:
node node_modules/@solidus-network/auth/demo/unlinkability-demo.mjsIt issues one credential, presents it to two relying parties, verifies both presentations, then pools everything both RPs received and shows the complete correlation surface: cohort-level constants and the disclosed claims — no per-holder value in common, randomized proofs sharing no bytes, and cross-challenge replay failing.
Re-recognition (pairwise DIDs)
When a returning user wants to be recognized by one relying party, the
wallet can attach a pairwise DID — deterministically derived from the
holder's seed and that verifier's identity, so the same user shows a
different, uncorrelatable pairwise DID to every other verifier. See
buildPairwiseAttachment / buildPairwiseDid. Two caveats, stated
plainly: the wallet must derive against the verifier's authenticated
origin (never an ID copied blindly from the challenge), and the pairwise
signature sits beside the credential proof rather than being
cryptographically bound to the same seed — assurance is "a returning
controller of this key who also holds a valid credential", which is right
for convenience and analytics, not for access-control-grade identity.
What this does and does not protect
Protected (cryptographic presentation layer): two relying parties — or the same one twice — cannot link presentations by anything in the envelopes: no subject DID, no credential ID, no document hash, randomized proofs, cohort-constant headers, challenge-bound replay protection.
NOT protected (out of scope — be honest with your users): network-level correlation (IP address, timing, TLS/device fingerprints, cookies), and relying parties pooling out-of-band data they each collected themselves (email, phone, payment details). If your application hands every RP the user's email address, unlinkable credentials will not make them unlinkable.
Status: the underlying @solidus-network/bbs implementation is
testnet-grade and audit-pending (external audit via NLnet NGI Zero,
H2 2026 target). did:solidus is submitted to the W3C DID method registry
and under review. This runs on the Solidus testnet — do not represent it
as audited or production-assured until those complete.
Features
- Ed25519 signature verification via @noble/ed25519
- BLAKE3 hashing for challenge digests via @noble/hashes
- Time-bounded challenges with TTL enforcement
- Domain-bound challenges (prevents replay across origins)
- DID-less BBS+ selective-disclosure verification with fail-closed epoch-key resolution
- A runnable unlinkability demonstration (
demo/unlinkability-demo.mjs)
License
Apache-2.0 — see LICENSE.
