YubiKey EVM Signer

Contents
Legacy Hardware Wallet Signing stuff is so hot right now
Legacy Hardware Wallet Signing stuff is so hot right now

Announcing YubiKey EVM Signer: an Airgapped Hardware Wallet using your YubiKey natively in EVM chains. It is comprised of three published libraries:

The code can be found in GitHub at storopoli/yubikey-evm-signer.

secp256r1

If you use Bitcoin or Ethereum, you probably are familiar with the elliptic curve secp256k1. It is the curve that is used to sign transactions and verify signatures. The “k” in secp256k1 stands for “Koblitz” which ia a family of curves that are optimized for fast scalar multiplication.

secp256k1 has a sister curve called secp256r1 (change the “k” to an “r”) that was proposed in the same Standards for Efficient Cryptography (SEC) document (Certicom Research, http://www.secg.org/sec2-v2.pdf), but it was never adopted by both ecosystems. However, secp256r1, also known as P-256, is a very popular curve in the secure hardware world. It is used by YubiKeys, Apple Secure Enclave, and Android Keystore, for example.

The “r” in secp256r1 stands for “random”, since the curve is supposedly from random numbers, however, it is impossible to prove that’s really the case1.

There is a revival of secp256r1 in the cryptographic engineering world recently. It is a curve that has been around for a long time and is well understood; additionally, it has tons of current and legacy hardware support. For example, Google recently announced mobile IDs that produces zero-knowledge proofs using legacy hardware. The new Google Wallet uses the secp256r1 curve for anonymous credential verification, see IACR 2024/2010. It has even an IETF RFC draft. This is because they need to support not only the latest hardware, but also legacy hardware as a restriction from the European Union2.

With the new Fusaka upgrade, we have the new EIP-7951, that brings native support for secp256r1 signatures to the EVM.

The Fusaka Upgrade and EIP-7951

EIP-7951 introduces the P256VERIFY precompile enabling native ECDSA signature verification over the secp256r1 (P-256/NIST P-256) curve directly on the EVM. This seemingly small addition unlocks a massive opportunity: native support for signatures generated by modern and legacy secure hardware.

Why secp256r1 Matters for Hardware Wallets

The significance of native secp256r1 support cannot be overstated. This curve is ubiquitous in secure hardware:

  • Apple Secure Enclave: Every modern iPhone and Mac
  • Android Keystore: Hardware-backed key storage on Android devices
  • Hardware Security Modules (HSMs): Enterprise-grade key management
  • Trusted Execution Environments (TEEs): Intel SGX, ARM TrustZone
  • FIDO2/WebAuthn authenticators: Including YubiKeys, Titan keys, and passkeys
  • Smart Cards: PIV-compliant devices used in government and enterprise

This means billions of devices worldwide can now participate in Ethereum’s security model without any curve conversion or wrapper contracts.

YubiKeys: Air-Gapped Security for Everyone

YubiKeys deserve special attention in this landscape. These small, affordable USB devices provide:

True Air-Gapped Key Storage:

  • Private keys are generated inside the YubiKey’s secure element.
  • Keys never leave the device—not during generation, not during signing, not ever.
  • Even with physical access to the device, extracting keys is computationally infeasible.

Battle-Tested Security:

  • FIPS 140-2 certified (YubiKey 5 FIPS series).
  • Used by governments, enterprises, and security-conscious individuals worldwide.
  • No batteries, no wireless connectivity—nothing to hack remotely.

Accessible Price Point:

  • Starting around $25-50 USD, hardware security is accessible to everyone
  • No recurring fees, no cloud dependencies

PIN Protection:

  • Even if your YubiKey is physically stolen, the attacker still needs your PIN
  • After multiple failed attempts, the device locks permanently

For Ethereum users, this means you can sign transactions with the same confidence level as a bank’s HSM, using a device that fits on your keychain.

The Core Library: yubikey-evm-signer-core

The heart of the project is yubikey-evm-signer-core, a platform-agnostic Rust library that handles all cryptographic operations. It is built on top of the p256 crate, which is a pure Rust implementation of the secp256r1 curve by the amazing folks at RustCrypto.

Full Test Vector Compliance

The library passes all 781 EIP-7951 test vectors sourced from the Ethereum Foundation’s in EIP-7951. These vectors cover:

  • Valid signatures with various edge cases
  • Invalid signatures (wrong hash, corrupted r/s values)
  • Signature malleability attacks
  • Boundary conditions and special values

Comprehensive Transaction Support

use yubikey_evm_signer_core::{Transaction, Eip1559Transaction, Address};
use alloy_primitives::U256;

// Create an EIP-1559 transaction
let tx = Transaction::Eip1559(Eip1559Transaction {
    chain_id: 1,
    nonce: 0,
    max_priority_fee_per_gas: U256::from(1_000_000_000u64),
    max_fee_per_gas: U256::from(100_000_000_000u64),
    gas_limit: 21000,
    to: Some(Address::zero()),
    value: U256::from(1_000_000_000_000_000_000u128),
    data: vec![],
    access_list: vec![],
});

let hash = tx.signing_hash();

The library supports:

  • EIP-155 Legacy Transactions: With chain ID replay protection
  • EIP-1559 (Type 2) Transactions: Priority fees and max fees
  • EIP-712 Typed Data: Human-readable structured data signing for DApps
  • EIP-191 Personal Messages: Standard message signing

Transport Abstraction

The core library defines a Transport trait that abstracts the communication layer. This allows the same cryptographic code to work across:

  • CCID/PC/SC: Native smart card interface on macOS, Linux, and Windows
  • WebUSB: Browser-based communication (Chromium browsers)

Security Features

  • Low-S Signature Normalization: All signatures are normalized to prevent malleability attacks (BIP-62, EIP-2)
  • Proper Address Derivation: Keccak-256 hash of uncompressed public key with EIP-55 checksum support
  • Recovery Parameter Calculation: Correct V-value computation for on-chain verification

Browser Support: yubikey-evm-signer-wasm

For web applications, yubikey-evm-signer-wasm provides WebAssembly bindings that expose a clean JavaScript API:

import init, { YubiKeyDevice } from 'yubikey-evm-signer';

await init();

// Connect to YubiKey (requires user gesture)
const device = await YubiKeyDevice.connect();

// Generate a new key
const address = await device.generateKey("123456");

// Sign a transaction
const signature = await device.signTransaction("123456", JSON.stringify({
    type: "eip1559",
    chain_id: 1,
    nonce: 0,
    max_priority_fee_per_gas: "1000000000",
    max_fee_per_gas: "20000000000",
    gas_limit: 21000,
    to: "0x...",
    value: "1000000000000000000",
    input: "0x"
}));

await device.disconnect();

Mostly importantly, all cryptographic operations are deferred via WebAssembly to the much more secure Rust library: yubikey-evm-signer-core.

The WASM crate uses WebUSB to communicate directly with the YubiKey from the browser—no browser extensions, no intermediary servers. Just plug in your YubiKey and sign.

Browser Support: Chrome, Edge, Opera, Brave (Chromium-based browsers with WebUSB support). Note: macOS has kernel driver conflicts that prevent WebUSB from working; use the native CLI instead.

NPM Package: yubikey-evm-signer

For the smoothest JavaScript/TypeScript integration, install via npm:

npm install yubikey-evm-signer

The yubikey-evm-signer package bundles the WASM module with TypeScript type definitions, ready for use in modern web applications.

Live Demo: Try it yourself at storopoli.github.io/yubikey-evm-signer

The demo showcases:

  • Device connection and key generation
  • Address derivation from P-256 public keys
  • Transaction signing (legacy and EIP-1559)
  • EIP-712 typed data signing
  • EIP-191 personal message signing

Conclusion

I have very strong opinions on JavaScript security. You can check my soydev post or my JavaScript Antichrist rant. That’s why this library is written in Rust and compiled to WebAssembly. JavaScript is not a secure language and should not be used for anything that requires security. I’m fine with it being an intern for serious languages, and just making things flashy and clicky on your browser. (That’s what it was designed for anyways.)

If are in need of a Rust-first native library to support secp256r1 signatures in EVM chains, this is the library for you.

The project is open source under MIT/Apache-2.0 dual license. I welcome contributions, feedback, and bug reports.

  1. 1See this Cryptography StackExchange discussion.
  2. 2The Google Wallet engineers and cryptographers discuss these EU restrictions in this Podcast episode.