1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use anyhow::{anyhow, Result};
use libipld_cbor::DagCborCodec;
use ucan::{builder::UcanBuilder, crypto::KeyMaterial};

use crate::{
    authority::{generate_capability, Authorization, SphereAbility},
    data::{
        ChangelogIpld, DelegationIpld, Did, IdentityIpld, Jwt, Link, MapOperation, MemoIpld,
        RevocationIpld, VersionedMapKey, VersionedMapValue,
    },
};

use noosphere_storage::{BlockStore, UcanStore};

#[cfg(doc)]
use crate::{
    data::VersionedMapIpld,
    view::versioned_map::{Content, Delegations, Identities, Revocations},
};

/// A [VersionedMapMutation] that corresponds to [Content]
pub type ContentMutation = VersionedMapMutation<String, Link<MemoIpld>>;

/// A [VersionedMapMutation] that corresponds to [Identities]
pub type IdentitiesMutation = VersionedMapMutation<String, IdentityIpld>;

/// A [VersionedMapMutation] that corresponds to [Delegations]
pub type DelegationsMutation = VersionedMapMutation<Link<Jwt>, DelegationIpld>;

/// A [VersionedMapMutation] that corresponds to [Revocations]
pub type RevocationsMutation = VersionedMapMutation<Link<Jwt>, RevocationIpld>;

#[cfg(doc)]
use crate::view::Sphere;

/// A [SphereRevision] represents a new, unsigned version of a [Sphere]. A
/// [SphereRevision] must be signed as a final step before the [Link<MemoIpld>]
/// of a new sphere version can be considered part of the official history of
/// the sphere. The credential used to sign must be authorized to create new
/// history by the sphere's key.
#[derive(Debug)]
pub struct SphereRevision<S: BlockStore> {
    /// The [Did] of the sphere that this revision corresponds to
    pub sphere_identity: Did,
    /// A [BlockStore] that contains blocks assocaited with the sphere that this
    /// revision corresponds to
    pub store: S,
    /// The unsigned memo that wraps the root of the [SphereIpld] for this
    /// sphere revision
    pub memo: MemoIpld,
}

impl<S: BlockStore> SphereRevision<S> {
    /// Sign the [SphereRevision] with the provided credential and return the
    /// [Link<MemoIpld>] pointing to the root of the new, signed revision
    pub async fn sign<Credential: KeyMaterial>(
        &mut self,
        credential: &Credential,
        authorization: Option<&Authorization>,
    ) -> Result<Link<MemoIpld>> {
        let proof = match authorization {
            Some(authorization) => {
                let witness_ucan = authorization
                    .as_ucan(&UcanStore(self.store.clone()))
                    .await?;

                Some(
                    UcanBuilder::default()
                        .issued_by(credential)
                        .for_audience(&self.sphere_identity)
                        .with_lifetime(120)
                        .witnessed_by(&witness_ucan, None)
                        .claiming_capability(&generate_capability(
                            &self.sphere_identity,
                            SphereAbility::Publish,
                        ))
                        .with_nonce()
                        .build()?
                        .sign()
                        .await?,
                )
            }
            None => None,
        };

        self.memo.sign(credential, proof.as_ref()).await?;
        Ok(self.store.save::<DagCborCodec, _>(&self.memo).await?.into())
    }
}

/// A [SphereMutation] is created and modified in order to describe changes to a
/// [Sphere]. After initializing the [SphereMutation], changes to the sphere are
/// made to it and then it is "applied" to the [Sphere] to produce a
/// [SphereRevision], which may then be signed.
#[derive(Debug)]
pub struct SphereMutation {
    did: Did,
    content: ContentMutation,
    identities: IdentitiesMutation,
    delegations: DelegationsMutation,
    revocations: RevocationsMutation,
}

impl SphereMutation {
    /// Initialize a new [SphereMutation] with the [Did] of the author of the
    /// mutation
    pub fn new(did: &str) -> Self {
        SphereMutation {
            did: did.into(),
            content: ContentMutation::new(did),
            identities: IdentitiesMutation::new(did),
            delegations: DelegationsMutation::new(did),
            revocations: RevocationsMutation::new(did),
        }
    }

    /// Get the identity of the author of this mutation
    pub fn author(&self) -> &Did {
        &self.did
    }

    /// Reset the state of the [SphereMutation], so that it may be re-used
    /// without being recreated. This is sometimes useful if the code that is
    /// working with the [SphereMutation] does not have sufficient information
    /// to set the author [Did] for a new [SphereMutation].
    pub fn reset(&mut self) {
        self.content = ContentMutation::new(&self.did);
        self.identities = IdentitiesMutation::new(&self.did);
        self.delegations = DelegationsMutation::new(&self.did);
        self.revocations = RevocationsMutation::new(&self.did);
    }

    /// The [Did] of the author of the mutation
    pub fn did(&self) -> &str {
        &self.did
    }

