Skip to main content

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:

  1. Bob initiates contact by sending his VaultysID
  2. Alice validates the request and responds with her VaultysID and signature
  3. Bob verifies Alice's signature and sends his signature
  4. 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

  1. 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
  2. Channel Security:

    • Always use encrypted channels for sensitive data
    • Generate fresh encryption keys for each session
    • Close channels properly after use
  3. Identity Verification:

    • Display fingerprints for out-of-band verification
    • Implement proper error handling for failed authentications
    • Log authentication attempts for security monitoring
  4. 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