1use async_trait::async_trait;
2use serde_json::Value;
3use std::collections::BTreeMap;
4
5use ssi_caips::caip10::BlockchainAccountId;
6use ssi_caips::caip2::ChainId;
7use ssi_dids::did_resolve::{
8 DIDResolver, DocumentMetadata, ResolutionInputMetadata, ResolutionMetadata, ERROR_INVALID_DID,
9 TYPE_DID_LD_JSON,
10};
11use ssi_dids::{
12 Context, Contexts, DIDMethod, Document, Source, VerificationMethod, VerificationMethodMap,
13 DEFAULT_CONTEXT, DIDURL,
14};
15use ssi_jwk::{Base64urlUInt, OctetParams, Params, JWK};
16
17const REFERENCE_SOLANA_MAINNET: &str = "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ";
19
20pub struct DIDSol;
22
23fn parse_did(did: &str) -> Option<(String, Vec<u8>)> {
24 let address = match did.split(':').collect::<Vec<&str>>().as_slice() {
25 ["did", "sol", address] => address.to_string(),
26 _ => return None,
27 };
28 let bytes = match bs58::decode(&address).into_vec() {
29 Ok(bytes) => bytes,
30 Err(_) => return None,
31 };
32 if bytes.len() != 32 {
33 return None;
34 }
35 Some((address, bytes))
36}
37
38#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
39#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
40impl DIDResolver for DIDSol {
41 async fn resolve(
42 &self,
43 did: &str,
44 _input_metadata: &ResolutionInputMetadata,
45 ) -> (
46 ResolutionMetadata,
47 Option<Document>,
48 Option<DocumentMetadata>,
49 ) {
50 let (address, public_key_bytes) = match parse_did(did) {
51 Some(parsed) => parsed,
52 None => {
53 return (
54 ResolutionMetadata::from_error(ERROR_INVALID_DID),
55 None,
56 None,
57 )
58 }
59 };
60 let mut context = BTreeMap::new();
61 context.insert(
62 "blockchainAccountId".to_string(),
63 Value::String("https://w3id.org/security#blockchainAccountId".to_string()),
64 );
65 context.insert(
66 "publicKeyJwk".to_string(),
67 serde_json::json!({
68 "@id": "https://w3id.org/security#publicKeyJwk",
69 "@type": "@json"
70 }),
71 );
72 context.insert(
73 "Ed25519VerificationKey2018".to_string(),
74 Value::String("https://w3id.org/security#Ed25519VerificationKey2018".to_string()),
75 );
76 context.insert(
77 "SolanaMethod2021".to_string(),
78 Value::String("https://w3id.org/security#SolanaMethod2021".to_string()),
79 );
80 let blockchain_account_id = BlockchainAccountId {
81 account_address: address,
82 chain_id: ChainId {
83 namespace: "solana".to_string(),
84 reference: REFERENCE_SOLANA_MAINNET.to_string(),
85 },
86 };
87 let vm_didurl = DIDURL {
88 did: did.to_string(),
89 fragment: Some("controller".to_string()),
90 ..Default::default()
91 };
92 let solvm_didurl = DIDURL {
93 did: did.to_string(),
94 fragment: Some("SolanaMethod2021".to_string()),
95 ..Default::default()
96 };
97 let pk_jwk = JWK {
98 params: Params::OKP(OctetParams {
99 curve: "Ed25519".to_string(),
100 public_key: Base64urlUInt(public_key_bytes),
101 private_key: None,
102 }),
103 public_key_use: None,
104 key_operations: None,
105 algorithm: None,
106 key_id: None,
107 x509_url: None,
108 x509_certificate_chain: None,
109 x509_thumbprint_sha1: None,
110 x509_thumbprint_sha256: None,
111 };
112 let vm = VerificationMethod::Map(VerificationMethodMap {
113 id: vm_didurl.to_string(),
114 type_: "Ed25519VerificationKey2018".to_string(),
115 public_key_jwk: Some(pk_jwk.clone()),
116 controller: did.to_string(),
117 blockchain_account_id: Some(blockchain_account_id.to_string()),
118 ..Default::default()
119 });
120 let solvm = VerificationMethod::Map(VerificationMethodMap {
121 id: solvm_didurl.to_string(),
122 type_: "SolanaMethod2021".to_string(),
123 public_key_jwk: Some(pk_jwk),
124 controller: did.to_string(),
125 blockchain_account_id: Some(blockchain_account_id.to_string()),
126 ..Default::default()
127 });
128
129 let doc = Document {
130 context: Contexts::Many(vec![
131 Context::URI(DEFAULT_CONTEXT.into()),
132 Context::Object(context),
133 ]),
134 id: did.to_string(),
135 authentication: Some(vec![
136 VerificationMethod::DIDURL(vm_didurl.clone()),
137 VerificationMethod::DIDURL(solvm_didurl.clone()),
138 ]),
139 assertion_method: Some(vec![
140 VerificationMethod::DIDURL(vm_didurl),
141 VerificationMethod::DIDURL(solvm_didurl),
142 ]),
143 verification_method: Some(vec![vm, solvm]),
145 ..Default::default()
146 };
147
148 let res_meta = ResolutionMetadata {
149 content_type: Some(TYPE_DID_LD_JSON.to_string()),
150 ..Default::default()
151 };
152
153 let doc_meta = DocumentMetadata {
154 ..Default::default()
155 };
156
157 (res_meta, Some(doc), Some(doc_meta))
158 }
159
160 fn to_did_method(&self) -> Option<&dyn DIDMethod> {
161 Some(self)
162 }
163}
164
165impl DIDMethod for DIDSol {
166 fn name(&self) -> &'static str {
167 "sol"
168 }
169
170 fn generate(&self, source: &Source) -> Option<String> {
171 let jwk = match source {
172 Source::Key(jwk) => jwk,
173 Source::KeyAndPattern(jwk, pattern) => {
174 if !pattern.is_empty() {
175 return None;
177 }
178 jwk
179 }
180 _ => return None,
181 };
182 let did = match jwk.params {
183 Params::OKP(ref params) if params.curve == "Ed25519" => {
184 let addr = bs58::encode(¶ms.public_key.0).into_string();
185 format!("did:sol:{}", addr)
186 }
187 _ => {
188 dbg!(&jwk.params);
189 return None;
190 }
191 };
192 Some(did)
193 }
194
195 fn to_resolver(&self) -> &dyn DIDResolver {
196 self
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use serde_json::json;
204 use ssi_dids::did_resolve::ResolutionInputMetadata;
205 use ssi_jwk::JWK;
206 use ssi_ldp::{ProofSuite, ProofSuiteType};
207
208 #[test]
209 fn key_to_did_sol() {
210 let jwk: JWK = serde_json::from_value(json!({
211 "kty": "OKP",
212 "crv": "Ed25519",
213 "x": "qDkywhH-S6nNxQhA6SHKsoFW7A2gX-X0b3TtwVBMHm8"
214 }))
215 .unwrap();
216 let did = DIDSol.generate(&Source::Key(&jwk)).unwrap();
217 assert_eq!(did, "did:sol:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev");
218 }
219
220 #[tokio::test]
221 async fn resolve_did_sol() {
222 let (res_meta, doc_opt, _meta_opt) = DIDSol
223 .resolve(
224 "did:sol:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev",
225 &ResolutionInputMetadata::default(),
226 )
227 .await;
228 assert_eq!(res_meta.error, None);
229 let doc = doc_opt.unwrap();
230 eprintln!("{}", serde_json::to_string_pretty(&doc).unwrap());
231 let doc_expected: Document =
232 serde_json::from_str(include_str!("../tests/did.jsonld")).unwrap();
233 assert_eq!(
234 serde_json::to_value(doc).unwrap(),
235 serde_json::to_value(doc_expected).unwrap()
236 );
237 }
238
239 #[tokio::test]
240 async fn credential_prove_verify_did_sol() {
241 eprintln!("with Ed25519VerificationKey2018...");
242 credential_prove_verify_did_sol_1(false).await;
243 eprintln!("with SolanaMethod2021...");
244 credential_prove_verify_did_sol_1(true).await;
245 }
246
247 async fn credential_prove_verify_did_sol_1(solvm: bool) {
248 use ssi_vc::{Credential, Issuer, LinkedDataProofOptions, URI};
249
250 let key: JWK = serde_json::from_value(json!({
251 "kty": "OKP",
252 "crv": "Ed25519",
253 "x": "YwT3Ce5YqSjXK_bmI35kPEmzZT2WtUSE6XTllrLUGbQ",
254 "d": "ybizk5eZVSZiUtWURVdp-dx9JWtiP9uJaWfLupGU2ZU"
255 }))
256 .unwrap();
257 let did = DIDSol.generate(&Source::Key(&key)).unwrap();
258 eprintln!("did: {}", did);
259 let mut vc: Credential = serde_json::from_value(json!({
260 "@context": "https://www.w3.org/2018/credentials/v1",
261 "type": "VerifiableCredential",
262 "issuer": did.clone(),
263 "issuanceDate": "2021-02-18T20:23:13Z",
264 "credentialSubject": {
265 "id": "did:example:foo"
266 }
267 }))
268 .unwrap();
269 vc.validate_unsigned().unwrap();
270 let mut issue_options = LinkedDataProofOptions::default();
271 if solvm {
272 issue_options.verification_method =
273 Some(URI::String(did.to_string() + "#SolanaMethod2021"));
274 } else {
275 issue_options.verification_method = Some(URI::String(did.to_string() + "#controller"));
276 }
277 eprintln!("vm {:?}", issue_options.verification_method);
278 let mut context_loader = ssi_json_ld::ContextLoader::default();
279 let vc_no_proof = vc.clone();
280 let proof = vc
281 .generate_proof(&key, &issue_options, &DIDSol, &mut context_loader)
282 .await
283 .unwrap();
284 println!("{}", serde_json::to_string_pretty(&proof).unwrap());
285 vc.add_proof(proof);
286 vc.validate().unwrap();
287 let verification_result = vc.verify(None, &DIDSol, &mut context_loader).await;
288 println!("{:#?}", verification_result);
289 assert!(verification_result.errors.is_empty());
290
291 let mut vc_bad_issuer = vc.clone();
293 vc_bad_issuer.issuer = Some(Issuer::URI(URI::String("did:example:bad".to_string())));
294 assert!(!vc_bad_issuer
295 .verify(None, &DIDSol, &mut context_loader)
296 .await
297 .errors
298 .is_empty());
299
300 let mut vc_wrong_key = vc_no_proof.clone();
302 let other_key = JWK::generate_ed25519().unwrap();
303 let proof_bad = ProofSuiteType::Ed25519BLAKE2BDigestSize20Base58CheckEncodedSignature2021
304 .sign(
305 &vc_no_proof,
306 &issue_options,
307 &DIDSol,
308 &mut context_loader,
309 &other_key,
310 None,
311 )
312 .await
313 .unwrap();
314 vc_wrong_key.add_proof(proof_bad);
315 vc_wrong_key.validate().unwrap();
316 assert!(!vc_wrong_key
317 .verify(None, &DIDSol, &mut context_loader)
318 .await
319 .errors
320 .is_empty());
321
322 use ssi_core::one_or_many::OneOrMany;
324 use ssi_vc::{CredentialOrJWT, Presentation, ProofPurpose, DEFAULT_CONTEXT};
325 let mut vp = Presentation {
326 context: ssi_vc::Contexts::Many(vec![ssi_vc::Context::URI(ssi_vc::URI::String(
327 DEFAULT_CONTEXT.to_string(),
328 ))]),
329
330 id: Some("http://example.org/presentations/3731".try_into().unwrap()),
331 type_: OneOrMany::One("VerifiablePresentation".to_string()),
332 verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(vc))),
333 proof: None,
334 holder: None,
335 property_set: None,
336 holder_binding: None,
337 };
338 let mut vp_issue_options = LinkedDataProofOptions::default();
339 vp.holder = Some(URI::String(did.to_string()));
340 vp_issue_options.verification_method = Some(URI::String(did.to_string() + "#controller"));
341 vp_issue_options.proof_purpose = Some(ProofPurpose::Authentication);
342 let mut context_loader = ssi_json_ld::ContextLoader::default();
343 let vp_proof = vp
344 .generate_proof(&key, &vp_issue_options, &DIDSol, &mut context_loader)
345 .await
346 .unwrap();
347 vp.add_proof(vp_proof);
348 println!("VP: {}", serde_json::to_string_pretty(&vp).unwrap());
349 vp.validate().unwrap();
350 let vp_verification_result = vp
351 .verify(Some(vp_issue_options.clone()), &DIDSol, &mut context_loader)
352 .await;
353 println!("{:#?}", vp_verification_result);
354 assert!(vp_verification_result.errors.is_empty());
355
356 let mut vp1 = vp.clone();
358 match vp1.proof {
359 Some(OneOrMany::One(ref mut proof)) => match proof.jws {
360 Some(ref mut jws) => {
361 jws.insert(0, 'x');
362 }
363 _ => unreachable!(),
364 },
365 _ => unreachable!(),
366 }
367 let vp_verification_result = vp1
368 .verify(Some(vp_issue_options), &DIDSol, &mut context_loader)
369 .await;
370 println!("{:#?}", vp_verification_result);
371 assert!(!vp_verification_result.errors.is_empty());
372
373 let mut vp2 = vp.clone();
375 vp2.holder = Some(URI::String("did:example:bad".to_string()));
376 assert!(!vp2
377 .verify(None, &DIDSol, &mut context_loader)
378 .await
379 .errors
380 .is_empty());
381 }
382
383 }