Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create and resolve did:jwk #20

Merged
merged 2 commits into from
Dec 4, 2023
Merged

Create and resolve did:jwk #20

merged 2 commits into from
Dec 4, 2023

Conversation

amika-sq
Copy link
Contributor

This is the first DID implementation within the crates/dids crate, so the PR is a little bit on the larger side. I promise the next DID implementations will be MUCH smaller 😅

The actual creation/resolution of the did:jwk itself is being handled by Spruce's did-jwk crate.

Beyond just create/resolve, this PR sets up the following structure:

  • Define traits for Did and DidMethod. This will allow us to implement other DID methods in the future.
  • Defines our own unique DidResolutionResult. Our upcoming test vectors expect a resolution result in a specific format. Spruce's resolution result doesn't match that format, so I defined our own that does.

Resolves #13

Comment on lines +81 to +88
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);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently do not consume the test vector in this PR, as I wanted to keep it as small as I could. Going to be adding that in a follow-up PR to this. For now, I resolve the did:jwk as defined from the first vector here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, thanks for keeping things concise here

Comment on lines -11 to +13
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
serde_with = "3.4.0"
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the credentials and dids crate both now depend on serde crates. Moved them to the workspace Cargo.toml file so that their versions are always in sync with each other.

Ok(DidJwk { uri, key_manager })
}

async fn resolve_uri(did_uri: &str) -> DidResolutionResult {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of code within TBD that use the term "did." Depending on where you're looking, it could mean one of two things:

  1. The unique DID string
  2. A structured representation of a DID

In this PR, I chose to always refer to the unique DID string as did_uri to hopefully help clarity while reading the code.

Looking for feedback on this!
Do you love it? Do you hate it? Happy to change if there's any strong feelings here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with your decision. Which is to say, DID to me means (2) whereas (1) is a DID's uri.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, it's where we landed on the KT side (in most places).

assert!(did.uri.starts_with("did:jwk:"));
}

#[tokio::test]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why tokio?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume it has something to do with us actually testing an external network integration

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DidJwk resolution should not go over the network. Chad tells me that

The #[tokio::test] attribute is specific to the tokio runtime and is used to mark an asynchronous test function. It indicates that the following function is an asynchronous test that should be executed using the tokio runtime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andresuribe87 has it right.

Resolution is generic across all DID methods, and resolution CAN be an asynchronous operation for certain methods. So the DidMethod trait defines the resolve function as asynchronous.

While did:jwk does not resolve over the network, the function must be asynchronous to conform to the DidMethod trait.

We have a few options to test asynchronous functions:

  1. Write a normal, synchronous, test. Whenever you need to call an asynchronous function within the test, block the thread until the asynchronous function completes.
  2. Use tokio or async-std (amongst others).

Tokio is much more widely adopted, so I went with that. Happy to alter though if there's any strong opinions another way!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me 👍

Copy link
Contributor

@KendallWeihe KendallWeihe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! lgtm!

Copy link
Contributor

@andresuribe87 andresuribe87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, some question/suggestion from what we've seen in Kt.

crates/dids/src/did.rs Show resolved Hide resolved
fn key_manager(&self) -> &Arc<dyn KeyManager>;

async fn resolve(&self) -> DidResolutionResult {
DidResolver::resolve_uri(self.uri()).await
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only qualm with this is that consumers of the API have no way of specifying which HTTP client to use under the hood.

Comment on lines +49 to +53
let uri = SpruceDidJwkMethod
.generate(&Source::Key(&public_key.jwk()))
.ok_or(DidMethodError::DidCreationFailure(
"Failed to generate did:jwk".to_string(),
))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DidJwk is dead simple. What do you think about implementing it instead of using the spruce dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but its also a standard.

My thought is: why re-invent the wheel here?
The dependency is 3.38 KB according to crates.io, which seemed like a good tradeoff to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to note - the did:jwk spec is being moved to the IETF with some changes now (and more coming later). We will need to be agile with our implementation, so having more control is a benefit. For example, we'll soon need to add in optional did:dht resolution for jwk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can totally add any changes if/when needed! But right now, there's no difference. I'd be copy/pasting Spruce's implementation. If that's what we want, I can do it!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also puts us in a better position for supply chain security by minimizing deps.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also puts us in a better position for supply chain security by minimizing deps.

implementing did:jwk ourselves wouldn't put us in a position to drop spruce's sdk as a dependency

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless we have concerns about how did:jwk is currently implemented in spruce's sdk, i don't see why we'd implement it ourselves at the moment


/// Options that can be used to create a did:jwk DID
pub struct DidJwkCreateOptions {
pub key_type: KeyType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually found KeyType to be a bit confusing. Looking at the docs for KeyType didn't actually help.

Is KeyType meant to represent https://www.rfc-editor.org/rfc/rfc7518.html#section-6.1 ? Or is it meant to represent the name of the elliptic curve?

In kotlin, we defined the creation options as Algo and Curve, curious how/why this diverged?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had it as KeyAlgorithm, but was recommended to rename it to KeyType here: #17 (comment)

Each of the algorithms use the same curve in Kotlin, so I just didn't add them to the Rust version yet. Can add them later if/when we use them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an outsider, I like the clarity of KeyAlgorithm over KeyType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I'll make a new PR to change it back!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not key algorithms, they are types of keys. Algorithms are used with types of keys to do key operations (like sign/verify, encrypt/decrypt). We should not deviate from industry terms here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still confused. @decentralgabe can you share the URL for the industry term that you are referencing?

I was asking if KeyType is https://www.rfc-editor.org/rfc/rfc7518.html#section-6.1 (which is in turn defined in https://www.rfc-editor.org/rfc/rfc7517.html#section-4.1). For reference, definitions is below

The "kty" (key type) parameter identifies the cryptographic algorithm
family used with the key, such as "RSA" or "EC".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I was referring to the concept similar to kty https://www.rfc-editor.org/rfc/rfc7518.html#section-6.1

Ok(DidJwk { uri, key_manager })
}

async fn resolve_uri(did_uri: &str) -> DidResolutionResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, it's where we landed on the KT side (in most places).

crates/dids/src/did.rs Show resolved Hide resolved
fn uri(&self) -> &str;
fn key_manager(&self) -> &Arc<dyn KeyManager>;

async fn resolve(&self) -> DidResolutionResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think?

Suggested change
async fn resolve(&self) -> DidResolutionResult {
/// Returns a DidResolutionResult as specified in https://www.w3.org/TR/did-core/#did-resolution
async fn resolve(&self) -> DidResolutionResult {

options: CreateOptions,
) -> Result<T, DidMethodError>;

/// Resolve a DID URI to a DID Document.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Resolve a DID URI to a DID Document.
/// Resolve a DID URI to a DidResolutionResult.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update and submit a PR to the Kotlin side as well here.

Comment on lines +26 to +33
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])
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect this code to be inside the Did trait. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Did trait defines behavior for Did implementations. Each implementation is already defining the name it's implementing. This function is computing the method name from some arbitrarily provided string.

We could definitely move this into a utils module or something similar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The answer is never "utils"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, I agree. Just suggesting if we wanted to move it anywhere else, it doesn't really have a good home at the moment, because this is the only struct that deals with resolving arbitrary strings.

crates/dids/src/method/jwk.rs Show resolved Hide resolved
Copy link
Contributor

@andresuribe87 andresuribe87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of my comments are blocking, but I think there's good conversations that we need to figure out.

@amika-sq amika-sq merged commit 7e12405 into main Dec 4, 2023
@amika-sq amika-sq deleted the did-jwk-finally branch December 4, 2023 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement did:jwk creation and resolution
5 participants