evidence #
Type-level tags for cryptographic primitives.
evidence wraps cryptographic output (hashes, signatures, ciphertext) with phantom type parameters that track what was processed, which algorithm was used, and how the input was serialized — all at zero runtime cost.
The Problem #
Cryptographic code often looks like this:
fn verify_user(user_hash: [u8; 32], post_hash: [u8; 32]) {
// Oops! Easy to swap these by accident
if user_hash == post_hash { /* ... */ }
}
These are just byte arrays — the compiler can't help you distinguish a hash of a User from a hash of a Post, or a SHA-256 digest from a Blake3 digest.
The Solution #
With evidence, the type system tracks what was hashed/signed and how:
use evidence::digest::{Digest, sha2::Sha256};
use evidence::codec::Identity;
// These are incompatible types — can't be mixed up
let user_hash: Digest<User, Sha256, Identity> = Digest::hash(&user);
let post_hash: Digest<Post, Sha256, Identity> = Digest::hash(&post);
// Compile error: expected `Digest<User, ...>`, found `Digest<Post, ...>`
// verify_user(post_hash);
This prevents:
- Mixing up digests of different types
- Comparing hashes from different algorithms
- Accessing signed payloads before verification
- Confusing encodings (a CBOR hash != a JSON hash of the same value)
The supported types can be extended without changing this library. That said, contributions of common primitives are always welcome.
Usage #
use evidence::{
codec::Encode,
digest::{Digest, sha2::Sha256}
};
// Define your codec (or use the built-in Cbor / Json codecs)
enum MyCbor {}
impl<T: minicbor::Encode<()>> Encode<T> for MyCbor {
type Error = minicbor::encode::Error<core::convert::Infallible>;
fn encode(value: &T) -> Result<Vec<u8>, Self::Error> {
minicbor::to_vec(value)
}
}
// Now you can hash values
let digest: Digest<MyData, Sha256, MyCbor> = Digest::hash(&my_data);
// Access the raw bytes when needed
let bytes: &[u8] = digest.as_bytes();
Primitives #
Hash #
| Primitive | Feature | Output Size |
|---|---|---|
Sha256 |
sha2 |
32 bytes |
Sha384 |
sha2 |
48 bytes |
Sha512 |
sha2 |
64 bytes |
Sha3_224 |
sha3 |
28 bytes |
Sha3_256 |
sha3 |
32 bytes |
Sha3_384 |
sha3 |
48 bytes |
Sha3_512 |
sha3 |
64 bytes |
Keccak256 |
sha3 |
32 bytes |
Keccak512 |
sha3 |
64 bytes |
Blake3 |
blake3 |
32 bytes |
Signature #
| Primitive | Feature |
|---|---|
Ed25519 |
ed25519 |
Encryption (AEAD) #
| Primitive | Feature | Key Size | Nonce Size |
|---|---|---|---|
ChaCha20Poly1305 |
chacha20poly1305 |
32 bytes | 12 bytes |
Aes128Gcm |
aes-gcm |
16 bytes | 12 bytes |
Aes256Gcm |
aes-gcm |
32 bytes | 12 bytes |
MAC #
| Primitive | Feature | Tag Size |
|---|---|---|
HmacSha256 |
hmac |
32 bytes |
HmacSha384 |
hmac |
48 bytes |
HmacSha512 |
hmac |
64 bytes |
Features #
| Feature | Description |
|---|---|
sha2 |
SHA-256, SHA-384, SHA-512 |
sha3 |
SHA3-224/256/384/512, Keccak256/512 |
blake3 |
Blake3 |
ed25519 |
Ed25519 signatures |
hmac |
HMAC-SHA256/384/512 |
chacha20poly1305 |
ChaCha20-Poly1305 AEAD |
aes-gcm |
AES-128-GCM, AES-256-GCM |
minicbor |
Cbor codec Encode/Decode impls |
serde_json |
Json codec Encode/Decode impls (requires std) |
serde |
Serialize/Deserialize impls |
rkyv |
Zero-copy Archive/Serialize/Deserialize |
arbitrary |
Fuzzing support (arbitrary) |
bolero |
Fuzzing support (bolero) |
proptest |
Property testing (requires std) |
No features are enabled by default.
Cargo.toml #
[dependencies]
evidence = { version = "0.1", features = ["sha2"] }
# With signatures
evidence = { version = "0.1", features = ["sha2", "ed25519"] }
# With encryption
evidence = { version = "0.1", features = ["sha2", "chacha20poly1305"] }
MSRV #
The minimum supported Rust version is 1.90.
no_std #
This crate is #![no_std] compatible. It requires alloc for encoding.
License #
Licensed under either of Apache License, Version 2.0 or MIT license at your option.