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

Add new crates for DID parsing, DID document building, and DID resolution #812

Merged
merged 24 commits into from
May 9, 2023

Conversation

mirgee
Copy link
Contributor

@mirgee mirgee commented Apr 24, 2023

This PR introduces new crates related to DID parsing, DID document building, and DID resolution, thus partially addressing issue #706 .

Some features in the added crates have no immediate use inside of AriesVCX, and we may consider moving some of these crates to a separate project, especially as the number of implemented resolvers begins to grow. However, their inclusion in the PR is due to the immediate need for a Sovrin DID method resolver, while striving to keep in line with following design goals:

For example DID resolver HTTP(S) binding implementation using these crates, see here.

The following crates have been added:

did_parser

This crate is responsible for parsing DID URLs and DIDs into easily queriable components, as well as DID (URL) validation. Includes ParsedDID and ParsedDIDUrl structs, which are used heavily throughout the remaining crates.

did_doc_builder

The did_doc_builder crate allows its users to construct, serialize and deserialize DID documents and their components that comply with the DID Core specification (https://www.w3.org/TR/did-core/).

did_resolver

This crate defines traits that should be implemented by DID resolvers of specific DID methods. For now, the traits are DIDResolvable and DIDDereferenceable.

did_resolver_registry

The did_resolver_registry is a simple crate that enables the registration of multiple DID resolvers for different DID methods at runtime and subsequent resolution of DIDs for methods with a registered resolver, thus enabling support for variable number of DID methods.

did_resolver_sov

This crate provides a DID resolver for the Sovrin DID method (https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html), and hence is the main candidate for possible use in AriesVCX.

Please review and let me know if any further changes are required.

@mirgee mirgee linked an issue Apr 24, 2023 that may be closed by this pull request
@mirgee mirgee force-pushed the feature/did-resolver branch 6 times, most recently from f0e43ed to e3e8986 Compare April 24, 2023 07:47
@mirgee mirgee changed the title Add new crates for DID parsing, document building, and DID resolution Add new crates for DID parsing, DID document building, and DID resolution Apr 24, 2023
@mirgee mirgee force-pushed the feature/did-resolver branch 3 times, most recently from 5b457ca to 07f6ac2 Compare April 24, 2023 18:50
@nicobao
Copy link

nicobao commented Apr 25, 2023

Really nice!
I wonder if there is any work planned to support non-ledger methods such as did:web?
The did-web rust crate written by Spruce could be reused.

@mirgee mirgee force-pushed the feature/did-resolver branch 2 times, most recently from d62e108 to 39b3451 Compare April 26, 2023 14:00
@Patrik-Stas
Copy link
Contributor

Patrik-Stas commented Apr 27, 2023

@nicobao most definitely we'll be adding support for more methods. Thanks for heads up about the spruce repo - I wasn't aware of it and it actually looks amazing 👍 💯 - so many did methods ready to tap into at the fingertips https://github.com/spruceid/ssi

The main concern imo would be all the dependencies and the code it would bring it, many of which would likely be unnecessary. I think we could help spruceid to feature flag the dependencies, so it would be possible to bring in only the deps for desired set of did methods.

@mirgee @bobozaur @gmulhearn check it out guys :-)

did_parser/src/parsed_did.rs Outdated Show resolved Hide resolved
did_parser/src/utils/parse.rs Outdated Show resolved Hide resolved
did_parser/src/utils/parse.rs Show resolved Hide resolved
@@ -432,6 +432,19 @@ jobs:
run: |
RUST_TEST_THREADS=1 cargo test --manifest-path="libvcx/Cargo.toml" -F "pool_tests";

test-integration-resolver:
Copy link
Contributor

@Patrik-Stas Patrik-Stas Apr 28, 2023

Choose a reason for hiding this comment

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

The name is not quite accurate right now, the command also currently run unit tests too. Either update the job name, or tweak the cargo cmd

did_parser/Cargo.toml Outdated Show resolved Hide resolved
did_parser/Cargo.toml Outdated Show resolved Hide resolved
did_parser/Cargo.toml Outdated Show resolved Hide resolved
did_parser/Cargo.toml Outdated Show resolved Hide resolved
Copy link
Contributor

@bobozaur bobozaur left a comment

Choose a reason for hiding this comment

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

I like the DID parser using ranges of the input DID string, great job!

