trust-protocol

RLTP Personhood Predicates

Real Life Trust Protocol — verifier-relative witnessing predicates

Abstract

This document defines how a verifier evaluates recognition assertions — encounter credentials presented by a subject — into three measures: how many of the asserting anchors the verifier already knows (P1), which signed instants those assertions carry (P2), and how many asserters a named group’s own admission discipline vouches for (P3). The evaluator is a deterministic, clock-free, pure function from a defined input model to a defined result model; the few obligations that inherently need verifier state — issuing and consuming possession challenges — are defined as verifier duties around the function, never inside it.

The name of this document names the need it serves — evidence of personhood where no authority is in reach — not a claim any predicate makes. No predicate here says “this is a human,” and no predicate here proves a meeting took place: a credential in a third party’s hands is a signed assertion of recognition, and its weight is exactly the weight of the key that signed it (Encounter §8). Each measure says: “from this verifier’s standpoint, with the roots it names, this anchor is asserted thus.” Personhood evidence in RLTP is verifier-relative and graph-rooted, where issuer-rooted systems make it absolute and certificate-shaped. Both the strength and the limit of that choice are stated in this document, in the same breath.

Status of This Document

This is an Editor’s Draft with no standing beyond its own argument. It is developed through the same adversarial convergence process as the other RLTP documents: every casting is reviewed in full by an independent adversarial reviewer, findings are triaged, and the document is recast — never patched — until a casting is judged blocker-free and compatibly implementable. The companion documents it builds on have met that criterion: the RLTP Encounter Layer 0.22, the RLTP Delivery Contract 0.17, the RLTP Membership Tasks 0.11, and the RLTP Access Layer 0.25.

This twelfth casting has been read by that process and is converged: round twelve returned no finding at any level — blocker, major, or minor — and closed with the reviewer’s explicit statement that nothing further stands in the way of convergence and that the document is compatibly implementable. Rounds one to eleven drew 7, 3, 3, 3, 3, 2, 2, 1, 2, 1, and 1 blocker-level findings; every conceptual answer has held since round three, and the later rounds narrowed through canonicalization and the possession profile. The ninth casting attempted a context-binding upgrade to a present-counterparty claim; review nine showed a verifier-stated value can be relayed unchanged, so the tenth withdrew the upgrade rather than keeping it unsound — possession establishes the holder claim and nothing else, and the channel-binding profile a present-counterparty verdict would need is Open Issue PP-6.

Known open questions are collected in Section 10. Feedback is welcome via the issues of the publication repository (github.com/real-life-org/trust-protocol).

1. Introduction (informative)

1.1 The two structural facts

Everything here follows from two properties of the RLTP encounter graph:

The graph is nowhere. No global view, no directory, no crawling. Every person holds their own edges; a verifier learns of a subject’s edges only because the subject presents them. This is a presentation model, not a lookup model: disclosure is the subject’s controlled act, a verifier can anchor its judgment only in roots it already holds, and every step beyond the first edge would disclose edges of third parties who were never asked — the hard boundary that decides which predicates can exist at all (Section 7).

Trust does not propagate. No transitive computation, no introducer model, no trust depth. An edge is one anchor’s signed recognition of another; what a set of edges means is decided by the verifier’s policy, never by the graph. This document defines measures, not thresholds.

1.2 What a presented credential is — and is not

Inside an enactment, the Encounter layer’s gates (records, challenges, issuance windows) bind a credential to a live exchange — for the two participants. None of that travels. A third party holds no enactment record and cannot check one (Encounter §5.6 is participant-local by design). What a third party can verify is exactly this: a key signed a recognition of this subject, following the credential form. Where the subject also presents its own matching step credential of the same enactment, the third party can run the Encounter §8 pair verification — including recomputing the shared binding from both challenges — and learn that both keys signed reciprocally over one recomputed descriptor (the reciprocal quality, 4.1). That is still not proof of a meeting: Encounter §8 states in as many words that colluding key holders can manufacture a consistent pair. This document therefore speaks of assertions throughout, and every measure’s strength reduces to the trustworthiness of the asserting keys.

