did_pkh/
lib.rs

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 from_prefix(prefix: Prefix) -> Self {
36    //     match prefix {
37    //         Prefix::TZ1 | Prefix::KT1 => {
38    //             Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021
39    //         }
40    //         Prefix::TZ2 => VerificationMethodType::EcdsaSecp256k1RecoveryMethod2020,
41    //         Prefix::TZ3 => {
42    //             VerificationMethodType::P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021
43    //         }
44    //     }
45    // }
46
47    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
120// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-3.md
121const REFERENCE_EIP155_ETHEREUM_MAINNET: &str = "1";
122
123const REFERENCE_EIP155_CELO_MAINNET: &str = "42220";
124const REFERENCE_EIP155_POLYGON_MAINNET: &str = "137";
125
126// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md
127const REFERENCE_BIP122_BITCOIN_MAINNET: &str = "000000000019d6689c085ae165831e93";
128
129const REFERENCE_BIP122_DOGECOIN_MAINNET: &str = "1a91e3dace36e2be3bf030a65679fe82";
130
131// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-26.md
132const REFERENCE_TEZOS_MAINNET: &str = "NetXdQprcVkpaWU";
133
134// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-30.md
135const REFERENCE_SOLANA_MAINNET: &str = "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ";
136
137/// did:pkh DID Method
138///
139/// See: <https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md>
140pub 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        // Explanation of fragment differences:
231        //   https://github.com/spruceid/ssi/issues/297
232        "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            // Unknown network address: no prefix hash check
351        }
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    // Address data is decoded for validation only.
413    // The verification method object just uses the account address in blockchainAccountId.
414    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            // Non-CAIP-10 (deprecated)
497            "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            // CAIP-10
505            _ => {
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(&params.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(&params.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    // Require name to be a either CAIP-2 namespace or a
676    // full CAIP-2 string - namespace and reference (e.g. internal
677    // chain id or genesis hash).
678    // If reference is not provided, default to a known mainnet.
679    // If a reference is provided, pass it through.
680    // Return a CAIP-10 string, appended to "did:pkh:".
681    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            // Aliases for did:pkh pre-CAIP-10. Deprecate?
728            #[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            // CAIP-10/CAIP-2 chain id
742            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        // CAIP-10-based
839        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        // non-CAIP-10 (deprecated)
891        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        // use ssi_vc::{Credential, Issuer, LinkedDataProofOptions, URI};
973        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                // eip712_v0_1: eip712_domain_opt.clone().map(Into::into),
997                ..Default::default()
998            }
999            .with_public_key(key.to_public())
1000            .unwrap(),
1001        );
1002        eprintln!("vm {:?}", issue_options.verification_method);
1003        /*
1004        let proof = vc.generate_proof(&key, &issue_options).await.unwrap();
1005        */
1006        // Sign with proof suite directly because there is not currently a way to do it
1007        // for Eip712Signature2021 in did-pkh otherwise.
1008        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(&params).await.unwrap().is_ok());
1018
1019        // Test that issuer property is used for verification.
1020        let mut vc_bad_issuer = vc.clone();
1021        vc_bad_issuer.issuer = uri!("did:pkh:example:bad").to_owned().into();
1022
1023        // It should fail.
1024        assert!(vc_bad_issuer.verify(&params).await.unwrap().is_err());
1025
1026        // Check that proof JWK must match proof verificationMethod
1027        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(&params).await.unwrap().is_err());
1033
1034        // Mess with proof signature to make verify fail.
1035        let mut vc_fuzzed = vc.clone();
1036        vc_fuzzed.proofs.first_mut().unwrap().signature.alter();
1037        let vc_fuzzed_result = vc_fuzzed.verify(&params).await;
1038        assert!(vc_fuzzed_result.is_err() || vc_fuzzed_result.is_ok_and(|v| v.is_err()));
1039
1040        // Make it into a VP.
1041        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(&params).await.unwrap().is_ok());
1068
1069        // Mess with proof signature to make verify fail.
1070        let mut vp_fuzzed = vp.clone();
1071        vp_fuzzed.proofs.first_mut().unwrap().signature.alter();
1072        let vp_fuzzed_result = vp_fuzzed.verify(&params).await;
1073        assert!(vp_fuzzed_result.is_err() || vp_fuzzed_result.is_ok_and(|v| v.is_err()));
1074
1075        // Test that holder is verified.
1076        let mut vp_bad_holder = vp.clone();
1077        vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned());
1078
1079        // It should fail.
1080        assert!(vp_bad_holder.verify(&params).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        // test that issuer property is used for verification
1143        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        // Check that proof JWK must match proof verificationMethod.
1148        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        // Mess with proof signature to make verify fail
1156        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        // Make it into a VP
1162        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        // Mess with proof signature to make verify fail.
1184        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        // Test that holder is verified.
1190        let mut vp_bad_holder = vp.clone();
1191        vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned());
1192        // It should fail.
1193        assert!(vp_bad_holder.verify(&verifier).await.unwrap().is_err());
1194    }
1195
1196    // fn sign_tezos(prep: &ssi_ldp::ProofPreparation, algorithm: Algorithm, key: &JWK) -> String {
1197    //     // Simulate signing with a Tezos wallet
1198    //     let micheline = match prep.signing_input {
1199    //         ssi_ldp::SigningInput::Micheline { ref micheline } => hex::decode(micheline).unwrap(),
1200    //         _ => panic!("Expected Micheline expression for signing"),
1201    //     };
1202    //     ssi_tzkey::sign_tezos(&micheline, algorithm, key).unwrap()
1203    // }
1204
1205    #[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        // eth/Recovery2020
1243        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        // eth/Eip712
1255        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        // eth/epsig
1267        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        // eth/Eip712
1279        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        // eth/Eip712
1375        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        // TODO
1403        // println!("did:pkh:tz:tz2");
1404        // credential_prove_verify_did_pkh(
1405        //     key_secp256k1_recovery.clone(),
1406        //     other_key_secp256k1.clone(),
1407        //     "tz",
1408        //     "#blockchainAccountId",
1409        //     &ssi_ldp::EcdsaSecp256k1RecoverySignature2020,
1410        // )
1411        // .await;
1412
1413        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        /*
1442        println!("did:pkh:sol - SolanaMethod2021");
1443        credential_prove_verify_did_pkh(
1444            key_ed25519.clone(),
1445            other_key_ed25519.clone(),
1446            "sol",
1447            "#SolanaMethod2021",
1448            &ssi_ldp::SolanaSignature2021,
1449        )
1450        .await;
1451        */
1452
1453        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        /* https://github.com/spruceid/ssi/issues/194
1492        println!("did:pkh:tz:tz2 - TezosMethod2021");
1493        credential_prepare_complete_verify_did_pkh_tz(
1494            Algorithm::ESBlake2bK,
1495            key_secp256k1_recovery.clone(),
1496            other_key_secp256k1.clone(),
1497            "tz",
1498            "#TezosMethod2021",
1499            &ssi_ldp::TezosSignature2021,
1500        )
1501        .await;
1502        */
1503
1504        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        // TODO check warnings maybe?
1521        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        // // assert_eq!(verification_result.warnings.len(), num_warnings); // TODO warnings
1532
1533        // Negative test: tamper with the VC and watch verification fail.
1534        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            // Add the `foo` field to the EIP712 VC schema if necessary.
1540            // This is required so hashing can succeed.
1541            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            // Same as above but for the legacy EIP712 cryptosuite (v0.1).
1554            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        // TODO: update these to use CAIP-10 did:pkh issuers
1574        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        // TODO: either remove or update this test that uses an older version of
1582        // the `EthereumEip712Signature2021` suite.
1583        // test_verify_vc(
1584        //     "vc-eth-eip712sig.jsonld",
1585        //     include_str!("../tests/vc-eth-eip712sig.jsonld"),
1586        //     0,
1587        // )
1588        // .await;
1589        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}