This commit is contained in:
2025-05-13 08:02:23 +03:00
parent 393c4270d4
commit a4438d63e0
13 changed files with 48 additions and 19 deletions

View File

@@ -0,0 +1,271 @@
# Hero Vault Keypair Module
The Keypair module provides functionality for creating, managing, and using ECDSA keypairs for digital signatures and other cryptographic operations.
## Module Structure
The Keypair module is organized into:
- `keypair_types.rs` - Defines the KeyPair and related types.
- `session_manager.rs` - Implements the core logic for managing keypairs and key spaces.
- `mod.rs` - Module exports and public interface.
## Key Types
### KeyPair
The `KeyPair` type represents an ECDSA keypair used for digital signatures and other cryptographic operations.
```rust
pub struct KeyPair {
// Private fields
// ...
}
impl KeyPair {
// Create a new random keypair
pub fn new() -> Result<Self, CryptoError>;
// Create a keypair from an existing private key
pub fn from_private_key(private_key: &[u8]) -> Result<Self, CryptoError>;
// Get the public key
pub fn public_key(&self) -> &[u8];
// Sign a message
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError>;
// Verify a signature
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, CryptoError>;
// Derive an Ethereum address from the public key
pub fn to_ethereum_address(&self) -> Result<String, CryptoError>;
// Export the private key (should be used with caution)
pub fn export_private_key(&self) -> Result<Vec<u8>, CryptoError>;
}
```
### KeySpace
The `KeySpace` type represents a secure container for multiple keypairs, which can be encrypted and stored on disk.
```rust
pub struct KeySpace {
// Private fields
// ...
}
impl KeySpace {
// Create a new key space
pub fn new(name: &str, password: &str) -> Result<Self, CryptoError>;
// Load a key space from disk
pub fn load(name: &str, password: &str) -> Result<Self, CryptoError>;
// Save the key space to disk
pub fn save(&self) -> Result<(), CryptoError>;
// Create a new keypair in the key space
pub fn create_keypair(&mut self, name: &str, password: &str) -> Result<&KeyPair, CryptoError>;
// Select a keypair for use
pub fn select_keypair(&mut self, name: &str) -> Result<&KeyPair, CryptoError>;
// Get the currently selected keypair
pub fn current_keypair(&self) -> Result<&KeyPair, CryptoError>;
// List all keypairs in the key space
pub fn list_keypairs(&self) -> Result<Vec<String>, CryptoError>;
// Get a keypair by name
pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError>;
// Remove a keypair from the key space
pub fn remove_keypair(&mut self, name: &str) -> Result<(), CryptoError>;
// Rename a keypair
pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), CryptoError>;
// Get the name of the key space
pub fn name(&self) -> &str;
}
```
## Key Features
### Key Space Management
The module provides functionality for creating, loading, and managing key spaces:
```rust
// Create a new key space
let mut space = KeySpace::new("my_space", "secure_password")?;
// Save the key space to disk
space.save()?;
// Load a key space from disk
let mut loaded_space = KeySpace::load("my_space", "secure_password")?;
```
### Keypair Management
The module provides functionality for creating, selecting, and using keypairs:
```rust
use crate::vault::keypair::{KeySpace, KeyPair};
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
fn demonstrate_keypair_management() -> Result<(), CryptoError> {
// Create a new key space
let mut space = KeySpace::new("my_space", "secure_password")?;
// Create a new keypair in the key space
let keypair = space.create_keypair("my_keypair", "secure_password")?;
println!("Created keypair: {}", keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
// Select a keypair for use
space.select_keypair("my_keypair")?;
println!("Selected keypair: {}", space.current_keypair()?.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
// List all keypairs in the key space
let keypairs = space.list_keypairs()?;
println!("Keypairs in space: {:?}", keypairs);
// Get a keypair by name
let retrieved_keypair = space.get_keypair("my_keypair")?;
println!("Retrieved keypair: {}", retrieved_keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
// Rename a keypair
space.rename_keypair("my_keypair", "new_name")?;
println!("Renamed keypair to new_name");
let keypairs_after_rename = space.list_keypairs()?;
println!("Keypairs in space after rename: {:?}", keypairs_after_rename);
// Remove a keypair from the key space
space.remove_keypair("new_name")?;
println!("Removed keypair new_name");
let keypairs_after_remove = space.list_keypairs()?;
println!("Keypairs in space after removal: {:?}", keypairs_after_remove);
Ok(())
}
```
### Digital Signatures
The module provides functionality for signing and verifying messages using ECDSA:
```rust
use crate::vault::keypair::KeySpace;
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
fn demonstrate_digital_signatures() -> Result<(), CryptoError> {
// Assuming a key space and selected keypair exist
// let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space
let mut space = KeySpace::new("temp_space_for_demo", "password")?; // Or create a new one for demo
space.create_keypair("my_signing_key", "key_password")?;
space.select_keypair("my_signing_key")?;
// Sign a message using the selected keypair
let keypair = space.current_keypair()?;
let message = "This is a message to sign".as_bytes();
let signature = keypair.sign(message)?;
println!("Message signed. Signature: {:?}", signature);
// Verify a signature
let is_valid = keypair.verify(message, &signature)?;
println!("Signature valid: {}", is_valid);
// Example of invalid signature verification
let invalid_signature = vec![0u8; signature.len()]; // A dummy invalid signature
let is_valid_invalid = keypair.verify(message, &invalid_signature)?;
println!("Invalid signature valid: {}", is_valid_invalid);
Ok(())
}
```
### Ethereum Address Derivation
The module provides functionality for deriving Ethereum addresses from keypairs:
```rust
use crate::vault::keypair::KeySpace;
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
fn demonstrate_ethereum_address_derivation() -> Result<(), CryptoError> {
// Assuming a key space and selected keypair exist
// let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space
let mut space = KeySpace::new("temp_space_for_eth_demo", "password")?; // Or create a new one for demo
space.create_keypair("my_eth_key", "key_password")?;
space.select_keypair("my_eth_key")?;
// Derive an Ethereum address from a keypair
let keypair = space.current_keypair()?;
let address = keypair.to_ethereum_address()?;
println!("Derived Ethereum address: {}", address);
Ok(())
}
```
## Including in Your Project
To include the Hero Vault Keypair module in your Rust project, add the following to your `Cargo.toml` file:
```toml
[dependencies]
hero_vault = "0.1.0" # Replace with the actual version
```
Then, you can import and use the module in your Rust code:
```rust
use hero_vault::vault::keypair::{KeySpace, KeyPair};
use hero_vault::vault::error::CryptoError;
```
## Testing
Tests for the Keypair module are included within the source files, likely in `session_manager.rs` or `mod.rs` as inline tests.
To run the tests, navigate to the root directory of the project in your terminal and execute the following command:
```bash
cargo test --lib vault::keypair
```
This command will run all tests specifically within the `vault::keypair` module.
## Security Considerations
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
- Private keys are never stored in plaintext
- The module uses secure random number generation for key creation
- All cryptographic operations use well-established libraries and algorithms
## Error Handling
The module uses the `CryptoError` type for handling errors that can occur during keypair operations:
- `InvalidKeyLength` - Invalid key length
- `SignatureFormatError` - Signature format error
- `KeypairAlreadyExists` - Keypair already exists
- `KeypairNotFound` - Keypair not found
- `NoActiveSpace` - No active key space
- `NoKeypairSelected` - No keypair selected
- `SerializationError` - Serialization error
## Examples
For examples of how to use the Keypair module, see the `examples/hero_vault` directory, particularly:
- `example.rhai` - Basic example demonstrating key management and signing
- `advanced_example.rhai` - Advanced example with error handling
- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
- `load_existing_space.rhai` - Shows how to load a previously created key space

