Sign in with Google to your Identity Contract (for fun and profit)

There are many guidelines on using Ethereum’s accounts for signing in to an application. This enables a Sign In With Ethereum experience, that relies on the user signing an attestation of their identity with their private key, which is used to log them in to an app. This has the main benefit of decentralization over the traditional Sign In With Google, since we no longer depend on Google as a centralized identity provider.

However, some time ago, we wondered what would happen if we flipped this around. In other words, instead of using Ethereum as a replacement to Google to sign in to an app, what if we could use Google to sign in to Ethereum? As a concrete use case, could we support recovery of an Ethereum identity contract using Google Sign In, without adding any extra trusted components? Turns out we could.

Identity contracts & recovery

Identity contracts, also known as smart wallets, are a pattern for moving the user’s identity from their local keys to the blockchain itself. For all practical purposes, the user identity is no longer an externally owned, but a smart contract itself. This allows a user to have multiple devices, each of them with its own private key, which are registered as operators of a single identity contract. This contract’s address then holds the users’ funds and represents them online, and is used to interact with any other Ethereum protocols. Authereum, Gnosis Safe, Universal Login, and Argent (to cite a few examples) implement this pattern.

Having access from multiple devices ensures that, if you lose one, you can still access your online identity from a different one. However, if you lose access to all of them, you still need some recovery option baked-in, since forgot my password does not work in a decentralized world. One of the most popular options to solve this is problem is social recovery, where you designate a set of friends to grant you access back to your identity should you get locked out.

With this scenario in mind, together with @frangio, we set out to build an experiment: given that most users already trust Google with their digital life, could we add a Google Recovery option, in which the user’s identity in Google could be used to regain access to their identity contract?

How it looks like for a user

We built a small client-side-only demo app to showcase how this experience looks for the user. The user first signs in with Google to a page, in order to get their Google user ID. This ID is stored when deploying their identity contract, as well as the identifier of the application the user signed in to.

Note: This application (which will be referred to as the audience later) needs to be created once from the Google Developer Console, specifying the domain where it’ll be hosted.

Afterwards, if the user switches to a different Ethereum account, they can go through a recover flow where they are asked to sign in with Google again and send a transaction to the identity contract from their new Ethereum account.

Screenshot%20from%202019-10-26%2013-06-47

The authentication result is then sent to the identity contract, which recognizes the user ID validated by Google, and adds the new Ethereum account as owner of the identity contract. This allows a user to regain access to their decentralized Ethereum identity with just a single transaction - and a Google Sign In.

How it works under the hood

We are using Google Sign In for Websites, which is a flow designed to work without a server. In this flow, the user is prompted to sign in to their Google account, and Google returns a signed JSON Web Token (JWT) with the user’s identity.

JWTs are composed of three sections, all base64-encoded: a header, a payload, and a signature. The header contains information such as the signer and the audience, while the payload contains the user information. The interesting thing here is that the JWT is compact enough that it can be decoded and verified in a smart contract.

In our project, all this magic happens in the recover method of the sample Identity contract we have built.

  function recover(string memory headerJson, string memory payloadJson, bytes memory signature) public {
    string memory headerBase64 = headerJson.encode();
    string memory payloadBase64 = payloadJson.encode();
    StringUtils.slice[] memory slices = new StringUtils.slice[](2);
    slices[0] = headerBase64.toSlice();
    slices[1] = payloadBase64.toSlice();
    string memory message = ".".toSlice().join(slices);
    string memory kid = parseHeader(headerJson);
    bytes memory exponent = getRsaExponent(kid);
    bytes memory modulus = getRsaModulus(kid);
    require(message.pkcs1Sha256VerifyStr(signature, exponent, modulus) == 0, "RSA signature check failed");

    (string memory aud, string memory nonce, string memory sub) = parseToken(payloadJson);
    
    require(aud.strCompare(audience) == 0 || true, "Audience does not match");
    require(sub.strCompare(subject) == 0, "Subject does not match");

    string memory senderBase64 = string(abi.encodePacked(msg.sender)).encode();
    require(senderBase64.strCompare(nonce) == 0, "Sender does not match nonce");

    if (!accounts[msg.sender]) {
      accounts[msg.sender] = true;
      accountsList.push(msg.sender);
    }
  }

