dubp_documents/membership/
v10.rs

1//  Copyright (C) 2020  Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Wrappers around Membership documents v10.
17
18use crate::*;
19
20/// Type of a Membership.
21#[derive(Debug, Deserialize, Clone, Copy, Hash, Serialize, PartialEq, Eq)]
22pub enum MembershipType {
23    /// The member wishes to opt-in.
24    In(),
25    /// The member wishes to opt-out.
26    Out(),
27}
28
29/// Wrap an Membership document.
30///
31/// Must be created by parsing a text document or using a builder.
32#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
33pub struct MembershipDocumentV10 {
34    /// Document as text.
35    ///
36    /// Is used to check signatures, and other values mut be extracted from it.
37    text: Option<String>,
38
39    /// Name of the currency.
40    currency: String,
41    /// Document issuer.
42    issuer: ed25519::PublicKey,
43    /// Blockstamp
44    blockstamp: Blockstamp,
45    /// Membership message.
46    membership: MembershipType,
47    /// Identity to use for this public key.
48    identity_username: String,
49    /// Identity document blockstamp.
50    identity_blockstamp: Blockstamp,
51    /// Document signature.
52    signature: ed25519::Signature,
53}
54
55#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
56/// identity document for jsonification
57pub struct MembershipDocumentV10Stringified {
58    /// Currency.
59    pub currency: String,
60    /// Document issuer
61    pub issuer: String,
62    /// Blockstamp
63    pub blockstamp: String,
64    /// Membership message.
65    pub membership: String,
66    /// Unique ID
67    pub username: String,
68    /// Identity document blockstamp.
69    pub identity_blockstamp: String,
70    /// Document signature
71    pub signature: String,
72}
73
74impl ToStringObject for MembershipDocumentV10 {
75    type StringObject = MembershipDocumentV10Stringified;
76    /// Transforms an object into a json object
77    fn to_string_object(&self) -> MembershipDocumentV10Stringified {
78        MembershipDocumentV10Stringified {
79            currency: self.currency.clone(),
80            issuer: self.issuer.to_base58(),
81            blockstamp: format!("{}", self.blockstamp),
82            membership: match self.membership {
83                MembershipType::In() => "IN".to_owned(),
84                MembershipType::Out() => "OUT".to_owned(),
85            },
86            username: self.identity_username.clone(),
87            identity_blockstamp: format!("{}", self.identity_blockstamp),
88            signature: self.signature.to_base64(),
89        }
90    }
91}
92
93#[derive(Debug, Copy, Clone, PartialEq, Hash, Deserialize, Serialize)]
94/// Membership event type (blockchain event)
95pub enum MembershipEventType {
96    /// Newcomer
97    Join(),
98    /// Renewal
99    Renewal(),
100    /// Renewal after expire or leave
101    Rejoin(),
102    /// Expire
103    Expire(),
104}
105
106#[derive(Debug, Clone, PartialEq, Hash, Deserialize, Serialize)]
107/// Membership event (blockchain event)
108pub struct MembershipEvent {
109    /// Blockstamp of block event
110    pub blockstamp: Blockstamp,
111    /// Membership document
112    pub doc: MembershipDocumentV10,
113    /// Event type
114    pub event_type: MembershipEventType,
115    /// Chainable time
116    pub chainable_on: u64,
117}
118
119impl MembershipDocumentV10 {
120    /// Membership message.
121    pub fn membership(&self) -> MembershipType {
122        self.membership
123    }
124
125    /// Blockstamp of associated identity
126    pub fn identity_blockstamp(&self) -> Blockstamp {
127        self.identity_blockstamp
128    }
129
130    /// Username of associated identity
131    pub fn identity_username(&self) -> &str {
132        &self.identity_username
133    }
134
135    /// Lightens the membership (for example to store it while minimizing the space required)
136    pub fn reduce(&mut self) {
137        self.text = None;
138    }
139}
140
141impl Document for MembershipDocumentV10 {
142    type PublicKey = ed25519::PublicKey;
143
144    fn version(&self) -> usize {
145        10
146    }
147
148    fn currency(&self) -> &str {
149        &self.currency
150    }
151
152    fn blockstamp(&self) -> Blockstamp {
153        self.blockstamp
154    }
155
156    fn issuers(&self) -> SmallVec<[Self::PublicKey; 1]> {
157        svec![self.issuer]
158    }
159
160    fn signatures(&self) -> SmallVec<[<Self::PublicKey as PublicKey>::Signature; 1]> {
161        svec![self.signature]
162    }
163
164    fn as_bytes(&self) -> BeefCow<[u8]> {
165        BeefCow::borrowed(self.as_text().as_bytes())
166    }
167}
168
169impl CompactTextDocument for MembershipDocumentV10 {
170    fn as_compact_text(&self) -> String {
171        format!(
172            "{issuer}:{signature}:{blockstamp}:{idty_blockstamp}:{username}",
173            issuer = self.issuer,
174            signature = self.signature,
175            blockstamp = self.blockstamp,
176            idty_blockstamp = self.identity_blockstamp,
177            username = self.identity_username,
178        )
179    }
180}
181
182/// CompactPoolMembershipDoc
183#[derive(Copy, Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
184pub struct CompactPoolMembershipDoc {
185    /// Document creation blockstamp
186    pub blockstamp: Blockstamp,
187    /// Signature
188    pub signature: Sig,
189}
190
191impl TextDocument for MembershipDocumentV10 {
192    type CompactTextDocument_ = MembershipDocumentV10;
193
194    fn as_text(&self) -> &str {
195        if let Some(ref text) = self.text {
196            text
197        } else {
198            panic!("Try to get text of reduce membership !")
199        }
200    }
201
202    fn to_compact_document(&self) -> Cow<Self::CompactTextDocument_> {
203        Cow::Borrowed(self)
204    }
205}
206
207/// Membership document builder.
208#[derive(Debug, Copy, Clone)]
209pub struct MembershipDocumentV10Builder<'a> {
210    /// Document currency.
211    pub currency: &'a str,
212    /// Document/identity issuer.
213    pub issuer: ed25519::PublicKey,
214    /// Reference blockstamp.
215    pub blockstamp: Blockstamp,
216    /// Membership message.
217    pub membership: MembershipType,
218    /// Identity username.
219    pub identity_username: &'a str,
220    /// Identity document blockstamp.
221    pub identity_blockstamp: Blockstamp,
222}
223
224impl<'a> TextDocumentBuilder for MembershipDocumentV10Builder<'a> {
225    type Document = MembershipDocumentV10;
226    type Signator = ed25519::Signator;
227
228    fn build_with_text_and_sigs(
229        self,
230        text: String,
231        signatures: SmallVec<
232            [<<Self::Document as Document>::PublicKey as PublicKey>::Signature; 1],
233        >,
234    ) -> MembershipDocumentV10 {
235        MembershipDocumentV10 {
236            text: Some(text),
237            currency: self.currency.to_string(),
238            issuer: self.issuer,
239            blockstamp: self.blockstamp,
240            membership: self.membership,
241            identity_username: self.identity_username.to_string(),
242            identity_blockstamp: self.identity_blockstamp,
243            signature: signatures[0],
244        }
245    }
246
247    fn generate_text(&self) -> String {
248        format!(
249            "Version: 10
250Type: Membership
251Currency: {currency}
252Issuer: {issuer}
253Block: {blockstamp}
254Membership: {membership}
255UserID: {username}
256CertTS: {ity_blockstamp}
257",
258            currency = self.currency,
259            issuer = self.issuer,
260            blockstamp = self.blockstamp,
261            membership = match self.membership {
262                MembershipType::In() => "IN",
263                MembershipType::Out() => "OUT",
264            },
265            username = self.identity_username,
266            ity_blockstamp = self.identity_blockstamp,
267        )
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use smallvec::smallvec;
275    use std::str::FromStr;
276    use unwrap::unwrap;
277
278    #[test]
279    fn generate_real_document() {
280        let keypair = ed25519::KeyPairFromSeed32Generator::generate(unwrap!(
281            Seed32::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"),
282            "fail to build Seed32"
283        ));
284        let pubkey = keypair.public_key();
285        let signator = keypair.generate_signator();
286
287        let sig = unwrap!(ed25519::Signature::from_base64(
288                "cUgoc8AI+Tae/AZmRfTnW+xq3XFtmYoUi2LXlmXr8/7LaXiUccQb8+Ds1nZoBp/8+t031HMwqAUpVIqww2FGCg==",
289            ), "fail to build Signature");
290
291        let blockstamp = unwrap!(
292            Blockstamp::from_str(
293                "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
294            ),
295            "fail to build Blockstamp"
296        );
297
298        let builder = MembershipDocumentV10Builder {
299            currency: "duniter_unit_test_currency",
300            issuer: pubkey,
301            blockstamp,
302            membership: MembershipType::In(),
303            identity_username: "tic",
304            identity_blockstamp: blockstamp,
305        };
306
307        assert!(builder
308            .build_with_signature(smallvec![sig])
309            .verify_signatures()
310            .is_ok());
311        assert!(builder
312            .build_and_sign(vec![signator])
313            .verify_signatures()
314            .is_ok());
315    }
316}