Ramblings
The following is essentially just my ramblings about changes I made to keyvault’s architectural design and thoughts about how else I’d like to modify keyvault’s architectural design.
Btw, the consolidated GitHub repo is GitHub - lousydropout/keyvault: Password manager built on the Astar network. It has been consolidated from
Dumb issues I encountered and stuff like that
Finite State Machines (part 1)
Chrome extension’s FSM hook: password-manager-extension/src/hooks/useFiniteStateMachine.ts at main · lousydropout/password-manager-extension · GitHub
Webapp’s FSM hook: password-manager/frontend/src/hooks/useFiniteStateMachine.ts at main · lousydropout/password-manager · GitHub
Initially, I had imagined keyvault’s webapp and chrome extension being able to communicate with each other seamlessly. In order to do this, they’d each need to be able to respond both to any and all valid incoming messages from the other side AND messages from within. So, finite state machines sound like the perfect fit, right? Well, it may or may not be, but my implementation certainly was not. In brief, the state machine ended up being overly complicated even though FSMs, I’d imagine, are supposed to help simplify things by providing organization and clarity. Instead, I occasionally opted to bypass the state machine out of laziness, so states started changing without the state machine being aware. Then, there were also a bunch of “autoreplies” from one state machine to the other’s messages, and, before long, 10+ messages have been passed between the two. Those were a nightmare to try and debug.
So, in the new version, I got rid of state machines. Users will now need to click the occasional button or copy and paste their pubkey from Metamask or Subwallet. That’s probably worse UX, but much better for my sanity.
Finite State Machines (part 2) and Chrome Storage
Chrome storage hook: password-manager-extension/src/hooks/useChromeLocalStorage.ts at main · lousydropout/password-manager-extension · GitHub
For context, this useChromeLocalStorage
hook is a custom React hook that also reads and stores the state into chrome.storage.local
. This turned out to be pretty cool since it both provided persistent storage and acted as a global state manager. So, think something like Zustand or Redux but with the added persistent storage.
The issue ended being that both my finite state machines and this useChromeLocalStorage
hook were modifying, occasionally, the same states. So, using both just became a headache.
Finite State Machines (part 3)
So, what was the before and after?
Before: Chrome extension and webapp communicated with each other. Webapp submits transactions to blockchain. Chrome extension reads data from smart contract via AWS Lambda (see next section).
After: Chrome extension sends messages to webapp (thus far, none going the other way). Webapp continues to submit transactions to blockchain. Chrome extension reads data directly from blockchain without AWS Lambda.
Summary:
- Webapp no longer sends messages to Chrome extension and, so, no more state machines.
- No more webassembly and so no need for AWS Lambda.
@polkadot/api-contract
and webassembly
It turned out @polkadot/api-contract
makes use of webassembly and Chrome extensions do not like that (I dunno why). So, I packaged the needed node modules into an AWS Lambda Layer package and used AWS Lambda as a proxy API.
Viem
, the typescript library used to interact with EVM chains, apparently doesn’t contain any webassembly bits (maybe it’s a Polkadot thing because it uses Rust?). Took me a while to realize this but, once I did, I got rid of AWS Lambda.
A design I wish I thought of earlier
One thing I really want is for users to know there is no middleman that can get in between them and their passwords.
Having a verified smart contract that users can confirm doesn’t contain any malicious code goes a long way in my opinion, especially when the main alternative is a private company who gates access.
However, I recently learned about solidity’s delegatecall
, which allows a contract (let’s call it A
) execute another contract’s (say, B
’s) function in A
. The result is that it’s A
’s states that changes and not B
’s even thought it’s B
’s function that was executed.
If I had known earlier, I think I’d want each user to deploy their own contract A
that keeps track of their ciphertexts and that delegatecalls functions in B
as appropriate instead of B
storing everybody’s ciphertext. Then, A
might also have an additional field that keeps track of B
’s address and a function that can switch from B
’s address to that of their own deployed version of B
if something happens to B
(e.g. some sort of malicious action such as hiking the fee from the current zero to some large number).
Oh, well. maybe for keyvault version 2.
An idiotic mistake
One thing I only realized after submitting a verified contract’s address is that there was a useful function I neglected to include.
So, the keyvault smart contract keeps track of ciphertexts and who those ciphertexts belong to. And, I have a storeEntry
function for that.
However, storeEntry
accepts a single ciphertext. So, if a user creates/updates/deletes, say, N
credentials, that user will need to approve the storeEntry
transaction N
times. Not great for UX.
I’m obviously gonna deploy a new version with a storeEntries
function. I just wish I noticed prior to submitting the address of a verified contract that lacks this function.