@l8d/viem-account-yubikey
v0.1.2
Published
Helper library to use YubiKey hardware security keys with viem
Downloads
351
Maintainers
Readme
@l8d/viem-account-yubikey
Use YubiKey hardware security keys for signing Ethereum transactions and messages with viem.
This package enables you to create viem accounts that use YubiKey's OpenPGP card applet for signing operations, providing hardware-based security for your Ethereum interactions.
Features
- 🔐 Hardware Security: Sign transactions using YubiKey's secure element
- 🔌 Viem Integration: Works seamlessly with viem's wallet client
- 🎯 OpenPGP Slots: Support for all three OpenPGP key slots (Signature, Authentication, Decryption)
- 🔍 Auto-Discovery: Automatically find which slot controls your Ethereum address
- ⚡ Type-Safe: Full TypeScript support with type definitions
- 📦 Zero Config: Works out of the box with sensible defaults
Installation
npm install @l8d/viem-account-yubikey viem@^2.0.0 pcsclite@^1.0.0System Requirements
- Node.js: 18 or higher
- PC/SC Daemon:
- macOS: Built-in, works out of the box
- Linux: Install
pcscd(sudo apt install pcscdon Debian/Ubuntu) - Windows: Built-in smart card service
- YubiKey: With secp256k1 keys configured in OpenPGP applet
Quick Start
import { yubikeyToAccount } from '@l8d/viem-account-yubikey';
import { createWalletClient, http } from 'viem';
import { mainnet } from 'viem/chains';
// Create YubiKey account
const account = await yubikeyToAccount({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
// Optional: specify PIN (defaults to '123456')
pin: '123456',
// Optional: slot will be auto-discovered if not specified
});
// Create wallet client
const client = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
// Send transaction
const hash = await client.sendTransaction({
to: '0x...',
value: 1000000000000000000n, // 1 ETH
});
console.log('Transaction hash:', hash);Usage
Basic Account Creation
import { yubikeyToAccount } from '@l8d/viem-account-yubikey';
// Minimal configuration (slot will be auto-discovered)
const account = await yubikeyToAccount({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
});Explicit Slot Specification
For better performance, you can explicitly specify which OpenPGP slot to use:
import { yubikeyToAccount, OpenPGPSlot } from '@l8d/viem-account-yubikey';
const account = await yubikeyToAccount({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
slot: OpenPGPSlot.Signature, // or Authentication, or Decryption
pin: '123456',
});Available OpenPGP Slots
YubiKey's OpenPGP applet has three key slots:
enum OpenPGPSlot {
Signature = 0xb6, // Primary signing slot
Authentication = 0xa4, // Authentication operations
Decryption = 0xb8, // Encryption/decryption operations
}All three slots can hold secp256k1 keys and be used for Ethereum signing.
Signing Messages
// Sign a personal message (EIP-191)
const signature = await account.signMessage({
message: 'Hello, Ethereum!',
});Signing Typed Data
// Sign EIP-712 typed data
const signature = await account.signTypedData({
domain: {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0x...',
},
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
},
primaryType: 'Person',
message: {
name: 'Bob',
wallet: '0x...',
},
});Direct Signing (Without Client)
You can also use the account directly without creating a wallet client:
const account = await yubikeyToAccount({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
});
// Sign transaction
const signedTx = await account.signTransaction({
to: '0x...',
value: 1000000000000000000n,
chainId: 1,
nonce: 42,
maxFeePerGas: 100n,
maxPriorityFeePerGas: 100n,
});
// Sign message
const signature = await account.signMessage({
message: 'Hello!',
});Configuration Options
interface YubikeyAccountConfig {
/**
* The Ethereum address controlled by the YubiKey
*/
address: `0x${string}`;
/**
* Optional: Explicitly specify which OpenPGP slot to use
* If not provided, the slot will be auto-discovered
*/
slot?: OpenPGPSlot;
/**
* Optional: OpenPGP card PIN (default: "123456")
* Required for signing operations
*/
pin?: string;
/**
* Optional: Timeout for signing operations in milliseconds (default: 30000)
*/
timeout?: number;
/**
* Optional: Enable debug logging
*/
debug?: boolean;
}YubiKey Setup
Before using this package, you need to:
- Configure secp256k1 keys in your YubiKey's OpenPGP applet
- Know the Ethereum address derived from your YubiKey's public key
Configuring Your YubiKey
You can use the YubiKey Manager (ykman) or GPG to configure OpenPGP keys:
# Install YubiKey Manager
brew install ykman # macOS
# or
sudo apt install yubikey-manager # Linux
# View current OpenPGP configuration
ykman openpgp infoFor importing existing secp256k1 keys or generating new ones, refer to the YubiKey OpenPGP documentation.
Finding Your Ethereum Address
If you already have a secp256k1 key on your YubiKey but don't know the corresponding Ethereum address, you can use utilities from the parent @l8d/hardhat-yubikey package to discover it.
Transaction Types Support
This package supports all Ethereum transaction types:
- ✅ Legacy Transactions (Type 0)
- ✅ EIP-2930 Access List Transactions (Type 1)
- ✅ EIP-1559 Dynamic Fee Transactions (Type 2)
The v value is automatically calculated based on the transaction type and chain ID.
Error Handling
try {
const account = await yubikeyToAccount({
address: '0x...',
});
} catch (error) {
if (error.message.includes('PC/SC')) {
console.error('YubiKey connection failed. Is it plugged in?');
} else if (error.message.includes('PIN verification failed')) {
console.error('Incorrect PIN provided');
} else if (error.message.includes('Could not find OpenPGP slot')) {
console.error('Address not found on any YubiKey slot');
} else {
console.error('Unexpected error:', error);
}
}Debug Logging
Enable debug logging to troubleshoot issues:
// Enable for specific account
const account = await yubikeyToAccount({
address: '0x...',
debug: true,
});
// Or enable via environment variable
DEBUG=viem-account-yubikey:* node your-script.jsHow It Works
- Connection: Uses PC/SC (Personal Computer/Smart Card) interface to communicate with YubiKey
- OpenPGP Applet: Sends APDU commands to YubiKey's OpenPGP card application
- ECDSA Signing: YubiKey performs ECDSA signing using secp256k1 curve
- Signature Recovery: Recovers the Ethereum
vvalue by trying both recovery IDs - Format Conversion: Converts OpenPGP signature format to Ethereum signature format
Security Considerations
- 🔐 Private keys never leave the YubiKey: All signing happens on the secure element
- 🔑 PIN Protection: YubiKey requires PIN verification before signing
- 🛡️ Physical Confirmation: Some YubiKey models require physical touch for signing
- ⚠️ Default PIN: Change the default PIN (
123456) after initial setup!
Limitations
- Slot Auto-Discovery: First-time slot discovery requires trying all three slots, which may take a few seconds
- Single Device: Currently supports one YubiKey at a time
- PC/SC Required: Requires PC/SC daemon to be running on the system
Related Packages
@l8d/hardhat-yubikey- Hardhat plugin for YubiKey integrationviem- Lightweight Ethereum library@celo/viem-account-ledger- Similar package for Ledger devices
Troubleshooting
"Failed to initialize PC/SC"
- macOS: PC/SC should work out of the box
- Linux: Install and start pcscd:
sudo apt install pcscd && sudo systemctl start pcscd - Windows: Ensure Smart Card service is running
"Could not find OpenPGP slot"
- Verify your YubiKey has secp256k1 keys configured
- Check that the address matches the public key on your YubiKey
- Try explicitly specifying the slot
"PIN verification failed"
- Default PIN is
123456 - After 3 failed attempts, PIN will be blocked
- Use PUK to unlock if blocked
Contributing
Contributions are welcome! Please see the main repository for contribution guidelines.
License
MIT
Author
Tenor Biel (@l8d)
Acknowledgments
This package is inspired by:
@celo/viem-account-ledgerby cLabs@nomicfoundation/hardhat-ledgerby Nomic Foundation
Need help? Open an issue on GitHub.
