mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-28 15:38:56 +00:00
core: create binds no profile either
Create and import differed only in whether they bound a profile, which made the convenient one inconsistent. Neither binds now, so the store keeps one function for both, and a profile reaches an account only through bind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rvc3HbiWBTqbAvRT45G5oX
This commit is contained in:
co-authored by
Claude Opus 5
parent
c5b44fe848
commit
1e0c3503e0
@@ -58,7 +58,7 @@ import qualified Data.UUID.V4 as V4
|
||||
import Simplex.Chat.Library.Subscriber
|
||||
import Simplex.Chat.Badges (BadgeCredential (..), LocalBadge (..), badgeServerCredential, maxXFTPFileSize, mkBadgeStatus, verifyCredential)
|
||||
import Simplex.Chat.Names (SimplexDomainProof (..), SimplexDomainClaim (..), claimDomain, mkDomainClaim)
|
||||
import Simplex.Chat.Store.Wallets (bindAccountIndex, createSeed, deleteSeed, getAccountIndex, getDeviceSeed, getSeedProfiles, importSeed)
|
||||
import Simplex.Chat.Store.Wallets (bindAccountIndex, createSeed, deleteSeed, getAccountIndex, getDeviceSeed, getSeedProfiles)
|
||||
import Simplex.Chat.Wallet (NameIndex, WalletSeed (..), accountAddress, accountSecret, deriveNameKey, importRecoveryKey, newSeed, recoveryKeyPhrase, renderNameKeyPath)
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Controller
|
||||
@@ -1515,8 +1515,8 @@ processChatCommand cxt nm = \case
|
||||
processChatCommand cxt nm APIWallet
|
||||
APIWalletImport phrase -> withUser $ \_ -> do
|
||||
entropy <- either (const $ throwCmdError "bad recovery phrase") pure $ importRecoveryKey (encodeUtf8 phrase)
|
||||
imported <- withFastStore' $ \db -> importSeed db entropy
|
||||
unless imported $ throwCmdError "this device already has a wallet key"
|
||||
created <- withFastStore' $ \db -> createSeed db entropy
|
||||
unless created $ throwCmdError "this device already has a wallet key"
|
||||
processChatCommand cxt nm APIWallet
|
||||
APIWalletExportSeedMnemonic -> withUser $ \user -> do
|
||||
seed <- withFastStore' getDeviceSeed >>= maybe (throwCmdError noKeyError) pure
|
||||
|
||||
@@ -9,17 +9,14 @@ module Simplex.Chat.Store.Wallets
|
||||
getAccountIndex,
|
||||
getSeedProfiles,
|
||||
createSeed,
|
||||
importSeed,
|
||||
bindAccountIndex,
|
||||
deleteSeed,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (forM_)
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Data.Text (Text)
|
||||
import Simplex.Chat.Store.Shared (insertedRowId)
|
||||
import Simplex.Chat.Types (User (..))
|
||||
import Simplex.Chat.Wallet (AccountIndex, SeedId (..), WalletSeed (..))
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow)
|
||||
@@ -70,26 +67,13 @@ bindUser db uId (SeedId sId) acct =
|
||||
"UPDATE users SET wallet_seed_id = ?, wallet_account_index = ? WHERE user_id = ?"
|
||||
(sId, acct, uId)
|
||||
|
||||
-- | False if the device already has a key. Every profile is bound, as a new
|
||||
-- seed has no account that already owns a name.
|
||||
-- | False if the device already has a key. No profile is bound, as which
|
||||
-- account a profile takes is said with bind.
|
||||
createSeed :: DB.Connection -> ByteString -> IO Bool
|
||||
createSeed db entropy =
|
||||
getDeviceSeed db >>= \case
|
||||
Just _ -> pure False
|
||||
Nothing -> do
|
||||
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
|
||||
|
||||
-- | 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
|
||||
Nothing -> True <$ DB.execute db "INSERT INTO wallet_seeds (seed) VALUES (?)" (Only entropy)
|
||||
|
||||
-- | Without an account the next free one is taken. False if another profile
|
||||
-- holds the account asked for.
|
||||
@@ -111,12 +95,6 @@ bindAccountIndex db User {userId} sId@(SeedId sId') = \case
|
||||
setNextAccountIndex db sId (fromIntegral acct + 1)
|
||||
pure True
|
||||
|
||||
createWalletSeed :: DB.Connection -> ByteString -> IO WalletSeed
|
||||
createWalletSeed db entropy = do
|
||||
DB.execute db "INSERT INTO wallet_seeds (seed) VALUES (?)" (Only entropy)
|
||||
sId <- insertedRowId db
|
||||
pure WalletSeed {wsId = SeedId sId, wsEntropy = entropy}
|
||||
|
||||
-- | Incremented in SQL, so two profiles cannot be handed the same account.
|
||||
takeAccountIndex :: DB.Connection -> SeedId -> IO Int64
|
||||
takeAccountIndex db (SeedId sId) = do
|
||||
|
||||
@@ -70,19 +70,20 @@ testWalletCreate ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
alice <## "no wallet key"
|
||||
alice ##> "/_wallet export"
|
||||
alice <## "bad chat command: no wallet key on this device"
|
||||
alice ##> "/create user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
-- a new seed has no account that owns a name, so every profile is bound
|
||||
-- creating the seed binds no profile to an account
|
||||
alice ##> "/_wallet create"
|
||||
alice <## "this profile has no wallet key"
|
||||
alice ##> "/_wallet bind"
|
||||
rows <- nameRows alice
|
||||
alice <## "also on same seed: alice"
|
||||
map fst rows `shouldBe` ["m/44'/60'/1'/0/0", "m/44'/60'/1'/0/1"]
|
||||
map fst rows `shouldBe` ["m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1"]
|
||||
length (nub $ map snd rows) `shouldBe` 2
|
||||
|
||||
testWalletPersists :: HasCallStack => TestParams -> IO ()
|
||||
testWalletPersists ps = do
|
||||
rows <- withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
alice ##> "/_wallet create"
|
||||
alice <## "this profile has no wallet key"
|
||||
alice ##> "/_wallet bind"
|
||||
nameRows alice
|
||||
-- same database, new session: a name bought at that address must stay reachable
|
||||
withTestChat ps "alice" $ \alice -> do
|
||||
@@ -93,6 +94,8 @@ testWalletPersists ps = do
|
||||
testWalletSecondProfile :: HasCallStack => TestParams -> IO ()
|
||||
testWalletSecondProfile ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
alice ##> "/_wallet create"
|
||||
alice <## "this profile has no wallet key"
|
||||
alice ##> "/_wallet bind"
|
||||
rows <- nameRows alice
|
||||
alice ##> "/_wallet export"
|
||||
phrase <- getTermLine alice
|
||||
|
||||
Reference in New Issue
Block a user