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    /// Require the exact live input receipts that produced this complete
258    /// participant census. Equivalent IDs, a different valid path/provider
259    /// context or independently collected credentials do not establish that
260    /// relation. This is a current-operation metadata query, not a replay of
261    /// parsing, crypto, credential authentication or catalogue allocation.
262    pub fn require_inputs<S, C, R, Q>(
263        &self,
264        signer: &S,
265        counters: &C,
266        credentials: &R,
267        timestamps: &TimestampPaths<'_, '_, '_, N, D, G, I>,
268        query: &Q,
269    ) -> Result<(), ParticipantError<N>>
270    where
271        D: Eq,
272        G: Eq,
273        Q: Query<Id = N, Document = D, Generation = G>,
274    {
275        let signature = self.signature;
276        if self.scope.location().snapshot != query.snapshot() {
277            return Err(ParticipantError::Foreign { signature });
278        }
279        // One dispatch plus the four constant-time identity comparisons.
280        query.admit(signature, 5)?;
281        if !self.origins.signer.matches(signer)
282            || !self.origins.counters.matches(counters)
283            || !self.same_credentials(credentials)
284            || !self.origins.timestamps.matches(timestamps)
285        {
286            return Err(ParticipantError::Foreign { signature });
287        }
288        Ok(())
289    }
290}
291
292/// Insufficient/mixed inputs and resource errors remain distinct.
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
294pub enum ParticipantError<N> {
295    /// Timestamp obligations omitted the complete actual counter-source census.
296    CounterTimestamps,
297    /// Counter timestamp membership or its scope refused.
298    Obligation(ObligationError<N>),
299    /// Current original source/generation/operation refused.
300    Scope(ScopeError<N>),
301    /// A proof belongs to another root signature or source generation.
302    Foreign {
303        /// Expected original root signature.
304        signature: N,
305    },
306    /// The original selected root key/certificate or its signed path digest
307    /// restrictions were not established. No partial participant set escapes.
308    UnprovenSigner,
309    /// An authenticated credential reported an empty authority path set: that
310    /// is incomplete provider evidence, distinct from certificate-free proof.
311    MissingAuthorities {
312        /// Original credential payload.
313        node: N,
314    },
315    /// Traversal, identity comparison or retained metadata was not admitted.
316    Resource {
317        /// Original actor or enclosing scope source.
318        node: N,
319        /// Actual current-operation refusal.
320        cause: MemoryError,
321    },
322}
323
324/// Complete source-actor requirements in the query's identity domain.
325pub type ParticipantResult<'path, 'document, Q, I> = Result<
326    ParticipantRequirements<
327        'path,
328        'document,
329        <Q as Query>::Id,
330        <Q as Query>::Document,
331        <Q as Query>::Generation,
332        I,
333    >,
334    ParticipantError<<Q as Query>::Id>,
335>;
336
337impl<N> From<ScopeError<N>> for ParticipantError<N> {
338    fn from(cause: ScopeError<N>) -> Self {
339        Self::Scope(cause)
340    }
341}
342
343/// Complete participant roots and every reachable validation authority. The
344/// original participant attribution is retained even when several actors use
345/// the same path. This is input completeness, not Good status or a B-LTA grade.
346pub struct ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I> {
347    requirements: &'requirements ParticipantRequirements<'view, 'document, N, D, G, I>,
348    authorities: AuthorityClosure<'view, 'certificate, 'document>,
349}
350
351impl<'requirements, 'view, 'certificate, 'document, N, D, G, I>
352    ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>
353{
354    /// Complete original signer/counter/credential/earlier-token requirements.
355    #[must_use]
356    pub fn requirements(
357        &self,
358    ) -> &'requirements ParticipantRequirements<'view, 'document, N, D, G, I> {
359        self.requirements
360    }
361
362    /// Complete per-path inputs and their original actual status authorities.
363    #[must_use]
364    pub fn authorities(&self) -> &AuthorityClosure<'view, 'certificate, 'document> {
365        &self.authorities
366    }
367}
368
369/// A missing actor input is distinct from a negative authenticated status.
370#[derive(Debug)]
371pub enum ParticipantClosureError<N> {
372    /// Source/generation/current operation refused before input processing.
373    Scope(ScopeError<N>),
374    /// The requirements belong to another original source generation.
375    Foreign,
376    /// One root is required per Path actor, preserving source order; direct
377    /// anchors and certificate-free credentials do not acquire a status root.
378    Count,
379    /// The root was substituted with a different live checked-path receipt.
380    Path {
381        /// Original participant-requirement ordinal.
382        participant: usize,
383    },
384    /// Original status-authority closure could not be established.
385    Authority(AuthorityClosureError),
386    /// Current input traversal refused before its work.
387    Resource(MemoryError),
388}
389
390/// The closure of one census, or the refusal to close it.
391pub type ClosureResult<'requirements, 'view, 'certificate, 'document, N, D, G, I> = Result<
392    ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
393    ParticipantClosureError<N>,
394>;
395
396impl<'path, 'document, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, I>
397    ParticipantRequirements<'path, 'document, N, D, G, I>
398{
399    /// Associate EVERY source-derived Path actor with its complete status inputs,
400    /// then follow the existing CRL/OCSP authority kernel. Evaluation labels are
401    /// explicit original inputs, not guessed from a claimed signing time or
402    /// token order; their usability and historical trust remain separate checks.
403    /// Paths may legitimately repeat for several actors or distinct evaluation
404    /// labels. Direct anchors retain their separate certificate-material
405    /// obligation. No parse, provider, hash, status evaluation or signature is
406    /// repeated.
407    pub fn close_statuses<'requirements, 'view, 'certificate, Q>(
408        &'requirements self,
409        roots: &'view [&'view PathStatuses<'view, 'certificate, 'document>],
410        supplied: &'view [AuthorityNode<'view, 'certificate, 'document>],
411        query: &Q,
412    ) -> ClosureResult<'requirements, 'view, 'certificate, 'document, N, D, G, I>
413    where
414        Q: Query<Id = N, Document = D, Generation = G>,
415        'path: 'view,
416    {
417        if self.scope.location().snapshot != query.snapshot() {
418            return Err(ParticipantClosureError::Foreign);
419        }
420        query
421            .admit(self.signature, 1)
422            .map_err(ParticipantClosureError::Scope)?;
423        let work = query.execution();
424        let mut cursor = 0;
425        for (participant, entry) in self.entries.iter().enumerate() {
426            work.charge_work(1)
427                .map_err(ParticipantClosureError::Resource)?;
428            let ParticipantAuthority::Path(path) = entry.authority else {
429                continue;
430            };
431            let Some(root) = roots.get(cursor) else {
432                return Err(ParticipantClosureError::Count);
433            };
434            if !core::ptr::eq(path, root.path()) {
435                return Err(ParticipantClosureError::Path { participant });
436            }
437            cursor += 1;
438        }
439        if cursor != roots.len() {
440            return Err(ParticipantClosureError::Count);
441        }
442        let authorities = super::close_authorities(roots, supplied, work)
443            .map_err(ParticipantClosureError::Authority)?;
444        Ok(ParticipantClosure {
445            requirements: self,
446            authorities,
447        })
448    }
449}
450
451struct Collect<'path, 'document, N, W: Work> {
452    entries: OwnedVec<ParticipantRequirement<'path, 'document, N>, W>,
453}
454
455impl<'path, 'document, N: Copy, W: Work> Collect<'path, 'document, N, W> {
456    fn push<Q: Query<Id = N, Work = W>>(
457        &mut self,
458        participant: Participant<N>,
459        authority: ParticipantAuthority<'path, 'document>,
460        node: N,
461        query: &Q,
462    ) -> Result<(), ParticipantError<N>> {
463        query.admit(node, 2)?;
464        self.entries
465            .try_push(ParticipantRequirement {
466                participant,
467                authority,
468            })
469            .map_err(|cause| ParticipantError::Resource { node, cause })
470    }
471
472    fn credentials<Q: Query<Id = N, Work = W>, R: CredentialGroup<'path, 'document, Id = N>>(
473        &mut self,
474        credentials: &R,
475        query: &Q,
476    ) -> Result<(), ParticipantError<N>> {
477        for ordinal in 0..credentials.len() {
478            query.admit(credentials.signature(), 2)?;
479            let credential = credentials.credential(ordinal);
480            let node = credential.node;
481            let participant = |authority| Participant::Credential {
482                signature: credential.signature,
483                node,
484                authority,
485            };
486            match credential.authorities {
487                // The enum contract makes an empty path set incomplete provider
488                // evidence; letting it through would drop the credential's actor.
489                Authorities::Paths([]) => {
490                    return Err(ParticipantError::MissingAuthorities { node });
491                }
492                Authorities::Paths(paths) => {
493                    for (index, path) in paths.iter().enumerate() {
494                        self.push(
495                            participant(index),
496                            ParticipantAuthority::Path(path),
497                            node,
498                            query,
499                        )?;
500                    }
501                }
502                Authorities::DirectAnchor(anchor) => self.push(
503                    participant(0),
504                    ParticipantAuthority::Anchor(anchor),
505                    node,
506                    query,
507                )?,
508                Authorities::CertificateFree => self.push(
509                    participant(0),
510                    ParticipantAuthority::CertificateFree,
511                    node,
512                    query,
513                )?,
514            }
515        }
516        Ok(())
517    }
518}
519
520// Credential groups are an ordered SUBSET of the full original counters.
521// A linear merge preserves source identity without quadratic rescanning or
522// another retained hash index. Unmatched foreign groups cannot disappear.
523fn counter_and_role_requirements<'path, 'document, C, R, Q, I>(
524    collect: &mut Collect<'path, 'document, Q::Id, Q::Work>,
525    counters: &C,
526    credentials: &R,
527    timestamps: &TimestampPaths<'path, '_, 'document, Q::Id, Q::Document, Q::Generation, I>,
528    query: &Q,
529) -> Result<(), ParticipantError<Q::Id>>
530where
531    Q: Query,
532    C: CounterReceipts<'path, 'document, Id = Q::Id>,
533    R: CredentialReceipts<'path, 'document, Id = Q::Id>,
534{
535    let signature = counters.signature();
536    let mut ordinal = 0;
537    while ordinal < counters.len() {
538        query.admit(signature, 2)?;
539        let selected = timestamps
540            .required()
541            .counter_in_scope(ordinal, query)
542            .map_err(ParticipantError::Obligation)?;
543        let counter = counters.counter(ordinal);
544        ordinal += 1;
545        if !selected {
546            continue;
547        }
548        collect.push(
549            Participant::Counter {
550                signature: counter.signature,
551                property: counter.property,
552            },
553            ParticipantAuthority::Path(counter.path),
554            counter.property,
555            query,
556        )?;
557    }
558    if let Some(root) = credentials.root() {
559        collect.credentials(root, query)?;
560    }
561    let mut credential = 0;
562    let mut ordinal = 0;
563    while ordinal < counters.len() && credential < credentials.counters() {
564        query.admit(signature, 2)?;
565        let (child, group) = credentials.counter(credential);
566        let same = query
567            .equal(counters.counter(ordinal).signature, child)
568            .map_err(|cause| ParticipantError::Resource {
569                node: signature,
570                cause,
571            })?;
572        if same {
573            if timestamps
574                .required()
575                .counter_in_scope(ordinal, query)
576                .map_err(ParticipantError::Obligation)?
577            {
578                collect.credentials(group, query)?;
579            }
580            credential += 1;
581        }
582        ordinal += 1;
583    }
584    if credential != credentials.counters() {
585        return Err(ParticipantError::Foreign { signature });
586    }
587    Ok(())
588}
589
590/// Derive the complete actor set from actual source-bound receipts.
591/// Root certificate binding, the complete incorporated-counter set, every
592/// protected credential group and every original token in the explicit scope
593/// must already have completed their corresponding shared checks. The SAME
594/// kernel serves all-token LT and6.3(aa)'s preceding-archive obligations; only
595/// the latter excludes later counters/credentials and the current/later TSA
596/// using the SAME original archive coverage, not token success counts.
597/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
598///
599/// No provider, parse, certificate hash or signature is repeated. A late
600/// source/admission refusal releases all partial output; direct anchors and
601/// certificate-free format proofs are not silently treated as ordinary paths.
602///
603/// The census names neither receipt type, and the receipt traits are open to
604/// any implementation, so requiring the issuer value is what keeps a caller of
605/// the format from deriving the format's census from receipts of its own.
606pub fn participants<'path, 'document, S, C, R, Q, I>(
607    _issuer: I,
608    signer: &'path S,
609    counters: &'path C,
610    credentials: &'path R,
611    timestamps: &'path TimestampPaths<'path, '_, 'document, Q::Id, Q::Document, Q::Generation, I>,
612    query: &Q,
613) -> ParticipantResult<'path, 'document, Q, I>
614where
615    Q: Query,
616    S: SignerReceipt<
617            'path,
618            'document,
619            Id = Q::Id,
620            Document = Q::Document,
621            Generation = Q::Generation,
622        >,
623    C: CounterReceipts<
624            'path,
625            'document,
626            Id = Q::Id,
627            Document = Q::Document,
628            Generation = Q::Generation,
629        >,
630    R: CredentialReceipts<
631            'path,
632            'document,
633            Id = Q::Id,
634            Document = Q::Document,
635            Generation = Q::Generation,
636        >,
637{
638    let selected = signer.signature();
639    let signature = selected.node;
640    let scope = timestamps.required().scope();
641    if selected.snapshot != query.snapshot()
642        || counters.snapshot() != query.snapshot()
643        || credentials.snapshot() != query.snapshot()
644        || scope.location().snapshot != query.snapshot()
645    {
646        return Err(ParticipantError::Foreign { signature });
647    }
648    query.admit(signature, 1)?;
649    let entries = query.execution().vector();
650    for actual in [
651        counters.signature(),
652        credentials.signature(),
653        timestamps.required().signature(),
654    ] {
655        let cost = signature
656            .hash_work()
657            .and_then(|cost| {
658                cost.checked_add(actual.hash_work()?)
659                    .ok_or(MemoryError::WorkExceeded)
660            })
661            .map_err(|cause| ParticipantError::Resource {
662                node: signature,
663                cause,
664            })?;
665        query
666            .execution()
667            .charge_work(cost)
668            .map_err(|cause| ParticipantError::Resource {
669                node: signature,
670                cause,
671            })?;
672        if actual != signature {
673            return Err(ParticipantError::Foreign { signature });
674        }
675    }
676    let Some(path) = signer.path() else {
677        return Err(ParticipantError::UnprovenSigner);
678    };
679    if !timestamps
680        .required()
681        .includes_counters(counters, query)
682        .map_err(ParticipantError::Scope)?
683    {
684        return Err(ParticipantError::CounterTimestamps);
685    }
686    let mut collect = Collect { entries };
687    collect.push(
688        Participant::Signer(signature),
689        ParticipantAuthority::Path(path),
690        signature,
691        query,
692    )?;
693    counter_and_role_requirements(&mut collect, counters, credentials, timestamps, query)?;
694    for path in timestamps.paths() {
695        query.admit(signature, 2)?;
696        let source = path.actor().verified();
697        collect.push(
698            Participant::Timestamp {
699                property: source.property(),
700                token: source.token().node,
701            },
702            ParticipantAuthority::Path(path.path()),
703            source.property(),
704            query,
705        )?;
706    }
707    query.admit(signature, 1)?;
708    Ok(ParticipantRequirements {
709        scope,
710        signature,
711        entries: collect.entries.into_owned(),
712        origins: Origins {
713            signer: Origin::of(signer),
714            counters: Origin::of(counters),
715            credentials: Origin::of(credentials),
716            timestamps: Origin::of(timestamps),
717        },
718        issuer: PhantomData,
719    })
720}