View File

@@ -0,0 +1,312 @@
/// Implementation of keypair functionality.
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
use k256::ecdh::EphemeralSecret;
use rand::rngs::OsRng;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use sha2::{Sha256, Digest};
use crate::vault::symmetric::implementation;
use crate::vault::error::CryptoError;
/// A keypair for signing and verifying messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyPair {
pub name: String,
#[serde(with = "verifying_key_serde")]
pub verifying_key: VerifyingKey,
#[serde(with = "signing_key_serde")]
pub signing_key: SigningKey,
}
// Serialization helpers for VerifyingKey
mod verifying_key_serde {
use super::*;
use serde::{Serializer, Deserializer};
use serde::de::{self, Visitor};
use std::fmt;
pub fn serialize<S>(key: &VerifyingKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.to_sec1_bytes();
// Convert bytes to a Vec<u8> and serialize that instead
serializer.collect_seq(bytes)
}
struct VerifyingKeyVisitor;
impl<'de> Visitor<'de> for VerifyingKeyVisitor {
type Value = VerifyingKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array representing a verifying key")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
VerifyingKey::from_sec1_bytes(v).map_err(|e| {
log::error!("Error deserializing verifying key: {:?}", e);
E::custom(format!("invalid verifying key: {:?}", e))
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
// Collect all bytes from the sequence
let mut bytes = Vec::new();
while let Some(byte) = seq.next_element()? {
bytes.push(byte);
}
VerifyingKey::from_sec1_bytes(&bytes).map_err(|e| {
log::error!("Error deserializing verifying key from seq: {:?}", e);
de::Error::custom(format!("invalid verifying key from seq: {:?}", e))
})
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<VerifyingKey, D::Error>
where
D: Deserializer<'de>,
{
// Try to deserialize as bytes first, then as a sequence
deserializer.deserialize_any(VerifyingKeyVisitor)
}
}
// Serialization helpers for SigningKey
mod signing_key_serde {
use super::*;
use serde::{Serializer, Deserializer};
use serde::de::{self, Visitor};
use std::fmt;
pub fn serialize<S>(key: &SigningKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.to_bytes();
// Convert bytes to a Vec<u8> and serialize that instead
serializer.collect_seq(bytes)
}
struct SigningKeyVisitor;
impl<'de> Visitor<'de> for SigningKeyVisitor {
type Value = SigningKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array representing a signing key")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
SigningKey::from_bytes(v.into()).map_err(|e| {
log::error!("Error deserializing signing key: {:?}", e);
E::custom(format!("invalid signing key: {:?}", e))
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
// Collect all bytes from the sequence
let mut bytes = Vec::new();
while let Some(byte) = seq.next_element()? {
bytes.push(byte);
}
SigningKey::from_bytes(bytes.as_slice().into()).map_err(|e| {
log::error!("Error deserializing signing key from seq: {:?}", e);
de::Error::custom(format!("invalid signing key from seq: {:?}", e))
})
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SigningKey, D::Error>
where
D: Deserializer<'de>,
{
// Try to deserialize as bytes first, then as a sequence
deserializer.deserialize_any(SigningKeyVisitor)
}
}
impl KeyPair {
/// Creates a new keypair with the given name.
pub fn new(name: &str) -> Self {
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
KeyPair {
name: name.to_string(),
verifying_key,
signing_key,
}
}
/// Gets the public key bytes.
pub fn pub_key(&self) -> Vec<u8> {
self.verifying_key.to_sec1_bytes().to_vec()
}
/// Derives a public key from a private key.
pub fn pub_key_from_private(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
let signing_key = SigningKey::from_bytes(private_key.into())
.map_err(|_| CryptoError::InvalidKeyLength)?;
let verifying_key = VerifyingKey::from(&signing_key);
Ok(verifying_key.to_sec1_bytes().to_vec())
}
/// Signs a message.
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
let signature: Signature = self.signing_key.sign(message);
signature.to_bytes().to_vec()
}
/// Verifies a message signature.
pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
match self.verifying_key.verify(message, &signature) {
Ok(_) => Ok(true),
Err(_) => Ok(false), // Verification failed, but operation was successful
}
}
/// Verifies a message signature using only a public key.
pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
match verifying_key.verify(message, &signature) {
Ok(_) => Ok(true),
Err(_) => Ok(false), // Verification failed, but operation was successful
}
}
/// Encrypts a message using the recipient's public key.
/// This implements ECIES (Elliptic Curve Integrated Encryption Scheme):
/// 1. Generate an ephemeral keypair
/// 2. Derive a shared secret using ECDH
/// 3. Derive encryption key from the shared secret
/// 4. Encrypt the message using symmetric encryption
/// 5. Return the ephemeral public key and the ciphertext
pub fn encrypt_asymmetric(&self, recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Parse recipient's public key
let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
// Generate ephemeral keypair
let ephemeral_signing_key = SigningKey::random(&mut OsRng);
let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key);
// Derive shared secret using ECDH
let ephemeral_secret = EphemeralSecret::random(&mut OsRng);
let shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.to_public_key());
// Derive encryption key from the shared secret (e.g., using HKDF or hashing)
// For simplicity, we'll hash the shared secret here
let encryption_key = {
let mut hasher = Sha256::default();
hasher.update(shared_secret.raw_secret_bytes());
hasher.finalize().to_vec()
};
// Encrypt the message using the derived key
let ciphertext = implementation::encrypt_with_key(&encryption_key, message)
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
// Format: ephemeral_public_key || ciphertext
let mut result = ephemeral_public_key.to_encoded_point(false).as_bytes().to_vec();
result.extend_from_slice(&ciphertext);
Ok(result)
}
/// Decrypts a message using the recipient's private key.
/// This is the counterpart to encrypt_asymmetric.
pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
// The first 33 or 65 bytes (depending on compression) are the ephemeral public key
// For simplicity, we'll assume uncompressed keys (65 bytes)
if ciphertext.len() <= 65 {
return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string()));
}
// Extract ephemeral public key and actual ciphertext
let ephemeral_public_key = &ciphertext[..65];
let actual_ciphertext = &ciphertext[65..];
// Parse ephemeral public key
let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
// Derive shared secret using ECDH
let recipient_secret = EphemeralSecret::random(&mut OsRng);
let shared_secret = recipient_secret.diffie_hellman(&sender_key.to_public_key());
// Derive decryption key from the shared secret (using the same method as encryption)
let decryption_key = {
let mut hasher = Sha256::default();
hasher.update(shared_secret.raw_secret_bytes());
hasher.finalize().to_vec()
};
// Decrypt the message using the derived key
implementation::decrypt_with_key(&decryption_key, actual_ciphertext)
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
}
}
/// A collection of keypairs.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KeySpace {
pub name: String,
pub keypairs: HashMap<String, KeyPair>,
}
impl KeySpace {
/// Creates a new key space with the given name.
pub fn new(name: &str) -> Self {
KeySpace {
name: name.to_string(),
keypairs: HashMap::new(),
}
}
/// Adds a new keypair to the space.
pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> {
if self.keypairs.contains_key(name) {
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
}
let keypair = KeyPair::new(name);
self.keypairs.insert(name.to_string(), keypair);
Ok(())
}
/// Gets a keypair by name.
pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> {
self.keypairs.get(name).ok_or(CryptoError::KeypairNotFound(name.to_string()))
}
/// Lists all keypair names in the space.
pub fn list_keypairs(&self) -> Vec<String> {
self.keypairs.keys().cloned().collect()
}
}

