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 support for meta/signed transactions #164

Merged
merged 9 commits into from Sep 5, 2022

Conversation

lleifermann
Copy link
Contributor

This adds support for the EIP1056 "signed" functions for meta transactions. Enabling users of this dependencies to carry out transactions on behalf of someone else's wallet, as long as the signer of the transaction can prove he has access to the private key material of the current owner of the identity.

We've added tests for each meta transaction case actually resolving the did document afterwards. (Huge kudos to the whole test suite. It really enabled us to understand all of this fast.)

Some open questions we still have:

  • We added a helper function signMetaTxData to sign the data in the correct format for the tests to work. I think this is not normally something this repository really needs thats why we kept it in the dev deps. Is this OK?
  • Added an Interface MetaSignature to keep the signed method variants input parameters clean. Should this be something this package also exports for others to use?

@lleifermann
Copy link
Contributor Author

@mirceanis

Just FYI we are still discussing right now if this repository shouldn't export hashing functions for each of the signed methods. Like hashSetAttributeSigned as this is a very contract specific thing on how this has to be hashed.
(This would move the the ethers dev dep to a normal dep though)

TBH i think the best way would be if the contract actually exposed some functions and you let the hashing (before signing) happen via the contract. But as changing the contract is a rather hard way to pull off we think locating this as close as possible to the contract interaction itself might make sense.

This would also enable us in the future that the contract owns this logic. WDYT?

cc @strumswell @dennisvonderbey

Copy link
Member

@mirceanis mirceanis left a comment

Choose a reason for hiding this comment

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

Wonderful initiative here, thanks for contributing this.

Please see my notes about the dependencies, but especially about the nonce tracking and tell me what you think too.

