The new DeSo Identity UI with MetaMask login
Our MetaMask integration ushers in a new era of interoperability.
You can use MetaMask as a central hub to freely interact with DeSo, Ethereum, and soon many other blockchain ecosystem.
And it all happens with a single click.
But how does it all work?
DeSo foundation engineer Piotr Nojszewski breaks it all down below.
DeSo is a layer-1 blockchain separate from Ethereum, meaning MetaMask doesn’t natively integrate with DeSo.
However, Ethereum uses the same cryptographic structures as DeSo to handle user wallets.
As a result, a private key on Ethereum corresponds to the exact same public key on both blockchains.
This allows us to translate a MetaMask wallet into a DeSo wallet easily.
But, there’s a caveat.
While we can easily map public keys across both blockchains, we can’t use MetaMask to sign DeSo transactions – and even if we could, signing each transaction would require opening the MetaMask window and manually clicking the “approve” button, yielding an unsuitable UX for our application.
We can circumvent these limitations thanks to DeSo’s derived keys, which allow controllable access-sharing of one’s account.
What’s a derived key?
Namely, a derived key is a crypto keypair that can sign transactions on behalf of another keypair, subject to some defined transaction and spending limits.
For example, a creator could issue a derived key only able to send DMs and share it with their team to respond to their followers’ correspondence.
The range of possible applications of derived keys is vast because we can specify any possible combination of transactions a key can execute and set an upper limit on how much $DESO a key can spend.
In the case of MetaMask sign-up, we use an unlimited derived key created and stored within the DeSo Identity.
This unlimited derived key will allow the Identity to perform DeSo transactions on behalf of MetaMask’s main public key, with all the convenience of DeSo Identity API and particularly the background signing.
Let’s take a look at how this authoritative derived key is created.
Unlimited Derived Key
To create a derived key, one needs to broadcast a special blockchain transaction called an AuthorizeDerivedKey transaction.
This transaction contains three descriptor fields about the derived key, such as the public key, the blockchain height at which the key expires, and the transaction spending limit; in addition, there is a security field called access signature.
The access signature is essentially an unforgeable certificate of the AuthorizeDerivedKey descriptor fields, issued by the user for whom the derived key is.
The access signature is the backbone of the security of derived keys, ensuring that no unwarranted party can issue a derived key to another user illegitimately.
To be more precise, this certificate is a digital signature issued by the user’s main public key, which in our case is the MetaMask public key.
But, there’s a catch – creating digital signatures requires knowledge of the private key, which, for the right reasons, we can’t retrieve from MetaMask.
Instead, we utilize Ethereum’s eth_sign and prompt the user with the signing request in the MetaMask window UI that will produce an authorizing signature and effectively enable us to issue a tantamount derived key on DeSo.
The signature request UI displays the descriptor fields for the DeSo-derived key.
With some string formatting, we can show the fields in a human-readable form so users can know what key they’re authorizing.
We haven’t seen many other MetaMask integrations displaying signatures in this way in the UI. While it took a bit of engineering in the core blockchain to get it to work, we think it’s much more user-friendly than signing an unintelligible byte array.
Similar to traditionally created DeSo accounts, MetaMask-based accounts can be used to issue limited derived keys.
Since the main seed of an MM-based DeSo account remains in the MetaMask vault, we need to use the MetaMask API to be able to create derived keys.
From the developer's perspective, this is no different than authorizing regular derived keys, with the exception that the user will have to confirm the derived key through the MetaMask window, as seen in Fig 3.
The attached screenshot used a more elaborate spending limit to exemplify the MetaMask signing UI.
Fig 3. Issuing a certificate for a DeSo-derived key with a spending limit.
Consequently, MetaMask wallet will act as your primary key store, while DeSo Identity will store a derived key that you can use for interacting with the blockchain.
One design limitation for MetaMask to DeSo login flow was submitting an authorized derived key transaction.
The authorized derived key transaction and all other transactions require at least 1000 Nanos (smallest unit of DeSo) to be written on the next block.
Since your MetaMask determined DeSo public key was just generated, it will hold no funds and thus not be able to submit the authorized derive key transaction.
To fix this issue, we injected an airdrop payment of .001 DeSo to any eligible account after generating the public key from the MetaMask step and before submitting the authorize derived key transaction step.
To make the MetaMask flow seamlessly integrate with existing DeSo apps, we made some changes to the core protocol, which we will now outline.
To introduce the changes, we will soft fork so the new code is completely backward-compatible and doesn’t require a resync. There are three main core changes:
Access Signature formatting
Reference PR: https://github.com/deso-protocol/core/pull/379
Accepting derived key access signatures utilizing the MetaMask human-readable string. Essentially now, two possible encodings of an access signature will be accepted by core:
Access Bytes Encoding 1.0
This is the traditional encoding which includes an ECDSA signature of a sha256 hash of compressed derived public key bytes || expiration block || encoded transaction spending limit.
The signature is then encoded into a DER format and passed in AuthorizeDerivedKey transaction metadata.
Access Bytes Encoding 2.0
This new encoding also utilizes the ECDSA signature algorithm, with slight modifications to accommodate the eth_sign functionality.
The eth_sign and consequently Access Bytes 2.0 messages must start with "\x19Ethereum Signed Message:\n" concatenated with the byte length of the actual access bytes.
Moreover, unlike DeSo Access Bytes 1.0, which uses sha256, the 2.0 uses keccak256 to be compatible with the eth_sign.
Moreover, the access bytes are formatted differently so MetaMask can parse them in a human-readable format, as previously mentioned in Fig 2. and Fig 3.
The access bytes are composed using the function AssembleAccessBytesWithMetaMaskStrings. Here’s the line in the PR for reference. Importantly, this encoding is injective from the domain of TransactionSpendingLimits to bytes.
Additionally, while the images of encodings 1.0 and 2.0 might have a non-empty intersection, finding this intersection would be equivalent to breaking the collision resistance of sha-2 or sha-3 hash functions.
Given these properties, the dual encoding of derived key access bytes maintains safety.
DeSo-DER recoverable signature standard
Reference PR: https://github.com/deso-protocol/core/pull/380 (you should diff it with PR#379 to only see the recoverable signature code)
With this change, you can say goodbye to passing a derived public key in transaction ExtraData.
This change allows for compatibility of the MetaMask login with the DeSo Identity APIs so that all existing DeSo apps work with the new login.
The new signature format tells you whether a transaction was signed by the owner or by a derived key without having to pass any additional information in ExtraData.
If a transaction was signed by a derived key, core can extract the public key from the signature and validate it.
The new signature scheme combines the ideas from the DER and SEC/Compact ECDSA encodings into a non-malleable DER-like encoded signature with a recovery id in the header byte.
More formally, the DeSo-Der signature format for an (R, S) ECDSA signature looks as follows:
<0x30 + derivedRecoveryByte> <length of whole message> <0x02> <length of R> <R> <0x02> <length of S> <S>
Where the derivedRecoveryByte is either 0x00 if transaction was signed by the owner public key, or 0x01 + recoveryId which is a number between [0, 3] depending on which public key was used to produce the signature (note that there are theoretically at most 4 public keys that can produce a given (R, S) pair under message M).
As far as security is concerned, the primary attack vector appears to be malleability; however, given that public key recovery only works for derived keys (gated by the decisional 0x00 / 0x01 constant) and that derived public key must be known beforehand by the node, the malleability attacks are impossible.
Unlimited Derived Keys
Reference PR: https://github.com/deso-protocol/core/pull/384 (you should diff it with PR#380 to only see the unlimited derived keys code)
This change is quite straightforward, it enables authorizing derived keys with no transaction or spending restrictions.
This is done by adding a new boolean field to the TransactionSpendingLimits object called IsUnlimited.
NOTE: If you’re a developer, you should know that while an unlimited derived key might, on the surface, appear preferable, it should be approached with the utmost caution and generally discouraged.
If, hypothetically speaking, you do decide to use unlimited derived keys (which you shouldn’t), and somehow this information gets leaked, you’re putting yourself and your users at very costly risk. Instead, you should always use limited derived keys and accommodate authorizing a new key if the limits are exhausted.