18
src/vault/keyspace/mod.rs Normal file
View File

@@ -0,0 +1,18 @@
//! Key pair management functionality
//!
//! This module provides functionality for creating and managing ECDSA key pairs.
pub mod keypair_types;
pub mod session_manager;
// Re-export public types and functions
pub use keypair_types::{KeyPair, KeySpace};
pub use session_manager::{
create_space, set_current_space, get_current_space, clear_session,
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
};
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,174 @@
use once_cell::sync::Lazy;
use std::sync::Mutex;
use crate::vault::error::CryptoError;
use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; // Assuming KeyPair and KeySpace will be in keypair_types.rs
/// Session state for the current key space and selected keypair.
pub struct Session {
pub current_space: Option<KeySpace>,
pub selected_keypair: Option<String>,
}
impl Default for Session {
fn default() -> Self {
Session {
current_space: None,
selected_keypair: None,
}
}
}
/// Global session state.
pub static SESSION: Lazy<Mutex<Session>> = Lazy::new(|| Mutex::new(Session::default()));
// Session management and selected keypair operation functions will be added here
/// Creates a new key space with the given name.
pub fn create_space(name: &str) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
// Create a new space
let space = KeySpace::new(name);
// Set as current space
session.current_space = Some(space);
session.selected_keypair = None;
Ok(())
}
/// Sets the current key space.
pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
session.current_space = Some(space);
session.selected_keypair = None;
Ok(())
}
/// Gets the current key space.
pub fn get_current_space() -> Result<KeySpace, CryptoError> {
let session = SESSION.lock().unwrap();
session
.current_space
.clone()
.ok_or(CryptoError::NoActiveSpace)
}
/// Clears the current session (logout).
pub fn clear_session() {
let mut session = SESSION.lock().unwrap();
session.current_space = None;
session.selected_keypair = None;
}
/// Creates a new keypair in the current space.
pub fn create_keypair(name: &str) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
if let Some(ref mut space) = session.current_space {
if space.keypairs.contains_key(name) {
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
}
let keypair = KeyPair::new(name);
space.keypairs.insert(name.to_string(), keypair);
// Automatically select the new keypair
session.selected_keypair = Some(name.to_string());
Ok(())
} else {
Err(CryptoError::NoActiveSpace)
}
}
/// Selects a keypair for use.
pub fn select_keypair(name: &str) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
if let Some(ref space) = session.current_space {
if !space.keypairs.contains_key(name) {
return Err(CryptoError::KeypairNotFound(name.to_string()));
}
session.selected_keypair = Some(name.to_string());
Ok(())
} else {
Err(CryptoError::NoActiveSpace)
}
}
/// Gets the currently selected keypair.
pub fn get_selected_keypair() -> Result<KeyPair, CryptoError> {
let session = SESSION.lock().unwrap();
if let Some(ref space) = session.current_space {
if let Some(ref keypair_name) = session.selected_keypair {
if let Some(keypair) = space.keypairs.get(keypair_name) {
return Ok(keypair.clone());
}
return Err(CryptoError::KeypairNotFound(keypair_name.clone()));
}
return Err(CryptoError::NoKeypairSelected);
}
Err(CryptoError::NoActiveSpace)
}
/// Lists all keypair names in the current space.
pub fn list_keypairs() -> Result<Vec<String>, CryptoError> {
let session = SESSION.lock().unwrap();
if let Some(ref space) = session.current_space {
Ok(space.keypairs.keys().cloned().collect())
} else {
Err(CryptoError::NoActiveSpace)
}
}
/// Gets the public key of the selected keypair.
pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
Ok(keypair.pub_key())
}
/// Derives a public key from a private key.
pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
KeyPair::pub_key_from_private(private_key)
}
/// Signs a message with the selected keypair.
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
Ok(keypair.sign(message))
}
/// Verifies a message signature with the selected keypair.
pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.verify(message, signature_bytes)
}
/// Verifies a message signature with a public key.
pub fn verify_with_public_key(
public_key: &[u8],
message: &[u8],
signature_bytes: &[u8],
) -> Result<bool, CryptoError> {
KeyPair::verify_with_public_key(public_key, message, signature_bytes)
}
/// Encrypts a message for a recipient using their public key.
pub fn encrypt_asymmetric(
recipient_public_key: &[u8],
message: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.encrypt_asymmetric(recipient_public_key, message)
}
/// Decrypts a message that was encrypted with the current keypair's public key.
pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.decrypt_asymmetric(ciphertext)
}