1.3 Relation to issuer-rooted personhood

Issuer-rooted systems bind a boolean to a person: an accredited issuer certifies humanity, governance ensures it happens once. That serves strangers — anyone can check the boolean — and it stands or falls with accreditation and global uniqueness, which are hard open problems. The predicates here claim no uniqueness and need no issuer. They serve acquaintances: a verifier that already holds roots — its own encounter history, a group it belongs to — learns something real about a subject asserted by them. A verifier with no roots learns nothing, correctly. Most real trust decisions are taken among acquaintances; that is the ground this document stands on, and its honest boundary.

2. Conventions and Terminology

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals.

The interim securing profile of Encounter §2.3 applies to every document evaluated here. Timestamps compare as instants (RFC3339 UTC, per that profile); this document never derives durations. Credential digest — the multibase multihash over JCS(document) per Encounter §2.2; the equivalence under which documents are counted here; where this document uses one, it is normalized to its canonical u form. Local digest rule — the digests this document itself constructs (snapshotId, the K digest, presentedSet) are, uniformly: the SHA-256 multihash (0x12 0x20 followed by the 32 digest bytes) over the UTF-8 bytes of the JCS serialization of the named value, encoded multibase u (base64url, no padding). The rule takes a JSON value and performs the JCS serialization itself; callers hand it the value, never a pre-serialized string — digesting JCS(JCS(value)) is non-conformant. Encounter §2.3 governs evaluated documents; this rule governs the evaluator’s own constructions — stated separately so neither is stretched. Canonical base64url — wherever this document requires a base64url value, it means, in full (the Delivery §6.2 rule): the RFC 4648 base64url alphabet only, no padding character, the stated length, zero trailing bits in the final character, and round-trip identity encode(decode(v)) == v. Any value failing one of these is not that value in another spelling; it is non-canonical, and the section defining the value says what non-canonical means there.

Subject — the anchor whose assertions are being evaluated. Verifier — the party evaluating; evaluator — the pure function of Sections 3–5, which the verifier runs. Asserter — the issuer of an encounter credential about the subject. Incoming candidate — a presented document issued by an asserter about the subject; the only kind that forms edges (Encounter §4.2). Outgoing candidate — a presented document issued by the subject about an asserter; serves only the reciprocal quality (4.1), never forms an edge. Presented set — the input collection of documents (3.1); deliberately not named “presentation” — the Access layer’s encounter-presentation is a different, operation-bound artifact. Known-anchor set (K) — the set of anchors the verifier already associates with people it has reason to treat as distinct; its curation is the verifier’s responsibility and P1/P2’s entire root (Section 8). Group input — per requested group, either a snapshot (3.3) or a declared unavailability (5.3). Edge record — the per-asserter unit every measure is computed from (4.1). Measure — a deterministic value over edge records. Non-result — a named outcome from the closed set of 5.3.

3. The Input Model (normative, abstract)

The evaluator is a pure function of the following input; no other information may influence any result field.

3.1 The presented set

