-
Notifications
You must be signed in to change notification settings - Fork 91
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
Remove Gundb and generate MACI key from wallet signature #615
Conversation
✅ Deploy Preview for deterministic-key ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
✅ Deploy Preview for clrfund-testnet ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
@daodesigner , I have reimplemented the MACI key generation using the public key from the passkey and would like to hear your opinion about this approach. According to the webAuthn standards, 3 components are involved:
Because Clrfund runs on the client side only (no backend server), we will not store the public key, instead, we will use ecrecover to recover the public key for generating the MACI key. Because of this, we currently only support the ECDSA p256 curve, which allows public key recovery from the signatures. Note that only the website that owns the domain can prompt the users to sign a random message with the passkey they created on the website. So, only the website can get the signature to recover the public key. Users can manage (rename/delete) the passkeys they created on the authenticator. Here's how to manage passkeys: Security wise, I think it's more secure in that users do not need to sign a static message with their wallets, and no need to remember passwords. However, there are limitations:
Here's a test site if you would like to see how the UX flow: How to get the test DAI used by the app: |
Very cool, definitely looks way better off the bat, (though ideally you could use the private key) and will look through the changes/try it out tonight 😊. Great work! |
Thank you, looking forward to your feedback |
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.
any chance there's a diagram of all the interactions happening?
if (key) { | ||
return key | ||
} else { | ||
const key = await credential.create() |
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 this the public key or the private key?
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 is the public key of the passkey, we can't get the private key from the passkey
} | ||
|
||
const credential = new Credential(address) | ||
const keys = await credential.get() |
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.
how does this get locked down? this just on the user's browser?
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 is between the user's browser and the authenticator.
timeout: TIMEOUT, | ||
} | ||
|
||
const credential = await navigator.credentials.get({ |
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.
where does navigator come from?
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.
navigator is the user's browser api
publicKey, | ||
}) | ||
|
||
return recoverPubKeyFromCredential(credential) |
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.
just return one of the private keys instead, which would be a better source of entropy
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.
The private key is never exposed, what we get from the authenticator is only the signature of the data we asked it to sign. That's why we are recovering the public keys from the signature.
} | ||
|
||
async create(): Promise<string> { | ||
const challenge = utils.randomBytes(32) |
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.
user should save this somewhere, I assume this is the password?
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.
No, this is not a password, this is just a random piece of data that we ask the authenticator to sign. The standard suggests using random data to avoid replay attack, but, in our case, we never expose any data from the passkey other than using the public key to encrypt the votes on the browser, we could probably use a constant here.
Obviously, this logic won't work if we ever exposed the public key or can trick the authenticator and user for the signature. I've tried testing from different domains and passing different domain from the api, all tests didn't get the signature.
Yes, added https://github.com/clrfund/monorepo/blob/deterministic-key/docs/passkey.mmd |
changes merged |
No description provided.