mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 08:33:09 +00:00
core: create makes the seed, bind claims a path
The two were one command: on a device that already had a seed, /_wallet create only bound a profile. Now create makes the seed and refuses when one exists, and binds every profile that exists, as a new seed has no account that already owns a name. /_wallet import binds nothing. Which account a profile had is what an import is recovering, and the seed does not say, so the profile says it with /_wallet bind <account>. /_wallet bind with no account takes the next free one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rvc3HbiWBTqbAvRT45G5oX
This commit is contained in:
1 parent
07acdbb85b
commit
9cba89aa17
4 files changed
+80
-56
No files matched your search
@@ -8,13 +8,14 @@ module Simplex.Chat.Store.Wallets
|
||||
( getDeviceSeed,
|
||||
getAccountIndex,
|
||||
getSeedProfiles,
|
||||
getOrCreateAccountRef,
|
||||
createSeed,
|
||||
importSeed,
|
||||
bindAccountIndex,
|
||||
deleteSeed,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (forM_)
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Data.Text (Text)
|
||||
@@ -62,47 +63,53 @@ getSeedProfiles db (SeedId sId) User {userId} =
|
||||
|]
|
||||
(sId, userId)
|
||||
|
||||
bindAccount :: DB.Connection -> User -> SeedId -> AccountIndex -> IO ()
|
||||
bindAccount db User {userId} (SeedId sId) acct =
|
||||
bindUser :: DB.Connection -> Int64 -> SeedId -> Int64 -> IO ()
|
||||
bindUser db uId (SeedId sId) acct =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE users SET wallet_seed_id = ?, wallet_account_index = ? WHERE user_id = ?"
|
||||
(sId, fromIntegral acct :: Int64, userId)
|
||||
(sId, acct, uId)
|
||||
|
||||
getOrCreateAccountRef :: DB.Connection -> User -> ByteString -> IO ()
|
||||
getOrCreateAccountRef db user entropy =
|
||||
getAccountIndex db user >>= \case
|
||||
Just _ -> pure ()
|
||||
Nothing -> getDeviceSeed db >>= maybe (createWalletSeed db entropy) pure >>= bindNewAccount db user
|
||||
|
||||
-- | False if the device already has a key.
|
||||
importSeed :: DB.Connection -> User -> ByteString -> IO Bool
|
||||
importSeed db user entropy =
|
||||
-- | False if the device already has a key. Every profile is bound, as a new
|
||||
-- seed has no account that already owns a name.
|
||||
createSeed :: DB.Connection -> ByteString -> IO Bool
|
||||
createSeed db entropy =
|
||||
getDeviceSeed db >>= \case
|
||||
Just _ -> pure False
|
||||
Nothing -> True <$ (createWalletSeed db entropy >>= bindNewAccount db user)
|
||||
|
||||
-- | False if another profile holds the account. The counter moves past it, so
|
||||
-- the next profile is not handed the same one.
|
||||
bindAccountIndex :: DB.Connection -> User -> SeedId -> AccountIndex -> IO Bool
|
||||
bindAccountIndex db user sId@(SeedId sId') acct = do
|
||||
taken <- maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
"SELECT 1 FROM users WHERE wallet_seed_id = ? AND wallet_account_index = ? AND user_id != ?"
|
||||
(sId', fromIntegral acct :: Int64, userId user)
|
||||
case (taken :: Maybe Int64) of
|
||||
Just _ -> pure False
|
||||
Nothing -> do
|
||||
bindAccount db user sId acct
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE wallet_seeds SET next_account_index = ? WHERE wallet_seed_id = ? AND next_account_index <= ?"
|
||||
(fromIntegral acct + 1 :: Int64, sId', fromIntegral acct :: Int64)
|
||||
s <- createWalletSeed db entropy
|
||||
uIds <- map fromOnly <$> DB.query_ db "SELECT user_id FROM users ORDER BY user_id"
|
||||
forM_ (zip uIds [0 ..]) $ \(uId, acct) -> bindUser db uId (wsId s) acct
|
||||
setNextAccountIndex db (wsId s) (fromIntegral $ length uIds)
|
||||
pure True
|
||||
|
||||
bindNewAccount :: DB.Connection -> User -> WalletSeed -> IO ()
|
||||
bindNewAccount db user s = takeAccountIndex db (wsId s) >>= bindAccount db user (wsId s)
|
||||
-- | False if the device already has a key. No profile is bound: which account
|
||||
-- a profile had is what the import is recovering, and the seed does not say.
|
||||
importSeed :: DB.Connection -> ByteString -> IO Bool
|
||||
importSeed db entropy =
|
||||
getDeviceSeed db >>= \case
|
||||
Just _ -> pure False
|
||||
Nothing -> True <$ createWalletSeed db entropy
|
||||
|
||||
-- | Without an account the next free one is taken. False if another profile
|
||||
-- holds the account asked for.
|
||||
bindAccountIndex :: DB.Connection -> User -> SeedId -> Maybe AccountIndex -> IO Bool
|
||||
bindAccountIndex db User {userId} sId@(SeedId sId') = \case
|
||||
Nothing -> True <$ (takeAccountIndex db sId >>= bindUser db userId sId)
|
||||
Just acct -> do
|
||||
taken <-
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
"SELECT 1 FROM users WHERE wallet_seed_id = ? AND wallet_account_index = ? AND user_id != ?"
|
||||
(sId', fromIntegral acct :: Int64, userId)
|
||||
case (taken :: Maybe Int64) of
|
||||
Just _ -> pure False
|
||||
Nothing -> do
|
||||
bindUser db userId sId (fromIntegral acct)
|
||||
-- the counter moves past it, so the next profile is not handed the same one
|
||||
setNextAccountIndex db sId (fromIntegral acct + 1)
|
||||
pure True
|
||||
|
||||
createWalletSeed :: DB.Connection -> ByteString -> IO WalletSeed
|
||||
createWalletSeed db entropy = do
|
||||
@@ -111,18 +118,21 @@ createWalletSeed db entropy = do
|
||||
pure WalletSeed {wsId = SeedId sId, wsEntropy = entropy}
|
||||
|
||||
-- | Incremented in SQL, so two profiles cannot be handed the same account.
|
||||
takeAccountIndex :: DB.Connection -> SeedId -> IO AccountIndex
|
||||
takeAccountIndex db sId@(SeedId sId') = do
|
||||
DB.execute db "UPDATE wallet_seeds SET next_account_index = next_account_index + 1 WHERE wallet_seed_id = ?" (Only sId')
|
||||
subtract 1 <$> getNextAccountIndex db sId
|
||||
|
||||
getNextAccountIndex :: DB.Connection -> SeedId -> IO AccountIndex
|
||||
getNextAccountIndex db (SeedId sId) =
|
||||
maybe 0 (fromIntegral :: Int64 -> AccountIndex)
|
||||
takeAccountIndex :: DB.Connection -> SeedId -> IO Int64
|
||||
takeAccountIndex db (SeedId sId) = do
|
||||
DB.execute db "UPDATE wallet_seeds SET next_account_index = next_account_index + 1 WHERE wallet_seed_id = ?" (Only sId)
|
||||
maybe 0 (subtract 1)
|
||||
<$> ( maybeFirstRow fromOnly $
|
||||
DB.query db "SELECT next_account_index FROM wallet_seeds WHERE wallet_seed_id = ?" (Only sId)
|
||||
)
|
||||
|
||||
setNextAccountIndex :: DB.Connection -> SeedId -> Int64 -> IO ()
|
||||
setNextAccountIndex db (SeedId sId) acct =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE wallet_seeds SET next_account_index = ? WHERE wallet_seed_id = ? AND next_account_index < ?"
|
||||
(acct, sId, acct)
|
||||
|
||||
-- | Profiles are unbound first, as the foreign key is ON DELETE RESTRICT.
|
||||
deleteSeed :: DB.Connection -> SeedId -> IO ()
|
||||
deleteSeed db (SeedId sId) = do
|
||||
|
||||
Reference in new issue
Block a user