#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
use bitcoin::{Address, NetworkKind, PrivateKey, key::CompressedPublicKey};
use zeroize::Zeroizing;
use crate::address::create_address;
use crate::{AddressType, Error, Network};
#[derive(Debug)]
pub struct StandardWallet {
private_key: PrivateKey,
public_key: CompressedPublicKey,
address: Address,
network: Network,
address_type: AddressType,
}
impl StandardWallet {
#[cfg(feature = "rand")]
pub fn generate(network: Network, address_type: AddressType) -> Result<Self, Error> {
let secp = bitcoin::secp256k1::Secp256k1::new();
let (secret_key, _) = secp.generate_keypair(&mut bitcoin::secp256k1::rand::thread_rng());
let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
.expect("valid private key always produces valid public key");
let address = create_address(&public_key, network, address_type);
Ok(Self {
private_key,
public_key,
address,
network,
address_type,
})
}
pub fn from_wif(wif: &str, address_type: AddressType) -> Result<Self, Error> {
let private_key: PrivateKey = wif.parse().map_err(|_| Error::InvalidWif)?;
let network = if private_key.network == NetworkKind::Main {
Network::Mainnet
} else {
Network::Testnet
};
let secp = bitcoin::secp256k1::Secp256k1::new();
let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
.expect("valid private key always produces valid public key");
let address = create_address(&public_key, network, address_type);
Ok(Self {
private_key,
public_key,
address,
network,
address_type,
})
}
pub fn from_hex(
hex_str: &str,
network: Network,
address_type: AddressType,
) -> Result<Self, Error> {
let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidHex)?;
if bytes.len() != 32 {
return Err(Error::InvalidPrivateKey);
}
let secret_key = bitcoin::secp256k1::SecretKey::from_slice(&bytes)
.map_err(|_| Error::InvalidPrivateKey)?;
let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
let secp = bitcoin::secp256k1::Secp256k1::new();
let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
.expect("valid private key always produces valid public key");
let address = create_address(&public_key, network, address_type);
Ok(Self {
private_key,
public_key,
address,
network,
address_type,
})
}
#[inline]
#[must_use]
pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
Zeroizing::new(self.private_key.inner.secret_bytes())
}
#[inline]
#[must_use]
pub fn secret_hex(&self) -> Zeroizing<String> {
Zeroizing::new(hex::encode(self.private_key.inner.secret_bytes()))
}
#[inline]
#[must_use]
pub fn to_wif(&self) -> Zeroizing<String> {
Zeroizing::new(self.private_key.to_wif())
}
#[inline]
#[must_use]
pub fn pubkey_hex(&self) -> String {
self.public_key.to_string()
}
#[inline]
#[must_use]
pub fn address(&self) -> String {
self.address.to_string()
}
#[must_use]
pub const fn network(&self) -> Network {
self.network
}
#[must_use]
pub const fn address_type(&self) -> AddressType {
self.address_type
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "rand")]
#[test]
fn test_generate_mainnet_p2wpkh() {
let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2wpkh).unwrap();
assert!(wallet.address().starts_with("bc1q"));
assert_eq!(wallet.network(), Network::Mainnet);
}
#[cfg(feature = "rand")]
#[test]
fn test_generate_mainnet_p2pkh() {
let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2pkh).unwrap();
assert!(wallet.address().starts_with('1'));
}
#[cfg(feature = "rand")]
#[test]
fn test_generate_mainnet_p2sh() {
let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2shP2wpkh).unwrap();
assert!(wallet.address().starts_with('3'));
}
#[cfg(feature = "rand")]
#[test]
fn test_generate_mainnet_p2tr() {
let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2tr).unwrap();
assert!(wallet.address().starts_with("bc1p"));
}
#[cfg(feature = "rand")]
#[test]
fn test_generate_testnet() {
let wallet = StandardWallet::generate(Network::Testnet, AddressType::P2wpkh).unwrap();
assert!(wallet.address().starts_with("tb1q"));
}
}