Skip to content

Commit

Permalink
[ADP-3479] Add XPrv management in deposit wallet REST interface (#4842)
Browse files Browse the repository at this point in the history
  • Loading branch information
paolino authored Nov 15, 2024
2 parents c3a6712 + 1509e26 commit b639cc7
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 207 deletions.
4 changes: 4 additions & 0 deletions lib/customer-deposit-wallet/customer-deposit-wallet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ library
, async
, base
, base58-bytestring
, base16-bytestring
, bech32
, bech32-th
, bytestring
, cardano-addresses
, cardano-balance-tx
, cardano-crypto
, cardano-ledger-api
Expand Down Expand Up @@ -226,7 +228,9 @@ test-suite unit
, openapi3
, pretty-simple
, QuickCheck
, serialise
, temporary
, text
, time
, text
, transformers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import Cardano.Wallet.Deposit.HTTP.Types.JSON
import Cardano.Wallet.Deposit.IO
( WalletBootEnv
)
import Cardano.Wallet.Deposit.Pure.State.Creation
( credentialsFromEncodedXPub
, credentialsFromMnemonics
)
import Cardano.Wallet.Deposit.REST
( WalletResource
, WalletResourceM
Expand All @@ -32,10 +36,8 @@ import Cardano.Wallet.Deposit.REST.Catch
( catchRunWalletResourceM
)
import Cardano.Wallet.Deposit.REST.Wallet.Create
( PostWalletViaMenmonic (..)
( PostWalletViaMnemonic (..)
, PostWalletViaXPub (..)
, decodeXPub
, xpubFromMnemonics
)
import Control.Tracer
( Tracer
Expand Down Expand Up @@ -81,23 +83,23 @@ createWalletViaMnemonic
-> FilePath
-> WalletBootEnv IO
-> WalletResource
-> PostWalletViaMenmonic
-> PostWalletViaMnemonic
-> Handler NoContent
createWalletViaMnemonic
tracer
dir
boot
resource
(PostWalletViaMenmonic mnemonics' users') =
(PostWalletViaMnemonic mnemonics' passphrase' users') =
onlyOnWalletIntance resource initWallet $> NoContent
where
initWallet :: WalletResourceM ()
initWallet =
REST.initXPubWallet
REST.initWallet
tracer
boot
dir
(xpubFromMnemonics mnemonics')
(credentialsFromMnemonics mnemonics' passphrase')
(fromIntegral users')

createWalletViaXPub
Expand All @@ -119,17 +121,16 @@ createWalletViaXPub
Right () -> pure NoContent
where
initWallet :: WalletResourceM (Either String ())
initWallet = case decodeXPub xpubText of
Left e -> pure $ Left e
Right (Just xpub') ->
initWallet = case credentialsFromEncodedXPub xpubText of
Left e -> pure $ Left $ show e
Right credentials ->
Right
<$> REST.initXPubWallet
<$> REST.initWallet
tracer
boot
dir
xpub'
credentials
(fromIntegral users')
Right Nothing -> pure $ Left "Invalid XPub"

listCustomerH
:: WalletResource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Cardano.Wallet.Deposit.HTTP.Types.JSON
, CustomerList
)
import Cardano.Wallet.Deposit.REST.Wallet.Create
( PostWalletViaMenmonic
( PostWalletViaMnemonic
, PostWalletViaXPub
)
import Servant.API
Expand All @@ -47,7 +47,7 @@ type API =
:> Capture "customerId" (ApiT Customer)
:> Put '[JSON] (ApiT Address)
:<|> "mnemonics"
:> ReqBody '[JSON] PostWalletViaMenmonic
:> ReqBody '[JSON] PostWalletViaMnemonic
:> PutNoContent
:<|> "xpub"
:> ReqBody '[JSON] PostWalletViaXPub
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import Cardano.Wallet.Deposit.Read
, ChainPoint (..)
)
import Cardano.Wallet.Deposit.REST.Wallet.Create
( PostWalletViaMenmonic
( PostWalletViaMnemonic
, PostWalletViaXPub
)
import Control.Applicative
Expand Down Expand Up @@ -223,6 +223,6 @@ instance ToSchema (ApiT ChainPoint) where
(Just "ApiT ChainPoint")
chainPointSchema

instance FromJSON PostWalletViaMenmonic
instance FromJSON PostWalletViaMnemonic

instance FromJSON PostWalletViaXPub
90 changes: 60 additions & 30 deletions lib/customer-deposit-wallet/rest/Cardano/Wallet/Deposit/REST.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- |
-- Copyright: © 2024 Cardano Foundation
Expand All @@ -26,7 +27,7 @@ module Cardano.Wallet.Deposit.REST
-- * Operations

-- ** Initialization
, initXPubWallet
, initWallet
, loadWallet

-- ** Mapping between customers and addresses
Expand Down Expand Up @@ -57,11 +58,15 @@ module Cardano.Wallet.Deposit.REST
import Prelude

import Cardano.Address.Derivation
( xpubFromBytes
, xpubToBytes
( xpubToBytes
)
import Cardano.Crypto.Wallet
( XPub (..)
( XPrv
, XPub (..)
, unXPrv
, unXPub
, xprv
, xpub
)
import Cardano.Wallet.Address.BIP32
( BIP32Path
Expand All @@ -74,20 +79,25 @@ import Cardano.Wallet.Deposit.IO.Resource
, ErrResourceMissing (..)
)
import Cardano.Wallet.Deposit.Pure
( Customer
( Credentials
, Customer
, ErrCreatePayment
, Word31
, fromXPubAndGenesis
, fromCredentialsAndGenesis
)
import Cardano.Wallet.Deposit.Pure.API.TxHistory
( ByCustomer
, ByTime
)
import Cardano.Wallet.Deposit.Pure.State.Creation
( xpubFromCredentials
)
import Cardano.Wallet.Deposit.Read
( Address
)
import Codec.Serialise
( deserialise
( Serialise (..)
, deserialise
, serialise
)
import Control.DeepSeq
Expand Down Expand Up @@ -120,6 +130,9 @@ import Data.ByteArray.Encoding
( Base (..)
, convertToBase
)
import Data.ByteString
( ByteString
)
import Data.List
( isPrefixOf
)
Expand Down Expand Up @@ -245,52 +258,69 @@ findTheDepositWalletOnDisk dir action = do
ds <- scanDirectoryForDepositPrefix dir
case ds of
[d] -> do
(xpub, users) <- deserialise <$> BL.readFile (dir </> d)
case xpubFromBytes xpub of
Nothing -> action $ Left $ ErrDatabaseCorrupted (dir </> d)
Just identity -> do
let state =
fromXPubAndGenesis
identity
(fromIntegral @Int users)
Read.mockGenesisDataMainnet
store <- newStore
writeS store state
action $ Right store
(credentials, users) <-
deserialise <$> BL.readFile (dir </> d)
let state =
fromCredentialsAndGenesis
credentials
(fromIntegral @Int users)
Read.mockGenesisDataMainnet
store <- newStore
writeS store state
action $ Right store
[] -> action $ Left $ ErrDatabaseNotFound dir
ds' -> action $ Left $ ErrMultipleDatabases ((dir </>) <$> ds')

instance Serialise XPub where
encode = encode . unXPub
decode = do
b <- decode
case xpub b of
Right x -> pure x
Left e -> fail e

instance Serialise XPrv where
encode = encode . unXPrv
decode = do
b :: ByteString <- decode
case xprv b of
Right x -> pure x
Left e -> fail e

instance Serialise Credentials

-- | Try to create a new wallet
createTheDepositWalletOnDisk
:: Tracer IO String
-- ^ Tracer for logging
-> FilePath
-- ^ Path to the wallet database directory
-> XPub
-> Credentials
-- ^ Id of the wallet
-> Word31
-- ^ Max number of users ?
-> (Maybe WalletIO.WalletStore -> IO a)
-- ^ Action to run if the wallet is created
-> IO a
createTheDepositWalletOnDisk _tr dir identity users action = do
createTheDepositWalletOnDisk _tr dir credentials users action = do
ds <- scanDirectoryForDepositPrefix dir
case ds of
[] -> do
let fp = dir </> depositPrefix <> hashWalletId identity
let fp = dir </> depositPrefix <> hashWalletId credentials
BL.writeFile fp
$ serialise (xpubToBytes identity, fromIntegral users :: Int)
$ serialise (credentials, fromIntegral users :: Int)
store <- newStore
action $ Just store
_ -> do
action Nothing
where
hashWalletId :: XPub -> String
hashWalletId :: Credentials -> String
hashWalletId =
B8.unpack
. convertToBase Base16
. blake2b160
. xpubPublicKey
. xpubToBytes
. xpubFromCredentials

-- | Load an existing wallet from disk.
loadWallet
Expand All @@ -316,27 +346,27 @@ loadWallet bootEnv dir = do
<$> Resource.putResource action resource

-- | Initialize a new wallet from an 'XPub'.
initXPubWallet
initWallet
:: Tracer IO String
-- ^ Tracer for logging
-> WalletIO.WalletBootEnv IO
-- ^ Environment for the wallet
-> FilePath
-- ^ Path to the wallet database directory
-> XPub
-> Credentials
-- ^ Id of the wallet
-> Word31
-- ^ Max number of users ?
-> WalletResourceM ()
initXPubWallet tr bootEnv dir xpub users = do
initWallet tr bootEnv dir credentials users = do
let action
:: (WalletIO.WalletInstance -> IO b) -> IO (Either ErrDatabase b)
action f = createTheDepositWalletOnDisk tr dir xpub users $ \case
action f = createTheDepositWalletOnDisk tr dir credentials users $ \case
Just wallet -> do
fmap Right
$ WalletIO.withWalletInit
(WalletIO.WalletEnv bootEnv wallet)
xpub
credentials
users
$ \i -> do
addresses <- map snd <$> WalletIO.listCustomers i
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,24 @@
{-# LANGUAGE DuplicateRecordFields #-}

module Cardano.Wallet.Deposit.REST.Wallet.Create
( PostWalletViaMenmonic (..)
( PostWalletViaMnemonic (..)
, PostWalletViaXPub (..)
, decodeXPub
, xpubFromMnemonics
, encodeXPub
)
where

import Prelude

import Cardano.Address.Derivation
( XPub
, generate
, toXPub
, xpubFromBytes
, xpubToBytes
)
import Data.ByteArray.Encoding
( Base (Base64)
, convertFromBase
, convertToBase
)
import Data.ByteString.Char8
( ByteString
)
import Data.Text
( Text
)
import GHC.Generics
( Generic
)

import qualified Data.Text.Encoding as T

-- | Data for a request to create a wallet via a mnemonic.
data PostWalletViaMenmonic = PostWalletViaMenmonic
data PostWalletViaMnemonic = PostWalletViaMnemonic
{ mnemonics :: Text
, password :: Text
, trackedCustomers :: Int
}
deriving (Generic)
Expand All @@ -49,19 +30,3 @@ data PostWalletViaXPub = PostWalletViaXPub
, trackedCustomers :: Int
}
deriving (Generic)

unBase64 :: ByteString -> Either String ByteString
unBase64 = convertFromBase Base64

-- | Decode an extended public key from a base64-encoded text.
decodeXPub :: Text -> Either String (Maybe XPub)
decodeXPub = fmap xpubFromBytes . unBase64 . T.encodeUtf8

-- | Encode an extended public key to a base64-encoded text.
encodeXPub :: XPub -> Text
encodeXPub = T.decodeUtf8 . convertToBase Base64 . xpubToBytes

-- | Generate an extended public key from a mnemonic.
-- this is not what one wants to use in production
xpubFromMnemonics :: Text -> XPub
xpubFromMnemonics = toXPub . generate . T.encodeUtf8
Loading

0 comments on commit b639cc7

Please sign in to comment.