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

Related Hooks