- FIP: 68
- title: Login into Portable account with metamask
- author: Sabyasachi Patra, @asabya
- status: draft
- created: 2023-03-01
Addressing web3 login workflow to portable account where users can retrieve their wallet in a decentralized manner from browser with metamask.
The solution worked out here allows to log in to the user account from any browser into the same account described in FIP-59 created with fdp-create-account project.
For the login to work with metamask, user should already have a portable account created with fdp-create-account.
As we know while creating a portable account users can provide a 12 word mnemonic phrase for the portable wallet or a 12 word mnemonic will be generated for them.
This solution starts with importing the portable wallet into metamask. After that it is a two-step process
ConnectPortableAccountWithWallet(userName, passPhrase, addressHex, signature string) errorThis function will get the portable wallet (wallet seed) and re-upload an alternate socTopic described later
LoginWithWallet(addressHex, signature string) (*user.Info, error) This function will get the encrypted portable wallet (wallet seed) and username from the alternate socTopic and decrypt it with the signature provided by the user.
In FIP-59 we have discussed, about the socTopic which is a topic in the swarm where the portable account is stored. In this solution we will be using an alternate socTopic, but with the same logic, to store the wallet seed.
socTopic = H(fdpLoginVersion + portableWalletAddress + signature)
Where portableWalletAddress and the signature will be provided by the user. As we have already imported the portable wallet into metamask, we can get the portableWalletAddress from there. The signature will be generated by signing the portableWalletAddress with metamask with the portable wallet.
In FIP-59 we have discussed, about the encryption of wallet seed and storing it in a chunk.
- We will first generate FIP-59 socTopic with username and password provided by the user.
- Then download the wallet seed chunk from FIP-59 socTopic and decrypt it with the password provided by the user.
We have the seed now. We will store the username with the seed for identifying the user while logging in with metamask.
The content of the chunk will look something lite this
SEED + USERNAME_SIZE + USERNAME + RANDOM_DATA
We store the username length in 8 bytes, then the username and then the random data.
The SEED + USERNAME_SIZE + USERNAME will be padded with random data with byte length CHUNK_SIZE - SEED_SIZE - IV_LENGTH - USERNAME_LENGTH - USERNAME_SIZE.
*** USERNAME_SIZE is 8 bytes, USERNAME_LENGTH is the length of the username provided by the user.
The resulted data will be encrypted with AES, where the encryption key is the SHA256 hash of the signature.
func (*Info) PadSeedName(seed []byte, username string, passphrase string) ([]byte, error) {
usernameLength := len(username)
endIndexBytes := make([]byte, nameSize)
binary.LittleEndian.PutUint64(endIndexBytes, uint64(usernameLength))
paddingLength := utils.MaxChunkLength - aes.BlockSize - seedSize - nameSize - usernameLength
randomBytes, err := utils.GetRandBytes(paddingLength)
if err != nil {
return nil, err
}
chunkData := make([]byte, 0, utils.MaxChunkLength)
chunkData = append(chunkData, seed...)
chunkData = append(chunkData, endIndexBytes...)
chunkData = append(chunkData, []byte(username)...)
chunkData = append(chunkData, randomBytes...)
encryptedBytes, err := utils.EncryptBytes([]byte(passphrase), chunkData)
if err != nil {
return nil, fmt.Errorf("seed padding failed: %w", err)
}
return encryptedBytes, nil
}We decrypt the chunk with the same signature and then get the seed and username from the decrypted data.
func (*Info) RemovePadFromSeedName(paddedSeed []byte, passphrase string) ([]byte, string, error) {
decryptedBytes, err := utils.DecryptBytes([]byte(passphrase), paddedSeed)
if err != nil {
return nil, "", fmt.Errorf("seed decryption failed: %w", err)
}
usernameLength := int(binary.LittleEndian.Uint64(decryptedBytes[seedSize : seedSize+nameSize]))
return decryptedBytes[:seedSize], string(decryptedBytes[seedSize+nameSize : seedSize+nameSize+usernameLength]), nil
}Implementation can be found here
Copyright and related rights waived via CC0.