use crate::WireError;
use crate::cipher::{DataKey, IvStash};
use crate::mac::{Mac, verify_mac_field_recording};
#[must_use = "an Unverified value is unauthenticated until you call verify()"]
pub struct Unverified<T> {
inner: T,
computed: Mac,
mac_field: String,
lastmodified: String,
leaves_fed: usize,
}
impl<T> Unverified<T> {
pub fn new(
inner: T,
computed: Mac,
mac_field: impl Into<String>,
lastmodified: impl Into<String>,
leaves_fed: usize,
) -> Self {
Self {
inner,
computed,
mac_field: mac_field.into(),
lastmodified: lastmodified.into(),
leaves_fed,
}
}
pub fn computed_mac(&self) -> &Mac {
&self.computed
}
pub fn leaves_fed(&self) -> usize {
self.leaves_fed
}
pub fn verify(self, key: &DataKey) -> Result<T, WireError> {
self.verify_recording(key, None)
}
pub fn verify_recording(
self,
key: &DataKey,
stash: Option<&mut IvStash>,
) -> Result<T, WireError> {
if self.leaves_fed == 0 {
return Err(WireError::MacMismatch);
}
verify_mac_field_recording(
key,
&self.mac_field,
&self.lastmodified,
&self.computed,
stash,
)?;
Ok(self.inner)
}
pub fn verify_allowing_empty(self, key: &DataKey) -> Result<T, WireError> {
verify_mac_field_recording(
key,
&self.mac_field,
&self.lastmodified,
&self.computed,
None,
)?;
Ok(self.inner)
}
pub fn into_inner_ignoring_mac(self) -> T {
self.inner
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Unverified<U> {
Unverified {
inner: f(self.inner),
computed: self.computed,
mac_field: self.mac_field,
lastmodified: self.lastmodified,
leaves_fed: self.leaves_fed,
}
}
}
impl<T> std::fmt::Debug for Unverified<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Unverified")
.field("computed", &self.computed)
.field("leaves_fed", &self.leaves_fed)
.field("value", &"***")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::leaf::Plaintext;
use crate::mac::{MacAccumulator, seal_mac_field};
fn key() -> DataKey {
DataKey::from_bytes(&[5u8; 32]).expect("32")
}
fn wrapped(contents: &[&str], ts: &str) -> (Unverified<Vec<String>>, DataKey) {
let k = key();
let mut acc = MacAccumulator::new(false);
for c in contents {
acc.feed(&Plaintext::string(*c));
}
let fed = acc.leaves_fed();
let mac = acc.finish();
let field = seal_mac_field(&k, &mac, ts, None).expect("seal");
let tree: Vec<String> = contents.iter().map(|s| (*s).to_string()).collect();
(Unverified::new(tree, mac, field, ts, fed), k)
}
#[test]
fn a_matching_mac_releases_the_value() {
let (u, k) = wrapped(&["a", "b"], "2026-08-18T00:00:00Z");
assert_eq!(u.verify(&k).expect("verify"), vec!["a", "b"]);
}
#[test]
fn a_wrong_key_does_not_release_the_value() {
let (u, _) = wrapped(&["a"], "2026-08-18T00:00:00Z");
let other = DataKey::from_bytes(&[6u8; 32]).expect("32");
assert_eq!(u.verify(&other), Err(WireError::MacUndecryptable));
}
#[test]
fn a_zero_leaf_verification_is_refused_as_vacuous() {
let (u, k) = wrapped(&[], "2026-08-18T00:00:00Z");
assert_eq!(u.leaves_fed(), 0);
assert_eq!(u.verify(&k), Err(WireError::MacMismatch));
}
#[test]
fn an_explicitly_empty_document_can_still_be_accepted() {
let (u, k) = wrapped(&[], "2026-08-18T00:00:00Z");
assert!(u.verify_allowing_empty(&k).is_ok());
}
#[test]
fn the_ignore_mac_escape_works_and_is_named() {
let (u, _) = wrapped(&["a"], "2026-08-18T00:00:00Z");
assert_eq!(u.into_inner_ignoring_mac(), vec!["a"]);
}
#[test]
fn map_preserves_the_marker_and_the_denominator() {
let (u, k) = wrapped(&["a", "b"], "2026-08-18T00:00:00Z");
let mapped = u.map(|v| v.len());
assert_eq!(mapped.leaves_fed(), 2);
assert_eq!(mapped.verify(&k).expect("verify"), 2);
}
#[test]
fn debug_never_prints_the_wrapped_value() {
let (u, _) = wrapped(&["hunter2"], "2026-08-18T00:00:00Z");
let shown = format!("{u:?}");
assert!(
!shown.contains("hunter2"),
"Unverified Debug leaked plaintext: {shown}"
);
assert!(shown.contains("leaves_fed: 1"));
}
}