View File

@@ -0,0 +1,36 @@
# Keyspace Module Specification
This document explains the purpose and functionality of the `keyspace` module within the Hero Vault.
## Purpose of the Module
The `keyspace` module provides a secure and organized way to manage cryptographic keypairs. It allows for the creation, storage, loading, and utilization of keypairs within designated containers called keyspaces. This module is essential for handling sensitive cryptographic material securely.
## What is a Keyspace?
A keyspace is a logical container designed to hold multiple cryptographic keypairs. It is represented by the `KeySpace` struct in the code. Keyspaces can be encrypted and persisted to disk, providing a secure method for storing collections of keypairs. Each keyspace is identified by a unique name.
## What is a Keypair?
A keypair, represented by the `KeyPair` struct, is a fundamental cryptographic element consisting of a mathematically linked pair of keys: a public key and a private key. In this module, ECDSA (Elliptic Curve Digital Signature Algorithm) keypairs are used.
* **Private Key:** This key is kept secret and is used for operations like signing data or decrypting messages intended for the keypair's owner.
* **Public Key:** This key can be shared openly and is used to verify signatures created by the corresponding private key or to encrypt messages that can only be decrypted by the private key.
## How Many Keypairs Per Space?
A keyspace can hold multiple keypairs. The `KeySpace` struct uses a `HashMap` to store keypairs, where each keypair is associated with a unique string name. There is no inherent, fixed limit on the number of keypairs a keyspace can contain, beyond the practical limitations of system memory.
## How Do We Load Them?
Keyspaces are loaded from persistent storage (disk) using the `KeySpace::load` function, which requires the keyspace name and a password for decryption. Once a `KeySpace` object is loaded into memory, it can be set as the currently active keyspace for the session using the `session_manager::set_current_space` function. Individual keypairs within the loaded keyspace are then accessed by their names using functions like `session_manager::select_keypair` and `session_manager::get_selected_keypair`.
## What Do They Do?
Keypairs within a keyspace are used to perform various cryptographic operations. The `KeyPair` struct provides methods for:
* **Digital Signatures:** Signing messages with the private key (`KeyPair::sign`) and verifying those signatures with the public key (`KeyPair::verify`).
* **Ethereum Address Derivation:** Generating an Ethereum address from the public key (`KeyPair::to_ethereum_address`).
* **Asymmetric Encryption/Decryption:** Encrypting data using a recipient's public key (`KeyPair::encrypt_asymmetric`) and decrypting data encrypted with the keypair's public key using the private key (`KeyPair::decrypt_asymmetric`).
The `session_manager` module provides functions that utilize the currently selected keypair to perform these operations within the context of the active session.

