duniter_documents/blockchain/v10/documents/
identity.rs1use duniter_crypto::keys::{PublicKey, 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 IDENTITY_REGEX: Regex = Regex::new(
28 "^Issuer: (?P<issuer>[1-9A-Za-z][^OIl]{43,44})\nUniqueID: (?P<uid>[[:alnum:]_-]+)\nTimestamp: (?P<blockstamp>[0-9]+-[0-9A-F]{64})\n$"
29 ).unwrap();
30}
31
32#[derive(Debug, Clone, PartialEq, Hash)]
36pub struct IdentityDocument {
37 text: String,
42
43 currency: String,
45 username: String,
47 blockstamp: Blockstamp,
49 issuers: Vec<ed25519::PublicKey>,
51 signatures: Vec<ed25519::Signature>,
53}
54
55impl IdentityDocument {
56 pub fn username(&self) -> &str {
58 &self.username
59 }
60}
61
62impl Document for IdentityDocument {
63 type PublicKey = ed25519::PublicKey;
64 type CurrencyType = str;
65
66 fn version(&self) -> u16 {
67 10
68 }
69
70 fn currency(&self) -> &str {
71 &self.currency
72 }
73
74 fn issuers(&self) -> &Vec<ed25519::PublicKey> {
75 &self.issuers
76 }
77
78 fn signatures(&self) -> &Vec<ed25519::Signature> {
79 &self.signatures
80 }
81
82 fn as_bytes(&self) -> &[u8] {
83 self.as_text().as_bytes()
84 }
85}
86
87impl TextDocument for IdentityDocument {
88 fn as_text(&self) -> &str {
89 &self.text
90 }
91
92 fn generate_compact_text(&self) -> String {
93 format!(
94 "{issuer}:{signature}:{blockstamp}:{username}",
95 issuer = self.issuers[0],
96 signature = self.signatures[0],
97 blockstamp = self.blockstamp,
98 username = self.username,
99 )
100 }
101}
102
103impl IntoSpecializedDocument<BlockchainProtocol> for IdentityDocument {
104 fn into_specialized(self) -> BlockchainProtocol {
105 BlockchainProtocol::V10(Box::new(V10Document::Identity(self)))
106 }
107}
108
109#[derive(Debug, Copy, Clone)]
111pub struct IdentityDocumentBuilder<'a> {
112 pub currency: &'a str,
114 pub username: &'a str,
116 pub blockstamp: &'a Blockstamp,
118 pub issuer: &'a ed25519::PublicKey,
120}
121
122impl<'a> IdentityDocumentBuilder<'a> {
123 fn build_with_text_and_sigs(
124 self,
125 text: String,
126 signatures: Vec<ed25519::Signature>,
127 ) -> IdentityDocument {
128 IdentityDocument {
129 text,
130 currency: self.currency.to_string(),
131 username: self.username.to_string(),
132 blockstamp: *self.blockstamp,
133 issuers: vec![*self.issuer],
134 signatures,
135 }
136 }
137}
138
139impl<'a> DocumentBuilder for IdentityDocumentBuilder<'a> {
140 type Document = IdentityDocument;
141 type PrivateKey = ed25519::PrivateKey;
142
143 fn build_with_signature(&self, signatures: Vec<ed25519::Signature>) -> IdentityDocument {
144 self.build_with_text_and_sigs(self.generate_text(), signatures)
145 }
146
147 fn build_and_sign(&self, private_keys: Vec<ed25519::PrivateKey>) -> IdentityDocument {
148 let (text, signatures) = self.build_signed_text(private_keys);
149 self.build_with_text_and_sigs(text, signatures)
150 }
151}
152
153impl<'a> TextDocumentBuilder for IdentityDocumentBuilder<'a> {
154 fn generate_text(&self) -> String {
155 format!(
156 "Version: 10
157Type: Identity
158Currency: {currency}
159Issuer: {issuer}
160UniqueID: {username}
161Timestamp: {blockstamp}
162",
163 currency = self.currency,
164 issuer = self.issuer,
165 username = self.username,
166 blockstamp = self.blockstamp
167 )
168 }
169}
170
171#[derive(Debug, Clone, Copy)]
173pub struct IdentityDocumentParser;
174
175impl StandardTextDocumentParser for IdentityDocumentParser {
176 fn parse_standard(
177 doc: &str,
178 body: &str,
179 currency: &str,
180 signatures: Vec<ed25519::Signature>,
181 ) -> Result<V10Document, V10DocumentParsingError> {
182 if let Some(caps) = IDENTITY_REGEX.captures(body) {
183 let issuer = &caps["issuer"];
184 let uid = &caps["uid"];
185 let blockstamp = &caps["blockstamp"];
186
187 let issuer = ed25519::PublicKey::from_base58(issuer).unwrap();
190 let blockstamp = Blockstamp::from_string(blockstamp).unwrap();
191
192 Ok(V10Document::Identity(IdentityDocument {
193 text: doc.to_owned(),
194 currency: currency.to_owned(),
195 username: uid.to_owned(),
196 blockstamp,
197 issuers: vec![issuer],
198 signatures,
199 }))
200 } else {
201 Err(V10DocumentParsingError::InvalidInnerFormat(
202 "Identity".to_string(),
203 ))
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use duniter_crypto::keys::{PrivateKey, PublicKey, Signature};
212 use blockchain::{Document, VerificationResult};
213
214 #[test]
215 fn generate_real_document() {
216 let pubkey = ed25519::PublicKey::from_base58(
217 "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV",
218 ).unwrap();
219
220 let prikey = ed25519::PrivateKey::from_base58(
221 "468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5G\
222 iERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7",
223 ).unwrap();
224
225 let sig = ed25519::Signature::from_base64(
226 "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGM\
227 MmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
228 ).unwrap();
229
230 let block = Blockstamp::from_string(
231 "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
232 ).unwrap();
233
234 let builder = IdentityDocumentBuilder {
235 currency: "duniter_unit_test_currency",
236 username: "tic",
237 blockstamp: &block,
238 issuer: &pubkey,
239 };
240
241 assert_eq!(
242 builder.build_with_signature(vec![sig]).verify_signatures(),
243 VerificationResult::Valid()
244 );
245 assert_eq!(
246 builder.build_and_sign(vec![prikey]).verify_signatures(),
247 VerificationResult::Valid()
248 );
249 }
250
251 #[test]
252 fn identity_standard_regex() {
253 assert!(IDENTITY_REGEX.is_match(
254 "Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
255UniqueID: tic
256Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
257"
258 ));
259 }
260
261 #[test]
262 fn parse_identity_document() {
263 let doc = "Version: 10
264Type: Identity
265Currency: duniter_unit_test_currency
266Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
267UniqueID: tic
268Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
269";
270
271 let body = "Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
272UniqueID: tic
273Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
274";
275
276 let currency = "duniter_unit_test_currency";
277
278 let signatures = vec![Signature::from_base64(
279"1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMMmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg=="
280 ).unwrap(),];
281
282 let doc = IdentityDocumentParser::parse_standard(doc, body, currency, signatures).unwrap();
283 if let V10Document::Identity(doc) = doc {
284 println!("Doc : {:?}", doc);
285 assert_eq!(doc.verify_signatures(), VerificationResult::Valid())
286 } else {
287 panic!("Wrong document type");
288 }
289 }
290}