yubikey-evm-signer-core

Crates.ioyubikey-evm-signer-core
lib.rsyubikey-evm-signer-core
version0.0.1
created_at2026-01-04 12:47:50.585872+00
updated_at2026-01-04 12:47:50.585872+00
descriptionYubiKey EVM Signer Core Cryptography Library
homepage
repositoryhttps://github.com/storopoli/yubikey-evm-signer
max_upload_size
id2021853
size667,694
Jose Storopoli (storopoli)

documentation

README

YubiKey EVM Signer

License: MIT License: Apache-2.0 ci

Crate crates.io docs.rs
yubikey-evm-signer-core crates.io docs.rs
yubikey-evm-signer-wasm crates.io docs.rs
Package npm
yubikey-evm-signer npm

Sign Ethereum transactions using a YubiKey's PIV applet with secp256r1 (P-256) ECDSA. Leverages EIP-7951 for native secp256r1 signature verification on the EVM. Passes all 781 EIP-7951 test vectors.

Features

  • EIP-7951 Compatible: Native secp256r1 signatures without curve conversion
  • Transaction Support: EIP-155 legacy and EIP-1559 transactions
  • EIP-712: Typed structured data signing
  • EIP-191: Personal message signing
  • Hardware Security: Private keys never leave the YubiKey
  • Cross-Platform: Native (CCID) and browser (WebUSB) support

Usage

Rust

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![],
});

// Get the hash to sign
let hash = tx.signing_hash();

JavaScript/TypeScript (Browser)

Live Demo: storopoli.github.io/yubikey-evm-signer

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();

Note: WebUSB is only supported in Chromium-based browsers (Chrome, Edge, Opera, Brave) and requires HTTPS. WebUSB does not work on macOS due to kernel driver conflicts. Use the native CLI instead.

Native CLI (macOS/Linux/Windows)

For native access via PC/SC (smart card interface), use the CLI example:

# List connected YubiKeys
just cli-list

# Generate a new P-256 key in slot 9a
just cli-generate

# Create a certificate (required for address retrieval)
ykman piv keys export 9a /tmp/pubkey.pem
ykman piv certificates generate -P 123456 \
  -m 010203040506070801020304050607080102030405060708 \
  -s "CN=YubiKey EVM Signer" 9a /tmp/pubkey.pem

# Get Ethereum address
just cli-address

# Sign a sample EIP-1559 transaction
just cli-sign-tx

# Sign a custom 32-byte hash
just cli-sign 0x0123456789abcdef...

Or run directly with cargo:

cargo run --example yubikey-cli -p yubikey-evm-signer-core --features pcsc -- <command>

Platform Requirements

Platform Requirements
macOS Works out of the box (built-in smart card daemon)
Linux pcscd service running, may need udev rules
Windows Works out of the box (built-in smart card service)

Default PIV Credentials

Credential Default Value
PIN 123456
PUK 12345678
Management Key 010203040506070801020304050607080102030405060708

Security: Change default credentials for production use with ykman piv access.

Contributing

Contributions are generally welcome. If you intend to make larger changes please discuss them in an issue before opening a PR to avoid duplicate work and architectural mismatches.

For more information please see CONTRIBUTING.md.

License

This work is dual-licensed under MIT and Apache 2.0. You can choose between one of them if you use this work.

Commit count: 0

cargo fmt