veramolabs / did-eth

evolution of did:ethr
Apache License 2.0
14 stars 8 forks source link

Make use of the diamond pattern (EIP-2535) #8

Open Hmac512 opened 1 year ago

Hmac512 commented 1 year ago

Don’t want to crowd the other issue with Diamonds talk.

https://eips.ethereum.org/EIPS/eip-2535

What I mean by Diamonds magic is that it’s possible to let anyone add a resolver module to the contract. You call a function in the contract with EVM byte code for the resolver logic, which is then registered with an identifier from a hash of the byte code.

Using the hash is important as the identifier will be the same across all deployments for the same resolver logic.

You can then use the hash identifier when interacting with your DIDs.

What remains is how to tie the resolver to a string like “did:eth”. This can be gated by governance.

It would then become possible to upgrade the did method while the previous version(s) remain available. You add a new resolver module, and point the method string at the new one.

There are a lot of details I’ve left out. Security and gas implications are the big questions.

Then did:keri isn’t a competitor, but can be used within our contract.

Originally posted by @Hmac512 in https://github.com/veramolabs/did-eth/issues/2#issuecomment-1336085105

Hmac512 commented 1 year ago

https://github.com/ethereum/EIPs/blob/45ec3cd319d20e772104aedbb42f3f103dea05d5/EIPS/eip-2535.md?plain=1#L28

Oh man, i completely forgot this part of the spec until I read over it again. There might be a way to get did:ethr into our new contract. Not super hopeful about this route, although someone should look into what that would look like.

fermentfan commented 1 year ago

How would management of a DID work with this pattern? Would you call everything through the central contract? Especially considering we want to implement meta transactions (probably EIP-712? With its domain objects tied to a contract address).

lleifermann commented 1 year ago

How would management of a DID work with this pattern? Would you call everything through the central contract? Especially considering we want to implement meta transactions (probably EIP-712? With its domain objects tied to a contract address).

As far as I can tell we would probably have a dao managed diamond contract with each of its facets working as functions. I kinda imagine it like a traditional program with a main entry class and other sub classes + functions in them.

