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::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        // use ssi_vc::{Credential, Issuer, LinkedDataProofOptions, URI};
970        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                // eip712_v0_1: eip712_domain_opt.clone().map(Into::into),
994                ..Default::default()
995            }
996            .with_public_key(key.to_public())
997            .unwrap(),
998        );
999        eprintln!("vm {:?}", issue_options.verification_method);
1000        /*
1001        let proof = vc.generate_proof(&key, &issue_options).await.unwrap();
1002        */
1003        // Sign with proof suite directly because there is not currently a way to do it
1004        // for Eip712Signature2021 in did-pkh otherwise.
1005        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(&params).await.unwrap().is_ok());
1015
1016        // Test that issuer property is used for verification.
1017        let mut vc_bad_issuer = vc.clone();
1018        vc_bad_issuer.issuer = uri!("did:pkh:example:bad").to_owned().into();
1019
1020        // It should fail.
1021        assert!(vc_bad_issuer.verify(&params).await.unwrap().is_err());
1022
1023        // Check that proof JWK must match proof verificationMethod
1024        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(&params).await.unwrap().is_err());
1030
1031        // Mess with proof signature to make verify fail.
1032        let mut vc_fuzzed = vc.clone();
1033        vc_fuzzed.proofs.first_mut().unwrap().signature.alter();
1034        let vc_fuzzed_result = vc_fuzzed.verify(&params).await;
1035        assert!(vc_fuzzed_result.is_err() || vc_fuzzed_result.is_ok_and(|v| v.is_err()));
1036
1037        // Make it into a VP.
1038        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(&params).await.unwrap().is_ok());
1065
1066        // Mess with proof signature to make verify fail.
1067        let mut vp_fuzzed = vp.clone();
1068        vp_fuzzed.proofs.first_mut().unwrap().signature.alter();
1069        let vp_fuzzed_result = vp_fuzzed.verify(&params).await;
1070        assert!(vp_fuzzed_result.is_err() || vp_fuzzed_result.is_ok_and(|v| v.is_err()));
1071
1072        // Test that holder is verified.
1073        let mut vp_bad_holder = vp.clone();
1074        vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned().into());
1075
1076        // It should fail.
1077        assert!(vp_bad_holder.verify(&params).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        // test that issuer property is used for verification
1137        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        // Check that proof JWK must match proof verificationMethod.
1142        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        // Mess with proof signature to make verify fail
1150        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        // Make it into a VP
1156        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        // Mess with proof signature to make verify fail.
1178        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        // Test that holder is verified.
1184        let mut vp_bad_holder = vp.clone();
1185        vp_bad_holder.holder = Some(uri!("did:pkh:example:bad").to_owned().into());
1186        // It should fail.
1187        assert!(vp_bad_holder.verify(&verifier).await.unwrap().is_err());
1188    }
1189
1190    // fn sign_tezos(prep: &ssi_ldp::ProofPreparation, algorithm: Algorithm, key: &JWK) -> String {
1191    //     // Simulate signing with a Tezos wallet
1192    //     let micheline = match prep.signing_input {
1193    //         ssi_ldp::SigningInput::Micheline { ref micheline } => hex::decode(micheline).unwrap(),
1194    //         _ => panic!("Expected Micheline expression for signing"),
1195    //     };
1196    //     ssi_tzkey::sign_tezos(&micheline, algorithm, key).unwrap()
1197    // }
1198
1199    #[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        // eth/Recovery2020
1237        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        // eth/Eip712
1249        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        // eth/epsig
1261        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        // eth/Eip712
1273        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        // eth/Eip712
1369        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        // TODO
1397        // println!("did:pkh:tz:tz2");
1398        // credential_prove_verify_did_pkh(
1399        //     key_secp256k1_recovery.clone(),
1400        //     other_key_secp256k1.clone(),
1401        //     "tz",
1402        //     "#blockchainAccountId",
1403        //     &ssi_ldp::EcdsaSecp256k1RecoverySignature2020,
1404        // )
1405        // .await;
1406
1407        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        /*
1436        println!("did:pkh:sol - SolanaMethod2021");
1437        credential_prove_verify_did_pkh(
1438            key_ed25519.clone(),
1439            other_key_ed25519.clone(),
1440            "sol",
1441            "#SolanaMethod2021",
1442            &ssi_ldp::SolanaSignature2021,
1443        )
1444        .await;
1445        */
1446
1447        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        /* https://github.com/spruceid/ssi/issues/194
1486        println!("did:pkh:tz:tz2 - TezosMethod2021");
1487        credential_prepare_complete_verify_did_pkh_tz(
1488            Algorithm::ESBlake2bK,
1489            key_secp256k1_recovery.clone(),
1490            other_key_secp256k1.clone(),
1491            "tz",
1492            "#TezosMethod2021",
1493            &ssi_ldp::TezosSignature2021,
1494        )
1495        .await;
1496        */
1497
1498        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        // TODO check warnings maybe?
1515        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        // // assert_eq!(verification_result.warnings.len(), num_warnings); // TODO warnings
1526
1527        // Negative test: tamper with the VC and watch verification fail.
1528        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            // Add the `foo` field to the EIP712 VC schema if necessary.
1534            // This is required so hashing can succeed.
1535            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            // Same as above but for the legacy EIP712 cryptosuite (v0.1).
1548            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        // TODO: update these to use CAIP-10 did:pkh issuers
1568        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        // TODO: either remove or update this test that uses an older version of
1576        // the `EthereumEip712Signature2021` suite.
1577        // test_verify_vc(
1578        //     "vc-eth-eip712sig.jsonld",
1579        //     include_str!("../tests/vc-eth-eip712sig.jsonld"),
1580        //     0,
1581        // )
1582        // .await;
1583        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}