mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-06 05:37:47 +00:00
self-review iteration 3
This commit is contained in:
@@ -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 <= ?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user