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::v1::{JsonCredential, JsonPresentation},
961 VerificationParameters,
962 };
963 use ssi_verification_methods_core::{ProofPurpose, SingleSecretSigner};
964 use static_iref::uri;
965
966 let didpkh = VerificationMethodDIDResolver::new(DIDPKH);
967 let params = VerificationParameters::from_resolver(&didpkh);
968
969 let did = DIDPKH::generate(&key, type_).unwrap();
971
972 eprintln!("did: {}", did);
973 let cred = JsonCredential::new(
974 None,
975 did.clone().into_uri().into(),
976 "2021-03-18T16:38:25Z".parse().unwrap(),
977 vec![json_syntax::json!({
978 "id": "did:example:foo"
979 })],
980 );
981
982 let issuance_date = cred.issuance_date.clone().unwrap();
983 let created_date =
984 xsd_types::DateTimeStamp::new(issuance_date.date_time, issuance_date.offset.unwrap());
985 let issue_options = ProofOptions::new(
986 created_date,
987 IriBuf::new(did.to_string() + vm_relative_url)
988 .unwrap()
989 .into(),
990 ProofPurpose::Assertion,
991 AnyInputSuiteOptions {
992 eip712: eip712_domain_opt.clone(),
993 ..Default::default()
995 }
996 .with_public_key(key.to_public())
997 .unwrap(),
998 );
999 eprintln!("vm {:?}", issue_options.verification_method);
1000 let signer = SingleSecretSigner::new(key.clone()).into_local();
1006 eprintln!("key: {key}");
1007 eprintln!("suite: {proof_suite:?}");
1008 println!("cred: {}", serde_json::to_string_pretty(&cred).unwrap());
1009 let vc = proof_suite
1010 .sign(cred.clone(), &didpkh, &signer, issue_options.clone())
1011 .await
1012 .unwrap();
1013 println!("VC: {}", serde_json::to_string_pretty(&vc).unwrap());
1014 assert!(vc.verify(¶ms).await.unwrap().is_ok());
1015
1016 let mut vc_bad_issuer = vc.clone();
1018 vc_bad_issuer.issuer = uri!("did:pkh:example:bad").to_owned().into();
1019
1020 assert!(vc_bad_issuer.verify(¶ms).await.unwrap().is_err());
1022
1023 let wrong_signer = SingleSecretSigner::new(wrong_key.clone()).into_local();
1025 let vc_wrong_key = proof_suite
1026 .sign(cred, &didpkh, &wrong_signer, issue_options)
1027 .await
1028 .unwrap();
1029 assert!(vc_wrong_key.verify(¶ms).await.unwrap().is_err());
1030
1031 let mut vc_fuzzed = vc.clone();
1033 vc_fuzzed.proofs.first_mut().unwrap().signature.alter();
1034 let vc_fuzzed_result = vc_fuzzed.verify(¶ms).await;
1035 assert!(vc_fuzzed_result.is_err() || vc_fuzzed_result.is_ok_and(|v| v.is_err()));
1036
1037 let presentation = JsonPresentation::new(None, Some(did.clone().into()), vec![vc]);
1039
1040 let vp_issue_options = ProofOptions::new(
1041 "2021-03-18T16:38:25Z".parse().unwrap(),
1042 IriBuf::new(did.to_string() + vm_relative_url)
1043 .unwrap()
1044 .into(),
1045 ProofPurpose::Authentication,
1046 AnyInputSuiteOptions {
1047 eip712: vp_eip712_domain_opt.clone(),
1048 ..Default::default()
1049 }
1050 .with_public_key(key.to_public())
1051 .unwrap(),
1052 );
1053
1054 eprintln!(
1055 "presentation: {}",
1056 serde_json::to_string_pretty(&presentation).unwrap()
1057 );
1058 let vp = proof_suite
1059 .sign(presentation, &didpkh, &signer, vp_issue_options)
1060 .await
1061 .unwrap();
1062
1063 println!("VP: {}", serde_json::to_string_pretty(&vp).unwrap());
1064 assert!(vp.verify(¶ms).await.unwrap().is_ok());
1065
1066 let mut vp_fuzzed = vp.clone();
1068 vp_fuzzed.proofs.first_mut().unwrap().signature.alter();
1069 let vp_fuzzed_result = vp_fuzzed.verify(¶ms).await;
1070 assert!(vp_fuzzed_result.is_err() || vp_fuzzed_result.is_ok_and(|v| v.is_err()));
1071
1072 let mut vp_bad_holder = vp.clone();
1074 vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned().into());
1075
1076 assert!(vp_bad_holder.verify(¶ms).await.unwrap().is_err());
1078 }
1079
1080 #[cfg(all(feature = "eip", feature = "tezos"))]
1081 async fn credential_prepare_complete_verify_did_pkh_tz(
1082 key: JWK,
1083 wrong_key: JWK,
1084 type_: &str,
1085 vm_relative_url: &str,
1086 proof_suite: ssi_claims::data_integrity::AnySuite,
1087 ) {
1088 use iref::IriBuf;
1089 use ssi_claims::{
1090 data_integrity::{
1091 signing::AlterSignature, AnyInputSuiteOptions, CryptographicSuite, ProofOptions,
1092 },
1093 vc::v1::{JsonCredential, JsonPresentation},
1094 VerificationParameters,
1095 };
1096 use ssi_verification_methods_core::{ProofPurpose, SingleSecretSigner};
1097 use static_iref::uri;
1098
1099 let didpkh = VerificationMethodDIDResolver::new(DIDPKH);
1100 let verifier = VerificationParameters::from_resolver(&didpkh);
1101 let did = DIDPKH::generate(&key, type_).unwrap();
1102
1103 eprintln!("did: {}", did);
1104 let cred = JsonCredential::new(
1105 None,
1106 did.clone().into_uri().into(),
1107 "2021-03-18T16:38:25Z".parse().unwrap(),
1108 vec![json_syntax::json!({
1109 "id": "did:example:foo"
1110 })],
1111 );
1112 let issuance_date = cred.issuance_date.clone().unwrap();
1113 let created_date =
1114 xsd_types::DateTimeStamp::new(issuance_date.date_time, issuance_date.offset.unwrap());
1115 let issue_options = ProofOptions::new(
1116 created_date,
1117 IriBuf::new(did.to_string() + vm_relative_url)
1118 .unwrap()
1119 .into(),
1120 ProofPurpose::Assertion,
1121 AnyInputSuiteOptions::default()
1122 .with_public_key(key.to_public())
1123 .unwrap(),
1124 );
1125 eprintln!("vm {:?}", issue_options.verification_method);
1126 let signer = SingleSecretSigner::new(key.clone()).into_local();
1127 eprintln!("key: {key}");
1128 eprintln!("suite: {proof_suite:?}");
1129 let vc = proof_suite
1130 .sign(cred.clone(), &didpkh, &signer, issue_options.clone())
1131 .await
1132 .unwrap();
1133 println!("VC: {}", serde_json::to_string_pretty(&vc).unwrap());
1134 assert!(vc.verify(&verifier).await.unwrap().is_ok());
1135
1136 let mut vc_bad_issuer = vc.clone();
1138 vc_bad_issuer.issuer = uri!("did:pkh:example:bad").to_owned().into();
1139 assert!(!vc_bad_issuer.verify(&verifier).await.unwrap().is_ok());
1140
1141 let wrong_signer = SingleSecretSigner::new(wrong_key.clone()).into_local();
1143 let vc_wrong_key = proof_suite
1144 .sign(cred, &didpkh, &wrong_signer, issue_options)
1145 .await
1146 .unwrap();
1147 assert!(vc_wrong_key.verify(&verifier).await.unwrap().is_err());
1148
1149 let mut vc_fuzzed = vc.clone();
1151 vc_fuzzed.proofs.first_mut().unwrap().signature.alter();
1152 let vc_fuzzed_result = vc_fuzzed.verify(&verifier).await;
1153 assert!(vc_fuzzed_result.is_err() || vc_fuzzed_result.is_ok_and(|v| v.is_err()));
1154
1155 let presentation = JsonPresentation::new(None, Some(did.clone().into()), vec![vc]);
1157
1158 let vp_issue_options = ProofOptions::new(
1159 "2021-03-18T16:38:25Z".parse().unwrap(),
1160 IriBuf::new(did.to_string() + vm_relative_url)
1161 .unwrap()
1162 .into(),
1163 ProofPurpose::Authentication,
1164 AnyInputSuiteOptions::default()
1165 .with_public_key(key.to_public())
1166 .unwrap(),
1167 );
1168
1169 let vp = proof_suite
1170 .sign(presentation, &didpkh, &signer, vp_issue_options)
1171 .await
1172 .unwrap();
1173
1174 println!("VP: {}", serde_json::to_string_pretty(&vp).unwrap());
1175 assert!(vp.verify(&verifier).await.unwrap().is_ok());
1176
1177 let mut vp_fuzzed = vp.clone();
1179 vp_fuzzed.proofs.first_mut().unwrap().signature.alter();
1180 let vp_fuzzed_result = vp_fuzzed.verify(&verifier).await;
1181 assert!(vp_fuzzed_result.is_err() || vp_fuzzed_result.is_ok_and(|v| v.is_err()));
1182
1183 let mut vp_bad_holder = vp.clone();
1185 vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned().into());
1186 assert!(vp_bad_holder.verify(&verifier).await.unwrap().is_err());
1188 }
1189
1190 #[tokio::test]
1200 #[cfg(all(feature = "eip", feature = "tezos"))]
1201 async fn resolve_vc_issue_verify() {
1202 use serde_json::json;
1203 use ssi_claims::data_integrity::AnySuite;
1204 use ssi_jwk::Algorithm;
1205
1206 let key_secp256k1: JWK = serde_json::from_str(include_str!(
1207 "../../../../../tests/secp256k1-2021-02-17.json"
1208 ))
1209 .unwrap();
1210 let key_secp256k1_recovery = JWK {
1211 algorithm: Some(Algorithm::ES256KR),
1212 ..key_secp256k1.clone()
1213 };
1214 let key_secp256k1_eip712sig = JWK {
1215 algorithm: Some(Algorithm::ES256KR),
1216 key_operations: Some(vec!["signTypedData".to_string()]),
1217 ..key_secp256k1.clone()
1218 };
1219 let key_secp256k1_epsig = JWK {
1220 algorithm: Some(Algorithm::ES256KR),
1221 key_operations: Some(vec!["signPersonalMessage".to_string()]),
1222 ..key_secp256k1.clone()
1223 };
1224
1225 let mut key_ed25519: JWK =
1226 serde_json::from_str(include_str!("../../../../../tests/ed25519-2020-10-18.json"))
1227 .unwrap();
1228 let mut key_p256: JWK = serde_json::from_str(include_str!(
1229 "../../../../../tests/secp256r1-2021-03-18.json"
1230 ))
1231 .unwrap();
1232 let other_key_secp256k1 = JWK::generate_secp256k1();
1233 let mut other_key_ed25519 = JWK::generate_ed25519().unwrap();
1234 let mut other_key_p256 = JWK::generate_p256();
1235
1236 credential_prove_verify_did_pkh(
1238 key_secp256k1_recovery.clone(),
1239 other_key_secp256k1.clone(),
1240 "eip155",
1241 "#blockchainAccountId",
1242 AnySuite::EcdsaSecp256k1RecoverySignature2020,
1243 None,
1244 None,
1245 )
1246 .await;
1247
1248 credential_prove_verify_did_pkh(
1250 key_secp256k1_eip712sig.clone(),
1251 other_key_secp256k1.clone(),
1252 "eip155",
1253 "#blockchainAccountId",
1254 AnySuite::Eip712Signature2021,
1255 None,
1256 None,
1257 )
1258 .await;
1259
1260 credential_prove_verify_did_pkh(
1262 key_secp256k1_eip712sig.clone(),
1263 other_key_secp256k1.clone(),
1264 "eip155",
1265 "#blockchainAccountId",
1266 AnySuite::EthereumPersonalSignature2021,
1267 None,
1268 None,
1269 )
1270 .await;
1271
1272 let eip712_domain = serde_json::from_value(json!({
1274 "types": {
1275 "EIP712Domain": [
1276 { "name": "name", "type": "string" }
1277 ],
1278 "VerifiableCredential": [
1279 { "name": "@context", "type": "string[]" },
1280 { "name": "type", "type": "string[]" },
1281 { "name": "issuer", "type": "string" },
1282 { "name": "issuanceDate", "type": "string" },
1283 { "name": "credentialSubject", "type": "CredentialSubject" },
1284 { "name": "proof", "type": "Proof" }
1285 ],
1286 "CredentialSubject": [
1287 { "name": "id", "type": "string" },
1288 ],
1289 "Proof": [
1290 { "name": "@context", "type": "string" },
1291 { "name": "verificationMethod", "type": "string" },
1292 { "name": "created", "type": "string" },
1293 { "name": "proofPurpose", "type": "string" },
1294 { "name": "type", "type": "string" }
1295 ]
1296 },
1297 "domain": {
1298 "name": "EthereumEip712Signature2021",
1299 },
1300 "primaryType": "VerifiableCredential"
1301 }))
1302 .unwrap();
1303 let vp_eip712_domain = serde_json::from_value(json!({
1304 "types": {
1305 "EIP712Domain": [
1306 { "name": "name", "type": "string" }
1307 ],
1308 "VerifiablePresentation": [
1309 { "name": "@context", "type": "string[]" },
1310 { "name": "type", "type": "string" },
1311 { "name": "holder", "type": "string" },
1312 { "name": "verifiableCredential", "type": "VerifiableCredential" },
1313 { "name": "proof", "type": "Proof" }
1314 ],
1315 "VerifiableCredential": [
1316 { "name": "@context", "type": "string[]" },
1317 { "name": "type", "type": "string[]" },
1318 { "name": "issuer", "type": "string" },
1319 { "name": "issuanceDate", "type": "string" },
1320 { "name": "credentialSubject", "type": "CredentialSubject" },
1321 { "name": "proof", "type": "Proof" }
1322 ],
1323 "CredentialSubject": [
1324 { "name": "id", "type": "string" },
1325 ],
1326 "Proof": [
1327 { "name": "@context", "type": "string" },
1328 { "name": "verificationMethod", "type": "string" },
1329 { "name": "created", "type": "string" },
1330 { "name": "proofPurpose", "type": "string" },
1331 { "name": "proofValue", "type": "string" },
1332 { "name": "eip712", "type": "EIP712Info" },
1333 { "name": "type", "type": "string" }
1334 ],
1335 "EIP712Info": [
1336 { "name": "domain", "type": "EIP712Domain" },
1337 { "name": "primaryType", "type": "string" },
1338 { "name": "types", "type": "Types" },
1339 ],
1340 "Types": [
1341 { "name": "EIP712Domain", "type": "Type[]" },
1342 { "name": "VerifiableCredential", "type": "Type[]" },
1343 { "name": "CredentialSubject", "type": "Type[]" },
1344 { "name": "Proof", "type": "Type[]" },
1345 ],
1346 "Type": [
1347 { "name": "name", "type": "string" },
1348 { "name": "type", "type": "string" }
1349 ]
1350 },
1351 "domain": {
1352 "name": "EthereumEip712Signature2021",
1353 },
1354 "primaryType": "VerifiablePresentation"
1355 }))
1356 .unwrap();
1357 credential_prove_verify_did_pkh(
1358 key_secp256k1_eip712sig.clone(),
1359 other_key_secp256k1.clone(),
1360 "eip155",
1361 "#blockchainAccountId",
1362 AnySuite::EthereumEip712Signature2021,
1363 Some(eip712_domain),
1364 Some(vp_eip712_domain),
1365 )
1366 .await;
1367
1368 credential_prove_verify_did_pkh(
1370 key_secp256k1_epsig.clone(),
1371 other_key_secp256k1.clone(),
1372 "eip155",
1373 "#blockchainAccountId",
1374 AnySuite::Eip712Signature2021,
1375 None,
1376 None,
1377 )
1378 .await;
1379
1380 println!("did:pkh:tz:tz1");
1381 key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1382 other_key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1383 credential_prove_verify_did_pkh(
1384 key_ed25519.clone(),
1385 other_key_ed25519.clone(),
1386 "tz",
1387 "#blockchainAccountId",
1388 AnySuite::Ed25519BLAKE2BDigestSize20Base58CheckEncodedSignature2021,
1389 None,
1390 None,
1391 )
1392 .await;
1393 key_ed25519.algorithm = Some(Algorithm::EdDSA);
1394 other_key_ed25519.algorithm = Some(Algorithm::EdDSA);
1395
1396 println!("did:pkh:tz:tz3");
1408 key_p256.algorithm = Some(Algorithm::ESBlake2b);
1409 other_key_p256.algorithm = Some(Algorithm::ESBlake2b);
1410 credential_prove_verify_did_pkh(
1411 key_p256.clone(),
1412 other_key_p256.clone(),
1413 "tz",
1414 "#blockchainAccountId",
1415 AnySuite::P256BLAKE2BDigestSize20Base58CheckEncodedSignature2021,
1416 None,
1417 None,
1418 )
1419 .await;
1420 key_p256.algorithm = Some(Algorithm::ES256);
1421 other_key_p256.algorithm = Some(Algorithm::ES256);
1422
1423 println!("did:pkh:sol");
1424 credential_prove_verify_did_pkh(
1425 key_ed25519.clone(),
1426 other_key_ed25519.clone(),
1427 "sol",
1428 "#controller",
1429 AnySuite::Ed25519Signature2018,
1430 None,
1431 None,
1432 )
1433 .await;
1434
1435 println!("did:pkh:btc");
1448 credential_prove_verify_did_pkh(
1449 key_secp256k1_recovery.clone(),
1450 other_key_secp256k1.clone(),
1451 "btc",
1452 "#blockchainAccountId",
1453 AnySuite::EcdsaSecp256k1RecoverySignature2020,
1454 None,
1455 None,
1456 )
1457 .await;
1458
1459 println!("did:pkh:doge");
1460 credential_prove_verify_did_pkh(
1461 key_secp256k1_recovery.clone(),
1462 other_key_secp256k1.clone(),
1463 "doge",
1464 "#blockchainAccountId",
1465 AnySuite::EcdsaSecp256k1RecoverySignature2020,
1466 None,
1467 None,
1468 )
1469 .await;
1470
1471 println!("did:pkh:tz:tz1 - TezosMethod2021");
1472 key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1473 other_key_ed25519.algorithm = Some(Algorithm::EdBlake2b);
1474 credential_prepare_complete_verify_did_pkh_tz(
1475 key_ed25519.clone(),
1476 other_key_ed25519.clone(),
1477 "tz",
1478 "#TezosMethod2021",
1479 AnySuite::TezosSignature2021,
1480 )
1481 .await;
1482 key_ed25519.algorithm = Some(Algorithm::EdDSA);
1483 other_key_ed25519.algorithm = Some(Algorithm::EdDSA);
1484
1485 println!("did:pkh:tz:tz3 - TezosMethod2021");
1499 key_p256.algorithm = Some(Algorithm::ESBlake2b);
1500 other_key_p256.algorithm = Some(Algorithm::ESBlake2b);
1501 credential_prepare_complete_verify_did_pkh_tz(
1502 key_p256.clone(),
1503 other_key_p256.clone(),
1504 "tz",
1505 "#TezosMethod2021",
1506 AnySuite::TezosSignature2021,
1507 )
1508 .await;
1509 key_p256.algorithm = Some(Algorithm::ES256);
1510 other_key_p256.algorithm = Some(Algorithm::ES256);
1511 }
1512
1513 async fn test_verify_vc(name: &str, vc_str: &str, _num_warnings: usize) {
1514 eprintln!("test verify vc `{name}`");
1516 eprintln!("input: {vc_str}");
1517
1518 let vc = ssi_claims::vc::v1::data_integrity::any_credential_from_json_str(vc_str).unwrap();
1519
1520 let didpkh = VerificationMethodDIDResolver::new(DIDPKH);
1521 let verifier = VerificationParameters::from_resolver(&didpkh);
1522 let verification_result = vc.verify(&verifier).await.unwrap();
1523 assert!(verification_result.is_ok());
1524
1525 let mut bad_vc = vc.clone();
1529 bad_vc
1530 .additional_properties
1531 .insert("http://example.org/foo".into(), "bar".into());
1532 for proof in &mut bad_vc.proofs {
1533 if let Some(eip712) = proof.options.eip712_mut() {
1536 if let Some(ssi_claims::data_integrity::suites::eip712::TypesOrURI::Object(types)) =
1537 &mut eip712.types
1538 {
1539 let vc_schema = types.types.get_mut("VerifiableCredential").unwrap();
1540 vc_schema.push(ssi_eip712::MemberVariable::new(
1541 "http://example.org/foo".to_owned(),
1542 ssi_eip712::TypeRef::String,
1543 ));
1544 }
1545 }
1546
1547 if let Some(eip712) = proof.options.eip712_v0_1_mut() {
1549 if let Some(ssi_claims::data_integrity::suites::eip712::TypesOrURI::Object(types)) =
1550 &mut eip712.message_schema
1551 {
1552 let vc_schema = types.types.get_mut("VerifiableCredential").unwrap();
1553 vc_schema.push(ssi_eip712::MemberVariable::new(
1554 "http://example.org/foo".to_owned(),
1555 ssi_eip712::TypeRef::String,
1556 ));
1557 }
1558 }
1559 }
1560
1561 let verification_result = bad_vc.verify(verifier).await.unwrap();
1562 assert!(verification_result.is_err());
1563 }
1564
1565 #[tokio::test]
1566 async fn verify_vc() {
1567 test_verify_vc("vc-tz1", include_str!("../tests/vc-tz1.jsonld"), 0).await;
1569 test_verify_vc(
1570 "vc-tz1-jcs.jsonld",
1571 include_str!("../tests/vc-tz1-jcs.jsonld"),
1572 1,
1573 )
1574 .await;
1575 test_verify_vc(
1584 "vc-eth-eip712vm",
1585 include_str!("../tests/vc-eth-eip712vm.jsonld"),
1586 0,
1587 )
1588 .await;
1589 test_verify_vc(
1590 "vc-eth-epsig",
1591 include_str!("../tests/vc-eth-epsig.jsonld"),
1592 0,
1593 )
1594 .await;
1595 test_verify_vc(
1596 "vc-celo-epsig",
1597 include_str!("../tests/vc-celo-epsig.jsonld"),
1598 0,
1599 )
1600 .await;
1601 test_verify_vc(
1602 "vc-poly-epsig",
1603 include_str!("../tests/vc-poly-epsig.jsonld"),
1604 0,
1605 )
1606 .await;
1607 test_verify_vc(
1608 "vc-poly-eip712sig",
1609 include_str!("../tests/vc-poly-eip712sig.jsonld"),
1610 0,
1611 )
1612 .await;
1613 }
1614}