1
0
mirror of https://codeberg.org/fediverse/fep.git synced 2026-08-05 11:46:04 +00:00

FEP-0391: Special collection proofs (#309)

Reviewed-on: https://codeberg.org/fediverse/fep/pulls/309
Co-authored-by: a <a@trwnh.com>
Co-committed-by: a <a@trwnh.com>
This commit is contained in:
a
2024-04-30 17:18:57 +00:00
committed by silverpill
parent e55c51da80
commit 498eee4f37
11 changed files with 1216 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
{
"@context": {
"inReplyToProof": {
"@id": "https://w3id.org/fep/0391/inReplyToProof",
"@type": "@id"
},
"contextProof": {
"@id": "https://w3id.org/fep/0391/contextProof",
"@type": "@id"
},
"relationshipProof": {
"@id": "https://w3id.org/fep/0391/relationshipProof",
"@type": "@id"
}
}
}
+35
View File
@@ -0,0 +1,35 @@
contextProof <https://w3id.org/fep/0391/contextProof>
Provides proof that some object was added to some `context` collection
Domain: Object with context set to a Collection that has attributedTo set
Range: Add
Example:
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/create-some-object",
"actor": "https://example.com/some-actor",
"type": "Create",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"attributedTo": "https://example.com/some-actor",
"content": "This object is part of some context, and I can prove it was added to the context collection.",
"context": {
"id": "https://example.com/some-context",
"type": "Collection",
"attributedTo": "https://example.com/some-context-moderator"
},
"contextProof": "https://example.com/some-proof"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/some-context-moderator",
"type": "Add",
"object": "https://example.com/some-comment",
"target": "https://example.com/some-context",
"attributedTo": "https://example.com/create-some-object"
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"@context": {
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"owl": "http://www.w3.org/2002/07/owl#",
"as": "https://www.w3.org/ns/activitystreams#"
},
"@id": "https://w3id.org/fep/0391/contextProof",
"@type": "owl:ObjectProperty",
"rdfs:label": {
"@value": "contextProof",
"@language": "en"
},
"rdfs:comment": {
"@value": "Provides proof that some object was added to some context collection",
"@language": "en"
},
"rdfs:domain": [
{
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "as:context"
},
"owl:minCardinality": 1
},
{
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "as:context"
},
"owl:someValuesFrom": {
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "as:attributedTo"
},
"owl:minCardinality": 1
}
}
],
"rdfs:range": {
"@id": "as:Add"
}
}
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:ObjectProperty rdf:about="https://w3id.org/fep/0391/contextProof">
<rdfs:label xml:lang="en">contextProof</rdfs:label>
<rdfs:comment xml:lang="en">Provides proof that some object was added to some context collection</rdfs:comment>
<rdfs:domain>
<owl:Restriction>
<owl:onProperty rdf:resource="https://www.w3.org/ns/activitystreams#context" />
<owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">1</owl:minCardinality>
</owl:Restriction>
</rdfs:domain>
<rdfs:domain>
<owl:Restriction>
<owl:onProperty rdf:resource="https://www.w3.org/ns/activitystreams#context" />
<owl:someValuesFrom>
<owl:Restriction>
<owl:onProperty rdf:resource="https://www.w3.org/ns/activitystreams#attributedTo" />
<owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">1</owl:minCardinality>
</owl:Restriction>
</owl:someValuesFrom>
</owl:Restriction>
</rdfs:domain>
<rdfs:range rdf:resource="https://www.w3.org/ns/activitystreams#Add" />
</owl:ObjectProperty>
</rdf:RDF>
+25
View File
@@ -0,0 +1,25 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix as: <https://www.w3.org/ns/activitystreams#> .
<https://w3id.org/fep/0391/contextProof>
a owl:ObjectProperty;
rdfs:label "contextProof"@en;
rdfs:comment "Provides proof that some object was added to some context collection"@en;
rdfs:domain [
a owl:Restriction;
owl:onProperty as:context;
owl:minCardinality 1; # context should have at least 1 value.
]
, [
a owl:Restriction;
owl:onProperty as:context;
owl:someValuesFrom [ # for at least some value of context,
a owl:Restriction;
owl:onProperty as:attributedTo;
owl:minCardinality 1 # there should be at least 1 attributedTo actor.
]
];
rdfs:range as:Add
.
+831
View File
@@ -0,0 +1,831 @@
---
slug: "0391"
authors: a <a@trwnh.com>
status: DRAFT
dateReceived: 2024-04-29
relatedFeps: FEP-7458, FEP-7888
---
# FEP-0391: Special collection proofs
## Summary
Some properties represent special collections, such as:
- `outbox` ([ActivityPub][AP])
- `inbox` ([ActivityPub][AP])
- `followers` ([ActivityPub][AP])
- `following` ([ActivityPub][AP])
- `liked` ([ActivityPub][AP])
- `likes` ([ActivityPub][AP])
- `shares` ([ActivityPub][AP])
- `replies` (FEP-7458)
- `context` (FEP-7888)
Verifying that any given object is part of a special collection is usually only possible by resolving that collection and checking its items one-by-one until the current object is found. This can be inefficient to verify. It would be easier if there was an inverse claim for each claim made about an object being part of a special collection. This FEP aims to define some properties that can be used to make those inverse claims.
## Mapping claims and inverse claims
Given the special collections above, we can map the following claims and inverse claims. The last two inverse claims are particularly salient, since inclusion in `replies` and/or `context` carries additional semantic meaning and is a socially loaded relation used to establish conversational constructs.
### Verifying presence in `outbox` {#outbox-claim}
Claim
: Activity `<A>`'s actor (`<A>.actor`) is actor `<B>`
Inverse claim
: Actor `<B>`'s outbox collection (`<B>.outbox`) contains activity `<A>`
This is not particularly useful to prove.
### Verifying presence in `inbox` {#inbox-claim}
Claim
: Activity `<A>` has `to`/`cc`/`audience` including actor `<B>`
Inverse claim
: Actor `<B>`'s inbox collection (`<B>.inbox`) contains activity `<A>`
This is not particularly useful to prove, and at best can only be implied if `<B>` is addressed directly and not through some collection that triggers inbox forwarding.
### Verifying presence in `followers` {#followers-claim}
Claim
: Actor `<B>`'s following collection (`<B>.following`) contains actor `<A>`
Equivalent claim
: Actor `<B>` claims that `<B>` is following `<A>`
Another equivalent claim
: Actor `<B>` claims that `<A>` is followed by `<B>`
Inverse claim
: Actor `<A>`'s followers collection (`<A>.followers`) contains actor `<B>`
Equivalent inverse claim
: Actor `<A>` claims that `<A>` is followed by `<B>`
Another equivalent inverse claim
: Actor `<A>` claims that `<B>` is following `<A>`
This can be verified by showing one of the following:
- `<A>.followers` includes `<B>` as well
- A Relationship exists where:
- `attributedTo` is `<A>`
- `subject` is `<B>`
- `relationship` is `IsFollowing`
- `object` is `<A>`
- A Relationship exists where:
- `attributedTo` is `<A>`
- `subject` is `<A>`
- `relationship` is `IsFollowedBy`
- `object` is `<B>`
- A trusted activity exists where:
- `actor` is `<A>`
- `type` is `Accept`
- `object.actor` is `<B>`
- `object.type` is `Follow`
- `object.object` is `<A>`
- A trusted activity exists where:
- `actor` is `<A>`
- `type` is `Add`
- `object` is `<B>`
- `target` is `<A>.followers`
### Verifying presence in `following` {#following-claim}
Claim
: Actor `<B>`'s followers collection (`<B>.followers`) contains actor `<A>`
Equivalent claim
: Actor `<B>` claims that `<B>` is followed by `<A>`
Another equivalent claim
: Actor `<B>` claims that `<A>` is following `<B>`
Inverse claim
: Actor `<A>`'s following collection (`<A>.following`) contains actor `<B>`
Equivalent inverse claim
: Actor `<A>` claims that `<A>` is following `<B>`
Another equivalent inverse claim
: Actor `<A>` claims that `<B>` is followed by `<A>`
This can be verified by showing one of the following:
- `<A>.following` includes `<B>` as well
- A Relationship exists where:
- `attributedTo` is `<A>`
- `subject` is `<A>`
- `relationship` is `IsFollowing`
- `object` is `<B>`
- A Relationship exists where:
- `attributedTo` is `<A>`
- `subject` is `<B>`
- `relationship` is `IsFollowedBy`
- `object` is `<A>`
- A trusted activity exists where:
- `actor` is `<B>`
- `type` is `Accept`
- `object.actor` is `<A>`
- `object.type` is `Follow`
- `object.object` is `<B>`
- `object` is a trusted activity
- A trusted activity exists where:
- `actor` is `<A>`
- `type` is `Add`
- `object` is `<B>`
- `target` is `<A>.following`
### Verifying presence in `liked` {#liked-claim}
Claim
: Actor `<A>`'s liked collection (`<A>.liked`) contains object `<O>`
This can be verified by showing a trusted activity exists in `<O>.likes` where:
- `actor` is `<A>`
- `type` is `Like`
- `object` is `<O>`
Note that there is an issue that may occur if `<A>` issues multiple `Like` activities for the same object `<O>`, and then issues any `Undo Like` activities at a later point in time. The most recent activity will have its side-effects carried out. It is possible for some of these functionally duplicate `Like` activites to remain in `<O>.likes` even though the object `<O>` is no longer in `<A>.liked`. See https://github.com/w3c/activitypub/issues/381 for more information.
### Verifying presence in `likes` {#likes-claim}
Claim
: Object `<O>`'s likes collection (`<O>.likes`) contains a Like activity `<L>` from actor `<A>`
If `<L>` is not already trusted via some other mechanism, this can be verified by showing that `<A>.liked` includes `<O>`. Note the duplication issue from the previous section.
The Like activity `<L>` MAY `result` in an `Add` activity `<R>` targeting `<O>.likes`. This `result` can be used by `<A>` as proof of the claim, if it can be shown to be a trusted activity.
### Verifying presence in `shares` {#shares-claim}
Claim
: Object `<O>`'s shares collection (`<O>.shares`) contains an Announce activity `<S>` from actor `<A>`
The Announce activity `<S>` MAY `result` in an `Add` activity `<R>` targeting `<O>.shares`. This `result` can be used by `<A>` as proof of the claim, if it can be shown to be a trusted activity.
### Verifying presence in `replies` {#replies-claim}
Claim
: Object `<R>` is `inReplyTo` object `<O>`
Inverse claim
: Object `<O>`'s replies collection (`<O>.replies`) contains object `<R>` attributed to actor `<A>`
The inverse claim can be verified by showing that an activity exists where:
- `actor` is `<O>.attributedTo`
- `type` set includes `Add`
- `object` is `<R>`
- `target` is `<O>.replies`
### Verifying presence in `context` {#context-claim}
Claim
: Object `<O>` has a `context` pointing to context collection `<C>`
Inverse claim
: Context collection `<C>` contains object `<O>`
The inverse claim can be verified by showing that an activity exists where:
- `actor` is `<O>.context.attributedTo`
- `type` set includes `Add`
- `object` is `<R>`
- `target` is `<O>.context`
## Types of proofs
We can map proofs onto the `result` property provided that its subject is an activity and its referent is a trusted activity. We may also define some extension properties for non-activity objects.
### Showing inclusion in `replies` collections via a reply stamp {#replies-stamp}
Provides proof that some object was added to some `replies` collection.
On an activity where `inReplyTo` has been set, the proof can be verified if all of the following are satisfied:
- `result.actor` MUST be included in either `inReplyTo.actor` or `inReplyTo.attributedTo`
- `result.type` MUST include `Add`
- `result.object` MUST be equivalent to the current activity
- `result.target` MUST be equivalent to `inReplyTo.replies`
On a `Create` activity where the `object` has `inReplyTo` set, the proof can be verified if all of the following are satisfied:
- `result.actor` MUST be included in either `object.inReplyTo.attributedTo` or `object.inReplyTo.actor`
- `result.type` MUST include `Add`
- `result.object` MUST be equivalent to `object`
- `result.target` MUST be equivalent to `object.inReplyTo.replies`
On a non-activity Object where `inReplyTo` has been set, we define the extension property `inReplyToProof` since the use of `result` is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
- `inReplyToProof.actor` MUST be included in either `inReplyTo.attributedTo` or `inReplyTo.actor`
- `inReplyToProof.type` MUST include `Add`
- `inReplyToProof.object` MUST be equivalent to the current object
- `inReplyToProof.target` MUST be equivalent to `inReplyTo.replies`
Example of an activity with `inReplyTo` set to some activity with a `replies` collection:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-activity",
"actor": "https://example.com/actors/2",
"type": "Activity",
"object": "https://example.com/some-object",
"inReplyTo": {
"id": "https://example.com/some-other-create",
"actor": "https://example.com/actors/1",
"type": "Create",
"object": "https://example.com/some-other-object",
"content": "I am accepting replies to this activity.",
"replies": "https://example.com/some-other-create/replies"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/some-activity",
"target": "https://example.com/some-other-create/replies",
"attributedTo": "https://example.com/some-activity"
}
}
```
Example of a `Create` activity with `object.inReplyTo` set to some non-activity object with a `replies` collection:
```json
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/create-some-reply",
"actor": "https://example.com/actors/2",
"type": "Create",
"object": {
"id": "https://example.com/some-reply",
"type": "Note",
"attributedTo": "https://example.com/actors/2",
"content": "This is a reply, and I can prove it was added to the replies collection.",
"inReplyTo": {
"id": "https://example.com/some-object",
"type": "Note",
"attributedTo": "https://example.com/actors/1",
"content": "I am accepting replies to this object.",
"replies": "https://example.com/some-object/replies"
},
"inReplyToProof": "https://example.com/some-proof"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/some-reply",
"target": "https://example.com/some-object/replies",
"attributedTo": "https://example.com/create-some-reply"
}
}
```
### Showing inclusion in `context` collections via a context stamp {#context-stamp}
Provides proof that some object was added to some `context` collection.
On an activity where the `context` has been set to a collection, the proof can be verified if all of the following are satisfied:
- `result.actor` MUST be included in `context.attributedTo`
- `result.type` MUST include `Add`
- `result.object` MUST be equivalent to the current activity
- `result.target` MUST be equivalent to `context`
On a `Create` activity where the `object` has `context` set, the proof can be verified if all of the following are satisfied:
- `result.actor` MUST be included in `object.context.attributedTo`
- `result.type` MUST include `Add`
- `result.object` MUST be equivalent to `object`
- `result.target` MUST be equivalent to `object.context`
On a non-activity Object where `context` has been set, we define the extension property `contextProof` since the use of `result` is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
- `contextProof.actor` MUST be included in `context.attributedTo`
- `contextProof.type` MUST include `Add`
- `contextProof.object` MUST be equivalent to the current object
- `contextProof.target` MUST be equivalent to `context`
Example of an activity with `context` set to some owned collection:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-activity",
"actor": "https://example.com/some-actor",
"type": "Activity",
"object": "https://example.com/some-object",
"context": {
"id": "https://example.com/some-context",
"type": "Collection",
"attributedTo": "https://example.com/some-context-moderator"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/some-context-moderator",
"type": "Add",
"object": "https://example.com/some-activity",
"target": "https://example.com/some-context",
"attributedTo": "https://example.com/some-activity"
}
}
```
Example of a `Create` activity with `object.context` set to some owned collection:
```json
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/create-some-object",
"actor": "https://example.com/some-actor",
"type": "Create",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"attributedTo": "https://example.com/some-actor",
"content": "This object is part of some context, and I can prove it was added to the context collection.",
"context": {
"id": "https://example.com/some-context",
"type": "Collection",
"attributedTo": "https://example.com/some-context-moderator"
},
"contextProof": "https://example.com/some-proof"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/some-context-moderator",
"type": "Add",
"object": "https://example.com/some-object",
"target": "https://example.com/some-context",
"attributedTo": "https://example.com/create-some-object"
}
}
```
### Showing inclusion in `likes` collections via a like stamp {#likes-stamp}
Provides proof that the current activity was added to the `object.likes` collection.
On a `Like` activity where the `object` has a `likes` collection, the proof can be verified if all of the following are satisfied:
- `result.actor` MUST be included in `object.attributedTo`
- `result.type` MUST include `Add`
- `result.object` MUST be equivalent to the current activity
- `result.target` MUST be equivalent to `object.likes`
Example:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-like",
"summary": "A Like activity, with proof that it was added to the likes collection.",
"actor": "https://example.com/actors/2",
"type": "Like",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"content": "I am accepting likes of this object.",
"likes": "https://example.com/some-object/likes",
"attributedTo": "https://example.com/actors/1"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/some-like",
"target": "https://example.com/some-object/likes",
"attributedTo": "https://example.com/some-like"
}
}
```
### Showing inclusion in `shares` collections via a share stamp {#shares-stamp}
Provides proof that the current activity was added to the `object.shares` collection.
On an `Announce` activity where the `object` has a `shares` collection, the proof can be verified if all of the following are satisfied:
- `result.actor` MUST be included in `object.attributedTo`
- `result.type` MUST include `Add`
- `result.object` MUST be equivalent to the current activity
- `result.target` MUST be equivalent to `object.shares`
Example:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-announce",
"summary": "An Announce activity, with proof that it was added to the shares collection.",
"actor": "https://example.com/actors/2",
"type": "Announce",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"content": "I am accepting shares of this object.",
"shares": "https://example.com/some-object/shares",
"attributedTo": "https://example.com/actors/1"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/some-announce",
"target": "https://example.com/some-object/shares",
"attributedTo": "https://example.com/some-announce"
}
}
```
### Relationship proofs
>[!WARNING]
> Experimental, requires further thought.
>[!WARNING]
> Currently bugged. See https://github.com/w3c/activitystreams/issues/593 for more details.
Provides proof that the current relationship is reciprocally claimed.
Relationships other than following or being a follower may be proved using this property, but the requirements for such a proof are out of scope of this FEP.
We define the extension property `relationshipProof` since the use of `result` is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
Example that proves a user is following another user:
```json
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/some-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/1",
"subject": {
"id": "https://example.com/actors/1",
"following": "https://example.com/actors/1/following"
},
"relationship": "IsFollowing",
"object": {
"id": "https://example.com/actors/2",
"followers": "https://example.com/actors/2/followers"
},
"relationshipProof": [
{
"id": "https://example.com/not-enough-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/actors/2",
"target": "https://example.com/actors/1/following"
},
{
"id": "https://example.com/proof-by-inverse-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/2",
"subject": "https://example.com/actors/2",
"relationship": "IsFollowedBy",
"object": "https://example.com/actors/1"
},
{
"id": "https://example.com/proof-by-being-added-to-followers",
"actor": "https://example.com/actors/2",
"type": "Add",
"object": "https://example.com/actors/1",
"target": "https://example.com/actors/2/followers"
},
{
"id": "https://example.com/proof-by-having-follow-accepted",
"actor": "https://example.com/actors/2",
"type": "Accept",
"object": {
"actor": "https://example.com/actors/1",
"type": "Follow",
"object": "https://example.com/actors/2"
}
}
]
}
```
Example that proves a user is followed by another user:
```json
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/some-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/1",
"subject": {
"id": "https://example.com/actors/1",
"followers": "https://example.com/actors/1/following"
},
"relationship": "IsFollowedBy",
"object": {
"id": "https://example.com/actors/2",
"following": "https://example.com/actors/2/followers"
},
"relationshipProof": [
{
"id": "https://example.com/not-enough-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/actors/2",
"target": "https://example.com/actors/1/followers"
},
{
"id": "https://example.com/proof-by-inverse-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/2",
"subject": "https://example.com/actors/2",
"relationship": "IsFollowing",
"object": "https://example.com/actors/1"
},
{
"id": "https://example.com/proof-by-being-added-to-following",
"actor": "https://example.com/actors/2",
"type": "Add",
"object": "https://example.com/actors/1",
"target": "https://example.com/actors/2/following"
},
{
"id": "https://example.com/proof-by-having-follow",
"actor": "https://example.com/actors/2",
"type": "Follow",
"object": "https://example.com/actors/1"
}
]
}
```
Miscellaneous examples:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-follow",
"actor": {
"id": "https://example.com/actors/2",
"following": "https://example.com/actors/2/following"
},
"type": "Follow",
"object": {
"id": "https://example.com/actors/1",
"followers": "https://example.com/actors/1/followers"
},
"result": {
"id": "https://example.com/accept-follow",
"actor": "https://example.com/actor/1",
"type": "Accept",
"object": "https://example.com/some-follow",
"result": [
{
"id": "https://example.com/resulting-add-to-followers",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/actors/2",
"target": "https://example.com/actors/1/followers",
"attributedTo": "https://example.com/accept-follow"
},
{
"id": "https://example.com/resulting-add-to-following",
"actor": "https://example.com/actors/2",
"type": "Add",
"object": "https://example.com/actors/1",
"target": "https://example.com/actors/2/following",
"attributedTo": "https://example.com/accept-follow"
}
]
}
}
```
## Existing mechanisms of trust {#root-trust}
Verifying stamps has an issue with bootstrapping trust. In addition to verifying stamps via logical inference, ActivityPub clients SHOULD also consider the following:
- Same-origin policy, if the root of trust is the DNS name.
- Cryptographic signatures, if the root of trust is some cryptographic key associated with the actor. See HTTP Signatures, Linked Data Signatures, [FEP-8b32: Object Integrity Proofs][8b32], or similar.
## Obtaining and revoking stamps {#flow}
Upon receiving an activity with a side effect of adding something to a special collection, ActivityPub servers SHOULD generate and deliver an `Add` activity representing this side-effect. The ActivityPub server MAY require manual action by a user. The resulting stamp activity SHOULD be `attributedTo` the activity that caused the side-effect, delivered to its `actor`, and additionally SHOULD either be resolvable (to allow direct same-origin checking) or otherwise include a cryptographic proof. If a cryptographic proof is included, the proof SHOULD expire after some reasonable window, beyond which point a new proof should be issued. If the resulting stamp activity is resolvable, it MAY become unresolvable after some time; HTTP caching SHOULD be used to indicate a time-to-live for ActivityPub clients to re-check cached stamps. The time-to-live MAY be used as the duration of the cryptographic proof, if one exists.
The recipient of a stamp SHOULD `Update` their object to include the appropriate property for the stamp -- `result` if it is an activity, `inReplyToProof` if it is a non-activity object that declares `inReplyTo`, `contextProof` if it is a non-activity object that declares `context`. [TODO: how to handle relationship proofs?]
Revoking a stamp can be done by:
- If it is resolvable, making it no longer resolve
- If it is signed, then waiting for the signature to expire
- `Update` the stamp with some property to signal that the `Add` activity has been undone. [TODO: flesh this out more -- how does this work exactly? should it use Remove? Tombstone? Undo? how does this interact with `outbox` and the activity history? i'm thinking Update -> Tombstone, or extension like "undoneBy"]
### A simpler example flow for issuing and revoking a like stamp
This flow is similar for any stamp that uses `result` on an activity -- `Like`, `Announce`, Activity with `inReplyTo`, Activity with `context`.
Actor 2 likes a post by actor 1:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-like",
"summary": "Actor 2 liked a Note by actor 1",
"actor": {
"id": "https://example.com/actors/2",
"followers": "https://example.com/actors/2/followers"
},
"type": "Like",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"content": "I am accepting likes of this object.",
"likes": "https://example.com/some-object/likes",
"attributedTo": {
"id": "https://example.com/actors/1",
"followers": "https://example.com/actors/1/followers"
},
"cc": [
"https://example.com/actors/1/followers",
"as:Public"
],
"audience": "https://example.com/some-object/audience"
},
"to": "https://example.com/actors/1",
"cc": [
"https://example.com/actors/2/followers",
"https://example.com/some-object/audience",
"as:Public"
],
"audience": "https://example.com/some-like/audience"
}
```
Actor 1 sends a like stamp:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-proof",
"summary": "Actor 1 approved a like",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/some-like",
"target": "https://example.com/some-object/likes",
"attributedTo": "https://example.com/some-like",
"to": "https://example.com/actors/2",
"cc": [
"https://example.com/actors/2/followers",
"https://example.com/some-like/audience",
"as:Public"
]
}
```
Actor 2 updates their Like activity with proof:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-update",
"summary": "Actor 2 updated their Like with proof",
"actor": {
"id": "https://example.com/actors/2",
"followers": "https://example.com/actors/2/followers"
},
"type": "Update",
"object": {
"id": "https://example.com/some-like",
"summary": "Actor 2 liked a Note by actor 1, with proof",
"actor": "https://example.com/actors/2",
"type": "Like",
"object": "https://example.com/some-object",
"to": "https://example.com/actors/1",
"cc": [
"https://example.com/actors/2/followers",
"https://example.com/some-object/audience",
"as:Public"
],
"audience": "https://example.com/some-like/audience",
"result": "https://example.com/some-proof"
},
"cc": [
"https://example.com/actors/2/followers",
"https://example.com/some-like/audience",
"as:Public"
]
}
```
If actor 1 had provided cryptographic proof, actor 2 would be able to authoritatively serve the stamp until the proof expired. Actor 1 can silently revoke the stamp by making it no longer resolve, or actively revoke the stamp by issuing a revocation activity [TODO: Update with "undoneBy" pointing to a Remove?]
### A more complex flow for stamps involving embedded objects
This flow uses `inReplyToProof` or `contextProof` on the `object` of a `Create` instead of using only `result` on the activity itself.
Some actor creates some object that is part of some context, owned by some context moderator:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/create-some-object",
"actor": "https://example.com/some-actor",
"type": "Create",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"attributedTo": {
"id": "https://example.com/some-actor",
"followers": "https://example.com/some-actor/followers"
},
"content": "This object is part of some context, and I can prove it was added to the context collection.",
"context": {
"id": "https://example.com/some-context",
"type": "Collection",
"attributedTo": "https://example.com/some-context-moderator",
"audience": "https://example.com/some-context/audience"
},
"to": "https://example.com/some-context-moderator",
"cc": "as:Public"
},
"to": [
"https://example.com/some-context-moderator",
"https://example.com/some-context/audience"
],
"cc": "as:Public",
"audience": "https://example.com/create-some-object/audience"
}
```
The context moderator adds the object to the context:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/some-proof",
"summary": "A new post was added to the context",
"actor": "https://example.com/some-context-moderator",
"type": "Add",
"object": "https://example.com/some-object",
"target": "https://example.com/some-context",
"attributedTo": "https://example.com/create-some-object",
"cc": [
"https://example.com/some-context/audience",
"https://example.com/some-actor",
"https://example.com/create-some-object/audience",
"as:Public"
]
}
```
The actor who created the object now can issue two updates: one for the Create activity to add a `result`, and one for the `object` of that activity to add `object.contextProof`:
```json
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/update-some-object",
"actor": {
"id": "https://example.com/some-actor",
"followers": "https://example.com/some-actor/followers"
},
"type": "Update",
"object": {
"id": "https://example.com/some-object",
"type": "Note",
"attributedTo": "https://example.com/some-actor",
"content": "This object is part of some context, and I can prove it was added to the context collection.",
"context": "https://example.com/some-context",
"contextProof": "https://example.com/some-proof",
"to": "https://example.com/some-context-moderator",
"cc": "as:Public"
},
"to": [
"https://example.com/some-actor/followers",
"as:Public"
]
}
```
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/update-create",
"actor": "https://example.com/some-actor",
"type": "Update",
"object": {
"id": "https://example.com/create-some-object",
"actor": "https://example.com/some-actor",
"type": "Create",
"object": "https://example.com/some-object",
"to": "https://example.com/some-context-moderator",
"cc": "as:Public",
"result": "https://example.com/some-proof"
}
}
```
## References
- Christine Lemmer Webber, Jessica Tallon, [ActivityPub][AP], 2018
- a, [FEP-7458: Using the replies collection][7458], 2024
- a, [FEP-7888: Demystifying the context property][7888], 2023
- silverpill, [FEP-8b32: Object Integrity Proofs][8b32], 2022
[AP]: https://www.w3.org/TR/activitypub/
[7458]: https://w3id.org/fep/7458
[7888]: https://w3id.org/fep/7888
[8b32]: https://w3id.org/fep/8b32
## Copyright
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
+37
View File
@@ -0,0 +1,37 @@
inReplyToProof <https://w3id.org/fep/0391/inReplyToProof>
Provides proof that some object was added to the replies collection of some inReplyTo object
Domain: Object with inReplyTo set to an Object that has a replies collection
Range: Add
Example:
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/create-some-reply",
"actor": "https://example.com/actors/2",
"type": "Create",
"object": {
"id": "https://example.com/some-reply",
"type": "Note",
"attributedTo": "https://example.com/actors/2",
"content": "This is a reply, and I can prove it was added to the replies collection.",
"inReplyTo": {
"id": "https://example.com/some-object",
"type": "Note",
"attributedTo": "https://example.com/actors/1",
"content": "I am accepting replies to this object.",
"replies": "https://example.com/some-object/replies"
},
"inReplyToProof": "https://example.com/some-proof"
},
"result": {
"id": "https://example.com/some-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/some-reply",
"target": "https://example.com/some-object/replies",
"attributedTo": "https://example.com/create-some-reply"
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"@context": {
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"owl": "http://www.w3.org/2002/07/owl#",
"as": "https://www.w3.org/ns/activitystreams#"
},
"@id": "https://w3id.org/fep/0391/inReplyToProof",
"@type": "owl:ObjectProperty",
"rdfs:label": {
"@value": "inReplyToProof",
"@language": "en"
},
"rdfs:comment": {
"@value": "Provides proof that some object was added to the replies collection of some inReplyTo object",
"@language": "en"
},
"rdfs:domain": [
{
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "as:inReplyTo"
},
"owl:minCardinality": 1
},
{
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "as:inReplyTo"
},
"owl:someValuesFrom": {
"@type": "owl:Restriction",
"owl:onProperty": {
"@id": "as:replies"
},
"owl:minCardinality": 1
}
}
],
"rdfs:range": {
"@id": "as:Add"
}
}
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:ObjectProperty rdf:about="https://w3id.org/fep/0391/inReplyToProof">
<rdfs:label xml:lang="en">inReplyToProof</rdfs:label>
<rdfs:comment xml:lang="en">Provides proof that some object was added to the replies collection of some inReplyTo object</rdfs:comment>
<rdfs:domain>
<owl:Restriction>
<owl:onProperty rdf:resource="https://www.w3.org/ns/activitystreams#inReplyTo" />
<owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">1</owl:minCardinality>
</owl:Restriction>
</rdfs:domain>
<rdfs:domain>
<owl:Restriction>
<owl:onProperty rdf:resource="https://www.w3.org/ns/activitystreams#inReplyTo" />
<owl:someValuesFrom>
<owl:Restriction>
<owl:onProperty rdf:resource="https://www.w3.org/ns/activitystreams#replies" />
<owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">1</owl:minCardinality>
</owl:Restriction>
</owl:someValuesFrom>
</owl:Restriction>
</rdfs:domain>
<rdfs:range rdf:resource="https://www.w3.org/ns/activitystreams#Add" />
</owl:ObjectProperty>
</rdf:RDF>
+25
View File
@@ -0,0 +1,25 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix as: <https://www.w3.org/ns/activitystreams#> .
<https://w3id.org/fep/0391/inReplyToProof>
a owl:ObjectProperty;
rdfs:label "inReplyToProof"@en;
rdfs:comment "Provides proof that some object was added to the replies collection of some inReplyTo object"@en;
rdfs:domain [
a owl:Restriction;
owl:onProperty as:inReplyTo;
owl:minCardinality 1; # inReplyTo should have at least 1 value.
]
, [
a owl:Restriction;
owl:onProperty as:inReplyTo;
owl:someValuesFrom [ # for at least some value of inReplyTo,
a owl:Restriction;
owl:onProperty as:replies;
owl:minCardinality 1 # there should be a replies collection.
]
];
rdfs:range as:Add
.
+107
View File
@@ -0,0 +1,107 @@
relationshipProof <https://w3id.org/fep/0391/relationshipProof>
Provides proof that the current relationship is reciprocally claimed.
Domain: Relationship
Range: Relationship | Add | Follow? | Accept Follow?
Status: Experimental
Example 1:
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/some-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/1",
"subject": {
"id": "https://example.com/actors/1",
"following": "https://example.com/actors/1/following"
},
"relationship": "IsFollowing",
"object": {
"id": "https://example.com/actors/2",
"followers": "https://example.com/actors/2/followers"
},
"relationshipProof": [
{
"id": "https://example.com/not-enough-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/actors/2",
"target": "https://example.com/actors/1/following"
},
{
"id": "https://example.com/proof-by-inverse-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/2",
"subject": "https://example.com/actors/2",
"relationship": "IsFollowedBy",
"object": "https://example.com/actors/1"
},
{
"id": "https://example.com/proof-by-being-added-to-followers",
"actor": "https://example.com/actors/2",
"type": "Add",
"object": "https://example.com/actors/1",
"target": "https://example.com/actors/2/followers"
},
{
"id": "https://example.com/proof-by-having-follow-accepted",
"actor": "https://example.com/actors/2",
"type": "Accept",
"object": {
"actor": "https://example.com/actors/1",
"type": "Follow",
"object": "https://example.com/actors/2"
}
}
]
}
Example 2:
{
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/0391"],
"id": "https://example.com/some-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/1",
"subject": {
"id": "https://example.com/actors/1",
"followers": "https://example.com/actors/1/following"
},
"relationship": "IsFollowedBy",
"object": {
"id": "https://example.com/actors/2",
"following": "https://example.com/actors/2/followers"
},
"relationshipProof": [
{
"id": "https://example.com/not-enough-proof",
"actor": "https://example.com/actors/1",
"type": "Add",
"object": "https://example.com/actors/2",
"target": "https://example.com/actors/1/followers"
},
{
"id": "https://example.com/proof-by-inverse-relationship",
"type": "Relationship",
"attributedTo": "https://example.com/actors/2",
"subject": "https://example.com/actors/2",
"relationship": "IsFollowing",
"object": "https://example.com/actors/1"
},
{
"id": "https://example.com/proof-by-being-added-to-following",
"actor": "https://example.com/actors/2",
"type": "Add",
"object": "https://example.com/actors/1",
"target": "https://example.com/actors/2/following"
},
{
"id": "https://example.com/proof-by-having-follow",
"actor": "https://example.com/actors/2",
"type": "Follow",
"object": "https://example.com/actors/1"
}
]
}