This method verifies Google’s signature to check the JWT is valid. It then checks that the audience (ie the app that requested the token) is the same as used when creating the identity contract, and then checks that the subject (ie the user) is the same as well. This requires base64 encoding and RSA verification, all done on-chain. As you can imagine, this process consumes a lot of gas; but recovery should not be a frequent operation. If everything is verified correctly, then the user’s new Ethereum address is added to the identity contract.

However, we now have a missing piece: how do we prevent someone from front-running the transaction, grab the JWT sent to the contract, and use it to gain access themselves?

The solution we found here is tricking Google into signing the new Ethereum address as part of the JWT itself. And we are doing it by setting the Ethereum address as the JWT’s request nonce, which in turn gets signed as part of the JWT itself, which we can then validate in the identity contract. A bit hacky, but works!

Note: As you may have seen if you tried the demo, the nonce is never exposed to the user as they Sign In With Google in the application, meaning that they could be inadvertently getting Google to sign a different Ethereum account if the app is malicious. This could even be done by any other application in which the user signs in with Google, which uses the JWT behind the scenes to gain access to the user’s Ethereum Identity. We currently mitigate this by validating the audience (ie the Google app identifier) in the smart contract, so only the original app can recover access to the contract. This requires the user to trust this single app, which could be further mitigated by storing the page in IPFS to ensure its immutability after it was audited once.

However, this still requires trust on the owner of the Google Developer project, who may add new malicious domains and trick the users into signing in there, or even close the project removing the possibility of recovery. That in turn could be mitigated by having users could download the page and run it locally, configuring their own Google Developer project, though the user experience is far from ideal.

A keys oracle

The last piece of the puzzle is how to get the public keys used by Google to sign the JWTs, since these are rotated frequently. These keys are advertised in this URL, as indicated on Google’s OpenID configuration.

At the moment, we are storing these keys in a trusted JWKS (JSON Web Key Set) contract which keeps track of these keys. However, the owner of the contract (currently us) could inject a malicious public key, and use it to self sign fake JWTs, gaining access to all users’ identity contracts. This makes this a contract a key piece of the infrastructure.

An oracle solution could be used to fill in this gap, such as Chainlink or Provable. In particular, since the keys are advertised from a single URL endpoint, an integration with TLSNotary could be used here.

FAQ

  • Can I try this out?
    Sure! Head over to https://identity-recovery-demo.openzeppelin.com/ on the Rinkeby network to test our demo. Note that the keys may have already been rotated, and updating them is a manual process - so if recovery fails, let us know! :pray:

  • Show me the code!
    Ok, not an actual question, but we get the point. Everything’s available on our github! :books:

  • Can I use this in production?
    Dear lord, no! There are many things to figure out still (such as trust on the google developer project, or the oracle for the signing keys), and the whole solution relies on the hack of using an ethereum account as a nonce - which is even undocumented :skull_and_crossbones:

  • Why did you do this?
    Because we can! We were interested in seeing how far we could push Ethereum :slight_smile:

  • Is that a good enough reason?
    Sure! We are buidlers after all, right? :construction_worker_man: :construction_worker_woman:

5 Likes

How much gas would be used

1 Like

Technically clever, but weaker in terms of security.

The scenario this scheme is trying to address is loss of keys. In the scheme presented, one would have to trust:

  1. Google
  2. Any current or past employee of Google who may have access
  3. Governments who have authority over google
  4. Operators of the smart contract.

This is addition to the trust that Google can protect its passwords. Its a lot more secure to just AES encrypt your keys and storing your keys on google docs if you don’t want to rely on your social recovery scheme. In general, adding a centralized party to a decentralized scheme creates more problems than it solves.

The ideal setup is to control all identities via a hardware wallet and memorize the seed phrase.

2 Likes

Hi @bharathrao,

Welcome to the community :wave:

I suspect that many future users of Ethereum will expect that there is a way to centrally recover access to their identity contract if they lose their keys. This has been their experience with their bank, their email and all their other non-web3 accounts.
I am thinking about what my parents would do.

I can imagine identity contracts offering some from of centralized recovery as an option, that users can turn off. I can also imagine protections such as access only being allowed after no activity for a period of time e.g. 3 months.

Again this is a Proof of Concept and in that context, it is great to push Ethereum forward and create discussion.

Looking at solutions that will work for the future users, including my parents. :smile:

Hi @pr0toshi,

Welcome to the community :wave:

You can try it yourself :smile:
Which is what I did, check out the gas usage below:

Create new identity

