usePRF
Generate cryptographic keys from passkeys using the WebAuthn Pseudo-Random Function extension.
Cryptographic Operations: End-to-end encryption, key derivation, digital signatures, and secure channels
Import
import { usePRF } from '@heliorim/sdk-react';Type Signature
interface PRFState {
isSupported: boolean;
isGenerating: boolean;
keys: Map<string, CryptoKey>;
publicKeys: Map<string, string>;
error: Error | null;
}
interface PRFActions {
generateKey: (salt: string, options?: PRFOptions) => Promise<CryptoKey>;
deriveKey: (password: string, salt: string) => Promise<CryptoKey>;
encrypt: (data: any, key: CryptoKey) => Promise<EncryptedData>;
decrypt: (encryptedData: EncryptedData, key: CryptoKey) => Promise<any>;
sign: (data: any, key: CryptoKey) => Promise<string>;
verify: (data: any, signature: string, key: CryptoKey) => Promise<boolean>;
exportKey: (key: CryptoKey) => Promise<string>;
importKey: (keyData: string, type: 'private' | 'public') => Promise<CryptoKey>;
}
interface PRFOptions {
algorithm?: 'ECDH' | 'ECDSA' | 'RSA-PSS';
length?: 256 | 384 | 521;
usage?: KeyUsage[];
extractable?: boolean;
}
interface EncryptedData {
ciphertext: string;
iv: string;
salt: string;
algorithm: string;
}
type KeyUsage = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits';
type UsePRFReturn = PRFState & PRFActions;Basic Usage
function EncryptedMessaging() {
const {
isSupported,
generateKey,
encrypt,
decrypt,
keys
} = usePRF();
const [encryptionKey, setEncryptionKey] = useState<CryptoKey | null>(null);
const [messages, setMessages] = useState<any[]>([]);
const initializeEncryption = async () => {
if (!isSupported) {
console.error('PRF not supported');
return;
}
try {
// Generate encryption key from passkey
const key = await generateKey('messaging-key', {
algorithm: 'ECDH',
usage: ['encrypt', 'decrypt']
});
setEncryptionKey(key);
console.log('Encryption key generated from passkey');
} catch (err) {
console.error('Failed to generate key:', err);
}
};
const sendEncryptedMessage = async (message: string) => {
if (!encryptionKey) {
await initializeEncryption();
return;
}
try {
// Encrypt message
const encrypted = await encrypt({ text: message, timestamp: Date.now() }, encryptionKey);
// Send to server/peer
await sendMessage(encrypted);
console.log('Message encrypted and sent');
} catch (err) {
console.error('Encryption failed:', err);
}
};
const decryptMessage = async (encryptedData: EncryptedData) => {
if (!encryptionKey) return;
try {
const decrypted = await decrypt(encryptedData, encryptionKey);
setMessages(prev => [...prev, decrypted]);
console.log('Message decrypted:', decrypted.text);
} catch (err) {
console.error('Decryption failed:', err);
}
};
return (
<div>
<h3>End-to-End Encrypted Chat</h3>
<button onClick={initializeEncryption}>
Initialize Encryption
</button>
<div className="chat-interface">
<input
type="text"
placeholder="Type encrypted message..."
onKeyPress={(e) => {
if (e.key === 'Enter') {
sendEncryptedMessage(e.currentTarget.value);
e.currentTarget.value = '';
}
}}
/>
</div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i}>
{msg.text} <span className="timestamp">{new Date(msg.timestamp).toLocaleTimeString()}</span>
</div>
))}
</div>
</div>
);
}Advanced Cryptography
Digital Signatures
function DocumentSigning() {
const { generateKey, sign, verify } = usePRF();
const [signingKey, setSigningKey] = useState<CryptoKey | null>(null);
const initializeSigning = async () => {
const key = await generateKey('document-signing', {
algorithm: 'ECDSA',
usage: ['sign', 'verify']
});
setSigningKey(key);
};
const signDocument = async (document: any) => {
if (!signingKey) {
await initializeSigning();
return;
}
// Create signature
const signature = await sign(document, signingKey);
// Package signed document
const signedDoc = {
document,
signature,
signer: await getUserIdentity(),
timestamp: Date.now()
};
return signedDoc;
};
const verifyDocument = async (signedDoc: any) => {
if (!signingKey) return false;
try {
const isValid = await verify(
signedDoc.document,
signedDoc.signature,
signingKey
);
if (isValid) {
console.log('Document signature valid');
console.log('Signed by:', signedDoc.signer);
console.log('Signed at:', new Date(signedDoc.timestamp));
} else {
console.error('Invalid signature!');
}
return isValid;
} catch (err) {
console.error('Verification failed:', err);
return false;
}
};
return (
<div className="document-signing">
<h3>Digital Document Signing</h3>
<button onClick={initializeSigning}>
Setup Signing Key
</button>
<button onClick={async () => {
const doc = {
title: 'Contract',
content: 'Agreement terms...',
value: 100000
};
const signed = await signDocument(doc);
console.log('Signed document:', signed);
}}>
Sign Document
</button>
<button onClick={async () => {
// Verify a signed document
const signedDoc = getSignedDocument();
const valid = await verifyDocument(signedDoc);
alert(valid ? 'Signature Valid!' : 'Invalid Signature!');
}}>
Verify Signature
</button>
</div>
);
}Key Derivation & Hierarchical Keys
function HierarchicalKeys() {
const { generateKey, deriveKey } = usePRF();
const generateKeyHierarchy = async () => {
// Master key from passkey
const masterKey = await generateKey('master', {
algorithm: 'ECDH',
usage: ['deriveKey', 'deriveBits']
});
// Derive purpose-specific keys
const keys = {
encryption: await deriveKey('encryption', masterKey),
authentication: await deriveKey('authentication', masterKey),
signing: await deriveKey('signing', masterKey),
backup: await deriveKey('backup', masterKey)
};
// Derive time-based keys
const sessionKey = await deriveKey(
`session-${Date.now()}`,
keys.authentication
);
// Derive per-contact keys
const contactKeys = new Map();
for (const contact of getContacts()) {
const contactKey = await deriveKey(
`contact-${contact.id}`,
keys.encryption
);
contactKeys.set(contact.id, contactKey);
}
return {
master: masterKey,
derived: keys,
session: sessionKey,
contacts: contactKeys
};
};
return (
<div>
<h3>Hierarchical Key Management</h3>
<button onClick={generateKeyHierarchy}>
Generate Key Hierarchy
</button>
</div>
);
}Secure Channel Establishment
function SecureChannel() {
const { generateKey, deriveKey, encrypt, decrypt } = usePRF();
const establishSecureChannel = async (peerId: string) => {
// Generate ephemeral key pair
const ephemeralKey = await generateKey(`channel-${peerId}`, {
algorithm: 'ECDH',
usage: ['deriveKey', 'deriveBits']
});
// Exchange public keys with peer
const myPublicKey = await exportPublicKey(ephemeralKey);
const peerPublicKey = await exchangeKeys(peerId, myPublicKey);
// Derive shared secret
const sharedSecret = await deriveKey(
peerPublicKey,
ephemeralKey
);
// Create channel encryption keys
const channelKeys = {
send: await deriveKey('send', sharedSecret),
receive: await deriveKey('receive', sharedSecret),
hmac: await deriveKey('hmac', sharedSecret)
};
return {
channelId: crypto.randomUUID(),
peerId,
keys: channelKeys,
established: Date.now()
};
};
const sendSecureMessage = async (channel: any, message: string) => {
// Encrypt with send key
const encrypted = await encrypt(
{ message, nonce: crypto.randomUUID() },
channel.keys.send
);
// Add HMAC for integrity
const hmac = await sign(encrypted, channel.keys.hmac);
return {
channelId: channel.channelId,
payload: encrypted,
hmac,
timestamp: Date.now()
};
};
const receiveSecureMessage = async (channel: any, data: any) => {
// Verify HMAC
const valid = await verify(data.payload, data.hmac, channel.keys.hmac);
if (!valid) {
throw new Error('Message integrity check failed');
}
// Decrypt with receive key
const decrypted = await decrypt(data.payload, channel.keys.receive);
return decrypted.message;
};
return (
<div className="secure-channel">
<h3>Secure Channel Communication</h3>
<button onClick={() => establishSecureChannel('peer-123')}>
Establish Secure Channel
</button>
</div>
);
}Encryption Patterns
File Encryption
function FileEncryption() {
const { generateKey, encrypt, decrypt } = usePRF();
const [fileKey, setFileKey] = useState<CryptoKey | null>(null);
const encryptFile = async (file: File) => {
if (!fileKey) {
const key = await generateKey('file-encryption', {
algorithm: 'ECDH',
usage: ['encrypt', 'decrypt']
});
setFileKey(key);
}
// Read file
const buffer = await file.arrayBuffer();
const data = new Uint8Array(buffer);
// Encrypt file contents
const encrypted = await encrypt(data, fileKey!);
// Create encrypted file blob
const encryptedBlob = new Blob([
JSON.stringify({
name: file.name,
type: file.type,
size: file.size,
encrypted: encrypted
})
], { type: 'application/encrypted' });
// Download encrypted file
const url = URL.createObjectURL(encryptedBlob);
const a = document.createElement('a');
a.href = url;
a.download = `${file.name}.encrypted`;
a.click();
};
const decryptFile = async (encryptedFile: File) => {
if (!fileKey) {
alert('No decryption key available');
return;
}
// Parse encrypted file
const text = await encryptedFile.text();
const data = JSON.parse(text);
// Decrypt contents
const decrypted = await decrypt(data.encrypted, fileKey);
// Restore original file
const blob = new Blob([decrypted], { type: data.type });
// Download decrypted file
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = data.name;
a.click();
};
return (
<div className="file-encryption">
<h3>File Encryption</h3>
<input
type="file"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) encryptFile(file);
}}
placeholder="Select file to encrypt"
/>
<input
type="file"
accept=".encrypted"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) decryptFile(file);
}}
placeholder="Select encrypted file"
/>
</div>
);
}Field-Level Encryption
function FieldEncryption() {
const { generateKey, encrypt, decrypt } = usePRF();
const encryptFormData = async (formData: any) => {
// Generate field-specific keys
const keys = {
ssn: await generateKey('field-ssn'),
creditCard: await generateKey('field-cc'),
medical: await generateKey('field-medical')
};
// Encrypt sensitive fields
const encrypted = {
...formData,
ssn: await encrypt(formData.ssn, keys.ssn),
creditCard: await encrypt(formData.creditCard, keys.creditCard),
medicalHistory: await encrypt(formData.medicalHistory, keys.medical)
};
return encrypted;
};
const searchEncryptedField = async (encryptedData: any[], fieldName: string, searchValue: string) => {
// Generate deterministic encryption for search
const searchKey = await generateKey(`search-${fieldName}`);
const encryptedSearch = await encrypt(searchValue, searchKey);
// Search without decrypting
return encryptedData.filter(item =>
item[fieldName] === encryptedSearch
);
};
return (
<div>
<h3>Field-Level Encryption</h3>
<p>Encrypt sensitive fields while keeping others searchable</p>
</div>
);
}Key Management
function KeyManagement() {
const { keys, exportKey, importKey, generateKey } = usePRF();
const [keyBackup, setKeyBackup] = useState<string>('');
const backupKeys = async () => {
const backup: Record<string, string> = {};
for (const [name, key] of keys) {
backup[name] = await exportKey(key);
}
// Encrypt backup with recovery key
const recoveryKey = await generateKey('recovery');
const encryptedBackup = await encrypt(backup, recoveryKey);
setKeyBackup(JSON.stringify(encryptedBackup));
console.log('Keys backed up successfully');
};
const restoreKeys = async (backup: string) => {
try {
const encryptedBackup = JSON.parse(backup);
const recoveryKey = await generateKey('recovery');
const decrypted = await decrypt(encryptedBackup, recoveryKey);
for (const [name, keyData] of Object.entries(decrypted)) {
const key = await importKey(keyData as string, 'private');
keys.set(name, key);
}
console.log('Keys restored successfully');
} catch (err) {
console.error('Key restoration failed:', err);
}
};
const rotateKeys = async () => {
const oldKeys = new Map(keys);
keys.clear();
// Generate new keys
for (const [name] of oldKeys) {
const newKey = await generateKey(`${name}-rotated-${Date.now()}`);
keys.set(name, newKey);
}
console.log('Keys rotated successfully');
};
return (
<div className="key-management">
<h3>Key Management</h3>
<button onClick={backupKeys}>Backup Keys</button>
<button onClick={rotateKeys}>Rotate Keys</button>
{keyBackup && (
<div>
<h4>Key Backup</h4>
<textarea value={keyBackup} readOnly />
<button onClick={() => navigator.clipboard.writeText(keyBackup)}>
Copy Backup
</button>
</div>
)}
</div>
);
}Browser Compatibility
PRF Extension Support:
- Chrome/Edge 113+ (Full support)
- Safari: Under development
- Firefox: Not yet supported
- Requires CTAP 2.1+ authenticators
- Platform authenticators recommended
Security Considerations
✓ Best Practices
- Use unique salts for each key generation
- Implement key rotation policies
- Never expose private keys
- Use authenticated encryption (AEAD)
- Verify signatures before trusting data
- Store public keys only when necessary
⚠️ Warnings
- Keys are device-bound, not synced
- Loss of passkey means loss of derived keys
- PRF output is deterministic with same salt
- Not a replacement for HSM in high-security contexts