generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
314 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use crate::resolver::{DidResolutionResult, DidResolver}; | ||
use async_trait::async_trait; | ||
use crypto::key_manager::KeyManager; | ||
use std::sync::Arc; | ||
|
||
/// Trait that defines all common behavior for a DID. | ||
#[async_trait] | ||
pub trait Did { | ||
/// Returns the DID URI the target [`Did`] represents (e.g: `did:jwk:12345`). | ||
fn uri(&self) -> &str; | ||
|
||
/// Returns a reference to the [`KeyManager`] that contains all the keys necessary to | ||
/// manage and sign using the target [`Did`]. | ||
fn key_manager(&self) -> &Arc<dyn KeyManager>; | ||
|
||
/// Returns a [`DidResolutionResult`] for the target [`Did`], as specified in | ||
/// [Resolving a DID](https://w3c-ccg.github.io/did-resolution/#resolving). | ||
async fn resolve(&self) -> DidResolutionResult { | ||
DidResolver::resolve_uri(self.uri()).await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,3 @@ | ||
pub fn add(left: usize, right: usize) -> usize { | ||
left + right | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn it_works() { | ||
let result = add(2, 2); | ||
assert_eq!(result, 4); | ||
} | ||
} | ||
pub mod did; | ||
pub mod method; | ||
pub mod resolver; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use crate::did::Did; | ||
use crate::method::{DidMethod, DidMethodError}; | ||
use crate::resolver::DidResolutionResult; | ||
use async_trait::async_trait; | ||
use crypto::key::{Key, KeyType}; | ||
use crypto::key_manager::KeyManager; | ||
use did_jwk::DIDJWK as SpruceDidJwkMethod; | ||
use ssi_dids::did_resolve::{DIDResolver, ResolutionInputMetadata}; | ||
use ssi_dids::{DIDMethod, Source}; | ||
use std::sync::Arc; | ||
|
||
/// Concrete implementation for a did:jwk DID | ||
pub struct DidJwk { | ||
uri: String, | ||
key_manager: Arc<dyn KeyManager>, | ||
} | ||
|
||
impl Did for DidJwk { | ||
fn uri(&self) -> &str { | ||
&self.uri | ||
} | ||
|
||
fn key_manager(&self) -> &Arc<dyn KeyManager> { | ||
&self.key_manager | ||
} | ||
} | ||
|
||
/// Options that can be used to create a did:jwk DID | ||
pub struct DidJwkCreateOptions { | ||
pub key_type: KeyType, | ||
} | ||
|
||
#[async_trait] | ||
impl DidMethod<DidJwk, DidJwkCreateOptions> for DidJwk { | ||
const NAME: &'static str = "jwk"; | ||
|
||
fn create( | ||
key_manager: Arc<dyn KeyManager>, | ||
options: DidJwkCreateOptions, | ||
) -> Result<DidJwk, DidMethodError> { | ||
let key_alias = key_manager.generate_private_key(options.key_type)?; | ||
let public_key = | ||
key_manager | ||
.get_public_key(&key_alias)? | ||
.ok_or(DidMethodError::DidCreationFailure( | ||
"PublicKey not found".to_string(), | ||
))?; | ||
|
||
let uri = SpruceDidJwkMethod | ||
.generate(&Source::Key(&public_key.jwk())) | ||
.ok_or(DidMethodError::DidCreationFailure( | ||
"Failed to generate did:jwk".to_string(), | ||
))?; | ||
|
||
Ok(DidJwk { uri, key_manager }) | ||
} | ||
|
||
async fn resolve_uri(did_uri: &str) -> DidResolutionResult { | ||
let input_metadata = ResolutionInputMetadata::default(); | ||
let (did_resolution_metadata, did_document, did_document_metadata) = | ||
SpruceDidJwkMethod.resolve(did_uri, &input_metadata).await; | ||
|
||
DidResolutionResult { | ||
did_resolution_metadata, | ||
did_document, | ||
did_document_metadata, | ||
..Default::default() | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use ssi_dids::did_resolve::ERROR_INVALID_DID; | ||
|
||
fn create_did_jwk() -> DidJwk { | ||
let key_manager = Arc::new(crypto::key_manager::LocalKeyManager::new_in_memory()); | ||
let options = DidJwkCreateOptions { | ||
key_type: KeyType::Ed25519, | ||
}; | ||
|
||
DidJwk::create(key_manager, options).unwrap() | ||
} | ||
|
||
#[test] | ||
fn create_produces_correct_uri() { | ||
let did = create_did_jwk(); | ||
assert!(did.uri.starts_with("did:jwk:")); | ||
} | ||
|
||
#[tokio::test] | ||
async fn instance_resolve() { | ||
let did = create_did_jwk(); | ||
let result = did.resolve().await; | ||
assert!(result.did_resolution_metadata.error.is_none()); | ||
|
||
let did_document = result.did_document.unwrap(); | ||
assert_eq!(did_document.id, did.uri); | ||
} | ||
|
||
#[tokio::test] | ||
async fn resolve_uri_success() { | ||
let did = create_did_jwk(); | ||
let result = DidJwk::resolve_uri(&did.uri).await; | ||
assert!(result.did_resolution_metadata.error.is_none()); | ||
|
||
let did_document = result.did_document.unwrap(); | ||
assert_eq!(did_document.id, did.uri); | ||
} | ||
|
||
#[tokio::test] | ||
async fn resolve_uri_failure() { | ||
let result = DidJwk::resolve_uri("did:jwk:does-not-exist").await; | ||
assert_eq!( | ||
result.did_resolution_metadata.error, | ||
Some(ERROR_INVALID_DID.to_string()) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
pub mod jwk; | ||
|
||
use crate::did::Did; | ||
use crate::resolver::DidResolutionResult; | ||
use async_trait::async_trait; | ||
use crypto::key_manager::{KeyManager, KeyManagerError}; | ||
use std::sync::Arc; | ||
|
||
/// Errors that can occur when working with DID methods. | ||
#[derive(thiserror::Error, Debug)] | ||
pub enum DidMethodError { | ||
#[error(transparent)] | ||
KeyManagerError(#[from] KeyManagerError), | ||
#[error("Failure creating DID: {0}")] | ||
DidCreationFailure(String), | ||
} | ||
|
||
/// A trait with common behavior across all DID methods. | ||
#[async_trait] | ||
pub trait DidMethod<T: Did, CreateOptions> { | ||
/// The name of the implemented DID method (e.g. `jwk`). | ||
/// | ||
/// This is used to identify the [`DidMethod`] responsible for creating/resolving an arbitrary | ||
/// DID URI. | ||
/// | ||
/// # Example | ||
/// If a consumer wants to resolve a DID URI of `did:jwk:12345`, the method portion of the URI | ||
/// (`jwk` in this example) is compared against each [`DidMethod`]'s `NAME` constant. If a match | ||
/// is found, the corresponding [`DidMethod`] is used to resolve the DID URI. | ||
const NAME: &'static str; | ||
|
||
/// Create a new DID instance. | ||
fn create( | ||
key_manager: Arc<dyn KeyManager>, | ||
options: CreateOptions, | ||
) -> Result<T, DidMethodError>; | ||
|
||
/// Resolve a DID URI to a [`DidResolutionResult`], as specified in | ||
/// [Resolving a DID](https://w3c-ccg.github.io/did-resolution/#resolving). | ||
async fn resolve_uri(did_uri: &str) -> DidResolutionResult; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use crate::method::jwk::DidJwk; | ||
use crate::method::DidMethod; | ||
use serde::{Deserialize, Serialize}; | ||
use ssi_dids::did_resolve::{ | ||
DocumentMetadata as DidDocumentMetadata, ResolutionMetadata as DidResolutionMetadata, | ||
}; | ||
use ssi_dids::Document as DidDocument; | ||
|
||
pub struct DidResolver; | ||
|
||
impl DidResolver { | ||
/// Resolves a DID URI, using the appropriate DID method, to a DID Document. | ||
pub async fn resolve_uri(did_uri: &str) -> DidResolutionResult { | ||
let method_name = match DidResolver::method_name(did_uri) { | ||
Some(method_name) => method_name, | ||
None => return DidResolutionResult::from_error(ERROR_INVALID_DID), | ||
}; | ||
|
||
match method_name { | ||
DidJwk::NAME => DidJwk::resolve_uri(did_uri).await, | ||
_ => return DidResolutionResult::from_error(ERROR_METHOD_NOT_SUPPORTED), | ||
} | ||
} | ||
|
||
/// Returns the method name of a DID URI, if the provided DID URI is valid, `None` otherwise. | ||
fn method_name(did_uri: &str) -> Option<&str> { | ||
let parts: Vec<&str> = did_uri.split(':').collect(); | ||
if parts.len() < 3 || parts[0] != "did" { | ||
return None; | ||
}; | ||
|
||
Some(parts[1]) | ||
} | ||
} | ||
|
||
/// Result of a DID resolution. | ||
/// | ||
/// See [Resolving a DID](https://w3c-ccg.github.io/did-resolution/#resolving) for more information | ||
/// about the resolution process, and documentation around expected results formats in the case | ||
/// there was an error resolving the DID. | ||
#[derive(Debug, Deserialize, Serialize)] | ||
pub struct DidResolutionResult { | ||
#[serde(rename = "@context")] | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub context: Option<String>, | ||
pub did_resolution_metadata: DidResolutionMetadata, | ||
pub did_document: Option<DidDocument>, | ||
pub did_document_metadata: Option<DidDocumentMetadata>, | ||
} | ||
|
||
const DID_RESOLUTION_V1_CONTEXT: &str = "https://w3id.org/did-resolution/v1"; | ||
const ERROR_METHOD_NOT_SUPPORTED: &str = "methodNotSupported"; | ||
const ERROR_INVALID_DID: &str = "invalidDid"; | ||
|
||
impl Default for DidResolutionResult { | ||
fn default() -> Self { | ||
Self { | ||
context: Some(DID_RESOLUTION_V1_CONTEXT.to_string()), | ||
did_resolution_metadata: DidResolutionMetadata::default(), | ||
did_document: None, | ||
did_document_metadata: None, | ||
} | ||
} | ||
} | ||
|
||
impl DidResolutionResult { | ||
/// Convenience method for creating a DidResolutionResult with an error. | ||
pub fn from_error(err: &str) -> Self { | ||
Self { | ||
did_resolution_metadata: DidResolutionMetadata::from_error(err), | ||
..Default::default() | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[tokio::test] | ||
async fn resolve_did_jwk() { | ||
let did_uri = "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9"; | ||
let result = DidResolver::resolve_uri(did_uri).await; | ||
assert!(result.did_resolution_metadata.error.is_none()); | ||
|
||
let did_document = result.did_document.unwrap(); | ||
assert_eq!(did_document.id, did_uri); | ||
} | ||
|
||
#[tokio::test] | ||
async fn resolve_invalid_did() { | ||
let did_uri = "did:jwk"; | ||
let result = DidResolver::resolve_uri(did_uri).await; | ||
assert_eq!( | ||
result.did_resolution_metadata.error, | ||
Some(ERROR_INVALID_DID.to_string()) | ||
); | ||
} | ||
|
||
#[tokio::test] | ||
async fn resolve_unsupported_method() { | ||
let did_uri = "did:unsupported:1234"; | ||
let result = DidResolver::resolve_uri(did_uri).await; | ||
assert_eq!( | ||
result.did_resolution_metadata.error, | ||
Some(ERROR_METHOD_NOT_SUPPORTED.to_string()) | ||
); | ||
} | ||
} |