self-review iteration 3

This commit is contained in:
Alain Brenzikofer
2026-09-24 15:47:36 +02:00
parent 68c84a9e75
commit 24ef8bf1f5
11 changed files with 119 additions and 119 deletions
+4 -4
View File
@@ -65,7 +65,6 @@ import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind (..), BadgeIss
import Simplex.Chat.Badges.Code (badgeCodeText, parseBadgeCode)
import Simplex.Chat.Badges.Service (BadgeBalance (..), BadgeServiceCommand (..), BadgeServiceErrorCode (..), BadgeServiceRequest (..), BadgeServiceResponse (..), BadgeStatement (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..), currentBadgeServiceVersion)
import Simplex.Chat.Names (SimplexDomainProof (..), SimplexDomainClaim (..), claimDomain, mkDomainClaim)
import Simplex.Chat.Store.Wallets (WalletSeed (..), accountHeldBy, bindAccount, createWalletSeed, deleteWalletSeed, getUserAccounts, getWalletSeed, resolveAccount)
import Simplex.Chat.Wallet (AccountIndex, AccountKey, WalletAddress, WalletError (..), accountSecret, deriveAccount, entropyFromMnemonic, newSeedEntropy, seedMnemonic)
import Simplex.Chat.Call
import Simplex.Chat.Controller
@@ -97,6 +96,7 @@ import Simplex.Chat.Store.Messages
import Simplex.Chat.Store.NoteFolders
import Simplex.Chat.Store.Profiles
import Simplex.Chat.Store.Shared
import Simplex.Chat.Store.Wallets (WalletSeed (..), accountHeldBy, bindAccount, createWalletSeed, deleteWalletSeed, getUserAccounts, getWalletSeed, resolveAccount)
import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
@@ -1507,7 +1507,7 @@ processChatCommand cxt nm = \case
-- the counter starts at 0 for a generated seed and is unknown for an imported one
(entropy, nextAccount) <- case mnemonic_ of
Nothing -> (,Just 0) <$> (asks random >>= atomically . newSeedEntropy)
Just phrase -> (,Nothing) <$> liftWallet (entropyFromMnemonic $ encodeUtf8 phrase)
Just phrase -> (,Nothing) <$> liftWallet (entropyFromMnemonic phrase)
created <- withFastStore' $ \db -> createWalletSeed db entropy nextAccount
unless created $ throwWalletError WEMasterExists
pure $ CRWallet user (Just [])
@@ -6029,13 +6029,13 @@ throwWalletError :: WalletError -> CM a
throwWalletError = throwChatError . CEWallet
liftWallet :: Either WalletError a -> CM a
liftWallet = either throwWalletError pure
liftWallet = liftEitherWith (ChatError . CEWallet)
withWalletStore :: (DB.Connection -> IO (Either WalletError a)) -> CM a
withWalletStore action = liftWallet =<< withFastStore' action
seedAccount :: WalletSeed -> AccountIndex -> CM (AccountKey, WalletAddress)
seedAccount WalletSeed {wsEntropy} n = liftWallet =<< liftIO (deriveAccount wsEntropy n)
seedAccount WalletSeed {wsEntropy} n = liftError' (ChatError . CEWallet) (deriveAccount wsEntropy n)
chatCommandP :: Parser ChatCommand
chatCommandP =
@@ -28,4 +28,4 @@ CREATE UNIQUE INDEX idx_wallet_accounts_wallet_seed_id_account_index ON wallet_a
CREATE INDEX idx_wallet_accounts_user_id ON wallet_accounts(user_id);
|]
-- no reverse step, see the SQLite migration
-- no down migration, see the SQLite migration
@@ -27,4 +27,4 @@ CREATE UNIQUE INDEX idx_wallet_accounts_wallet_seed_id_account_index ON wallet_a
CREATE INDEX idx_wallet_accounts_user_id ON wallet_accounts(user_id);
|]
-- No reverse step: it would drop the only copy of the master entropy.
-- No down migration: it would delete the master entropy, which may have no other copy.
@@ -4416,6 +4416,14 @@ Query:
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE wallet_accounts SET user_id = ?
WHERE wallet_seed_id = ? AND account_index = ? AND user_id IS NULL
RETURNING wallet_account_id
Plan:
SEARCH wallet_accounts USING INDEX idx_wallet_accounts_wallet_seed_id_account_index (wallet_seed_id=? AND account_index=?)
Query:
WITH user_groups AS MATERIALIZED (
SELECT g.group_id
@@ -5723,13 +5731,6 @@ Query:
Plan:
SEARCH users USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE wallet_accounts SET user_id = ?
WHERE wallet_seed_id = ? AND account_index = ? AND user_id IS NULL
Plan:
SEARCH wallet_accounts USING INDEX idx_wallet_accounts_wallet_seed_id_account_index (wallet_seed_id=? AND account_index=?)
Query:
UPDATE wallet_seeds SET next_account_index = ?
WHERE wallet_seed_id = ? AND next_account_index IS NOT NULL AND next_account_index <= ?
+14 -13
View File
@@ -91,7 +91,7 @@ getUserAccounts db sId userId =
|]
(sId, userId)
-- | Which profile holds an account: 'Nothing' when it is unknown, @Just Nothing@ when no profile holds it.
-- | Which profile holds an account: 'Nothing' when there is no row for it, @Just Nothing@ when no profile holds it.
accountUser :: DB.Connection -> SeedId -> AccountIndex -> IO (Maybe (Maybe UserId))
accountUser db sId n =
maybeFirstRow fromOnly $
@@ -100,24 +100,25 @@ accountUser db sId n =
bindAccount :: DB.Connection -> UserId -> Maybe AccountIndex -> IO (Either WalletError (WalletSeed, AccountIndex))
bindAccount db userId accountIdx_ = runExceptT $ do
r@(WalletSeed {wsId}, n) <- ExceptT $ resolveAccount db accountIdx_
taken <- liftIO $ accountUser db wsId n >>= \case
held <- liftIO $ accountUser db wsId n >>= \case
Just (Just heldBy) -> pure $ heldBy == userId
-- the update sets user_id only while it is NULL, so the read after it shows which profile holds the account
Just Nothing -> setAccountUser db wsId userId n >> accountHeldBy db wsId userId n
Just Nothing -> setAccountUser db wsId userId n
Nothing -> True <$ insertAccount db wsId userId n
unless taken $ throwError WEAccountBound
unless held $ throwError WEAccountBound
liftIO $ raiseNextAccount db wsId n
pure r
setAccountUser :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO ()
setAccountUser :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO Bool
setAccountUser db sId userId n =
DB.execute
db
[sql|
UPDATE wallet_accounts SET user_id = ?
WHERE wallet_seed_id = ? AND account_index = ? AND user_id IS NULL
|]
(userId, sId, n)
fmap isJust . maybeFirstRow (fromOnly @Int64) $
DB.query
db
[sql|
UPDATE wallet_accounts SET user_id = ?
WHERE wallet_seed_id = ? AND account_index = ? AND user_id IS NULL
RETURNING wallet_account_id
|]
(userId, sId, n)
accountHeldBy :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO Bool
accountHeldBy db sId userId n = (== Just (Just userId)) <$> accountUser db sId n
+2 -2
View File
@@ -1119,10 +1119,10 @@ walletErrorText = \case
WENoMaster -> "this device has no wallet"
WEMasterExists -> "this device already has a wallet"
WEBadMnemonic -> "not a valid 24 word recovery phrase"
WEHiddenProfile -> "a hidden profile cannot own an account"
WEHiddenProfile -> "a hidden profile cannot hold an account"
WEAccountBound -> "another profile holds this account"
WEAccountNotHeld -> "this profile does not hold this account"
WECounterUnknown -> "the next account is unknown after an import, scan the chain first"
WECounterUnknown -> "the next account is unknown after an import"
WEIndexTooLarge -> "account index is too large to harden"
WEDerivation e -> "derivation failed: " <> T.pack e
+1 -2
View File
@@ -23,7 +23,6 @@ import qualified Data.Aeson.TH as JQ
import Data.Bifunctor (bimap, first)
import qualified Data.ByteArray as BA
import qualified Data.ByteArray.Encoding as BAE
import Data.ByteString (ByteString)
import Data.Text (Text)
import Data.Text.Encoding (decodeLatin1)
import Data.Word (Word32)
@@ -69,7 +68,7 @@ masterStrength = B39.MS256
newSeedEntropy :: TVar ChaChaDRG -> STM BA.ScrubbedBytes
newSeedEntropy g = B39.mnemonicToEntropy <$> B39.randomMnemonic masterStrength g
entropyFromMnemonic :: ByteString -> Either WalletError BA.ScrubbedBytes
entropyFromMnemonic :: Text -> Either WalletError BA.ScrubbedBytes
entropyFromMnemonic phrase = case B39.parseMnemonic phrase of
Right m | length (B39.mnemonicWords m) == B39.strengthWordCount masterStrength ->
Right $ B39.mnemonicToEntropy m