next review round

This commit is contained in:
Alain Brenzikofer committed 2026-09-22 08:01:01 +02:00
1 parent faa96e3da7
commit 6b908c87e0
5 files changed
+104 -11

No files matched your search

+5 -6
View File
@@ -66,7 +66,7 @@ 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 (..), accountHeldByOther, bindAccount, createWalletSeed, deleteWalletSeed, getUserAccounts, getWalletSeed, resolveAccount)
import Simplex.Chat.Wallet (AccountIndex, AccountKey, WalletAddress (..), WalletError (..), accountSecret, checkAccountIndex, deriveAccountKey, entropyFromMnemonic, newSeedEntropy, renderAccountPath, seedMaster, seedMnemonic)
import Simplex.Chat.Wallet (AccountIndex, AccountKey, WalletAddress (..), WalletError (..), accountSecret, deriveAccountKey, entropyFromMnemonic, newSeedEntropy, renderAccountPath, seedMaster, seedMnemonic)
import Simplex.Messaging.Eth.Address (addressFromPrivateKey)
import Simplex.Chat.Call
import Simplex.Chat.Controller
@@ -1520,8 +1520,7 @@ processChatCommand cxt nm = \case
APIExportWalletMnemonic -> withUser $ \user ->
CRWalletMnemonic user <$> (liftWallet . seedMnemonic . wsEntropy =<< walletSeed)
APIExportWalletAccount n -> withUser $ \user@User {userId} -> do
seed <- walletSeed
liftWallet $ checkAccountIndex n
(seed, _) <- liftWallet =<< withFastStore' (`resolveAccount` Just n)
-- a key another profile holds is not this profile's to hand out
heldByOther <- withFastStore' $ \db -> accountHeldByOther db (wsId seed) userId n
when heldByOther $ throwWalletError WEAccountBound
@@ -6643,11 +6642,11 @@ chatCommandP =
quotedP = safeDecodeUtf8 <$> (A.char '"' *> A.takeTill (== '"') <* A.char '"')
text1P = safeDecodeUtf8 <$> A.takeTill (== ' ')
char_ = optional . A.char
-- ten digits fit an Int, and the hardening bound is a typed error when the command runs
-- a long digit run is not free to convert; the hardening bound is checked when the command runs
accountIndexP = do
ds <- A.takeWhile1 isDigit
case if B.length ds <= 10 then B.readInt ds else Nothing of
Just (i, _) | i <= fromIntegral (maxBound :: AccountIndex) -> pure $ fromIntegral i
case if B.length ds <= 10 then B.readInteger ds else Nothing of
Just (i, _) | i <= toInteger (maxBound :: AccountIndex) -> pure (fromInteger i)
_ -> fail "account index too large"
displayNameP :: Parser Text
@@ -1724,6 +1724,49 @@ ALTER TABLE test_chat_schema.users ALTER COLUMN user_id ADD GENERATED ALWAYS AS
CREATE TABLE test_chat_schema.wallet_accounts (
wallet_account_id bigint NOT NULL,
wallet_seed_id bigint NOT NULL,
account_index bigint,
user_id bigint,
CONSTRAINT wallet_accounts_account_index_check CHECK (((account_index >= 0) AND (account_index <= 2147483647)))
);
ALTER TABLE test_chat_schema.wallet_accounts ALTER COLUMN wallet_account_id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME test_chat_schema.wallet_accounts_wallet_account_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE test_chat_schema.wallet_seeds (
wallet_seed_id bigint NOT NULL,
entropy bytea NOT NULL,
next_account_index bigint,
single_seed smallint DEFAULT 1 NOT NULL,
CONSTRAINT wallet_seeds_entropy_check CHECK ((length(entropy) = 32)),
CONSTRAINT wallet_seeds_next_account_index_check CHECK (((next_account_index >= 0) AND (next_account_index <= '2147483648'::bigint)))
);
ALTER TABLE test_chat_schema.wallet_seeds ALTER COLUMN wallet_seed_id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME test_chat_schema.wallet_seeds_wallet_seed_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE test_chat_schema.xftp_file_descriptions (
file_descr_id bigint NOT NULL,
user_id bigint NOT NULL,
@@ -2137,6 +2180,16 @@ ALTER TABLE ONLY test_chat_schema.users
ALTER TABLE ONLY test_chat_schema.wallet_accounts
ADD CONSTRAINT wallet_accounts_pkey PRIMARY KEY (wallet_account_id);
ALTER TABLE ONLY test_chat_schema.wallet_seeds
ADD CONSTRAINT wallet_seeds_pkey PRIMARY KEY (wallet_seed_id);
ALTER TABLE ONLY test_chat_schema.xftp_file_descriptions
ADD CONSTRAINT xftp_file_descriptions_pkey PRIMARY KEY (file_descr_id);
@@ -2938,6 +2991,18 @@ CREATE INDEX idx_users_shown_badge ON test_chat_schema.users USING btree (shown_
CREATE INDEX idx_wallet_accounts_user_id ON test_chat_schema.wallet_accounts USING btree (user_id);
CREATE UNIQUE INDEX idx_wallet_accounts_wallet_seed_id_account_index ON test_chat_schema.wallet_accounts USING btree (wallet_seed_id, account_index);
CREATE UNIQUE INDEX idx_wallet_seeds_single_seed ON test_chat_schema.wallet_seeds USING btree (single_seed);
CREATE INDEX idx_xftp_file_descriptions_user_id ON test_chat_schema.xftp_file_descriptions USING btree (user_id);
@@ -3663,6 +3728,16 @@ ALTER TABLE ONLY test_chat_schema.users
ALTER TABLE ONLY test_chat_schema.wallet_accounts
ADD CONSTRAINT wallet_accounts_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE SET NULL;
ALTER TABLE ONLY test_chat_schema.wallet_accounts
ADD CONSTRAINT wallet_accounts_wallet_seed_id_fkey FOREIGN KEY (wallet_seed_id) REFERENCES test_chat_schema.wallet_seeds(wallet_seed_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.xftp_file_descriptions
ADD CONSTRAINT xftp_file_descriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE;
@@ -975,6 +975,18 @@ CREATE TABLE badge_code_redemptions(
created_at TEXT NOT NULL,
UNIQUE(user_id, code)
) STRICT;
CREATE TABLE wallet_seeds(
wallet_seed_id INTEGER PRIMARY KEY AUTOINCREMENT,
entropy BLOB NOT NULL CHECK(length(entropy) = 32), -- BIP-39 entropy, 24 words
next_account_index INTEGER CHECK(next_account_index BETWEEN 0 AND 2147483648),
single_seed INTEGER NOT NULL DEFAULT 1
) STRICT;
CREATE TABLE wallet_accounts(
wallet_account_id INTEGER PRIMARY KEY AUTOINCREMENT,
wallet_seed_id INTEGER NOT NULL REFERENCES wallet_seeds ON DELETE CASCADE,
account_index INTEGER CHECK(account_index BETWEEN 0 AND 2147483647),
user_id INTEGER REFERENCES users ON DELETE SET NULL
) STRICT;
CREATE INDEX contact_profiles_index ON contact_profiles(
display_name,
full_name
@@ -1538,6 +1550,12 @@ CREATE INDEX idx_badge_code_redemptions_user ON badge_code_redemptions(
CREATE UNIQUE INDEX idx_badge_purchases_code_redemption ON badge_purchases(
badge_code_redemption_id
);
CREATE UNIQUE INDEX idx_wallet_seeds_single_seed ON wallet_seeds(single_seed);
CREATE UNIQUE INDEX idx_wallet_accounts_wallet_seed_id_account_index ON wallet_accounts(
wallet_seed_id,
account_index
);
CREATE INDEX idx_wallet_accounts_user_id ON wallet_accounts(user_id);
CREATE TRIGGER on_group_members_insert_update_summary
AFTER INSERT ON group_members
FOR EACH ROW
+3 -5
View File
@@ -19,7 +19,7 @@ module Simplex.Chat.Store.Wallets
)
where
import Control.Monad (join, unless, when)
import Control.Monad (join, unless)
import Control.Monad.Except
import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteArray as BA
@@ -123,10 +123,8 @@ accountHeldByOther db sId userId n = heldByOther userId <$> accountUser db sId n
bindAccount :: DB.Connection -> UserId -> Maybe AccountIndex -> IO (Either WalletError ())
bindAccount db userId accountIdx_ = runExceptT $ do
(WalletSeed {wsId = sId}, n) <- ExceptT $ resolveAccount db accountIdx_
held <- liftIO $ accountUser db sId n
when (heldByOther userId held) $ throwError WEAccountBound
taken <- liftIO $ case held of
Just (Just _) -> pure True -- already this profile's
taken <- liftIO $ accountUser db sId n >>= \case
Just (Just heldBy) -> pure $ heldBy == userId
-- the update takes the account only while no profile holds it, the read after says whether this one got it
Just Nothing -> setAccountUser db sId userId n >> accountHeldBy db sId userId n
Nothing -> True <$ insertAccount db sId userId n
+3
View File
@@ -198,6 +198,9 @@ testWalletAddress ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do
alice ##> "/_wallet address account=3"
at3 <- getTermLine alice
words at3 !! 1 `shouldBe` "m/44'/60'/3'/0/0"
-- a zero-padded index is the same index
alice ##> "/_wallet address account=0000000003"
getTermLine alice `shouldReturn` at3
-- a malformed index is a parse error, never a silent bind of the next account
alice ##> "/_wallet bind account=abc"
alice <## "bad chat command: Failed reading: empty"