identity-bridge
v0.1.0
Published
Identity middleware for credential wallet discovery, VP exchange, and cryptographic verification — the identity layer crypto wallets are missing
Maintainers
Readme
identity-bridge
Universal discovery protocol for credential wallet browser extensions — like EIP-6963 but for W3C identity wallets.
Sites broadcast a discovery event, installed wallet extensions announce themselves with their DID identity and metadata. Multiple wallets can coexist — the user always chooses.
sequenceDiagram
participant Site as Website
participant A as Extension A<br>(Attestto Creds)
participant B as Extension B<br>(Credible)
Site->>Site: dispatchEvent('credential-wallet:discover')
A-->>Site: announce → did:web:attestto.com:wallets:attestto-creds
B-->>Site: announce → did:web:credible.dev:wallets:credible
Note over Site: Collect announcements (800ms window)
Site->>Site: Show wallet picker → user chooses
Site->>A: navigator.credentials.get({ VerifiablePresentation })
A-->>Site: Returns signed VP
Site->>Site: Verify VP → resolve DID → check signature → issuer trust → revocationIdentity Middleware — not a wallet connector
WalletConnect, Dynamic, and Wagmi are crypto wallet connectors. They connect MetaMask, Phantom, and Ledger to dApps for transaction signing — send ETH, swap tokens, call contracts. They prove "this person controls this private key." That's where they stop.
This package is the credential exchange layer that comes after. It connects credential wallets — Attestto Creds, Credible, Trinsic — to sites for Verifiable Presentations. It proves "a trusted issuer attested this about you" with field-level selective disclosure and cryptographic verification.
Why this matters
A crypto wallet connector tells you someone owns address 0xabc.... It cannot tell you that person passed KYC, holds a vLEI credential from GLEIF, or has a verified institutional identity. For any regulated use case — FATF Travel Rule, eIDAS 2.0, AML compliance — you need the identity layer, not just the key.
What you get vs. what exists
The full stack
- WalletConnect → connect Solana/Ethereum wallet → get address
- identity-resolver → resolve that address → find SNS domain, Attestto credentials, Civic pass, vLEI attestation
- identity-bridge → discover credential wallet extensions → request VP → verify cryptographically
How this relates to existing standards and tools
Several projects touch parts of this problem. None cover the same surface.
Where identity-bridge fits: The W3C Digital Credentials API routes to OS-level wallets. identity-bridge discovers browser extension wallets — the same gap EIP-6963 filled for Ethereum wallets when window.ethereum only supported one provider at a time. As DC-API matures for OS wallets and CHAPI standardizes the browser API, identity-bridge provides the missing extension discovery layer with built-in VP verification that neither standard includes.
Install
npm install identity-bridgeQuick Start
Site-side (your web app)
import { discoverWallets, verifyPresentation } from 'identity-bridge'
// 1. Discover installed credential wallets
const wallets = await discoverWallets()
if (wallets.length === 0) {
// No wallet found — show install prompts
} else if (wallets.length === 1) {
// One wallet — auto-select
console.log('Using', wallets[0].name, wallets[0].did)
} else {
// Multiple wallets — show picker
wallets.forEach(w => console.log(w.name, w.did, w.protocols))
}
// 2. After user picks a wallet, request a credential via standard CHAPI
const credential = await navigator.credentials.get({
web: {
VerifiablePresentation: {
query: { type: 'DIDAuthentication' },
challenge: crypto.randomUUID(),
domain: window.location.origin,
},
},
})
// 3. Verify the returned VP cryptographically
const result = await verifyPresentation(credential, wallets[0], {
resolverUrl: 'https://your-backend.com/api/resolver',
trustedIssuers: ['did:web:attestto.com'],
})
if (result.valid) {
console.log('Verified holder:', result.holderDid)
console.log('DID Document:', result.didDocument)
} else {
console.error('Verification failed:', result.errors)
}Wallet-side (your browser extension)
import { registerWallet } from 'identity-bridge'
// Call once in your content script (MAIN world)
registerWallet({
did: 'did:web:yourorg.com:wallets:your-wallet',
name: 'Your Wallet',
icon: 'https://yourorg.com/icon-64.svg',
version: '1.0.0',
protocols: ['chapi', 'didcomm-v2'],
maintainer: {
name: 'Your Org',
did: 'did:web:yourorg.com',
url: 'https://yourorg.com',
},
})API
discoverWallets(timeoutMs?: number): Promise<WalletAnnouncement[]>
Discover all credential wallet extensions. Dispatches a discover event and collects announcements within the timeout window (default 800ms).
registerWallet(wallet: WalletAnnouncement): void
Register your wallet extension to respond to discovery events. Call once in your content script's MAIN world.
verifyPresentation(vp, wallet, options): Promise<VerifyResult>
Verify a Verifiable Presentation returned by a credential wallet. Performs the full trust chain:
- Wallet trust check — is this wallet in your trusted wallets list?
- Holder extraction — extract the holder DID from the VP
- DID resolution — resolve the holder's DID Document from your resolver
- Signature verification — verify the VP signature against the DID Document
- Issuer trust check — are all VC issuers in your trusted issuers list?
- Revocation check — query each VC's Bitstring Status List for revocation
const result = await verifyPresentation(vp, wallet, {
resolverUrl: 'https://your-backend.com/api/resolver', // DID resolver endpoint (required)
trustedIssuers: ['did:web:attestto.com'], // Trusted VC issuers (required)
trustedWallets: ['did:web:attestto.com:wallets:attestto-creds'], // Optional wallet allowlist
checkRevocation: true, // Check Bitstring Status List (default true)
signal: abortController.signal, // Optional AbortSignal
})Returns:
interface VerifyResult {
valid: boolean // true if zero errors
holderDid: string | null // The holder's DID extracted from the VP
errors: VerifyError[] // All verification failures
didDocument: Record<string, unknown> | null // Resolved DID Document
}
interface VerifyError {
code: VerifyErrorCode // Machine-readable error code
message: string // Human-readable description
}
type VerifyErrorCode =
| 'NO_HOLDER' // VP has no holder DID
| 'RESOLUTION_FAILED' // Could not resolve the holder's DID
| 'SIGNATURE_INVALID' // VP signature verification failed
| 'ISSUER_UNTRUSTED' // VC issuer not in trustedIssuers list
| 'CREDENTIAL_REVOKED' // VC revoked via Bitstring Status List
| 'WALLET_UNTRUSTED' // Wallet DID not in trustedWallets listWalletAnnouncement
interface WalletAnnouncement {
did: string // Wallet's own DID
name: string // Human-readable name
icon: string // Icon URL (SVG or PNG, 64x64)
version: string // Semantic version
protocols: WalletProtocol[] // Supported protocols
maintainer: WalletMaintainer
url?: string // Homepage / docs
}
type WalletProtocol = 'chapi' | 'didcomm-v2' | 'oid4vp' | 'waci-didcomm'
interface WalletMaintainer {
name: string
did?: string
url?: string
}Event Constants
import { DISCOVER_EVENT, ANNOUNCE_EVENT } from 'identity-bridge'
// 'credential-wallet:discover'
// 'credential-wallet:announce'Writing a Custom Wallet Integration
Follow these steps to make your browser extension discoverable via this protocol.
Step 1 — Install and register
Add the package to your extension and call registerWallet() in a content script that runs in the page's MAIN world (not the isolated extension world).
// content-script.ts (MAIN world)
import { registerWallet } from 'identity-bridge'
registerWallet({
did: 'did:web:yourorg.com:wallets:your-wallet',
name: 'Your Wallet',
icon: 'https://yourorg.com/icon-64.svg',
version: '1.0.0',
protocols: ['chapi'],
maintainer: { name: 'Your Org', did: 'did:web:yourorg.com' },
})Your did field must be a real, resolvable DID. The protocol eats its own dog food — wallets identify themselves the same way users do.
Step 2 — Handle CHAPI requests
Override navigator.credentials.get() in the MAIN world to intercept credential requests:
const originalGet = navigator.credentials.get.bind(navigator.credentials)
navigator.credentials.get = async function (options) {
// Check if this is a VP request
const vpRequest = (options as any)?.web?.VerifiablePresentation
if (!vpRequest) return originalGet(options)
// Show your consent UI to the user
const userConsented = await showConsentDialog(vpRequest)
if (!userConsented) throw new DOMException('User denied', 'NotAllowedError')
// Build and return the VP
return buildVerifiablePresentation(vpRequest)
}Step 3 — Return a Verifiable Presentation
The VP you return must include:
holder— the user's DID (string or{ id: 'did:...' })verifiableCredential— array of VCsproof— cryptographic signature over the VP
function buildVerifiablePresentation(request) {
return {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiablePresentation'],
holder: 'did:web:user.example.com',
verifiableCredential: [/* user's selected VCs */],
proof: {
type: 'Ed25519Signature2020',
created: new Date().toISOString(),
challenge: request.challenge,
domain: request.domain,
verificationMethod: 'did:web:user.example.com#key-1',
proofValue: '...', // Sign with the user's private key
},
}
}Step 4 — Verify your DID is resolvable
The site will verify your VP by resolving the holder's DID and checking the signature against the public key in the DID Document. Make sure:
- The holder's DID resolves to a valid DID Document
- The DID Document contains the public key referenced in
proof.verificationMethod - The
proof.challengeandproof.domainmatch what the site sent
Supported Protocols
Security
See SECURITY.md for:
- Wallet discovery spoofing — discovery is untrusted metadata; trust is established via VP verification
- Trusted wallet allowlist — restrict which wallet DIDs your app accepts
- Cross-origin considerations — CORS for DID resolution and revocation checks
- API key exposure — always use a backend proxy for resolver calls
- Trust chain — the DID method spec defines where to resolve, not the VC
See It In Action
The DID Landscape Explorer uses this package in its self-assessment wizard. The Identity step discovers installed wallets and lets users present their DID.
Contributing
See CONTRIBUTING.md.
License
MIT
