| Crates.io | rustywallet-signer |
| lib.rs | rustywallet-signer |
| version | 0.2.0 |
| created_at | 2025-12-31 15:42:34.750825+00 |
| updated_at | 2026-01-03 05:31:40.891909+00 |
| description | ECDSA and Schnorr message signing and verification for Bitcoin and Ethereum |
| homepage | |
| repository | https://github.com/nirvagold/rustywallet |
| max_upload_size | |
| id | 2014905 |
| size | 85,771 |
ECDSA and Schnorr message signing and verification for Bitcoin and Ethereum using secp256k1.
Add this to your Cargo.toml:
[dependencies]
rustywallet-signer = "0.2"
rustywallet-keys = "0.1"
use rustywallet_keys::private_key::PrivateKey;
use rustywallet_signer::prelude::*;
use sha2::{Sha256, Digest};
// Generate a private key
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
// Sign a message hash
let message = b"Hello, World!";
let hash: [u8; 32] = Sha256::digest(message).into();
let signature = sign(&private_key, &hash)?;
// Verify the signature
assert!(verify(&public_key, &hash, &signature));
use rustywallet_keys::private_key::PrivateKey;
use rustywallet_signer::schnorr::{SchnorrSigner, SchnorrVerifier};
use sha2::{Sha256, Digest};
// Generate a private key
let private_key = PrivateKey::random();
// Sign a message hash with Schnorr
let message = b"Hello, Taproot!";
let hash: [u8; 32] = Sha256::digest(message).into();
let signature = private_key.sign_schnorr(&hash)?;
// Get x-only public key and verify
let xonly_pubkey = private_key.x_only_public_key();
assert!(xonly_pubkey.verify_schnorr(&signature, &hash));
Schnorr signatures are used in Bitcoin's Taproot upgrade and provide several advantages:
use rustywallet_keys::private_key::PrivateKey;
use rustywallet_signer::schnorr::{SchnorrSigner, SchnorrVerifier};
use sha2::{Sha256, Digest};
let key = PrivateKey::random();
let hash: [u8; 32] = Sha256::digest(b"message").into();
// Sign using the trait
let signature = key.sign_schnorr(&hash)?;
// Sign with auxiliary randomness for extra security
let aux_rand = [0x42u8; 32];
let signature_with_aux = key.sign_schnorr_with_aux(&hash, &aux_rand)?;
// Verify using the trait
let xonly = key.x_only_public_key();
assert!(xonly.verify_schnorr(&signature, &hash));
use rustywallet_keys::private_key::PrivateKey;
use rustywallet_signer::schnorr::{sign_schnorr, verify_schnorr, SchnorrSigner};
use sha2::{Sha256, Digest};
let key = PrivateKey::random();
let hash: [u8; 32] = Sha256::digest(b"message").into();
// Sign using convenience function
let signature = sign_schnorr(&key, &hash)?;
// Verify using convenience function
let xonly = key.x_only_public_key();
assert!(verify_schnorr(&xonly, &signature, &hash));
Sign messages compatible with Bitcoin Core's signmessage RPC:
use rustywallet_keys::private_key::PrivateKey;
use rustywallet_signer::bitcoin::{sign_bitcoin_message, verify_bitcoin_message};
let private_key = PrivateKey::random();
let message = "Hello Bitcoin!";
// Sign the message
let signature = sign_bitcoin_message(&private_key, message)?;
println!("Signature: {}", signature);
// Verify with Bitcoin address
let address = private_key.to_bitcoin_address();
assert!(verify_bitcoin_message(&address, message, &signature)?);
Sign messages compatible with MetaMask and web3.js personal_sign:
use rustywallet_keys::private_key::PrivateKey;
use rustywallet_signer::ethereum::{
sign_ethereum_message, verify_ethereum_message,
public_key_to_address, format_address
};
let private_key = PrivateKey::random();
let message = b"Hello Ethereum!";
// Sign the message
let signature = sign_ethereum_message(&private_key, message)?;
println!("Signature: {}", signature.to_ethereum_hex());
// Get Ethereum address
let address = public_key_to_address(&private_key.public_key());
println!("Address: {}", format_address(&address));
// Verify the signature
assert!(verify_ethereum_message(&address, message, &signature)?);
use rustywallet_signer::{sign, verify};
use sha2::{Sha256, Digest};
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let hash: [u8; 32] = Sha256::digest(b"message").into();
let signature = sign(&private_key, &hash)?;
assert!(verify(&public_key, &hash, &signature));
use rustywallet_signer::bitcoin::verify_bitcoin_message;
let address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
let message = "Hello Bitcoin!";
let signature = "base64_signature_here";
let is_valid = verify_bitcoin_message(address, message, signature)?;
use rustywallet_signer::ethereum::verify_ethereum_message;
let address = [0u8; 20]; // 20-byte Ethereum address
let message = b"Hello Ethereum!";
let signature_hex = "0x1234..."; // 65-byte hex signature
let signature = EthereumSignature::from_hex(signature_hex)?;
let is_valid = verify_ethereum_message(&address, message, &signature)?;
Extract public keys from signatures without prior knowledge:
use rustywallet_signer::{sign_recoverable, recover_public_key};
use sha2::{Sha256, Digest};
let private_key = PrivateKey::random();
let original_pubkey = private_key.public_key();
let hash: [u8; 32] = Sha256::digest(b"recoverable message").into();
// Create recoverable signature
let recoverable_sig = sign_recoverable(&private_key, &hash)?;
// Recover the public key
let recovered_pubkey = recover_public_key(&recoverable_sig, &hash)?;
assert_eq!(original_pubkey.to_compressed(), recovered_pubkey.to_compressed());
use rustywallet_signer::ethereum::{sign_ethereum_message, recover_ethereum_address};
let private_key = PrivateKey::random();
let message = b"Recover my address";
let signature = sign_ethereum_message(&private_key, message)?;
let recovered_address = recover_ethereum_address(&signature, message)?;
let expected_address = public_key_to_address(&private_key.public_key());
assert_eq!(recovered_address, expected_address);
// Sign a 32-byte hash with ECDSA
pub fn sign(private_key: &PrivateKey, message_hash: &[u8; 32]) -> Result<Signature>;
// Sign with recovery information
pub fn sign_recoverable(private_key: &PrivateKey, message_hash: &[u8; 32]) -> Result<RecoverableSignature>;
// Verify a signature against a public key
pub fn verify(public_key: &PublicKey, message_hash: &[u8; 32], signature: &Signature) -> bool;
// Recover public key from signature
pub fn recover_public_key(signature: &RecoverableSignature, message_hash: &[u8; 32]) -> Result<PublicKey>;
// SchnorrSigner trait - implemented for PrivateKey
pub trait SchnorrSigner {
fn sign_schnorr(&self, message_hash: &[u8; 32]) -> Result<SchnorrSignature>;
fn sign_schnorr_with_aux(&self, message_hash: &[u8; 32], aux_rand: &[u8; 32]) -> Result<SchnorrSignature>;
fn x_only_public_key(&self) -> XOnlyPublicKey;
}
// SchnorrVerifier trait - implemented for XOnlyPublicKey
pub trait SchnorrVerifier {
fn verify_schnorr(&self, signature: &SchnorrSignature, message_hash: &[u8; 32]) -> bool;
}
// Convenience functions
pub fn sign_schnorr(private_key: &PrivateKey, message_hash: &[u8; 32]) -> Result<SchnorrSignature>;
pub fn verify_schnorr(pubkey: &XOnlyPublicKey, signature: &SchnorrSignature, message_hash: &[u8; 32]) -> bool;
// Sign a Bitcoin message (BIP-137)
pub fn sign_bitcoin_message(private_key: &PrivateKey, message: &str) -> Result<String>;
// Verify a Bitcoin message signature
pub fn verify_bitcoin_message(address: &str, message: &str, signature: &str) -> Result<bool>;
// Create Bitcoin message hash
pub fn bitcoin_message_hash(message: &str) -> [u8; 32];
// Sign an Ethereum personal message (EIP-191)
pub fn sign_ethereum_message(private_key: &PrivateKey, message: &[u8]) -> Result<EthereumSignature>;
// Verify an Ethereum message signature
pub fn verify_ethereum_message(address: &[u8; 20], message: &[u8], signature: &EthereumSignature) -> Result<bool>;
// Recover address from signature
pub fn recover_ethereum_address(signature: &EthereumSignature, message: &[u8]) -> Result<[u8; 20]>;
// Convert public key to Ethereum address
pub fn public_key_to_address(public_key: &PublicKey) -> [u8; 20];
// Format address with EIP-55 checksum
pub fn format_address(address: &[u8; 20]) -> String;
// Create Ethereum message hash
pub fn ethereum_message_hash(message: &[u8]) -> [u8; 32];
// Standard ECDSA signature (64 bytes: r + s)
pub struct Signature { /* ... */ }
// Recoverable signature (65 bytes: r + s + recovery_id)
pub struct RecoverableSignature { /* ... */ }
// Ethereum-specific signature with v, r, s components
pub struct EthereumSignature { /* ... */ }
// BIP340 Schnorr signature (64 bytes)
pub struct SchnorrSignature { /* ... */ }
// X-only public key for Schnorr (32 bytes)
pub struct XOnlyPublicKey { /* ... */ }
All signing operations return Result<T, SignerError>:
use rustywallet_signer::{SignerError, sign};
match sign(&private_key, &hash) {
Ok(signature) => println!("Signed successfully"),
Err(SignerError::InvalidPrivateKey) => eprintln!("Invalid private key"),
Err(SignerError::SigningFailed) => eprintln!("Signing operation failed"),
Err(e) => eprintln!("Other error: {}", e),
}
secp256k1 crateSee the examples/ directory for complete working examples:
basic_signing.rs - Basic ECDSA signing and verificationbitcoin_messages.rs - Bitcoin message signing workflowethereum_messages.rs - Ethereum personal_sign implementationsignature_recovery.rs - Public key recovery examplesContributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.