mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-05 23:07:57 +00:00
review iterations
This commit is contained in:
@@ -69,8 +69,9 @@ import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Util (liftIOEither)
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletAddress, WalletError)
|
||||
import Simplex.Chat.Wallet (WalletAddress, WalletError, WalletInfo)
|
||||
import Simplex.FileTransfer.Description (FileDescriptionURI)
|
||||
import Simplex.Messaging.Crypto.BIP44 (AccountIndex)
|
||||
import Simplex.Messaging.Server.Information (ServerPublicInfo)
|
||||
import Simplex.Messaging.Agent (AgentClient, DatabaseDiff, SubscriptionsInfo)
|
||||
import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo)
|
||||
@@ -879,7 +880,7 @@ data ChatResponse
|
||||
| CRBadgeRedeemed {user :: User, redeemedBadge :: LocalBadge, newBadge :: Bool, badgeState :: Maybe BadgeState}
|
||||
| CRBadgeState {user :: User, badgeState :: Maybe BadgeState}
|
||||
| CRBadgeLedger {user :: User, badgeLedger :: [StatementEntry]}
|
||||
| CRWallet {user :: User, accountIndexes_ :: Maybe [AccountIndex]}
|
||||
| CRWallet {user :: User, walletInfo_ :: Maybe WalletInfo}
|
||||
| CRWalletMnemonic {user :: User, mnemonic :: Text}
|
||||
| CRWalletAddress {user :: User, walletAddress :: WalletAddress}
|
||||
| CRWalletAccountSecret {user :: User, walletAddress :: WalletAddress, secret :: Text}
|
||||
|
||||
@@ -65,7 +65,7 @@ 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.Wallet (AccountIndex, AccountKey, WalletAddress, WalletError (..), accountSecret, deriveAccount, importWalletMaster, newWalletMaster, seedMnemonic)
|
||||
import Simplex.Chat.Wallet (AccountKey, WalletAddress, WalletError (..), WalletInfo (..), accountSecret, deriveAccount, importWalletMaster, masterMnemonic, newWalletMaster)
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Delivery (DeliveryJobScope (..), DeliveryJobSpec (..), DeliveryWorkerScope (..))
|
||||
@@ -96,7 +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 (Wallet (..), accountHeldBy, bindAccount, createWallet, deleteWallet, getUserAccounts, getWallet, resolveAccount)
|
||||
import Simplex.Chat.Store.Wallets (Wallet (..), bindAccount, createWallet, deleteWallet, getUserAccounts, getWallet, heldAccount, resolveAccount)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
@@ -116,7 +116,8 @@ import Simplex.Messaging.Agent.Store.Interface (getCurrentMigrations)
|
||||
import Simplex.Messaging.Client (NetworkConfig (..), NetworkRequestMode (..), NetworkTimeout (..), SMPWebPortServers (..), SocksMode (SMAlways), pattern NRMInteractive, textToHostMode)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Crypto.ShortLink as SL
|
||||
import Simplex.Messaging.Crypto.BIP44 (mkAccountIndex)
|
||||
import Simplex.Messaging.Crypto.BIP32 (WalletMaster)
|
||||
import Simplex.Messaging.Crypto.BIP44 (AccountIndex, mkAccountIndex)
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import Simplex.Messaging.Crypto.Ratchet (E2ERatchetParamsUri (..), InitialKeys (..), PQEncryption (..), PQSupport (..), pattern IKPQOff, pattern IKPQOn, pattern PQSupportOff, pattern PQSupportOn)
|
||||
@@ -1501,32 +1502,30 @@ processChatCommand cxt nm = \case
|
||||
connId <- withAgent $ \a -> sendServiceReplyAsync a "" (aUserId user) invId (LB.toStrict $ J.encode responseData)
|
||||
pure $ CRServiceReplyAccepted user (AgentConnId connId)
|
||||
APIGetWallet userId -> withUserId userId $ \user ->
|
||||
CRWallet user <$> withFastStore (\db -> getWallet db >>= mapM (\Wallet {walletId} -> liftIO $ getUserAccounts db walletId userId))
|
||||
CRWallet user <$> withFastStore (\db -> getWallet db >>= mapM (\Wallet {walletId, nextAccountIndex} -> liftIO $ (`WalletInfo` nextAccountIndex) <$> getUserAccounts db userId walletId))
|
||||
APICreateWallet mnemonic_ -> withUser $ \user -> do
|
||||
wallet_ <- withFastStore getWallet
|
||||
when (isJust wallet_) $ throwWalletError WEMasterExists
|
||||
-- the counter starts at 0 for a generated seed and is unknown for an imported one
|
||||
-- the counter starts at 1 for a generated seed, leaving account 0 to other wallets, and is unknown for an imported one
|
||||
(master, nextAccount) <- case mnemonic_ of
|
||||
Nothing -> (,Just 0) <$> (liftIO . newWalletMaster =<< asks random)
|
||||
Nothing -> (,Just 1) <$> (liftIO . newWalletMaster =<< asks random)
|
||||
Just phrase -> (,Nothing) <$> liftWallet (importWalletMaster phrase)
|
||||
created <- withFastStore' $ \db -> createWallet db master nextAccount
|
||||
unless created $ throwWalletError WEMasterExists
|
||||
pure $ CRWallet user (Just [])
|
||||
pure $ CRWallet user (Just $ WalletInfo [] nextAccount)
|
||||
APIBindWalletAccount userId accountIdx_ -> withUserId userId $ \user@User {viewPwdHash} -> do
|
||||
when (isJust viewPwdHash) $ throwWalletError WEHiddenProfile
|
||||
(wallet, n) <- withWalletStore $ \db -> bindAccount db userId accountIdx_
|
||||
CRWalletAddress user . snd <$> walletAccount wallet n
|
||||
(master, n) <- withWalletStore $ \db -> bindAccount db userId accountIdx_
|
||||
CRWalletAddress user . snd <$> walletAccount master n
|
||||
APIGetWalletAddress accountIdx_ -> withUser $ \user -> do
|
||||
(wallet, n) <- withWalletStore (`resolveAccount` accountIdx_)
|
||||
CRWalletAddress user . snd <$> walletAccount wallet n
|
||||
(master, n) <- withWalletStore (`resolveAccount` accountIdx_)
|
||||
CRWalletAddress user . snd <$> walletAccount master n
|
||||
APIExportWalletMnemonic -> withUser $ \user -> do
|
||||
Wallet {walletMaster} <- withFastStore getWallet >>= maybe (throwWalletError WENoMaster) pure
|
||||
pure $ CRWalletMnemonic user (seedMnemonic walletMaster)
|
||||
pure $ CRWalletMnemonic user (masterMnemonic walletMaster)
|
||||
APIExportWalletAccount userId n -> withUserId userId $ \user -> do
|
||||
(wallet@Wallet {walletId}, _) <- withWalletStore (`resolveAccount` Just n)
|
||||
held <- withFastStore' $ \db -> accountHeldBy db walletId userId n
|
||||
unless held $ throwWalletError WEAccountNotHeld
|
||||
(k, a) <- walletAccount wallet n
|
||||
master <- withWalletStore $ \db -> heldAccount db userId n
|
||||
(k, a) <- walletAccount master n
|
||||
pure $ CRWalletAccountSecret user a (accountSecret k)
|
||||
APIDeleteWallet -> withUser_ $ do
|
||||
deleted <- withFastStore' deleteWallet
|
||||
@@ -6035,10 +6034,10 @@ liftWallet = liftEitherWith (ChatError . CEWallet)
|
||||
withWalletStore :: (DB.Connection -> ExceptT StoreError IO (Either WalletError a)) -> CM a
|
||||
withWalletStore action = liftWallet =<< withFastStore action
|
||||
|
||||
walletAccount :: Wallet -> AccountIndex -> CM (AccountKey, WalletAddress)
|
||||
walletAccount Wallet {walletMaster} n = do
|
||||
walletAccount :: WalletMaster -> AccountIndex -> CM (AccountKey, WalletAddress)
|
||||
walletAccount master n = do
|
||||
g <- asks random
|
||||
liftIO $ deriveAccount g walletMaster n
|
||||
liftIO $ deriveAccount g master n
|
||||
|
||||
chatCommandP :: Parser ChatCommand
|
||||
chatCommandP =
|
||||
@@ -6714,8 +6713,12 @@ chatCommandP =
|
||||
text1P = safeDecodeUtf8 <$> A.takeTill (== ' ')
|
||||
char_ = optional . A.char
|
||||
accountIndexP = do
|
||||
ds <- A.lookAhead $ A.takeWhile1 isDigit
|
||||
when (B.length (B.dropWhile (== '0') ds) > 10) tooLarge
|
||||
i <- A.decimal
|
||||
maybe (fail "account index too large") pure $ if i <= toInteger (maxBound :: Word32) then mkAccountIndex (fromInteger i) else Nothing
|
||||
if i <= toInteger (maxBound :: Word32) then either fail pure $ mkAccountIndex (fromInteger i) else tooLarge
|
||||
where
|
||||
tooLarge = fail "account index too large"
|
||||
|
||||
displayNameP :: Parser Text
|
||||
displayNameP = safeDecodeUtf8 <$> displayNameP_
|
||||
|
||||
@@ -11,7 +11,7 @@ m20260924_wallet_seeds =
|
||||
[r|
|
||||
CREATE TABLE wallet_seeds (
|
||||
wallet_seed_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
entropy BYTEA NOT NULL CHECK (length(entropy) = 32),
|
||||
entropy BYTEA NOT NULL CHECK (length(entropy) IN (16, 20, 24, 28, 32)),
|
||||
master BYTEA NOT NULL CHECK (length(master) = 64),
|
||||
next_account_index BIGINT CHECK (next_account_index BETWEEN 0 AND 2147483648),
|
||||
single_seed SMALLINT NOT NULL DEFAULT 1
|
||||
|
||||
@@ -1757,7 +1757,7 @@ CREATE TABLE test_chat_schema.wallet_seeds (
|
||||
master 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_entropy_check CHECK ((length(entropy) = ANY (ARRAY[16, 20, 24, 28, 32]))),
|
||||
CONSTRAINT wallet_seeds_master_check CHECK ((length(master) = 64)),
|
||||
CONSTRAINT wallet_seeds_next_account_index_check CHECK (((next_account_index >= 0) AND (next_account_index <= '2147483648'::bigint)))
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ m20260924_wallet_seeds =
|
||||
[sql|
|
||||
CREATE TABLE wallet_seeds (
|
||||
wallet_seed_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entropy BLOB NOT NULL CHECK (length(entropy) = 32),
|
||||
entropy BLOB NOT NULL CHECK (length(entropy) IN (16, 20, 24, 28, 32)),
|
||||
master BLOB NOT NULL CHECK (length(master) = 64),
|
||||
next_account_index INTEGER CHECK (next_account_index BETWEEN 0 AND 2147483648),
|
||||
single_seed INTEGER NOT NULL DEFAULT 1
|
||||
|
||||
@@ -983,7 +983,7 @@ CREATE TABLE badge_code_redemptions(
|
||||
) STRICT;
|
||||
CREATE TABLE wallet_seeds(
|
||||
wallet_seed_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entropy BLOB NOT NULL CHECK(length(entropy) = 32),
|
||||
entropy BLOB NOT NULL CHECK(length(entropy) IN(16, 20, 24, 28, 32)),
|
||||
master BLOB NOT NULL CHECK(length(master) = 64),
|
||||
next_account_index INTEGER CHECK(next_account_index BETWEEN 0 AND 2147483648),
|
||||
single_seed INTEGER NOT NULL DEFAULT 1
|
||||
|
||||
@@ -13,25 +13,26 @@ module Simplex.Chat.Store.Wallets
|
||||
deleteWallet,
|
||||
resolveAccount,
|
||||
getUserAccounts,
|
||||
accountHeldBy,
|
||||
heldAccount,
|
||||
bindAccount,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.Except
|
||||
import Data.Bifunctor (first)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.ByteArray as BA
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Data.Word (Word32)
|
||||
import Simplex.Chat.Store.Shared (StoreError (..))
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletError (..))
|
||||
import Simplex.Chat.Wallet (WalletError (..))
|
||||
import Simplex.Messaging.Agent.Protocol (UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Crypto.BIP32 (WalletMaster, masterBytes, masterEntropy, parseWalletMaster)
|
||||
import Simplex.Messaging.Crypto.BIP39 (unEntropy)
|
||||
import Simplex.Messaging.Crypto.BIP44 (mkAccountIndex, unAccountIndex)
|
||||
import Simplex.Messaging.Crypto.BIP44 (AccountIndex, mkAccountIndex, unAccountIndex)
|
||||
import Simplex.Messaging.Util (liftEitherWith)
|
||||
|
||||
#if defined(dbPostgres)
|
||||
@@ -47,7 +48,7 @@ type SeedId = Int64
|
||||
data Wallet = Wallet
|
||||
{ walletId :: SeedId,
|
||||
walletMaster :: WalletMaster,
|
||||
nextAccount :: Maybe Word32
|
||||
nextAccountIndex :: Maybe Word32
|
||||
}
|
||||
|
||||
getWallet :: DB.Connection -> ExceptT StoreError IO (Maybe Wallet)
|
||||
@@ -56,8 +57,8 @@ getWallet db =
|
||||
>>= mapM toWallet
|
||||
where
|
||||
toWallet :: (SeedId, ByteString, ByteString, Maybe Word32) -> ExceptT StoreError IO Wallet
|
||||
toWallet (walletId, entropy, master, nextAccount) =
|
||||
liftEitherWith SEInternalError $ (\walletMaster -> Wallet {walletId, walletMaster, nextAccount}) <$> parseWalletMaster (BA.convert entropy) (BA.convert master)
|
||||
toWallet (walletId, entropy, master, nextAccountIndex) =
|
||||
liftEitherWith SEInternalError $ (\walletMaster -> Wallet {walletId, walletMaster, nextAccountIndex}) <$> parseWalletMaster (BA.convert entropy) (BA.convert master)
|
||||
|
||||
createWallet :: DB.Connection -> WalletMaster -> Maybe Word32 -> IO Bool
|
||||
createWallet db master nextAccount =
|
||||
@@ -75,16 +76,19 @@ deleteWallet :: DB.Connection -> IO Bool
|
||||
deleteWallet db =
|
||||
rowReturned $ DB.query_ db "DELETE FROM wallet_seeds RETURNING wallet_seed_id"
|
||||
|
||||
resolveAccount :: DB.Connection -> Maybe AccountIndex -> ExceptT StoreError IO (Either WalletError (Wallet, AccountIndex))
|
||||
resolveAccount db accountIdx_ =
|
||||
resolveAccount :: DB.Connection -> Maybe AccountIndex -> ExceptT StoreError IO (Either WalletError (WalletMaster, AccountIndex))
|
||||
resolveAccount db accountIdx_ = fmap (first walletMaster) <$> resolveAccount_ db accountIdx_
|
||||
|
||||
resolveAccount_ :: DB.Connection -> Maybe AccountIndex -> ExceptT StoreError IO (Either WalletError (Wallet, AccountIndex))
|
||||
resolveAccount_ db accountIdx_ =
|
||||
getWallet db >>= \case
|
||||
Nothing -> pure $ Left WENoMaster
|
||||
Just w@Wallet {nextAccount} -> pure $ (w,) <$> maybe next Right accountIdx_
|
||||
Just w@Wallet {nextAccountIndex} -> pure $ (w,) <$> maybe next Right accountIdx_
|
||||
where
|
||||
next = maybe (Left WECounterUnknown) (maybe (Left WEAccountsExhausted) Right . mkAccountIndex) nextAccount
|
||||
next = maybe (Left WECounterUnknown) (first (const WEAccountsExhausted) . mkAccountIndex) nextAccountIndex
|
||||
|
||||
getUserAccounts :: DB.Connection -> SeedId -> UserId -> IO [AccountIndex]
|
||||
getUserAccounts db sId userId =
|
||||
getUserAccounts :: DB.Connection -> UserId -> SeedId -> IO [AccountIndex]
|
||||
getUserAccounts db userId sId =
|
||||
map fromOnly
|
||||
<$> DB.query
|
||||
db
|
||||
@@ -101,19 +105,19 @@ accountUser db sId n =
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query db "SELECT user_id FROM wallet_accounts WHERE wallet_seed_id = ? AND account_index = ?" (sId, n)
|
||||
|
||||
bindAccount :: DB.Connection -> UserId -> Maybe AccountIndex -> ExceptT StoreError IO (Either WalletError (Wallet, AccountIndex))
|
||||
bindAccount db userId accountIdx_ = resolveAccount db accountIdx_ >>= either (pure . Left) (liftIO . bind)
|
||||
bindAccount :: DB.Connection -> UserId -> Maybe AccountIndex -> ExceptT StoreError IO (Either WalletError (WalletMaster, AccountIndex))
|
||||
bindAccount db userId accountIdx_ = resolveAccount_ db accountIdx_ >>= either (pure . Left) (liftIO . bind)
|
||||
where
|
||||
bind r@(Wallet {walletId}, n) = do
|
||||
bind (Wallet {walletId, walletMaster}, n) = do
|
||||
held <-
|
||||
accountUser db walletId n >>= \case
|
||||
Just (Just heldBy) -> pure $ heldBy == userId
|
||||
Just Nothing -> setAccountUser db walletId userId n
|
||||
Nothing -> True <$ insertAccount db walletId userId n
|
||||
if held then Right r <$ raiseNextAccount db walletId n else pure $ Left WEAccountBound
|
||||
Just Nothing -> setAccountUser db userId walletId n
|
||||
Nothing -> True <$ insertAccount db userId walletId n
|
||||
if held then Right (walletMaster, n) <$ raiseNextAccount db walletId n else pure $ Left WEAccountBound
|
||||
|
||||
setAccountUser :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO Bool
|
||||
setAccountUser db sId userId n =
|
||||
setAccountUser :: DB.Connection -> UserId -> SeedId -> AccountIndex -> IO Bool
|
||||
setAccountUser db userId sId n =
|
||||
rowReturned $
|
||||
DB.query
|
||||
db
|
||||
@@ -124,8 +128,11 @@ setAccountUser db sId userId n =
|
||||
|]
|
||||
(userId, sId, n)
|
||||
|
||||
accountHeldBy :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO Bool
|
||||
accountHeldBy db sId userId n = (== Just (Just userId)) <$> accountUser db sId n
|
||||
heldAccount :: DB.Connection -> UserId -> AccountIndex -> ExceptT StoreError IO (Either WalletError WalletMaster)
|
||||
heldAccount db userId n =
|
||||
getWallet db >>= \case
|
||||
Nothing -> pure $ Left WENoMaster
|
||||
Just Wallet {walletId, walletMaster} -> liftIO $ (\held -> if held == Just (Just userId) then Right walletMaster else Left WEAccountNotHeld) <$> accountUser db walletId n
|
||||
|
||||
raiseNextAccount :: DB.Connection -> SeedId -> AccountIndex -> IO ()
|
||||
raiseNextAccount db sId n =
|
||||
@@ -137,8 +144,8 @@ raiseNextAccount db sId n =
|
||||
|]
|
||||
(unAccountIndex n + 1, sId, n)
|
||||
|
||||
insertAccount :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO ()
|
||||
insertAccount db sId userId n =
|
||||
insertAccount :: DB.Connection -> UserId -> SeedId -> AccountIndex -> IO ()
|
||||
insertAccount db userId sId n =
|
||||
DB.execute db "INSERT INTO wallet_accounts (wallet_seed_id, account_index, user_id) VALUES (?, ?, ?)" (sId, n, userId)
|
||||
|
||||
rowReturned :: IO [Only Int64] -> IO Bool
|
||||
|
||||
@@ -60,7 +60,7 @@ import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Wallet (WalletAddress (..), WalletError (..))
|
||||
import Simplex.Chat.Wallet (WalletAddress (..), WalletError (..), WalletInfo (..))
|
||||
import qualified Simplex.FileTransfer.Transport as XFTP
|
||||
import Simplex.Messaging.Agent (DatabaseDiff (..))
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..))
|
||||
@@ -197,10 +197,12 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, showFullLinks, te
|
||||
CRBadgeRedeemed u badge newBadge _ -> ttyUser u $ if newBadge then "badge redeemed" : viewContactBadge (Just badge) else ["badge already redeemed"]
|
||||
CRBadgeState u st -> ttyUser u $ viewUserBadgeState st
|
||||
CRBadgeLedger u entries -> ttyUser u $ viewBadgeLedger entries
|
||||
CRWallet u accounts_ -> ttyUser u $ case accounts_ of
|
||||
CRWallet u info_ -> ttyUser u $ case info_ of
|
||||
Nothing -> ["no wallet on this device"]
|
||||
Just [] -> ["wallet, no accounts for this profile"]
|
||||
Just accounts -> [plain $ "accounts: " <> T.intercalate ", " (map (tshow . unAccountIndex) accounts)]
|
||||
Just WalletInfo {accountIndexes, nextAccountIndex} ->
|
||||
let next = maybe "unknown" tshow nextAccountIndex
|
||||
accounts = if null accountIndexes then "no accounts for this profile" else "accounts: " <> T.intercalate ", " (map (tshow . unAccountIndex) accountIndexes)
|
||||
in [plain $ "wallet, next account " <> next <> ", " <> accounts]
|
||||
CRWalletMnemonic u mnemonic -> ttyUser u [plain mnemonic]
|
||||
CRWalletAddress u a -> ttyUser u [walletAddressRow a]
|
||||
CRWalletAccountSecret u a secret -> ttyUser u [walletAddressRow a <> " " <> plain secret]
|
||||
@@ -1113,7 +1115,7 @@ viewChatCleared (AChatInfo _ chatInfo) = case chatInfo of
|
||||
|
||||
walletAddressRow :: WalletAddress -> StyledString
|
||||
walletAddressRow WalletAddress {accountIndex, keyPath, address} =
|
||||
plain $ tshow (unAccountIndex accountIndex) <> " " <> keyPath <> " " <> address
|
||||
plain $ tshow (unAccountIndex accountIndex) <> " " <> keyPath <> " " <> safeDecodeUtf8 (strEncode address)
|
||||
|
||||
viewContactsList :: [Contact] -> [StyledString]
|
||||
viewContactsList =
|
||||
@@ -2817,7 +2819,7 @@ viewChatError isCmd logLevel testView = \case
|
||||
reason = case walletErr of
|
||||
WENoMaster -> "this device has no wallet"
|
||||
WEMasterExists -> "this device already has a wallet"
|
||||
WEBadMnemonic -> "not a valid 24 word recovery phrase"
|
||||
WEBadMnemonic -> "not a valid recovery phrase"
|
||||
WEHiddenProfile -> "an account cannot be bound to a hidden profile"
|
||||
WEAccountBound -> "another profile holds this account"
|
||||
WEAccountNotHeld -> "this profile does not hold this account"
|
||||
|
||||
+21
-14
@@ -2,14 +2,14 @@
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module Simplex.Chat.Wallet
|
||||
( AccountIndex,
|
||||
AccountKey,
|
||||
( AccountKey,
|
||||
WalletAddress (..),
|
||||
WalletInfo (..),
|
||||
WalletError (..),
|
||||
newWalletMaster,
|
||||
entropyFromMnemonic,
|
||||
importWalletMaster,
|
||||
seedMnemonic,
|
||||
masterMnemonic,
|
||||
deriveAccount,
|
||||
accountSecret,
|
||||
)
|
||||
@@ -18,15 +18,16 @@ where
|
||||
import Control.Concurrent.STM
|
||||
import Crypto.Random (ChaChaDRG)
|
||||
import qualified Data.Aeson.TH as JQ
|
||||
import Data.Bifunctor (first)
|
||||
import qualified Data.ByteArray.Encoding as BAE
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Encoding (decodeLatin1)
|
||||
import Data.Word (Word32)
|
||||
import qualified Simplex.Messaging.Crypto.BIP32 as B32
|
||||
import qualified Simplex.Messaging.Crypto.BIP39 as B39
|
||||
import Simplex.Messaging.Crypto.BIP44 (AccountIndex, CoinType (..), bip44Path)
|
||||
import qualified Simplex.Messaging.Crypto.Secp256k1 as S
|
||||
import Simplex.Messaging.Encoding.String (strEncode)
|
||||
import Simplex.Messaging.Eth.Address (addressFromPrivateKey)
|
||||
import Simplex.Messaging.Eth.Address (Address, addressFromPrivateKey)
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON)
|
||||
|
||||
type AccountKey = S.Secp256k1PrivateKey
|
||||
@@ -34,7 +35,13 @@ type AccountKey = S.Secp256k1PrivateKey
|
||||
data WalletAddress = WalletAddress
|
||||
{ accountIndex :: AccountIndex,
|
||||
keyPath :: Text,
|
||||
address :: Text
|
||||
address :: Address
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data WalletInfo = WalletInfo
|
||||
{ accountIndexes :: [AccountIndex],
|
||||
nextAccountIndex :: Maybe Word32
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
@@ -53,24 +60,22 @@ masterStrength :: B39.EntropyStrength
|
||||
masterStrength = B39.ES256
|
||||
|
||||
newWalletMaster :: TVar ChaChaDRG -> IO B32.WalletMaster
|
||||
newWalletMaster g = (`B32.mkWalletMaster` "") <$> atomically (B39.randomEntropy masterStrength g)
|
||||
newWalletMaster g = B32.mkWalletMaster <$> atomically (B39.randomEntropy masterStrength g)
|
||||
|
||||
entropyFromMnemonic :: Text -> Either WalletError B39.WalletEntropy
|
||||
entropyFromMnemonic phrase = case B39.parsePhrase phrase of
|
||||
Right ent | B39.entropyWordCount ent == 24 -> Right ent
|
||||
_ -> Left WEBadMnemonic
|
||||
entropyFromMnemonic = first (const WEBadMnemonic) . B39.parsePhrase
|
||||
|
||||
importWalletMaster :: Text -> Either WalletError B32.WalletMaster
|
||||
importWalletMaster phrase = (`B32.mkWalletMaster` "") <$> entropyFromMnemonic phrase
|
||||
importWalletMaster phrase = B32.mkWalletMaster <$> entropyFromMnemonic phrase
|
||||
|
||||
seedMnemonic :: B32.WalletMaster -> Text
|
||||
seedMnemonic = decodeLatin1 . B39.entropyPhrase . B32.masterEntropy
|
||||
masterMnemonic :: B32.WalletMaster -> Text
|
||||
masterMnemonic = decodeLatin1 . B39.entropyPhrase . B32.masterEntropy
|
||||
|
||||
deriveAccount :: TVar ChaChaDRG -> B32.WalletMaster -> AccountIndex -> IO (AccountKey, WalletAddress)
|
||||
deriveAccount g master n = do
|
||||
k <- B32.xkKey <$> B32.derivePath g (B32.walletMasterKey master) path
|
||||
a <- addressFromPrivateKey g k
|
||||
pure (k, WalletAddress {accountIndex = n, keyPath = decodeLatin1 $ B32.renderPath path, address = decodeLatin1 $ strEncode a})
|
||||
pure (k, WalletAddress {accountIndex = n, keyPath = decodeLatin1 $ B32.renderPath path, address = a})
|
||||
where
|
||||
path = bip44Path Ethereum n
|
||||
|
||||
@@ -79,4 +84,6 @@ accountSecret k = "0x" <> decodeLatin1 (BAE.convertToBase BAE.Base16 $ S.unPriva
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''WalletAddress)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''WalletInfo)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "WE") ''WalletError)
|
||||
|
||||
Reference in New Issue
Block a user