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

[Contributor Proposal] [Do not merge] Account Abstraction - (Nextjs, WebAuthn, Safe) #223

Open
wants to merge 21 commits into
base: development
Choose a base branch
from

Conversation

Keyrxng
Copy link
Contributor

@Keyrxng Keyrxng commented Apr 13, 2024

Related to #219

Changes

  • Implements Safe
  • Removed all Auth0/GitHub login etc
  • cleaner UX
  • Auto-login for registered users
  • Choice to create multiple accounts but not with same username (passkey)

  • Less secure PK generation
  • still requires a move to NextJS
  • no fingerprinting implemented yet, curious what the view towards privacy concerns are for it? Can a better approach be used? In every collection of docs and articles I've read, it's recommended to always have another auth step so we for sure need one

QA:

  • Register a new passkey
  • Reload for auto-login
  • Delete localStorage
  • Login with passkey
  • Claim permit

QA comment with video

Copy link

github-actions bot commented Apr 13, 2024

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 13, 2024

We'd also need to implement some way for them to control their funds as if a non-web3 user comes in without a wallet and we create one for them (before the cards are rolled out) then they'd have no way to access the funds unless we provide them a UI as we are now technically their wallet provider, otherwise a way for them to securely swap and off-ramp.

@rndquu rndquu requested a review from molecula451 April 15, 2024 11:28
@molecula451
Copy link
Member

the build doesn't suppose to be failing, does it, @Keyrxng

@gentlementlegen
Copy link
Contributor

the build doesn't suppose to be failing, does it, @Keyrxng

I believe that some environment variables are missing according to the build

Error: AUTH0_CLIENT_ID and AUTH0_DOMAIN must be set in the environment

@gentlementlegen gentlementlegen self-requested a review April 16, 2024 15:37
@molecula451 molecula451 removed the request for review from pavlovcik April 16, 2024 17:06
@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 17, 2024

The build is failing because CI is expecting the build to come from the esbuild script but it's coming from next build now which is not setup for CI. I should have made a better commit comment build ready should be next build ready, like I said if this was given the green-light a bit of a clean-up would be needed.