package.json Outdated Show resolved Hide resolved
src/__tests__/resolver.test.ts Outdated Show resolved Hide resolved
src/helpers.ts Outdated Show resolved Hide resolved
dataBytes: Uint8Array,
didReg: Contract
) {
const nonce = await didReg.nonce(signerAddress)
Copy link
Member

Choose a reason for hiding this comment

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

This method should be tested against the existing deployments because there are differences between the older contract (0xdca7ef03e98e0dc2b855be647c39abe984fcf21b) and the latest one (for example 0x63eD58B671EeD12Bc1652845ba5b2CDfBff198e0) regarding nonce tracking.

See uport-project/ethr-did-registry#12

This issue in particular makes me want to move to a new contract.

Copy link
Contributor

Choose a reason for hiding this comment

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

So is this about staying compatible to different versions of the contract?

Copy link
Member

Choose a reason for hiding this comment

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

Yes. It's about compatibility.
The older contract, which is deployed to mainnet and ethereum testnets tracks the nonce as a property of the owner, and not as a property of the signer.
Most of the time these are the same address, until the owner of the DID is changed, at which point this becomes an issue.

src/helpers.ts Show resolved Hide resolved
@mirceanis
Copy link
Member

@mirceanis

Just FYI we are still discussing right now if this repository shouldn't export hashing functions for each of the signed methods. Like hashSetAttributeSigned as this is a very contract specific thing on how this has to be hashed. (This would move the the ethers dev dep to a normal dep though)

TBH i think the best way would be if the contract actually exposed some functions and you let the hashing (before signing) happen via the contract. But as changing the contract is a rather hard way to pull off we think locating this as close as possible to the contract interaction itself might make sense.

This would also enable us in the future that the contract owns this logic. WDYT?

cc @strumswell @dennisvonderbey

This could also be achieved using a wrapper contract that exposes some pure functions for hashing. But I'm not sure I understand what the difference would be between having the contract do the hashing or performing the hashing off-chain.
Is there something else than keccak256 natively available for hashing on EVM ? Or have I misunderstood your ask here?

@lleifermann
Copy link
Contributor Author

@mirceanis
Just FYI we are still discussing right now if this repository shouldn't export hashing functions for each of the signed methods. Like hashSetAttributeSigned as this is a very contract specific thing on how this has to be hashed. (This would move the the ethers dev dep to a normal dep though)
TBH i think the best way would be if the contract actually exposed some functions and you let the hashing (before signing) happen via the contract. But as changing the contract is a rather hard way to pull off we think locating this as close as possible to the contract interaction itself might make sense.
This would also enable us in the future that the contract owns this logic. WDYT?
cc @strumswell @dennisvonderbey

This could also be achieved using a wrapper contract that exposes some pure functions for hashing. But I'm not sure I understand what the difference would be between having the contract do the hashing or performing the hashing off-chain. Is there something else than keccak256 natively available for hashing on EVM ? Or have I misunderstood your ask here?

This is more of a code style and accessibility question than anything else to be honest. Our reasoning was that code which uses this dependency will then have to exactly do the same procedure of hashing as the contract does. Mirroring the code into upstream dependencies.

It's really a question about 'who should own how params are hashed' and having this as close to the contract interaction as possible made sense to us.

The contract has the requirement that hashing happens in some exact way so it's the direct owner of that requirement. Not having to worry about that in this or other dependencies would've been nice.

@mirceanis
Copy link
Member

This is more of a code style and accessibility question than anything else to be honest. Our reasoning was that code which uses this dependency will then have to exactly do the same procedure of hashing as the contract does. Mirroring the code into upstream dependencies.

It's really a question about 'who should own how params are hashed' and having this as close to the contract interaction as possible made sense to us.

I see what you mean.
The way I see this, the "signed" methods require a 2 step process, and both steps could be provided by this library.

Step 1, the DID controller calls some createDoSomethingRequest() method, where they are expected to generate the signature
Step 2, the request + signature is sent to the transaction signer, which calls the actual methods on the contract, or rather a method from EthrDidController (paying the ethereum transaction fee along the way).

Example:

  • DID owner calls createSetAttributeSignerRequest(identity, attrKey, attrValue, rawSigner), which returns an object like:
    { operation: setAttribute, identity: 0x address of the DID, attrKey: <32 byte string>, attrValue: , signature: MetaSignature }
  • controller sends this object to the fee payer
  • fee payer calls setAttributeSigned() on the contract with the above parameters.

Does this make sense? What do you think?

src/helpers.ts Show resolved Hide resolved
dataBytes: Uint8Array,
didReg: Contract
) {
const nonce = await didReg.nonce(signerAddress)
Copy link
Member

Choose a reason for hiding this comment

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

Yes. It's about compatibility.
The older contract, which is deployed to mainnet and ethereum testnets tracks the nonce as a property of the owner, and not as a property of the signer.
Most of the time these are the same address, until the owner of the DID is changed, at which point this becomes an issue.

@lleifermann
Copy link
Contributor Author

@mirceanis

I've just added changes implementing the things we discussed about. For each meta transaction method we now have a correlating createHash function which generates the correct hash with the needed input parameters.
This means that this library really does not have to deal with the key material at all and it fully stays in the callers hands. We just generate the hash and play it back where it then gets signed (for instance in the kms plugin).

Originally we wanted to put this into some abstraction with only one method but that added a lot of complexity. This approach should be rather straight forward to understand and more importantly debug 😄

Not sure exactly on how to test the nonce function against the older version of the contract though. Do you have an example for this with the ganache setup this repo is running with?

@mirceanis
Copy link
Member

I've just added changes implementing the things we discussed about. For each meta transaction method we now have a correlating createHash function which generates the correct hash with the needed input parameters. This means that this library really does not have to deal with the key material at all and it fully stays in the callers hands. We just generate the hash and play it back where it then gets signed (for instance in the kms plugin).

I love this approach!

Not sure exactly on how to test the nonce function against the older version of the contract though. Do you have an example for this with the ganache setup this repo is running with?

I don't know either, I haven't done anything like this yet so I don't have an example.
The steps to reproduce weird behavior in the old contract:

  • create a DID
  • changeOwner
  • setAttributeSigned (increments nonce for the old owner)
  • setAttributeSigned again << fails with bad_signature if nonce is loaded from signer and not from the owner.

Even worse bug in the old contract:

  • changeOwnerSigned (increments nonce for the old owner, but hash is computed with nonce from the signer)
  • add/removeDelegateSigned (increments nonce for the old owner, but hash is computed with nonce from the signer)
    This means that owner and delegates can be changed at most once using metaTX, until the owner and signer nonces diverge.

Perhaps @piouslove can jump in with an example, or maybe @nichonien has run into this issue at EW

@lleifermann
Copy link
Contributor Author

@mirceanis Now everything should be in-place i believe 😄 - For the tests i had to copy the ABI of the old version of the contract as old packages don't export the abi sadly. But the two new tests shoud cover the compatability of nonces.

Copy link
Member

@mirceanis mirceanis left a comment

Choose a reason for hiding this comment

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

This looks good.

I think the newer versions of the contract should list a version, so that the user doesn't have to guess if the deployment they are interacting with tracks nonces properly or not. This will also come in handy for further upgrades.
What do you think?

@mirceanis mirceanis merged commit ce93e70 into decentralized-identity:master Sep 5, 2022
uport-automation-bot pushed a commit that referenced this pull request Sep 5, 2022
# [6.2.0](6.1.0...6.2.0) (2022-09-05)

### Features

* add controller support for meta/signed transactions ([#164](#164)) ([ce93e70](ce93e70))
@uport-automation-bot
Copy link
Collaborator

🎉 This PR is included in version 6.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants