Skip to main content

structured_cms/evidence/
input.rs

1//! Accounted RFC4998 reduced-tree computation; no cryptographic engine here.
2
3use super::objects::Sources;
4use super::{ArchiveTimeStamp, FIELD};
5use crate::{
6    Error,
7    digest::{DigestBatch, DigestError, DigestMemo},
8    stream::{self, Sink, Source, StreamError},
9    timestamp::{ComputedInput, ImprintContext, ImprintProof, InputFailure},
10};
11use core::cell::{OnceCell, RefCell};
12use core::cmp::Ordering;
13use structured_asn1::{
14    digest::Resolution,
15    memory::{MemoryError, OwnedVec, SortError, Work},
16};
17use structured_pkix::algorithm::digest::{self, AsnDigest};
18
19/// Whether the proof names one object or exactly an entire document group.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Membership {
22    /// Exactly one supplied object must occur in the first partial tree.
23    Object,
24    /// All supplied objects, and no others, form the first partial tree.
25    Group,
26}
27
28/// Original acquisition/provider failure or a completed unusable ERS recipe.
29#[derive(Debug)]
30pub enum InputError<SE, PE> {
31    /// Original source/depth/storage/work refusal.
32    Processing(Error),
33    /// The selected provider failed; its cause is preserved unchanged.
34    Provider(PE),
35    /// Actual covered source did not reach successful EOF.
36    Source(SE),
37    /// The selected provider does not implement this stream or written identity.
38    Unsupported,
39    /// A written digest violates its algorithm-specific syntax.
40    MalformedAlgorithm,
41    /// A supplied group is empty, or Object was given several sources.
42    ObjectCount,
43    /// The selected object/group is absent from the first partial tree.
44    Membership,
45    /// A declared tree hash has the wrong full output width.
46    HashWidth,
47    /// Provider output contradicts its own promised width.
48    OutputWidth,
49    /// A previous-sequence receipt names another record or another new chain.
50    ForeignRenewal,
51    /// Cached input came from another provider, identity or request ordinal.
52    ForeignDigest,
53}
54
55/// Closed tree recipe over complete caller-supplied object streams.
56///
57/// Sources are visited once per required record algorithm, never buffered into
58/// a whole-document allocation. Group equality is a multiset equality: repeated
59/// identical objects/hashes are not collapsed.
60pub struct ObjectInput<'input, 'document, S> {
61    stamp: &'input ArchiveTimeStamp<'document>,
62    sources: &'input mut [S],
63    membership: Membership,
64    previous: Option<&'input super::PriorSequence<'input, 'document>>,
65}
66impl<'i, 'd, S> ObjectInput<'i, 'd, S> {
67    /// Selects one original archive timestamp and the actual complete input.
68    #[must_use]
69    pub fn new(
70        stamp: &'i ArchiveTimeStamp<'d>,
71        sources: &'i mut [S],
72        membership: Membership,
73    ) -> Self {
74        Self {
75            stamp,
76            sources,
77            membership,
78            previous: None,
79        }
80    }
81    /// Selects hash-tree renewal for this prefix's actual first new timestamp.
82    /// RFC4998 §5.2(4) hashes dataHash then previousSequenceHash, unsorted;
83    /// Figure4 and reported erratum7411 do not enable a silent alternative.
84    #[must_use]
85    pub fn renewed(
86        stamp: &'i ArchiveTimeStamp<'d>,
87        sources: &'i mut [S],
88        membership: Membership,
89        previous: &'i super::PriorSequence<'i, 'd>,
90    ) -> Self {
91        Self {
92            stamp,
93            sources,
94            membership,
95            previous: Some(previous),
96        }
97    }
98}
99
100pub(super) struct Hasher<'p, 'i, P: AsnDigest<'p>, W: Work> {
101    pub(super) provider: &'p P,
102    pub(super) identity: &'i P::Identity,
103    pub(super) work: W,
104    pub(super) at: u64,
105}
106impl<'p, P: AsnDigest<'p>, W: Work> Hasher<'p, '_, P, W> {
107    pub(super) fn resource<SE>(&self, cause: MemoryError) -> InputError<SE, P::Error> {
108        InputError::Processing(Error::resource(cause, FIELD, self.at))
109    }
110    pub(super) fn start<SE>(&self) -> Result<P::State, InputError<SE, P::Error>> {
111        match self
112            .provider
113            .start(self.identity, &self.work)
114            .map_err(InputError::Provider)?
115        {
116            Resolution::Supported(state) => Ok(state),
117            Resolution::Unsupported => Err(InputError::Unsupported),
118            Resolution::Malformed => Err(InputError::MalformedAlgorithm),
119        }
120    }
121    pub(super) fn finish<SE>(
122        &self,
123        state: P::State,
124    ) -> Result<P::Output, InputError<SE, P::Error>> {
125        let output = self
126            .provider
127            .finish(state, &self.work)
128            .map_err(InputError::Provider)?;
129        if let Some(width) = self.provider.width(self.identity)
130            && output.as_ref().len() != width
131        {
132            return Err(InputError::OutputWidth);
133        }
134        Ok(output)
135    }
136    pub(super) fn update<SE>(
137        &self,
138        state: &mut P::State,
139        bytes: &[u8],
140    ) -> Result<(), InputError<SE, P::Error>> {
141        self.work
142            .charge_work(1)
143            .map_err(|cause| self.resource(cause))?;
144        self.provider
145            .update(state, bytes, &self.work)
146            .map_err(InputError::Provider)
147    }
148    pub(super) fn hash_source<S: Source>(
149        &self,
150        source: &mut S,
151    ) -> Result<P::Output, InputError<S::Error, P::Error>> {
152        let mut state = self.start()?;
153        let mut sink = Feed {
154            provider: self.provider,
155            state: &mut state,
156        };
157        stream::run(source, &mut sink, &self.work, FIELD, self.at).map_err(
158            |cause| match cause {
159                StreamError::Source(cause) => InputError::Source(cause),
160                StreamError::Callback(cause) => InputError::Provider(cause),
161                StreamError::Processing(cause) => InputError::Processing(cause),
162            },
163        )?;
164        self.finish(state)
165    }
166    fn hash_list<SE>(
167        &self,
168        values: &[Hash<'_, P::Output>],
169    ) -> Result<P::Output, InputError<SE, P::Error>> {
170        let mut state = self.start()?;
171        for value in values {
172            self.update(&mut state, value.bytes())?;
173        }
174        self.finish(state)
175    }
176    fn compare<SE>(&self, a: &[u8], b: &[u8]) -> Result<Ordering, InputError<SE, P::Error>> {
177        self.work
178            .charge_work(1 + a.len().min(b.len()) as u64)
179            .map_err(|cause| self.resource(cause))?;
180        Ok(a.cmp(b))
181    }
182}
183
184struct Feed<'p, 's, P: AsnDigest<'p>> {
185    provider: &'p P,
186    state: &'s mut P::State,
187}
188impl<'p, P: AsnDigest<'p>> Sink for Feed<'p, '_, P> {
189    type Error = P::Error;
190    fn write<W: Work>(&mut self, bytes: &[u8], work: &W) -> Result<(), Self::Error> {
191        self.provider.update(self.state, bytes, work)
192    }
193}
194
195enum Hash<'d, O> {
196    Borrowed(&'d [u8]),
197    Joined(OwnedVec<u8>),
198    Root(Root<'d, O>),
199}
200impl<O: AsRef<[u8]>> Hash<'_, O> {
201    fn bytes(&self) -> &[u8] {
202        match self {
203            Self::Borrowed(bytes) => bytes,
204            Self::Joined(bytes) => bytes,
205            Self::Root(output) => output.output().as_ref(),
206        }
207    }
208}
209
210impl<P, PE, S: Source> ComputedInput<P, PE> for ObjectInput<'_, '_, S> {
211    type Error = InputError<S::Error, PE>;
212    fn compute<'context, 'reader, 'document>(
213        &mut self,
214        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
215    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
216    where
217        P: AsnDigest<'document, Error = PE>,
218    {
219        let stamp = self.stamp;
220        let (root, written) = self.compute_root(context).map_err(InputFailure::Source)?;
221        let identity = written.as_ref().unwrap_or(context.identity());
222        let origin = stamp
223            .original
224            .reader_in(context.reader())
225            .map_err(|cause| InputFailure::Source(InputError::Processing(cause)))?;
226        let execution = origin.execution_query(context.reader());
227        context
228            .compare_derived(|expected, _| {
229                context
230                    .provider()
231                    .matches(identity, root.output(), expected, &execution)
232                    .map_err(InputError::Provider)
233            })
234            .map_err(InputFailure::Refused)
235    }
236}
237impl<S: Source> ObjectInput<'_, '_, S> {
238    fn compute_root<'p, P: AsnDigest<'p>>(
239        &mut self,
240        context: &ImprintContext<'_, 'p, P, InputError<S::Error, P::Error>>,
241    ) -> ResolvedRoot<'_, P::Output, P::Identity, S::Error, P::Error> {
242        if self.sources.is_empty()
243            || (self.membership == Membership::Object && self.sources.len() != 1)
244        {
245            return Err(InputError::ObjectCount);
246        }
247        if !membership_shape(self.stamp, self.sources.len(), self.membership) {
248            return Err(InputError::Membership);
249        }
250        if let Some(previous) = self.previous
251            && !previous.contains(self.stamp)
252        {
253            return Err(InputError::ForeignRenewal);
254        }
255        let origin = self
256            .stamp
257            .original
258            .reader_in(context.reader())
259            .map_err(InputError::Processing)?;
260        let work = origin.execution_query(context.reader());
261        // Explicit identity can differ from the RFC3161 imprint algorithm:
262        // RFC4998 §4.2(5). Absence uses the context identity without cloning it.
263        let provider = context.provider();
264        let written = if let Some(identifier) = self.stamp.algorithm {
265            match digest::resolve(provider, &identifier, &work)
266                .map_err(|cause| InputError::Processing(Error::pkix(cause)))?
267            {
268                Resolution::Supported(identity) => Some(identity),
269                Resolution::Unsupported => return Err(InputError::Unsupported),
270                Resolution::Malformed => return Err(InputError::MalformedAlgorithm),
271            }
272        } else {
273            None
274        };
275        let identity = written.as_ref().unwrap_or(context.identity());
276        let hasher = Hasher {
277            provider,
278            identity,
279            work,
280            at: self.stamp.original.element.span().start,
281        };
282        let output = root(
283            &hasher,
284            self.stamp,
285            self.sources,
286            self.membership,
287            self.previous,
288        )?;
289        Ok((output, written))
290    }
291}
292
293fn root<'s, 'p, P: AsnDigest<'p>, W: Work, S: Source>(
294    hasher: &Hasher<'p, '_, P, W>,
295    stamp: &'s ArchiveTimeStamp<'_>,
296    sources: &mut [S],
297    membership: Membership,
298    previous: Option<&super::PriorSequence<'_, '_>>,
299) -> RootResult<'s, P::Output, S::Error, P::Error> {
300    validate_tree(hasher, stamp)?;
301    let mut objects = hasher.work.vector();
302    objects
303        .try_reserve_exact(sources.len())
304        .map_err(|cause| hasher.resource(cause))?;
305    let previous_hash = previous.map(|prior| prior.hash(hasher)).transpose()?;
306    for source in sources {
307        let mut output = hasher.hash_source(source)?;
308        if let Some(previous) = &previous_hash {
309            let mut state = hasher.start()?;
310            hasher.update(&mut state, output.as_ref())?;
311            hasher.update(&mut state, previous.as_ref())?;
312            output = hasher.finish(state)?;
313        }
314        objects
315            .try_push(Hash::Root(Root::Owned(output)))
316            .map_err(|cause| hasher.resource(cause))?;
317    }
318    fold(hasher, stamp, objects, membership)
319}
320
321fn validate_tree<'p, P: AsnDigest<'p>, W: Work, SE>(
322    hasher: &Hasher<'p, '_, P, W>,
323    stamp: &ArchiveTimeStamp<'_>,
324) -> Result<(), InputError<SE, P::Error>> {
325    // Validate every declared width before provider state or source I/O.
326    let width = hasher.provider.width(hasher.identity);
327    if let Some(tree) = &stamp.tree {
328        for level in tree {
329            hasher
330                .work
331                .charge_work(1)
332                .map_err(|cause| hasher.resource(cause))?;
333            for hash in &level.hashes {
334                hasher
335                    .work
336                    .charge_work(1)
337                    .map_err(|cause| hasher.resource(cause))?;
338                if let Some(width) = width
339                    && hash.len() != width
340                {
341                    return Err(InputError::HashWidth);
342                }
343            }
344        }
345    }
346    Ok(())
347}
348
349pub(super) fn membership_shape(
350    stamp: &ArchiveTimeStamp<'_>,
351    objects: usize,
352    membership: Membership,
353) -> bool {
354    let Some(tree) = &stamp.tree else {
355        return objects == 1;
356    };
357    let Some(first) = tree.first() else {
358        return false;
359    };
360    if first.hashes.is_empty() {
361        return false;
362    }
363    membership != Membership::Group || first.hashes.len() == objects
364}
365
366pub(super) enum Root<'s, O> {
367    Owned(O),
368    Borrowed(&'s O),
369}
370type RootResult<'s, O, SE, PE> = Result<Root<'s, O>, InputError<SE, PE>>;
371type ResolvedRoot<'s, O, I, SE, PE> = Result<(Root<'s, O>, Option<I>), InputError<SE, PE>>;
372impl<O> Root<'_, O> {
373    pub(super) fn output(&self) -> &O {
374        match self {
375            Self::Owned(o) => o,
376            Self::Borrowed(o) => o,
377        }
378    }
379}
380
381fn fold<'s, 'p, P: AsnDigest<'p>, W: Work, SE>(
382    hasher: &Hasher<'p, '_, P, W>,
383    stamp: &'s ArchiveTimeStamp<'_>,
384    mut objects: OwnedVec<Hash<'s, P::Output>, W>,
385    membership: Membership,
386) -> Result<Root<'s, P::Output>, InputError<SE, P::Error>> {
387    sort(hasher, &mut objects)?;
388    let Some(tree) = &stamp.tree else {
389        if objects.len() != 1 {
390            return Err(InputError::Membership);
391        }
392        let Some(Hash::Root(output)) = objects.pop() else {
393            unreachable!("one complete output")
394        };
395        return Ok(output);
396    };
397    let Some(first) = tree.first() else {
398        return Err(InputError::Membership);
399    };
400    let mut siblings = hasher.work.vector();
401    for hash in &first.hashes {
402        siblings
403            .try_push(read_hash(hash, hasher)?)
404            .map_err(|cause| hasher.resource(cause))?;
405    }
406    sort(hasher, &mut siblings)?;
407    if membership == Membership::Group {
408        if objects.len() != siblings.len() {
409            return Err(InputError::Membership);
410        }
411        for (object, hash) in objects.iter().zip(siblings.iter()) {
412            if hasher.compare(object.bytes(), hash.bytes())? != Ordering::Equal {
413                return Err(InputError::Membership);
414            }
415        }
416    } else {
417        let mut found = false;
418        for hash in &siblings {
419            if hasher.compare(objects[0].bytes(), hash.bytes())? == Ordering::Equal {
420                found = true;
421                break;
422            }
423        }
424        if !found {
425            return Err(InputError::Membership);
426        }
427    }
428    let mut current = if siblings.len() == 1 {
429        // A singleton leaf is already the root (RFC4998 §4.2 steps3–4).
430        let Some(Hash::Root(output)) = objects.pop() else {
431            unreachable!("matched singleton has one actual output")
432        };
433        output
434    } else {
435        Root::Owned(hasher.hash_list(&siblings)?)
436    };
437    drop(objects);
438    for level in &tree[1..] {
439        siblings.clear();
440        siblings
441            .try_push(Hash::Root(current))
442            .map_err(|cause| hasher.resource(cause))?;
443        for hash in &level.hashes {
444            siblings
445                .try_push(read_hash(hash, hasher)?)
446                .map_err(|cause| hasher.resource(cause))?;
447        }
448        sort(hasher, &mut siblings)?;
449        current = if siblings.len() == 1 {
450            let Some(Hash::Root(output)) = siblings.pop() else {
451                unreachable!("computed child")
452            };
453            output
454        } else {
455            Root::Owned(hasher.hash_list(&siblings)?)
456        };
457    }
458    Ok(current)
459}
460
461fn read_hash<'d, 'p, P: AsnDigest<'p>, W: Work, SE>(
462    hash: &crate::Octets<'d>,
463    hasher: &Hasher<'p, '_, P, W>,
464) -> Result<Hash<'d, P::Output>, InputError<SE, P::Error>> {
465    if let Some(bytes) = hash.as_one() {
466        return Ok(Hash::Borrowed(bytes));
467    }
468    let mut joined = hasher.work.vector();
469    joined
470        .try_reserve_exact(hash.len())
471        .map_err(|cause| hasher.resource(cause))?;
472    for piece in hash.pieces() {
473        hasher
474            .work
475            .charge_work(1)
476            .map_err(|cause| hasher.resource(cause))?;
477        joined
478            .try_extend_from_slice(piece.as_bytes())
479            .map_err(|cause| hasher.resource(cause))?;
480    }
481    Ok(Hash::Joined(joined.into_owned()))
482}
483
484fn sort<'p, P: AsnDigest<'p>, W: Work, SE>(
485    hasher: &Hasher<'p, '_, P, W>,
486    siblings: &mut OwnedVec<Hash<'_, P::Output>, W>,
487) -> Result<(), InputError<SE, P::Error>> {
488    siblings
489        .try_sort_unstable_by(|a, b| hasher.compare(a.bytes(), b.bytes()))
490        .map_err(|cause| match cause {
491            SortError::Resource(cause) => hasher.resource(cause),
492            SortError::Comparison(cause) => cause,
493        })
494}
495
496// Record verification hashes every actual object once through DigestBatch's
497// full-identity fan-out. Nested joined TSTInfo checks borrow those outputs;
498// identities/outputs cannot borrow that temporary inner information source.
499pub(super) struct MemoSet<'p, P: AsnDigest<'p>> {
500    pub(super) provider: &'p P,
501    pub(super) memos: OwnedVec<DigestMemo<'p, P>>,
502}
503trait RootComputation<P> {
504    type Output: AsRef<[u8]>;
505    type Error;
506    type SourceError;
507    fn belongs_to_provider(&self, provider: &P) -> bool;
508    fn root<'s>(
509        &'s self,
510        stamp: &'s ArchiveTimeStamp<'_>,
511        ordinal: usize,
512        membership: Membership,
513        previous: Option<&super::PriorSequence<'_, '_>>,
514        query: &structured_asn1::Reader<'_>,
515    ) -> RootResult<'s, Self::Output, Self::SourceError, Self::Error>;
516    fn matches<W: Work>(
517        &self,
518        ordinal: usize,
519        output: &Self::Output,
520        expected: &[u8],
521        work: &W,
522    ) -> Result<bool, InputError<Self::SourceError, Self::Error>>;
523}
524impl<'p, P: AsnDigest<'p>> RootComputation<P> for MemoSet<'p, P> {
525    type Output = P::Output;
526    type Error = P::Error;
527    type SourceError = core::convert::Infallible;
528    fn belongs_to_provider(&self, provider: &P) -> bool {
529        core::ptr::eq(self.provider, provider)
530    }
531    fn root<'s>(
532        &'s self,
533        stamp: &'s ArchiveTimeStamp<'_>,
534        ordinal: usize,
535        membership: Membership,
536        previous: Option<&super::PriorSequence<'_, '_>>,
537        query: &structured_asn1::Reader<'_>,
538    ) -> Result<Root<'s, P::Output>, InputError<core::convert::Infallible, P::Error>> {
539        let origin = stamp
540            .original
541            .reader_in(query)
542            .map_err(InputError::Processing)?;
543        let work = origin.execution_query(query);
544        if self.memos.is_empty() || (membership == Membership::Object && self.memos.len() != 1) {
545            return Err(InputError::ObjectCount);
546        }
547        let mut objects = work.vector();
548        objects
549            .try_reserve_exact(self.memos.len())
550            .map_err(|cause| {
551                InputError::Processing(Error::resource(
552                    cause,
553                    FIELD,
554                    stamp.original.element.span().start,
555                ))
556            })?;
557        let first = self.memos[0]
558            .for_request(ordinal, &work)
559            .map_err(map_digest)?
560            .ok_or(InputError::ForeignDigest)?;
561        if !first.finished().from_provider(self.provider) {
562            return Err(InputError::ForeignDigest);
563        }
564        if let Some(prior) = previous
565            && !prior.contains(stamp)
566        {
567            return Err(InputError::ForeignRenewal);
568        }
569        let hasher = Hasher {
570            provider: self.provider,
571            identity: first.identity(),
572            work,
573            at: stamp.original.element.span().start,
574        };
575        validate_tree(&hasher, stamp)?;
576        let previous = previous.map(|prior| prior.hash(&hasher)).transpose()?;
577        for memo in &self.memos {
578            let value = memo
579                .for_request(ordinal, &hasher.work)
580                .map_err(map_digest)?
581                .ok_or(InputError::ForeignDigest)?;
582            if !value.finished().from_provider(self.provider) {
583                return Err(InputError::ForeignDigest);
584            }
585            if self
586                .provider
587                .compare(value.identity(), hasher.identity, &hasher.work)
588                .map_err(InputError::Provider)?
589                != Ordering::Equal
590            {
591                return Err(InputError::ForeignDigest);
592            }
593            let output = if let Some(previous) = &previous {
594                let mut state = hasher.start()?;
595                hasher.update(&mut state, value.as_bytes())?;
596                hasher.update(&mut state, previous.as_ref())?;
597                Root::Owned(hasher.finish(state)?)
598            } else {
599                Root::Borrowed(value.finished().output())
600            };
601            objects
602                .try_push(Hash::Root(output))
603                .map_err(|cause| hasher.resource(cause))?;
604        }
605        fold(&hasher, stamp, objects, membership)
606    }
607    fn matches<W: Work>(
608        &self,
609        ordinal: usize,
610        output: &P::Output,
611        expected: &[u8],
612        work: &W,
613    ) -> Result<bool, InputError<core::convert::Infallible, P::Error>> {
614        let first = self.memos[0]
615            .for_request(ordinal, work)
616            .map_err(map_digest)?
617            .ok_or(InputError::ForeignDigest)?;
618        self.provider
619            .matches(first.identity(), output, expected, work)
620            .map_err(InputError::Provider)
621    }
622}
623
624// Initialize on the first actual object-input request, AFTER the same token's
625// common schema/profile/key preparation. Successful output is immutable and
626// owned by OnceCell; later trees borrow it without refcount, Clone or replay.
627pub(super) struct LazyMemos<'s, 'p, P: AsnDigest<'p>, S: Sources<'p, P>> {
628    provider: &'p P,
629    algorithms: &'s [structured_pkix::algorithm::digest::Algorithm],
630    sources: RefCell<S>,
631    initialized: OnceCell<MemoSet<'p, P>>,
632}
633impl<'s, 'p, P: AsnDigest<'p>, S: Sources<'p, P>> LazyMemos<'s, 'p, P, S> {
634    pub(super) fn new(
635        provider: &'p P,
636        algorithms: &'s [structured_pkix::algorithm::digest::Algorithm],
637        sources: S,
638    ) -> Self {
639        Self {
640            provider,
641            algorithms,
642            sources: RefCell::new(sources),
643            initialized: OnceCell::new(),
644        }
645    }
646    fn ready(
647        &self,
648        stamp: &ArchiveTimeStamp<'_>,
649        query: &structured_asn1::Reader<'_>,
650    ) -> Result<&MemoSet<'p, P>, InputError<S::Error, P::Error>> {
651        if let Some(memos) = self.initialized.get() {
652            return Ok(memos);
653        }
654        let origin = stamp
655            .original
656            .reader_in(query)
657            .map_err(InputError::Processing)?;
658        let work = origin.execution_query(query);
659        let at = stamp.original.element.span().start;
660        let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, at));
661        let mut sources = self
662            .sources
663            .try_borrow_mut()
664            .map_err(|_| InputError::ForeignDigest)?;
665        let mut memos = work.vector();
666        memos.try_reserve_exact(sources.len()).map_err(resource)?;
667        for index in 0..sources.len() {
668            let mut identities = work.vector();
669            for algorithm in self.algorithms {
670                work.charge_work(1).map_err(resource)?;
671                let identity = self
672                    .provider
673                    .identity(*algorithm)
674                    .ok_or(InputError::Unsupported)?;
675                identities.try_push(identity).map_err(resource)?;
676            }
677            let batch =
678                DigestBatch::prepare(self.provider, identities.into_owned(), &work, FIELD, at)
679                    .map_err(map_digest)?;
680            let memo = sources
681                .run(index, batch, &work)
682                .map_err(|cause| match cause {
683                    StreamError::Source(cause) => InputError::Source(cause),
684                    StreamError::Callback(cause) => map_digest(cause),
685                    StreamError::Processing(cause) => InputError::Processing(cause),
686                })?;
687            memos.try_push(memo).map_err(resource)?;
688        }
689        self.initialized
690            .set(MemoSet {
691                provider: self.provider,
692                memos: memos.into_owned(),
693            })
694            .map_err(|_| InputError::ForeignDigest)?;
695        self.initialized.get().ok_or(InputError::ForeignDigest)
696    }
697}
698impl<'p, P: AsnDigest<'p>, S: Sources<'p, P>> RootComputation<P> for LazyMemos<'_, 'p, P, S> {
699    type Output = P::Output;
700    type Error = P::Error;
701    type SourceError = S::Error;
702    fn belongs_to_provider(&self, provider: &P) -> bool {
703        core::ptr::eq(self.provider, provider)
704    }
705    fn root<'s>(
706        &'s self,
707        stamp: &'s ArchiveTimeStamp<'_>,
708        ordinal: usize,
709        membership: Membership,
710        previous: Option<&super::PriorSequence<'_, '_>>,
711        query: &structured_asn1::Reader<'_>,
712    ) -> Result<Root<'s, P::Output>, InputError<S::Error, P::Error>> {
713        self.ready(stamp, query)?
714            .root(stamp, ordinal, membership, previous, query)
715            .map_err(widen)
716    }
717    fn matches<W: Work>(
718        &self,
719        ordinal: usize,
720        output: &P::Output,
721        expected: &[u8],
722        work: &W,
723    ) -> Result<bool, InputError<S::Error, P::Error>> {
724        self.initialized
725            .get()
726            .ok_or(InputError::ForeignDigest)?
727            .matches(ordinal, output, expected, work)
728            .map_err(widen)
729    }
730}
731fn widen<SE, PE>(cause: InputError<core::convert::Infallible, PE>) -> InputError<SE, PE> {
732    match cause {
733        InputError::Processing(cause) => InputError::Processing(cause),
734        InputError::Provider(cause) => InputError::Provider(cause),
735        InputError::Source(never) => match never {},
736        InputError::Unsupported => InputError::Unsupported,
737        InputError::MalformedAlgorithm => InputError::MalformedAlgorithm,
738        InputError::ObjectCount => InputError::ObjectCount,
739        InputError::Membership => InputError::Membership,
740        InputError::HashWidth => InputError::HashWidth,
741        InputError::OutputWidth => InputError::OutputWidth,
742        InputError::ForeignRenewal => InputError::ForeignRenewal,
743        InputError::ForeignDigest => InputError::ForeignDigest,
744    }
745}
746pub(super) struct CachedInput<'s, 'd, F> {
747    pub(super) stamp: &'s ArchiveTimeStamp<'d>,
748    pub(super) memos: &'s F,
749    pub(super) ordinal: usize,
750    pub(super) membership: Membership,
751    pub(super) previous: Option<&'s super::PriorSequence<'s, 'd>>,
752}
753impl<P, PE, SE, F: RootComputation<P, Error = PE, SourceError = SE>> ComputedInput<P, PE>
754    for CachedInput<'_, '_, F>
755{
756    type Error = InputError<SE, PE>;
757    fn compute<'context, 'reader, 'document>(
758        &mut self,
759        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
760    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
761    where
762        P: AsnDigest<'document, Error = PE>,
763    {
764        if !self.memos.belongs_to_provider(context.provider()) {
765            return Err(InputFailure::Source(InputError::ForeignDigest));
766        }
767        let root = self
768            .memos
769            .root(
770                self.stamp,
771                self.ordinal,
772                self.membership,
773                self.previous,
774                context.reader(),
775            )
776            .map_err(InputFailure::Source)?;
777        context
778            .compare_derived(|expected, work| {
779                self.memos
780                    .matches(self.ordinal, root.output(), expected, work)
781            })
782            .map_err(InputFailure::Refused)
783    }
784}
785fn map_digest<SE, PE>(cause: DigestError<PE>) -> InputError<SE, PE> {
786    match cause {
787        DigestError::Processing(cause) => InputError::Processing(cause),
788        DigestError::Callback(cause) => InputError::Provider(cause),
789        DigestError::OutputWidth { .. } => InputError::OutputWidth,
790        DigestError::Unstreamable => InputError::Unsupported,
791    }
792}