So my apologies if it wasn't clear before now but this is more of a reference PR in regards to account abstraction than an actual implementation PR. A few things to be decided first before an impl PR is allowed I think?

  1. Is a move to NextJS permitted? (if so I assume that should have it's own PR with minimal changes?)
  2. What type of smart account should be used, minimally extensible lightweight or fully extensible modular account?
  3. Is the smart account being created at bot-level (on GitHub) or as part of pay.ubq.fi user flow?
  4. How would the user access/control their funds once claimed into their smart account, pseudo-wallet UI or direct off-ramp?

This recent EIP proposes baking AA into EOAs, it's quite a hot topic on CT right now. It's only in review (draft > review > last call > final) so it's pretty far out but should be kept in mind I think.

@0x4007
Copy link
Member

0x4007 commented Apr 18, 2024

It's interesting research. I'll be back on my computer early next week to be able to hopefully spend time on a deeper dive because I am not well read on all the latest in account abstraction.

Looking at the video: I had a far more lightweight implementation in mind. The key point would be leveraging the webauthn browser API.

  • Needs more research but basically generating the same key every time from the same webauthn credentials. In my case I have iCloud on my iPhone and Mac, and can associate my iCloud user with webauthn.
  • Using biometrics I can instantly login (on my phone it would be faceid for example)
  • The UX I see being realistic is that the page loads, and it prompts the login OS modal right away.
  • Biometric authentication passes
  • The private key is deterministically generated
  • And then the new app provider instance is constructed using the generated private key.

I think all these clicks, modals (and in this case, redirects with more inputs) that are prevalent in this industry is bad UX. This should all be handled behind the scenes and as seamlessly as possible.


Further UX optimizations:

  • Save key in localStorage so we don't need to login again (not sure if security risk)
  • allow user to login with browser wallet if it's detected
  • allow user to automatically register their connected wallet with their GitHub username for future permit generation
  • seeding gas money by subtracting from their upcoming permit upon registration of wallet (this would have to occur when they register on GitHub, and while the task is closed as complete-perhaps a new plugin)

@molecula451
Copy link
Member

The build is failing because CI is expecting the build to come from the esbuild script but it's coming from next build now which is not setup for CI. I should have made a better commit comment build ready should be next build ready, like I said if this was given the green-light a bit of a clean-up would be needed.

So my apologies if it wasn't clear before now but this is more of a reference PR in regards to account abstraction than an actual implementation PR. A few things to be decided first before an impl PR is allowed I think?

  1. Is a move to NextJS permitted? (if so I assume that should have it's own PR with minimal changes?)
  2. What type of smart account should be used, minimally extensible lightweight or fully extensible modular account?
  3. Is the smart account being created at bot-level (on GitHub) or as part of pay.ubq.fi user flow?
  4. How would the user access/control their funds once claimed into their smart account, pseudo-wallet UI or direct off-ramp?

This recent EIP proposes baking AA into EOAs, it's quite a hot topic on CT right now. It's only in review (draft > review > last call > final) so it's pretty far out but should be kept in mind I think.

Yes, great initiative and im in favor sounds great some points:

  1. Yes, in fact we use nextjs in dollar so it's feasible
  2. I think we should let this as an user decision, but both sounds good to me
  3. It's an obviously use case to all the things web3 on the repo, but seems usable on one instance as first, say pay.ubq.fi repo as firrst
  4. A UI sounds betterr

@molecula451
Copy link
Member

if you can add generic js tests is plus

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 19, 2024

great stuff, I'll carry it through to completion over the weekend taking these comments into account

@molecula451 molecula451 changed the title Account Abstraction - (Nextjs, Web3auth, Auth0, Alchemy) [Contributor Proposal] [Do not merge] Account Abstraction - (Nextjs, Web3auth, Auth0, Alchemy) Apr 21, 2024
@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 22, 2024

How it works
  • User comes to portal, if EOA detected connect EOA and save address to db, user claims permit.

  • Else, try to grab credentials. If the user chooses cancel or none are found then prompt to create new credentials.

  • If create new credentials, authenticate immediately after in order to create the new account and populate the db.

  • optional: we either try to fund and deploy the SMA right away or that could be left until the account has a valid permit, depending on at which level the account is created will determine the best way to fund/debit the account


The private key is deterministically generated

Implemented using information from cookie based auth and MFA (webauthn) credential.

Save key in localStorage so we don't need to login again

Not implemented. We could rebuild the account pk using the cookie auth and browser-stored credentialPubKey and sign tx that way but only for aa accounts only, but that is already stored along with the wallet information and it heightens security by keeping those details gated to an authenticated user. But it's not a security risk storing that by itself but this statement below from the docs is why I done cookie-first auth. We could get away with it but it has trade-offs.

In order to prevent such information leakage, the Relying Party could for example:

Perform a separate authentication step, such as username and password authentication or session cookie authentication, before initiating the WebAuthn authentication ceremony and exposing the user’s credential IDs.

allow user to automatically register their connected wallet with their GitHub username for future permit generation

Both newly visiting EOA's and smart accounts are populated into the users and wallets tables for future use.

seeding gas money by subtracting from their upcoming permit upon registration of wallet

I was using a tweaked version of the gas faucet to fund the few accounts that I saw through to deployment but I've been thinking about this.

  • if it's an EOA and they are not creating an AA account we never have control of an already assigned permit so maybe we create a new tx for them to sign which ends with the funds in the user's wallet effectively paying for gas with a transfer of a debit/fee or whatever to another wallet? Otherwise, we create AA accounts for EOAs too and sort of force it on users (bad move?) or fund those with the faucet and track it via KV then debit pre-permit

  • if it's a SMA then 1. Have we just created it? 2. If we haven't just created it how've they got a SMA before this point?

while the task is closed as complete-perhaps a new plugin

We could just use the faucet seeing as that is a worker, just setup a KV for it and track which newly created accounts both SMAs and EOAs have been funded and expose access to the KV through calls to the worker so other plugins/modules can use it


this would have to occur when they register on GitHub

So will the user entry point for wallet and account creation always be GitHub via slash commands? Considering what @molecula451 said about it being user choice between the two smart account versions, how would they choose that at a bot level via slash command?

I think it makes sense to either choose only one and have a dedicated section in docs or onboarding explaining things or create a new section for the claims portal. A visit to / without any claim permit could allow a user to create their new smart account, control their funds, export their private key and mnemonic so long as they are cookie-auth'd in order to create the account then also auth'd with webauthn in order to move funds, view private key etc.

If only one is being chosen it makes sense to use the Multi-Owner-Modular account because of it's extensibility with plugins etc it would likely work very well with whatever plans you have for ubiquity cards, I assume they go hand-in-hand with the long term vision being preferably everyone has at least an abstracted account, if not also the card too? But it would also make things easier to register at bot level, as we'd be able to setup, fund, debit and deploy all without leaving github.


QA

  • I had to speed up and drop it to 720p to show it e2e
  • I show the mnemonic and publickey in the toasts for QA purposes but unless a dedicated path for user's to obtain their mnemonic/pk repeatedly or as a "you're only shown this once" sort of vibe, toasting it was the best option at least for QA
  • I started to write tests and realised a couple of things:
    • These interactions are system native and cross-platform which is going to make testing a bitch in Cypress if I'm not mistaken
    • Too many unknowns in regards to what shape the UI is going to take but the logic for AA and WebAuthn handling is implemented to a functional standard which no doubt will be improved on but the groundwork is laid out for it
aa-webauthn.mp4

four_funded_and_deployed


std-eoa.mp4

@0x4007
Copy link
Member

0x4007 commented Apr 23, 2024

This is nifty but it's a bit hard to tell watching from my phone screen some of the details.

  1. Why do you have to webauthn multiple times?
  2. Why do you have to login with GitHub?

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 23, 2024

Why do you have to webauthn multiple times?

The Request a Credential algorithm accepts a CredentialRequestOptions (options), and returns a Promise that resolves with a Credential if one can be unambiguously obtained, or with null if not.

I'm either grabbing or creating the account because we only have one entry point /?claim=.... So if they have an account the first navigator.credentials.get() signs them in. If they don't have an account and they accept and we try to grab a credential it'll return null or if they cancel in which case a new account/credential set is created.

The 2nd step (the 3rd step in the video) in the new creation process pulls info from the credential to use in creating the account and just makes sure nothing went wrong in creating the credential like a timeout of the OS request or some other cross-platform issue.

The createNew step is skipped if they have an account already but if /?claim=... is the default entry, we can reduce the steps down to two by first checking Supabase for their credential public Key.


If we chose to store a flag in localStorage to indicate they've created a passkey (their authenticator already knows they have but we do not) and we have no Auth0/Supabase step and somehow have user info for the credential creation, if they remove it and we create an account based on this flag then we'd create a new credential set in their authenticator making it cumbersome on the user to be able to identify and manage their separate credential sets/smart accounts. Otherwise we do a three step check and we could even add some UI changes that prompts the user if they are sure that they want to create a new credential set/smart account because as of right now we only offer 1 default account any previous would be overwritten or would fail.

With Auth0/Supabase we can be sure to always return the correct credentials and allows us to handle multiple credentials/smart accounts if that is a feature to be offered (does that fall inline with how cards will work maybe?)

My suggestion in that a visit to / could be the entry point to create a new account and credential set then any visits to /?claim=... would only ever request credentials.


Why do you have to login with GitHub?

When creating the credentials we need to fill in identifying information from us as the host and them as the user and some other settings.

publicKey: {
        challenge,
        user: {
          id: new Uint8Array(0),
          name: "",
          displayName: "",
        },

As recommended, gated behind auth0 we have multiple layers of security and more entropy for creating the new account with so long as the info we are using from that step is secured properly. I've tried to make it as secure as possible while still being deterministic.

Perform a separate authentication step, such as username and password authentication or session cookie authentication, before initiating the WebAuthn authentication ceremony and exposing the user’s credential IDs.

This statement from the docs is in regards to a user being identifiable with the easily accessed credential information through the credentialID (which is used as entropy for the pk), so a better way to handle it is to gate it behind an additional auth step.


But it would also make things easier to register at bot level, as we'd be able to setup, fund, debit and deploy all without leaving GitHub

I forgot to add this implies that they first setup their credential via "/"

@0x4007
Copy link
Member

0x4007 commented Apr 25, 2024

Cloudflare login uses my iCloud so I figured there is some OS level abstractions we can take advantage of for our identities instead of logging into GitHub as an extra step.

The cloudflare UX prompts an OS modal with a single button to login. When I press it, it does faceid and I log in right away. It's really seamless and seems to have all the strengths with none of the weaknesses.

If for some reason that's not viable for non Apple devices, an alternative suggestion is to generate a unique device fingerprint/signature (theres techniques that use webgl) to deterministically generate a dedicated signing key per device.

Then we could in theory allow multiple claim addresses per user although that would require a forked version of permit2. But this would be great for private key security because the user doesn't ever need to export or save it elsewhere. The device should be able to deterministically generate it every time.

@0x4007
Copy link
Member

0x4007 commented Apr 25, 2024

To clarify, I just test at the Cloudflare login again and I'm specifically referring to the two factor auth. This doesn't require an iCloud ID but it's shared in my "keychain"

Which iCloud syncs across all devices.

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 25, 2024

Cloudflare login uses my iCloud

Which is just another MFA auth step like GitHub login, but I hear what you are saying.

The device should be able to deterministically generate it every time.

The pk could be generate deterministically with only the information from the passkey although you'd want to add in some secure extra entropy not attached to the key. And we could remove any other auth step like Github or Auth0.

I avoided it because it seemed insecure to me at first but I can push this ahead with just WebAuthn if that's acceptable, which by the sounds of your comment it is and enhancing pk generation/security can be hashed out later? pun intended

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 25, 2024

Your keychain is part of your iCloud ID. I'm not sure how it works with Android so I can't comment but it's my understanding that they go hand-in-hand

iCloud Keychain can also keep the accounts you use in Mail, Contacts, Calendar, and Messages up to date across all your iPhone and iPad devices and Mac computers.

When you turn on iCloud Keychain on an additional device, your other devices using iCloud Keychain receive a notification requesting your approval of the additional device.

On one of your other devices, approve the additional device. Your iCloud Keychain automatically begins updating on the additional device.

To approve iCloud Keychain when you don’t have access to your other devices, follow the onscreen instructions to use your iCloud Security Code.


Maybe I misundestand.

You are logging into Cloudflare using your keychain. I tried "login with apple", is that the same as what you are seeing/doing?

image

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 25, 2024

Yeah I tried on my mobile too and same affect

image

@0x4007
Copy link
Member

0x4007 commented Apr 25, 2024

I wanted to include a video but I'm unsure how to censor the key it displays. To be honest I'm not even sure if that key is considered sensitive data.

image

I can also 2FA with my hardware wallets for other accounts. Basically there are APIs to inject keys into the browser with elegant OS level integrations that we should be taking advantage of.

I'm not so concerned with entropy because the raw inputs aren't "exportable" by the user. I don't see how it can be leaked if we don't provide that option in the first place.

We could consider browser fingerprinting so that it's all seamless but still not sure that this additional entropy is necessary.

However that would be a really nice architecture for a user to be able to automatically add their generated (browser fingerprint based) device keys to a gnosis safe where all their assets are. They can conveniently "2fa" with another device they own. Key rotation shouldn't be necessary if we don't save the key anywhere (it can be deterministically generated every time)

In this case perhaps it makes sense to add that additional entropy via a login (maybe GitHub or something better?) so that a malicious actor won't use our same algorithm to make a fake page to generate the key and save it on their database

(I understand now why you wanted to make additional entropy, it's so that their browsers can't be easily phished with a fake page?)

Another seamless way to handle this without a login: what if we use a posix timestamp as a salt and then save the generated key in localStorage? At this point, they would need a way to export the key which is not desired.

However the benefit is that even with the same algorithm, a phisher won't be able to generate the same key.

Pros and cons...

I wish there was a way to not necessitate exporting/backups. One last idea is using a password as a salt, combined with a browser fingerprint. Autofill can make it seamless for the user to authenticate. That way the user just needs to remember the password and the device they used. We can encourage users to have minimum two keys connected to their claims so that if they lose their phone etc they can still manage their account from the other? We could also consider the GitHub user as a super user for their account so they can always manage their keys even if all their devices are unavailable.

@0x4007
Copy link
Member

0x4007 commented Apr 25, 2024

We could black box the key generation algorithm by distributing it as a compiled wasm module.

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Apr 25, 2024

gnosis safe

do you mean a literal Gnosis Safe or are you using that interchangeably with just an abstracted smart account? I see Gnosis have rolled out points for transacting with your own Safe which is cool, maybe swap out Alchemy as the AA provider then and go with Safe?

it's so that their browsers can't be easily phished with a fake page

Sort of, the passkeys are tied to the domain that they get registered to so it's already sort of 'unphishable' in that sense.

But if the whole AA system is based off of just the user-accessible passkey info, I believe all that would be needed on the attacker's part is to manage to hack their iCloud account. Or if a user was MITMd their info would be revealed after a successful passkey request, an attacker would likely be able to use this and the algorithm to reproduce the pk or just access the website with the user's account logged in via browser ext for example (with github too, if the user has an icloud passkey saved they are fucked so maybe that's redundant anyway?). Will access to their card(s) use the same credentials/auth flow for moving funds etc?

It seems shaky to me at least to not include additional entropy somewhere that either only UBQ can produce or that we can tie to a user regardless of device, location, browser, etc. and confidently reproduce it.

save the generated key in localStorage?

I would not save my wallet private key anywhere on a browser personally and I don't think it would be a huge selling point of a novel AA system, but idk. If you meant the passkey "key" (lol) then with entropy it's not totally unsafe I guess. But what happens when they get a new laptop or clear their localStorage, bye bye smart account unless we always first attempt to retrieve a set of credentials and if null is returned then we create a new set (with "are you sure?" prompts maybe), providing whatever entropy we are using can be securely reproduced.

password as a salt, combined with a browser fingerprint

I like the sound of this actually

two keys connected to their claims

For me browser is the natural claiming point, I don't dev on my mobile and mobile web3 sucks ass.

As part of the account creation path, it should be mandatory to create a passkey from your desktop browser and another from your mobile, then we are totally hands-off.

You can likely imagine the scenes with a huge devpool base with user errors with an optional 2nd key.

  • forgotten password
  • forgetten iCloud password to update the credential password they forgot
  • broken phone/laptop
  • probs more

So I'd suggest making it mandatory with disclaimers and warnings that the onus is on them after account creation regarding account access.

Although I'm not sure of how the UBQ cards are coming together but depending on how those cards are "owned", does it make sense to have AA be foundational in how the cards are constructed or vice-versa? I imagine the cards will be Fort Knox and their smart accounts should be too, but will this early account abstraction system being described here work well with cards?

I wish there was a way to not necessitate exporting/backups.

So no-one really knows the private key for the wallet, it's just produced and only ever produced on ubiquity domains via the wasm module? (credentials can be shared across domains)

That's exactly like being a custodial wallet provider like a CeX.

"not your keys, not your coins" springs to mind.

With both the cards and AA coming into play, it might be an idea to define exactly what sort of "wallet provider" Ubiquity is going to be as that is certainly the direction it's all heading with the need for UI's for handling funds etc.

  • is it custodial, completely hands-off for the user and they have no control of pk or mnemonic and cannot "exit" the UBQ eco-system with that wallet per-se but can send tokens to other wallets and use the cards to transact fiat?

  • is it non-custodial, we give them everything for their account and they keep it secure (how does that work with cards, as it'll be UBQ's job to keep them secure)

  • some other hybrid?

@0x4007
Copy link
Member

0x4007 commented Apr 25, 2024

That's exactly like being a custodial wallet provider like a CeX.

"not your keys, not your coins" springs to mind.

That's not at all what I'm proposing. The keys are in the users browser. They should never be transmitted off of the device. We don't have any access to their keys.

The cards have almost nothing to do with blockchain right now. The current plan is to transfer ubiquity dollars to a "card minter contract" and then we mint a new card up to $1000 and give it to the user. It's our bank account. In order to reimburse it, we take the ubiquity dollars and redeem them for USD.

Sort of, the passkeys are tied to the domain that they get registered to so it's already sort of 'unphishable' in that sense.

This is great!

As part of the account creation path, it should be mandatory to create a passkey from your desktop browser and another from your mobile, then we are totally hands-off.

You can likely imagine the scenes with a huge devpool base with user errors with an optional 2nd key.

The idea is that each key technically weakens the users security. But if they keep adding keys to their account to be able to claim their rewards then it's more convenient for them. I imagine that the signing key can be for signing but the funds should really be stored elsewhere.

So in this case maybe a modified permit2 contract makes sense for any user to be able to claim any permit. This means that we can use any signer to claim (and transfer) to their registered wallet on GitHub.

The thing is, when I personally receive rewards, I like to claim them instantly. I imagine others may also do the same because it ensures that you can't get rugged on your payment if it's already in your wallet.

@Keyrxng
Copy link
Contributor Author

Keyrxng commented May 2, 2024

Started anew and took it in the new direction discussed.

This means that we can use any signer to claim (and transfer) to their registered wallet on GitHub.

I don't see why using the faucet to fund (with kv upgrade) isn't a suitable approach?

Or a relayer setup to process claims from the portal domain and only if reward.owner == ubiquibot which would be best served as a worker for KV for funding debits.

Soon as an account is registered (before any tasks are completed because no address yet) let worker deploy user Safe with PK alg inside worker, save Safe address to db for permits and store fee in KV. Permit generation or new plugin fetches from KV and applies debit before final payment output. This is a much better approach I think because we could build another auth step into the worker.


Safe are working on implementing adding passkeys to existing Safes through their SDK so I think it might be an idea to focus on a more secure pk algorithm and multi-passkey to account features once it's rolled out via the SDK?


Changes

  • Implements Safe
  • Removed all Auth0/GitHub login etc
  • cleaner UX
  • Auto-login for registered users
  • Choice to create multiple accounts but not with same username (passkey)

  • Less secure PK generation
  • still requires a move to NextJS
  • no fingerprinting implemented yet, curious what the view towards privacy concerns are for it? Can a better approach be used? In every collection of docs and articles I've read, it's recommended to always have another auth step so we for sure need one

QA:

  • Register a new passkey
  • Reload for auto-login
  • Delete localStorage
  • Login with passkey
  • Claim permit
For the sake of QA I manually funded a previously created passkey address so I knew the address for funding but I've left out any sort of funding logic from this new approach
webauthn-safe-aa.mp4

@Keyrxng Keyrxng changed the title [Contributor Proposal] [Do not merge] Account Abstraction - (Nextjs, Web3auth, Auth0, Alchemy) [Contributor Proposal] [Do not merge] Account Abstraction - (Nextjs, WebAuthn, Safe) May 2, 2024
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.

None yet

4 participants