-
Notifications
You must be signed in to change notification settings - Fork 90
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
Generalise CredentialValidator
, PresentationValidator
to support arbitrary DID Documents
#935
Conversation
…feat/generalise-validators
CredentialValidator
, PresentationValidator
to support arbitrary DID DocumentsCredentialValidator
, PresentationValidator
to support arbitrary DID Documents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the approach in general.
I'm mainly wondering about the need for generic parameters which are practically always (?) Object
.
In regards to the open questions:
1, 3, 4: Not sure if we can answer these questions well now. Can't we add them on-demand, when we encounter the need? In general they sound useful, though.
2: Didn't have time to look into this more yet, apologies.
5: Yes, I think we should use D: ValidatorDocument
wherever possible.
7: Yeah seems fine to leave it as-is for now. The first option would be my preference, I think.
fn id(&self) -> &Self::D; | ||
|
||
/// Returns the first [`Service`] with an `id` property matching the provided `query`, if present. | ||
fn resolve_service<'query, 'me, Q>(&'me self, query: Q) -> Option<&Service<Self::D, Self::V>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there actually a use case where we or our users use a generic parameter other than Object
for Service
or VerificationMethod
? It seems Iota{Service, Document, VerificationMethod}
all work fine for us with Object
being fixed, so I'm wondering whether the core types need to be that flexible and in turn this trait and everything building on top.
I suppose if we want to implement option 3 from your proposed serde tagged enum approach for different service types, then we would likely want it, at least for Service
. But if we don't go with that, I'm wondering whether the complexity that this generates in the Document
trait and elsewhere is warranted.
This would also "address" open question 6.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was the previous design direction and makes it convenient to add extra fields to Service<D, U>
or VerificationMethod<D, U>
by injecting an arbitrary struct for U
, and convert between them cheaply without duplicating field definitions.
Note that even if we eliminate U
we still rely on the D: DID
generic parameter everywhere, though we might want to reconsider it and go with CoreDID
to enable storing identifiers from other methods in our structs for whatever reason. I still don't really comprehend why the fully-qualified DID Url is required for the id
fields instead of just a fragment in the specification.
The tagged enum approach still has its drawbacks, I would actually consider a Service
trait to be a potentially better avenue now.
Specifically regarding open question 6: I would be fine removing T
from Credential<T>
and Presentation<T>
, but we need to come up with a canonical way for extending credentials and presentation contents easily.
Overall this is just something to consider during the bigger refactors, will leave the generics as-is for now.
I think it would be good to decide so we can utilise it for generalising the Wasm bindings, if we go with interface/trait-based approaches there. Perhaps this is better discussed when it becomes necessary though. |
Please fix doc generation on this branch before merging.
|
@eike-hass what is needed to merge #925 to catch broken documentation links in CI? Edit: ah, Rust docs not wiki documentation. We should probably add that to CI too then. Edit: opened #937 to catch this in the future. |
The crate level error in
With the changes brought here this is no longer correct. Hence the documentation either needs to be reworded, or the error needs to be updated to include variants that wrap the various validation errors, or alternatively one can remove the crate level error. I am fine with either option as long as something gets done to avoid misleading documentation. |
With regards to the open questions I would agree with @PhilippGackstatter that it would be preferable to wait with answering 1,3,4. With regards to 2. I would say yes that would be useful, but would not go with a blanket implementation. In fact if we could get away with using a I guess 5. is less relevant now (after re-introducing generic parameters in the validation methods)? Number 6. can be left as is for now and 7. can probably be left for later. |
That would mean each DID Document method would have to implement |
I would also prefer to have just one trait, at least eventually. But considering this is the first attempt at such an abstraction, I find it hard to judge where we might go with the trait or which approach is really better. Going with both for now might allow for easier exploration for which approach is better suited, so I'm still fine with merging it as-is. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, thanks for the changes.
The Document
trait introduced here will almost certainly need to be revisited before the next release, but that is OK.
Description of change
Abstracts the
CredentialValidator
andPresentationValidator
interfaces to support any DID Document that implements theDocument
trait (added by this PR).Added
Document
trait.ValidatorDocument
trait.CredentialValidator::extract_issuer
.PresentationValidator::extract_holder
.Document::resolve_service
.IotaDocument::(un)revoke_credentials
logic toCoreDocument
.Changed
CredentialValidator
,PresentationValidator
function parameters fromAsRef<IotaDocument>
toValidatorDocument
.CredentialValidator
,PresentationValidator
fromidentity_iota_client
toidentity_credential
under a newvalidator
feature.(un)revoke_credentials
parameter from&str
toInto<DIDUrlQuery>
.Note that using
Document
andValidatorDocument
meanResolvedIotaDocument
no longer works as an input parameter; it previously worked withAsRef<IotaDocument>
.Approach Explanation
The abstraction is achieved through dynamic dispatch, where concrete
Document
implementations are cast to a sealed, object-safe traitValidatorDocument
. This is done to work around limitations of Rust when it comes to collections of different structs, which we use in several function parameters for issuer DID Documents.E.g.
is now
The issuers of credentials in a presentation could all use different DID Methods (
did:iota
,did:key
, etc.), which we want to support. Monomorphisation means we cannot useD: Document
, sincetrusted_issuers: &[D]
would be a slice of the same concrete type. Using dynamic dispatch withtrusted_issuers: &[&dyn Document]
ortrusted_issuers: &[Box<dyn Document>]
is not possible because the trait needs to be object-safe.Document
itself cannot be made object-safe (without significant loss of functionality) due to generic type parameters in structs likeService<D,U>
, andVerificationMethod<D, V>
. This is why theValidatorDocument
trait is introduced, to provide an object-safe wrapper trait with a blanket implementation forDocument
. Note that associated types are used forDocument
rather than generics, otherwise the compiler complains about unconstrained type parameters in the blanket implementation.Another approach would have been to use
CoreDocument
, but even that has limitations due to generics. I.e. all DID Documents would need to be converted toCoreDocument<CoreDID, Object, Object, Object>
. That would be a less ergonomic interface and likely have more overhead due to the conversion toCoreDocument
require transforming the types with.map
or going throughserde
.Unfortunately, the
Document/ValidatorDocument
approach chosen still requires some work when passing in collections due to explicit casts to&dyn Document
being necessary. E.g.This can be slightly shortened with the provided
ValidatorDocument::as_validator
function:Not ideal but I rate this is still better, ergonomically and performance-wise, than converting to
CoreDocument
. To be clear: I would greatly welcome any alternative that could eliminate this clunkiness.Open Questions
Document
be extended with more functions, like accessing method or service collections directly (service()
,authentication()
, and so on)? This would make it more difficult for downstream implementers not building onCoreDocument
and its underlying structs, likeOrderedSet
. But the methods returnService<D, U>
andVerificationMethod<D, V>
, so it would be very difficult if not usingCoreDocument
anyway.sign_data
, andverify_data
out ofDocument
into one or more separate traits? E.g.SignerDocument
,VerifierDocument
? Named to avoid existingSigner
andVerifier
traits inidentity_core
. We could make a blanket implementation forVerifierDocument
usingDocument
, but that may make custom signature schemes more difficult to support in the future.DocumentMut
trait for methods likeid_mut
,controller_mut
,insert_method
,insert_service
etc.?Document
for&T
,&mut T
,Box<T>
etc.?D: ValidatorDocument
in addition to&dyn ValidatorDocument
? The benefit is that developers can choose either static or dynamic dispatch but only with generic type parameter functions (D: ValidatorDocument
).Credential<T>
, since it means all credentials in aPresentation
must have the same properties typeT
(though one could always circumvent that restriction by deserializing toCredential<Object>
). I think it's fine to leave as-is for now since it has a relatively simple workaround.Document | ResolvedDocument
for now, since currently every relevant type is assumed to be specific to IOTA DID. The generalisation of the Wasm bindings is left for a future PR, but it's undecided what approach would be best there. My ideas are either:IotaDocument | KeyDocument | IotanxDocument
etc. and deserialising toCoreDocument
internally.Document
trait, thenDocument
can be implemented for saidTypescriptDocumentInterface
which would callresolve_service
etc. via Javascript. Unclear whether that performance impact would be worse than deserializing toCoreDocument
via JSON.Links to any relevant issues
Task of #908.
Type of change
How the change has been tested
Added new unit tests in
resolver.rs
, also the Rust compiler would shout at me if this didn't work.Tests passed with and without default-features, i.e.
Wasm bindings are mostly unchanged but all tests pass there too.
Change checklist