sal-vault 0.1.0

SAL Vault - Cryptographic functionality including key management, digital signatures, symmetric encryption, Ethereum wallets, and encrypted key-value store
Documentation
//! Ethereum wallet implementation.

use ethers::prelude::*;
use ethers::signers::{LocalWallet, Signer, Wallet};
use ethers::utils::hex;
use k256::ecdsa::SigningKey;
use sha2::{Digest, Sha256};
use std::str::FromStr;

use super::networks::NetworkConfig;
use crate::error::CryptoError;
use crate::keyspace::KeyPair;

/// An Ethereum wallet derived from a keypair.
#[derive(Debug, Clone)]
pub struct EthereumWallet {
    pub address: Address,
    pub wallet: Wallet<SigningKey>,
    pub network: NetworkConfig,
}

impl EthereumWallet {
    /// Creates a new Ethereum wallet from a keypair for a specific network.
    pub fn from_keypair(
        keypair: &crate::keyspace::keypair_types::KeyPair,
        network: NetworkConfig,
    ) -> Result<Self, CryptoError> {
        // Get the private key bytes from the keypair
        let private_key_bytes = keypair.signing_key.to_bytes();

        // Convert to a hex string (without 0x prefix)
        let private_key_hex = hex::encode(private_key_bytes);

        // Create an Ethereum wallet from the private key
        let wallet = LocalWallet::from_str(&private_key_hex)
            .map_err(|_e| CryptoError::InvalidKeyLength)?
            .with_chain_id(network.chain_id);

        // Get the Ethereum address
        let address = wallet.address();

        Ok(EthereumWallet {
            address,
            wallet,
            network,
        })
    }

    /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network.
    pub fn from_name_and_keypair(
        name: &str,
        keypair: &KeyPair,
        network: NetworkConfig,
    ) -> Result<Self, CryptoError> {
        // Get the private key bytes from the keypair
        let private_key_bytes = keypair.signing_key.to_bytes();

        // Create a deterministic seed by combining name and private key
        let mut hasher = Sha256::default();
        hasher.update(name.as_bytes());
        hasher.update(&private_key_bytes);
        let seed = hasher.finalize();

        // Use the seed as a private key
        let private_key_hex = hex::encode(seed);

        // Create an Ethereum wallet from the derived private key
        let wallet = LocalWallet::from_str(&private_key_hex)
            .map_err(|_e| CryptoError::InvalidKeyLength)?
            .with_chain_id(network.chain_id);

        // Get the Ethereum address
        let address = wallet.address();

        Ok(EthereumWallet {
            address,
            wallet,
            network,
        })
    }

    /// Creates a new Ethereum wallet from a private key for a specific network.
    pub fn from_private_key(
        private_key: &str,
        network: NetworkConfig,
    ) -> Result<Self, CryptoError> {
        // Remove 0x prefix if present
        let private_key_clean = private_key.trim_start_matches("0x");

        // Create an Ethereum wallet from the private key
        let wallet = LocalWallet::from_str(private_key_clean)
            .map_err(|_e| CryptoError::InvalidKeyLength)?
            .with_chain_id(network.chain_id);

        // Get the Ethereum address
        let address = wallet.address();

        Ok(EthereumWallet {
            address,
            wallet,
            network,
        })
    }

    /// Gets the Ethereum address as a string.
    pub fn address_string(&self) -> String {
        format!("{:?}", self.address)
    }

    /// Signs a message with the Ethereum wallet.
    pub async fn sign_message(&self, message: &[u8]) -> Result<String, CryptoError> {
        let signature = self
            .wallet
            .sign_message(message)
            .await
            .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;

        Ok(signature.to_string())
    }

    /// Gets the private key as a hex string.
    pub fn private_key_hex(&self) -> String {
        let bytes = self.wallet.signer().to_bytes();
        hex::encode(bytes)
    }
}