1use core::fmt;
2use std::borrow::Cow;
3
4use base64::Engine;
5use json_patch::Patch;
6use serde::{Deserialize, Serialize};
7use ssi_dids_core::{
8 document::{service::Endpoint as ServiceEndpoint, Service},
9 registration::{DIDDocumentOperation, DIDDocumentOperationKind, DIDTransactionCreationError},
10};
11use ssi_jwk::{Base64urlUInt, JWK};
12use ssi_verification_methods::ProofPurpose;
13
14mod client;
15mod did;
16mod operation;
17mod resolver;
18
19pub use client::*;
20pub use did::*;
21pub use operation::*;
22pub use resolver::*;
23
24const MULTIHASH_SHA2_256_PREFIX: &[u8] = &[0x12];
25const MULTIHASH_SHA2_256_SIZE: &[u8] = &[0x20];
26
27pub const VERIFICATION_METHOD_TYPE: &str = "JsonWebSignature2020";
36
37#[derive(Debug, thiserror::Error)]
38#[error("key generation failed")]
39pub struct KeyGenerationFailed;
40
41#[derive(Debug, thiserror::Error)]
42pub enum CreateError {
43 #[error("same update and recovery keys")]
44 SameUpdateAndRecoveryKeys,
45
46 #[error(transparent)]
47 KeyGenerationFailed(#[from] KeyGenerationFailed),
48
49 #[error("invalid update key")]
50 InvalidUpdateKey,
51
52 #[error("invalid recovery key")]
53 InvalidRecoveryKey,
54}
55
56impl From<CreateError> for DIDTransactionCreationError {
57 fn from(value: CreateError) -> Self {
58 match value {
59 CreateError::SameUpdateAndRecoveryKeys => {
60 DIDTransactionCreationError::SameUpdateAndRecoveryKeys
61 }
62 CreateError::KeyGenerationFailed(_) => DIDTransactionCreationError::KeyGenerationFailed,
63 CreateError::InvalidUpdateKey => DIDTransactionCreationError::InvalidUpdateKey,
64 CreateError::InvalidRecoveryKey => DIDTransactionCreationError::InvalidRecoveryKey,
65 }
66 }
67}
68
69#[derive(Debug, thiserror::Error)]
70pub enum UpdateError {
71 #[error("invalid update key")]
72 InvalidUpdateKey,
73
74 #[error("update key unchanged")]
75 UpdateKeyUnchanged,
76
77 #[error("signature failed")]
78 SignatureFailed,
79}
80
81impl From<UpdateError> for DIDTransactionCreationError {
82 fn from(value: UpdateError) -> Self {
83 match value {
84 UpdateError::InvalidUpdateKey => Self::InvalidUpdateKey,
85 UpdateError::UpdateKeyUnchanged => Self::UpdateKeyUnchanged,
86 UpdateError::SignatureFailed => Self::SignatureFailed,
87 }
88 }
89}
90
91#[derive(Debug, thiserror::Error)]
92pub enum DeactivateError {
93 #[error("invalid recovery key")]
94 InvalidRecoveryKey,
95
96 #[error("signature failed")]
97 SignatureFailed,
98}
99
100impl From<DeactivateError> for DIDTransactionCreationError {
101 fn from(value: DeactivateError) -> Self {
102 match value {
103 DeactivateError::InvalidRecoveryKey => Self::InvalidRecoveryKey,
104 DeactivateError::SignatureFailed => Self::SignatureFailed,
105 }
106 }
107}
108
109#[derive(Debug, thiserror::Error)]
110pub enum RecoverError {
111 #[error("invalid recovery key")]
112 InvalidRecoveryKey,
113
114 #[error("recovery key unchanged")]
115 RecoveryKeyUnchanged,
116
117 #[error("signature failed")]
118 SignatureFailed,
119
120 #[error(transparent)]
121 KeyGenerationFailed(#[from] KeyGenerationFailed),
122}
123
124impl From<RecoverError> for DIDTransactionCreationError {
125 fn from(value: RecoverError) -> Self {
126 match value {
127 RecoverError::InvalidRecoveryKey => Self::InvalidRecoveryKey,
128 RecoverError::RecoveryKeyUnchanged => Self::RecoveryKeyUnchanged,
129 RecoverError::SignatureFailed => Self::SignatureFailed,
130 RecoverError::KeyGenerationFailed(_) => Self::KeyGenerationFailed,
131 }
132 }
133}
134
135pub trait Sidetree {
142 fn hash_protocol(data: &[u8]) -> Vec<u8> {
155 let (prefix, hash) = Self::hash_protocol_algorithm(data);
156 [prefix, hash].concat()
157 }
158
159 fn hash_algorithm(data: &[u8]) -> Vec<u8> {
170 let (_prefix, hash) = Self::hash_protocol_algorithm(data);
171 hash
172 }
173
174 fn hash_protocol_algorithm(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
189 use sha2::{Digest, Sha256};
190 let mut hasher = Sha256::new();
191 hasher.update(data);
192 let hash = hasher.finalize().to_vec();
193 (
194 [MULTIHASH_SHA2_256_PREFIX, MULTIHASH_SHA2_256_SIZE].concat(),
195 hash,
196 )
197 }
198
199 fn data_encoding_scheme(data: &[u8]) -> String {
201 base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(data)
202 }
203
204 fn generate_key() -> JWK;
208
209 fn validate_key(key: &JWK) -> bool;
215
216 const SIGNATURE_ALGORITHM: ssi_jwk::Algorithm;
218
219 fn reveal_value(commitment_value: &[u8]) -> String {
221 let hash = Self::hash_protocol(commitment_value);
225 Self::data_encoding_scheme(&hash)
226 }
227
228 const MAX_OPERATION_HASH_LENGTH: usize = 100;
230
231 const NONCE_SIZE: usize = 16;
233
234 const METHOD: &'static str;
238
239 const NETWORK: Option<&'static str> = None;
246
247 const MAX_CONTROLLER_LENGTH: Option<usize> = None;
251
252 const MAX_PKMB_LENGTH: Option<usize> = None;
256
257 fn hash(data: &[u8]) -> String {
261 let hash = Self::hash_protocol(data);
262 Self::data_encoding_scheme(&hash)
269 }
270
271 fn commitment_scheme(pkjwk: &PublicKeyJwk) -> String {
275 let canonicalized_public_key = json_canonicalization_scheme(&pkjwk).unwrap();
276 let reveal_value = Self::hash_algorithm(canonicalized_public_key.as_bytes());
279 Self::hash(&reveal_value)
280 }
281
282 fn create_existing(
299 update_pk: &PublicKeyJwk,
300 recovery_pk: &PublicKeyJwk,
301 patches: Vec<DIDStatePatch>,
302 ) -> Result<Operation, CreateError> {
303 if update_pk == recovery_pk {
304 return Err(CreateError::SameUpdateAndRecoveryKeys);
305 }
306
307 let update_commitment = Self::commitment_scheme(update_pk);
308
309 let create_operation_delta_object = Delta {
310 patches,
311 update_commitment,
312 };
313 let delta_string = json_canonicalization_scheme(&create_operation_delta_object).unwrap();
314 let delta_hash = Self::hash(delta_string.as_bytes());
315
316 let recovery_commitment = Self::commitment_scheme(recovery_pk);
317
318 let create_operation_suffix_data_object = SuffixData {
319 r#type: None,
320 delta_hash,
321 recovery_commitment,
322 anchor_origin: None,
323 };
324
325 let create_operation = CreateOperation {
326 suffix_data: create_operation_suffix_data_object,
327 delta: create_operation_delta_object,
328 };
329
330 Ok(Operation::Create(create_operation))
331 }
332
333 fn create(patches: Vec<DIDStatePatch>) -> Result<(Operation, JWK, JWK), CreateError> {
340 let update_keypair = Self::generate_key();
341 let recovery_keypair = Self::generate_key();
342 let update_pk = PublicKeyJwk::try_from(update_keypair.to_public())
343 .map_err(|_| CreateError::InvalidUpdateKey)?;
344 let recovery_pk = PublicKeyJwk::try_from(recovery_keypair.to_public())
345 .map_err(|_| CreateError::InvalidRecoveryKey)?;
346 let create_op = Self::create_existing(&update_pk, &recovery_pk, patches)?;
347 Ok((create_op, update_keypair, recovery_keypair))
348 }
349
350 fn update(
364 did_suffix: DIDSuffix,
365 update_key: &JWK,
366 new_update_pk: &PublicKeyJwk,
367 patches: Vec<DIDStatePatch>,
368 ) -> Result<UpdateOperation, UpdateError> {
369 let update_pk = PublicKeyJwk::try_from(update_key.to_public())
370 .map_err(|_| UpdateError::InvalidUpdateKey)?;
371 let canonicalized_update_pk = json_canonicalization_scheme(&update_pk).unwrap();
372 let update_reveal_value = Self::reveal_value(canonicalized_update_pk.as_bytes());
373
374 if new_update_pk == &update_pk {
375 return Err(UpdateError::UpdateKeyUnchanged);
376 }
377
378 let new_update_commitment = Self::commitment_scheme(new_update_pk);
379
380 let update_operation_delta_object = Delta {
381 patches,
382 update_commitment: new_update_commitment,
383 };
384
385 let delta_string = json_canonicalization_scheme(&update_operation_delta_object).unwrap();
386 let delta_hash = Self::hash(delta_string.as_bytes());
387
388 let algorithm = Self::SIGNATURE_ALGORITHM;
389 let claims = UpdateClaims {
390 update_key: update_pk,
391 delta_hash,
392 };
393 let signed_data = ssi_jwt::encode_sign(algorithm, &claims, update_key)
394 .map_err(|_| UpdateError::SignatureFailed)?;
395 let update_op = UpdateOperation {
396 did_suffix,
397 reveal_value: update_reveal_value,
398 delta: update_operation_delta_object,
399 signed_data,
400 };
401
402 Ok(update_op)
403 }
404
405 fn recover_existing(
413 did_suffix: DIDSuffix,
414 recovery_key: &JWK,
415 new_update_pk: &PublicKeyJwk,
416 new_recovery_pk: &PublicKeyJwk,
417 patches: Vec<DIDStatePatch>,
418 ) -> Result<Operation, RecoverError> {
419 let recovery_pk = PublicKeyJwk::try_from(recovery_key.to_public())
420 .map_err(|_| RecoverError::InvalidRecoveryKey)?;
421
422 if new_recovery_pk == &recovery_pk {
423 return Err(RecoverError::RecoveryKeyUnchanged);
424 }
425
426 let canonicalized_recovery_pk = json_canonicalization_scheme(&recovery_pk).unwrap();
427 let recover_reveal_value = Self::reveal_value(canonicalized_recovery_pk.as_bytes());
428 let new_update_commitment = Self::commitment_scheme(new_update_pk);
429 let new_recovery_commitment = Self::commitment_scheme(new_recovery_pk);
430
431 let recover_operation_delta_object = Delta {
432 patches,
433 update_commitment: new_update_commitment,
434 };
435
436 let delta_string = json_canonicalization_scheme(&recover_operation_delta_object).unwrap();
437 let delta_hash = Self::hash(delta_string.as_bytes());
438
439 let algorithm = Self::SIGNATURE_ALGORITHM;
440 let claims = RecoveryClaims {
441 recovery_commitment: new_recovery_commitment,
442 recovery_key: recovery_pk,
443 delta_hash,
444 anchor_origin: None,
445 };
446 let signed_data = ssi_jwt::encode_sign(algorithm, &claims, recovery_key)
447 .map_err(|_| RecoverError::SignatureFailed)?;
448 let recover_op = RecoverOperation {
449 did_suffix,
450 reveal_value: recover_reveal_value,
451 delta: recover_operation_delta_object,
452 signed_data,
453 };
454 Ok(Operation::Recover(recover_op))
455 }
456
457 fn recover(
464 did_suffix: DIDSuffix,
465 recovery_key: &JWK,
466 patches: Vec<DIDStatePatch>,
467 ) -> Result<(Operation, JWK, JWK), RecoverError> {
468 let new_update_keypair = Self::generate_key();
469 let new_update_pk = PublicKeyJwk::try_from(new_update_keypair.to_public()).unwrap();
470
471 let new_recovery_keypair = Self::generate_key();
472 let new_recovery_pk = PublicKeyJwk::try_from(new_recovery_keypair.to_public()).unwrap();
473
474 let recover_op = Self::recover_existing(
475 did_suffix,
476 recovery_key,
477 &new_update_pk,
478 &new_recovery_pk,
479 patches,
480 )?;
481
482 Ok((recover_op, new_update_keypair, new_recovery_keypair))
483 }
484
485 fn deactivate(
492 did_suffix: DIDSuffix,
493 recovery_key: JWK,
494 ) -> Result<DeactivateOperation, DeactivateError> {
495 let recovery_pk = PublicKeyJwk::try_from(recovery_key.to_public())
496 .map_err(|_| DeactivateError::InvalidRecoveryKey)?;
497 let canonicalized_recovery_pk = json_canonicalization_scheme(&recovery_pk).unwrap();
498 let recover_reveal_value = Self::reveal_value(canonicalized_recovery_pk.as_bytes());
499 let algorithm = Self::SIGNATURE_ALGORITHM;
500 let claims = DeactivateClaims {
501 did_suffix: did_suffix.clone(),
502 recovery_key: recovery_pk,
503 };
504 let signed_data = ssi_jwt::encode_sign(algorithm, &claims, &recovery_key)
505 .map_err(|_| DeactivateError::SignatureFailed)?;
506 let recover_op = DeactivateOperation {
507 did_suffix,
508 reveal_value: recover_reveal_value,
509 signed_data,
510 };
511 Ok(recover_op)
512 }
513
514 fn serialize_suffix_data(suffix_data: &SuffixData) -> DIDSuffix {
519 let string = json_canonicalization_scheme(suffix_data).unwrap();
520 let hash = Self::hash(string.as_bytes());
521 DIDSuffix(hash)
522 }
523
524 fn validate_did_suffix(suffix: &DIDSuffix) -> Result<(), InvalidSidetreeDIDSuffix> {
526 let bytes = base64::prelude::BASE64_URL_SAFE_NO_PAD
527 .decode(&suffix.0)
528 .map_err(|_| InvalidSidetreeDIDSuffix::Base64)?;
529
530 if bytes.len() != 34 {
531 return Err(InvalidSidetreeDIDSuffix::Length(bytes.len()));
532 }
533
534 if &bytes[0..1] != MULTIHASH_SHA2_256_PREFIX || &bytes[1..2] != MULTIHASH_SHA2_256_SIZE {
535 return Err(InvalidSidetreeDIDSuffix::Prefix);
536 }
537
538 Ok(())
539 }
540}
541
542fn json_canonicalization_scheme<T: Serialize + ?Sized>(
544 value: &T,
545) -> Result<String, serde_json::Error> {
546 serde_jcs::to_string(value)
547}
548
549#[derive(Debug, thiserror::Error)]
550pub enum InvalidSidetreeDIDSuffix {
551 #[error("invalid base64")]
552 Base64,
553
554 #[error("unexpected DID suffix length ({0})")]
555 Length(usize),
556
557 #[error("unexpected DID suffix prefix")]
558 Prefix,
559}
560
561#[derive(Debug, Serialize, Deserialize, Clone)]
570#[serde(rename_all = "camelCase")]
571pub enum PublicKey {
572 PublicKeyJwk(PublicKeyJwk),
576
577 PublicKeyMultibase(String),
581}
582
583#[derive(Debug, Serialize, Deserialize, Clone)]
592#[serde(rename_all = "camelCase")]
593pub struct PublicKeyEntry {
594 pub id: String,
598
599 pub r#type: String,
601
602 #[serde(skip_serializing_if = "Option::is_none")]
606 pub controller: Option<String>,
607
608 #[serde(flatten)]
610 pub public_key: PublicKey,
611
612 pub purposes: Vec<ProofPurpose>,
618}
619
620#[derive(Debug, thiserror::Error)]
621#[error("invalid public key entry")]
622pub struct InvalidPublicKeyEntry(pub JWK);
623
624impl TryFrom<JWK> for PublicKeyEntry {
625 type Error = InvalidPublicKeyEntry;
626
627 fn try_from(jwk: JWK) -> Result<Self, Self::Error> {
628 let Ok(id) = jwk.thumbprint() else {
629 return Err(InvalidPublicKeyEntry(jwk));
630 };
631
632 let Ok(pkjwk) = PublicKeyJwk::try_from(jwk.to_public()) else {
633 return Err(InvalidPublicKeyEntry(jwk));
634 };
635
636 let public_key = PublicKey::PublicKeyJwk(pkjwk);
637 Ok(PublicKeyEntry {
638 id,
639 r#type: VERIFICATION_METHOD_TYPE.to_owned(),
640 controller: None,
641 public_key,
642 purposes: vec![
643 ProofPurpose::Assertion,
644 ProofPurpose::Authentication,
645 ProofPurpose::KeyAgreement,
646 ProofPurpose::CapabilityInvocation,
647 ProofPurpose::CapabilityDelegation,
648 ],
649 })
650 }
651}
652
653#[derive(Debug, Serialize, Deserialize, Clone)]
662#[serde(rename_all = "camelCase")]
663pub struct ServiceEndpointEntry {
664 pub id: String,
668
669 pub r#type: String,
673
674 pub service_endpoint: ServiceEndpoint,
676}
677
678#[derive(Debug, Serialize, Deserialize, Clone, Default)]
682#[serde(rename_all = "camelCase")]
683pub struct DocumentState {
684 #[serde(skip_serializing_if = "Option::is_none")]
686 pub public_keys: Option<Vec<PublicKeyEntry>>,
687
688 #[serde(skip_serializing_if = "Option::is_none")]
690 pub services: Option<Vec<ServiceEndpointEntry>>,
691}
692
693#[derive(Debug, Serialize, Deserialize, Clone)]
698#[serde(tag = "action")]
699#[serde(rename_all = "kebab-case")]
700pub enum DIDStatePatch {
701 AddPublicKeys {
705 #[serde(rename = "publicKeys")]
707 public_keys: Vec<PublicKeyEntry>,
708 },
709
710 RemovePublicKeys {
714 ids: Vec<String>,
716 },
717
718 AddServices {
722 services: Vec<ServiceEndpointEntry>,
724 },
725
726 RemoveServices {
730 ids: Vec<String>,
732 },
733
734 Replace {
738 document: DocumentState,
740 },
741
742 IetfJsonPatch {
747 patches: Patch,
749 },
750}
751
752#[derive(Debug, Serialize, Deserialize, Clone)]
763#[serde(rename_all = "camelCase")]
764pub struct Delta {
765 pub patches: Vec<DIDStatePatch>,
767
768 pub update_commitment: String,
770}
771
772#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
781#[serde(rename_all = "camelCase")]
782pub struct PublicKeyJwk {
783 #[serde(skip_serializing_if = "Option::is_none")]
784 pub nonce: Option<Base64urlUInt>,
785 #[serde(flatten)]
786 jwk: serde_json::Value,
787}
788
789#[derive(thiserror::Error, Debug)]
791pub enum PublicKeyJwkFromJWKError {
792 #[error("Public Key JWK must not contain private key parameters")]
794 PrivateKeyParameters,
795}
796
797#[derive(thiserror::Error, Debug)]
799pub enum JWKFromPublicKeyJwkError {
800 #[error("Unable to convert Value to JWK")]
802 FromValue(#[from] serde_json::Error),
803}
804
805impl TryFrom<JWK> for PublicKeyJwk {
806 type Error = PublicKeyJwkFromJWKError;
807 fn try_from(jwk: JWK) -> Result<Self, Self::Error> {
808 let jwk_value = serde_json::to_value(jwk).unwrap();
809 if jwk_value.get("d").is_some() {
810 return Err(PublicKeyJwkFromJWKError::PrivateKeyParameters);
811 };
812 Ok(Self {
813 jwk: jwk_value,
814 nonce: None,
815 })
816 }
817}
818
819impl TryFrom<PublicKeyJwk> for JWK {
823 type Error = JWKFromPublicKeyJwkError;
824 fn try_from(pkjwk: PublicKeyJwk) -> Result<Self, Self::Error> {
825 let jwk = serde_json::from_value(pkjwk.jwk).map_err(JWKFromPublicKeyJwkError::FromValue)?;
826 Ok(jwk)
827 }
828}
829
830fn b64len(s: &str) -> usize {
831 base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(s).len()
832}
833
834impl DIDStatePatch {
835 fn try_from_with_did<S: Sidetree>(
840 did_doc_op: DIDDocumentOperation,
841 did: &SidetreeDID<S>,
842 ) -> Result<Self, DIDTransactionCreationError> {
843 match did_doc_op {
844 DIDDocumentOperation::SetDidDocument(_doc) => {
845 Err(DIDTransactionCreationError::UnimplementedDocumentOperation(
846 DIDDocumentOperationKind::SetDidDocument,
847 ))
848 }
849 DIDDocumentOperation::AddToDidDocument(_props) => {
850 Err(DIDTransactionCreationError::UnimplementedDocumentOperation(
851 DIDDocumentOperationKind::AddToDidDocument,
852 ))
853 }
854 DIDDocumentOperation::RemoveFromDidDocument(_props) => {
855 Err(DIDTransactionCreationError::UnimplementedDocumentOperation(
856 DIDDocumentOperationKind::RemoveFromDidDocument,
857 ))
858 }
859 DIDDocumentOperation::SetVerificationMethod { vmm, purposes } => {
860 let sub_id = did_url_to_id(&vmm.id, did)?;
861 let mut value = serde_json::to_value(vmm).unwrap();
862 value["id"] = serde_json::Value::String(sub_id);
863 value["purposes"] = serde_json::to_value(purposes).unwrap();
864 let entry: PublicKeyEntry = serde_json::from_value(value)
865 .map_err(|_| DIDTransactionCreationError::InvalidVerificationMethod)?;
866 Ok(DIDStatePatch::AddPublicKeys {
868 public_keys: vec![entry],
869 })
870 }
871 DIDDocumentOperation::SetService(service) => {
872 let Service {
873 id,
874 type_,
875 service_endpoint,
876 property_set,
877 } = service;
878
879 if !property_set.is_empty() {
880 return Err(DIDTransactionCreationError::UnsupportedServiceProperty);
881 }
882
883 let service_endpoint = match service_endpoint {
884 None => return Err(DIDTransactionCreationError::MissingServiceEndpoint),
885 Some(values) => match values.into_single() {
886 Some(value) => value,
887 None => return Err(DIDTransactionCreationError::AmbiguousServiceEndpoint),
888 },
889 };
890
891 let sub_id = did_url_to_id(&id, did)?;
892 let service_type = match type_.into_single() {
893 Some(type_) => type_,
894 None => return Err(DIDTransactionCreationError::AmbiguousServiceType),
895 };
896
897 if b64len(&service_type) > 30 {
898 return Err(DIDTransactionCreationError::UnsupportedService {
899 reason: Cow::Borrowed("Sidetree service type must contain no more than 30 Base64Url-encoded characters")
900 });
901 }
902
903 if b64len(&sub_id) > 50 {
904 return Err(DIDTransactionCreationError::UnsupportedService {
905 reason: Cow::Borrowed("Sidetree service id must contain no more than 50 Base64Url-encoded characters")
906 });
907 }
908
909 let entry = ServiceEndpointEntry {
910 id: sub_id,
911 r#type: service_type,
912 service_endpoint,
913 };
914
915 Ok(DIDStatePatch::AddServices {
916 services: vec![entry],
917 })
918 }
919 DIDDocumentOperation::RemoveVerificationMethod(did_url) => {
920 let id = did_url.to_string();
921 Ok(DIDStatePatch::RemovePublicKeys { ids: vec![id] })
922 }
923 DIDDocumentOperation::RemoveService(did_url) => {
924 let id = did_url.to_string();
925 Ok(DIDStatePatch::RemoveServices { ids: vec![id] })
926 }
927 }
928 }
929}
930
931fn did_url_to_id<S: Sidetree>(
935 did_url: &str,
936 did: &SidetreeDID<S>,
937) -> Result<String, DIDTransactionCreationError> {
938 let did_string = did.to_string();
939 let unprefixed = did_url
940 .strip_prefix(&did_string)
941 .ok_or(DIDTransactionCreationError::InvalidDIDURL)?;
942 let fragment = unprefixed
943 .strip_prefix('#')
944 .ok_or(DIDTransactionCreationError::InvalidDIDURL)?;
945 Ok(fragment.to_string())
946}
947
948#[derive(Debug, Serialize, Deserialize, Clone)]
949pub struct SidetreeAPIError {
950 pub code: String,
952 pub message: Option<String>,
953}
954
955impl fmt::Display for SidetreeAPIError {
956 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
957 write!(f, "Sidetree error {}", self.code)?;
958 if let Some(ref message) = self.message {
959 write!(f, ": {}", message)?;
960 }
961 Ok(())
962 }
963}
964
965#[cfg(test)]
966mod tests {
967 use std::str::FromStr;
968
969 use crate::ion::is_secp256k1;
970
971 use super::*;
972 use serde_json::json;
973 use ssi_jwk::Algorithm;
974
975 struct Example;
976
977 impl Sidetree for Example {
978 fn generate_key() -> JWK {
979 JWK::generate_secp256k1()
980 }
981 fn validate_key(key: &JWK) -> bool {
982 is_secp256k1(key)
983 }
984 const SIGNATURE_ALGORITHM: Algorithm = Algorithm::ES256K;
985 const METHOD: &'static str = "sidetree";
986 }
987
988 static LONGFORM_DID: &str = "did:sidetree:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJwdWJsaWNLZXlNb2RlbDFJZCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0WFNLQl9ydWJYUzdzQ2pYcXVwVkpFelRjVzNNc2ptRXZxMVlwWG45NlpnIiwieSI6ImRPaWNYcWJqRnhvR0otSzAtR0oxa0hZSnFpY19EX09NdVV3a1E3T2w2bmsifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iLCJrZXlBZ3JlZW1lbnQiXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoic2VydmljZTFJZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly93d3cuc2VydmljZTEuY29tIiwidHlwZSI6InNlcnZpY2UxVHlwZSJ9XX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpREtJa3dxTzY5SVBHM3BPbEhrZGI4Nm5ZdDBhTnhTSFp1MnItYmhFem5qZEEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNmRFdSbllsY0Q5RUdBM2RfNVoxQUh1LWlZcU1iSjluZmlxZHo1UzhWRGJnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCZk9aZE10VTZPQnc4UGs4NzlRdFotMkotOUZiYmpTWnlvYUFfYnFENHpoQSJ9fQ";
990 static SHORTFORM_DID: &str = "did:sidetree:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg";
991
992 lazy_static::lazy_static! {
993
994 static ref CREATE_OPERATION: Operation = serde_json::from_value(json!({
996 "type": "create",
997 "suffixData": {
998 "deltaHash": "EiCfDWRnYlcD9EGA3d_5Z1AHu-iYqMbJ9nfiqdz5S8VDbg",
999 "recoveryCommitment": "EiBfOZdMtU6OBw8Pk879QtZ-2J-9FbbjSZyoaA_bqD4zhA"
1000 },
1001 "delta": {
1002 "updateCommitment": "EiDKIkwqO69IPG3pOlHkdb86nYt0aNxSHZu2r-bhEznjdA",
1003 "patches": [
1004 {
1005 "action": "replace",
1006 "document": {
1007 "publicKeys": [
1008 {
1009 "id": "publicKeyModel1Id",
1010 "type": "EcdsaSecp256k1VerificationKey2019",
1011 "publicKeyJwk": {
1012 "kty": "EC",
1013 "crv": "secp256k1",
1014 "x": "tXSKB_rubXS7sCjXqupVJEzTcW3MsjmEvq1YpXn96Zg",
1015 "y": "dOicXqbjFxoGJ-K0-GJ1kHYJqic_D_OMuUwkQ7Ol6nk"
1016 },
1017 "purposes": [
1018 "authentication",
1019 "keyAgreement"
1020 ]
1021 }
1022 ],
1023 "services": [
1024 {
1025 "id": "service1Id",
1026 "type": "service1Type",
1027 "serviceEndpoint": "http://www.service1.com"
1028 }
1029 ]
1030 }
1031 }
1032 ]
1033 }
1034 })).unwrap();
1035
1036 static ref UPDATE_OPERATION: Operation = serde_json::from_value(json!({
1038 "type": "update",
1039 "didSuffix": "EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg",
1040 "revealValue": "EiBkRSeixqX-PhOij6PIpuGfPld5Nif5MxcrgtGCw-t6LA",
1041 "delta": {
1042 "patches": [
1043 {
1044 "action": "add-public-keys",
1045 "publicKeys": [
1046 {
1047 "id": "additional-key",
1048 "type": "EcdsaSecp256k1VerificationKey2019",
1049 "publicKeyJwk": {
1050 "kty": "EC",
1051 "crv": "secp256k1",
1052 "x": "aN75CTjy3VCgGAJDNJHbcb55hO8CobEKzgCNrUeOwAY",
1053 "y": "K9FhCEpa_jG09pB6qriXrgSvKzXm6xtxBvZzIoXXWm4"
1054 },
1055 "purposes": [
1056 "authentication",
1057 "assertionMethod",
1058 "capabilityInvocation",
1059 "capabilityDelegation",
1060 "keyAgreement"
1061 ]
1062 }
1063 ]
1064 }
1065 ],
1066 "updateCommitment": "EiDOrcmPtfMHuwIWN6YoihdeIPxOKDHy3D6sdMXu_7CN0w"
1067 },
1068 "signedData": "eyJhbGciOiJFUzI1NksifQ.eyJ1cGRhdGVLZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4Ijoid2Z3UUNKM09ScVZkbkhYa1Q4UC1MZ19HdHhCRWhYM3R5OU5VbnduSHJtdyIsInkiOiJ1aWU4cUxfVnVBblJEZHVwaFp1eExPNnFUOWtQcDNLUkdFSVJsVHBXcmZVIn0sImRlbHRhSGFzaCI6IkVpQ3BqTjQ3ZjBNcTZ4RE5VS240aFNlZ01FcW9EU19ycFEyOVd5MVY3M1ZEYncifQ.RwZK1DG5zcr4EsrRImzStb0VX5j2ZqApXZnuoAkA3IoRdErUscNG8RuxNZ0FjlJtjMJ0a-kn-_MdtR0wwvWVgg"
1069 })).unwrap();
1070
1071 static ref RECOVER_OPERATION: Operation = serde_json::from_value(json!({
1073 "type": "recover",
1074 "didSuffix": "EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg",
1075 "revealValue": "EiAJ-97Is59is6FKAProwDo870nmwCeP8n5nRRFwPpUZVQ",
1076 "signedData": "eyJhbGciOiJFUzI1NksifQ.eyJkZWx0YUhhc2giOiJFaUNTem1ZSk0yWGpaWE00a1Q0bGpKcEVGTjVmVkM1QVNWZ3hSekVtMEF2OWp3IiwicmVjb3ZlcnlLZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4IjoibklxbFJDeDBleUJTWGNRbnFEcFJlU3Y0enVXaHdDUldzc29jOUxfbmo2QSIsInkiOiJpRzI5Vks2bDJVNXNLQlpVU0plUHZ5RnVzWGdTbEsyZERGbFdhQ004RjdrIn0sInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQ3NBN1NHTE5lZGE1SW5sb3Fub2tVY0pGejZ2S1Q0SFM1ZGNLcm1ubEpocEEifQ.lxWnrg5jaeCAhYuz1fPhidKw6Z2cScNlEc6SWcs15DtJbrHZFxl5IezGJ3cWdOSS2DlzDl4M1ZF8dDE9kRwFeQ",
1077 "delta": {
1078 "patches": [
1079 {
1080 "action": "replace",
1081 "document": {
1082 "publicKeys": [
1083 {
1084 "id": "newKey",
1085 "type": "EcdsaSecp256k1VerificationKey2019",
1086 "publicKeyJwk": {
1087 "kty": "EC",
1088 "crv": "secp256k1",
1089 "x": "JUWp0pAMGevNLhqq_Qmd48izuLYfO5XWpjSmy5btkjc",
1090 "y": "QYaSu1NHYnxR4qfk-RkXb4NQnQf1X3XQCpDYuibvlNc"
1091 },
1092 "purposes": [
1093 "authentication",
1094 "assertionMethod",
1095 "capabilityInvocation",
1096 "capabilityDelegation",
1097 "keyAgreement"
1098 ]
1099 }
1100 ],
1101 "services": [
1102 {
1103 "id": "serviceId123",
1104 "type": "someType",
1105 "serviceEndpoint": "https://www.url.com"
1106 }
1107 ]
1108 }
1109 }
1110 ],
1111 "updateCommitment": "EiD6_csybTfxELBoMgkE9O2BTCmhScG_RW_qaZQkIkJ_aQ"
1112 }
1113 })).unwrap();
1114
1115 static ref DEACTIVATE_OPERATION: Operation = serde_json::from_value(json!({
1117 "type": "deactivate",
1118 "didSuffix": "EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg",
1119 "revealValue": "EiB-dib5oumdaDGH47TB17Qg1nHza036bTIGibQOKFUY2A",
1120 "signedData": "eyJhbGciOiJFUzI1NksifQ.eyJkaWRTdWZmaXgiOiJFaUR5T1FiYlpBYTNhaVJ6ZUNrVjdMT3gzU0VSampIOTNFWG9JTTNVb040b1dnIiwicmVjb3ZlcnlLZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4IjoiSk1ucF9KOW5BSGFkTGpJNmJfNVU3M1VwSEZqSEZTVHdtc1ZUUG9FTTVsMCIsInkiOiJ3c1QxLXN0UWJvSldPeEJyUnVINHQwVV9zX1lSQy14WXQyRkFEVUNHR2M4In19.ARTZrvupKdShOFNAJ4EWnsuaONKBgXUiwY5Ct10a9IXIp1uFsg0UyDnZGZtJT2v2bgtmYsQBmT6L9kKaaDcvUQ"
1121 })).unwrap();
1122 }
1123
1124 #[test]
1125 fn test_did_parse_format() {
1126 let longform_did = SidetreeDID::<Example>::from_str(LONGFORM_DID).unwrap();
1127 let shortform_did = SidetreeDID::<Example>::from_str(SHORTFORM_DID).unwrap();
1128 assert_eq!(longform_did.to_string(), LONGFORM_DID);
1129 assert_eq!(shortform_did.to_string(), SHORTFORM_DID);
1130 assert!(LONGFORM_DID.starts_with(SHORTFORM_DID));
1131 }
1132
1133 #[test]
1134 fn test_longform_did_construction() {
1135 let create_operation = match &*CREATE_OPERATION {
1136 Operation::Create(op) => op,
1137 _ => panic!("Expected Create Operation"),
1138 };
1139 let did: SidetreeDID<Example> = create_operation.to_sidetree_did();
1140 assert_eq!(did.to_string(), LONGFORM_DID);
1141 }
1142
1143 #[test]
1144 fn test_update_verify_reveal() {
1145 let create_pvo = CREATE_OPERATION
1146 .clone()
1147 .partial_verify::<Example>()
1148 .unwrap();
1149 let update_pvo = UPDATE_OPERATION
1150 .clone()
1151 .partial_verify::<Example>()
1152 .unwrap();
1153 update_pvo.follows::<Example>(&create_pvo).unwrap();
1154 }
1155
1156 #[test]
1157 fn test_recover_verify_reveal() {
1158 let create_pvo = CREATE_OPERATION
1159 .clone()
1160 .partial_verify::<Example>()
1161 .unwrap();
1162 let recover_pvo = RECOVER_OPERATION
1163 .clone()
1164 .partial_verify::<Example>()
1165 .unwrap();
1166 recover_pvo.follows::<Example>(&create_pvo).unwrap();
1167 }
1168
1169 #[test]
1170 fn test_deactivate_verify_reveal() {
1171 let recover_pvo = RECOVER_OPERATION
1172 .clone()
1173 .partial_verify::<Example>()
1174 .unwrap();
1175 let deactivate_pvo = DEACTIVATE_OPERATION
1176 .clone()
1177 .partial_verify::<Example>()
1178 .unwrap();
1179 deactivate_pvo.follows::<Example>(&recover_pvo).unwrap();
1180 }
1181}