| Crates.io | redoubt-test-utils |
| lib.rs | redoubt-test-utils |
| version | 0.1.0-rc.3 |
| created_at | 2026-01-21 06:51:16.554526+00 |
| updated_at | 2026-01-21 07:09:34.109194+00 |
| description | Test utilities for Redoubt crates |
| homepage | |
| repository | https://github.com/memparanoid/redoubt |
| max_upload_size | |
| id | 2058491 |
| size | 14,787 |
Systematic encryption-at-rest for in-memory sensitive data in Rust.
Redoubt is a Rust library for storing secrets in memory. Encrypted at rest, zeroized on drop, accessible only when you need them.
no_std compatible โ Works in embedded and WASI environmentscargo add redoubt --features full
Or in your Cargo.toml:
[dependencies]
redoubt = { version = "0.1.0-rc.3", features = ["full"] }
use redoubt::alloc::{RedoubtArray, RedoubtString};
use redoubt::codec::RedoubtCodec;
use redoubt::secret::RedoubtSecret;
use redoubt::vault::cipherbox;
use redoubt::zero::RedoubtZero;
#[cipherbox(Wallet)]
#[derive(Default, RedoubtCodec, RedoubtZero)]
struct WalletData {
seed: RedoubtArray<u8, 32>,
mnemonic: RedoubtString,
counter: RedoubtSecret<u64>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut wallet = Wallet::new();
// Open the box and modify secrets
wallet.open_mut(|w| {
w.seed.replace_from_mut_array(&mut [0u8; 32]);
let mut mnemonic = String::from("abandon abandon ...");
w.mnemonic.replace_from_mut_string(&mut mnemonic);
w.counter.replace(&mut 0u64);
Ok(())
})?;
// Box is re-encrypted here
// Read-only access
wallet.open(|w| {
let _ = w.counter.as_ref();
Ok(())
})?;
// Field-level access (decrypts only that field)
wallet.open_counter_mut(|counter| {
let mut next = *counter.as_ref() + 1;
counter.replace(&mut next);
Ok(())
})?;
// Leak to use outside closure scope
{
let seed = wallet.leak_seed()?;
// use seed...
} // seed is zeroized on drop
Ok(())
}
open / open_mutAccess the entire decrypted struct. Re-encrypts when the closure returns:
wallet.open(|w| {
// read-only access to all fields
Ok(())
})?;
wallet.open_mut(|w| {
// read-write access to all fields
Ok(())
})?;
open_<field> / open_<field>_mutAccess individual fields without decrypting the entire struct:
wallet.open_seed(|seed| {
// read-only access to seed
Ok(())
})?;
wallet.open_seed_mut(|seed| {
// read-write access to seed
Ok(())
})?;
leak_<field>Get a field value outside the closure. Returns a ZeroizingGuard that wipes memory on drop:
let seed = wallet.leak_seed()?;
// use seed...
// seed is zeroized when dropped
Closures can return values. The return value is wrapped in a ZeroizingGuard that wipes memory on drop:
let counter = wallet.open_counter_mut(|c| {
let mut next = *c.as_ref() + 1;
c.replace(&mut next);
Ok(next)
})?; // Returns Result<ZeroizingGuard<u64>, CipherBoxError>
// counter is zeroized when dropped
Redoubt provides secure containers for different use cases:
use redoubt::alloc::{RedoubtArray, RedoubtString, RedoubtVec};
use redoubt::secret::RedoubtSecret;
// Fixed-size arrays (automatically zeroized on drop)
let mut api_key = RedoubtArray::<u8, 32>::new();
let mut signing_key = RedoubtArray::<u8, 64>::new();
// Dynamic collections (zeroized on realloc and drop)
let mut tokens = RedoubtVec::<u8>::new();
let mut password = RedoubtString::new();
// Primitives wrapped in Secret
let mut counter = RedoubtSecret::from(&mut 0u64);
let mut timestamp = RedoubtSecret::from(&mut 0i64);
RedoubtArray<T, N>: Fixed-size sensitive data (keys, hashes, seeds). Size known at compile time.RedoubtVec<T>: Variable-length byte arrays that may grow (encrypted tokens, variable-size keys).RedoubtString: Variable-length UTF-8 strings (passwords, mnemonics, API keys).RedoubtSecret<T>: Primitive types (u64, i32, bool) that need protection. Prevents accidental copies via controlled access.All sensitive data in #[cipherbox] structs MUST ultimately come from: RedoubtArray, RedoubtVec, RedoubtString, or RedoubtSecret.
These types were forensically validated (see forensics/README.md) to leave no traces during the encryption-at-rest workflow. You can compose them into nested structures, but the leaf values containing sensitive data must be these types. Using standard types (Vec<u8>, String, [u8; 32], u64) would leave unzeroized copies during encoding/decoding, defeating the security guarantees.
RedoubtVec / RedoubtString: Pre-zeroize old allocation before reallocation
extend_from_mut_slice, extend_from_mut_string (zeroize source)Vec/String leave copies in abandoned allocationsRedoubtArray: Prevents copies during assignment
let arr2 = arr1;) can leave copies on stackreplace_from_mut_array uses ptr::swap_nonoverlapping to exchange contents without intermediate copiesRedoubtSecret: Prevents accidental dereferencing of Copy types
as_ref()/as_mut() calls to access the inner valueu64 which implement Copy and could silently duplicate if accessed directlyDebug with REDACTED output (no accidental leaks in logs)Copy or Clone traits (prevents unintended copies of sensitive data)prctl and mlock, inaccessible to non-root memory dumpsCipherBox generates failure injection methods for testing error handling:
#[cipherbox(WalletBox, testing_feature = "test-utils")]
struct Wallet { /* ... */ }
// In tests:
let mut wallet = WalletBox::new();
wallet.set_failure_mode(WalletBoxFailureMode::FailOnNthOperation(2));
assert!(wallet.open(|_| Ok(())).is_ok()); // 1st succeeds
assert!(wallet.open(|_| Ok(())).is_err()); // 2nd fails
#[cfg(test)]testing_feature to export them conditionallySee examples/wallet/tests for a complete example.
| Platform | Protection level |
|---|---|
| Linux | Full (prctl, rlimit, mlock, mprotect) |
| macOS | Partial (mlock, mprotect) |
| Windows | Encryption only |
| WASI | Encryption only |
no_std |
Encryption only |
For detailed information about testing methodology and other interesting technical details, see INSIGHTS.md.
To run benchmarks:
cargo bench -p benchmarks --bench aegis128l
cargo bench -p benchmarks --bench alloc
cargo bench -p benchmarks --bench cipherbox
cargo bench -p benchmarks --bench codec
This project is licensed under the GNU General Public License v3.0-only.