Skip to Content
SDK@solidus-network/auth

@solidus-network/auth

Auth primitives for DID-based authentication. This package implements a challenge-response protocol where a verifier issues a nonce, the holder signs it as a verifiable presentation, and the verifier checks the signature.

npm install @solidus-network/auth
import { createChallenge, verifyPresentation } from '@solidus-network/auth'

createChallenge

Generates a cryptographic challenge for a DID holder to prove control of their private key.

createChallenge(did: string, ttlSeconds?: number): Challenge

Parameters

NameTypeDefaultDescription
didstringThe DID to challenge
ttlSecondsnumber300Time-to-live in seconds (5 minutes default)

Returns a Challenge object.

interface Challenge { id: string // UUID v4 did: string // The challenged DID nonce: string // 32-byte random hex nonce issuedAt: string // ISO 8601 timestamp expiresAt: string // ISO 8601 timestamp (issuedAt + ttlSeconds) }

How it works:

  1. Generates a UUID v4 as the challenge ID
  2. Generates 32 bytes of cryptographically random data, hex-encoded as the nonce
  3. Sets issuedAt to the current time
  4. Sets expiresAt to issuedAt + ttlSeconds (default 300 seconds)

Example

import { createChallenge } from '@solidus-network/auth' // Default 5-minute TTL const challenge = createChallenge('did:solidus:testnet:user123') console.log(challenge) // { // id: "f47ac10b-58cc-4372-a567-0e02b2c3d479", // did: "did:solidus:testnet:user123", // nonce: "a4f2e8c1d3b5f6a7890123456789abcdef...", // issuedAt: "2026-05-07T12:00:00.000Z", // expiresAt: "2026-05-07T12:05:00.000Z" // } // Custom 60-second TTL const shortChallenge = createChallenge( 'did:solidus:testnet:user123', 60 )

verifyPresentation

Verifies a signed verifiable presentation against a challenge. Performs four sequential checks — if any check fails, verification stops and returns the failure reason.

verifyPresentation( challenge: Challenge, presentation: VerifiablePresentation, getPublicKey: (did: string) => Promise<Uint8Array> ): Promise<PresentationVerificationResult>

Parameters

NameTypeDescription
challengeChallengeThe original challenge object
presentationVerifiablePresentationThe signed presentation from the holder
getPublicKey(did: string) => Promise<Uint8Array>Callback to resolve a DID to its Ed25519 public key

Returns a PresentationVerificationResult.

interface PresentationVerificationResult { valid: boolean did?: string error?: string }

Verification Steps

The four checks run in order. If any fails, the remaining checks are skipped.

StepCheckFails when
1ExpiryDate.now() > challenge.expiresAt
2Holder matchpresentation.holder !== challenge.did
3Nonce matchpresentation.proof.challenge !== challenge.nonce
4SignatureEd25519 signature verification fails

Presentation Format

The holder must construct a VerifiablePresentation in this format:

interface VerifiablePresentation { '@context': ['https://www.w3.org/2018/credentials/v1'] type: ['VerifiablePresentation'] holder: string // The holder's DID proof: { type: 'Ed25519Signature2020' created: string // ISO 8601 timestamp verificationMethod: string // DID key reference (e.g. "did:solidus:...#key-1") proofPurpose: 'authentication' challenge: string // The nonce from the challenge jws: string // Detached JWS signature } }

JWS Format

The jws field uses detached JWS format: base64url(header)..base64url(signature) (note the two dots — the payload is omitted).

The header is:

{ "alg": "EdDSA", "b64": false, "crit": ["b64"] }

The signature is computed over the challenge nonce using the holder’s Ed25519 private key.


Complete Auth Flow

Here is a full example showing challenge creation, presentation signing, and verification.

import { createChallenge, verifyPresentation } from '@solidus-network/auth' import { ed25519 } from '@noble/ed25519' // --- Verifier side --- // 1. Create a challenge const challenge = createChallenge('did:solidus:testnet:user123') // Send challenge.id and challenge.nonce to the holder... // --- Holder side --- // 2. Build and sign a presentation const presentation = { '@context': ['https://www.w3.org/2018/credentials/v1'], type: ['VerifiablePresentation'], holder: 'did:solidus:testnet:user123', proof: { type: 'Ed25519Signature2020', created: new Date().toISOString(), verificationMethod: 'did:solidus:testnet:user123#key-1', proofPurpose: 'authentication', challenge: challenge.nonce, jws: await signDetachedJws(challenge.nonce, privateKey), }, } // Send presentation back to verifier... // --- Verifier side --- // 3. Verify the presentation const result = await verifyPresentation( challenge, presentation, async (did) => { // Resolve the DID to get its public key const doc = await resolveDid(did) return doc.verificationMethod[0].publicKeyBytes } ) if (result.valid) { console.log('Authenticated:', result.did) // Create session, issue JWT, etc. } else { console.log('Authentication failed:', result.error) }

Error Cases

ErrorCauseResolution
"Challenge expired"More than 300s (or custom TTL) elapsedIssue a new challenge
"Holder mismatch"Presentation holder DID does not match challenged DIDEnsure the correct DID signs
"Nonce mismatch"Proof challenge field does not match issued nonceUse the nonce from the original challenge
"Invalid signature"Ed25519 signature does not verifyCheck key pair and signing logic
Last updated on