A finite collection of candidate documents. A candidate that does not parse as JSON or is not JCS-canonicalizable is excluded with reason malformed before anything else — digesting and size measurement need canonical bytes, so this is necessarily the first gate — after one bound that precedes even it: the raw-size gate, a plain length and count check needing neither parser nor hash. A candidate whose raw input exceeds 8192 bytes, a total of the candidates’ raw byte lengths exceeding 4 MiB, or more than 4096 candidate occurrences before any deduplication, yields the whole-evaluation non-result input-too-large before anything is parsed, hashed, or classified — so no input, however malformed or however duplicated, makes the evaluator touch unbounded bytes or unbounded entries. All three bounds are computed over the candidates themselves — how the collection travelled (its transport envelope, framing, whitespace) never enters the pure function; bounding the envelope is the transport adapter’s duty, outside this document’s model. Within that gate, malformed candidates have an identity too: they are deduplicated by SHA-256 over their raw input bytes, so a duplicated malformed input yields one exclusion, and every count in the result is deterministic over the whole input. The surviving candidates are counted and deduplicated by credential digest (documents with equal digest are one document, whatever their serialization). The two identity spaces never mix, and the distinct presented count is exactly their sum: the number of distinct malformed raw-byte identities plus the number of distinct credential digests — [M, M, V] with equal malformed bytes presents two distinct units. Bounds: a distinct presented count above 1024 (that sum, exactly) yields the whole-evaluation non-result input-too-large; a single document exceeding 2048 bytes in its JCS serialization (the Encounter credential cap) is excluded individually with reason oversize and never fails the evaluation.

3.2 Parameters

The subject anchor; the known-anchor set K (possibly empty; bound into results by digest, 5.2); the requested groups (possibly empty) — a duplicate-free set identified by genesisDigest in its canonical u form, because the genesis digest is the group identity (Access §3.2): a group DID is an address under which sibling geneses may coexist, and MUST NOT serve as the identity here — each with its group input (3.3); optionally a possession input (3.4) together with the challenge and audience the verifier issued and stated for it.

Domain precondition: parameters — the subject anchor, every K entry, every requested-group identity, every group input, and the possession parameters (challenge, audience) — MUST be syntactically well-formed per their definitions before the evaluator is invoked; a call with malformed parameters is a caller error outside the function’s domain, not an evaluation outcome. Documents and the possession input (3.4) come from the counterparty: they face the hostile boundary, and they answer with malformed (3.1) and failed (3.4) respectively — never with a caller error.

3.3 Group inputs (for P3)

For each requested group, the verifier’s Access implementation supplies exactly one of:

This makes P3 total: every requested group yields either a measure or a named non-result, and nothing else.

3.4 The possession input (profile)

Predicates evaluate evidence about an anchor. Whether the subject anchor’s key holder signed this request is a separate proof — that, and only that, is what this profile can add. This profile MUST NOT bind any evaluator or verifier output — released(proven) included — to the submitting party, transport peer, session, or present counterparty. What the proof establishes, exactly: the holder of the subject anchor’s key signed this fresh, verifier-specific request. What it does not establish: which transport peer delivered it — a live relay (a party forwarding the verifier’s challenge to the real holder and returning the holder’s signature as its own) is indistinguishable at this layer, and no verifier-supplied co-signed value changes that, because the relaying party knows every value its own session carries. Deriving the submitting party’s identity from a proven is therefore non-conformant, always: a present-counterparty verdict requires channel-bound authentication that this profile deliberately does not define — the channel-binding profile it would take (both endpoints deriving the binding independently from non-exported channel state, signers rejecting request-supplied values) is Open Issue PP-6, not a feature this transport-independent evaluator can carry honestly.

The possession input, when present, is exactly { "signature": … }: a canonical base64url string (§2) of exactly 86 characters, decoding to exactly 64 bytes — an Ed25519 signature, nothing else. It is counterparty data at the hostile boundary: a value of any other length, alphabet, or spelling — padded, hex, raw bytes, or an 86-character string with non-zero trailing bits — yields possession status failed, deterministically and before any decoding (length and alphabet are checkable first, the trailing-bit rule during decode) — never a caller error. The evaluator reconstructs the signed object entirely from its other inputs — the profile constant, the verifier-supplied challenge and audience, the subject anchor, and the presented-set digest it computes itself (below); the submitting party supplies no part of the signed object, only the signature over it — and signing is the subject-key holder’s act, whoever submits.