View File

@@ -0,0 +1,86 @@
use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keypair_creation() {
let keypair = KeyPair::new("test_keypair");
assert_eq!(keypair.name, "test_keypair");
// Basic check that keys are generated (they should have non-zero length)
assert!(!keypair.pub_key().is_empty());
}
#[test]
fn test_keypair_sign_and_verify() {
let keypair = KeyPair::new("test_keypair");
let message = b"This is a test message";
let signature = keypair.sign(message);
assert!(!signature.is_empty());
let is_valid = keypair.verify(message, &signature).expect("Verification failed");
assert!(is_valid);
// Test with a wrong message
let wrong_message = b"This is a different message";
let is_valid_wrong = keypair.verify(wrong_message, &signature).expect("Verification failed with wrong message");
assert!(!is_valid_wrong);
}
#[test]
fn test_verify_with_public_key() {
let keypair = KeyPair::new("test_keypair");
let message = b"Another test message";
let signature = keypair.sign(message);
let public_key = keypair.pub_key();
let is_valid = KeyPair::verify_with_public_key(&public_key, message, &signature).expect("Verification with public key failed");
assert!(is_valid);
// Test with a wrong public key
let wrong_keypair = KeyPair::new("wrong_keypair");
let wrong_public_key = wrong_keypair.pub_key();
let is_valid_wrong_key = KeyPair::verify_with_public_key(&wrong_public_key, message, &signature).expect("Verification with wrong public key failed");
assert!(!is_valid_wrong_key);
}
#[test]
fn test_asymmetric_encryption_decryption() {
// Sender's keypair
let sender_keypair = KeyPair::new("sender");
let sender_public_key = sender_keypair.pub_key();
// Recipient's keypair
let recipient_keypair = KeyPair::new("recipient");
let recipient_public_key = recipient_keypair.pub_key();
let message = b"This is a secret message";
// Sender encrypts for recipient
let ciphertext = sender_keypair.encrypt_asymmetric(&recipient_public_key, message).expect("Encryption failed");
assert!(!ciphertext.is_empty());
// Recipient decrypts
let decrypted_message = recipient_keypair.decrypt_asymmetric(&ciphertext).expect("Decryption failed");
assert_eq!(decrypted_message, message);
// Test decryption with wrong keypair
let wrong_keypair = KeyPair::new("wrong_recipient");
let result = wrong_keypair.decrypt_asymmetric(&ciphertext);
assert!(result.is_err());
}
#[test]
fn test_keyspace_add_keypair() {
let mut space = KeySpace::new("test_space");
space.add_keypair("keypair1").expect("Failed to add keypair1");
assert_eq!(space.keypairs.len(), 1);
assert!(space.keypairs.contains_key("keypair1"));
// Test adding a duplicate keypair
let result = space.add_keypair("keypair1");
assert!(result.is_err());
}
}

