From 1a0f454c7170acd1104f8b03ac5d447ada6e7c57 Mon Sep 17 00:00:00 2001 From: Continuist Date: Sat, 4 Oct 2025 14:46:33 -0400 Subject: [PATCH] Implement restructuring --- Cargo.lock | 15 +- Cargo.toml | 22 +- README.md | 41 +- libs/sharenet-passport/Cargo.toml | 37 + .../src}/application/error.rs | 4 - .../sharenet-passport/src}/application/mod.rs | 2 +- .../src}/application/use_cases.rs | 0 .../src}/application/use_cases_test.rs | 0 .../sharenet-passport/src}/domain/entities.rs | 0 .../src}/domain/entities_test.rs | 0 .../sharenet-passport/src}/domain/error.rs | 7 +- .../sharenet-passport/src}/domain/mod.rs | 2 +- .../sharenet-passport/src}/domain/traits.rs | 0 .../src}/infrastructure/crypto.rs | 0 .../src}/infrastructure/crypto_test.rs | 7 +- .../src/infrastructure/mod.rs | 15 + .../src}/infrastructure/storage.rs | 0 .../src}/infrastructure/storage_test.rs | 2 + libs/sharenet-passport/src/lib.rs | 29 + sharenet-passport-cli/Cargo.toml | 16 + .../src}/cli/commands.rs | 0 sharenet-passport-cli/src/cli/interface.rs | 163 +++ {src => sharenet-passport-cli/src}/cli/mod.rs | 0 {src => sharenet-passport-cli/src}/main.rs | 6 +- sharenet_spec.md | 1231 +++++++++++++++++ src/cli/interface.rs | 216 --- src/infrastructure/mod.rs | 2 - 27 files changed, 1555 insertions(+), 262 deletions(-) create mode 100644 libs/sharenet-passport/Cargo.toml rename {src => libs/sharenet-passport/src}/application/error.rs (78%) rename {src => libs/sharenet-passport/src}/application/mod.rs (67%) rename {src => libs/sharenet-passport/src}/application/use_cases.rs (100%) rename {src => libs/sharenet-passport/src}/application/use_cases_test.rs (100%) rename {src => libs/sharenet-passport/src}/domain/entities.rs (100%) rename {src => libs/sharenet-passport/src}/domain/entities_test.rs (100%) rename {src => libs/sharenet-passport/src}/domain/error.rs (78%) rename {src => libs/sharenet-passport/src}/domain/mod.rs (74%) rename {src => libs/sharenet-passport/src}/domain/traits.rs (100%) rename {src => libs/sharenet-passport/src}/infrastructure/crypto.rs (100%) rename {src => libs/sharenet-passport/src}/infrastructure/crypto_test.rs (92%) create mode 100644 libs/sharenet-passport/src/infrastructure/mod.rs rename {src => libs/sharenet-passport/src}/infrastructure/storage.rs (100%) rename {src => libs/sharenet-passport/src}/infrastructure/storage_test.rs (95%) create mode 100644 libs/sharenet-passport/src/lib.rs create mode 100644 sharenet-passport-cli/Cargo.toml rename {src => sharenet-passport-cli/src}/cli/commands.rs (100%) create mode 100644 sharenet-passport-cli/src/cli/interface.rs rename {src => sharenet-passport-cli/src}/cli/mod.rs (100%) rename {src => sharenet-passport-cli/src}/main.rs (86%) create mode 100644 sharenet_spec.md delete mode 100644 src/cli/interface.rs delete mode 100644 src/infrastructure/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5441c12..8ba2074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,16 +632,13 @@ dependencies = [ name = "sharenet-passport" version = "0.1.0" dependencies = [ - "assert_matches", "bip39", "chacha20poly1305", - "clap", "ed25519-dalek", "hex", "hkdf", "rand", "rand_core", - "rpassword", "serde", "serde_cbor", "sha2", @@ -650,6 +647,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sharenet-passport-cli" +version = "0.1.0" +dependencies = [ + "assert_matches", + "clap", + "hex", + "rpassword", + "sharenet-passport", + "tempfile", +] + [[package]] name = "signature" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index 0d6efb7..311cf01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,12 @@ -[package] -name = "sharenet-passport" -version = "0.1.0" -edition = "2021" +[workspace] +members = [ + "libs/sharenet-passport", + "sharenet-passport-cli" +] +resolver = "2" -[dependencies] +[workspace.dependencies] +# Common dependencies shared across workspace members bip39 = "2.1" ed25519-dalek = { version = "2.1", features = ["serde"] } chacha20poly1305 = "0.10" @@ -13,13 +16,8 @@ rand = "0.8" rand_core = "0.6" serde = { version = "1.0", features = ["derive"] } serde_cbor = "0.11" -clap = { version = "4.4", features = ["derive"] } thiserror = "1.0" zeroize = { version = "1.7", features = ["zeroize_derive"] } -rpassword = "7.2" hex = "0.4" - -[dev-dependencies] -assert_matches = "1.5" -hex = "0.4" -tempfile = "3.8" \ No newline at end of file +clap = { version = "4.4", features = ["derive"] } +rpassword = "7.2" \ No newline at end of file diff --git a/README.md b/README.md index d792e47..904872f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ cd sharenet_passport_creator cargo build --release # Run the CLI -./target/release/sharenet-passport --help +./target/release/sharenet-passport-cli-cli --help ``` ## Usage Guide @@ -34,7 +34,7 @@ cargo build --release Create a new Sharenet Passport with a secure recovery phrase: ```bash -./target/release/sharenet-passport create my-passport.spf +./target/release/sharenet-passport-cli create --output my-passport.spf ``` You'll be prompted to: @@ -54,7 +54,7 @@ The tool will then: If you have a 24-word recovery phrase, you can import it to create a new .spf file: ```bash -./target/release/sharenet-passport import-recovery recovered-passport.spf +./target/release/sharenet-passport-cli import-recovery --output recovered-passport.spf ``` You'll be prompted to: @@ -67,10 +67,10 @@ Import an existing .spf file (useful for re-encryption or verification): ```bash # Import without re-encryption -./target/release/sharenet-passport import-file existing.spf +./target/release/sharenet-passport-cli import-file existing.spf # Import and re-encrypt to new file -./target/release/sharenet-passport import-file existing.spf new-passport.spf +./target/release/sharenet-passport-cli import-file existing.spf --output new-passport.spf ``` You'll be prompted for the access password of the existing file. @@ -80,7 +80,7 @@ You'll be prompted for the access password of the existing file. Export a passport with a new access password: ```bash -./target/release/sharenet-passport export old-passport.spf new-passport.spf +./target/release/sharenet-passport-cli export old-passport.spf --output new-passport.spf ``` You'll be prompted to: @@ -92,7 +92,7 @@ You'll be prompted to: Display information about a .spf file: ```bash -./target/release/sharenet-passport info my-passport.spf +./target/release/sharenet-passport-cli info my-passport.spf ``` Shows: @@ -105,7 +105,7 @@ Shows: Sign a message using your passport's private key: ```bash -./target/release/sharenet-passport sign my-passport.spf "Hello, Sharenet!" +./target/release/sharenet-passport-cli sign my-passport.spf "Hello, Sharenet!" ``` Output includes: @@ -144,17 +144,30 @@ Your 24-word recovery phrase is the master key to your identity: ### Running Tests ```bash -cargo test +# Run tests for all workspace crates +cargo test --workspace + +# Run tests for specific crates +cargo test -p sharenet-passport # Library crate tests +cargo test -p sharenet-passport-cli # CLI crate tests ``` ### Project Structure ``` -src/ -├── domain/ # Core entities and traits -├── application/ # Use cases and business logic -├── infrastructure/ # Crypto and storage implementations -└── cli/ # Command-line interface +sharenet_passport_creator/ +├── libs/ +│ └── sharenet-passport/ # Core library crate +│ ├── src/ +│ │ ├── domain/ # Core entities and traits +│ │ ├── application/ # Use cases and business logic +│ │ └── infrastructure/ # Crypto and storage implementations +│ └── Cargo.toml +├── sharenet-passport-cli/ # CLI crate +│ ├── src/ +│ │ └── cli/ # Command-line interface +│ └── Cargo.toml +└── Cargo.toml # Workspace configuration ``` ### Architecture diff --git a/libs/sharenet-passport/Cargo.toml b/libs/sharenet-passport/Cargo.toml new file mode 100644 index 0000000..738f900 --- /dev/null +++ b/libs/sharenet-passport/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "sharenet-passport" +version = "0.1.0" +edition = "2021" +description = "Core library for Sharenet Passport creation and management" +authors = ["Your Name "] +license = "MIT OR Apache-2.0" +repository = "https://git.sharenet.sh/your-org/sharenet-passport" + +[dependencies] +bip39 = "2.1" +ed25519-dalek = { version = "2.1", features = ["serde"] } +chacha20poly1305 = "0.10" +hkdf = "0.12" +sha2 = "0.10" +rand = "0.8" +rand_core = "0.6" +serde = { version = "1.0", features = ["derive"] } +serde_cbor = "0.11" +thiserror = "1.0" +zeroize = { version = "1.7", features = ["zeroize_derive"] } +hex = "0.4" + +[lib] +crate-type = ["cdylib", "rlib"] # Support both native and WASM + +[features] +default = ["std"] +std = [] # Standard library support +alloc = [] # No-std with alloc support +wasm = ["alloc"] # WASM target support + +[dev-dependencies] +tempfile = "3.8" + +[publish] +registry = "sharenet" \ No newline at end of file diff --git a/src/application/error.rs b/libs/sharenet-passport/src/application/error.rs similarity index 78% rename from src/application/error.rs rename to libs/sharenet-passport/src/application/error.rs index 5b34fdf..9234269 100644 --- a/src/application/error.rs +++ b/libs/sharenet-passport/src/application/error.rs @@ -5,10 +5,6 @@ pub enum ApplicationError { #[error("Use case error: {0}")] UseCaseError(String), - #[error("Invalid input: {0}")] - InvalidInput(String), - - #[error("Domain error: {0}")] DomainError(#[from] crate::domain::error::DomainError), } \ No newline at end of file diff --git a/src/application/mod.rs b/libs/sharenet-passport/src/application/mod.rs similarity index 67% rename from src/application/mod.rs rename to libs/sharenet-passport/src/application/mod.rs index 9ed4bb3..207d3c7 100644 --- a/src/application/mod.rs +++ b/libs/sharenet-passport/src/application/mod.rs @@ -2,4 +2,4 @@ pub mod use_cases; pub mod error; #[cfg(test)] -pub mod use_cases_test; \ No newline at end of file +mod use_cases_test; \ No newline at end of file diff --git a/src/application/use_cases.rs b/libs/sharenet-passport/src/application/use_cases.rs similarity index 100% rename from src/application/use_cases.rs rename to libs/sharenet-passport/src/application/use_cases.rs diff --git a/src/application/use_cases_test.rs b/libs/sharenet-passport/src/application/use_cases_test.rs similarity index 100% rename from src/application/use_cases_test.rs rename to libs/sharenet-passport/src/application/use_cases_test.rs diff --git a/src/domain/entities.rs b/libs/sharenet-passport/src/domain/entities.rs similarity index 100% rename from src/domain/entities.rs rename to libs/sharenet-passport/src/domain/entities.rs diff --git a/src/domain/entities_test.rs b/libs/sharenet-passport/src/domain/entities_test.rs similarity index 100% rename from src/domain/entities_test.rs rename to libs/sharenet-passport/src/domain/entities_test.rs diff --git a/src/domain/error.rs b/libs/sharenet-passport/src/domain/error.rs similarity index 78% rename from src/domain/error.rs rename to libs/sharenet-passport/src/domain/error.rs index 3a745b6..24c03da 100644 --- a/src/domain/error.rs +++ b/libs/sharenet-passport/src/domain/error.rs @@ -2,12 +2,15 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum DomainError { + #[error("Cryptographic error: {0}")] + CryptographicError(String), + #[error("Invalid mnemonic: {0}")] InvalidMnemonic(String), #[error("Invalid file format: {0}")] InvalidFileFormat(String), - #[error("Cryptographic error: {0}")] - CryptographicError(String), + #[error("File operation failed: {0}")] + FileOperationError(String), } \ No newline at end of file diff --git a/src/domain/mod.rs b/libs/sharenet-passport/src/domain/mod.rs similarity index 74% rename from src/domain/mod.rs rename to libs/sharenet-passport/src/domain/mod.rs index a9eb2d8..edbe929 100644 --- a/src/domain/mod.rs +++ b/libs/sharenet-passport/src/domain/mod.rs @@ -3,4 +3,4 @@ pub mod traits; pub mod error; #[cfg(test)] -pub mod entities_test; \ No newline at end of file +mod entities_test; \ No newline at end of file diff --git a/src/domain/traits.rs b/libs/sharenet-passport/src/domain/traits.rs similarity index 100% rename from src/domain/traits.rs rename to libs/sharenet-passport/src/domain/traits.rs diff --git a/src/infrastructure/crypto.rs b/libs/sharenet-passport/src/infrastructure/crypto.rs similarity index 100% rename from src/infrastructure/crypto.rs rename to libs/sharenet-passport/src/infrastructure/crypto.rs diff --git a/src/infrastructure/crypto_test.rs b/libs/sharenet-passport/src/infrastructure/crypto_test.rs similarity index 92% rename from src/infrastructure/crypto_test.rs rename to libs/sharenet-passport/src/infrastructure/crypto_test.rs index fbf98e9..a845907 100644 --- a/src/infrastructure/crypto_test.rs +++ b/libs/sharenet-passport/src/infrastructure/crypto_test.rs @@ -2,6 +2,8 @@ mod tests { use super::*; use crate::domain::entities::*; + use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor}; + use crate::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor}; #[test] fn test_bip39_generator_creates_valid_mnemonic() { @@ -54,9 +56,10 @@ mod tests { #[test] fn test_file_encryptor_round_trip() { let encryptor = XChaCha20FileEncryptor; + let key_deriver = Ed25519KeyDeriver; - let seed = Seed::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); - let public_key = PublicKey(vec![1; 32]); + let seed = Seed::new(vec![1; 32]); + let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap(); let did = Did::new(&public_key); let password = "test-password"; diff --git a/libs/sharenet-passport/src/infrastructure/mod.rs b/libs/sharenet-passport/src/infrastructure/mod.rs new file mode 100644 index 0000000..6a430c5 --- /dev/null +++ b/libs/sharenet-passport/src/infrastructure/mod.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "std")] +pub mod crypto; +#[cfg(feature = "std")] +pub mod storage; + +#[cfg(feature = "std")] +pub use crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor}; +#[cfg(feature = "std")] +pub use storage::FileSystemStorage; + +#[cfg(all(test, feature = "std"))] +mod crypto_test; + +#[cfg(all(test, feature = "std"))] +mod storage_test; \ No newline at end of file diff --git a/src/infrastructure/storage.rs b/libs/sharenet-passport/src/infrastructure/storage.rs similarity index 100% rename from src/infrastructure/storage.rs rename to libs/sharenet-passport/src/infrastructure/storage.rs diff --git a/src/infrastructure/storage_test.rs b/libs/sharenet-passport/src/infrastructure/storage_test.rs similarity index 95% rename from src/infrastructure/storage_test.rs rename to libs/sharenet-passport/src/infrastructure/storage_test.rs index 87c4f1e..1a42c45 100644 --- a/src/infrastructure/storage_test.rs +++ b/libs/sharenet-passport/src/infrastructure/storage_test.rs @@ -2,6 +2,8 @@ mod tests { use super::*; use tempfile::NamedTempFile; + use crate::domain::traits::FileStorage; + use crate::{FileSystemStorage, PassportFile}; #[test] fn test_file_storage_round_trip() { diff --git a/libs/sharenet-passport/src/lib.rs b/libs/sharenet-passport/src/lib.rs new file mode 100644 index 0000000..d5b58a7 --- /dev/null +++ b/libs/sharenet-passport/src/lib.rs @@ -0,0 +1,29 @@ +//! Sharenet Passport Core Library +//! +//! This library provides core functionality for creating, managing, and verifying +//! Sharenet Passports using the .spf file format. + +pub mod domain; +pub mod application; +pub mod infrastructure; + +// Public API surface +pub use domain::entities::{Passport, RecoveryPhrase, PassportFile, PublicKey, PrivateKey, Did, Seed}; +pub use domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor, FileStorage}; +pub use domain::error::DomainError; + +pub use application::use_cases::{ + CreatePassportUseCase, + ImportFromRecoveryUseCase, + ImportFromFileUseCase, + ExportPassportUseCase, + SignCardUseCase +}; +pub use application::error::ApplicationError; + +pub use infrastructure::{ + Bip39MnemonicGenerator, + Ed25519KeyDeriver, + XChaCha20FileEncryptor, + FileSystemStorage +}; \ No newline at end of file diff --git a/sharenet-passport-cli/Cargo.toml b/sharenet-passport-cli/Cargo.toml new file mode 100644 index 0000000..82fe5e5 --- /dev/null +++ b/sharenet-passport-cli/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "sharenet-passport-cli" +version = "0.1.0" +edition = "2021" +description = "CLI interface for Sharenet Passport" +authors = ["Your Name "] + +[dependencies] +sharenet-passport = { path = "../libs/sharenet-passport" } +clap = { version = "4.4", features = ["derive"] } +rpassword = "7.2" +hex = "0.4" + +[dev-dependencies] +assert_matches = "1.5" +tempfile = "3.8" \ No newline at end of file diff --git a/src/cli/commands.rs b/sharenet-passport-cli/src/cli/commands.rs similarity index 100% rename from src/cli/commands.rs rename to sharenet-passport-cli/src/cli/commands.rs diff --git a/sharenet-passport-cli/src/cli/interface.rs b/sharenet-passport-cli/src/cli/interface.rs new file mode 100644 index 0000000..1dc6c08 --- /dev/null +++ b/sharenet-passport-cli/src/cli/interface.rs @@ -0,0 +1,163 @@ +use sharenet_passport::{ + application::use_cases::*, + infrastructure::*, + ApplicationError, + FileStorage, +}; +use rpassword::prompt_password; +use hex; + +pub struct CliInterface; + +impl CliInterface { + pub fn new() -> Self { + Self + } + + pub fn handle_create(&self, output: &str) -> Result<(), ApplicationError> { + let password = prompt_password("Enter password for new passport: ").unwrap(); + let confirm_password = prompt_password("Confirm password: ").unwrap(); + + if password != confirm_password { + return Err(ApplicationError::UseCaseError("Passwords do not match".to_string())); + } + + let use_case = CreatePassportUseCase::new( + Bip39MnemonicGenerator, + Ed25519KeyDeriver, + XChaCha20FileEncryptor, + FileSystemStorage, + ); + + let (passport, recovery_phrase) = use_case.execute(&password, output)?; + + println!("✅ Passport created successfully!"); + println!("📄 Saved to: {}", output); + println!("🔑 Public Key: {}", hex::encode(&passport.public_key().0)); + println!("🆔 DID: {}", passport.did().as_str()); + println!("\n📝 IMPORTANT: Save your recovery phrase in a secure location!"); + println!("Recovery phrase: {}", recovery_phrase.to_string()); + + Ok(()) + } + + pub fn handle_import_recovery(&self, output: &str) -> Result<(), ApplicationError> { + println!("Enter your 24-word recovery phrase:"); + let mut recovery_words = Vec::new(); + + for i in 1..=24 { + let word = prompt_password(&format!("Word {}: ", i)).unwrap(); + recovery_words.push(word); + } + + let password = prompt_password("Enter new password for passport file: ").unwrap(); + let confirm_password = prompt_password("Confirm password: ").unwrap(); + + if password != confirm_password { + return Err(ApplicationError::UseCaseError("Passwords do not match".to_string())); + } + + let use_case = ImportFromRecoveryUseCase::new( + Bip39MnemonicGenerator, + Ed25519KeyDeriver, + XChaCha20FileEncryptor, + FileSystemStorage, + ); + + let passport = use_case.execute(&recovery_words, &password, output)?; + + println!("✅ Passport imported successfully!"); + println!("📄 Saved to: {}", output); + println!("🔑 Public Key: {}", hex::encode(&passport.public_key().0)); + println!("🆔 DID: {}", passport.did().as_str()); + + Ok(()) + } + + pub fn handle_import_file(&self, input: &str, output: Option<&str>) -> Result<(), ApplicationError> { + let password = prompt_password("Enter password for passport file: ").unwrap(); + + let use_case = ImportFromFileUseCase::new( + XChaCha20FileEncryptor, + FileSystemStorage, + ); + + let passport = use_case.execute(input, &password, output)?; + + println!("✅ Passport imported successfully!"); + if let Some(output_path) = output { + println!("📄 Re-encrypted to: {}", output_path); + } + println!("🔑 Public Key: {}", hex::encode(&passport.public_key().0)); + println!("🆔 DID: {}", passport.did().as_str()); + + Ok(()) + } + + pub fn handle_export(&self, input: &str, output: &str) -> Result<(), ApplicationError> { + let password = prompt_password("Enter password for passport file: ").unwrap(); + let new_password = prompt_password("Enter new password for exported file: ").unwrap(); + let confirm_password = prompt_password("Confirm new password: ").unwrap(); + + if new_password != confirm_password { + return Err(ApplicationError::UseCaseError("Passwords do not match".to_string())); + } + + // First import to get the passport + let import_use_case = ImportFromFileUseCase::new( + XChaCha20FileEncryptor, + FileSystemStorage, + ); + + let passport = import_use_case.execute(input, &password, None)?; + + // Then export with new password + let export_use_case = ExportPassportUseCase::new( + XChaCha20FileEncryptor, + FileSystemStorage, + ); + + export_use_case.execute(&passport, &new_password, output)?; + + println!("✅ Passport exported successfully!"); + println!("📄 Saved to: {}", output); + + Ok(()) + } + + pub fn handle_info(&self, file: &str) -> Result<(), ApplicationError> { + let passport_file = FileSystemStorage.load(file) + .map_err(|e| ApplicationError::UseCaseError(format!("Failed to load file: {}", e)))?; + + println!("📄 Passport File Information:"); + println!(" File: {}", file); + println!(" Version: {}", passport_file.version); + println!(" Created: {}", passport_file.created_at); + println!(" DID: {}", passport_file.did); + println!(" Public Key: {}", hex::encode(&passport_file.public_key)); + println!(" KDF: {}", passport_file.kdf); + println!(" Cipher: {}", passport_file.cipher); + + Ok(()) + } + + pub fn handle_sign(&self, file: &str, message: &str) -> Result<(), ApplicationError> { + let password = prompt_password("Enter password for passport file: ").unwrap(); + + let import_use_case = ImportFromFileUseCase::new( + XChaCha20FileEncryptor, + FileSystemStorage, + ); + + let passport = import_use_case.execute(file, &password, None)?; + + let sign_use_case = SignCardUseCase::new(); + let signature = sign_use_case.execute(&passport, message)?; + + println!("✅ Message signed successfully!"); + println!("📝 Message: {}", message); + println!("🔏 Signature: {}", hex::encode(&signature)); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/cli/mod.rs b/sharenet-passport-cli/src/cli/mod.rs similarity index 100% rename from src/cli/mod.rs rename to sharenet-passport-cli/src/cli/mod.rs diff --git a/src/main.rs b/sharenet-passport-cli/src/main.rs similarity index 86% rename from src/main.rs rename to sharenet-passport-cli/src/main.rs index 28a9c2d..a58b191 100644 --- a/src/main.rs +++ b/sharenet-passport-cli/src/main.rs @@ -1,15 +1,11 @@ -mod application; mod cli; -mod domain; -mod infrastructure; use clap::Parser; use crate::cli::commands::{Cli, Commands}; use crate::cli::interface::CliInterface; -use crate::application::error::ApplicationError; -fn main() -> Result<(), ApplicationError> { +fn main() -> Result<(), Box> { let cli = Cli::parse(); let interface = CliInterface::new(); diff --git a/sharenet_spec.md b/sharenet_spec.md new file mode 100644 index 0000000..6f5bc60 --- /dev/null +++ b/sharenet_spec.md @@ -0,0 +1,1231 @@ +# Sharenet Protocol Specification (Draft) + +## 1. Introduction + +This document specifies a decentralized protocol for the creation, distribution, and validation of signed and encrypted **Cards**. Cards are the fundamental unit of communication in the system. They may be public or private, and may carry access control, distribution rules, and revocation semantics. The protocol also defines a system of **Capabilities** that allow interoperable feature negotiation without central coordination. + +This protocol assumes three fundamental building blocks in addition to Cards: + +- **Passports**: User-held identifiers derived from cryptographic seeds. +- **Nodes**: Servers that maintain membership, relay Cards, and enforce network policies. +- **Networks**: Collections of nodes and users defined by a shared Genesis Document. + +## 2. Terminology + +- **Card**: A signed object representing a unit of content. Cards may be public or private, and may be encrypted. +- **Capability (cap)**: A content-addressed specification document that describes a feature, schema, or protocol behavior. +- **Capability ID (cap_id)**: The multibase-encoded hash of a capability spec. Immutable. +- **Requirements (`reqs`)**: Capabilities that a verifier MUST support in order to process the Card. +- **Provenance (`prov`)**: Capabilities the producer actually used when generating the Card. Defaults to `reqs` if omitted. +- **Bundle (rollup cap)**: A capability that implies a deterministic set of member capabilities. +- **Policy capsule**: An encrypted structure that defines the Card’s visibility, distribution, and keying material. +- **Visibility**: Who may decrypt the Card payload. +- **Distribution**: Where the Card may be transmitted. +- **Permanent public Card**: A Card with no encryption and no revocation path. Immutable. +- **Passport**: A user-held self-sovereign identifier derived from a mnemonic seed. +- **Node**: A server implementing this protocol and providing membership and relay functions. +- **Network**: A collection of nodes and users sharing a Genesis Document. +- **Genesis Document**: Immutable initial configuration defining a network. + +## 3. Cards + +### 3.1 Structure + +A Card is a CBOR map wrapped in a COSE_Sign1 envelope. Fields: + +- `ver`: Protocol version. +- `type`: MUST be `"card"`. +- `net`: Network identifier. +- `rid`: Unique Card identifier. +- `ts`: Creation timestamp. +- `reqs`: Array of capability IDs required to interpret the Card. +- `prov`: Optional. Array of capabilities actually used. If absent, treated as identical to `reqs`. +- `policy_hash`: Integrity hash of the policy capsule (encrypted for private Cards, plaintext for public Cards). +- `payload_hash`: Integrity hash of the payload. +- `payload`: The actual payload (ciphertext for private Cards, plaintext for public Cards). +- `policy`: The policy capsule (encrypted for private Cards, plaintext for public Cards). +- `dist_tag`: Optional opaque routing hint. +- `sig`: Producer signature. + +### 3.2 Policy Capsule + +The policy capsule is an encrypted CBOR map. Fields: + +- `vis`: `"public" | "direct" | "node" | "trustset" | "group"`. +- `dist`: `"to_recipients_only" | "this_node_only" | "trusted_nodes" | "public"`. +- `keying`: HPKE encapsulations, MLS group material, or gate instructions for CEK recovery. +- `flags`: e.g. `permanent_public`. +- `exp`, `ttl`: Optional expiry or validity constraints. +- `pad`: Optional padding to obscure audience size. +- `roster_encrypted`: Optional, group roster encrypted under the group secret. + +### 3.3 Payload + +- Private Cards: `payload` = AEAD(CEK, plaintext_card_body, AAD={net, rid, policy_hash}) +- Public Cards: `payload` = plaintext_card_body +- `payload_hash` = SHA-256(payload) + +## 4. Capabilities + +### 4.1 Capability ID + +``` +cap_id = multibase( sha256( canonical_cbor(spec_without_signatures) ) ) +``` + +### 4.2 Capability Spec + +A capability spec is a signed document: + +- `schema`: MUST be `"cap-spec/v1"`. +- `name`: Human-friendly name. +- `semver`: Version. +- `purpose`: Summary of semantics. +- `behavior`: Protocol rules. +- `interop`: `{requires, supersedes, conflicts}`. +- `test_vectors`: Golden values for conformance. +- `docs_uri`: Documentation. +- `signatures`: Array of COSE signatures from authors/curators. + +### 4.3 Bundles + +A bundle spec lists `members` (cap IDs). Bundles MUST expand deterministically into their atomic members before enforcement. + +### 4.4 Negotiation + +- Nodes advertise supported caps at `/.well-known/node-caps`. +- Cards include `reqs` for enforcement, `prov` for provenance. +- Consumers MUST reject Cards if any `reqs` are unsupported. + +## 5. Visibility Modes + +- **Public**: No encryption. MAY be flagged permanent. +- **Direct**: Encrypted CEK to a single recipient (HPKE). +- **Node**: CEK encrypted to a node’s group key or gate. +- **Trustset**: CEK encrypted to each trusted node’s group key. +- **Group**: CEK encrypted to an MLS group. Roster MAY remain private. + +## 6. Distribution Modes + +- **to_recipients_only**: Honest nodes forward only to recipients. +- **this_node_only**: Not gossiped beyond node. +- **trusted_nodes**: Forwarded only to trustset. +- **public**: Freely gossiped. Encryption still protects confidentiality. + +Distribution policies MAY be encrypted inside the policy capsule. + +## 7. Revocation + +- **Permanent public cards**: Irrevocable. +- **Strong revoke (online gate)**: CEK fetched from gate; revocation = disable at gate. Instant, requires availability. +- **Crypto-shred (offline)**: CEK wrapped under KEK. Revocation = delete KEK. Prevents new decryption, not past. +- **Card Revocation List (CRL)**: Optional signed log of revoked card IDs. Honest clients suppress display. + +## 8. Security Considerations + +- All hashes MUST be computed over canonical CBOR. +- Policy capsule MUST bind `{net, rid, payload_hash}` in AEAD AAD. +- Bundles MUST be expanded before enforcement. +- Unknown required caps MUST cause rejection. +- Distribution enforcement relies on honest nodes; confidentiality relies on crypto. +- Padding SHOULD be used to obscure audience sizes. + +## 9. Extensibility + +- New caps define new features, schemas, and crypto suites. +- Specs are content-addressed, so evolution produces new cap IDs. +- Cards MAY carry adapters in `reqs` to allow downgrade/interop. +- Deprecated caps SHOULD publish signed deprecation notices. + +## 10. Conformance + +A conforming implementation MUST: +- Validate COSE signatures and hashes. +- Enforce all capabilities in `reqs`. +- Expand bundles before enforcement. +- Respect revocation policies. +- Reject unsupported or malformed Cards. + +## 11. Passports + +### 11.1 Definition +A **Passport** is a user’s self-sovereign identifier. It consists of: + +- A 24-word mnemonic seed (BIP-39 style). +- A derived Ed25519 keypair (public/private). +- A DID constructed from the public key. + +The seed MUST remain private to the user’s device. +The private key is deterministically derived from the seed. +The public key is used to generate the DID and sign Cards. + +### 11.2 Export and Recovery +- Users MAY export a **Passport file**: a locally encrypted container for the seed. +- Users MAY recover their Passport from the 24-word mnemonic if the file is lost. +- A password protects the Passport file, separate from the mnemonic. +- Importing from file requires the password; importing from mnemonic requires only the words. + +### 11.3 Usage +- Cards are signed with the Passport’s Ed25519 private key. +- Verification uses the DID → public key mapping. +- Users MAY belong to multiple networks with the same Passport. + +## 12. Nodes + +### 12.1 Definition +A **Node** is a server implementing the protocol, typically written in Rust. +It provides: + +- Membership management (accepting/revoking Passports). +- Card relay and storage. +- Capability advertisement. +- Optional Key Gate services for strong revocation. + +### 12.2 Membership +- A user joins a Node by presenting a signed request with their Passport public DID. +- The Node MAY issue a Verifiable Credential (VC) attesting to membership. +- Users MAY leave a Node by revoking their VC locally. +- Nodes MAY gossip membership VCs to peers. + +### 12.3 Federation +Nodes communicate using Cards. +Distribution policies determine which nodes are eligible to receive a Card. +Nodes MAY maintain trustsets to decide forwarding scopes. + +## 13. Networks + +### 13.1 Genesis Document +Each network begins with a **Genesis Document** that defines: + +- `net_id`: Unique network identifier. +- `genesis_ts`: Timestamp of creation. +- `founders`: Initial node and user DIDs. +- `bootstrap_caps`: Minimal capability set required for participation. +- `initial_policies`: Distribution and trust defaults. + +The Genesis Document is signed by the founders and distributed to all participants. +It MUST be immutable. Any update creates a **new network**. + +### 13.2 Participation +- Users MAY join multiple networks. +- Nodes MAY serve one or more networks, but each Card is bound to a single `net_id`. +- Networks are sovereign: no privileged global operator exists. + +### 13.3 Migration +- Cards and Passports MAY move between networks. +- Migration of state between networks requires new credentials or attestations. +- Cards created under one `net_id` remain bound to that `net_id` forever. + +## 14. Security Model & Assumptions + +The protocol operates under the following assumptions and goals: + +- **Honest-but-curious nodes**: Nodes may relay Cards faithfully but attempt to learn additional metadata. +- **Malicious peers**: Adversaries may forge, replay, strip capabilities, or attempt downgrade attacks. +- **Passive observers**: Adversaries may monitor network traffic but lack access to private keys. +- **Compromised devices**: If a user’s Passport seed is stolen, their identity is compromised until rotated. +- **Goals**: Confidentiality of private Cards, authenticity of signatures, integrity of metadata (AAD), forward secrecy when possible, and user ability to revoke non-permanent Cards. + +## 15. Cryptographic Primitives & Parameters + +Implementations MUST use the following algorithms unless explicitly negotiated by capabilities: + +- **AEAD**: XChaCha20-Poly1305 with 256-bit keys. +- **HPKE**: DHKEM(X25519, HKDF-SHA256) with AEAD-CHACHA20POLY1305. +- **Signatures**: Ed25519 for Passports. +- **Hashing**: SHA-256 for `content_hash`, `policy_ref`, and capability IDs. +- **KDFs**: HKDF-SHA256 for deriving CEKs and KEKs. +- **Randomness**: CEKs MUST be generated with a CSPRNG; nonces MUST NOT repeat. +- **AAD binding**: `{net, rid, content_hash, policy_ref}` MUST be included as Additional Authenticated Data in all AEAD operations. + +## 16. Key Management & Rotation + +### 16.1 Hierarchy +- **Seed**: Root of identity, represented as a 24-word mnemonic. +- **Ed25519 keypair**: Derived deterministically from the seed. Used for DIDs and signing. +- **CEK (Content Encryption Key)**: Fresh symmetric key per Card. +- **KEK (Key Encryption Key)**: Optional higher-level key wrapping CEKs, enabling revocation by deletion. + +### 16.2 Rotation +- Users MAY rotate Passport keys by generating a new keypair from the seed with a different derivation path. +- DID key rotation certificates MAY be issued: signed by both old and new keys, linking continuity. +- Nodes and verifiers MUST accept a chain of rotation certificates when validating historical Cards. + +### 16.3 Revocation +- Revocation of Cards MAY occur via deletion of KEKs (crypto-shred) or via refusal to release CEKs (online gate). +- Membership credentials (VCs) MUST include status lists or revocation registries to support user exit from nodes. + +## 17. Policy Capsule Wire Format + +### 17.1 Encoding +- The policy capsule is encoded as **CBOR**, then wrapped in **COSE_Encrypt0**. +- Key encapsulation MAY use **HPKE** (per recipient) or **MLS** (per group). +- The capsule MUST be integrity-protected with AEAD; AAD MUST include `{net, rid, payload_hash}`. + +### 17.2 Structure (plaintext before encryption) +```cbor +{ + "vis": "public" | "direct" | "node" | "trustset" | "group", + "dist": "to_recipients_only" | "this_node_only" | "trusted_nodes" | "public", + "keying": { + "hpke": [ { "kid": , "enc": }, ... ], + "mls": { "group_id": bstr, "epoch": int, "welcome": bstr }, + "gate": { "uri": tstr, "aud": bstr, "ttl": int } + }, + "flags": { "permanent_public": bool, ... }, + "exp": int, ; expiry timestamp (optional) + "pad": bstr, ; optional padding + "roster_encrypted": bstr ; optional roster encrypted under group secret +} +``` + +### 17.3 Padding +- To obscure audience size, implementers SHOULD pad the capsule to fixed bucket sizes (e.g. 1kB, 2kB, 4kB). +- `pad` MUST be random bytes ignored by consumers. + +### 17.4 Roster Secrecy +- Group rosters MUST NOT appear in plaintext. +- If included, `roster_encrypted` MUST be encrypted under the group key. + +--- + +## 18. Revocation: Gate API, Crypto-Shred, and Status Lists + +### 18.1 Gate API +An optional HTTP(S) service MAY act as a **key gate**. + +Endpoints: +- `POST /cek` → returns CEK wrapped for the requester. +- `POST /decrypt` → returns plaintext stream of Card body. + +Rules: +- Requests MUST carry an **audience binding** (`rid`, `net`). +- Gates MUST enforce **TTL** on issued tokens. +- Rate limits and replay protection MUST be applied. +- Revocation = gate refuses further issuance. + +### 18.2 Crypto-Shred +- CEK is wrapped under a KEK stored at a node or device. +- Revocation = securely delete KEK. +- Clients encountering missing KEK MUST treat the Card as revoked. + +### 18.3 Card Revocation Lists (CRL) +- A CRL is a signed CBOR array of revoked `rid`s with metadata: +```cbor +{ + "net": , + "ts": , + "revoked": [ , , ... ] +} +``` +- CRLs MUST be signed by the issuer (user or node). +- Clients MUST hide revoked Cards even if cached. +- Freshness MUST be enforced (CRL expiry or versioning). + +--- + +## 19. Distribution Rules & Relay Behavior + +### 19.1 Enforcement by Honest Nodes +- **to_recipients_only**: forward only if node is itself a recipient or directly connected to one. +- **this_node_only**: MUST NOT forward beyond local storage. +- **trusted_nodes**: MAY forward only to nodes in the local trustset. +- **public**: MAY forward freely. + +### 19.2 Logging +- Nodes SHOULD log distribution violations (attempts to forward beyond scope). +- Logs MUST contain only hashes (`rid`, `content_hash`), not plaintext. + +### 19.3 Confidentiality Interaction +- Distribution rules are **policy**, not crypto. +- Confidentiality is preserved by encryption regardless of distribution. +- Even if a Card leaks beyond scope, non-recipients cannot decrypt. + +### 19.4 Error Signaling +- If a node refuses to forward a Card due to distribution rules, it SHOULD return an error code: + - `err_dist_scope` = distribution violation + - `err_recipient_only` = not a recipient + +## 20. Transport & Gossip Protocol + +### 20.1 Discovery +- Nodes MAY be discovered via: + - Static configuration files or bootstrap lists. + - Distributed Hash Table (DHT) lookups. + - Rendezvous services (non-privileged). + +### 20.2 Message Relay +- Cards are propagated between peers using gossip-style replication. +- Implementations SHOULD batch multiple Cards into a single message for efficiency. +- Backoff strategies MUST be applied when peers are overloaded or unresponsive. + +### 20.3 Deduplication and Replay Protection +- Every Card has a unique `rid`. +- Nodes MUST maintain a deduplication cache to avoid re-processing the same Card. +- Replay attacks MUST be prevented by rejecting Cards with duplicate `rid`s or stale timestamps. + +### 20.4 Idempotency and Limits +- All Card handling MUST be idempotent: re-processing MUST NOT change system state. +- Maximum Card size SHOULD be capped (e.g. 1 MB). +- Nodes MAY reject or truncate oversized payloads. + +--- + +## 21. Identifiers, Timestamps, and Causality + +### 21.1 RIDs +- `rid` is the globally unique identifier for a Card. +- Recommended format: **UUIDv7** (time-ordered, random component). +- Collisions are cryptographically improbable; nodes MUST treat duplicates as replay attempts. + +### 21.2 Timestamps +- Each Card includes a creation timestamp (`ts`). +- Verifiers SHOULD accept timestamps within a tolerance window (e.g. ±5 minutes) to handle clock skew. +- Cards outside this window MAY be flagged or rejected. + +### 21.3 Causality and Linking +- Cards MAY include causal references: + - `prev`: previous Card in a sequence. + - `thread`: identifier for a conversation thread. +- Verifiers MAY use these references to reconstruct conversations or detect edits. + +--- + +## 22. Schema Packages & Module Loading + +### 22.1 Package Format +- Schemas are distributed as **content-addressed packages** (hash = ID). +- Packages MUST include: + - Schema definition (e.g., JSON Schema or CDDL). + - Version metadata. + - Optional validation logic. + +### 22.2 Signatures and Trust +- Packages SHOULD be signed by their authors. +- Node admins MAY define local trust policies (e.g., trusted keyrings). +- Unsigned or untrusted packages MAY be rejected. + +### 22.3 Module Execution +- Validation logic MAY be provided as **WebAssembly (WASM) modules**. +- Nodes MUST sandbox modules with: + - CPU, memory, and time limits. + - Deterministic APIs for schema validation and normalization. + +### 22.4 Installation +- Packages MAY be installed automatically from peers or manually by node admins. +- A CLI MAY be provided: +```bash +syspm install +``` +- Auto-installed packages MUST be verified by signature and hash. +- Admin-installed packages override peer-provided ones. + +## 23. Capability Specs, Bundles, and Lifecycle + +### 23.1 Capability Specs +- Capabilities are described in signed **spec documents**, encoded in CBOR. +- Each spec MUST include: + - `cap_id`: multibase-encoded SHA-256 hash of the spec. + - `version`: semantic version string. + - `description`: human-readable text. + - `deps`: optional list of dependent capabilities. +- Specs SHOULD be signed by their authors to establish provenance. + +### 23.2 Bundles +- A bundle is a capability that expands into a fixed set of other capabilities. +- Bundles MAY form a directed acyclic graph (DAG). +- Expansion MUST be canonical (deterministic order by `cap_id`). +- Consumers MUST fully expand bundles before validation. + +### 23.3 Deprecation +- A deprecated capability MUST include a **deprecation notice** object: +```cbor +{ + "deprecated": true, + "replacement": [ , ], + "reason": tstr +} +``` +- Consumers SHOULD reject deprecated caps unless explicitly whitelisted. +- Producers SHOULD migrate to replacements at earliest opportunity. + +--- + +## 24. Node Roles & Trustsets + +### 24.1 Node Roles +Nodes MAY declare one or more roles: +- **archive**: long-term storage of all Cards. +- **relay**: ephemeral forwarder, minimal storage. +- **light**: partial state, relies on peers for history. + +Roles MAY be declared in Node metadata and MAY guide peer selection. + +### 24.2 Trustsets +- A trustset is a locally defined list of nodes considered trustworthy by a given node. +- Trustsets MAY be defined by DID, fingerprint, or signed config. +- Trustsets MUST be rotatable without changing the Genesis Document. +- Distribution rules (e.g. “trusted_nodes”) are enforced using trustsets. + +### 24.3 Rotation +- Trustsets SHOULD support rotation via signed update lists. +- Nodes MAY advertise trustset digests to peers. +- Discrepancies MAY be logged for audit. + +--- + +## 25. Membership Credentials & Status + +### 25.1 VC Schema +A membership Verifiable Credential (VC) MUST contain: +```cbor +{ + "vc": { + "sub": , + "iss": , + "claim": "membership", + "net": , + "ts": , + "exp": + }, + "sig": +} +``` + +### 25.2 Status Lists +- Nodes MAY maintain status lists indicating active, revoked, or suspended memberships. +- Status lists MUST be signed and periodically refreshed. +- Clients MUST check membership status before validating Cards. + +### 25.3 Verification +- To validate a Card: + - Verify the Card signature against the user’s DID. + - Fetch the user’s membership VC for the target node/network. + - Check the VC’s signature, validity window, and status list entry. +- Historical Cards MUST remain valid if the VC was active at the time of creation. + +## 26. Network Genesis, Publication, and Forks + +### 26.1 Canonical Genesis +- The Genesis Document MUST be encoded in canonical CBOR. +- The `net_id` is computed as `SHA-256` of the canonical bytes. +- Genesis MUST include: + - `net_id`, `genesis_ts`, founders (node/user DIDs), bootstrap capabilities, and policies. +- Genesis is immutable. Any change results in a new `net_id` and thus a new network. + +### 26.2 Multi-Signature Policy +- Genesis SHOULD be co-signed by multiple founders. +- Minimum signature threshold MUST be specified (e.g., 2-of-3). +- Verifiers MUST reject Genesis docs without sufficient signatures. + +### 26.3 Publication Channels +- Genesis MAY be published via: + - Static files. + - Peer-to-peer distribution. + - Content-addressed storage (IPFS, Git, etc.). + +### 26.4 No-Tombstone Rule +- Once published, a Genesis Document MUST NOT be deleted. +- Networks have permanence; dissolution only occurs if all peers stop participating. + +### 26.5 Fork Handling +- If conflicting successor Genesis docs appear, each creates a distinct new `net_id`. +- Participants MAY choose which fork to join. +- Forks MUST NOT silently overwrite each other. + +--- + +## 27. Passport Storage & Recovery + +### 27.1 Secure Storage Recommendations +- **iOS**: Secure Enclave + Keychain. +- **Android**: Keystore with StrongBox if available. +- **macOS**: Keychain, optionally hardware-backed. +- **Windows**: DPAPI + TPM if present. +- **Linux**: Kernel keyrings, Gnome Keyring, or hardware token (e.g., YubiKey). +- **Raspberry Pi**: Encrypted filesystem or external hardware token. + +### 27.2 Export Format +- Passport file MUST be an **envelope**: +```cbor +{ + "enc_seed": , + "kdf": "HKDF-SHA256", + "cipher": "XChaCha20-Poly1305", + "salt": bstr, + "nonce": bstr +} +``` +- Protected with a KEK derived from a user-chosen password. + +### 27.3 Recovery from Mnemonic +- A 24-word BIP-39 mnemonic MAY recreate the seed if the file is lost. +- No password required unless PBKDF2 passphrase extension is used. +- Users SHOULD be instructed to back up the mnemonic offline in a safe place. + +### 27.4 Password Policies +- Passwords protecting Passport files SHOULD be high-entropy. +- Minimum recommended length: 12 characters. +- Implementations MAY enforce rate limiting to prevent brute-force attacks. + +### 27.5 Device-as-Gate +- Devices MAY serve as ephemeral gates: refusing to release CEKs unless locally approved. +- Useful for preventing silent key misuse if the device is compromised. + +--- + +## 28. Privacy Considerations + +### 28.1 Metadata Minimization +- Cleartext MUST be minimized: only `rid`, `cap_ids`, and distribution policies are visible. +- Membership VCs SHOULD NOT be included unless necessary. +- Recipient lists MUST remain encrypted when possible. + +### 28.2 Padding +- Implementations SHOULD pad policy capsules and encrypted payloads to fixed sizes. +- Padding helps obscure audience size and plaintext length. + +### 28.3 Cover Traffic +- Nodes MAY generate synthetic Cards with random content to obscure traffic patterns. +- Cover traffic MUST be clearly marked so peers can discard after validation. + +### 28.4 CEK Caching +- Recipients MAY cache CEKs for performance. +- Caches MUST expire after a reasonable TTL (e.g., 24 hours). +- Revoked CEKs MUST be purged immediately if a CRL or gate refusal is received. + +### 28.5 Oblivious HTTP for Gates +- Gates MAY use **Oblivious HTTP** to conceal requester identity. +- This prevents correlation between user DID and requested Card IDs. + +## 29. Abuse & Resource Controls + +### 29.1 Rate Limits +- Nodes MUST enforce per-peer request limits to prevent denial of service. +- Suggested baseline: 100 requests/minute per peer, adjustable by implementation. + +### 29.2 Proof-of-Work or Postage +- Networks MAY require lightweight proof-of-work or postage tokens. +- This mitigates spam and incentivizes responsible use of resources. + +### 29.3 Object Size Limits +- Maximum Card size SHOULD be capped (e.g., 1 MB). +- Oversized Cards MAY be rejected with `err_size_exceeded`. + +### 29.4 Sandbox Budgets +- WASM modules used for schema validation MUST run in strict sandboxes. +- Execution MUST be limited to: + - CPU time (e.g., 100ms). + - Memory (e.g., 16 MB). +- Modules exceeding budgets MUST be terminated. + +### 29.5 Signature Verification Quotas +- Nodes SHOULD limit the number of signature verifications per peer per unit time. +- Helps protect against CPU exhaustion attacks. + +### 29.6 Ban Lists +- Nodes MAY maintain local ban lists of misbehaving peers. +- Ban lists SHOULD be rotatable and MAY be shared among trusted peers. + +--- + +## 30. Errors & Diagnostics + +### 30.1 Error Codes +Implementations MUST support the following error codes for interoperability: + +- `err_req_missing`: Required field missing. +- `err_schema_unsupported`: Schema not recognized. +- `err_cap_unknown`: Capability not recognized. +- `err_revoked`: Card has been revoked. +- `err_gate_denied`: Gate refused to provide CEK. +- `err_dist_scope`: Distribution rule violation. +- `err_recipient_only`: Node not an intended recipient. +- `err_size_exceeded`: Payload too large. + +### 30.2 Error Format +Errors MUST be encoded as CBOR objects: +```cbor +{ + "err": , + "rid": , + "hint": tstr +} +``` +- `hint` is an optional human-readable remediation suggestion. + +### 30.3 Logging +- Errors SHOULD be logged locally for diagnostics. +- Logs MUST redact sensitive payload data. + +--- + +## 31. Conformance Profiles & Test Vectors + +### 31.1 Profiles +- **Baseline Profile**: MUST implement + - Ed25519 signatures. + - XChaCha20-Poly1305 AEAD. + - Direct HPKE. + - Public and direct private Cards. + +- **Extended Profile**: MUST additionally implement + - MLS groups. + - Gate-based revocation. + - Cover traffic. + - Schema packages with WASM validation. + +### 31.2 Mandatory-to-Implement Capabilities +- `cap:sign/ed25519` +- `cap:encrypt/xchacha20poly1305` +- `cap:hpke/x25519-sha256` + +### 31.3 Test Vectors +The specification MUST include canonical CBOR + COSE test vectors for: +- Public Card. +- Direct HPKE-encrypted Card. +- MLS group-encrypted Card. +- Gate token retrieval. +- CRL with revoked Cards. + +Implementations MUST validate against these vectors to ensure interoperability. + +## 32. Content Addressing & Storage + +### 32.1 Hash Algorithms +- Content hashes MUST use SHA-256 by default. +- Alternative hash functions MAY be advertised via capability IDs. +- The `content_hash` MUST uniquely identify the payload of a Card. + +### 32.2 URIs +- Content MAY be referenced via URIs: + - `ipfs://` + - `https:///` + - `s3:///` +- Implementations MUST verify integrity by recomputing the hash. + +### 32.3 Chunking and Range Fetch +- Large payloads MAY be split into chunks. +- Chunks MUST each carry their own `content_hash`. +- Range fetch MUST allow partial retrieval and recombination. + +### 32.4 Retention and Garbage Collection +- Nodes MAY garbage-collect old payloads not recently requested. +- Garbage collection policies MUST NOT break integrity: missing payloads must be detectable via hashes. +- Permanent public Cards SHOULD be retained indefinitely. + +--- + +## 33. Observability & Audit + +### 33.1 Minimal Logging +- Nodes SHOULD log only: + - `rid` + - `cap_ids` + - error codes +- Payloads and policy capsules MUST NOT be logged in plaintext. + +### 33.2 Redaction Rules +- Logs MUST redact personally identifiable information (PII). +- Only hashed identifiers may be retained for correlation. + +### 33.3 Migration Receipts +- When a Card is re-issued due to schema or protocol migration, nodes MAY attach a signed **migration receipt**: +```cbor +{ + "old_rid": , + "new_rid": , + "ts": , + "sig": +} +``` +- Receipts provide an auditable trail for backward compatibility. + +### 33.4 Telemetry +- Aggregate, privacy-preserving telemetry MAY be published (e.g., number of Cards relayed). +- Telemetry MUST NOT expose user identifiers or Card contents. + +--- + +## 34. Human Aliases & I18N + +### 34.1 Aliases for Capabilities +- Capabilities MAY have human-readable aliases. +- Aliases are **non-authoritative** and for UI purposes only. +- Each alias MUST map to a single `cap_id`, but a `cap_id` MAY have multiple aliases. + +### 34.2 Localization +- Aliases MAY be localized into different languages. +- Localization data MUST be distributed separately from the canonical spec. + +### 34.3 Collision Handling +- If two aliases collide: + - Node admins MAY configure preferred mappings. + - UIs SHOULD disambiguate by showing both alias and `cap_id`. + +### 34.4 Example +```cbor +{ + "alias": "en:signatures/ed25519", + "cap_id": "zQm..." +} +``` + +## 35. Security Bulletins + +### 35.1 Purpose +- Security bulletins provide a mechanism to urgently disable compromised capabilities, algorithms, or schema packages. +- Bulletins are signed notices distributed across the network. + +### 35.2 Structure +```cbor +{ + "bulletin": "disable", + "cap_id": , + "reason": tstr, + "ts": , + "sig": +} +``` + +### 35.3 Issuers +- Bulletins MAY be issued by capability authors, network founders, or node operators. +- Clients MUST verify the issuer’s signature against a trusted keyring. + +### 35.4 Client Behavior +- Upon receiving a valid bulletin: + - Clients MUST immediately reject any Card requiring the disabled capability. + - Historical Cards MAY remain valid unless otherwise specified. + - Warnings SHOULD be logged for operator review. + +### 35.5 Distribution +- Bulletins MAY be distributed via gossip, CRLs, or dedicated feeds. +- Clients SHOULD retain bulletins for audit purposes. + +--- + +## 36. Evolution Without Epochs + +### 36.1 Approach +- This protocol evolves without centralized epochs. +- Capabilities (`cap_id`s) are the atomic unit of evolution. +- Each new schema or feature is expressed as a new capability. + +### 36.2 Reqs Defaulting +- Cards MUST include a `reqs` field listing required capabilities. +- If `prov` is omitted, verifiers MUST assume `prov = reqs`. +- This ensures backward compatibility when producers omit `prov`. + +### 36.3 Prov Optionality +- The `prov` field MAY list capabilities actually used to generate the Card. +- If present, `prov` MUST be a subset of `reqs`. +- Consumers MAY use `prov` for debugging or migration tracking. + +### 36.4 Breaking Changes +- A breaking schema change MUST result in a new `cap_id`. +- Producers MUST NOT reuse `cap_id`s for incompatible updates. +- Consumers MUST reject Cards that require unknown or deprecated `cap_id`s. + +### 36.5 Adapters +- Nodes MAY provide **adapters** to transform Cards from older schema versions to newer ones. +- Adapters MUST be deterministic and SHOULD be distributed as signed schema packages. +- Consumers MUST record the use of adapters in audit logs. + +## Appendix A. Example Public Card + +```json +{ + "ver": 1, + "type": "card", + "net": "net:prod:z9…", + "rid": "0x12b…", + "ts": 1738123456, + "reqs": ["cap:card/v1@…"], + "policy_hash": "sha256:…", + "payload_hash": "sha256:…", + "payload": { "msg": "hello world" }, + "policy": { + "vis": "public", + "dist": "public", + "flags": { "permanent_public": true } + } +} +``` + +## Appendix B. Example Private Card (Direct) + +```json +{ + "ver": 1, + "type": "card", + "net": "net:prod:z9…", + "rid": "0x44a…", + "ts": 1738126789, + "reqs": ["cap:card/v2@…","cap:hpke-x25519-xc20p@…"], + "policy_hash": "sha256:7f…", + "payload_hash": "sha256:aa…", + "payload": "", + "policy": "" +} +``` + +--- + +**Status of this Document** +This is a working draft. It is subject to change as the protocol evolves. Implementers SHOULD anticipate breaking changes and support migration strategies. + +# Object Taxonomy & Wire Types (Draft Addendum) + +This addendum enumerates object types beyond **Cards** that a compliant implementation SHOULD/MUST support. It groups them by plane (data, control, governance/ops) and specifies wire shapes, minimal required fields, and security properties. Language follows the style of W3C specs used in the main document. + +--- + +## 1. Conventions + +- All objects are encoded as **CBOR**. +- Signed objects use **COSE_Sign1** (envelope outside the CBOR map). +- Encrypted objects use **COSE_Encrypt0**; HPKE/MLS may be used to deliver content keys. +- Hashes MUST be over **canonical CBOR**. +- `rid` identifies a Card; non-Card objects have their own identifiers as noted. +- Capability IDs (`cap_id`) are multibase-encoded SHA-256 of the canonical spec bytes. + +--- + +## 2. Data-Plane Objects + +### 2.1 Media / Blob Chunks +**Purpose:** Large attachments referenced from Cards. + +**Integrity:** `content_hash` (SHA-256) over the chunk bytes. +**Encryption:** OPTIONAL; if used, reference ciphertext hash. + +**Shape (meta, unsigned or signed)**: +```cbor +{ + "schema": "blob-meta/v1", + "content_uri": tstr, + "content_hash": bstr, ; sha256 digest + "size": uint, + "mime": tstr +} +``` + +### 2.2 Search / Index Hints (Optional) +**Purpose:** Precomputed, normalized views to aid indexing/search. + +**Security:** MUST be **signed** by the producer. + +```cbor +{ + "schema": "index-hint/v1", + "rid": bstr, + "view": { * tstr => any } ; normalized fields per schema module +} +``` + +--- + +## 3. Control-Plane Objects + +### 3.1 Schema Packages +**Purpose:** Define fields & validation for schema-specific payloads. + +**Security:** MUST be **signed** by author/curators. +**Fetch:** Content-addressed by `schema_id` (hash of canonical package bytes). + +```cbor +{ + "schema": "schema-package/v1", + "schema_id": tstr, ; cap:schema:/@ + "defs": { ... }, ; CDDL/JSON Schema/CUE + "module": { ; OPTIONAL WASM validator/normalizer + "type": "wasm", + "hash": bstr, + "size": uint + }, + "test_vectors": [ bstr, ... ], + "docs_uri": tstr +} +``` + +### 3.2 Schema Descriptors +**Purpose:** Operator-friendly pointer to install a module out-of-band. + +**Security:** MUST be **signed**. Node validates artifact hash before load. + +```cbor +{ + "schema": "schema-desc/v1", + "schema_id": tstr, + "module": { + "id": tstr, + "version": tstr, + "artifact_hash": bstr, + "size": uint, + "signatures": [ bstr, ... ] + } +} +``` + +### 3.3 Capability Specs & Bundles +**Purpose:** Advertise features/algorithms/behaviors. Bundles roll up sets. + +**Security:** MUST be **signed**. +**Bundle rule:** MUST expand deterministically to atomic caps before enforcement. + +```cbor +{ + "schema": "cap-spec/v1", + "cap_id": tstr, + "version": tstr, + "purpose": tstr, + "behavior": { * tstr => any }, + "interop": { + "requires": [ tstr, ... ], + "supersedes": [ tstr, ... ], + "conflicts": [ tstr, ... ] + } +} +``` + +**Bundle spec:** +```cbor +{ + "schema": "cap-bundle/v1", + "cap_id": tstr, + "members": [ tstr, ... ] ; ordered, canonical +} +``` + +### 3.4 Node Capability Manifests +**Purpose:** What a node supports (caps, roles). + +**Security:** SHOULD be **signed** and available at `/.well-known/node-caps`. + +```cbor +{ + "schema": "node-caps/v1", + "net": tstr, + "caps_supported": [ tstr, ... ], + "roles": [ "relay", "archive", "light" ] +} +``` + +### 3.5 Membership Credentials (VC) +**Purpose:** “Alice @ Node B” assertion for a given network. + +**Security:** MUST be **signed** by the issuing node. + +```cbor +{ + "schema": "membership-vc/v1", + "vc": { + "sub": tstr, ; user DID + "iss": tstr, ; node DID + "net": tstr, + "claim": "membership", + "ts": uint, + "exp": uint + } +} +``` + +### 3.6 Membership Status Lists +**Purpose:** Revocation/suspension state for membership VCs. + +**Security:** MUST be **signed** and time-bounded. + +```cbor +{ + "schema": "vc-status/v1", + "net": tstr, + "ts": uint, + "entries": [ { "did": tstr, "status": "active" | "revoked" | "suspended", "exp": uint } ] +} +``` + +### 3.7 Key Rotation Certificates +**Purpose:** Link old→new public key for a DID. + +**Security:** MUST be **countersigned** by both old and new keys. + +```cbor +{ + "schema": "key-rotate/v1", + "did": tstr, + "prev": bstr, ; old public key + "next": bstr ; new public key +} +``` + +### 3.8 Group/MLS Control +**Purpose:** Group add/remove, epoch updates. + +**Security:** MLS-native + optional outer signature if wrapped for uniform transport. + +```cbor +{ + "schema": "mls-control/v1", + "group_id": bstr, + "epoch": uint, + "msg": bstr ; MLS-encoded frame +} +``` + +### 3.9 Gate Tokens / Requests (Optional) +**Purpose:** Short-lived authorization to retrieve CEKs or stream decrypts. + +**Security:** MUST be **signed**, bound to `{rid, net}`, have short TTL. + +```cbor +{ + "schema": "gate-token/v1", + "rid": bstr, + "net": tstr, + "aud": bstr, + "exp": uint +} +``` + +### 3.10 Trustset Updates +**Purpose:** Rotate which nodes are considered trusted for forwarding. + +**Security:** MUST be **signed** by local operator key. + +```cbor +{ + "schema": "trustset/v1", + "version": uint, + "entries": [ { "node": tstr, "op": "add" | "remove" } ] +} +``` + +### 3.11 DID Resolution Hints (Optional) +**Purpose:** Cache pointers to DID docs/keys when using non-self-certifying DIDs. + +**Security:** SHOULD be **signed**; TOFU acceptable per policy. + +```cbor +{ + "schema": "did-hint/v1", + "did": tstr, + "endpoints": [ tstr, ... ] +} +``` + +--- + +## 4. Governance & Ops-Plane Objects + +### 4.1 Capability Deprecation Notices +**Purpose:** Announce deprecation and recommended replacements. + +**Security:** MUST be **signed**. + +```cbor +{ + "schema": "cap-deprecate/v1", + "cap_id": tstr, + "replacement": [ tstr, ... ], + "reason": tstr +} +``` + +### 4.2 Security Bulletins +**Purpose:** Urgently disable compromised capabilities or modules. + +**Security:** MUST be **signed** by a trusted issuer. + +```cbor +{ + "schema": "sec-bulletin/v1", + "action": "disable", + "cap_id": tstr, + "reason": tstr, + "ts": uint +} +``` + +### 4.3 Card Revocation Lists (CRL) +**Purpose:** Hide/invalidate Cards after intentional revocation. + +**Security:** MUST be **signed**; freshness enforced. + +```cbor +{ + "schema": "crl/v1", + "net": tstr, + "ts": uint, + "revoked": [ bstr, ... ] ; list of rid +} +``` + +### 4.4 Migration Receipts +**Purpose:** Show lineage when a Card is re-issued under new schema/keys. + +**Security:** MUST be **signed** by the re-issuer. + +```cbor +{ + "schema": "migrate-receipt/v1", + "old_rid": bstr, + "new_rid": bstr, + "ts": uint +} +``` + +### 4.5 Error Objects +**Purpose:** Interoperable failure signaling across peers. + +**Security:** MAY be unsigned; MAY be signed for audit chains. + +```cbor +{ + "schema": "error/v1", + "err": "err_schema_unsupported" | "err_req_missing" | "err_dist_scope" | "err_gate_denied" | "err_revoked" | "err_size_exceeded", + "rid": bstr, + "hint": tstr +} +``` + +### 4.6 Observability / Audit Events +**Purpose:** Privacy-preserving telemetry and diagnostics. + +**Security:** MUST NOT include plaintext payloads or PII. + +```cbor +{ + "schema": "audit-event/v1", + "ts": uint, + "event": tstr, + "rid": bstr, + "caps": [ tstr, ... ], + "code": tstr +} +``` + +--- + +## 5. Transport Considerations + +- All control-plane and governance objects MAY be gossiped like Cards or fetched on demand by ID (content-addressed). +- Nodes SHOULD cache verified specs/packages and bulletins. +- Deduplicate by `(schema, primary id field)` to avoid reprocessing. +- Objects MUST be small (typically <64KB), except blob metadata. + +--- + +## 6. Security & Validation Rules (Normative) + +- **Signature verification:** Required for all objects that change validation or trust (schema packages, caps, VCs, status lists, deprecations, bulletins, trustsets). +- **Determinism:** Validators/normalizers MUST be pure and deterministic; no network/file I/O. +- **Sandboxing:** Any executable validation logic MUST run in a WASM sandbox under strict CPU/mem/time budgets. +- **Hash binding:** Where objects reference Cards or payloads, they MUST use `rid` or `content_hash` and verify before acting. +- **Policy precedence:** Security Bulletins override Deprecation Notices; both override local caches. +- **Fail closed:** If a required control object is missing or unverifiable, verifiers MUST reject dependent Cards. + +--- + +## 7. IANA-Like Registries (Non-Normative) + +This ecosystem is intentionally **registry-free**. Names are content-addressed and self-describing. Communities MAY publish curated alias lists for human UX; implementations MUST NOT rely on aliases for security decisions. \ No newline at end of file diff --git a/src/cli/interface.rs b/src/cli/interface.rs deleted file mode 100644 index dca0365..0000000 --- a/src/cli/interface.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::io::{self, Write}; - -use crate::application::use_cases::*; -use crate::application::error::ApplicationError; -use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor}; -use crate::infrastructure::storage::FileSystemStorage; - -pub struct CliInterface { - mnemonic_generator: Bip39MnemonicGenerator, - key_deriver: Ed25519KeyDeriver, - file_encryptor: XChaCha20FileEncryptor, - file_storage: FileSystemStorage, -} - -impl CliInterface { - pub fn new() -> Self { - Self { - mnemonic_generator: Bip39MnemonicGenerator, - key_deriver: Ed25519KeyDeriver, - file_encryptor: XChaCha20FileEncryptor, - file_storage: FileSystemStorage, - } - } - - pub fn prompt_password(&self) -> Result { - print!("Enter Access Password: "); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let password = rpassword::read_password() - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?; - - print!("Confirm Access Password: "); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let password_confirm = rpassword::read_password() - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?; - - if password != password_confirm { - return Err(ApplicationError::InvalidInput("Passwords do not match".to_string())); - } - - if password.is_empty() { - return Err(ApplicationError::InvalidInput("Password cannot be empty".to_string())); - } - - Ok(password) - } - - pub fn prompt_recovery_phrase(&self) -> Result, ApplicationError> { - println!("Enter your 24-word Recovery Phrase (one word per line):"); - - let mut words = Vec::new(); - for i in 1..=24 { - print!("Word {}: ", i); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let mut word = String::new(); - io::stdin().read_line(&mut word) - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read input: {}", e)))?; - - let word = word.trim().to_string(); - if word.is_empty() { - return Err(ApplicationError::InvalidInput(format!("Word {} cannot be empty", i))); - } - words.push(word); - } - - Ok(words) - } - - pub fn handle_create(&self, output_path: &str) -> Result<(), ApplicationError> { - let password = self.prompt_password()?; - - let use_case = CreatePassportUseCase::new( - self.mnemonic_generator.clone(), - self.key_deriver.clone(), - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - let (passport, recovery_phrase) = use_case.execute(&password, output_path)?; - - println!("\n✅ Passport created successfully!"); - println!("\n📄 Passport saved to: {}", output_path); - println!("\n🔑 Your DID: {}", passport.did().as_str()); - println!("\n📝 Your 24-word Recovery Phrase:"); - println!("{}", recovery_phrase.to_string()); - println!("\n⚠️ IMPORTANT: Store this Recovery Phrase securely offline!"); - println!(" It can regenerate your identity if you lose access."); - - Ok(()) - } - - pub fn handle_import_recovery(&self, output_path: &str) -> Result<(), ApplicationError> { - let recovery_words = self.prompt_recovery_phrase()?; - let password = self.prompt_password()?; - - let use_case = ImportFromRecoveryUseCase::new( - self.mnemonic_generator.clone(), - self.key_deriver.clone(), - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - let passport = use_case.execute(&recovery_words, &password, output_path)?; - - println!("\n✅ Passport imported successfully!"); - println!("\n📄 Passport saved to: {}", output_path); - println!("\n🔑 Your DID: {}", passport.did().as_str()); - - Ok(()) - } - - pub fn handle_import_file(&self, input_path: &str, output_path: Option<&str>) -> Result<(), ApplicationError> { - print!("Enter Access Password for {}: ", input_path); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let password = rpassword::read_password() - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?; - - let use_case = ImportFromFileUseCase::new( - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - let passport = use_case.execute(input_path, &password, output_path)?; - - println!("\n✅ Passport imported successfully!"); - println!("\n🔑 Your DID: {}", passport.did().as_str()); - - if let Some(output_path) = output_path { - println!("\n📄 Re-encrypted passport saved to: {}", output_path); - } - - Ok(()) - } - - pub fn handle_export(&self, input_path: &str, output_path: &str) -> Result<(), ApplicationError> { - print!("Enter Access Password for {}: ", input_path); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let password = rpassword::read_password() - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?; - - let new_password = self.prompt_password()?; - - // Load the passport first - let import_use_case = ImportFromFileUseCase::new( - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - let passport = import_use_case.execute(input_path, &password, None)?; - - // Export with new password - let export_use_case = ExportPassportUseCase::new( - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - export_use_case.execute(&passport, &new_password, output_path)?; - - println!("\n✅ Passport exported successfully!"); - println!("\n📄 New passport saved to: {}", output_path); - - Ok(()) - } - - pub fn handle_info(&self, file_path: &str) -> Result<(), ApplicationError> { - print!("Enter Access Password for {}: ", file_path); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let password = rpassword::read_password() - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?; - - let use_case = ImportFromFileUseCase::new( - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - let passport = use_case.execute(file_path, &password, None)?; - - println!("\n📋 Passport Information:"); - println!("🔑 DID: {}", passport.did().as_str()); - println!("🔐 Public Key: {}", hex::encode(passport.public_key().0.clone())); - println!("📄 Source: {}", file_path); - - Ok(()) - } - - pub fn handle_sign(&self, file_path: &str, message: &str) -> Result<(), ApplicationError> { - print!("Enter Access Password for {}: ", file_path); - io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?; - - let password = rpassword::read_password() - .map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?; - - let use_case = ImportFromFileUseCase::new( - self.file_encryptor.clone(), - self.file_storage.clone(), - ); - - let passport = use_case.execute(file_path, &password, None)?; - - // Sign the message using the SignCardUseCase - let sign_use_case = SignCardUseCase::new(); - let signature = sign_use_case.execute(&passport, message)?; - - println!("\n✅ Message signed successfully!"); - println!("📝 Message: {}", message); - println!("🔐 Signature: {}", hex::encode(&signature)); - println!("🔑 Public Key: {}", hex::encode(passport.public_key().0.clone())); - - Ok(()) - } -} \ No newline at end of file diff --git a/src/infrastructure/mod.rs b/src/infrastructure/mod.rs deleted file mode 100644 index eb50d67..0000000 --- a/src/infrastructure/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod crypto; -pub mod storage; \ No newline at end of file -- 2.34.1