Typelevel cryptographic algo tracking crates.io/crates/evidence
typelevel cryptography util
evidence HACKING.md
11 kB
Markdown

Hacking on Evidence #

Development Setup #

# 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:

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 #

# 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<T, P, C>                     │
│  ┌─────────────────────────────────────────────────┐   │
│  │  bytes: Array<u8, P::Size>                      │   │
│  │  _marker: PhantomData<fn() -> (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<S>             AsyncSigner<S, K>        Encode<T>       Decode<T>
    │                      │                      │                │
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:

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/:
// 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<u8, Self::Size> {
        // ... implementation
    }
}
  1. Add pub mod my_hash; to src/digest.rs (feature-gated)
  2. 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/:
// 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<MySig> 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<K: FutureForm> AsyncSigner<MySig, K> 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 { ... }
}
  1. Add pub mod my_sig; to src/signature.rs (feature-gated)
  2. 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/:
// 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<u8>, Array<u8, Self::NonceSize>) {
        // ... generate nonce, encrypt
    }

    fn encrypt_with_nonce(
        key: &Self::Key,
        nonce: &Array<u8, Self::NonceSize>,
        plaintext: &[u8],
    ) -> Vec<u8> {
        // ... encrypt with given nonce
    }

    fn decrypt(
        key: &Self::Key,
        nonce: &Array<u8, Self::NonceSize>,
        ciphertext: &[u8],
    ) -> Result<Vec<u8>, Self::Error> {
        // ... decrypt
    }
}
  1. Add pub mod my_aead; to src/encryption.rs (feature-gated)
  2. 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/:
// 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!)
    }
}
  1. Add pub mod my_mac; to src/mac.rs (feature-gated)
  2. Add to the feature table in README.md

Adding a New Codec #

Implement Encode<T> (and optionally Decode<T>, Canonical):

pub enum MyCbor {}

impl<T: minicbor::Encode<()>> Encode<T> for MyCbor {
    type Error = minicbor::encode::Error<Infallible>;

    fn encode(value: &T) -> Result<Vec<u8>, Self::Error> {
        minicbor::to_vec(value)
    }
}

impl Canonical for MyCbor {}

Testing #

Property-Based Tests #

We use bolero for property-based testing:

# 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 #

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 #

<type>: <subject>

<body>

Co-Authored-By: ...

Types: feat, fix, docs, refactor, test, chore