View File

@@ -0,0 +1,3 @@
mod keypair_types_tests;
mod session_manager_tests;

View File

@@ -0,0 +1,111 @@
use crate::vault::keyspace::session_manager::{
clear_session, create_keypair, create_space, get_current_space, get_selected_keypair,
list_keypairs, select_keypair, set_current_space, SESSION,
};
use crate::vault::keyspace::keypair_types::KeySpace;
// Helper function to clear the session before each test
fn setup_test() {
clear_session();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_and_get_space() {
setup_test();
create_space("test_space").expect("Failed to create space");
let space = get_current_space().expect("Failed to get current space");
assert_eq!(space.name, "test_space");
}
#[test]
fn test_set_current_space() {
setup_test();
let space = KeySpace::new("another_space");
set_current_space(space.clone()).expect("Failed to set current space");
let current_space = get_current_space().expect("Failed to get current space");
assert_eq!(current_space.name, "another_space");
}
#[test]
fn test_clear_session() {
setup_test();
create_space("test_space").expect("Failed to create space");
clear_session();
let result = get_current_space();
assert!(result.is_err());
}
#[test]
fn test_create_and_select_keypair() {
setup_test();
create_space("test_space").expect("Failed to create space");
create_keypair("test_keypair").expect("Failed to create keypair");
let keypair = get_selected_keypair().expect("Failed to get selected keypair");
assert_eq!(keypair.name, "test_keypair");
select_keypair("test_keypair").expect("Failed to select keypair");
let selected_keypair = get_selected_keypair().expect("Failed to get selected keypair after select");
assert_eq!(selected_keypair.name, "test_keypair");
}
#[test]
fn test_list_keypairs() {
setup_test();
create_space("test_space").expect("Failed to create space");
create_keypair("keypair1").expect("Failed to create keypair1");
create_keypair("keypair2").expect("Failed to create keypair2");
let keypairs = list_keypairs().expect("Failed to list keypairs");
assert_eq!(keypairs.len(), 2);
assert!(keypairs.contains(&"keypair1".to_string()));
assert!(keypairs.contains(&"keypair2".to_string()));
}
#[test]
fn test_create_keypair_no_active_space() {
setup_test();
let result = create_keypair("test_keypair");
assert!(result.is_err());
}
#[test]
fn test_select_keypair_no_active_space() {
setup_test();
let result = select_keypair("test_keypair");
assert!(result.is_err());
}
#[test]
fn test_select_nonexistent_keypair() {
setup_test();
create_space("test_space").expect("Failed to create space");
let result = select_keypair("nonexistent_keypair");
assert!(result.is_err());
}
#[test]
fn test_get_selected_keypair_no_active_space() {
setup_test();
let result = get_selected_keypair();
assert!(result.is_err());
}
#[test]
fn test_get_selected_keypair_no_keypair_selected() {
setup_test();
create_space("test_space").expect("Failed to create space");
let result = get_selected_keypair();
assert!(result.is_err());
}
#[test]
fn test_list_keypairs_no_active_space() {
setup_test();
let result = list_keypairs();
assert!(result.is_err());
}
}