Skip to main content

structured_ltv/pki/
participants.rs

1//! Source-derived actors for all-token LT or one preceding-archive obligation.
2
3use super::origin::Origin;
4use super::{AuthorityClosure, AuthorityClosureError, AuthorityNode, CheckedPath, PathStatuses};
5use crate::source::{Location, Query, ScopeError, Snapshot};
6use crate::timestamp::{ObligationError, TimestampPaths, TimestampScope};
7use core::marker::PhantomData;
8use structured_asn1::memory::{HashWork, MemoryError, OwnedVec, Work};
9use structured_pkix::path::EffectiveAnchor;
10
11/// Original actor requiring validation data, independent of selected path reuse.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Participant<N> {
14    /// Actual selected root signer.
15    Signer(N),
16    /// Actual incorporated countersignature, including one without signed
17    /// properties of its own.
18    Counter {
19        /// Original child signature.
20        signature: N,
21        /// Original counter property.
22        property: N,
23    },
24    /// Original protected credential and an authority used by its handler.
25    Credential {
26        /// Signature protecting the credential.
27        signature: N,
28        /// Original credential payload.
29        node: N,
30        /// Ordinal in the handler's authority path set, or zero for a direct/format-specific authority.
31        authority: usize,
32    },
33    /// An actual timestamp in the original whole-signature/archive scope.
34    Timestamp {
35        /// Original timestamp property.
36        property: N,
37        /// Original encapsulated token element.
38        token: N,
39    },
40}
41
42/// Actual X.509 requirements from already associated participant evidence.
43#[derive(Clone, Copy)]
44pub enum ParticipantAuthority<'path, 'document> {
45    /// Every member and existing anchor of this actual checked path is needed.
46    Path(&'path CheckedPath<'document>),
47    /// Explicit direct anchor; its existing certificate is material, but the
48    /// anchor never becomes a non-anchor revocation obligation.
49    Anchor(&'path EffectiveAnchor<'document>),
50    /// Explicit credential-format evidence needed no X.509 path. This does NOT
51    /// prove absence/incorporation of other format-specific validation material.
52    CertificateFree,
53}
54
55/// Actual certificate requirements of an authenticated credential format.
56/// These are explicit format facts, not a fallback from missing evidence.
57#[derive(Clone, Copy)]
58pub enum Authorities<'path, 'document> {
59    /// All independently checked authority paths used by authentication.
60    /// An empty slice is incomplete provider evidence, not certificate-free.
61    Paths(&'path [&'path CheckedPath<'document>]),
62    /// The credential was authenticated directly by this explicitly selected
63    /// effective trust anchor. Incorporate its existing certificate when needed;
64    /// do not invent a non-anchor chain or a revocation obligation for it.
65    DirectAnchor(&'path EffectiveAnchor<'document>),
66    /// This concrete format authenticated the original credential without
67    /// requiring X.509 certificate paths. Its format-specific evidence remains
68    /// mandatory; this does not establish any other required validation material.
69    CertificateFree,
70}
71
72/// The format layer's binding of a signature to its signing certificate's path.
73///
74/// Implemented on the format's own receipt, so a later check can require that
75/// very receipt rather than an equivalent one. Only the issuer derives a
76/// census from it ([`participants`] takes the issuer value).
77pub trait SignerReceipt<'path, 'document> {
78    /// Original node identity.
79    type Id: Copy;
80    /// Stable document identity.
81    type Document: Copy;
82    /// Exact document generation.
83    type Generation: Copy;
84
85    /// The selected signature in its exact source generation.
86    fn signature(&self) -> Location<Self::Id, Self::Document, Self::Generation>;
87    /// The checked signer path, or `None` when the format has not established
88    /// the signing certificate or its signed path restrictions.
89    fn path(&self) -> Option<&'path CheckedPath<'document>>;
90}
91
92/// One incorporated countersignature and its signer's checked path.
93#[derive(Clone, Copy)]
94pub struct Counter<'path, 'document, N> {
95    /// Original child signature.
96    pub signature: N,
97    /// Original counter property.
98    pub property: N,
99    /// Checked path of the child's actual signer.
100    pub path: &'path CheckedPath<'document>,
101}
102
103/// The format layer's complete census of incorporated countersignatures.
104///
105/// It lists every original counter in source order, never a successful subset.
106pub trait CounterReceipts<'path, 'document> {
107    /// Original node identity.
108    type Id: Copy;
109    /// Stable document identity.
110    type Document: Copy;
111    /// Exact document generation.
112    type Generation: Copy;
113
114    /// Original root source and generation under which all counters were visited.
115    fn snapshot(&self) -> Snapshot<Self::Document, Self::Generation>;
116    /// Root signature whose complete incorporated counters were checked.
117    fn signature(&self) -> Self::Id;
118    /// Number of countersignatures in the census.
119    fn len(&self) -> usize;
120    /// Whether the census is empty.
121    fn is_empty(&self) -> bool {
122        self.len() == 0
123    }
124    /// The countersignature at this census ordinal.
125    ///
126    /// # Panics
127    /// When `ordinal` is not below [`Self::len`].
128    fn counter(&self, ordinal: usize) -> Counter<'path, 'document, Self::Id>;
129}
130
131/// One authenticated credential: its payload, protecting signature and authorities.
132#[derive(Clone, Copy)]
133pub struct Credential<'path, 'document, N> {
134    /// Signature protecting the credential.
135    pub signature: N,
136    /// Original credential payload.
137    pub node: N,
138    /// Actual authority inputs of its authentication.
139    pub authorities: Authorities<'path, 'document>,
140}
141
142/// Every authenticated credential of one signature, in original order.
143pub trait CredentialGroup<'path, 'document> {
144    /// Original node identity.
145    type Id: Copy;
146
147    /// Signature whose protected properties hold these credentials.
148    fn signature(&self) -> Self::Id;
149    /// Number of authenticated credentials.
150    fn len(&self) -> usize;
151    /// Whether the group is empty.
152    fn is_empty(&self) -> bool {
153        self.len() == 0
154    }
155    /// The credential at this ordinal.
156    ///
157    /// # Panics
158    /// When `ordinal` is not below [`Self::len`].
159    fn credential(&self, ordinal: usize) -> Credential<'path, 'document, Self::Id>;
160}
161
162/// The format layer's complete credential census of a root and its counters.
163///
164/// Counter groups appear in census order and only for counters that carry
165/// credentials; every other counter stays in the counter census.
166pub trait CredentialReceipts<'path, 'document> {
167    /// Original node identity.
168    type Id: Copy;
169    /// Stable document identity.
170    type Document: Copy;
171    /// Exact document generation.
172    type Generation: Copy;
173    /// One signature's credentials.
174    type Group: CredentialGroup<'path, 'document, Id = Self::Id>;
175
176    /// Original root source and generation for the whole participant set.
177    fn snapshot(&self) -> Snapshot<Self::Document, Self::Generation>;
178    /// Original root signature, independent of every credential issuer.
179    fn signature(&self) -> Self::Id;
180    /// The root's credentials, if it has a credential property.
181    fn root(&self) -> Option<&Self::Group>;
182    /// Number of counters with credentials.
183    fn counters(&self) -> usize;
184    /// The counter child signature and its credentials at this ordinal.
185    ///
186    /// # Panics
187    /// When `ordinal` is not below [`Self::counters`].
188    fn counter(&self, ordinal: usize) -> (Self::Id, &Self::Group);
189}
190
191/// One source actor and its actual authority, not a caller-supplied path count.
192pub struct ParticipantRequirement<'path, 'document, N> {
193    participant: Participant<N>,
194    authority: ParticipantAuthority<'path, 'document>,
195}
196
197impl<'path, 'document, N: Copy> ParticipantRequirement<'path, 'document, N> {
198    /// Original root/counter/credential/earlier-token source.
199    #[must_use]
200    pub fn participant(&self) -> Participant<N> {
201        self.participant
202    }
203    /// Actual path/anchor or explicit non-X509 format authority.
204    #[must_use]
205    pub fn authority(&self) -> ParticipantAuthority<'path, 'document> {
206        self.authority
207    }
208}
209
210/// All source-derived actor paths for ONE original validation-data scope.
211/// Paths shared by several actors remain separately attributed. Status-input
212/// closure, placement/coverage, format-specific material and trust are separate;
213/// this object cannot assert a baseline grade or turn negative facts into Good.
214/// It keeps the issuer `I` of the timestamp census it was derived from.
215pub struct ParticipantRequirements<'path, 'document, N, D, G, I> {
216    scope: TimestampScope<N, D, G>,
217    signature: N,
218    entries: OwnedVec<ParticipantRequirement<'path, 'document, N>>,
219    origins: Origins<'path>,
220    issuer: PhantomData<I>,
221}
222
223struct Origins<'a> {
224    signer: Origin<'a>,
225    counters: Origin<'a>,
226    credentials: Origin<'a>,
227    timestamps: Origin<'a>,
228}
229
230impl<'path, 'document, N: Copy, D: Copy, G: Copy, I>
231    ParticipantRequirements<'path, 'document, N, D, G, I>
232{
233    /// Whole selected signature or exact preceding-archive scope.
234    #[must_use]
235    pub fn scope(&self) -> TimestampScope<N, D, G> {
236        self.scope
237    }
238    /// Original selected root signature.
239    #[must_use]
240    pub fn signature(&self) -> N {
241        self.signature
242    }
243    /// Every participant authority, preserving signer/counter/credential/token order.
244    #[must_use]
245    pub fn entries(&self) -> &[ParticipantRequirement<'path, 'document, N>] {
246        &self.entries
247    }
248
249    /// Whether this census was derived from exactly this credential receipt.
250    /// Constant-time identity only: the caller admits its current query first;
251    /// no authentication is replayed.
252    #[must_use]
253    pub fn same_credentials<C>(&self, credentials: &C) -> bool {
254        self.origins.credentials.matches(credentials)
255    }
256
257    /// Whether this census was derived from this exact complete TSA-path
258    /// receipt. The current caller must admit this metadata query first.
259    #[must_use]
260    pub fn same_timestamps(&self, timestamps: &TimestampPaths<'_, '_, '_, N, D, G, I>) -> bool {
261        self.origins.timestamps.matches(timestamps)
262    }
263
264    /// Require the exact live input receipts that produced this complete
265    /// participant census. Equivalent IDs, a different valid path/provider
266    /// context or independently collected credentials do not establish that
267    /// relation. This is a current-operation metadata query, not a replay of
268    /// parsing, crypto, credential authentication or catalogue allocation.
269    pub fn require_inputs<S, C, R, Q>(
270        &self,
271        signer: &S,
272        counters: &C,
273        credentials: &R,
274        timestamps: &TimestampPaths<'_, '_, '_, N, D, G, I>,
275        query: &Q,
276    ) -> Result<(), ParticipantError<N>>
277    where
278        D: Eq,
279        G: Eq,
280        Q: Query<Id = N, Document = D, Generation = G>,
281    {
282        let signature = self.signature;
283        if self.scope.location().snapshot != query.snapshot() {
284            return Err(ParticipantError::Foreign { signature });
285        }
286        // One dispatch plus the four constant-time identity comparisons.
287        query.admit(signature, 5)?;
288        if !self.origins.signer.matches(signer)
289            || !self.origins.counters.matches(counters)
290            || !self.same_credentials(credentials)
291            || !self.same_timestamps(timestamps)
292        {
293            return Err(ParticipantError::Foreign { signature });
294        }
295        Ok(())
296    }
297}
298
299/// Insufficient/mixed inputs and resource errors remain distinct.
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum ParticipantError<N> {
302    /// Timestamp obligations omitted the complete actual counter-source census.
303    CounterTimestamps,
304    /// Counter timestamp membership or its scope refused.
305    Obligation(ObligationError<N>),
306    /// Current original source/generation/operation refused.
307    Scope(ScopeError<N>),
308    /// A proof belongs to another root signature or source generation.
309    Foreign {
310        /// Expected original root signature.
311        signature: N,
312    },
313    /// The original selected root key/certificate or its signed path digest
314    /// restrictions were not established. No partial participant set escapes.
315    UnprovenSigner,
316    /// An authenticated credential reported an empty authority path set: that
317    /// is incomplete provider evidence, distinct from certificate-free proof.
318    MissingAuthorities {
319        /// Original credential payload.
320        node: N,
321    },
322    /// Traversal, identity comparison or retained metadata was not admitted.
323    Resource {
324        /// Original actor or enclosing scope source.
325        node: N,
326        /// Actual current-operation refusal.
327        cause: MemoryError,
328    },
329}
330
331/// Complete source-actor requirements in the query's identity domain.
332pub type ParticipantResult<'path, 'document, Q, I> = Result<
333    ParticipantRequirements<
334        'path,
335        'document,
336        <Q as Query>::Id,
337        <Q as Query>::Document,
338        <Q as Query>::Generation,
339        I,
340    >,
341    ParticipantError<<Q as Query>::Id>,
342>;
343
344impl<N> From<ScopeError<N>> for ParticipantError<N> {
345    fn from(cause: ScopeError<N>) -> Self {
346        Self::Scope(cause)
347    }
348}
349
350/// Complete participant roots and every reachable validation authority. The
351/// original participant attribution is retained even when several actors use
352/// the same path. This is input completeness, not Good status or a B-LTA grade.
353pub struct ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I> {
354    requirements: &'requirements ParticipantRequirements<'view, 'document, N, D, G, I>,
355    authorities: AuthorityClosure<'view, 'certificate, 'document>,
356}
357
358impl<'requirements, 'view, 'certificate, 'document, N, D, G, I>
359    ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>
360{
361    /// Complete original signer/counter/credential/earlier-token requirements.
362    #[must_use]
363    pub fn requirements(
364        &self,
365    ) -> &'requirements ParticipantRequirements<'view, 'document, N, D, G, I> {
366        self.requirements
367    }
368
369    /// Complete per-path inputs and their original actual status authorities.
370    #[must_use]
371    pub fn authorities(&self) -> &AuthorityClosure<'view, 'certificate, 'document> {
372        &self.authorities
373    }
374}
375
376/// A missing actor input is distinct from a negative authenticated status.
377#[derive(Debug)]
378pub enum ParticipantClosureError<N> {
379    /// Source/generation/current operation refused before input processing.
380    Scope(ScopeError<N>),
381    /// The requirements belong to another original source generation.
382    Foreign,
383    /// One root is required per Path actor, preserving source order; direct
384    /// anchors and certificate-free credentials do not acquire a status root.
385    Count,
386    /// The root was substituted with a different live checked-path receipt.
387    Path {
388        /// Original participant-requirement ordinal.
389        participant: usize,
390    },
391    /// Original status-authority closure could not be established.
392    Authority(AuthorityClosureError),
393    /// Current input traversal refused before its work.
394    Resource(MemoryError),
395}
396
397/// The closure of one census, or the refusal to close it.
398pub type ClosureResult<'requirements, 'view, 'certificate, 'document, N, D, G, I> = Result<
399    ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
400    ParticipantClosureError<N>,
401>;
402
403impl<'path, 'document, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, I>
404    ParticipantRequirements<'path, 'document, N, D, G, I>
405{
406    /// Associate EVERY source-derived Path actor with its complete status inputs,
407    /// then follow the existing CRL/OCSP authority kernel. Evaluation labels are
408    /// explicit original inputs, not guessed from a claimed signing time or
409    /// token order; their usability and historical trust remain separate checks.
410    /// Paths may legitimately repeat for several actors or distinct evaluation
411    /// labels. Direct anchors retain their separate certificate-material
412    /// obligation. No parse, provider, hash, status evaluation or signature is
413    /// repeated.
414    pub fn close_statuses<'requirements, 'view, 'certificate, Q>(
415        &'requirements self,
416        roots: &'view [&'view PathStatuses<'view, 'certificate, 'document>],
417        supplied: &'view [AuthorityNode<'view, 'certificate, 'document>],
418        query: &Q,
419    ) -> ClosureResult<'requirements, 'view, 'certificate, 'document, N, D, G, I>
420    where
421        Q: Query<Id = N, Document = D, Generation = G>,
422        'path: 'view,
423    {
424        if self.scope.location().snapshot != query.snapshot() {
425            return Err(ParticipantClosureError::Foreign);
426        }
427        query
428            .admit(self.signature, 1)
429            .map_err(ParticipantClosureError::Scope)?;
430        let work = query.execution();
431        let mut cursor = 0;
432        for (participant, entry) in self.entries.iter().enumerate() {
433            work.charge_work(1)
434                .map_err(ParticipantClosureError::Resource)?;
435            let ParticipantAuthority::Path(path) = entry.authority else {
436                continue;
437            };
438            let Some(root) = roots.get(cursor) else {
439                return Err(ParticipantClosureError::Count);
440            };
441            if !core::ptr::eq(path, root.path()) {
442                return Err(ParticipantClosureError::Path { participant });
443            }
444            cursor += 1;
445        }
446        if cursor != roots.len() {
447            return Err(ParticipantClosureError::Count);
448        }
449        let authorities = super::close_authorities(roots, supplied, work)
450            .map_err(ParticipantClosureError::Authority)?;
451        Ok(ParticipantClosure {
452            requirements: self,
453            authorities,
454        })
455    }
456}
457
458struct Collect<'path, 'document, N, W: Work> {
459    entries: OwnedVec<ParticipantRequirement<'path, 'document, N>, W>,
460}
461
462impl<'path, 'document, N: Copy, W: Work> Collect<'path, 'document, N, W> {
463    fn push<Q: Query<Id = N, Work = W>>(
464        &mut self,
465        participant: Participant<N>,
466        authority: ParticipantAuthority<'path, 'document>,
467        node: N,
468        query: &Q,
469    ) -> Result<(), ParticipantError<N>> {
470        query.admit(node, 2)?;
471        self.entries
472            .try_push(ParticipantRequirement {
473                participant,
474                authority,
475            })
476            .map_err(|cause| ParticipantError::Resource { node, cause })
477    }
478
479    fn credentials<Q: Query<Id = N, Work = W>, R: CredentialGroup<'path, 'document, Id = N>>(
480        &mut self,
481        credentials: &R,
482        query: &Q,
483    ) -> Result<(), ParticipantError<N>> {
484        for ordinal in 0..credentials.len() {
485            query.admit(credentials.signature(), 2)?;
486            let credential = credentials.credential(ordinal);
487            let node = credential.node;
488            let participant = |authority| Participant::Credential {
489                signature: credential.signature,
490                node,
491                authority,
492            };
493            match credential.authorities {
494                // The enum contract makes an empty path set incomplete provider
495                // evidence; letting it through would drop the credential's actor.
496                Authorities::Paths([]) => {
497                    return Err(ParticipantError::MissingAuthorities { node });
498                }
499                Authorities::Paths(paths) => {
500                    for (index, path) in paths.iter().enumerate() {
501                        self.push(
502                            participant(index),
503                            ParticipantAuthority::Path(path),
504                            node,
505                            query,
506                        )?;
507                    }
508                }
509                Authorities::DirectAnchor(anchor) => self.push(
510                    participant(0),
511                    ParticipantAuthority::Anchor(anchor),
512                    node,
513                    query,
514                )?,
515                Authorities::CertificateFree => self.push(
516                    participant(0),
517                    ParticipantAuthority::CertificateFree,
518                    node,
519                    query,
520                )?,
521            }
522        }
523        Ok(())
524    }
525}
526
527// Credential groups are an ordered SUBSET of the full original counters.
528// A linear merge preserves source identity without quadratic rescanning or
529// another retained hash index. Unmatched foreign groups cannot disappear.
530fn counter_and_role_requirements<'path, 'document, C, R, Q, I>(
531    collect: &mut Collect<'path, 'document, Q::Id, Q::Work>,
532    counters: &C,
533    credentials: &R,
534    timestamps: &TimestampPaths<'path, '_, 'document, Q::Id, Q::Document, Q::Generation, I>,
535    query: &Q,
536) -> Result<(), ParticipantError<Q::Id>>
537where
538    Q: Query,
539    C: CounterReceipts<'path, 'document, Id = Q::Id>,
540    R: CredentialReceipts<'path, 'document, Id = Q::Id>,
541{
542    let signature = counters.signature();
543    let mut ordinal = 0;
544    while ordinal < counters.len() {
545        query.admit(signature, 2)?;
546        let selected = timestamps
547            .required()
548            .counter_in_scope(ordinal, query)
549            .map_err(ParticipantError::Obligation)?;
550        let counter = counters.counter(ordinal);
551        ordinal += 1;
552        if !selected {
553            continue;
554        }
555        collect.push(
556            Participant::Counter {
557                signature: counter.signature,
558                property: counter.property,
559            },
560            ParticipantAuthority::Path(counter.path),
561            counter.property,
562            query,
563        )?;
564    }
565    if let Some(root) = credentials.root() {
566        collect.credentials(root, query)?;
567    }
568    let mut credential = 0;
569    let mut ordinal = 0;
570    while ordinal < counters.len() && credential < credentials.counters() {
571        query.admit(signature, 2)?;
572        let (child, group) = credentials.counter(credential);
573        let same = query
574            .equal(counters.counter(ordinal).signature, child)
575            .map_err(|cause| ParticipantError::Resource {
576                node: signature,
577                cause,
578            })?;
579        if same {
580            if timestamps
581                .required()
582                .counter_in_scope(ordinal, query)
583                .map_err(ParticipantError::Obligation)?
584            {
585                collect.credentials(group, query)?;
586            }
587            credential += 1;
588        }
589        ordinal += 1;
590    }
591    if credential != credentials.counters() {
592        return Err(ParticipantError::Foreign { signature });
593    }
594    Ok(())
595}
596
597/// Derive the complete actor set from actual source-bound receipts.
598/// Root certificate binding, the complete incorporated-counter set, every
599/// protected credential group and every original token in the explicit scope
600/// must already have completed their corresponding shared checks. The SAME
601/// kernel serves all-token LT and6.3(aa)'s preceding-archive obligations; only
602/// the latter excludes later counters/credentials and the current/later TSA
603/// using the SAME original archive coverage, not token success counts.
604/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
605///
606/// No provider, parse, certificate hash or signature is repeated. A late
607/// source/admission refusal releases all partial output; direct anchors and
608/// certificate-free format proofs are not silently treated as ordinary paths.
609///
610/// The census names neither receipt type, and the receipt traits are open to
611/// any implementation, so requiring the issuer value is what keeps a caller of
612/// the format from deriving the format's census from receipts of its own.
613pub fn participants<'path, 'document, S, C, R, Q, I>(
614    _issuer: I,
615    signer: &'path S,
616    counters: &'path C,
617    credentials: &'path R,
618    timestamps: &'path TimestampPaths<'path, '_, 'document, Q::Id, Q::Document, Q::Generation, I>,
619    query: &Q,
620) -> ParticipantResult<'path, 'document, Q, I>
621where
622    Q: Query,
623    S: SignerReceipt<
624            'path,
625            'document,
626            Id = Q::Id,
627            Document = Q::Document,
628            Generation = Q::Generation,
629        >,
630    C: CounterReceipts<
631            'path,
632            'document,
633            Id = Q::Id,
634            Document = Q::Document,
635            Generation = Q::Generation,
636        >,
637    R: CredentialReceipts<
638            'path,
639            'document,
640            Id = Q::Id,
641            Document = Q::Document,
642            Generation = Q::Generation,
643        >,
644{
645    let selected = signer.signature();
646    let signature = selected.node;
647    let scope = timestamps.required().scope();
648    if selected.snapshot != query.snapshot()
649        || counters.snapshot() != query.snapshot()
650        || credentials.snapshot() != query.snapshot()
651        || scope.location().snapshot != query.snapshot()
652    {
653        return Err(ParticipantError::Foreign { signature });
654    }
655    query.admit(signature, 1)?;
656    let entries = query.execution().vector();
657    for actual in [
658        counters.signature(),
659        credentials.signature(),
660        timestamps.required().signature(),
661    ] {
662        let cost = signature
663            .hash_work()
664            .and_then(|cost| {
665                cost.checked_add(actual.hash_work()?)
666                    .ok_or(MemoryError::WorkExceeded)
667            })
668            .map_err(|cause| ParticipantError::Resource {
669                node: signature,
670                cause,
671            })?;
672        query
673            .execution()
674            .charge_work(cost)
675            .map_err(|cause| ParticipantError::Resource {
676                node: signature,
677                cause,
678            })?;
679        if actual != signature {
680            return Err(ParticipantError::Foreign { signature });
681        }
682    }
683    let Some(path) = signer.path() else {
684        return Err(ParticipantError::UnprovenSigner);
685    };
686    if !timestamps
687        .required()
688        .includes_counters(counters, query)
689        .map_err(ParticipantError::Scope)?
690    {
691        return Err(ParticipantError::CounterTimestamps);
692    }
693    let mut collect = Collect { entries };
694    collect.push(
695        Participant::Signer(signature),
696        ParticipantAuthority::Path(path),
697        signature,
698        query,
699    )?;
700    counter_and_role_requirements(&mut collect, counters, credentials, timestamps, query)?;
701    for path in timestamps.paths() {
702        query.admit(signature, 2)?;
703        let source = path.actor().verified();
704        collect.push(
705            Participant::Timestamp {
706                property: source.property(),
707                token: source.token().node,
708            },
709            ParticipantAuthority::Path(path.path()),
710            source.property(),
711            query,
712        )?;
713    }
714    query.admit(signature, 1)?;
715    Ok(ParticipantRequirements {
716        scope,
717        signature,
718        entries: collect.entries.into_owned(),
719        origins: Origins {
720            signer: Origin::of(signer),
721            counters: Origin::of(counters),
722            credentials: Origin::of(credentials),
723            timestamps: Origin::of(timestamps),
724        },
725        issuer: PhantomData,
726    })
727}