Verifier duties (stateful, outside the evaluator): generate a challenge of at least 128 bits of entropy, fresh per evaluation, never reused. On the wire and in the signed object, the challenge is a canonical base64url string (§2) whose decoded length is at least 16 and at most 64 bytes; the audience is the verifier’s own anchor (MUST — an audience namespace without uniqueness would weaken the gate, §8), carried as a string. Challenge and audience are verifier-side parameters (3.2): a non-canonical or malformed one is a caller error, and the evaluator is never invoked with it.

Challenge lifecycle (bounded, verify-then-consume): each challenge moves through issued → consumed or issued → expired, nothing else. The verifier evaluates tentatively — the evaluator is pure, so this commits nothing — and releases a proven only through an atomic compare-and-swap issued → consumed at a single serialization point. A lost swap — the challenge already consumed or expired, or never issued by this verifier — yields the named verifier outcome challenge-not-outstanding. The verifier’s release algebra is closed, two-valued, and total over the evaluator’s codomain EvaluationResult | input-too-large: the verifier publishes either released(output) — the evaluator’s output, exactly as produced, input-too-large included — or challenge-not-outstanding. There is exactly one check point: the outstanding state is consulted only when releasing a tentative proven, through the swap; every other evaluator output is released without touching challenge state — an invalid signature against an already-consumed challenge is released(possession: failed), never challenge-not-outstanding. On a lost swap the tentative result is discarded and never published: not a fourth possession status, not an evaluator non-result (5.2 and 5.3 are untouched), but the verifier declining to release. A failed evaluation consumes nothing: a junk signature cannot burn an outstanding challenge, and the holder’s subsequent valid proof still lands. Of two concurrent valid presentations, at most one wins the swap; the other gets challenge-not-outstanding. The verifier MUST bound both the number and the lifetime of outstanding challenges (issued → expired); that the bounds exist is normative, their values are deployment policy. Expired, consumed, and never-issued challenges answer uniformly — challenge-not-outstanding — so the outcome does not disclose which.

The signed object (checked inside the evaluator): the subject-key holder signs, with the subject anchor (Ed25519), the UTF-8 bytes of the JCS serialization of

