npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@l8d/hardhat-yubikey

v1.0.7

Published

Hardhat v3 plugin for YubiKey hardware wallet using OpenPGP card

Readme

@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

npm install @l8d/hardhat-yubikey

System Dependencies

macOS

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

Linux

# 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

YubiKey Setup

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

1. Install YubiKey Manager

# macOS
brew install ykman

# Linux
pip install yubikey-manager

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

2. Generate secp256k1 Keys

# 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:

# 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:

# 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:

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:

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:

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

Deploying Contracts

// 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

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

# 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:

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:

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:

// 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

# 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 for PC/SC communication
  • Uses @noble/curves for secp256k1 operations

Support