Open lyoshenka opened 2 years ago
Inspiration: You mentioned zksnarks. That got me thinking here.
I had an idea for a small but significant change to your approach that I think could ease a few of our problems. But I wanted to run it by you before I go too deeply into using it for use cases and diagrams.
KDF(password) = (walletKey, downloadKey)
walletKey
does the same thing as before. downloadKey
authorizes downloading encryptedWallet
and nothing else.
wallet
contains a new key pair: loginPublicKey
and loginPrivateKey
which will never change.
An auth request (login or password change) contains:
loginPublicKey
(user ID)downloadKey
loginPrivateKey
The response is sessionToken
. It also updates the server with the latest supplied downloadKey
.
The sessionToken
can authenticate all requests other than the auth request above. downloadKey
, again, can only authenticate download requests.
From this point, loginKey
in the previous design is replaced by sessionToken
or downloadKey
, and it works roughly the same. downloadKey
is the only medium-to-long term secret sent across the wire. loginPrivateKey
is a new permanent secret, but it's stored with the rest of the keys to the kingdom anyway.
I can think of several ways that it makes this system at least a little bit smoother:
downloadKey
, but would probably have access to an encryptedWallet
by that point anywaywalletKey
and is playing the long game: use downloadKey
to download future versions of the wallet, to quietly capture more secrets. (No idea how important that is).loginKey
system, the server would only respond to the previous password, which the user may have forgotten. So we'd have to be extra sure this never happens. With the login key pair system, it's not such a crisis: the login keypair is unchanged, it's encrypted with the new password that the user presumably remembers, and we can just try a new auth request to update downloadKey
whenever the network is back up.It just seems like a tighter system. More naturally decentralized. Are there downsides I haven't thought of? Did LBRY already consider and reject this idea at some point?
Is this somehow a bad idea for Odysee? Our plan involved decrypting the wallet in-browser for Odysee anyway, right?
I can think of one hypothetical downside: Without taking the time to think too deeply about it, my intuition tells me that this design may make it easier to clobber conflicting changes between two devices if there's a password change involved, that the previous design would stop us from doing. We just have to think extra carefully about it. Perhaps related: loginKey
is always kept in sync with encryptedWallet
in your design, and maybe I need to change things around to preserve that.
Some quick thoughts about tightening things up a bit more:
downloadKey
be a key pair, or is the KDF only good enough for a symmetric key? If it can be a key pair, we could do the same signature trick and only send downloadPublicKey
for auth requests, removing the small "future wallets" threat above.loginPublicKey
and loginPrivateKey
? Or, use the seed feature to make it deterministic? This way, if a user "upgrades" two different LBRY clients to this new system, they won't accidentally end up generating two different login key pairs. That could be a mess.sessionToken
, but perhaps there are speed concerns and/or no particular benefit. Plus you wanted to do oauth which uses tokens.just to make sure i understand the process:
to make a new account, you send the server a loginPublicKey and a downloadKey
to get the wallet, you use the loginPublicKey and the downloadKey
to set the wallet, you use the loginPublicKey and downloadKey and sign the request with the loginPrivateKey (you didn't say this, im just making it up)
to make a session which lets you interact with a particular service, you send it a loginPublicKey, a downloadKey, a few others fields, and sign it all with the loginPrivateKey
is that right? so the idea is that you can't really do anything on a service until you get your wallet and get the private key out?
this seems like it could work. next step to verify that is probably to draw out all the interactions. you could get a similar effect by doing KDF(password) = (walletKey, downloadKey, loginPrivateKey)
but idk if you can get enough bytes from the KDF for that to be secure
can downloadKey be a key pair?
sure. a private key is just some random bytes, and you derive a public key from the private key.
just to make sure i understand the process:
I should have given some example interactions.
to get the wallet, you use the loginPublicKey and the downloadKey
Okay, this was a bit of an oversight on my part. I'll assume the hard case, that you're talking about the user installing onto a second device (or recovering from backup). I was originally thinking that you don't need the loginPublicKey for this part, only the downloadKey. However, the server still needs to know which user is requesting their encryptedWallet in the first place. Expecting the user to somehow know their loginPublicKey before they have their wallet on a new device is possible but obviously very unfriendly.
So, I suppose we'd still need a username field (email address, etc) separate from the id (loginPublicKey). We could allow changing the username, but their id wouldn't change. The username would be relatively low stakes just like downloadKey: it would only be used for downloading the encryptedWallet.
With that in mind:
So now, the user only needs a username and password to set up a new device. The server does not get the loginPublicKey for this part.
is that right? so the idea is that you can't really do anything on a service until you get your wallet and get the private key out?
Everything other than downloading encryptedWallet, yes.
to make a session which lets you interact with a particular service, you send it a loginPublicKey, a downloadKey, a few others fields, and sign it all with the loginPrivateKey
To perhaps clairfy, the only reason I mentioned sending downloadKey during this request was to set it on the server side, so that another client could download the encryptedWallet after. It could and perhaps should be sent in a different request.
Othrewise, yes.
to set the wallet, you use the loginPublicKey and downloadKey and sign the request with the loginPrivateKey (you didn't say this, im just making it up)
Again, don't need downloadKey here, unless we choose to set it in this request along with the wallet.
KDF(password) = (walletKey, downloadKey, loginPrivateKey)
Note that the only reason I made downloadKey separate from the login keypair was that it was available without the wallet. With what you have here, loginPrivateKey is available without the wallet already. So we can get rid of downloadKey (giving us more bytes for security) and just require the auth token to download.
KDF(password) = (walletKey, loginPrivateKey)
However, a change of password would mean change of loginPrivateKey and thus loginPublicKey, and thus a change of identity according to the design I've laid out. So, it's a different system but maybe that's fine. It would be like your loginKey system plus the benefit of no secret crossing the wire, so it still fixes the password breach problem.
But given that login keypair can change, you'd lose these benefits from above:
The first and last may be minor. Not sure about the middle. In general I like the elegance of an identity that never changes, but again maybe there's a downside I'm missing.
a change of identity according to the design I've laid out
i didnt think about that. so its a question of whether your identity is tied to something like an email address, or to a private key. we'll have to think about that
Putting this here so I don't forget: it would be nice if this also worked with social login. Maybe there's a way to get a string from the social login params and use that as a password? Or store a password with the social login service.
https://github.com/lbryio/lbry-desktop/issues/6792 https://github.com/lbryio/lbry-android/issues/1212
I made a suggestion to the LBRY desktop and mobile clients on better wallet management, I am for these features since it will allow your LBRY account/wallet to only have one seed phrase and not a whole bunch of seed phrases over time as you login to new devices.
(draft)
Motivation
Your wallet file stores important data that apps need to access. There's no convenient noncustodial way to back up your wallet or keep it synced across multiple apps. If you use several LBRY apps, you'll end up with many separate wallets.
Goals
noncustodial
multiapp
onepw
pwreset
realtime
wallets synced live between apps (at least it should feel like its live). relaxing this constraint since realtime stuff is moving out to federation or local storagedecentralized
userfriendly
Plan
1. move as much stuff out of the wallet as possible
encrypted blob in channel?unsynced, or the sync is done by the app2. hosted wallet sync and oauth
set up https://lbry.id service to host wallets and provide oauth (should be open-source and documented so anyone can run their own auth service)
support 2fa?
3. client-side signing via lbry.id or js or browser plugin (a la metamask)
Once this sync protocol is adopted, apps will need a way for users to sign transactions to interact with LBRY. This must be done client side to satisfy the
noncustodial
requirement. This can be done (in increasing order of security) custom in the app via JS, or using a popup like Deso, or a browser extension like Metamask (which could also do hardware wallet support).Terms
Server Operations
register
Register a new account
params: id, password, version response: authToken errors: version not supported, id already exists
oauth
Support the standard Oauth flows. We'll probably integrate with existing IDP project
getWallet
Get the current wallet data
params: authToken response: version, encryptedWallet, sequence errors: invalid authToken
putWallet
Store new wallet data
params: authToken, version, encryptedWallet, sequence response: ok errors: invalid authToken, version not supported, sequence mismatch
changePassword
This is the same as a putWallet, but also changes the loginKey.
params: authToken, version, loginKey, encryptedWallet, sequence response: ok errors: invalid authToken, version not supported, sequence mismatch
info
Get info about a user
params: authToken response: id, version, sequence
websocket
Connect to websocket to be notified when new wallet data is stored
params: authToken
Common flows
New account
TODO
Connect an app
TODO
Conflicting writes
TODO
I forgot my password, what can I do?
TODO
Multiple user apps (desktop + mobile + paper backup)
TODO
UX Considerations
Different wallets have different needs. If you just started an account, its no big deal to lose your wallet. If you have a lot of channels/claims/lbc in there, you want more backups and security.
Security
how to do this so its secure? what's the threat model?
prolly want an audit for this, but shouldn't block deployment
Rabbit holes
password resets
oauth and multiple apps
secure client-side signing via lbry.id
migrating odysee to this system
phishing
do we have to have the same password across multiple apps?
Extras
When unlocking, run sync first to make sure latest version is stored in cloud
Unlock via QR code or copying a string?
optional passcode to wrap root key locally
encrypt data with intermediate key, then encrypt that key with the password. this lets you back up your wallet encrpytion key on paper
does this work with multiple apps? can you have separate passwords across multiple apps? prolly not… so then a malicious app could get your odysee password
can you login with zksnarks? proof you know some encrypted string without revealing that string?
tell ppl when their passwords are poor using haveibeenpwned.com
outline what changes sdk has to make
should we store an
updatedAt
timestamp for wallet changes?Alternative ideas
no wallets. everything is on-chain and can be restored from seed
store wallet in public cloud (s3, google drive, etc). define storage location in channel claim.
onepw
requirementRelated issues
https://github.com/lbryio/lbry-sdk/issues/2641
References