# Hacking on Evidence ## Development Setup ```bash # Enter the development shell (provides Rust toolchain, cargo tools, etc.) nix develop # Or without Nix, ensure you have Rust 1.90+ rustup update stable ``` ## Project Structure ``` src/ ├── codec.rs ├── digest.rs (blake3, sha2, sha3) ├── encrypted.rs ├── encryption.rs (aes_gcm, chacha20poly1305) ├── fingerprint.rs ├── mac.rs (hmac) ├── signature.rs (ed25519) ├── signed.rs └── verified.rs tests/ Property-based (bolero) ``` ## Commands The nix shell provides a `menu` command listing all available tasks: ```bash menu # Show all commands # Build & Check cargo check # Type-check cargo build # Build cargo build --release # Release build # Test cargo test # All tests cargo test --doc # Doc tests only cargo test --lib # Unit tests only # Lint & Format cargo clippy # Lints cargo fmt # Format code cargo fmt --check # Check formatting # Docs cargo doc --open # Build and open docs ``` ## Feature Flags ```bash # Check with all features cargo check --all-features # Check with no default features cargo check --no-default-features # Check specific feature cargo check --no-default-features --features blake3 ``` | Feature | Default | Provides | |--------------------|---------|--------------------------------------------------------------------------| | `sha2` | No | `Sha256`, `Sha384`, `Sha512` | | `sha3` | No | `Sha3_224`, `Sha3_256`, `Sha3_384`, `Sha3_512`, `Keccak256`, `Keccak512` | | `blake3` | No | `Blake3` | | `serde` | No | `Serialize`/`Deserialize` impls | | `arbitrary` | No | `Arbitrary` impl for fuzzing | | `bolero` | No | `TypeGenerator` impl for bolero | | `proptest` | No | `Arbitrary` impl for proptest (requires std) | | `rkyv` | No | rkyv `Archive`/`Serialize`/`Deserialize` impls | | `minicbor` | No | `Cbor` codec `Encode`/`Decode` impls | | `serde_json` | No | `Json` codec `Encode`/`Decode` impls (requires std) | | `ed25519` | No | `Ed25519` | | `hmac` | No | `HmacSha256`, `HmacSha384`, `HmacSha512` | | `chacha20poly1305` | No | `ChaCha20Poly1305` | | `aes-gcm` | No | `Aes128Gcm`, `Aes256Gcm` | ## Architecture ### Core Types ``` ┌────────────────────────────────────────────────────────┐ │ Digest │ │ ┌─────────────────────────────────────────────────┐ │ │ │ bytes: Array │ │ │ │ _marker: PhantomData (T, C)> │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ T = value type (phantom) │ │ P = DigestPrimitive (e.g., Sha256) │ │ C = Codec (phantom) │ └────────────────────────────────────────────────────────┘ ``` ### Trait Hierarchy ``` DigestPrimitive SignaturePrimitive EncryptionPrimitive MacPrimitive │ │ │ │ Sha256 Ed25519 ChaCha20Poly1305 HmacSha256 Sha384 ... Aes256Gcm HmacSha384 Sha512 Aes128Gcm HmacSha512 Sha3_256 Keccak256 Blake3 Signer AsyncSigner Encode Decode │ │ │ │ SigningKey SigningKey Identity Identity (K: FutureForm) Sendable = Send futures Local = !Send futures ``` ### Escape Hatches Extension traits for bypassing normal construction. _Intentionally not re-exported_ at crate root — users must explicitly import: ```rust use evidence::digest::DigestUnchecked; // Digest from raw bytes use evidence::encrypted::EncryptedUnchecked; // Encrypted from parts use evidence::fingerprint::FingerprintUnchecked; // Fingerprint from raw bytes use evidence::mac::MacUnchecked; // Mac from parts use evidence::signed::SignedUnchecked; // Signed from parts use evidence::verified::VerifiedUnchecked; // Verified without verification ``` This follows "Parse, Don't Validate" — the main API makes invalid states hard to reach, but escape hatches exist for legitimate use cases (deserialization, testing, trusted contexts). ### Adding a New Hash Primitive 1. Add the dependency to `Cargo.toml` (optional, feature-gated) 2. Create a module in `src/digest/`: ```rust // src/digest/my_hash.rs use super::DigestPrimitive; use hybrid_array::typenum::U32; use hybrid_array::Array; pub enum MyHash {} impl DigestPrimitive for MyHash { type Size = U32; fn hash(data: &[u8]) -> Array { // ... implementation } } ``` 3. Add `pub mod my_hash;` to `src/digest.rs` (feature-gated) 4. Add to the feature table in README.md ### Adding a New Signature Primitive 1. Add the dependency to `Cargo.toml` (optional, feature-gated) 2. Create a module in `src/signature/`: ```rust // src/signature/my_sig.rs use super::{SignaturePrimitive, Signer, AsyncSigner}; use future_form::{FutureForm, Local, Sendable}; #[derive(Debug, Clone, Copy)] pub enum MySig {} impl SignaturePrimitive for MySig { type VerifyingKey = my_crate::PublicKey; type Signature = my_crate::Signature; type SigningKey = my_crate::SecretKey; type Error = my_crate::Error; fn sign(key: &Self::SigningKey, message: &[u8]) -> Self::Signature { key.sign(message) } fn verify( key: &Self::VerifyingKey, message: &[u8], signature: &Self::Signature, ) -> Result<(), Self::Error> { key.verify(message, signature) } } impl Signer for my_crate::SecretKey { fn sign(&self, message: &[u8]) -> my_crate::Signature { ... } fn verifying_key(&self) -> my_crate::PublicKey { ... } } // Use the proc macro to generate both Sendable and Local impls #[future_form::future_form(Sendable, Local)] impl AsyncSigner for my_crate::SecretKey { fn sign<'a>(&'a self, message: &'a [u8]) -> K::Future<'a, my_crate::Signature> { K::ready(self.inner_sign(message)) } fn verifying_key(&self) -> my_crate::PublicKey { ... } } ``` 3. Add `pub mod my_sig;` to `src/signature.rs` (feature-gated) 4. Add to the feature table in README.md ### Adding a New Encryption Primitive 1. Add the dependency to `Cargo.toml` (optional, feature-gated) 2. Create a module in `src/encryption/`: ```rust // src/encryption/my_aead.rs use alloc::vec::Vec; use super::EncryptionPrimitive; use hybrid_array::Array; pub enum MyAead {} impl EncryptionPrimitive for MyAead { type Key = my_crate::Key; type NonceSize = hybrid_array::typenum::U12; type Error = my_crate::Error; fn encrypt(key: &Self::Key, plaintext: &[u8]) -> (Vec, Array) { // ... generate nonce, encrypt } fn encrypt_with_nonce( key: &Self::Key, nonce: &Array, plaintext: &[u8], ) -> Vec { // ... encrypt with given nonce } fn decrypt( key: &Self::Key, nonce: &Array, ciphertext: &[u8], ) -> Result, Self::Error> { // ... decrypt } } ``` 3. Add `pub mod my_aead;` to `src/encryption.rs` (feature-gated) 4. Add to the feature table in README.md ### Adding a New MAC Primitive 1. Add the dependency to `Cargo.toml` (optional, feature-gated) 2. Create a module in `src/mac/`: ```rust // src/mac/my_mac.rs use super::MacPrimitive; pub enum MyMac {} impl MacPrimitive for MyMac { type Key = my_crate::Key; type Tag = [u8; 32]; // or appropriate size type Error = my_crate::Error; fn mac(key: &Self::Key, message: &[u8]) -> Self::Tag { // ... compute MAC } fn verify(key: &Self::Key, message: &[u8], tag: &Self::Tag) -> Result<(), Self::Error> { // ... verify MAC (use constant-time comparison!) } } ``` 3. Add `pub mod my_mac;` to `src/mac.rs` (feature-gated) 4. Add to the feature table in README.md ### Adding a New Codec Implement `Encode` (and optionally `Decode`, `Canonical`): ```rust pub enum MyCbor {} impl> Encode for MyCbor { type Error = minicbor::encode::Error; fn encode(value: &T) -> Result, Self::Error> { minicbor::to_vec(value) } } impl Canonical for MyCbor {} ``` ## Testing ### Property-Based Tests We use [bolero](https://github.com/camshaft/bolero) for property-based testing: ```bash # Run property tests (randomized) cargo test --test digest # Run with more iterations BOLERO_RANDOM_ITERATIONS=10000 cargo test --test digest # Fuzz a specific test (requires cargo-bolero) cargo bolero test digest::digest_deterministic ``` ### Doc Tests ```bash cargo test --doc ``` ## Code Style - `#![no_std]` — avoid std, use `alloc` when needed - `#![forbid(unsafe_code)]` — no unsafe in this crate - Prefer `expect("reason")` over `unwrap()` - Manual trait impls to avoid spurious bounds on phantom types - Feature-gate optional dependencies - Prefer `pub mod` over `pub use` for submodules ## Commit Messages ``` : Co-Authored-By: ... ``` Types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`