npm package: @l8d/hardhat-yubikey
Description: Hardhat v3 plugin for YubiKey hardware wallet using OpenPGP card
Author: Tenor Biel
Latest version: 1.0.7

# @l8d/hardhat-yubikey

A Hardhat v3 plugin that enables integration with YubiKey hardware wallets using the OpenPGP card application with secp256k1 keys for Ethereum transaction signing.

## Features

- 🔐 Sign Ethereum transactions using YubiKey's OpenPGP card application
- 🔑 Support for secp256k1 elliptic curve (Ethereum-compatible)
- 🎯 Multiple OpenPGP key slots (signature, decryption, authentication)
- ⚡ Compatible with Hardhat v3 plugin system
- 🛡️ Hardware-backed private key security
- 📝 Support for all Ethereum signing methods (transactions, messages, EIP-712 typed data)
- 🔄 EIP-1559 and legacy transaction support

## Requirements

- **YubiKey firmware 5.2.3+** with OpenPGP card application
- **secp256k1 keys** configured in OpenPGP card
- PC/SC smart card daemon (pcscd) installed and running
- Node.js and Hardhat v3

## Installation

```bash
npm install @l8d/hardhat-yubikey
```

### System Dependencies

#### macOS
```bash
# PC/SC is built-in, but you may need build tools for native modules
xcode-select --install
```

#### Linux
```bash
# Debian/Ubuntu
sudo apt-get install libpcsclite1 libpcsclite-dev pcscd

# Fedora/RHEL
sudo dnf install pcsc-lite pcsc-lite-devel pcsc-lite-ccid

# Arch Linux
sudo pacman -S pcsclite ccid

# Start pcscd service
sudo systemctl start pcscd
sudo systemctl enable pcscd
```

