mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-05 23:07:57 +00:00
83 lines
2.5 KiB
Haskell
83 lines
2.5 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
|
|
module Simplex.Chat.Wallet
|
|
( AccountKey,
|
|
WalletAddress (..),
|
|
WalletInfo (..),
|
|
WalletError (..),
|
|
newEntropy,
|
|
entropyFromMnemonic,
|
|
masterMnemonic,
|
|
deriveAccount,
|
|
accountSecret,
|
|
)
|
|
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.Eth.Address (Address, deriveAddress)
|
|
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON)
|
|
|
|
type AccountKey = S.Secp256k1PrivateKey
|
|
|
|
data WalletAddress = WalletAddress
|
|
{ accountIndex :: AccountIndex,
|
|
keyPath :: Text,
|
|
address :: Address
|
|
}
|
|
deriving (Show)
|
|
|
|
data WalletInfo = WalletInfo
|
|
{ accountIndexes :: [AccountIndex],
|
|
nextAccountIndex :: Maybe Word32
|
|
}
|
|
deriving (Show)
|
|
|
|
data WalletError
|
|
= WENoMaster
|
|
| WEMasterExists
|
|
| WEBadMnemonic
|
|
| WEHiddenProfile
|
|
| WEAccountBound
|
|
| WEAccountNotHeld
|
|
| WECounterUnknown
|
|
| WEAccountsExhausted
|
|
deriving (Eq, Show)
|
|
|
|
newEntropy :: TVar ChaChaDRG -> IO B39.WalletEntropy
|
|
newEntropy = atomically . B39.randomEntropy B39.ES256
|
|
|
|
entropyFromMnemonic :: Text -> Either WalletError B39.WalletEntropy
|
|
entropyFromMnemonic = first (const WEBadMnemonic) . B39.parsePhrase
|
|
|
|
masterMnemonic :: B39.WalletEntropy -> Text
|
|
masterMnemonic = decodeLatin1 . B39.entropyPhrase
|
|
|
|
deriveAccount :: TVar ChaChaDRG -> B39.WalletEntropy -> AccountIndex -> IO (Either String (AccountKey, WalletAddress))
|
|
deriveAccount g entropy n = case B32.masterKey (B39.entropySeed entropy "") of
|
|
Left e -> pure $ Left e
|
|
Right master -> fmap account <$> deriveAddress g master path
|
|
where
|
|
path = bip44Path Ethereum n
|
|
account (xk, a) = (B32.xkKey xk, WalletAddress {accountIndex = n, keyPath = decodeLatin1 $ B32.renderPath path, address = a})
|
|
|
|
accountSecret :: AccountKey -> Text
|
|
accountSecret k = "0x" <> decodeLatin1 (BAE.convertToBase BAE.Base16 $ S.unPrivateKey k)
|
|
|
|
$(JQ.deriveJSON defaultJSON ''WalletAddress)
|
|
|
|
$(JQ.deriveJSON defaultJSON ''WalletInfo)
|
|
|
|
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "WE") ''WalletError)
|