To me it feels like a lot of places would need documentation and comments, like the structs and traits and the larger functions that are not really easy to follow. I'm honestly not able to grasp everything that's happening just from browsing through the code so I tried to start conversations where appropriate.

There are some minor things to adjust as far as idioms go, but nothing terrible. Another broader idea would be to incorporate some of the utility functions as associated methods, because it's easier to grasp what they are about if they belong to a namespace and most seem to have unique use cases.

Great job over all!

aries_vcx_core/Cargo.toml Outdated Show resolved Hide resolved
did_doc_builder/src/error.rs Outdated Show resolved Hide resolved
Comment on lines 89 to 91
pub fn extra(&self, key: &str) -> Option<&Value> {
self.extra.get(key)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This method is confusing to me. What if I wanted the whole extra field? I think it would be better to return that and consumers can lookup stuff in the map themselves.

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 way this limits the use of extra is intentional - you should never need the whole extra field. Moreover, the client code needs not and should not be aware of the internal data structure used for extra fields so that changing it for another collection is not a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Left in as is as discussed.

Copy link
Contributor

Choose a reason for hiding this comment

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

How about renaming it then? The method looks like a getter of the map field instead of a lookup into the map. I agree though that the inner field should not be exposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apologies, forgot about the suggestion to rename. What name do you suggest?

Copy link
Contributor

Choose a reason for hiding this comment

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

Something like lookup_extra_field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about get_extra_field instead of lookup_extra_field?

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 get is implicit. Maybe just extra_field?

did_doc_builder/src/schema/did_doc.rs Show resolved Hide resolved
did_doc_builder/src/schema/did_doc.rs Outdated Show resolved Hide resolved
did_resolver/src/traits/resolvable/resolution_error.rs Outdated Show resolved Hide resolved
did_resolver_registry/src/lib.rs Outdated Show resolved Hide resolved
did_resolver/src/traits/resolvable/mod.rs Outdated Show resolved Hide resolved
did_resolver/src/traits/dereferenceable/mod.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@Patrik-Stas Patrik-Stas left a comment

Choose a reason for hiding this comment

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

left some comments, still continuing in review

did_doc_builder/src/schema/did_doc.rs Show resolved Hide resolved
did_doc_builder/src/schema/service.rs Show resolved Hide resolved
did_doc_builder/src/schema/service.rs Show resolved Hide resolved
did_doc_builder/src/schema/types/multibase.rs Outdated Show resolved Hide resolved
did_resolver_sov/src/resolution/utils.rs Outdated Show resolved Hide resolved
did_resolver_sov/src/resolution/utils.rs Outdated Show resolved Hide resolved
let mut service_builder =
Service::builder(service_id, endpoint.endpoint.as_str().try_into()?)?;
for t in endpoint.types {
if t != DidSovServiceType::Unknown {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just a nice to have, and I toched on this elsewhere. Feel free to pass this, I think this could be nice good-first-issue.

If you leave this up for good-first-issue, please leave this comment un-resolved.

Although DID Core does not specify possible values of type, we could still make our code slightly developer friendlier making enum for types within our DidDocument structure. We could reuse DidSovServiceType - rename it toServiceType, move it to diddocument crate and tweak it a bit:

#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
pub enum ServiceType {
    #[serde(rename = "endpoint")] // AIP 1.0
    Endpoint,
    #[serde(rename = "did-communication")] // AIP 2.0
    DidCommunication,
    #[serde(rename = "DIDComm")] // DIDComm V2
    DIDComm,
    #[serde(other)]
    Unknown(value),
}

Just not sure about details how to get Unknown(value) such that the value parameter would get filled in.

Copy link
Contributor

Choose a reason for hiding this comment

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

This would require a custom impl for Serialize/Deserialize (or re-using the MaybeKnown enum?). The #[serde(other)] attribute only works with unit variants, unfortunately.

did_resolver_sov/src/resolution/utils.rs Outdated Show resolved Hide resolved
did_resolver_sov/src/resolution/utils.rs Outdated Show resolved Hide resolved
mirgee added 5 commits May 4, 2023 09:14
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
@mirgee mirgee force-pushed the feature/did-resolver branch from 11ac111 to c502bf9 Compare May 4, 2023 10:27
Copy link
Contributor

@Patrik-Stas Patrik-Stas left a comment

Choose a reason for hiding this comment

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

lgtm, can we move it from draft to ready state?

Patrik-Stas
Patrik-Stas previously approved these changes May 8, 2023
Copy link
Contributor

@Patrik-Stas Patrik-Stas left a comment

Choose a reason for hiding this comment

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

lgtm, can we move it from draft to ready state?

Copy link
Contributor

@bobozaur bobozaur left a comment

Choose a reason for hiding this comment

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

Most of the things left are minor things or personal opinions, so feel free to merely comment or ignore them, but I think the conversion between DidUrl and Did can really be optimized to avoid double-parsing.

did_doc_builder/src/schema/types/uri.rs Show resolved Hide resolved
did_parser/src/parsed_did.rs Outdated Show resolved Hide resolved
Comment on lines 77 to 82
Self::parse(
did_url
.did()
.ok_or(Self::Error::InvalidInput("No DID provided in the DID URL"))?
.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.

Shouldn't there be a better way to convert? The DID is already parsed, so it's just a matter of whether there are path arguments and stuff like that. If the DidUrl doesn't contain any of the additional stuff that makes it a did url, then the parts could be converted to a Did, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you suggesting to expose the fields of DidUrl for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Through pub(crate), yeah. Why not? Or simply a constructor method could be added that's crate internal, and the from impl could be near the ParsedDidUrl type so the fields can stay private.

Copy link
Contributor Author

@mirgee mirgee May 8, 2023

Choose a reason for hiding this comment

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

I think the overhead caused by this is so negligible it's not worth additional code or exposing the now private fields at all. But I will do as you wish.

Copy link
Contributor

Choose a reason for hiding this comment

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

The crate public constructor is probably the best way to go about it, so the fields aren't touched.

I think it's highly worth it. Why not optimize this when it's easy to do so? The difference between parsing and not parsing the string is huge in the number of executed instructions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, I don't think this is worth either changing the visibility of the fields or adding additional code. Keeping a struct's trait implementations in the same module it's defined in also makes the code navigable, orderly and well-organized.

And again, a call to parse_did_method_id has absolutely negligible performance cost.

But since you think it's highly worth it, I will change it according to your preference.

let mut service_builder =
Service::builder(service_id, endpoint.endpoint.as_str().try_into()?)?;
for t in endpoint.types {
if t != DidSovServiceType::Unknown {
Copy link
Contributor

Choose a reason for hiding this comment

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

This would require a custom impl for Serialize/Deserialize (or re-using the MaybeKnown enum?). The #[serde(other)] attribute only works with unit variants, unfortunately.

did_parser/src/parsed_did_url.rs Outdated Show resolved Hide resolved
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
Copy link
Contributor

@bobozaur bobozaur left a comment

Choose a reason for hiding this comment

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

Just a small comment regarding the TryFrom impl between DidUrl and Did

did_parser/src/parsed_did_url.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@bobozaur bobozaur left a comment

Choose a reason for hiding this comment

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

Can we also change the parsed_did and parsed_did_url module file names, for consistency?

Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
Patrik-Stas
Patrik-Stas previously approved these changes May 9, 2023
@Patrik-Stas
Copy link
Contributor

@mirgee please rerun the CI without skip-ci tag

Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
@codecov-commenter
Copy link

codecov-commenter commented May 9, 2023

Codecov Report

Merging #812 (594e161) into main (036bbf9) will increase coverage by 0.03%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##             main     #812      +/-   ##
==========================================
+ Coverage   49.06%   49.09%   +0.03%     
==========================================
  Files         410      410              
  Lines       33501    33518      +17     
  Branches     7440     7444       +4     
==========================================
+ Hits        16436    16455      +19     
- Misses      11949    11951       +2     
+ Partials     5116     5112       -4     
Flag Coverage Δ
unittests-aries-vcx 49.06% <ø> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
did_doc_builder/src/schema/types/mod.rs 50.00% <ø> (ø)
did_parser/src/did.rs 100.00% <ø> (ø)

... and 89 files with indirect coverage changes

Copy link
Contributor

@bobozaur bobozaur left a comment

Choose a reason for hiding this comment

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

If the CI runs, I'm okay with the PR.

@mirgee mirgee marked this pull request as ready for review May 9, 2023 11:21
@Patrik-Stas Patrik-Stas merged commit a7cc540 into main May 9, 2023
@Patrik-Stas Patrik-Stas deleted the feature/did-resolver branch May 9, 2023 14:08
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 DDO Resolver
5 participants