duniter_documents/blockchain/v10/documents/
certification.rs1use duniter_crypto::keys::{PublicKey, Signature, ed25519};
19use regex::Regex;
20
21use Blockstamp;
22use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
23use blockchain::v10::documents::{StandardTextDocumentParser, TextDocument, TextDocumentBuilder,
24 V10Document, V10DocumentParsingError};
25
26lazy_static! {
27 static ref CERTIFICATION_REGEX: Regex = Regex::new(
28 "^Issuer: (?P<issuer>[1-9A-Za-z][^OIl]{43,44})\n\
29 IdtyIssuer: (?P<target>[1-9A-Za-z][^OIl]{43,44})\n\
30 IdtyUniqueID: (?P<idty_uid>[[:alnum:]_-]+)\n\
31 IdtyTimestamp: (?P<idty_blockstamp>[0-9]+-[0-9A-F]{64})\n\
32 IdtySignature: (?P<idty_sig>(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)\n\
33 CertTimestamp: (?P<blockstamp>[0-9]+-[0-9A-F]{64})\n$"
34 ).unwrap();
35}
36
37#[derive(Debug, Clone)]
41pub struct CertificationDocument {
42 text: String,
46
47 currency: String,
49 issuers: Vec<ed25519::PublicKey>,
51 target: ed25519::PublicKey,
53 identity_username: String,
55 identity_blockstamp: Blockstamp,
57 identity_sig: ed25519::Signature,
59 blockstamp: Blockstamp,
61 signatures: Vec<ed25519::Signature>,
63}
64
65impl CertificationDocument {
66 pub fn identity_username(&self) -> &str {
68 &self.identity_username
69 }
70
71 pub fn source(&self) -> &ed25519::PublicKey {
73 &self.issuers[0]
74 }
75
76 pub fn target(&self) -> &ed25519::PublicKey {
78 &self.target
79 }
80}
81
82impl Document for CertificationDocument {
83 type PublicKey = ed25519::PublicKey;
84 type CurrencyType = str;
85
86 fn version(&self) -> u16 {
87 10
88 }
89
90 fn currency(&self) -> &str {
91 &self.currency
92 }
93
94 fn issuers(&self) -> &Vec<ed25519::PublicKey> {
95 &self.issuers
96 }
97
98 fn signatures(&self) -> &Vec<ed25519::Signature> {
99 &self.signatures
100 }
101
102 fn as_bytes(&self) -> &[u8] {
103 self.as_text().as_bytes()
104 }
105}
106
107impl TextDocument for CertificationDocument {
108 fn as_text(&self) -> &str {
109 &self.text
110 }
111
112 fn generate_compact_text(&self) -> String {
113 format!(
114 "{issuer}:{target}:{block_number}:{signature}",
115 issuer = self.issuers[0],
116 target = self.target,
117 block_number = self.blockstamp.id.0,
118 signature = self.signatures[0],
119 )
120 }
121}
122
123impl IntoSpecializedDocument<BlockchainProtocol> for CertificationDocument {
124 fn into_specialized(self) -> BlockchainProtocol {
125 BlockchainProtocol::V10(Box::new(V10Document::Certification(Box::new(self))))
126 }
127}
128
129#[derive(Debug, Copy, Clone)]
131pub struct CertificationDocumentBuilder<'a> {
132 pub currency: &'a str,
134 pub issuer: &'a ed25519::PublicKey,
136 pub blockstamp: &'a Blockstamp,
138 pub target: &'a ed25519::PublicKey,
140 pub identity_username: &'a str,
142 pub identity_blockstamp: &'a Blockstamp,
144 pub identity_sig: &'a ed25519::Signature,
146}
147
148impl<'a> CertificationDocumentBuilder<'a> {
149 fn build_with_text_and_sigs(
150 self,
151 text: String,
152 signatures: Vec<ed25519::Signature>,
153 ) -> CertificationDocument {
154 CertificationDocument {
155 text,
156 currency: self.currency.to_string(),
157 issuers: vec![*self.issuer],
158 blockstamp: *self.blockstamp,
159 target: *self.target,
160 identity_username: self.identity_username.to_string(),
161 identity_blockstamp: *self.identity_blockstamp,
162 identity_sig: *self.identity_sig,
163 signatures,
164 }
165 }
166}
167
168impl<'a> DocumentBuilder for CertificationDocumentBuilder<'a> {
169 type Document = CertificationDocument;
170 type PrivateKey = ed25519::PrivateKey;
171
172 fn build_with_signature(&self, signatures: Vec<ed25519::Signature>) -> CertificationDocument {
173 self.build_with_text_and_sigs(self.generate_text(), signatures)
174 }
175
176 fn build_and_sign(&self, private_keys: Vec<ed25519::PrivateKey>) -> CertificationDocument {
177 let (text, signatures) = self.build_signed_text(private_keys);
178 self.build_with_text_and_sigs(text, signatures)
179 }
180}
181
182impl<'a> TextDocumentBuilder for CertificationDocumentBuilder<'a> {
183 fn generate_text(&self) -> String {
184 format!(
185 "Version: 10
186Type: Certification
187Currency: {currency}
188Issuer: {issuer}
189IdtyIssuer: {target}
190IdtyUniqueID: {idty_uid}
191IdtyTimestamp: {idty_blockstamp}
192IdtySignature: {idty_sig}
193CertTimestamp: {blockstamp}
194",
195 currency = self.currency,
196 issuer = self.issuer,
197 target = self.target,
198 idty_uid = self.identity_username,
199 idty_blockstamp = self.identity_blockstamp,
200 idty_sig = self.identity_sig,
201 blockstamp = self.blockstamp,
202 )
203 }
204}
205
206#[derive(Debug, Clone, Copy)]
208pub struct CertificationDocumentParser;
209
210impl StandardTextDocumentParser for CertificationDocumentParser {
211 fn parse_standard(
212 doc: &str,
213 body: &str,
214 currency: &str,
215 signatures: Vec<ed25519::Signature>,
216 ) -> Result<V10Document, V10DocumentParsingError> {
217 if let Some(caps) = CERTIFICATION_REGEX.captures(body) {
218 let issuer = &caps["issuer"];
219 let target = &caps["target"];
220 let identity_username = &caps["idty_uid"];
221 let identity_blockstamp = &caps["idty_blockstamp"];
222 let identity_sig = &caps["idty_sig"];
223 let blockstamp = &caps["blockstamp"];
224
225 let issuer = ed25519::PublicKey::from_base58(issuer).unwrap();
228 let target = ed25519::PublicKey::from_base58(target).unwrap();
229 let identity_username = String::from(identity_username);
230 let identity_blockstamp = Blockstamp::from_string(identity_blockstamp).unwrap();
231 let identity_sig = ed25519::Signature::from_base64(identity_sig).unwrap();
232 let blockstamp = Blockstamp::from_string(blockstamp).unwrap();
233
234 Ok(V10Document::Certification(Box::new(
235 CertificationDocument {
236 text: doc.to_owned(),
237 issuers: vec![issuer],
238 currency: currency.to_owned(),
239 target,
240 identity_username,
241 identity_blockstamp,
242 identity_sig,
243 blockstamp,
244 signatures,
245 },
246 )))
247 } else {
248 Err(V10DocumentParsingError::InvalidInnerFormat(
249 "Certification".to_string(),
250 ))
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use duniter_crypto::keys::{PrivateKey, PublicKey, Signature};
259 use blockchain::VerificationResult;
260
261 #[test]
262 fn generate_real_document() {
263 let pubkey = ed25519::PublicKey::from_base58(
264 "4tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR",
265 ).unwrap();
266
267 let prikey = ed25519::PrivateKey::from_base58(
268 "3XGWuuU1dQ7zaYPzE76ATfY71STzRkbT3t4DE1bSjMhYje81XdJFeXVG9uMPi3oDeRTosT2dmBAFH8VydrAUWXRZ",
269 ).unwrap();
270
271 let sig = ed25519::Signature::from_base64(
272 "qfR6zqT1oJbqIsppOi64gC9yTtxb6g6XA9RYpulkq9ehMvqg2VYVigCbR0yVpqKFsnYiQTrnjgFuFRSJCJDfCw==",
273 ).unwrap();
274
275 let target = ed25519::PublicKey::from_base58(
276 "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV",
277 ).unwrap();
278
279 let identity_blockstamp = Blockstamp::from_string(
280 "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
281 ).unwrap();
282
283 let identity_sig = ed25519::Signature::from_base64(
284 "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMMmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
285 ).unwrap();
286
287 let blockstamp = Blockstamp::from_string(
288 "36-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B865",
289 ).unwrap();
290
291 let builder = CertificationDocumentBuilder {
292 currency: "duniter_unit_test_currency",
293 issuer: &pubkey,
294 target: &target,
295 identity_username: "tic",
296 identity_blockstamp: &identity_blockstamp,
297 identity_sig: &identity_sig,
298 blockstamp: &blockstamp,
299 };
300
301 assert_eq!(
302 builder.build_with_signature(vec![sig]).verify_signatures(),
303 VerificationResult::Valid()
304 );
305
306 assert_eq!(
307 builder.build_and_sign(vec![prikey]).verify_signatures(),
308 VerificationResult::Valid()
309 );
310 }
311
312 #[test]
313 fn certification_standard_regex() {
314 assert!(CERTIFICATION_REGEX.is_match(
315 "Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
316IdtyIssuer: 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9
317IdtyUniqueID: fbarbut
318IdtyTimestamp: 98221-000000575AC04F5164F7A307CDB766139EA47DD249E4A2444F292BC8AAB408B3
319IdtySignature: DjeipIeb/RF0tpVCnVnuw6mH1iLJHIsDfPGLR90Twy3PeoaDz6Yzhc/UjLWqHCi5Y6wYajV0dNg4jQRUneVBCQ==
320CertTimestamp: 99956-00000472758331FDA8388E30E50CA04736CBFD3B7C21F34E74707107794B56DD
321"
322 ));
323 }
324
325 #[test]
326 fn certification_document() {
327 let doc = "Version: 10
328Type: Certification
329Currency: g1
330Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT
331IdtyIssuer: 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9
332IdtyUniqueID: fbarbut
333IdtyTimestamp: 98221-000000575AC04F5164F7A307CDB766139EA47DD249E4A2444F292BC8AAB408B3
334IdtySignature: DjeipIeb/RF0tpVCnVnuw6mH1iLJHIsDfPGLR90Twy3PeoaDz6Yzhc/UjLWqHCi5Y6wYajV0dNg4jQRUneVBCQ==
335CertTimestamp: 99956-00000472758331FDA8388E30E50CA04736CBFD3B7C21F34E74707107794B56DD
336";
337
338 let body = "Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT
339IdtyIssuer: 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9
340IdtyUniqueID: fbarbut
341IdtyTimestamp: 98221-000000575AC04F5164F7A307CDB766139EA47DD249E4A2444F292BC8AAB408B3
342IdtySignature: DjeipIeb/RF0tpVCnVnuw6mH1iLJHIsDfPGLR90Twy3PeoaDz6Yzhc/UjLWqHCi5Y6wYajV0dNg4jQRUneVBCQ==
343CertTimestamp: 99956-00000472758331FDA8388E30E50CA04736CBFD3B7C21F34E74707107794B56DD
344";
345
346 let currency = "g1";
347
348 let signatures = vec![Signature::from_base64(
349"Hkps1QU4HxIcNXKT8YmprYTVByBhPP1U2tIM7Z8wENzLKIWAvQClkAvBE7pW9dnVa18sJIJhVZUcRrPAZfmjBA=="
350 ).unwrap(),];
351
352 let doc =
353 CertificationDocumentParser::parse_standard(doc, body, currency, signatures).unwrap();
354 if let V10Document::Certification(doc) = doc {
355 println!("Doc : {:?}", doc);
356 assert_eq!(doc.verify_signatures(), VerificationResult::Valid());
357 assert_eq!(
358 doc.generate_compact_text(),
359 "2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT:\
360 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9:99956:\
361 Hkps1QU4HxIcNXKT8YmprYTVByBhPP1U2tIM7Z8wENzLKIWAvQClkAvBE7pW9dnVa18sJIJhVZUcRrPAZfmjBA=="
362 );
363 } else {
364 panic!("Wrong document type");
365 }
366 }
367}