Contract creation transaction:
https://rinkeby.etherscan.io/tx/0xfb5ad15e0b1363943598142e200c8ecca39e7660354d0e373283fc490878bfc3
Gas Used by Transaction: 5,140,801

Recover identity

Recovery transaction:
https://rinkeby.etherscan.io/tx/0x13ce94befd5bf4fdac8110a1a6fd6deb8793e6327d79f976e64ef024b6323a43
Gas Used by Transaction: 4,648,095

Just wanted to segue this into emerging W3C standards of verifiable credentials and decentralized identifiers, we were thinking something similar for any OAuth2 IDP. We have an opportunity to consolidate approaches of bridging Web2 into Web3 and leveraging massive existing network effects. Here’s our proposed DID method with similar risks that you highlight:

Disclosure of risks is probably the most important thing that makes this into an engineering option rather than an ideological debate.

2 Likes

Thanks @abcoathup for the experiment! FWIW, the code is far from optimized. The gas cost for recovering the identity can probably be considerably reduced.

As for the cost of deploying a new identity, by using minimal proxies it can be driven down by several orders of magnitude.

1 Like

Well, if things go down the path that, say, Argent has taken then a contract would have N recovery addresses a subset of which is required to unlock the contract. If some unpopular OAuth provider ends up being just one of a number of co-signers then there’s no loss to “decentralization” as far as I see. Of course the user would need to know not to trust only one entity. In case of, again, Argent that’s a real problem right now because Argent themselves are initially the only co-signer, so a lazy user would be trusting a young and unprofitable company. I suppose a protocol could demand that the user diversify their co-signers just like websites demand 2FA.
EDIT: Actually, thinking about it, having a single recovery account is like having none at all, since if it is compromised then that’s all that’s needed to take over your main account.

1 Like

Social media

(including Twitter and Reddit)

Tweet thread

Tweets

(replies, apologies if I missed any)

Reddit comments

(level 1 with more than 0 points, apologies if I missed any)

moumous87 or r/ethereum

Quickly read the comments and I had a WTF moment. I can understand that some people are following Ethereum only for speculations and don’t really know/understand much about software and blockchain development, and thus might not be familiar with concepts like Proof of Concept or Minimum Viable Product… But only in the US would people use an acronym to racially profile people.

Onakander on r/ethereum

This is antithetical to the entire point of decentralized tech. “Hey, let’s make a centralized corporation able to “recover” (read: at any time usurp) your keys.”

vvpan on r/ethereum

The negative comments are very surprising and, frankly, enraging.

Firstly, they don’t seem to know what a Proof of Concept is, it certainly is not a finished product, it just showcases a certain technical relationship as being possible. Not needed, not mandatory, but theoretically possible. This is how technological progress is achieved, btw.

Secondly, the UX of blockchain is terrible and only a tiny subset of technically able people use it and that is not likely to change if things are left as they are. I develop on top of Ethereum and even I don’t want to take out my hardware wallet unless it is absolutely necessary (so like once a year). Too much is at stake and it’s too easy to make a mistake and lose everything. Account recovery is one of the most under-researched topics so don’t shit on people who are trying to improve the situation through experimentation.

Thirdly, if you knew something about account recovery you’d know that there are clever schemes (used by Argent for example) that allow you to recover by using a set of co-signers: a friend, a hardware wallet, the company that made the wallet. The scheme of signing via a an existing auth provider potentially gives you one more option - a real world account you already have, making on-boarding easier without sacrificing decentralization and making things more secure in the process.

idiotsecant on r/ethereum

This is an interesting gadget, as long as google’s JWT website sign on functionality is maintained and continues to work how it’s supposed to.

265 on r/ethereum

given that most users already trust Google with their digital life

Haha, no thanks.

ethstaking on r/ethereum

Nice POC. Really shows the potential of ethereum but I would not trust google to recover my funds. Better to memorize the seed and the only one you have to trust is YOU

ice0nine on r/ethereum

Very nice! What would even enhance your already good documentation would be a sample for a plain, (base64-)decrypted JWT (as done very nicely on jwt.io). This would ease the understanding, as I just dealt with JWT it’s ok for me, but for someone new to this it might be overwhelming.

Welcome to the community @wyc and @ivanvpan :wave:

Thanks for sharing.

For me, I would want the recovery option to have some form of time delay protection. e.g. 3 months of in activity, or have a two stage process, where the recovery process is initiated (and any existing key can cancel), then completed after a time delay.