#### Windows
- Install [PC/SC smart card drivers](https://www.microsoft.com/en-us/download/)
- Windows has built-in PC/SC support

## YubiKey Setup

Before using this plugin, you need to configure your YubiKey's OpenPGP card with secp256k1 keys:

### 1. Install YubiKey Manager

```bash
# macOS
brew install ykman

# Linux
pip install yubikey-manager

# Windows
# Download from https://www.yubico.com/support/download/yubikey-manager/
```

### 2. Generate secp256k1 Keys

```bash
# Check YubiKey info and firmware version (must be 5.2.3+)
ykman openpgp info

# Generate a secp256k1 signature key in slot 1 (SIGNATURE)
gpg --card-edit
> admin
> generate
# Select secp256k1 curve when prompted
```

Alternatively, you can import existing secp256k1 keys:

```bash
# Import private key to signature slot
gpg --expert --edit-key YOUR_KEY_ID
> keytocard
# Select slot 1 (Signature key)
```

### 3. Get Your Ethereum Address

To derive your Ethereum address from the YubiKey's secp256k1 public key:

```bash
# Export public key
gpg --export YOUR_KEY_ID > pubkey.gpg

# Use a tool to convert GPG public key to Ethereum address
# (or use the address you derived when generating the key)
```

## Usage

### Basic Configuration

Add the plugin to your `hardhat.config.ts`:

```typescript
import { defineConfig } from "hardhat/config";
import "@l8d/hardhat-yubikey";

export default defineConfig({
  solidity: "0.8.19",
  networks: {
    sepolia: {
      url: "https://sepolia.infura.io/v3/YOUR-PROJECT-ID",
      yubikeyAccounts: [
        {
          address: "0xYOUR_ETHEREUM_ADDRESS",
          slot: "signature", // or OpenPGPSlot.SIGNATURE (0x00)
        },
      ],
      yubikeyOptions: {
        connectionTimeout: 30000, // 30 seconds (default)
        requireConfirmation: true, // Require touch for each signature
      },
    },
  },
});
```

### Slot Options

The plugin supports three OpenPGP card key slots:

```typescript
yubikeyAccounts: [
  { address: "0x...", slot: "signature" },     // Slot 1 (0x00)
  { address: "0x...", slot: "decryption" },    // Slot 2 (0x01)
  { address: "0x...", slot: "authentication" }, // Slot 3 (0x02)
]
```

### Multiple Accounts

You can configure multiple accounts using different slots:

```typescript
yubikeyAccounts: [
  { address: "0xAddress1...", slot: "signature" },
  { address: "0xAddress2...", slot: "authentication" },
]
```

### Deploying Contracts

```typescript
// scripts/deploy.ts
import { ethers, network } from "hardhat";

async function main() {
  // Make sure YubiKey is connected and unlocked
  await network.provider.request({
    method: "eth_requestAccounts",
    params: [],
  });

  const [deployer] = await ethers.getSigners();

  console.log("Deploying with account:", deployer.address);

  // Plugin will prompt for YubiKey PIN and touch confirmation
  const MyContract = await ethers.getContractFactory("MyContract");
  const contract = await MyContract.deploy();

  await contract.waitForDeployment();
  console.log("Contract deployed to:", await contract.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

### Signing Messages

```typescript
const signer = await ethers.getSigner("0xYOUR_YUBIKEY_ADDRESS");

// Sign personal message
const message = "Hello, Ethereum!";
const signature = await signer.signMessage(message);

// Sign EIP-712 typed data
const domain = { name: "MyApp", version: "1", chainId: 1 };
const types = { Message: [{ name: "content", type: "string" }] };
const value = { content: "Hello!" };
const typedSignature = await signer.signTypedData(domain, types, value);
```

## Configuration Options

### `yubikeyAccounts`

Array of account configurations, each containing:

- `address` (string, required): Ethereum address (0x-prefixed)
- `slot` (string | number, required): OpenPGP key slot
  - String values: `"signature"`, `"decryption"`, `"authentication"`
  - Number values: `0x00`, `0x01`, `0x02`

### `yubikeyOptions`

Optional configuration object:

- `connectionTimeout` (number): Timeout in milliseconds for YubiKey connection (default: 30000)
- `requireConfirmation` (boolean): Require physical touch for signatures (default: true)

## How It Works

1. **Connection**: Plugin uses PC/SC to communicate with YubiKey's OpenPGP card application
2. **Key Selection**: Maps Ethereum addresses to specific OpenPGP key slots
3. **Signing**: When a transaction needs signing:
   - Hashes the transaction using Keccak256
   - Sends COMPUTE SIGNATURE command to YubiKey
   - YubiKey returns ECDSA signature (r, s)
   - Plugin recovers the v value and formats signature for Ethereum
4. **Confirmation**: Physical touch required for each signature (if enabled)

## Security Considerations

- **PIN Protection**: OpenPGP card requires PIN entry before signing
- **Touch Policy**: Configure YubiKey to require touch for signatures
- **Private Key**: Never leaves the YubiKey hardware
- **Slot Isolation**: Keys in different slots are cryptographically isolated
- **Cache Security**: Slot-to-address mappings cached in `~/.cache/hardhat/yubikey/accounts.json`

## Troubleshooting

### YubiKey Not Detected

```bash
# Check if pcscd is running
ps aux | grep pcscd

# Test YubiKey detection
pcsc_scan

# Check YubiKey status
ykman list
```

### secp256k1 Not Supported

Ensure your YubiKey firmware is 5.2.3 or later:

```bash
ykman openpgp info
# Look for: OpenPGP version: 3.4
# Supported curves should include: secp256k1
```

### PIN Required Error

The OpenPGP card requires PIN entry. Verify your PIN:

```bash
gpg --card-status
# This will prompt for PIN
```

### Wrong Signature Error

- Verify the address matches the public key in the specified slot
- Check that you're using secp256k1 (not secp256r1 or other curves)
- Ensure the key was generated or imported correctly

## Comparison to Ledger Plugin

| Feature | Ledger Plugin | YubiKey Plugin |
|---------|--------------|----------------|
| Hardware | Ledger devices | YubiKey 5.2.3+ |
| Protocol | Ledger proprietary | OpenPGP card |
| Key Derivation | BIP44 HD wallet paths | Fixed key slots |
| Curve Support | Native secp256k1 | OpenPGP secp256k1 |
| Setup Complexity | Low | Medium |
| Multi-purpose | Crypto-specific | General PKI + crypto |

## API Reference

### Provider Methods

The plugin wraps Hardhat's provider with `YubikeyProvider`:

```typescript
// Check connection
const isConnected = provider.connection.isConnected();

// Get YubiKey-controlled accounts
const accounts = await provider.request({ method: "eth_accounts" });

// Sign transaction
const txHash = await provider.request({
  method: "eth_sendTransaction",
  params: [{ from: "0x...", to: "0x...", value: "0x..." }],
});
```

## Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Lint
npm run lint
```

## License

MIT

## Contributing

Contributions welcome! Please open an issue or PR.

## Credits

- Based on `@l8d/hardhat-ledger`
- Uses [pcsclite](https://www.npmjs.com/package/pcsclite) for PC/SC communication
- Uses [@noble/curves](https://www.npmjs.com/package/@noble/curves) for secp256k1 operations

## Support

- Issues: [GitHub Issues](https://github.com/mezosphere/hardhat-yubikey/issues)
- Yubico Docs: [YubiKey OpenPGP](https://developers.yubico.com/PGP/)
- Hardhat Docs: [Hardhat Plugins](https://hardhat.org/hardhat-runner/docs/advanced/building-plugins)