We could build some facets just dedicated for EIP712 stuff and others just for determining what did a transaction wants to reference. The diamond pattern would then allow us to just update and add individual functions just as we do with traditional software. (Oh the authorization service is bugged? Let's fix the canActivate() method...)

strumswell commented 1 year ago

[...] let anyone add a resolver module to the contract. You call a function in the contract with EVM byte code for the resolver logic, [...] It would then become possible to upgrade the did method while the previous version(s) remain available. You add a new resolver module, and point the method string at the new one. [...] Then did:keri isn’t a competitor, but can be used within our contract.

I am still trying to fully understand the implications of the things you're describing here. I think your idea has two implications:

Can you clarify those things?

Hmac512 commented 1 year ago

How would management of a DID work with this pattern? Would you call everything through the central contract? Especially considering we want to implement meta transactions (probably EIP-712? With its domain objects tied to a contract address).

Here is an example production diamond contract with MetaTX support in many different Facets.

Execute MetaTx: https://github.com/aavegotchi/aavegotchi-contracts/blob/a9fbff896f7c0cb701f422fe0fb891d553262652/contracts/Aavegotchi/facets/MetaTransactionsFacet.sol#L72

Resolve correct sender: https://github.com/aavegotchi/aavegotchi-contracts/blob/a9fbff896f7c0cb701f422fe0fb891d553262652/contracts/shared/libraries/LibMeta.sol#L26

Resolved sender in separate facet: https://github.com/aavegotchi/aavegotchi-contracts/blob/a9fbff896f7c0cb701f422fe0fb891d553262652/contracts/Ethereum/facets/AavegotchiFacet.sol#L103

It seems they added the function signature as part of the metatransaction. It’s a clever setup overall.

This is a complicated contract, still need to map out how a lot of it works.

Oh, and the guy who wrote it also wrote the EIP-2535 spec.

Hmac512 commented 1 year ago

It seems they added the function signature as part of the metatransaction. It’s a clever setup overall.

For us, we would need to include the did method as well as the function signature(s) so the correct facet can be resolved.

Don’t have a concrete answer for how to set this up yet. It’ll be easier to figure out if we write some POC code.

Hmac512 commented 1 year ago

I am still trying to fully understand the implications of the things you're describing here. I think your idea has two implications: ...

Can you clarify those things?

Great questions.

  • We could have versioned resolvers. If we upgrade the logic of the/ a did method, we can still use the old facets (?) to resolve DIDs according to an old deployed version?

This is just an example to explain the idea.

Let's take did:eth as an example. We can start with this method being dedicated for secpk1 ethereum addresses. Vitalik authored the account abstraction which opens the door for different types of curves to be used behind an eth address (https://eips.ethereum.org/EIPS/eip-2938).

It's probably too early to build our resolver to support the account abstraction standard. If two years from now if EIP-2938 has enough adoption we may want to update how did:eth works. It is possible that we may have to introduce some changes that make the new method incompatible with the old method. We should be smart to try and prevent that from happening, but it remains a possibility.

The idea is we add a new resolver facet, and move over "did:eth" to the new facet. The old resolver is still available and maybe we can assign it "did:eth-old".

With the diamond pattern our contract doesn't have a size limit, so we can play this game of updating methods as many times as we need to. We can have as many resolvers as we like in this design.

  • On your comment with Keri, do you mean that this not only opens up such functionality for one DID method? Like in a sense of the contract housing potentially a multitude of different DID method that can be resolved through this one contract? Your Keri example almost sounds like wrapped tokens, e.g. WBTC.

Yeah, I overlooked that keri wraps around other did methods in my original comment. One simple solution is to have a separate resolver for each sub-method of keri.

Ex.

did:keri:eth did:keri:key ...

We can have these be separate resolvers, but they all work in the way Keri expects. The governance of this will be interesting.

Theoretically, we could make one giant resolver, but then it may need to be updated frequently to add support for sub-methods.

Does this clarify things? Let me know if you want me to explain more.

Hmac512 commented 1 year ago

It may actually be better is to follow the keri wrapping schema.

So we have:

did:eth:secpk1 did:eth:bls12381 ...

This way they don't need to be updated in the case of account abstraction. Methods still may need to be updated if there are gas optimizations that around found or something.

We should also make it clear that anyone building a smart contract should make the did method updatable. So they can execute a transaction to update the did method to an old version.


Also these design ideas may be premature. We should just build a POC with diamonds and the ability to register new resolvers and see if it's a design that makes sense.

The main reason I mentioned this design pattern is that it gives our contract a much longer time horizon. As SSI progresses, we can add functionality without needing a new contract.

Hmac512 commented 1 year ago

https://eip2535diamonds.substack.com/p/keep-your-data-right-in-eip2535-diamonds

This substack by Nick Mudge (Diamonds author) is really good. I'd recommend reading through it to better understand the concepts underlying Diamonds.

codynhat commented 1 year ago

https://github.com/ethereum/EIPs/blob/45ec3cd319d20e772104aedbb42f3f103dea05d5/EIPS/eip-2535.md?plain=1#L28

Oh man, i completely forgot this part of the spec until I read over it again. There might be a way to get did:ethr into our new contract. Not super hopeful about this route, although someone should look into what that would look like.

Just want to chime in here. I believe the existing did:ethr contract's code can be reused in another Diamond, but a newly deployed Diamond would have separate state. If the existing state of did:ethr wanted to still be used, there would still need to be another facet that explicitly uses the existing state of the did:ethr registry.

In other words, just pointing a new Diamond to the existing registry would only use its code, and not its state.

I've been working with Diamonds quite a bit the last few months and can help answer any questions people have.

italobb commented 1 year ago

There is a very important point regarding the upgradeability of a smart contract that is perhaps being overlooked in our context.

Upgradeability implies that:

That said, let's see the alternatives we have:

  1. Add upgradeability (current suggestion). In this case, just to take these arguments to a more concrete example, we would have a situation where no institution that deals with assets of considerable value would adopt this new DID method in their solution, because if there is a 0.000001% chance that a group of people can decide to update the DID method, it can't be trustable and considered fully decentralized.
  2. No upgradability (as did:ethr is today). Every once in a while, when we need some improvement, we'll have this group decide the evolution and roll out a new smart contract, which people may or may not adopt and that's fine. But we will always need to come up with a new DID method name (did:eth1, did:eth2, did:eth3, ...).
  3. Add upgradability, with stick version per DID. People could choose and stick to a defined version, updating it if they are compatible and trust the new version. Problem is that we would need to have the version informed somehow in the DID itself (did:eth1:...) or in the DID document (in the latter case we need to guarantee the part of the smart contract that establishes the version in use by a document DID will never be updated and we need to store DID document version attribute in blockchain state, not as events).
lleifermann commented 1 year ago

As far as i understand the core concepts of diamonds, in case of 'upgrades', old state and functionality would never be lost in that sense. If we would follow the existing smart contract design, where it's up to the resolvers to construct the did doc from events, the 'centralization' aspect of this could be avoided.

If some governance entity would make some ill advised upgrade, which may be seen as a regression from the community, resolvers may just choose to leverage the old facet and attached storage of the diamond. I believe with this pattern we could achieve 'true decentralization' where no entity can force some upgrade. Should the community choose to 'fork' the did method, they theoretically could to my understanding.

italobb commented 1 year ago

@lleifermann The EIP-2535 is something new to me. Thank you for explaining more. But I'm not yet understanding how it would happen in reality. From your explanation, I'm assuming my DID document would need a reference to the current version of the EIP-2535 smart contract deployed, in order to get sticked to the DID method version I trust. Is that the way you see the implementation moving forward? Would it be possible to get sticked to a version without having a DID document persisted on chain (from its creation off-chain)?

Hmac512 commented 1 year ago

@lleifermann The EIP-2535 is something new to me. Thank you for explaining more. But I'm not yet understanding how it would happen in reality. From your explanation, I'm assuming my DID document would need a reference to the current version of the EIP-2535 smart contract deployed, in order to get sticked to the DID method version I trust. Is that the way you see the implementation moving forward? Would it be possible to get sticked to a version without having a DID document persisted on chain (from its creation off-chain)?

I would really recommend reading through articles in this substack by the 2535 author (https://eip2535diamonds.substack.com/p/diamond-upgrades). It will explain a lot of the core concepts.

This is a great point. I actually want to build a simple POC diamonds did:eth contract to illustrate how the resolver can be separated from the document storage securely. I'll wait until the holiday season is over though.

The main security problem (which I think you are hitting on) is what happens if I add a malicious resolver that resolves to a DID owned by someone else. Can I then make changes to that DID? Obviously we want to prevent this.

One way we can do this is to whitelist within the document storage which resolvers are allowed to gate write access to the document. For example, when you make a DID with the did:eth resolver, you can specify that only that particular resolver can make changes to the document.

If I add my did:malicious resolver that resolves to your DID (made with did:eth), it should fail because the document says only did:eth can make changes.

From here it is straight forward to upgrade the document to whitelist other resolvers should the DID owner want to.

Important Caveat

I am confident what I've described will be secure if the whilelisted resolvers are not malicious. If Alice made a did:malicious resolver where Bob makes a DID, but Alice has a backdoor through the resolver to manage Bob's DID, I don't have a good answer on how to solve this problem.

I don't think we need to worry about this too much. You should only use resolvers where the code has been audited.

Would love to hear everyone's thoughts on this.

Hmac512 commented 1 year ago

We can call this bidirectional did resolution, or maybe resolver pinning.