1use crate::{TrustchainAPI, DATA_ATTRIBUTE, DATA_CREDENTIAL_TEMPLATE};
2use async_trait::async_trait;
3use did_ion::sidetree::DocumentState;
4use futures::{stream, StreamExt, TryStreamExt};
5use sha2::{Digest, Sha256};
6use ssi::{
7 did_resolve::DIDResolver,
8 jsonld::ContextLoader,
9 ldp::LinkedDataDocument,
10 vc::{Credential, CredentialOrJWT, LinkedDataProofOptions, Presentation, URI},
11};
12use std::error::Error;
13use trustchain_core::{
14 chain::DIDChain,
15 holder::Holder,
16 issuer::{Issuer, IssuerError},
17 resolver::{ResolverResult, TrustchainResolver},
18 vc::{CredentialError, DataCredentialError},
19 verifier::{Timestamp, Verifier, VerifierError},
20 vp::PresentationError,
21};
22use trustchain_ion::{
23 attest::attest_operation, attestor::IONAttestor, create::create_operation, trustchain_resolver,
24};
25
26#[async_trait]
28pub trait TrustchainDIDAPI {
29 fn create(
34 document_state: Option<DocumentState>,
35 verbose: bool,
36 ) -> Result<String, Box<dyn Error>> {
37 create_operation(document_state, verbose)
38 }
39 async fn attest(did: &str, controlled_did: &str, verbose: bool) -> Result<(), Box<dyn Error>> {
42 attest_operation(did, controlled_did, verbose).await
43 }
44 async fn resolve(did: &str, resolver: &dyn TrustchainResolver) -> ResolverResult {
46 resolver.resolve_as_result(did).await
48 }
49
50 async fn verify<T, U>(
52 did: &str,
53 root_event_time: Timestamp,
54 verifier: &U,
55 ) -> Result<DIDChain, VerifierError>
56 where
57 T: DIDResolver + Send,
58 U: Verifier<T> + Send + Sync,
59 {
60 verifier.verify(did, root_event_time).await
61 }
62
63 }
81
82#[async_trait]
84pub trait TrustchainVCAPI {
85 async fn sign(
87 mut credential: Credential,
88 did: &str,
89 linked_data_proof_options: Option<LinkedDataProofOptions>,
90 key_id: Option<&str>,
91 resolver: &dyn TrustchainResolver,
92 context_loader: &mut ContextLoader,
93 ) -> Result<Credential, IssuerError> {
94 credential.issuer = Some(ssi::vc::Issuer::URI(URI::String(did.to_string())));
95 let attestor = IONAttestor::new(did);
96 attestor
97 .sign(
98 &credential,
99 linked_data_proof_options,
100 key_id,
101 resolver,
102 context_loader,
103 )
104 .await
105 }
106
107 async fn verify_credential<T, U>(
109 credential: &Credential,
110 linked_data_proof_options: Option<LinkedDataProofOptions>,
111 root_event_time: Timestamp,
112 verifier: &U,
113 context_loader: &mut ContextLoader,
114 ) -> Result<DIDChain, CredentialError>
115 where
116 T: DIDResolver + Send,
117 U: Verifier<T> + Send + Sync,
118 {
119 let result = credential
121 .verify(
122 linked_data_proof_options,
123 verifier.resolver().as_did_resolver(),
124 context_loader,
125 )
126 .await;
127 if !result.errors.is_empty() {
128 return Err(CredentialError::VerificationResultError(result));
129 }
130 let issuer = credential
132 .get_issuer()
133 .ok_or(CredentialError::NoIssuerPresent)?;
134 Ok(verifier.verify(issuer, root_event_time).await?)
135 }
136}
137
138#[async_trait]
140pub trait TrustchainVPAPI {
141 async fn sign_presentation(
143 presentation: Presentation,
144 did: &str,
145 key_id: Option<&str>,
146 endpoint: &str,
147 linked_data_proof_options: Option<LinkedDataProofOptions>,
148 context_loader: &mut ContextLoader,
149 ) -> Result<Presentation, PresentationError> {
150 let resolver = trustchain_resolver(endpoint);
151 let attestor = IONAttestor::new(did);
152 Ok(attestor
153 .sign_presentation(
154 &presentation,
155 linked_data_proof_options,
156 key_id,
157 &resolver,
158 context_loader,
159 )
160 .await?)
161 }
162 async fn verify_presentation<T, U>(
164 presentation: &Presentation,
165 ldp_options: Option<LinkedDataProofOptions>,
166 root_event_time: Timestamp,
167 verifier: &U,
168 context_loader: &mut ContextLoader,
169 ) -> Result<(), PresentationError>
170 where
171 T: DIDResolver + Send,
172 U: Verifier<T> + Send + Sync,
173 {
174 let credentials = presentation
176 .verifiable_credential
177 .as_ref()
178 .ok_or(PresentationError::NoCredentialsPresent)?;
179
180 let limit = Some(5);
183 let ldp_opts_and_context_loader: Vec<(Option<LinkedDataProofOptions>, ContextLoader)> = (0
184 ..credentials.len())
185 .map(|_| (ldp_options.clone(), context_loader.clone()))
186 .collect();
187 stream::iter(credentials.into_iter().zip(ldp_opts_and_context_loader))
188 .map(Ok)
189 .try_for_each_concurrent(
190 limit,
191 |(credential_or_jwt, (ldp_opts, mut context_loader))| async move {
192 match credential_or_jwt {
193 CredentialOrJWT::Credential(credential) => {
194 TrustchainAPI::verify_credential(
195 credential,
196 ldp_opts,
197 root_event_time,
198 verifier,
199 &mut context_loader,
200 )
201 .await
202 .map(|_| ())
203 }
204 CredentialOrJWT::JWT(jwt) => {
205 match Credential::decode_verify_jwt(
207 jwt,
208 ldp_opts.clone(),
209 verifier.resolver().as_did_resolver(),
210 &mut context_loader,
211 )
212 .await
213 .0
214 .ok_or(CredentialError::FailedToDecodeJWT)
215 {
216 Ok(credential) => TrustchainAPI::verify_credential(
217 &credential,
218 ldp_opts,
219 root_event_time,
220 verifier,
221 &mut context_loader,
222 )
223 .await
224 .map(|_| ()),
225 Err(e) => Err(e),
226 }
227 }
228 }
229 },
230 )
231 .await?;
232
233 let result = presentation
235 .verify(
236 ldp_options.clone(),
237 verifier.resolver().as_did_resolver(),
238 context_loader,
239 )
240 .await;
241 if !result.errors.is_empty() {
242 return Err(PresentationError::VerifiedHolderUnauthenticated(result));
243 }
244 Ok(())
245 }
246}
247
248#[async_trait]
250pub trait TrustchainDataAPI {
251 async fn sign_data(
253 bytes: &[u8],
254 did: &str,
255 linked_data_proof_options: Option<LinkedDataProofOptions>,
256 key_id: Option<&str>,
257 resolver: &dyn TrustchainResolver,
258 context_loader: &mut ContextLoader,
259 ) -> Result<Credential, IssuerError> {
260 let mut credential = Credential::from_json_unsigned(DATA_CREDENTIAL_TEMPLATE).unwrap();
262 credential.issuer = Some(ssi::vc::Issuer::URI(URI::String(did.to_string())));
264 credential.issuance_date = Some(chrono::offset::Local::now().into());
265
266 let data_hash = Sha256::digest(bytes);
268
269 let data_element = credential
271 .credential_subject
272 .to_single_mut()
273 .expect("Template credential has a single credentialSubject.")
274 .property_set
275 .as_mut()
276 .expect("Template credential has a property set.")
277 .get_mut(DATA_ATTRIBUTE)
278 .expect("Template credential has a dataset property.");
279 *data_element = hex::encode(data_hash).to_string().into();
280
281 let attestor = IONAttestor::new(did);
283 Ok(attestor
284 .sign(
285 &credential,
286 linked_data_proof_options,
287 key_id,
288 resolver,
289 context_loader,
290 )
291 .await?)
292 }
293
294 async fn verify_data<T, U>(
296 bytes: &[u8],
297 credential: &Credential,
298 linked_data_proof_options: Option<LinkedDataProofOptions>,
299 root_event_time: Timestamp,
300 verifier: &U,
301 context_loader: &mut ContextLoader,
302 ) -> Result<DIDChain, DataCredentialError>
303 where
304 T: DIDResolver + Send,
305 U: Verifier<T> + Send + Sync,
306 {
307 let actual_hash = hex::encode(Sha256::digest(bytes));
309
310 let expected_hash = credential
312 .credential_subject
313 .to_single()
314 .ok_or(DataCredentialError::ManyCredentialSubject(
315 credential.credential_subject.clone(),
316 ))?
317 .property_set
318 .as_ref()
319 .ok_or(DataCredentialError::MissingAttribute(
320 "property_set".to_string(),
321 ))?
322 .get(DATA_ATTRIBUTE)
323 .ok_or(DataCredentialError::MissingAttribute(
324 DATA_ATTRIBUTE.to_string(),
325 ))?
326 .as_str()
327 .expect("dataset attribute is a str");
328
329 if actual_hash != expected_hash {
330 return Err(DataCredentialError::MismatchedHashDigests(
331 expected_hash.to_string(),
332 actual_hash,
333 ));
334 };
335 TrustchainAPI::verify_credential(
337 credential,
338 linked_data_proof_options,
339 root_event_time,
340 verifier,
341 context_loader,
342 )
343 .await
344 .map_err(DataCredentialError::CredentialError)
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use crate::api::{
351 TrustchainDataAPI, TrustchainVCAPI, TrustchainVPAPI, DATA_CREDENTIAL_TEMPLATE,
352 };
353 use crate::TrustchainAPI;
354 use sha2::{Digest, Sha256};
355 use ssi::jsonld::ContextLoader;
356 use ssi::ldp::now_ns;
357 use ssi::one_or_many::OneOrMany;
358 use ssi::vc::{Credential, CredentialOrJWT, Presentation, VCDateTime, URI};
359 use trustchain_core::utils::init;
360 use trustchain_core::vc::{CredentialError, DataCredentialError};
361 use trustchain_core::vp::PresentationError;
362 use trustchain_core::{holder::Holder, issuer::Issuer};
363 use trustchain_ion::attestor::IONAttestor;
364 use trustchain_ion::trustchain_resolver;
365 use trustchain_ion::verifier::TrustchainVerifier;
366
367 const ROOT_EVENT_TIME_1: u64 = 1666265405;
369
370 const TEST_UNSIGNED_VC: &str = r#"{
371 "@context": [
372 "https://www.w3.org/2018/credentials/v1",
373 "https://www.w3.org/2018/credentials/examples/v1",
374 "https://w3id.org/citizenship/v1"
375 ],
376 "type": ["VerifiableCredential"],
377 "issuer": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
378 "credentialSubject": {
379 "givenName": "Jane",
380 "familyName": "Doe",
381 "degree": {
382 "type": "BachelorDegree",
383 "name": "Bachelor of Science and Arts",
384 "college": "College of Engineering"
385 }
386 }
387 }
388 "#;
389
390 #[ignore = "requires a running Sidetree node listening on http://localhost:3000"]
391 #[tokio::test]
392 async fn test_verify_credential() {
393 init();
394 let issuer_did = "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"; let issuer = IONAttestor::new(issuer_did);
396 let mut vc_with_proof = signed_credential(issuer).await;
397 let resolver = trustchain_resolver("http://localhost:3000/");
398 let mut context_loader = ContextLoader::default();
399 let res = TrustchainAPI::verify_credential(
400 &vc_with_proof,
401 None,
402 ROOT_EVENT_TIME_1,
403 &TrustchainVerifier::new(resolver),
404 &mut context_loader,
405 )
406 .await;
407 assert!(res.is_ok());
408
409 vc_with_proof.expiration_date = Some(VCDateTime::from(now_ns()));
411
412 let resolver = trustchain_resolver("http://localhost:3000/");
414 let res = TrustchainAPI::verify_credential(
415 &vc_with_proof,
416 None,
417 ROOT_EVENT_TIME_1,
418 &TrustchainVerifier::new(resolver),
419 &mut context_loader,
420 )
421 .await;
422 if let CredentialError::VerificationResultError(ver_res) = res.err().unwrap() {
423 assert_eq!(ver_res.errors, vec!["signature error"]);
424 } else {
425 panic!("should error with VerificationResultError varient of CredentialError")
426 }
427 }
428
429 #[ignore = "requires a running Sidetree node listening on http://localhost:3000"]
430 #[tokio::test]
431 async fn test_verify_presentation() {
432 init();
433 let issuer_did = "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"; let holder_did = "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q"; let issuer = IONAttestor::new(issuer_did);
437 let holder = IONAttestor::new(holder_did);
438
439 let vc_with_proof = signed_credential(issuer).await;
440 let resolver = trustchain_resolver("http://localhost:3000/");
441 let mut context_loader = ContextLoader::default();
442
443 let mut presentation = Presentation {
447 verifiable_credential: Some(OneOrMany::Many(vec![
448 CredentialOrJWT::Credential(vc_with_proof.clone()),
449 CredentialOrJWT::Credential(vc_with_proof.clone()),
450 CredentialOrJWT::Credential(vc_with_proof.clone()),
451 CredentialOrJWT::Credential(vc_with_proof.clone()),
452 CredentialOrJWT::Credential(vc_with_proof.clone()),
453 CredentialOrJWT::Credential(vc_with_proof.clone()),
454 CredentialOrJWT::Credential(vc_with_proof.clone()),
455 CredentialOrJWT::Credential(vc_with_proof.clone()),
456 CredentialOrJWT::Credential(vc_with_proof.clone()),
457 CredentialOrJWT::Credential(vc_with_proof.clone()),
458 ])),
481 ..Default::default()
486 };
487
488 presentation = holder
489 .sign_presentation(&presentation, None, None, &resolver, &mut context_loader)
490 .await
491 .unwrap();
492 println!("{}", serde_json::to_string_pretty(&presentation).unwrap());
493 let res = TrustchainAPI::verify_presentation(
494 &presentation,
495 None,
496 ROOT_EVENT_TIME_1,
497 &TrustchainVerifier::new(resolver),
498 &mut context_loader,
499 )
500 .await;
501 println!("{:?}", res);
502 assert!(res.is_ok());
503 }
504
505 #[ignore = "requires a running Sidetree node listening on http://localhost:3000"]
506 #[tokio::test]
507 async fn test_verify_presentation_unauthenticated() {
509 init();
510 let issuer_did = "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"; let issuer = IONAttestor::new(issuer_did);
512
513 let vc_with_proof = signed_credential(issuer).await;
514 let resolver = trustchain_resolver("http://localhost:3000/");
515 let presentation = Presentation {
516 verifiable_credential: Some(OneOrMany::Many(vec![CredentialOrJWT::Credential(
517 vc_with_proof,
518 )])),
519 ..Default::default()
520 };
521
522 println!("{}", serde_json::to_string_pretty(&presentation).unwrap());
523 assert!(matches!(
524 TrustchainAPI::verify_presentation(
525 &presentation,
526 None,
527 ROOT_EVENT_TIME_1,
528 &TrustchainVerifier::new(resolver),
529 &mut ContextLoader::default()
530 )
531 .await,
532 Err(PresentationError::VerifiedHolderUnauthenticated(..))
533 ));
534 }
535
536 async fn signed_credential(attestor: IONAttestor) -> Credential {
538 let resolver = trustchain_resolver("http://localhost:3000/");
539 let vc: Credential = serde_json::from_str(TEST_UNSIGNED_VC).unwrap();
540 attestor
541 .sign(&vc, None, None, &resolver, &mut ContextLoader::default())
542 .await
543 .unwrap()
544 }
545
546 async fn signed_data_credential(issuer_did: &str, bytes: &[u8]) -> Credential {
548 let attestor = IONAttestor::new(issuer_did);
549 let resolver = trustchain_resolver("http://localhost:3000/");
550 let mut vc: Credential = serde_json::from_str(DATA_CREDENTIAL_TEMPLATE).unwrap();
551 vc.issuer = Some(ssi::vc::Issuer::URI(URI::String(issuer_did.to_string())));
552 let data_element = vc
554 .credential_subject
555 .to_single_mut()
556 .expect("Template credential has a single credentialSubject.")
557 .property_set
558 .as_mut()
559 .expect("Template credential has a property set.")
560 .get_mut(crate::DATA_ATTRIBUTE)
561 .expect("Template credential has a dataset property.");
562 *data_element = hex::encode(Sha256::digest(bytes)).to_string().into();
563 attestor
564 .sign(&vc, None, None, &resolver, &mut ContextLoader::default())
565 .await
566 .unwrap()
567 }
568
569 #[test]
570 fn test_data_credential_template() {
571 let credential = Credential::from_json_unsigned(DATA_CREDENTIAL_TEMPLATE).unwrap();
573 assert_eq!(credential.issuer.unwrap().get_id(), "did:ion:test:XYZ");
574 }
575
576 #[ignore = "requires a running Sidetree node listening on http://localhost:3000"]
577 #[tokio::test]
578 async fn test_verify_data() {
579 init();
580 let issuer_did = "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"; let bytes = "test-data-content".as_bytes();
583 let expected_hash = hex::encode(Sha256::digest(bytes));
584
585 let vc_with_proof = signed_data_credential(issuer_did, bytes).await;
586
587 let resolver = trustchain_resolver("http://localhost:3000/");
588 let mut context_loader = ContextLoader::default();
589
590 let res = TrustchainAPI::verify_data(
591 bytes,
592 &vc_with_proof,
593 None,
594 ROOT_EVENT_TIME_1,
595 &TrustchainVerifier::new(resolver),
596 &mut context_loader,
597 )
598 .await;
599 assert!(res.is_ok());
600
601 let bytes = "different-data-content".as_bytes();
603
604 let resolver = trustchain_resolver("http://localhost:3000/");
606 let res = TrustchainAPI::verify_data(
607 bytes,
608 &vc_with_proof,
609 None,
610 ROOT_EVENT_TIME_1,
611 &TrustchainVerifier::new(resolver),
612 &mut context_loader,
613 )
614 .await;
615 assert!(res.is_err());
616
617 if let DataCredentialError::MismatchedHashDigests(expected, actual) = res.err().unwrap() {
618 assert_eq!(expected, expected_hash);
619 assert_ne!(actual, expected_hash);
620 } else {
621 panic!("Unexpected CredentialError variant.")
622 }
623 }
624}