Getting Started with @vaultys/id
The @vaultys/id library provides a comprehensive identity management system with support for secure peer-to-peer authentication, multiple cryptographic algorithms including post-quantum options, and various authentication methods including FIDO2/WebAuthn.
Installation
npm install @vaultys/id
# Additional packages for specific features
npm install @vaultys/channel-peerjs # For P2P communication
npm install @vaultys/ui # For UI components like QR codes
Core Concepts
VaultysId
A VaultysId represents a cryptographic identity that can be used for authentication and secure communication. It supports multiple key types:
- Software keys: Standard Ed25519 cryptography
- Post-quantum keys: Dilithium + Ed25519 hybrid
- Hardware keys: FIDO2/WebAuthn (Passkeys)
IdManager
The IdManager class handles the lifecycle of a VaultysId, including:
- Contact management
- Authentication handshakes
- Key storage and retrieval
Channels
Channels provide the communication layer for identity exchange and authentication. The library supports multiple channel types:
- MemoryChannel: For in-memory testing
- PeerjsChannel: For P2P WebRTC communication
- CryptoChannel: Wrapper for encrypted communication
Creating an Identity
Software-based Identity
import { VaultysId, IdManager, MemoryStorage } from "@vaultys/id";
// Create a standard Ed25519 identity
const vaultysId = await VaultysId.generatePerson("ed25519");
// Create a post-quantum resistant identity
const pqVaultysId = await VaultysId.generatePerson("dilithium_ed25519");
// Create an identity for a machine/service
const machineId = await VaultysId.generateMachine("ed25519");
// Initialize the IdManager with storage
const storage = MemoryStorage(() => {});
const idManager = new IdManager(vaultysId, storage);
Hardware-based Identity (FIDO2/Passkey)
import { VaultysId } from "@vaultys/id";
const createSecureVaultysID = async (requireResidentKey) => {
const credentialOptions = VaultysId.createPublicKeyCredentialCreationOptions(requireResidentKey);
const attestation = await navigator.credentials.create({
publicKey: credentialOptions,
});
if (!attestation) return null;
return await VaultysId.fido2FromAttestation(attestation);
};
// Create a Passkey-based identity
const passKeyId = await createSecureVaultysID(true);
// Create a FIDO2 key-based identity
const fido2Id = await createSecureVaultysID(false);
Authentication Flow
The library uses a challenge-response authentication protocol with the following steps:
- Bob initiates contact by sending his VaultysID
- Alice validates the request and responds with her VaultysID and signature
- Bob verifies Alice's signature and sends his signature
- Both parties have now mutually authenticated
Basic Authentication Example
import { IdManager, VaultysId, MemoryStorage, CryptoChannel } from "@vaultys/id";
import { MemoryChannel } from "./MemoryChannel";
// Create two identities
const alice = new IdManager(
await VaultysId.generateMachine("ed25519"),
MemoryStorage(() => {})
);
const bob = new IdManager(
await VaultysId.generatePerson("ed25519"),
MemoryStorage(() => {})
);
// Create an encrypted bidirectional channel
const key = CryptoChannel.generateKey();
const channel = MemoryChannel.createEncryptedBidirectionnal(key);
// Perform mutual authentication
const [bobContact, aliceContact] = await Promise.all([
bob.askContact(channel),
alice.acceptContact(channel.otherend)
]);
console.log("Authentication successful!");
console.log("Bob now has Alice's contact:", aliceContact);
console.log("Alice now has Bob's contact:", bobContact);
QR Code Authentication
For mobile-to-web or cross-device authentication, you can use QR codes with P2P channels:
import { IdManager, Channel } from "@vaultys/id";
import { QrCodeElement } from "@vaultys/ui";
const ConnectWithQR = ({ profile }) => {
const [qrText, setQrText] = useState();
const [contact, setContact] = useState();
const startConnection = async () => {
// Import PeerJS channel dynamically
const { PeerjsChannel } = require("@vaultys/channel-peerjs");
const channel = new PeerjsChannel();
// Get encryption key if needed
const encryptionKey = Buffer.from(channel.key, "hex");
// Start the channel
await channel.start();
// Create QR code URL with connection details
const connectionUrl = `https://your-app.com/#${channel.getConnectionString()}&protocol=p2p&service=auth&did=${profile.vaultysId.did}`;
setQrText(connectionUrl);
// Wait for and accept incoming connection
try {
const remoteContact = await profile.acceptContact(channel);
setContact(remoteContact);
channel.close();
} catch (error) {
console.error("Handshake failed:", error);
}
};
return qrText ? (
<QrCodeElement text={qrText} />
) : (
<button onClick={startConnection}>Start Connection</button>
);
};
Challenge Types and Certificates
The authentication process generates certificates containing:
interface ChallengeType {
protocol: string; // e.g., "p2p"
service: string; // e.g., "auth"
pk1?: Buffer; // Bob's public key
pk2?: Buffer; // Alice's public key
sign1?: Buffer; // Bob's signature
sign2?: Buffer; // Alice's signature
challenge?: Buffer; // Raw encoded data
}
Storage Options
The library provides different storage backends:
MemoryStorage
For testing and temporary storage:
const storage = MemoryStorage(() => {
console.log("Storage updated");
});
Custom Storage
Implement your own storage backend:
class CustomStorage {
async get(key) {
// Retrieve from your storage
}
async set(key, value) {
// Store in your backend
}
async delete(key) {
// Remove from storage
}
}
Working with Fingerprints
Each VaultysId has a unique fingerprint that can be used for visual verification:
const vaultysId = await VaultysId.generatePerson("ed25519");
// Get the fingerprint
const fingerprint = vaultysId.fingerprint;
// Example: "0355 E57E 7D06 A275 C7BD E1E6 49E0 735D 0DAF 1151"
// Convert to different versions
const v1 = vaultysId.toVersion(1);
console.log(v1.fingerprint);
// Use for visual representation (e.g., with avatars)
<AvatarIcon fingerprint={fingerprint} />
Error Handling
Always wrap authentication operations in try-catch blocks:
try {
const contact = await idManager.acceptContact(channel);
if (!contact) {
throw new Error("Authentication failed - no contact returned");
}
// Success
} catch (error) {
if (error.message.includes("handshake")) {
console.error("Handshake failed - invalid signature");
} else if (error.message.includes("timeout")) {
console.error("Connection timeout");
} else {
console.error("Unknown error:", error);
}
}
Best Practices
-
Key Management:
- Use hardware keys (FIDO2/Passkeys) for high-security scenarios
- Consider post-quantum algorithms for future-proofing
- Store keys securely and never expose private keys
-
Channel Security:
- Always use encrypted channels for sensitive data
- Generate fresh encryption keys for each session
- Close channels properly after use
-
Identity Verification:
- Display fingerprints for out-of-band verification
- Implement proper error handling for failed authentications
- Log authentication attempts for security monitoring
-
Performance:
- Reuse IdManager instances when possible
- Close channels when not in use to free resources
- Use appropriate storage backends for your use case
Advanced Topics
Custom Channel Implementation
class CustomChannel {
async send(data) {
// Implement your transport
}
async receive() {
// Return received data
}
async start() {
// Initialize connection
}
async close() {
// Cleanup resources
}
getConnectionString() {
// Return connection details
}
fromConnectionString(conn) {
// Parse and connect
}
}
Post-Quantum Cryptography
The library supports Dilithium, a post-quantum signature algorithm:
// Create a hybrid post-quantum identity
const pqIdentity = await VaultysId.generatePerson("dilithium_ed25519");
// This uses both Dilithium (quantum-resistant) and Ed25519 (classical)
// for defense in depth
Next Steps
- Explore the VaultysId API Reference for detailed class and method documentation
- Check out the API Reference for complete library documentation
- Review Example Applications for real-world usage
- Learn about Security Considerations for production deployments