    /// A mutable reference to the changes to sphere content, given as a
    /// [ContentMutation]
    pub fn content_mut(&mut self) -> &mut ContentMutation {
        &mut self.content
    }

    /// An immutable reference to the changes to sphere content, given as a
    /// [ContentMutation]
    pub fn content(&self) -> &ContentMutation {
        &self.content
    }

    /// A mutable reference to the changes to sphere identities (petnames),
    /// given as a [IdentitiesMutation]
    pub fn identities_mut(&mut self) -> &mut IdentitiesMutation {
        &mut self.identities
    }

    /// An immutable reference to the changes to sphere identities (petnames),
    /// given as a [IdentitiesMutation]
    pub fn identities(&self) -> &IdentitiesMutation {
        &self.identities
    }

    /// A mutable reference to the changes to sphere delegations (of authority),
    /// given as a [DelegationsMutation]
    pub fn delegations_mut(&mut self) -> &mut DelegationsMutation {
        &mut self.delegations
    }

    /// An immutable reference to the changes to sphere delegations (of
    /// authority), given as a [DelegationsMutation]
    pub fn delegations(&self) -> &DelegationsMutation {
        &self.delegations
    }

    /// A mutable reference to the changes to sphere revocations (of authority),
    /// given as a [RevocationsMutation]
    pub fn revocations_mut(&mut self) -> &mut RevocationsMutation {
        &mut self.revocations
    }

    /// An immutable reference to the changes to sphere revocations (of
    /// authority), given as a [RevocationsMutation]
    pub fn revocations(&self) -> &RevocationsMutation {
        &self.revocations
    }

    /// Returns true if no new changes would be made by applying this
    /// mutation to a [Sphere]. Otherwise, false.
    pub fn is_empty(&self) -> bool {
        self.content.changes.len() == 0
            && self.identities.changes.len() == 0
            && self.delegations.changes.len() == 0
            && self.revocations.changes.len() == 0
    }

    /// Consume a [SphereMutation], appending its changes to this one
    pub fn append(&mut self, other: SphereMutation) {
        append_changes(&mut self.content.changes, other.content.changes);
        append_changes(&mut self.identities.changes, other.identities.changes);
        append_changes(&mut self.delegations.changes, other.delegations.changes);
        append_changes(&mut self.revocations.changes, other.revocations.changes);
    }
}

fn append_changes<K, V>(destination: &mut Vec<MapOperation<K, V>>, source: Vec<MapOperation<K, V>>)
where
    K: VersionedMapKey,
    V: VersionedMapValue,
{
    for change in source {
        let op_key = match &change {
            MapOperation::Add { key, .. } => key,
            MapOperation::Remove { key } => key,
        };

        destination.retain(|op| {
            let this_op_key = match &op {
                MapOperation::Add { key, .. } => key,
                MapOperation::Remove { key } => key,
            };

            this_op_key != op_key
        });

        destination.push(change);
    }
}

/// A generalized expression of a mutation to a [VersionedMapIpld]
#[derive(Default, Debug)]
pub struct VersionedMapMutation<K, V>
where
    K: VersionedMapKey,
    V: VersionedMapValue,
{
    did: String,
    changes: Vec<MapOperation<K, V>>,
}

impl<K, V> VersionedMapMutation<K, V>
where
    K: VersionedMapKey,
    V: VersionedMapValue,
{
    /// Set the changes as expressed by a [ChangelogIpld] to this
    /// [VersionedMapMutation]; the mutation will adopt the author of the
    /// changelog as its own author
    pub fn apply_changelog(&mut self, changelog: &ChangelogIpld<MapOperation<K, V>>) -> Result<()> {
        let did = changelog
            .did
            .as_ref()
            .ok_or_else(|| anyhow!("Changelog did not have an author DID"))?;

        if did != &self.did {
            return Err(anyhow!(
                "Changelog has unexpected author (was {}, expected {})",
                did,
                self.did
            ));
        }

        self.changes = changelog.changes.clone();

        Ok(())
    }

    /// Initialize a new [VersionedMapMutation] whose author has the given [Did]
    pub fn new(did: &str) -> Self {
        VersionedMapMutation {
            did: did.into(),
            changes: Default::default(),
        }
    }

    /// Get the [Did] of the author of the [VersionedMapMutation]
    pub fn did(&self) -> &str {
        &self.did
    }

    /// Get the changes (as [MapOperation]s) represented by this
    /// [VersionedMapMutation]
    pub fn changes(&self) -> &[MapOperation<K, V>] {
        &self.changes
    }

    /// Record a change to the related [VersionedMapIpld] by key and value
    pub fn set(&mut self, key: &K, value: &V) {
        self.changes.push(MapOperation::Add {
            key: key.clone(),
            value: value.clone(),
        });
    }

    /// Remove a change from the [VersionedMapMutation]
    pub fn remove(&mut self, key: &K) {
        self.changes.push(MapOperation::Remove { key: key.clone() });
    }
}