1use iref::{Iri, Uri, UriBuf};
2use json_patch::patch;
3use serde::Deserialize;
4use ssi_dids_core::{
5 document::{
6 self,
7 representation::{self, MediaType},
8 verification_method::ValueOrReference,
9 DIDVerificationMethod, Resource, Service,
10 },
11 resolution::Error,
12 resolution::{self, Content, DIDMethodResolver, DerefError, Output, Parameter},
13 DIDBuf, DIDMethod, DIDResolver, DIDURLBuf, Document, DID, DIDURL,
14};
15use ssi_jwk::{p256_parse, secp256k1_parse, Base64urlUInt, OctetParams, Params, JWK};
16use ssi_jws::{decode_unverified, decode_verify};
17use static_iref::iri;
18use std::{collections::BTreeMap, future::Future};
19
20mod explorer;
21mod prefix;
22
23pub use prefix::*;
24
25#[derive(Debug, thiserror::Error)]
26pub enum UpdateError {
27 #[error("missing key id in patch")]
28 MissingPatchKeyId,
29
30 #[error("key id `{0}` in patch is not a DID URL")]
31 InvalidPatchKeyId(String),
32
33 #[error("invalid public key `{0}`")]
34 InvalidPublicKey(String, ssi_jwk::Error),
35
36 #[error("invalid public key `{0}`: not base58")]
37 InvalidPublicKeyEncoding(String),
38
39 #[error("{0} support not enabled")]
40 PrefixNotEnabled(Prefix),
41
42 #[error("dereference failed: {0}")]
43 DereferenceFailed(DerefError),
44
45 #[error("expected a DID document")]
46 NotADocument,
47
48 #[error("missing public key for patch")]
49 MissingPublicKey,
50
51 #[error("invalid JWS: {0}")]
52 InvalidJws(ssi_jws::Error),
53
54 #[error("invalid patch: {0}")]
55 InvalidPatch(serde_json::Error),
56
57 #[error(transparent)]
58 Patch(json_patch::PatchError),
59
60 #[error("invalid patched document: {0}")]
61 InvalidPatchedDocument(serde_json::Error),
62}
63
64#[derive(Default, Clone)]
79pub struct DIDTz {
80 tzkt_url: Option<UriBuf>,
81}
82
83impl DIDTz {
84 pub const fn new(tzkt_url: Option<UriBuf>) -> Self {
85 Self { tzkt_url }
86 }
87}
88
89impl DIDMethod for DIDTz {
90 const DID_METHOD_NAME: &'static str = "tz";
91}
92
93impl DIDMethodResolver for DIDTz {
94 async fn resolve_method_representation<'a>(
95 &'a self,
96 id: &'a str,
97 options: ssi_dids_core::resolution::Options,
98 ) -> Result<Output<Vec<u8>>, Error> {
99 let did = DIDBuf::new(format!("did:tz:{id}").into_bytes()).unwrap();
100 let (network, address) = id.split_once(':').unwrap_or(("mainnet", id));
101
102 if address.len() != 36 {
103 return Err(Error::InvalidMethodSpecificId(id.to_owned()));
104 }
105
106 let genesis_block_hash = match network {
108 "mainnet" => "NetXdQprcVkpaWU",
109 "delphinet" => "NetXm8tYqnMWky1",
110 "granadanet" => "NetXz969SFaFn8k",
111 "edonet" => "NetXSgo1ZT2DRUG",
112 "florencenet" => "NetXxkAx4woPLyu",
113 _ => return Err(Error::InvalidMethodSpecificId(id.to_owned())),
114 };
115
116 let prefix = Prefix::from_address(address)
117 .map_err(|_| Error::InvalidMethodSpecificId(id.to_owned()))?;
118
119 let vm = TezosVerificationMethod {
120 id: DIDURLBuf::new(format!("{did}#blockchainAccountId").into_bytes()).unwrap(),
121 type_: VerificationMethodType::from_prefix(prefix),
122 controller: did.clone(),
123 blockchain_account_id: Some(format!("tezos:{}:{}", genesis_block_hash, address)),
124 public_key: None,
125 };
126
127 let authentication_vm = options
128 .parameters
129 .additional
130 .get("public_key")
131 .map(|value| {
132 value
133 .as_string()
134 .ok_or_else(|| Error::InvalidMethodSpecificId(id.to_owned()))
135 })
136 .transpose()?
137 .map(|public_key| TezosVerificationMethod {
138 id: vm.id.clone(),
139 type_: vm.type_,
140 controller: vm.controller.clone(),
141 blockchain_account_id: None,
142 public_key: Some(public_key.to_owned()),
143 });
144
145 let mut json_ld_context = JsonLdContext::default();
146 json_ld_context.add_verification_method(&vm);
147 if let Some(vm) = &authentication_vm {
148 json_ld_context.add_verification_method(vm);
149 }
150
151 let mut doc = DIDTz::tier1_derivation(&did, vm, authentication_vm);
152
153 let tzkt_url = match options.parameters.additional.get("tzkt_url") {
154 Some(value) => match value {
155 Parameter::String(v) => match UriBuf::new(v.as_bytes().to_vec()) {
156 Ok(url) => url,
157 Err(_) => return Err(Error::InvalidOptions),
158 },
159 _ => return Err(Error::InvalidOptions),
160 },
161 None => match &self.tzkt_url {
162 Some(u) => u.clone(),
163 None => UriBuf::new(format!("https://api.{network}.tzkt.io").into_bytes()).unwrap(),
164 },
165 };
166
167 if let (Some(service), Some(vm_url)) =
168 DIDTz::tier2_resolution(prefix, &tzkt_url, &did, address).await?
169 {
170 doc.service.push(service);
171 doc.verification_relationships
172 .authentication
173 .push(ValueOrReference::Reference(vm_url.into()));
174 }
175
176 if let Some(updates_metadata) = options.parameters.additional.get("updates") {
177 let conversion: String = match updates_metadata {
178 Parameter::String(s) => s.clone(),
179 _ => return Err(Error::InvalidOptions),
193 };
194
195 let updates: Updates = match serde_json::from_str(&conversion) {
196 Ok(uu) => uu,
197 Err(_) => return Err(Error::InvalidOptions),
198 };
199
200 self.tier3_updates(prefix, &mut doc, updates)
201 .await
202 .map_err(Error::internal)?;
203 }
204
205 let content_type = options.accept.unwrap_or(MediaType::JsonLd);
206 let represented = doc.into_representation(representation::Options::from_media_type(
207 content_type,
208 move || representation::json_ld::Options {
209 context: representation::json_ld::Context::array(
210 representation::json_ld::DIDContext::V1,
211 vec![json_ld_context.into()],
212 ),
213 },
214 ));
215
216 Ok(Output::new(
217 represented.to_bytes(),
218 document::Metadata::default(),
219 resolution::Metadata::from_content_type(Some(content_type.to_string())),
220 ))
221 }
222}
223
224fn get_public_key_from_doc<'a>(doc: &'a Document, auth_vm_id: &DIDURL) -> Option<&'a str> {
225 let mut is_authentication_method = false;
226 for vm in &doc.verification_relationships.authentication {
227 #[allow(clippy::single_match)]
228 match vm {
229 ValueOrReference::Value(vm) => {
230 if vm.id == auth_vm_id {
231 return vm
232 .properties
233 .get("publicKeyBase58")
234 .and_then(serde_json::Value::as_str);
235 }
236 }
237 ValueOrReference::Reference(_) => is_authentication_method = true,
238 }
239 }
240
241 if is_authentication_method {
242 for vm in &doc.verification_method {
243 if vm.id == auth_vm_id {
244 return vm
245 .properties
246 .get("publicKeyBase58")
247 .and_then(serde_json::Value::as_str);
248 }
249 }
250 }
251
252 None
253}
254
255pub struct TezosVerificationMethod {
256 id: DIDURLBuf,
257 type_: VerificationMethodType,
258 controller: DIDBuf,
259 blockchain_account_id: Option<String>,
260 public_key: Option<String>,
261}
262
263impl From<TezosVerificationMethod> for DIDVerificationMethod {
264 fn from(value: TezosVerificationMethod) -> Self {
265 let mut properties = BTreeMap::new();
266
267 if let Some(v) = value.blockchain_account_id {
268 properties.insert(
269 "blockchainAccountId".to_string(),
270 serde_json::Value::String(v),
271 );
272 }
273
274 if let Some(v) = value.public_key {
275 properties.insert("publicKeyBase58".to_string(), serde_json::Value::String(v));
276 }
277
278 DIDVerificationMethod::new(
279 value.id,
280 value.type_.name().to_string(),
281 value.controller,
282 properties,
283 )
284 }
285}
286
287#[derive(Debug, Clone, Copy)]
288pub enum VerificationMethodType {
289 Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021,
290 EcdsaSecp256k1RecoveryMethod2020,
291 P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021,
292}
293
294impl VerificationMethodType {
295 pub fn from_prefix(prefix: Prefix) -> Self {
296 match prefix {
297 Prefix::TZ1 | Prefix::KT1 => {
298 Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021
299 }
300 Prefix::TZ2 => VerificationMethodType::EcdsaSecp256k1RecoveryMethod2020,
301 Prefix::TZ3 => {
302 VerificationMethodType::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021
303 }
304 }
305 }
306
307 pub fn curve(&self) -> &'static str {
308 match self {
309 Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => "Ed25519",
310 Self::EcdsaSecp256k1RecoveryMethod2020 => "secp256k1",
311 Self::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => "P-256",
312 }
313 }
314
315 pub fn name(&self) -> &'static str {
316 match self {
317 Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => {
318 "Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"
319 }
320 Self::EcdsaSecp256k1RecoveryMethod2020 => "EcdsaSecp256k1RecoveryMethod2020",
321 Self::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => {
322 "P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"
323 }
324 }
325 }
326
327 pub fn as_iri(&self) -> &'static Iri {
328 match self {
329 Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => iri!("https://w3id.org/security#Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"),
330 Self::EcdsaSecp256k1RecoveryMethod2020 => iri!("https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020"),
331 Self::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => iri!("https://w3id.org/security#P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021")
332 }
333 }
334}
335
336fn decode_public_key(public_key: &str) -> Result<Vec<u8>, UpdateError> {
337 Ok(bs58::decode(public_key)
338 .with_check(None)
339 .into_vec()
340 .map_err(|_| {
341 UpdateError::InvalidPublicKeyEncoding(public_key.to_owned())
343 })?[4..]
344 .to_vec())
345}
346
347#[derive(Deserialize)]
348#[serde(rename_all = "kebab-case")]
349struct SignedIetfJsonPatchPayload {
350 ietf_json_patch: serde_json::Value,
351}
352
353#[derive(Deserialize)]
354#[serde(rename_all = "kebab-case")]
355#[serde(tag = "type", content = "value")]
356enum Updates {
357 SignedIetfJsonPatch(Vec<String>),
358}
359
360#[derive(Debug, Default)]
361struct JsonLdContext {
362 ecdsa_secp256k1_recovery_method_2020: bool,
363 ed_25519_public_key_blake2b_digest_size_20_base58_check_encoded2021: bool,
364 p256_public_key_blake2b_digest_size_20_base58_check_encoded2021: bool,
365 blockchain_account_id: bool,
366 public_key_base58: bool,
367}
368
369impl JsonLdContext {
370 pub fn add_verification_method(&mut self, m: &TezosVerificationMethod) {
371 self.blockchain_account_id |= m.blockchain_account_id.is_some();
372 self.public_key_base58 |= m.public_key.is_some();
373 self.add_verification_method_type(m.type_);
374 }
375
376 pub fn add_verification_method_type(&mut self, ty: VerificationMethodType) {
377 match ty {
378 VerificationMethodType::EcdsaSecp256k1RecoveryMethod2020 => {
379 self.ecdsa_secp256k1_recovery_method_2020 = true
380 }
381 VerificationMethodType::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => {
382 self.ed_25519_public_key_blake2b_digest_size_20_base58_check_encoded2021 = true
383 }
384 VerificationMethodType::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => {
385 self.p256_public_key_blake2b_digest_size_20_base58_check_encoded2021 = true
386 }
387 }
388 }
389}
390
391impl From<JsonLdContext> for representation::json_ld::ContextEntry {
392 fn from(value: JsonLdContext) -> Self {
393 use representation::json_ld::context::{Definition, TermDefinition};
394 let mut def = Definition::new();
395
396 if value.ecdsa_secp256k1_recovery_method_2020 {
397 let ty = VerificationMethodType::EcdsaSecp256k1RecoveryMethod2020;
398 def.bindings.insert(
399 ty.name().into(),
400 TermDefinition::Simple(ty.as_iri().to_owned().into()).into(),
401 );
402 }
403
404 if value.ed_25519_public_key_blake2b_digest_size_20_base58_check_encoded2021 {
405 let ty =
406 VerificationMethodType::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021;
407 def.bindings.insert(
408 ty.name().into(),
409 TermDefinition::Simple(ty.as_iri().to_owned().into()).into(),
410 );
411 }
412
413 if value.p256_public_key_blake2b_digest_size_20_base58_check_encoded2021 {
414 let ty = VerificationMethodType::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021;
415 def.bindings.insert(
416 ty.name().into(),
417 TermDefinition::Simple(ty.as_iri().to_owned().into()).into(),
418 );
419 }
420
421 if value.blockchain_account_id {
422 def.bindings.insert(
423 "blockchainAccountId".into(),
424 TermDefinition::Simple(
425 iri!("https://w3id.org/security#blockchainAccountId")
426 .to_owned()
427 .into(),
428 )
429 .into(),
430 );
431 }
432
433 Self::Definition(def)
434 }
435}
436
437impl DIDTz {
438 pub fn generate(&self, jwk: &JWK) -> Result<DIDBuf, ssi_jwk::Error> {
440 let hash = ssi_jwk::blakesig::hash_public_key(jwk)?;
441 Ok(DIDBuf::from_string(format!("did:tz:{hash}")).unwrap())
442 }
443
444 fn tier1_derivation(
445 did: &DID,
446 verification_method: TezosVerificationMethod,
447 authentication_verification_method: Option<TezosVerificationMethod>,
448 ) -> Document {
449 let mut document = Document::new(did.to_owned());
469
470 let authentication_verification_method = match authentication_verification_method {
471 Some(vm) => ValueOrReference::Value(vm.into()),
472 None => ValueOrReference::Reference(verification_method.id.clone().into()),
473 };
474
475 document
476 .verification_relationships
477 .assertion_method
478 .push(ValueOrReference::Reference(
479 verification_method.id.clone().into(),
480 ));
481 document
482 .verification_relationships
483 .authentication
484 .push(authentication_verification_method);
485 document
486 .verification_method
487 .push(verification_method.into());
488
489 document
490 }
491
492 async fn tier2_resolution(
493 prefix: Prefix,
494 tzkt_url: &Uri,
495 did: &DID,
496 address: &str,
497 ) -> Result<(Option<Service>, Option<DIDURLBuf>), Error> {
498 if let Some(did_manager) = match prefix {
499 Prefix::KT1 => Some(address.to_string()),
500 _ => explorer::retrieve_did_manager(tzkt_url, address).await?,
501 } {
502 Ok((
503 Some(explorer::execute_service_view(tzkt_url, did, &did_manager).await?),
504 Some(explorer::execute_auth_view(tzkt_url, &did_manager).await?),
505 ))
506 } else {
507 Ok((None, None))
508 }
509 }
510
511 fn tier3_updates<'a>(
512 &'a self,
513 prefix: Prefix,
514 doc: &'a mut Document,
515 updates: Updates,
516 ) -> impl 'a + Future<Output = Result<(), UpdateError>> {
517 Box::pin(async move {
518 match updates {
519 Updates::SignedIetfJsonPatch(patches) => {
520 for jws in patches {
521 let mut doc_json = serde_json::to_value(&*doc).unwrap();
522 let (patch_metadata, _) =
523 decode_unverified(&jws).map_err(UpdateError::InvalidJws)?;
524 let curve = VerificationMethodType::from_prefix(prefix)
525 .curve()
526 .to_string();
527
528 let kid = match patch_metadata.key_id {
529 Some(k) => DIDURLBuf::from_string(k)
530 .map_err(|e| UpdateError::InvalidPatchKeyId(e.0)),
531 None => {
532 Err(UpdateError::MissingPatchKeyId)
534 }
535 }?;
536
537 let kid_doc = if kid.did() == &doc.id {
540 doc.clone()
541 } else {
542 let deref = self
543 .dereference(&kid)
544 .await
545 .map_err(UpdateError::DereferenceFailed)?;
546 match deref.content {
547 Content::Resource(Resource::Document(d)) => d,
548 _ => {
549 return Err(UpdateError::NotADocument);
551 }
552 }
553 };
554
555 if let Some(public_key) = get_public_key_from_doc(&kid_doc, &kid) {
556 let jwk = match prefix {
557 Prefix::TZ1 | Prefix::KT1 => {
558 let pk = decode_public_key(public_key)?;
559
560 JWK {
561 params: Params::OKP(OctetParams {
562 curve,
563 public_key: Base64urlUInt(pk),
564 private_key: None,
565 }),
566 public_key_use: None,
567 key_operations: None,
568 algorithm: None,
569 key_id: None,
570 x509_url: None,
571 x509_thumbprint_sha1: None,
572 x509_certificate_chain: None,
573 x509_thumbprint_sha256: None,
574 }
575 }
576 Prefix::TZ2 => {
577 let pk = decode_public_key(public_key)?;
578 secp256k1_parse(&pk).map_err(|e| {
579 UpdateError::InvalidPublicKey(public_key.to_owned(), e)
581 })?
582 }
583 Prefix::TZ3 => {
584 let pk = decode_public_key(public_key)?;
585 p256_parse(&pk).map_err(|e| {
586 UpdateError::InvalidPublicKey(public_key.to_owned(), e)
588 })?
589 }
590 #[allow(unreachable_patterns)]
591 p => {
592 return Err(UpdateError::PrefixNotEnabled(p));
594 }
595 };
596 let (_, patch_) =
597 decode_verify(&jws, &jwk).map_err(UpdateError::InvalidJws)?;
598 patch(
599 &mut doc_json,
600 &serde_json::from_slice(
601 serde_json::from_slice::<SignedIetfJsonPatchPayload>(&patch_)
602 .map_err(UpdateError::InvalidPatch)?
603 .ietf_json_patch
604 .to_string()
605 .as_bytes(),
606 )
607 .map_err(UpdateError::InvalidPatch)?,
608 )
609 .map_err(UpdateError::Patch)?;
610
611 *doc = serde_json::from_value(doc_json)
612 .map_err(UpdateError::InvalidPatchedDocument)?;
613 } else {
614 return Err(UpdateError::MissingPublicKey);
616 }
617 }
618 }
619 }
620
621 Ok(())
622 })
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use serde_json::json;
630 use ssi_core::one_or_many::OneOrMany;
631 use ssi_dids_core::document::service;
632 use ssi_jws::encode_sign;
633 use static_iref::uri;
634
635 const DIDTZ: DIDTz = DIDTz { tzkt_url: None };
636
637 const JSON_PATCH: &str = r#"{"ietf-json-patch": [
638 {
639 "op": "add",
640 "path": "/service/1",
641 "value": {
642 "id": "http://example.org/test_service_id",
643 "type": "test_service",
644 "serviceEndpoint": "http://example.org/test_service_endpoint"
645 }
646 }
647 ]}"#;
648
649 #[tokio::test]
650 async fn test_json_patch_tz1() {
651 let address = "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb";
652 let pk = "edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn";
653 let sk = "edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq";
654 let did = format!("did:tz:{}:{}", "sandbox", address);
655 let mut doc: Document = serde_json::from_value(json!({
656 "@context": "https://www.w3.org/ns/did/v1",
657 "id": did,
658 "authentication": [{
659 "id": format!("{did}#blockchainAccountId"),
660 "type": "Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021",
661 "controller": did,
662 "blockchainAccountId": format!("tezos:sandbox:{address}"),
663 "publicKeyBase58": pk
664 }],
665 "service": [{
666 "id": format!("{did}#discovery"),
667 "type": "TezosDiscoveryService",
668 "serviceEndpoint": "test_service"
669 }]
670 }))
671 .unwrap();
672 let key = JWK {
673 key_id: Some(format!("{}#blockchainAccountId", did)),
674 ..ssi_tzkey::jwk_from_tezos_key(sk).unwrap()
675 };
676 let jws = encode_sign(ssi_jwk::Algorithm::EdDSA, JSON_PATCH, &key).unwrap();
677 let json_update = Updates::SignedIetfJsonPatch(vec![jws.clone()]);
678 DIDTZ
679 .tier3_updates(Prefix::TZ1, &mut doc, json_update)
680 .await
681 .unwrap();
682 assert_eq!(
683 doc.service[1],
684 Service {
685 id: uri!("http://example.org/test_service_id").to_owned(),
686 type_: OneOrMany::One("test_service".to_string()),
687 service_endpoint: Some(OneOrMany::One(service::Endpoint::Uri(
688 uri!("http://example.org/test_service_endpoint").to_owned()
689 ))),
690 property_set: BTreeMap::new()
691 }
692 );
693 }
694
695 #[tokio::test]
696 async fn test_json_patch_tz2() {
697 let address = "tz2RZoj9oqoA8bDeUoAKLjf8nLPQKmYjaj6Q";
698 let pk = "sppk7bRNbJ2n9PNQo295UJiYQ8iMma8ysRH9mCRFB14yhzLCwdGay9y";
699 let sk = "spsk1Uc5MDutpZmwPVeSLL2BbtCAqfrG8zbMs6dwoaeXX8kw35S474";
700 let did = format!("did:tz:{}:{}", "sandbox", address);
701 let mut doc: Document = serde_json::from_value(json!({
702 "@context": "https://www.w3.org/ns/did/v1",
703 "id": did,
704 "authentication": [{
705 "id": format!("{}#blockchainAccountId", did),
706 "type": "EcdsaSecp256k1RecoveryMethod2020",
707 "controller": did,
708 "blockchainAccountId": format!("tezos:sandbox:{}", address),
709 "publicKeyBase58": pk
710 }],
711 "service": [{
712 "id": format!("{}#discovery", did),
713 "type": "TezosDiscoveryService",
714 "serviceEndpoint": "test_service"
715 }]
716 }))
717 .unwrap();
718 let private_key = bs58::decode(&sk).with_check(None).into_vec().unwrap()[4..].to_owned();
720 use ssi_jwk::ECParams;
721 let key = JWK {
722 params: ssi_jwk::Params::EC(ECParams {
723 curve: Some("secp256k1".to_string()),
724 x_coordinate: None,
725 y_coordinate: None,
726 ecc_private_key: Some(Base64urlUInt(private_key)),
727 }),
728 public_key_use: None,
729 key_operations: None,
730 algorithm: None,
731 key_id: Some(format!("{}#blockchainAccountId", did)),
732 x509_url: None,
733 x509_certificate_chain: None,
734 x509_thumbprint_sha1: None,
735 x509_thumbprint_sha256: None,
736 };
737 let jws = encode_sign(ssi_jwk::Algorithm::ES256KR, JSON_PATCH, &key).unwrap();
738 let json_update = Updates::SignedIetfJsonPatch(vec![jws.clone()]);
739 DIDTZ
740 .tier3_updates(Prefix::TZ2, &mut doc, json_update)
741 .await
742 .unwrap();
743 assert_eq!(
744 doc.service[1],
745 Service {
746 id: uri!("http://example.org/test_service_id").to_owned(),
747 type_: OneOrMany::One("test_service".to_string()),
748 service_endpoint: Some(OneOrMany::One(service::Endpoint::Uri(
749 uri!("http://example.org/test_service_endpoint").to_owned()
750 ))),
751 property_set: BTreeMap::new()
752 }
753 );
754 }
755
756 #[tokio::test]
757 async fn test_json_patch_tz3() {
758 let address = "tz3agP9LGe2cXmKQyYn6T68BHKjjktDbbSWX";
759 let pk = "p2pk679D18uQNkdjpRxuBXL5CqcDKTKzsiXVtc9oCUT6xb82zQmgUks";
760 let sk = "p2sk3PM77YMR99AvD3fSSxeLChMdiQ6kkEzqoPuSwQqhPsh29irGLC";
761 let did = format!("did:tz:{}:{}", "sandbox", address);
762 let mut doc: Document = serde_json::from_value(json!({
763 "@context": "https://www.w3.org/ns/did/v1",
764 "id": did,
765 "authentication": [{
766 "id": format!("{}#blockchainAccountId", did),
767 "type": "JsonWebKey2020",
768 "controller": did,
769 "blockchainAccountId": format!("tezos:sandbox:{}", address),
770 "publicKeyBase58": pk
771 }],
772 "service": [{
773 "id": format!("{}#discovery", did),
774 "type": "TezosDiscoveryService",
775 "serviceEndpoint": "test_service"
776 }]
777 }))
778 .unwrap();
779 let private_key = bs58::decode(&sk).with_check(None).into_vec().unwrap()[4..].to_owned();
781 let key = JWK {
782 params: ssi_jwk::Params::EC(ssi_jwk::ECParams {
783 curve: Some("P-256".to_string()),
784 x_coordinate: None,
785 y_coordinate: None,
786 ecc_private_key: Some(Base64urlUInt(private_key)),
787 }),
788 public_key_use: None,
789 key_operations: None,
790 algorithm: None,
791 key_id: Some(format!("{}#blockchainAccountId", did)),
792 x509_url: None,
793 x509_certificate_chain: None,
794 x509_thumbprint_sha1: None,
795 x509_thumbprint_sha256: None,
796 };
797 let jws = encode_sign(ssi_jwk::Algorithm::ES256, JSON_PATCH, &key).unwrap();
798 let json_update = Updates::SignedIetfJsonPatch(vec![jws.clone()]);
799 DIDTZ
800 .tier3_updates(Prefix::TZ3, &mut doc, json_update)
801 .await
802 .unwrap();
803 assert_eq!(
804 doc.service[1],
805 Service {
806 id: uri!("http://example.org/test_service_id").to_owned(),
807 type_: OneOrMany::One("test_service".to_string()),
808 service_endpoint: Some(OneOrMany::One(service::Endpoint::Uri(
809 uri!("http://example.org/test_service_endpoint").to_owned()
810 ))),
811 property_set: BTreeMap::new()
812 }
813 );
814 }
815}