1use iref::Iri;
2use ssi_dids_core::document::representation::MediaType;
3use ssi_dids_core::document::verification_method::ValueOrReference;
4use ssi_dids_core::{document, resolution, DIDBuf, DIDMethod};
5use ssi_dids_core::{document::representation, resolution::Output};
6use static_iref::iri;
7use std::collections::BTreeMap;
8use std::str::FromStr;
9
10use ssi_caips::caip10::BlockchainAccountId;
11use ssi_caips::caip2::ChainId;
12use ssi_dids_core::{
13 document::DIDVerificationMethod,
14 resolution::{DIDMethodResolver, Error},
15 DIDURLBuf, Document, DID,
16};
17use ssi_jwk::{Base64urlUInt, OctetParams, Params, JWK};
18
19mod json_ld_context;
20
21pub use json_ld_context::*;
22
23#[derive(Debug, Clone, Copy)]
24pub enum PkhVerificationMethodType {
25 Ed25519VerificationKey2018,
26 EcdsaSecp256k1RecoveryMethod2020,
27 TezosMethod2021,
28 SolanaMethod2021,
29 Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021,
30 P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021,
31 BlockchainVerificationMethod2021,
32}
33
34impl PkhVerificationMethodType {
35 pub fn name(&self) -> &'static str {
48 match self {
49 Self::Ed25519VerificationKey2018 => "Ed25519VerificationKey2018",
50 Self::TezosMethod2021 => "TezosMethod2021",
51 Self::SolanaMethod2021 => "SolanaMethod2021",
52 Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => {
53 "Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"
54 }
55 Self::EcdsaSecp256k1RecoveryMethod2020 => "EcdsaSecp256k1RecoveryMethod2020",
56 Self::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => {
57 "P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"
58 }
59 Self::BlockchainVerificationMethod2021 => "BlockchainVerificationMethod2021",
60 }
61 }
62
63 pub fn as_iri(&self) -> &'static Iri {
64 match self {
65 Self::Ed25519VerificationKey2018 => iri!("https://w3id.org/security#Ed25519VerificationKey2018"),
66 Self::TezosMethod2021 => iri!("https://w3id.org/security#TezosMethod2021"),
67 Self::SolanaMethod2021 => iri!("https://w3id.org/security#SolanaMethod2021"),
68 Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => iri!("https://w3id.org/security#Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"),
69 Self::EcdsaSecp256k1RecoveryMethod2020 => iri!("https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020"),
70 Self::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 => iri!("https://w3id.org/security#P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021"),
71 Self::BlockchainVerificationMethod2021 => iri!("https://w3id.org/security#BlockchainVerificationMethod2021")
72 }
73 }
74}
75
76pub struct PkhVerificationMethod {
77 pub id: DIDURLBuf,
78 pub type_: PkhVerificationMethodType,
79 pub controller: DIDBuf,
80 pub blockchain_account_id: BlockchainAccountId,
81 pub public_key: Option<PublicKey>,
82}
83
84pub enum PublicKey {
85 Jwk(Box<JWK>),
86 Base58(String),
87}
88
89impl From<PkhVerificationMethod> for DIDVerificationMethod {
90 fn from(value: PkhVerificationMethod) -> Self {
91 let mut properties: BTreeMap<String, serde_json::Value> = BTreeMap::new();
92 properties.insert(
93 "blockchainAccountId".to_owned(),
94 value.blockchain_account_id.to_string().into(),
95 );
96
97 if let Some(key) = value.public_key {
98 match key {
99 PublicKey::Jwk(jwk) => {
100 properties.insert(
101 "publicKeyJwk".to_owned(),
102 serde_json::to_value(jwk).unwrap(),
103 );
104 }
105 PublicKey::Base58(key) => {
106 properties.insert("publicKeyBase58".to_owned(), key.into());
107 }
108 }
109 }
110
111 Self {
112 id: value.id,
113 type_: value.type_.name().to_owned(),
114 controller: value.controller,
115 properties,
116 }
117 }
118}
119
120const REFERENCE_EIP155_ETHEREUM_MAINNET: &str = "1";
122
123const REFERENCE_EIP155_CELO_MAINNET: &str = "42220";
124const REFERENCE_EIP155_POLYGON_MAINNET: &str = "137";
125
126const REFERENCE_BIP122_BITCOIN_MAINNET: &str = "000000000019d6689c085ae165831e93";
128
129const REFERENCE_BIP122_DOGECOIN_MAINNET: &str = "1a91e3dace36e2be3bf030a65679fe82";
130
131const REFERENCE_TEZOS_MAINNET: &str = "NetXdQprcVkpaWU";
133
134const REFERENCE_SOLANA_MAINNET: &str = "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ";
136
137pub struct DIDPKH;
141
142type ResolutionResult = Result<(Document, JsonLdContext), Error>;
143
144async fn resolve_tezos(did: &DID, account_address: &str, reference: &str) -> ResolutionResult {
145 if account_address.len() < 3 {
146 return Err(Error::InvalidMethodSpecificId(
147 did.method_specific_id().to_owned(),
148 ));
149 }
150
151 let vm_type = match account_address.get(0..3) {
152 Some("tz1") => {
153 PkhVerificationMethodType::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021
154 }
155 Some("tz2") => PkhVerificationMethodType::EcdsaSecp256k1RecoveryMethod2020,
156 Some("tz3") => {
157 PkhVerificationMethodType::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021
158 }
159 _ => {
160 return Err(Error::InvalidMethodSpecificId(
161 did.method_specific_id().to_owned(),
162 ))
163 }
164 };
165
166 let blockchain_account_id = BlockchainAccountId {
167 account_address: account_address.to_owned(),
168 chain_id: ChainId {
169 namespace: "tezos".to_string(),
170 reference: reference.to_string(),
171 },
172 };
173
174 let vm_url = DIDURLBuf::from_string(format!("{did}#blockchainAccountId")).unwrap();
175 let vm = PkhVerificationMethod {
176 id: vm_url.clone(),
177 type_: vm_type,
178 controller: did.to_owned(),
179 blockchain_account_id: blockchain_account_id.clone(),
180 public_key: None,
181 };
182
183 let vm2_url = DIDURLBuf::from_string(format!("{did}#TezosMethod2021")).unwrap();
184 let vm2 = PkhVerificationMethod {
185 id: vm2_url.clone(),
186 type_: PkhVerificationMethodType::TezosMethod2021,
187 controller: did.to_owned(),
188 blockchain_account_id,
189 public_key: None,
190 };
191
192 let mut json_ld_context = JsonLdContext::default();
193 json_ld_context.add_verification_method(&vm);
194 json_ld_context.add_verification_method(&vm2);
195
196 let mut doc = Document::new(did.to_owned());
197 doc.verification_method.extend([vm.into(), vm2.into()]);
198 doc.verification_relationships.authentication.extend([
199 ValueOrReference::Reference(vm_url.clone().into()),
200 ValueOrReference::Reference(vm2_url.clone().into()),
201 ]);
202 doc.verification_relationships.assertion_method.extend([
203 ValueOrReference::Reference(vm_url.into()),
204 ValueOrReference::Reference(vm2_url.into()),
205 ]);
206
207 Ok((doc, json_ld_context))
208}
209
210async fn resolve_eip155(
211 did: &DID,
212 account_address: &str,
213 reference: &str,
214 legacy: bool,
215) -> ResolutionResult {
216 if !account_address.starts_with("0x") {
217 return Err(Error::InvalidMethodSpecificId(
218 did.method_specific_id().to_owned(),
219 ));
220 }
221
222 let blockchain_account_id = BlockchainAccountId {
223 account_address: account_address.to_owned(),
224 chain_id: ChainId {
225 namespace: "eip155".to_string(),
226 reference: reference.to_string(),
227 },
228 };
229 let vm_fragment = if legacy {
230 "Recovery2020"
233 } else {
234 "blockchainAccountId"
235 };
236 let vm_url = DIDURLBuf::from_string(format!("{did}#{vm_fragment}")).unwrap();
237 let vm = PkhVerificationMethod {
238 id: vm_url.clone(),
239 type_: PkhVerificationMethodType::EcdsaSecp256k1RecoveryMethod2020,
240 controller: did.to_owned(),
241 blockchain_account_id,
242 public_key: None,
243 };
244
245 let mut json_ld_context = JsonLdContext::default();
246 json_ld_context.add_verification_method(&vm);
247
248 let mut doc = Document::new(did.to_owned());
249 doc.verification_method.push(vm.into());
250 doc.verification_relationships
251 .authentication
252 .push(ValueOrReference::Reference(vm_url.clone().into()));
253 doc.verification_relationships
254 .assertion_method
255 .push(ValueOrReference::Reference(vm_url.into()));
256
257 Ok((doc, json_ld_context))
258}
259
260async fn resolve_solana(did: &DID, account_address: &str, reference: &str) -> ResolutionResult {
261 let public_key_bytes = match bs58::decode(&account_address).into_vec() {
262 Ok(bytes) => bytes,
263 Err(_) => {
264 return Err(Error::InvalidMethodSpecificId(
265 did.method_specific_id().to_owned(),
266 ))
267 }
268 };
269 if public_key_bytes.len() != 32 {
270 return Err(Error::InvalidMethodSpecificId(
271 did.method_specific_id().to_owned(),
272 ));
273 }
274 let chain_id = ChainId {
275 namespace: "solana".to_string(),
276 reference: reference.to_string(),
277 };
278
279 let pk_jwk = JWK {
280 params: Params::OKP(OctetParams {
281 curve: "Ed25519".to_string(),
282 public_key: Base64urlUInt(public_key_bytes),
283 private_key: None,
284 }),
285 public_key_use: None,
286 key_operations: None,
287 algorithm: None,
288 key_id: None,
289 x509_url: None,
290 x509_certificate_chain: None,
291 x509_thumbprint_sha1: None,
292 x509_thumbprint_sha256: None,
293 };
294 let blockchain_account_id = BlockchainAccountId {
295 account_address: account_address.to_owned(),
296 chain_id,
297 };
298 let vm_url = DIDURLBuf::from_string(format!("{did}#controller")).unwrap();
299 let vm = PkhVerificationMethod {
300 id: vm_url.clone(),
301 type_: PkhVerificationMethodType::Ed25519VerificationKey2018,
302 public_key: Some(PublicKey::Base58(account_address.to_owned())),
303 controller: did.to_owned(),
304 blockchain_account_id: blockchain_account_id.clone(),
305 };
306 let solvm_url = DIDURLBuf::from_string(format!("{did}#SolanaMethod2021")).unwrap();
307 let solvm = PkhVerificationMethod {
308 id: solvm_url.clone(),
309 type_: PkhVerificationMethodType::SolanaMethod2021,
310 public_key: Some(PublicKey::Jwk(Box::new(pk_jwk))),
311 controller: did.to_owned(),
312 blockchain_account_id,
313 };
314
315 let mut json_ld_context = JsonLdContext::default();
316 json_ld_context.add_verification_method(&vm);
317 json_ld_context.add_verification_method(&solvm);
318
319 let mut doc = Document::new(did.to_owned());
320 doc.verification_method.extend([vm.into(), solvm.into()]);
321 doc.verification_relationships.authentication.extend([
322 ValueOrReference::Reference(vm_url.clone().into()),
323 ValueOrReference::Reference(solvm_url.clone().into()),
324 ]);
325 doc.verification_relationships.assertion_method.extend([
326 ValueOrReference::Reference(vm_url.into()),
327 ValueOrReference::Reference(solvm_url.into()),
328 ]);
329
330 Ok((doc, json_ld_context))
331}
332
333async fn resolve_bip122(did: &DID, account_address: &str, reference: &str) -> ResolutionResult {
334 match reference {
335 REFERENCE_BIP122_BITCOIN_MAINNET => {
336 if !account_address.starts_with('1') {
337 return Err(Error::InvalidMethodSpecificId(
338 did.method_specific_id().to_owned(),
339 ));
340 }
341 }
342 REFERENCE_BIP122_DOGECOIN_MAINNET => {
343 if !account_address.starts_with('D') {
344 return Err(Error::InvalidMethodSpecificId(
345 did.method_specific_id().to_owned(),
346 ));
347 }
348 }
349 _ => {
350 }
352 }
353 let blockchain_account_id = BlockchainAccountId {
354 account_address: account_address.to_owned(),
355 chain_id: ChainId {
356 namespace: "bip122".to_string(),
357 reference: reference.to_string(),
358 },
359 };
360 let vm_url = DIDURLBuf::from_string(format!("{did}#blockchainAccountId")).unwrap();
361 let vm = PkhVerificationMethod {
362 id: vm_url.clone(),
363 type_: PkhVerificationMethodType::EcdsaSecp256k1RecoveryMethod2020,
364 controller: did.to_owned(),
365 blockchain_account_id,
366 public_key: None,
367 };
368
369 let mut json_ld_context = JsonLdContext::default();
370 json_ld_context.add_verification_method(&vm);
371
372 let mut doc = Document::new(did.to_owned());
373 doc.verification_method.push(vm.into());
374 doc.verification_relationships
375 .authentication
376 .push(ValueOrReference::Reference(vm_url.clone().into()));
377 doc.verification_relationships
378 .assertion_method
379 .push(ValueOrReference::Reference(vm_url.into()));
380
381 Ok((doc, json_ld_context))
382}
383
384async fn resolve_aleo(did: &DID, account_address: &str, reference: &str) -> ResolutionResult {
385 use bech32::FromBase32;
386 let (hrp, data, _variant) = match bech32::decode(account_address) {
387 Err(_e) => {
388 return Err(Error::InvalidMethodSpecificId(
389 did.method_specific_id().to_owned(),
390 ))
391 }
392 Ok(data) => data,
393 };
394 if data.is_empty() {
395 return Err(Error::InvalidMethodSpecificId(
396 did.method_specific_id().to_owned(),
397 ));
398 }
399 if hrp != "aleo" {
400 return Err(Error::InvalidMethodSpecificId(
401 did.method_specific_id().to_owned(),
402 ));
403 }
404 let data = match Vec::<u8>::from_base32(&data) {
405 Err(_e) => {
406 return Err(Error::InvalidMethodSpecificId(
407 did.method_specific_id().to_owned(),
408 ))
409 }
410 Ok(data) => data,
411 };
412 if data.len() != 32 {
415 return Err(Error::InvalidMethodSpecificId(
416 did.method_specific_id().to_owned(),
417 ));
418 }
419 let chain_id = ChainId {
420 namespace: "aleo".to_string(),
421 reference: reference.to_string(),
422 };
423 let blockchain_account_id = BlockchainAccountId {
424 account_address: account_address.to_owned(),
425 chain_id,
426 };
427 let vm_url = DIDURLBuf::from_string(format!("{did}#blockchainAccountId")).unwrap();
428 let vm = PkhVerificationMethod {
429 id: vm_url.clone(),
430 type_: PkhVerificationMethodType::BlockchainVerificationMethod2021,
431 controller: did.to_owned(),
432 blockchain_account_id,
433 public_key: None,
434 };
435
436 let mut json_ld_context = JsonLdContext::default();
437 json_ld_context.add_blockchain_2021_v1();
438 json_ld_context.add_verification_method(&vm);
439
440 let mut doc = Document::new(did.to_owned());
441 doc.verification_method.push(vm.into());
442 doc.verification_relationships
443 .authentication
444 .push(ValueOrReference::Reference(vm_url.clone().into()));
445 doc.verification_relationships
446 .assertion_method
447 .push(ValueOrReference::Reference(vm_url.into()));
448
449 Ok((doc, json_ld_context))
450}
451
452async fn resolve_caip10(did: &DID, account_id: &str) -> ResolutionResult {
453 let account_id = match BlockchainAccountId::from_str(account_id) {
454 Ok(account_id) => account_id,
455 Err(_) => {
456 return Err(Error::InvalidMethodSpecificId(
457 did.method_specific_id().to_owned(),
458 ))
459 }
460 };
461
462 let namespace = account_id.chain_id.namespace;
463 let reference = account_id.chain_id.reference;
464 match &namespace[..] {
465 "tezos" => resolve_tezos(did, &account_id.account_address, &reference).await,
466 "eip155" => resolve_eip155(did, &account_id.account_address, &reference, false).await,
467 "bip122" => resolve_bip122(did, &account_id.account_address, &reference).await,
468 "solana" => resolve_solana(did, &account_id.account_address, &reference).await,
469 "aleo" => resolve_aleo(did, &account_id.account_address, &reference).await,
470 _ => Err(Error::InvalidMethodSpecificId(
471 did.method_specific_id().to_owned(),
472 )),
473 }
474}
475
476impl DIDMethod for DIDPKH {
477 const DID_METHOD_NAME: &'static str = "pkh";
478}
479
480impl DIDMethodResolver for DIDPKH {
481 fn method_name(&self) -> &str {
482 "pkh"
483 }
484
485 async fn resolve_method_representation<'a>(
486 &'a self,
487 id: &'a str,
488 options: ssi_dids_core::resolution::Options,
489 ) -> Result<Output<Vec<u8>>, Error> {
490 let (type_, data) = id
491 .split_once(':')
492 .ok_or_else(|| Error::InvalidMethodSpecificId(id.to_owned()))?;
493
494 let did = DIDBuf::from_string(format!("did:pkh:{id}")).unwrap();
495 let (doc, json_ld_context) = match type_ {
496 "tz" => resolve_tezos(&did, data, REFERENCE_TEZOS_MAINNET).await,
498 "eth" => resolve_eip155(&did, data, REFERENCE_EIP155_ETHEREUM_MAINNET, true).await,
499 "celo" => resolve_eip155(&did, data, REFERENCE_EIP155_CELO_MAINNET, true).await,
500 "poly" => resolve_eip155(&did, data, REFERENCE_EIP155_POLYGON_MAINNET, true).await,
501 "sol" => resolve_solana(&did, data, REFERENCE_SOLANA_MAINNET).await,
502 "btc" => resolve_bip122(&did, data, REFERENCE_BIP122_BITCOIN_MAINNET).await,
503 "doge" => resolve_bip122(&did, data, REFERENCE_BIP122_DOGECOIN_MAINNET).await,
504 _ => {
506 let account_id = type_.to_string() + ":" + data;
507 resolve_caip10(&did, &account_id).await
508 }
509 }?;
510
511 let content_type = options.accept.unwrap_or(MediaType::JsonLd);
512 let represented = doc.into_representation(representation::Options::from_media_type(
513 content_type,
514 move || representation::json_ld::Options {
515 context: representation::json_ld::Context::array(
516 representation::json_ld::DIDContext::V1,
517 json_ld_context.into_entries(),
518 ),
519 },
520 ));
521
522 Ok(Output::new(
523 represented.to_bytes(),
524 document::Metadata::default(),
525 resolution::Metadata::from_content_type(Some(content_type.to_string())),
526 ))
527 }
528}
529
530fn generate_sol(jwk: &JWK) -> Result<String, GenerateError> {
531 match jwk.params {
532 Params::OKP(ref params) if params.curve == "Ed25519" => {
533 Ok(bs58::encode(¶ms.public_key.0).into_string())
534 }
535 _ => Err(GenerateError::UnsupportedKeyType),
536 }
537}
538
539#[cfg(feature = "ripemd-160")]
540fn generate_btc(key: &JWK) -> Result<String, GenerateError> {
541 let addr = ssi_jwk::ripemd160::hash_public_key(key, 0x00).map_err(GenerateError::other)?;
542 #[cfg(test)]
543 if !addr.starts_with('1') {
544 return Err(GenerateError::other("Expected Bitcoin address"));
545 }
546 Ok(addr)
547}
548
549#[cfg(feature = "ripemd-160")]
550fn generate_doge(key: &JWK) -> Result<String, GenerateError> {
551 let addr = ssi_jwk::ripemd160::hash_public_key(key, 0x1e).map_err(GenerateError::other)?;
552 #[cfg(test)]
553 if !addr.starts_with('D') {
554 return Err(GenerateError::other("Expected Dogecoin address"));
555 }
556 Ok(addr)
557}
558
559#[cfg(feature = "tezos")]
560fn generate_caip10_tezos(
561 key: &JWK,
562 ref_opt: Option<String>,
563) -> Result<BlockchainAccountId, GenerateError> {
564 let hash = ssi_jwk::blakesig::hash_public_key(key).map_err(GenerateError::other)?;
565 let reference = ref_opt.unwrap_or_else(|| REFERENCE_TEZOS_MAINNET.to_string());
566 Ok(BlockchainAccountId {
567 account_address: hash,
568 chain_id: ChainId {
569 namespace: "tezos".to_string(),
570 reference,
571 },
572 })
573}
574
575#[cfg(feature = "eip")]
576fn generate_caip10_eip155(
577 key: &JWK,
578 ref_opt: Option<String>,
579) -> Result<BlockchainAccountId, GenerateError> {
580 let hash = ssi_jwk::eip155::hash_public_key_eip55(key).map_err(GenerateError::other)?;
581 let reference = ref_opt.unwrap_or_else(|| REFERENCE_EIP155_ETHEREUM_MAINNET.to_string());
582 Ok(BlockchainAccountId {
583 account_address: hash,
584 chain_id: ChainId {
585 namespace: "eip155".to_string(),
586 reference,
587 },
588 })
589}
590
591#[cfg(feature = "ripemd-160")]
592fn generate_caip10_bip122(
593 key: &JWK,
594 ref_opt: Option<String>,
595) -> Result<BlockchainAccountId, GenerateError> {
596 let reference = ref_opt.unwrap_or_else(|| REFERENCE_BIP122_BITCOIN_MAINNET.to_string());
597 let addr;
598 match &reference[..] {
599 REFERENCE_BIP122_BITCOIN_MAINNET => {
600 addr = ssi_jwk::ripemd160::hash_public_key(key, 0x00).map_err(GenerateError::other)?;
601 if !addr.starts_with('1') {
602 return Err(GenerateError::other("Expected Bitcoin address"));
603 }
604 }
605 REFERENCE_BIP122_DOGECOIN_MAINNET => {
606 addr = ssi_jwk::ripemd160::hash_public_key(key, 0x1e).map_err(GenerateError::other)?;
607 if !addr.starts_with('D') {
608 return Err(GenerateError::other("Expected Dogecoin address"));
609 }
610 }
611 _ => {
612 return Err(GenerateError::other("Expected Bitcoin address type"));
613 }
614 }
615
616 Ok(BlockchainAccountId {
617 account_address: addr,
618 chain_id: ChainId {
619 namespace: "bip122".to_string(),
620 reference,
621 },
622 })
623}
624
625#[cfg(feature = "solana")]
626fn generate_caip10_solana(
627 key: &JWK,
628 ref_opt: Option<String>,
629) -> Result<BlockchainAccountId, GenerateError> {
630 let reference = ref_opt.unwrap_or_default();
631 let chain_id = ChainId {
632 namespace: "solana".to_string(),
633 reference,
634 };
635 let pk_bs58 = match key.params {
636 Params::OKP(ref params) if params.curve == "Ed25519" => {
637 bs58::encode(¶ms.public_key.0).into_string()
638 }
639 _ => return Err(GenerateError::UnsupportedKeyType),
640 };
641 Ok(BlockchainAccountId {
642 account_address: pk_bs58,
643 chain_id,
644 })
645}
646
647#[cfg(feature = "aleo")]
648fn generate_caip10_aleo(
649 key: &JWK,
650 ref_opt: Option<String>,
651) -> Result<BlockchainAccountId, GenerateError> {
652 let reference = ref_opt.unwrap_or_else(|| "1".to_string());
653 let chain_id = ChainId {
654 namespace: "aleo".to_string(),
655 reference,
656 };
657 use bech32::ToBase32;
658 let pk_bs58 = match key.params {
659 Params::OKP(ref params) if params.curve == "AleoTestnet1Key" => bech32::encode(
660 "aleo",
661 params.public_key.0.to_base32(),
662 bech32::Variant::Bech32m,
663 )
664 .unwrap(),
665 _ => return Err(GenerateError::UnsupportedKeyType),
666 };
667 Ok(BlockchainAccountId {
668 account_address: pk_bs58,
669 chain_id,
670 })
671}
672
673#[allow(unused, unreachable_code)]
674fn generate_caip10_did(key: &JWK, name: &str) -> Result<DIDBuf, GenerateError> {
675 let (namespace, reference_opt) = match name.splitn(2, ':').collect::<Vec<&str>>().as_slice() {
682 [namespace] => (namespace.to_string(), None),
683 [namespace, reference] => (namespace.to_string(), Some(reference.to_string())),
684 _ => return Err(GenerateError::InvalidChainId),
685 };
686 let account_id: BlockchainAccountId = match &namespace[..] {
687 #[cfg(feature = "tezos")]
688 "tezos" => generate_caip10_tezos(key, reference_opt)?,
689 #[cfg(feature = "eip")]
690 "eip155" => generate_caip10_eip155(key, reference_opt)?,
691 #[cfg(feature = "ripemd-160")]
692 "bip122" => generate_caip10_bip122(key, reference_opt)?,
693 #[cfg(feature = "solana")]
694 "solana" => generate_caip10_solana(key, reference_opt)?,
695 #[cfg(feature = "aleo")]
696 "aleo" => generate_caip10_aleo(key, reference_opt)?,
697 _ => return Err(GenerateError::UnsupportedNamespace),
698 };
699
700 Ok(DIDBuf::from_string(format!("did:pkh:{}", account_id)).unwrap())
701}
702
703#[derive(Debug, thiserror::Error)]
704pub enum GenerateError {
705 #[error("Unable to parse chain id or namespace")]
706 InvalidChainId,
707
708 #[error("Namespace not supported")]
709 UnsupportedNamespace,
710
711 #[error("Unsupported key type")]
712 UnsupportedKeyType,
713
714 #[error("{0}")]
715 Other(String),
716}
717
718impl GenerateError {
719 pub fn other(e: impl ToString) -> Self {
720 Self::Other(e.to_string())
721 }
722}
723
724impl DIDPKH {
725 pub fn generate(key: &JWK, pkh_name: &str) -> Result<DIDBuf, GenerateError> {
726 let addr = match pkh_name {
727 #[cfg(feature = "tezos")]
729 "tz" => ssi_jwk::blakesig::hash_public_key(key).map_err(GenerateError::other)?,
730 #[cfg(feature = "eip")]
731 "eth" => ssi_jwk::eip155::hash_public_key(key).map_err(GenerateError::other)?,
732 #[cfg(feature = "eip")]
733 "celo" => ssi_jwk::eip155::hash_public_key(key).map_err(GenerateError::other)?,
734 #[cfg(feature = "eip")]
735 "poly" => ssi_jwk::eip155::hash_public_key(key).map_err(GenerateError::other)?,
736 "sol" => generate_sol(key)?,
737 #[cfg(feature = "ripemd-160")]
738 "btc" => generate_btc(key)?,
739 #[cfg(feature = "ripemd-160")]
740 "doge" => generate_doge(key)?,
741 name => return generate_caip10_did(key, name),
743 };
744
745 Ok(DIDBuf::from_string(format!("did:pkh:{}:{}", pkh_name, addr)).unwrap())
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752 use ssi_claims::VerificationParameters;
753 use ssi_dids_core::{did, resolution::ErrorKind, DIDResolver, VerificationMethodDIDResolver};
754
755 #[cfg(all(feature = "eip", feature = "tezos"))]
756 fn test_generate(jwk_value: serde_json::Value, type_: &str, did_expected: &str) {
757 let jwk: JWK = serde_json::from_value(jwk_value).unwrap();
758 let did = DIDPKH::generate(&jwk, type_).unwrap();
759 assert_eq!(did, did_expected);
760 }
761
762 #[test]
763 #[cfg(all(feature = "eip", feature = "tezos"))]
764 fn generate_did_pkh() {
765 use serde_json::json;
766
767 let secp256k1_pk = json!({
768 "kty": "EC",
769 "crv": "secp256k1",
770 "x": "yclqMZ0MtyVkKm1eBh2AyaUtsqT0l5RJM3g4SzRT96A",
771 "y": "yQzUwKnftWCJPGs-faGaHiYi1sxA6fGJVw2Px_LCNe8",
772 });
773 test_generate(
774 secp256k1_pk.clone(),
775 "eth",
776 "did:pkh:eth:0x2fbf1be19d90a29aea9363f4ef0b6bf1c4ff0758",
777 );
778 test_generate(
779 secp256k1_pk.clone(),
780 "celo",
781 "did:pkh:celo:0x2fbf1be19d90a29aea9363f4ef0b6bf1c4ff0758",
782 );
783 test_generate(
784 secp256k1_pk.clone(),
785 "poly",
786 "did:pkh:poly:0x2fbf1be19d90a29aea9363f4ef0b6bf1c4ff0758",
787 );
788 test_generate(
789 json!({
790 "kty": "OKP",
791 "crv": "EdBlake2b",
792 "x": "GvidwVqGgicuL68BRM89OOtDzK1gjs8IqUXFkjKkm8Iwg18slw==",
793 "d": "K44dAtJ-MMl-JKuOupfcGRPI5n3ZVH_Gk65c6Rcgn_IV28987PMw_b6paCafNOBOi5u-FZMgGJd3mc5MkfxfwjCrXQM-"
794 }),
795 "tz",
796 "did:pkh:tz:tz1YwA1FwpgLtc1G8DKbbZ6e6PTb1dQMRn5x",
797 );
798 test_generate(
799 secp256k1_pk,
800 "tz",
801 "did:pkh:tz:tz2CA2f3SWWcqbWsjHsMZPZxCY5iafSN3nDz",
802 );
803 test_generate(
804 json!({
805 "kty": "EC",
806 "crv": "P-256",
807 "x": "UmzXjEZzlGmpaM_CmFEJtOO5JBntW8yl_fM1LEQlWQ4",
808 "y": "OmoZmcbUadg7dEC8bg5kXryN968CJqv2UFMUKRERZ6s"
809 }),
810 "tz",
811 "did:pkh:tz:tz3agP9LGe2cXmKQyYn6T68BHKjjktDbbSWX",
812 );
813 }
814
815 async fn test_resolve(did: &DID, doc_str_expected: &str) {
816 let res = DIDPKH.resolve_with(did, Default::default()).await.unwrap();
817 eprintln!("{}", did);
818 let doc = res.document;
819 eprintln!("resolved:\n{}", serde_json::to_string_pretty(&doc).unwrap());
820 let doc_expected: Document = serde_json::from_str(doc_str_expected).unwrap();
821 eprintln!(
822 "expected:\n{}",
823 serde_json::to_string_pretty(&doc_expected).unwrap()
824 );
825 assert_eq!(
826 serde_json::to_value(doc).unwrap(),
827 serde_json::to_value(doc_expected).unwrap()
828 );
829 }
830
831 async fn test_resolve_error(did: &DID, error_expected: ErrorKind) {
832 let res = DIDPKH.resolve(did).await;
833 assert_eq!(res.err().unwrap().kind(), error_expected);
834 }
835
836 #[tokio::test]
837 async fn resolve_did_pkh() {
838 test_resolve(
840 did!("did:pkh:tezos:NetXdQprcVkpaWU:tz1TzrmTBSuiVHV2VfMnGRMYvTEPCP42oSM8"),
841 include_str!("../tests/did-tz1.jsonld"),
842 )
843 .await;
844 test_resolve(
845 did!("did:pkh:tezos:NetXdQprcVkpaWU:tz2BFTyPeYRzxd5aiBchbXN3WCZhx7BqbMBq"),
846 include_str!("../tests/did-tz2.jsonld"),
847 )
848 .await;
849 test_resolve(
850 did!("did:pkh:tezos:NetXdQprcVkpaWU:tz3agP9LGe2cXmKQyYn6T68BHKjjktDbbSWX"),
851 include_str!("../tests/did-tz3.jsonld"),
852 )
853 .await;
854 test_resolve(
855 did!("did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a"),
856 include_str!("../tests/did-eth.jsonld"),
857 )
858 .await;
859 test_resolve(
860 did!("did:pkh:eip155:42220:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011"),
861 include_str!("../tests/did-celo.jsonld"),
862 )
863 .await;
864 test_resolve(
865 did!("did:pkh:eip155:137:0x4e90e8a8191c1c23a24a598c3ab4fb47ce926ff5"),
866 include_str!("../tests/did-poly.jsonld"),
867 )
868 .await;
869 test_resolve(
870 did!("did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev"),
871 include_str!("../tests/did-sol.jsonld"),
872 )
873 .await;
874 test_resolve(
875 did!("did:pkh:bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6"),
876 include_str!("../tests/did-btc.jsonld"),
877 )
878 .await;
879 test_resolve(
880 did!("did:pkh:bip122:1a91e3dace36e2be3bf030a65679fe82:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L"),
881 include_str!("../tests/did-doge.jsonld"),
882 )
883 .await;
884 test_resolve(
885 did!("did:pkh:aleo:1:aleo1y90yg3yzs4g7q25f9nn8khuu00m8ysynxmcw8aca2d0phdx8dgpq4vw348"),
886 include_str!("../tests/did-aleo.jsonld"),
887 )
888 .await;
889
890 test_resolve(
892 did!("did:pkh:tz:tz1TzrmTBSuiVHV2VfMnGRMYvTEPCP42oSM8"),
893 include_str!("../tests/did-tz1-legacy.jsonld"),
894 )
895 .await;
896 test_resolve(
897 did!("did:pkh:tz:tz2BFTyPeYRzxd5aiBchbXN3WCZhx7BqbMBq"),
898 include_str!("../tests/did-tz2-legacy.jsonld"),
899 )
900 .await;
901 test_resolve(
902 did!("did:pkh:tz:tz3agP9LGe2cXmKQyYn6T68BHKjjktDbbSWX"),
903 include_str!("../tests/did-tz3-legacy.jsonld"),
904 )
905 .await;
906 test_resolve(
907 did!("did:pkh:eth:0xb9c5714089478a327f09197987f16f9e5d936e8a"),
908 include_str!("../tests/did-eth-legacy.jsonld"),
909 )
910 .await;
911 test_resolve(
912 did!("did:pkh:celo:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011"),
913 include_str!("../tests/did-celo-legacy.jsonld"),
914 )
915 .await;
916 test_resolve(
917 did!("did:pkh:poly:0x4e90e8a8191c1c23a24a598c3ab4fb47ce926ff5"),
918 include_str!("../tests/did-poly-legacy.jsonld"),
919 )
920 .await;
921 test_resolve(
922 did!("did:pkh:sol:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev"),
923 include_str!("../tests/did-sol-legacy.jsonld"),
924 )
925 .await;
926 test_resolve(
927 did!("did:pkh:btc:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6"),
928 include_str!("../tests/did-btc-legacy.jsonld"),
929 )
930 .await;
931 test_resolve(
932 did!("did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L"),
933 include_str!("../tests/did-doge-legacy.jsonld"),
934 )
935 .await;
936
937 test_resolve_error(did!("did:pkh:tz:foo"), ErrorKind::InvalidMethodSpecificId).await;
938 test_resolve_error(did!("did:pkh:eth:bar"), ErrorKind::InvalidMethodSpecificId).await;
939 }
940
941 #[cfg(all(feature = "eip", feature = "tezos"))]
942 async fn credential_prove_verify_did_pkh(
943 key: JWK,
944 wrong_key: JWK,
945 type_: &str,
946 vm_relative_url: &str,
947 proof_suite: ssi_claims::data_integrity::AnySuite,
948 eip712_domain_opt: Option<
949 ssi_claims::data_integrity::suites::ethereum_eip712_signature_2021::Eip712Options,
950 >,
951 vp_eip712_domain_opt: Option<
952 ssi_claims::data_integrity::suites::ethereum_eip712_signature_2021::Eip712Options,
953 >,
954 ) {
955 use iref::IriBuf;
956 use ssi_claims::{
957 data_integrity::{
958 signing::AlterSignature, AnyInputSuiteOptions, CryptographicSuite, ProofOptions,
959 },
960 vc::{
961 syntax::NonEmptyVec,
962 v1::{JsonCredential, JsonPresentation},
963 },
964 VerificationParameters,
965 };
966 use ssi_verification_methods_core::{ProofPurpose, SingleSecretSigner};
967 use static_iref::uri;
968
969 let didpkh = VerificationMethodDIDResolver::new(DIDPKH);
970 let params = VerificationParameters::from_resolver(&didpkh);
971
972 let did = DIDPKH::generate(&key, type_).unwrap();
974
975 eprintln!("did: {}", did);
976 let cred = JsonCredential::new(
977 None,
978 did.clone().into_uri().into(),
979 "2021-03-18T16:38:25Z".parse().unwrap(),
980 NonEmptyVec::new(json_syntax::json!({
981 "id": "did:example:foo"
982 })),
983 );
984
985 let issuance_date = cred.issuance_date.clone().unwrap();
986 let created_date =
987 xsd_types::DateTimeStamp::new(issuance_date.date_time, issuance_date.offset.unwrap());
988 let issue_options = ProofOptions::new(
989 created_date.into(),
990 IriBuf::new(did.to_string() + vm_relative_url)
991 .unwrap()
992 .into(),
993 ProofPurpose::Assertion,
994 AnyInputSuiteOptions {
995 eip712: eip712_domain_opt.clone(),
996 ..Default::default()
998 }
999 .with_public_key(key.to_public())
1000 .unwrap(),
1001 );
1002 eprintln!("vm {:?}", issue_options.verification_method);
1003 let signer = SingleSecretSigner::new(key.clone()).into_local();
1009 eprintln!("key: {key}");
1010 eprintln!("suite: {proof_suite:?}");
1011 println!("cred: {}", serde_json::to_string_pretty(&cred).unwrap());
1012 let vc = proof_suite
1013 .sign(cred.clone(), &didpkh, &signer, issue_options.clone())
1014 .await
1015 .unwrap();
1016 println!("VC: {}", serde_json::to_string_pretty(&vc).unwrap());
1017 assert!(vc.verify(¶ms).await.unwrap().is_ok());
1018
1019 let mut vc_bad_issuer = vc.clone();
1021 vc_bad_issuer.issuer = uri!("did:pkh:example:bad").to_owned().into();
1022
1023 assert!(vc_bad_issuer.verify(¶ms).await.unwrap().is_err());
1025
1026 let wrong_signer = SingleSecretSigner::new(wrong_key.clone()).into_local();
1028 let vc_wrong_key = proof_suite
1029 .sign(cred, &didpkh, &wrong_signer, issue_options)
1030 .await
1031 .unwrap();
1032 assert!(vc_wrong_key.verify(¶ms).await.unwrap().is_err());
1033
1034 let mut vc_fuzzed = vc.clone();
1036 vc_fuzzed.proofs.first_mut().unwrap().signature.alter();
1037 let vc_fuzzed_result = vc_fuzzed.verify(¶ms).await;
1038 assert!(vc_fuzzed_result.is_err() || vc_fuzzed_result.is_ok_and(|v| v.is_err()));
1039
1040 let presentation = JsonPresentation::new(None, Some(did.clone().into()), vec![vc]);
1042
1043 let vp_issue_options = ProofOptions::new(
1044 "2021-03-18T16:38:25Z".parse().unwrap(),
1045 IriBuf::new(did.to_string() + vm_relative_url)
1046 .unwrap()
1047 .into(),
1048 ProofPurpose::Authentication,
1049 AnyInputSuiteOptions {
1050 eip712: vp_eip712_domain_opt.clone(),
1051 ..Default::default()
1052 }
1053 .with_public_key(key.to_public())
1054 .unwrap(),
1055 );
1056
1057 eprintln!(
1058 "presentation: {}",
1059 serde_json::to_string_pretty(&presentation).unwrap()
1060 );
1061 let vp = proof_suite
1062 .sign(presentation, &didpkh, &signer, vp_issue_options)
1063 .await
1064 .unwrap();
1065
1066 println!("VP: {}", serde_json::to_string_pretty(&vp).unwrap());
1067 assert!(vp.verify(¶ms).await.unwrap().is_ok());
1068
1069 let mut vp_fuzzed = vp.clone();
1071 vp_fuzzed.proofs.first_mut().unwrap().signature.alter();
1072 let vp_fuzzed_result = vp_fuzzed.verify(¶ms).await;
1073 assert!(vp_fuzzed_result.is_err() || vp_fuzzed_result.is_ok_and(|v| v.is_err()));
1074
1075 let mut vp_bad_holder = vp.clone();
1077 vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned());
1078
1079 assert!(vp_bad_holder.verify(¶ms).await.unwrap().is_err());
1081 }
1082
1083 #[cfg(all(feature = "eip", feature = "tezos"))]
1084 async fn credential_prepare_complete_verify_did_pkh_tz(
1085 key: JWK,
1086 wrong_key: JWK,
1087 type_: &str,
1088 vm_relative_url: &str,
1089 proof_suite: ssi_claims::data_integrity::AnySuite,
1090 ) {
1091 use iref::IriBuf;
1092 use ssi_claims::{
1093 data_integrity::{
1094 signing::AlterSignature, AnyInputSuiteOptions, CryptographicSuite, ProofOptions,
1095 },
1096 vc::{
1097 syntax::NonEmptyVec,
1098 v1::{JsonCredential, JsonPresentation},
1099 },
1100 VerificationParameters,
1101 };
1102 use ssi_verification_methods_core::{ProofPurpose, SingleSecretSigner};
1103 use static_iref::uri;
1104
1105 let didpkh = VerificationMethodDIDResolver::new(DIDPKH);
1106 let verifier = VerificationParameters::from_resolver(&didpkh);
1107 let did = DIDPKH::generate(&key, type_).unwrap();
1108
1109 eprintln!("did: {}", did);
1110 let cred = JsonCredential::new(
1111 None,
1112 did.clone().into_uri().into(),
1113 "2021-03-18T16:38:25Z".parse().unwrap(),
1114 NonEmptyVec::new(json_syntax::json!({
1115 "id": "did:example:foo"
1116 })),
1117 );
1118 let issuance_date = cred.issuance_date.clone().unwrap();
1119 let created_date =
1120 xsd_types::DateTimeStamp::new(issuance_date.date_time, issuance_date.offset.unwrap());
1121 let issue_options = ProofOptions::new(
1122 created_date.into(),
1123 IriBuf::new(did.to_string() + vm_relative_url)
1124 .unwrap()
1125 .into(),
1126 ProofPurpose::Assertion,
1127 AnyInputSuiteOptions::default()
1128 .with_public_key(key.to_public())
1129 .unwrap(),
1130 );
1131 eprintln!("vm {:?}", issue_options.verification_method);
1132 let signer = SingleSecretSigner::new(key.clone()).into_local();
1133 eprintln!("key: {key}");
1134 eprintln!("suite: {proof_suite:?}");
1135 let vc = proof_suite
1136 .sign(cred.clone(), &didpkh, &signer, issue_options.clone())
1137 .await
1138 .unwrap();
1139 println!("VC: {}", serde_json::to_string_pretty(&vc).unwrap());
1140 assert!(vc.verify(&verifier).await.unwrap().is_ok());
1141
1142 let mut vc_bad_issuer = vc.clone();
1144 vc_bad_issuer.issuer = uri!("did:pkh:example:bad").to_owned().into();
1145 assert!(vc_bad_issuer.verify(&verifier).await.unwrap().is_err());
1146
1147 let wrong_signer = SingleSecretSigner::new(wrong_key.clone()).into_local();
1149 let vc_wrong_key = proof_suite
1150 .sign(cred, &didpkh, &wrong_signer, issue_options)
1151 .await
1152 .unwrap();
1153 assert!(vc_wrong_key.verify(&verifier).await.unwrap().is_err());
1154
1155 let mut vc_fuzzed = vc.clone();
1157 vc_fuzzed.proofs.first_mut().unwrap().signature.alter();
1158 let vc_fuzzed_result = vc_fuzzed.verify(&verifier).await;
1159 assert!(vc_fuzzed_result.is_err() || vc_fuzzed_result.is_ok_and(|v| v.is_err()));
1160
1161 let presentation = JsonPresentation::new(None, Some(did.clone().into()), vec![vc]);
1163
1164 let vp_issue_options = ProofOptions::new(
1165 "2021-03-18T16:38:25Z".parse().unwrap(),
1166 IriBuf::new(did.to_string() + vm_relative_url)
1167 .unwrap()
1168 .into(),
1169 ProofPurpose::Authentication,
1170 AnyInputSuiteOptions::default()
1171 .with_public_key(key.to_public())
1172 .unwrap(),
1173 );
1174
1175 let vp = proof_suite
1176 .sign(presentation, &didpkh, &signer, vp_issue_options)
1177 .await
1178 .unwrap();
1179
1180 println!("VP: {}", serde_json::to_string_pretty(&vp).unwrap());
1181 assert!(vp.verify(&verifier).await.unwrap().is_ok());
1182
1183 let mut vp_fuzzed = vp.clone();
1185 vp_fuzzed.proofs.first_mut().unwrap().signature.alter();
1186 let vp_fuzzed_result = vp_fuzzed.verify(&verifier).await;
1187 assert!(vp_fuzzed_result.is_err() || vp_fuzzed_result.is_ok_and(|v| v.is_err()));
1188
1189 let mut vp_bad_holder = vp.clone();
1191 vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned());
1192 assert!(vp_bad_holder.verify(&verifier).await.unwrap().is_err());
1194 }
1195
1196 #[tokio::test]
1206 #[cfg(all(feature = "eip", feature = "tezos"))]
1207 async fn resolve_vc_issue_verify() {
1208 use serde_json::json;
1209 use ssi_claims::data_integrity::AnySuite;
1210 use ssi_jwk::Algorithm;
1211
1212 let key_secp256k1: JWK = serde_json::from_str(include_str!(
1213 "../../../../../tests/secp256k1-2021-02-17.json"
1214 ))
1215 .unwrap();
1216 let key_secp256k1_recovery = JWK {
1217 algorithm: Some(Algorithm::ES256KR),
1218 ..key_secp256k1.clone()
1219 };
1220 let key_secp256k1_eip712sig = JWK {
1221 algorithm: Some(Algorithm::ES256KR),
1222 key_operations: Some(vec!["signTypedData".to_string()]),
1223 ..key_secp256k1.clone()
1224 };
1225 let key_secp256k1_epsig = JWK {
1226 algorithm: Some(Algorithm::ES256KR),
1227 key_operations: Some(vec!["signPersonalMessage".to_string()]),
1228 ..key_secp256k1.clone()
1229 };
1230
1231 let mut key_ed25519: JWK =
1232 serde_json::from_str(include_str!("../../../../../tests/ed25519-2020-10-18.json"))
1233 .unwrap();
1234 let mut key_p256: JWK = serde_json::from_str(include_str!(
1235 "../../../../../tests/secp256r1-2021-03-18.json"
1236 ))
1237 .unwrap();
1238 let other_key_secp256k1 = JWK::generate_secp256k1();
1239 let mut other_key_ed25519 = JWK::generate_ed25519().unwrap();
1240 let mut other_key_p256 = JWK::generate_p256();
1241
1242 credential_prove_verify_did_pkh(
1244 key_secp256k1_recovery.clone(),
1245 other_key_secp256k1.clone(),
1246 "eip155",
1247 "#blockchainAccountId",
1248 AnySuite::EcdsaSecp256k1RecoverySignature2020,
1249 None,
1250 None,
1251 )
1252 .await;
1253
1254 credential_prove_verify_did_pkh(
1256 key_secp256k1_eip712sig.clone(),
1257 other_key_secp256k1.clone(),
1258 "eip155",
1259 "#blockchainAccountId",
1260 AnySuite::Eip712Signature2021,
1261 None,
1262 None,
1263 )
1264 .await;
1265
1266 credential_prove_verify_did_pkh(
1268 key_secp256k1_eip712sig.clone(),
1269 other_key_secp256k1.clone(),
1270 "eip155",
1271 "#blockchainAccountId",
1272 AnySuite::EthereumPersonalSignature2021,
1273 None,
1274 None,
1275 )
1276 .await;
1277
1278 let eip712_domain = serde_json::from_value(json!({
1280 "types": {
1281 "EIP712Domain": [
1282 { "name": "name", "type": "string" }
1283 ],
1284 "VerifiableCredential": [
1285 { "name": "@context", "type": "string[]" },
1286 { "name": "type", "type": "string[]" },
1287 { "name": "issuer", "type": "string" },
1288 { "name": "issuanceDate", "type": "string" },
1289 { "name": "credentialSubject", "type": "CredentialSubject" },
1290 { "name": "proof", "type": "Proof" }
1291 ],
1292 "CredentialSubject": [
1293 { "name": "id", "type": "string" },
1294 ],
1295 "Proof": [
1296 { "name": "@context", "type": "string" },
1297 { "name": "verificationMethod", "type": "string" },
1298 { "name": "created", "type": "string" },
1299 { "name": "proofPurpose", "type": "string" },
1300 { "name": "type", "type": "string" }
1301 ]
1302 },
1303 "domain": {
1304 "name": "EthereumEip712Signature2021",
1305 },
1306 "primaryType": "VerifiableCredential"
1307 }))
1308 .unwrap();
1309 let vp_eip712_domain = serde_json::from_value(json!({
1310 "types": {
1311 "EIP712Domain": [
1312 { "name": "name", "type": "string" }
1313 ],
1314 "VerifiablePresentation": [
1315 { "name": "@context", "type": "string[]" },
1316 { "name": "type", "type": "string[]" },
1317 { "name": "holder", "type": "string" },
1318 { "name": "verifiableCredential", "type": "VerifiableCredential" },
1319 { "name": "proof", "type": "Proof" }
1320 ],
1321 "VerifiableCredential": [
1322 { "name": "@context", "type": "string[]" },
1323 { "name": "type", "type": "string[]" },
1324 { "name": "issuer", "type": "string" },
1325 { "name": "issuanceDate", "type": "string" },
1326 { "name": "credentialSubject", "type": "CredentialSubject" },
1327 { "name": "proof", "type": "Proof" }
1328 ],
1329 "CredentialSubject": [
1330 { "name": "id", "type": "string" },
1331 ],
1332 "Proof": [
1333 { "name": "@context", "type": "string" },
1334 { "name": "verificationMethod", "type": "string" },
1335 { "name": "created", "type": "string" },
1336 { "name": "proofPurpose", "type": "string" },
1337 { "name": "proofValue", "type": "string" },
1338 { "name": "eip712", "type": "EIP712Info" },
1339 { "name": "type", "type": "string" }
1340 ],
1341 "EIP712Info": [
1342 { "name": "domain", "type": "EIP712Domain" },
1343 { "name": "primaryType", "type": "string" },
1344 { "name": "types", "type": "Types" },
1345 ],
1346 "Types": [
1347 { "name": "EIP712Domain", "type": "Type[]" },
1348 { "name": "VerifiableCredential", "type": "Type[]" },
1349 { "name": "CredentialSubject", "type": "Type[]" },
1350 { "name": "Proof", "type": "Type[]" },
1351 ],
1352 "Type": [
1353 { "name": "name", "type": "string" },
1354 { "name": "type", "type": "string" }
1355 ]
1356 },
1357 "domain": {
1358 "name": "EthereumEip712Signature2021",
1359 },
1360 "primaryType": "VerifiablePresentation"
1361 }))
1362 .unwrap();
1363 credential_prove_verify_did_pkh(
1364 key_secp256k1_eip712sig.clone(),
1365 other_key_secp256k1.clone(),
1366 "eip155",
1367 "#blockchainAccountId",
1368 AnySuite::EthereumEip712Signature2021,
1369 Some(eip712_domain),
1370 Some(vp_eip712_domain),
1371 )
1372 .await;
1373
1374 credential_prove_verify_did_pkh(
1376 key_secp256k1_epsig.clone(),
1377 other_key_secp256k1.clone(),
1378 "eip155",
1379 "#blockchainAccountId",
1380 AnySuite::Eip712Signature2021,
1381 None,
1382 None,
1383 )
1384 .await;
1385
1386 println!("did:pkh:tz:tz1");
1387 key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1388 other_key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1389 credential_prove_verify_did_pkh(
1390 key_ed25519.clone(),
1391 other_key_ed25519.clone(),
1392 "tz",
1393 "#blockchainAccountId",
1394 AnySuite::Ed25519BLAKE2BDigestSize20Base58CheckEncodedSignature2021,
1395 None,
1396 None,
1397 )
1398 .await;
1399 key_ed25519.algorithm = Some(Algorithm::EdDSA);
1400 other_key_ed25519.algorithm = Some(Algorithm::EdDSA);
1401
1402 println!("did:pkh:tz:tz3");
1414 key_p256.algorithm = Some(Algorithm::ESBlake2b);
1415 other_key_p256.algorithm = Some(Algorithm::ESBlake2b);
1416 credential_prove_verify_did_pkh(
1417 key_p256.clone(),
1418 other_key_p256.clone(),
1419 "tz",
1420 "#blockchainAccountId",
1421 AnySuite::P256BLAKE2BDigestSize20Base58CheckEncodedSignature2021,
1422 None,
1423 None,
1424 )
1425 .await;
1426 key_p256.algorithm = Some(Algorithm::ES256);
1427 other_key_p256.algorithm = Some(Algorithm::ES256);
1428
1429 println!("did:pkh:sol");
1430 credential_prove_verify_did_pkh(
1431 key_ed25519.clone(),
1432 other_key_ed25519.clone(),
1433 "sol",
1434 "#controller",
1435 AnySuite::Ed25519Signature2018,
1436 None,
1437 None,
1438 )
1439 .await;
1440
1441 println!("did:pkh:btc");
1454 credential_prove_verify_did_pkh(
1455 key_secp256k1_recovery.clone(),
1456 other_key_secp256k1.clone(),
1457 "btc",
1458 "#blockchainAccountId",
1459 AnySuite::EcdsaSecp256k1RecoverySignature2020,
1460 None,
1461 None,
1462 )
1463 .await;
1464
1465 println!("did:pkh:doge");
1466 credential_prove_verify_did_pkh(
1467 key_secp256k1_recovery.clone(),
1468 other_key_secp256k1.clone(),
1469 "doge",
1470 "#blockchainAccountId",
1471 AnySuite::EcdsaSecp256k1RecoverySignature2020,
1472 None,
1473 None,
1474 )
1475 .await;
1476
1477 println!("did:pkh:tz:tz1 - TezosMethod2021");
1478 key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1479 other_key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1480 credential_prepare_complete_verify_did_pkh_tz(
1481 key_ed25519.clone(),
1482 other_key_ed25519.clone(),
1483 "tz",
1484 "#TezosMethod2021",
1485 AnySuite::TezosSignature2021,
1486 )
1487 .await;
1488 key_ed25519.algorithm = Some(Algorithm::EdDSA);
1489 other_key_ed25519.algorithm = Some(Algorithm::EdDSA);
1490
1491 println!("did:pkh:tz:tz3 - TezosMethod2021");
1505 key_p256.algorithm = Some(Algorithm::ESBlake2b);
1506 other_key_p256.algorithm = Some(Algorithm::ESBlake2b);
1507 credential_prepare_complete_verify_did_pkh_tz(
1508 key_p256.clone(),
1509 other_key_p256.clone(),
1510 "tz",
1511 "#TezosMethod2021",
1512 AnySuite::TezosSignature2021,
1513 )
1514 .await;
1515 key_p256.algorithm = Some(Algorithm::ES256);
1516 other_key_p256.algorithm = Some(Algorithm::ES256);
1517 }
1518
1519 async fn test_verify_vc(name: &str, vc_str: &str, _num_warnings: usize) {
1520 eprintln!("test verify vc `{name}`");
1522 eprintln!("input: {vc_str}");
1523
1524 let vc = ssi_claims::vc::v1::data_integrity::any_credential_from_json_str(vc_str).unwrap();
1525
1526 let didpkh = VerificationMethodDIDResolver::new(DIDPKH);
1527 let verifier = VerificationParameters::from_resolver(&didpkh);
1528 let verification_result = vc.verify(&verifier).await.unwrap();
1529 assert!(verification_result.is_ok());
1530
1531 let mut bad_vc = vc.clone();
1535 bad_vc
1536 .additional_properties
1537 .insert("http://example.org/foo".into(), "bar".into());
1538 for proof in &mut bad_vc.proofs {
1539 if let Some(eip712) = proof.options.eip712_mut() {
1542 if let Some(ssi_claims::data_integrity::suites::eip712::TypesOrURI::Object(types)) =
1543 &mut eip712.types
1544 {
1545 let vc_schema = types.types.get_mut("VerifiableCredential").unwrap();
1546 vc_schema.push(ssi_eip712::MemberVariable::new(
1547 "http://example.org/foo".to_owned(),
1548 ssi_eip712::TypeRef::String,
1549 ));
1550 }
1551 }
1552
1553 if let Some(eip712) = proof.options.eip712_v0_1_mut() {
1555 if let Some(ssi_claims::data_integrity::suites::eip712::TypesOrURI::Object(types)) =
1556 &mut eip712.message_schema
1557 {
1558 let vc_schema = types.types.get_mut("VerifiableCredential").unwrap();
1559 vc_schema.push(ssi_eip712::MemberVariable::new(
1560 "http://example.org/foo".to_owned(),
1561 ssi_eip712::TypeRef::String,
1562 ));
1563 }
1564 }
1565 }
1566
1567 let verification_result = bad_vc.verify(verifier).await.unwrap();
1568 assert!(verification_result.is_err());
1569 }
1570
1571 #[tokio::test]
1572 async fn verify_vc() {
1573 test_verify_vc("vc-tz1", include_str!("../tests/vc-tz1.jsonld"), 0).await;
1575 test_verify_vc(
1576 "vc-tz1-jcs.jsonld",
1577 include_str!("../tests/vc-tz1-jcs.jsonld"),
1578 1,
1579 )
1580 .await;
1581 test_verify_vc(
1590 "vc-eth-eip712vm",
1591 include_str!("../tests/vc-eth-eip712vm.jsonld"),
1592 0,
1593 )
1594 .await;
1595 test_verify_vc(
1596 "vc-eth-epsig",
1597 include_str!("../tests/vc-eth-epsig.jsonld"),
1598 0,
1599 )
1600 .await;
1601 test_verify_vc(
1602 "vc-celo-epsig",
1603 include_str!("../tests/vc-celo-epsig.jsonld"),
1604 0,
1605 )
1606 .await;
1607 test_verify_vc(
1608 "vc-poly-epsig",
1609 include_str!("../tests/vc-poly-epsig.jsonld"),
1610 0,
1611 )
1612 .await;
1613 test_verify_vc(
1614 "vc-poly-eip712sig",
1615 include_str!("../tests/vc-poly-eip712sig.jsonld"),
1616 0,
1617 )
1618 .await;
1619 }
1620}