{ "profile": "rltp-predicates@0.12",
  "challenge": <the verifier's challenge, base64url no padding>,
  "audience": <the verifier's anchor as stated in its need>,
  "subject": <the subject anchor>,
  "presentedSet": <per the local digest rule (§2), over the
                   array of the presented set's credential
                   digests — the canonicalizable documents,
                   deliberately: malformed bycatch has no
                   credential digest and does not alter this
                   binding — each in its canonical `u` form,
                   sorted by unsigned bytewise comparison of the
                   `u`-string's ASCII bytes> }

The signed message is, exactly: the ASCII bytes of the domain separator rltp/v1/possession, then a single 0x00 byte, then the UTF-8 bytes of the JCS serialization of the object above — fixed framing, so no object byte can be confused with the separator. The check is a single signature verification — no field comparison exists anywhere in it: the evaluator builds this object from its own inputs (the profile constant, the verifier’s challenge and audience, the subject anchor, the presented-set digest it computed), frames it, and verifies the submitted signature over exactly those bytes with the subject anchor’s key. A signature made over any differing preimage — another challenge, another audience, another subject, another presented set, other framing — simply fails that one verification: failed. There are no counterparty-supplied challenge, audience, or subject fields to compare, and no equality semantics beyond the bytes signed. Result states: proven / not-attempted / failed (5.2).

Scope, honestly: the presented-set binding ties a proof to one disclosed set of credentials — adding non-canonicalizable junk to the input does not disturb it, and this document says so rather than claiming a byte-exact input binding; the challenge ties it to one evaluation of one verifier provided the verifier honors its duties above — the evaluator is pure and answers for the bytes it is given, so a proven is a usable result only inside the verifier conformance class (exactly-once consumption, §11); a verifier accepting a challenge it did not issue is non-conformant, and audience binding rests on anchor uniqueness — which is why the audience MUST be the verifier’s own anchor. Conflating possession with assertion evidence is the classic mistake this section exists to prevent.

3.5 Admission of candidate documents

Each distinct document is classified independently by one executable sequence; the first failing step names the exclusion reason, and the closed set has exactly four members:

  1. malformed — does not parse or is not JCS-canonicalizable (3.1; the first classification — only the raw-size gate of 3.1, a whole-evaluation bound and not a classification, precedes it);
  2. oversize — exceeds 2048 bytes in JCS (3.1);
  3. invalid-document — fails Encounter §7 validation (schema, proof under the issuer’s anchor, decoded keys, pinned context);
  4. unrelated — the now schema-valid document fits neither lane: incoming candidate (credential subject = the evaluated subject, issuer ≠ subject) nor outgoing candidate (issuer = the evaluated subject, credential subject ≠ subject). Lane determination runs after validation, on a document guaranteed to carry both fields — never before, where they may not exist. A self-credential (issuer = credential subject = subject) fits neither lane and lands here, deliberately.

There are no other reasons; in particular, this profile evaluates raw anchors only — applying succession resolution and still claiming this profile is non-conformant (PP-5).

Documents surviving admission are admitted (in their lane); the rest are excluded with their reason. Exclusions MUST NOT fail the evaluation, and an invalid document never affects the standing of other documents from the same asserter (independent classification — no slot poisoning). A presented set claims at least, Section 6.

4. Edges and Measures

4.1 The edge model

From the admitted incoming candidates, the evaluator builds one edge record per distinct asserter anchor:

edge := { asserter, instants, reciprocal, documents }

Edges are a set keyed by asserter: deterministic for any input order and any duplication. All credential-derived evidence factors through the edge set — this is Encounter §4.2’s rule, counts edges, never credentials or enactments, made structural — and each measure then takes its named verifier-side inputs: P1 and P2 additionally take K; P3 additionally takes the requested group’s snapshot and its currency roster. The same edge set under a different K or roster is a different evaluation with different values — nothing may be cached or reused across those inputs.

What reciprocal is, honestly: pair verification proves that both keys signed reciprocally over one recomputed descriptor. Two colluding keys manufacture that trivially (Encounter §8). The one thing it excludes is a unilateral assertion the subject never countersigned — a reciprocity and consent marker, not stronger evidence of a meeting. No measure uses it; consumers MAY filter edges on it, and a consumer requiring reciprocal is requiring the subject’s own countersignature, nothing more. It carries no strength order, and Section 6’s floor statement is about measures, which it does not touch.

4.2 P1 — Known-asserter count

P1(K) = { edges e : e.asserter ∈ K }

“n of the anchors asserting this subject, I already know.” Distinctness is anchor-distinctness and nothing more: one person holding several anchors in K counts once per anchor (Section 8). Anchors outside K contribute nothing — anchors are free to create, assertions among unknown anchors are free to manufacture; what is expensive is an assertion signed by a specific, known key (Encounter §13). P1’s root is K, entirely.

4.3 P2 — Signed instants

P2(K) = { (e.asserter, e.instants) : e.asserter ∈ K }

“the assertions I can anchor carry these signed instants.” P2 returns per-edge instants, never ages — evaluation is clock-free; turning instants into durations, and deciding what a temporal pattern is worth, is the consumer’s act with the consumer’s clock and policy.

This document defines no strength order for P2 — deliberately. More input is more information (Section 6’s monotonicity), but an earlier instant is not automatically more strength: a single backdated entry is exactly what a compromised K-key would produce (Section 8), so “older is stronger” would reward precisely the forgery. The robust consumption pattern, stated informatively: a consumer requiring m distinct edges whose instants precede T demands m independent keys’ histories, which no single stolen key satisfies. P2 over asserters outside K MUST NOT be computed — unknown keys’ instants attest nothing.

4.4 P3 — Contextual assertion

P3(G) = { edges e : e.asserter ∈ currencyRoster(G) }

“n anchors this group’s own admission discipline vouches for have signed recognition assertions about this subject.” The roster is the snapshot’s policy currency (3.3). P3’s root is not K — it is G’s gatekeeping and the verifier’s own replica: trusting P3 means trusting the group’s admission policy and the integrity of the Access implementation that supplied the roster. That the verifier can obtain a roster at all means it has, or once had, access to that group state (membership is member-only state, Access §3.1/§13) — P3 proves nothing about the verifier’s current membership: a removed member may still hold an old snapshot, and its P3 is correct for exactly that named snapshot.

P3 is the sibling of the Access layer’s registered encounter(count) rule (Access §4.2), not the same predicate: the Access rule is evaluated at an operation’s causal position during admission decisions; P3 at a verifier-named snapshot for the verifier’s own purposes. Two verifiers holding different replicas may compute different P3 — inherent to local-first, and made comparable: the evaluator-computed snapshotId (3.3) names each evaluation’s claimed materialization, and equal snapshotIds mean the same named DAG. Whether the supplied roster truly is that materialization’s currency is the Access integration’s obligation (3.3) — the id binds the claim, the replica carries the trust.

4.5 Composition

Measures MAY be combined by the consumer in any way; this document defines no composition algebra and no thresholds. Groups wanting predicate thresholds as group policy would need new registered requirement types in the Access layer’s open rule set — a future registration, not a present capability.

5. The Result Model (normative, abstract)

5.1 No wire format, one data model

Interoperability happens at the credential level; a result is a local value. This document defines no serialization for results — but it defines their abstract content, because a result whose meaning cannot be reconstructed is not evidence of anything.

5.2 Result content

An evaluation result contains, at minimum: the profile version (rltp-predicates@0.12) · the subject anchor · the K digest (per the local digest rule of §2, over the array of K anchor strings sorted by unsigned bytewise comparison of their ASCII bytes — binding which K produced these numbers without embedding it) · the edge records of 4.1 (asserter, instants, reciprocal, document digests) · the counts: the distinct presented count (the exact two-namespace sum of 3.1), admitted documents per lane, and excluded documents per closed reason · P1 and P2 for the given K · per requested group, keyed by its genesisDigest (the group identity, 3.2, present in the entry): the computed snapshotId and P3, or its declared unavailability · the possession status: proven / not-attempted / failed — where proven carries weight only under the verifier class’s atomic challenge lifecycle (3.4): the pure evaluator re-run on the same bytes returns proven again, and it is the consumed challenge, not the evaluator, that makes replay unusable. A proven means the holder claim of 3.4 and nothing more — it binds the subject anchor, never the party that submitted the proof; there is no stronger possession class in this profile (PP-6). Timestamps appear as instants; no age, duration, or “now” may appear anywhere in a result.

A result contains graph knowledge — the subject and its asserters — and Section 9’s retention duty therefore applies to results exactly as to presented documents.

5.3 Non-results (closed set)

input-too-large (3.1, whole evaluation) · per requested group, carried from the group input: group-unknown · group-unreadable · group-forked · group-terminal. A non-result is distinct from every measure value; in particular, group-unreadable is not P3 = 0, and implementations MUST preserve the distinction in their result types.

6. What No Predicate Establishes

Presenting any of the following as established is non-conformant.

7. Excluded Predicates (named non-goals)

8. Security Considerations

9. Privacy Considerations

10. Open Issues

11. Conformance

References

[RFC2119] · [RFC8174] BCP 14 · RLTP Encounter Layer 0.22 (credential form §7, issuance time §7.2, issuer copy §7.4, evidence direction and counting rule §4.2, enactment binding §5.4, participant-local acceptance §5.6, pair verification and honesty §8, Sybil economics §13) · RLTP Access Layer 0.25 (materialized state and currency §3–§4, membership privacy §3.1/§13, encounter(count) rule §4.2) · RLTP Succession draft (anchor resolution, future PP-5) · W3C Verifiable Credentials Data Model 2.0