Skip to content

Commit

Permalink
Merge #2466 #2473
Browse files Browse the repository at this point in the history
2466: Expose account public key in api r=KtorZ a=paweljakubas

# Issue Number

<!-- Put here a reference to the issue that this PR relates to and which requirements it tackles. Jira issues of the form ADP- will be auto-linked. -->
adp-544

# Overview

<!-- Detail in a few bullet points the work accomplished in this PR -->

- [x] I have added endpoint definition in swagger
- [x] I have added Api scaffolding and needed types, instances
- [x] I have added wallet impl
- [x] I have updated unit tests
- [x] I have added illustrative integration test


# Comments

<!-- Additional comments or screenshots to attach if any -->

<!--
Don't forget to:

 ✓ Self-review your changes to make sure nothing unexpected slipped through
 ✓ Assign yourself to the PR
 ✓ Assign one or several reviewer(s)
 ✓ Jira will detect and link to this PR once created, but you can also link this PR in the description of the corresponding ticket
 ✓ Acknowledge any changes required to the Wiki
 ✓ Finally, in the PR description delete any empty sections and all text commented in <!--, so that this text does not appear in merge commit messages.
-->


2473: Bump node to 1.25.1 r=Anviking a=Anviking

# Issue Number

<!-- Put here a reference to the issue that this PR relates to and which requirements it tackles. Jira issues of the form ADP- will be auto-linked. -->


# Overview

<!-- Detail in a few bullet points the work accomplished in this PR -->

- [x] Bump node to 1.25.1
- [x] Get CI to regen nix

# Comments

- Snapshot bump in: input-output-hk/cardano-haskell#39

<!-- Additional comments or screenshots to attach if any -->

<!--
Don't forget to:

 ✓ Self-review your changes to make sure nothing unexpected slipped through
 ✓ Assign yourself to the PR
 ✓ Assign one or several reviewer(s)
 ✓ Jira will detect and link to this PR once created, but you can also link this PR in the description of the corresponding ticket
 ✓ Acknowledge any changes required to the Wiki
 ✓ Finally, in the PR description delete any empty sections and all text commented in <!--, so that this text does not appear in merge commit messages.
-->


Co-authored-by: Pawel Jakubas <[email protected]>
Co-authored-by: Matthias Benkort <[email protected]>
Co-authored-by: Johannes Lund <[email protected]>
Co-authored-by: IOHK <[email protected]>
  • Loading branch information
5 people authored Jan 28, 2021
3 parents 7fd1a9c + d828f08 + f846612 commit d13eaa5
Show file tree
Hide file tree
Showing 30 changed files with 450 additions and 49 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ See **Installation Instructions** for each available [release](https://github.co
>
> | cardano-wallet | cardano-node (compatible versions) | SMASH (compatible versions)
> | --- | --- | ---
> | `master` branch | [1.25.0](https://github.com/input-output-hk/cardano-node/releases/tag/1.25.0) | [1.3.0](https://github.com/input-output-hk/smash/releases/tag/1.3.0)
> | `master` branch | [1.25.1](https://github.com/input-output-hk/cardano-node/releases/tag/1.25.1) | [1.3.0](https://github.com/input-output-hk/smash/releases/tag/1.3.0)
> | [v2021-01-12](https://github.com/input-output-hk/cardano-wallet/releases/tag/v2021-01-12) | [1.24.2](https://github.com/input-output-hk/cardano-node/releases/tag/1.24.2) | [1.3.0](https://github.com/input-output-hk/smash/releases/tag/1.3.0)
> | [v2020-12-21](https://github.com/input-output-hk/cardano-wallet/releases/tag/v2020-12-21) | [1.24.2](https://github.com/input-output-hk/cardano-node/releases/tag/1.24.2) | [1.2.0](https://github.com/input-output-hk/smash/releases/tag/1.2.0)
> | [v2020-12-08](https://github.com/input-output-hk/cardano-wallet/releases/tag/v2020-12-08) | [1.24.2](https://github.com/input-output-hk/cardano-node/releases/tag/1.24.2) | [1.2.0](https://github.com/input-output-hk/smash/releases/tag/1.2.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ module Test.Integration.Framework.TestData
, errMsg403MinUTxOValue
, errMsg403CouldntIdentifyAddrAsMine
, errMsg503PastHorizon
, errMsg403WrongIndex
) where

import Prelude
Expand Down Expand Up @@ -412,3 +413,8 @@ errMsg403CouldntIdentifyAddrAsMine = "I \

errMsg503PastHorizon :: String
errMsg503PastHorizon = "Tried to convert something that is past the horizon"

errMsg403WrongIndex :: String
errMsg403WrongIndex = "It looks like you've provided a derivation index that is out of bound.\
\ The index is well-formed, but I require indexes valid for hardened derivation only. That\
\ is, indexes between 2147483648 and 4294967295 with a suffix 'H'."
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Prelude

import Cardano.Wallet.Api.Types
( AnyAddress
, ApiAccountKey
, ApiAddress
, ApiT (..)
, ApiTransaction
Expand Down Expand Up @@ -77,7 +78,7 @@ import Test.Integration.Framework.DSL
, walletId
)
import Test.Integration.Framework.TestData
( errMsg404NoWallet )
( errMsg403WrongIndex, errMsg404NoWallet )

import qualified Cardano.Wallet.Api.Link as Link
import qualified Data.Aeson as Aeson
Expand Down Expand Up @@ -650,6 +651,35 @@ spec = describe "SHELLEY_ADDRESSES" $ do
r <- request @AnyAddress ctx Link.postAnyAddress Default payload
expectResponseCode HTTP.status400 r
expectErrorMessage "must have at least one credential" r

it "POST_ACCOUNT_01 - Can retrieve account public keys" $ \ctx -> runResourceT $ do
let initPoolGap = 10
w <- emptyWalletWith ctx ("Wallet", fixturePassphrase, initPoolGap)

let endpoint = Link.postAccountKey w (DerivationIndex 0)
let payload = Json [json|{
"passphrase": #{fixturePassphrase},
"extended": true
}|]
resp <- request @ApiAccountKey ctx endpoint Default payload
expectErrorMessage errMsg403WrongIndex resp

-- Request first 10 extended account public keys
let indices = [0..9]
accountPublicKeys <- forM indices $ \index -> do
let accountPath = Link.postAccountKey w (DerivationIndex $ 2147483648 + index)
let payload1 = Json [json|{
"passphrase": #{fixturePassphrase},
"extended": true
}|]
let payload2 = Json [json|{
"passphrase": #{fixturePassphrase},
"extended": false
}|]
(_, accXPub) <- unsafeRequest @ApiAccountKey ctx accountPath payload1
(_, accPub) <- unsafeRequest @ApiAccountKey ctx accountPath payload2
pure [accXPub, accPub]
length (concat accountPublicKeys) `Expectations.shouldBe` 20
where
validateAddr resp expected = do
let addr = getFromResponse id resp
Expand Down
64 changes: 56 additions & 8 deletions lib/core/src/Cardano/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,13 @@ module Cardano.Wallet
-- ** Root Key
, withRootKey
, derivePublicKey
, readPublicAccountKey
, signMetadataWith
, ErrWithRootKey (..)
, ErrWrongPassphrase (..)
, ErrSignMetadataWith (..)
, ErrDerivePublicKey(..)
, ErrReadAccountPublicKey(..)
, ErrInvalidDerivationIndex(..)

-- * Logging
Expand Down Expand Up @@ -1914,7 +1916,7 @@ signMetadataWith ctx wid pwd (role_, ix) metadata = db & \DBLayer{..} -> do
where
db = ctx ^. dbLayer @s @k

-- | Derive public key of a wallet's account.
-- | Derive public key from a wallet's account key.
derivePublicKey
:: forall ctx s k n.
( HasDBLayer s k ctx
Expand Down Expand Up @@ -1943,13 +1945,50 @@ derivePublicKey ctx wid role_ ix = db & \DBLayer{..} -> do
where
db = ctx ^. dbLayer @s @k

-- | Retrieve public account key of a wallet.
readPublicAccountKey
:: forall ctx s k n.
( HasDBLayer s k ctx
, HardDerivation k
, WalletKey k
, s ~ SeqState n k
)
=> ctx
-> WalletId
-> Passphrase "raw"
-> DerivationIndex
-> ExceptT ErrReadAccountPublicKey IO (k 'AccountK XPub)
readPublicAccountKey ctx wid pwd ix = db & \DBLayer{..} -> do
acctIx <- withExceptT ErrReadAccountPublicKeyInvalidIndex $ guardHardIndex ix

_cp <- mapExceptT atomically
$ withExceptT ErrReadAccountPublicKeyNoSuchWallet
$ withNoSuchWallet wid
$ readCheckpoint (PrimaryKey wid)

withRootKey @ctx @s @k ctx wid pwd ErrReadAccountPublicKeyRootKey
$ \rootK scheme -> do
let encPwd = preparePassphrase scheme pwd
pure $ publicKey $ deriveAccountPrivateKey encPwd rootK acctIx
where
db = ctx ^. dbLayer @s @k

guardSoftIndex
:: Monad m
=> DerivationIndex
-> ExceptT ErrInvalidDerivationIndex m (Index 'Soft whatever)
-> ExceptT (ErrInvalidDerivationIndex 'Soft 'AddressK) m (Index 'Soft whatever)
guardSoftIndex ix =
if ix > DerivationIndex (getIndex @'Soft maxBound)
then throwE $ ErrIndexTooHigh maxBound ix
if ix > DerivationIndex (getIndex @'Soft maxBound) || ix < DerivationIndex (getIndex @'Soft minBound)
then throwE $ ErrIndexOutOfBound minBound maxBound ix
else pure (Index $ getDerivationIndex ix)

guardHardIndex
:: Monad m
=> DerivationIndex
-> ExceptT (ErrInvalidDerivationIndex 'Hardened 'AccountK) m (Index 'Hardened whatever)
guardHardIndex ix =
if ix > DerivationIndex (getIndex @'Hardened maxBound) || ix < DerivationIndex (getIndex @'Hardened minBound)
then throwE $ ErrIndexOutOfBound minBound maxBound ix
else pure (Index $ getDerivationIndex ix)

{-------------------------------------------------------------------------------
Expand All @@ -1961,19 +2000,28 @@ data ErrSignMetadataWith
-- ^ The wallet exists, but there's no root key attached to it
| ErrSignMetadataWithNoSuchWallet ErrNoSuchWallet
-- ^ The wallet doesn't exist?
| ErrSignMetadataWithInvalidIndex ErrInvalidDerivationIndex
| ErrSignMetadataWithInvalidIndex (ErrInvalidDerivationIndex 'Soft 'AddressK)
-- ^ User provided a derivation index outside of the 'Soft' domain
deriving (Eq, Show)

data ErrDerivePublicKey
= ErrDerivePublicKeyNoSuchWallet ErrNoSuchWallet
-- ^ The wallet doesn't exist?
| ErrDerivePublicKeyInvalidIndex ErrInvalidDerivationIndex
| ErrDerivePublicKeyInvalidIndex (ErrInvalidDerivationIndex 'Soft 'AddressK)
-- ^ User provided a derivation index outside of the 'Soft' domain
deriving (Eq, Show)

data ErrInvalidDerivationIndex
= ErrIndexTooHigh (Index 'Soft 'AddressK) DerivationIndex
data ErrReadAccountPublicKey
= ErrReadAccountPublicKeyNoSuchWallet ErrNoSuchWallet
-- ^ The wallet doesn't exist?
| ErrReadAccountPublicKeyInvalidIndex (ErrInvalidDerivationIndex 'Hardened 'AccountK)
-- ^ User provided a derivation index outside of the 'Hard' domain
| ErrReadAccountPublicKeyRootKey ErrWithRootKey
-- ^ The wallet exists, but there's no root key attached to it
deriving (Eq, Show)

data ErrInvalidDerivationIndex derivation level
= ErrIndexOutOfBound (Index derivation level) (Index derivation level) DerivationIndex
deriving (Eq, Show)

-- | Errors that can occur when listing UTxO statistics.
Expand Down
12 changes: 12 additions & 0 deletions lib/core/src/Cardano/Wallet/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module Cardano.Wallet.Api
, WalletKeys
, GetWalletKey
, SignMetadata
, PostAccountKey

, Assets
, ListAssets
Expand Down Expand Up @@ -124,6 +125,7 @@ import Cardano.Wallet
( WalletLayer (..), WalletLog )
import Cardano.Wallet.Api.Types
( AnyAddress
, ApiAccountKey
, ApiAddressData
, ApiAddressIdT
, ApiAddressInspect
Expand All @@ -140,6 +142,7 @@ import Cardano.Wallet.Api.Types
, ApiNetworkInformation
, ApiNetworkParameters
, ApiPoolId
, ApiPostAccountKeyData
, ApiPostRandomAddressData
, ApiPutAddressesDataT
, ApiSelectCoinsDataT
Expand Down Expand Up @@ -313,6 +316,7 @@ type GetUTxOsStatistics = "wallets"
type WalletKeys =
GetWalletKey
:<|> SignMetadata
:<|> PostAccountKey

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/getWalletKey
type GetWalletKey = "wallets"
Expand All @@ -331,6 +335,14 @@ type SignMetadata = "wallets"
:> ReqBody '[JSON] ApiWalletSignData
:> Post '[OctetStream] ByteString

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/postAccountKey
type PostAccountKey = "wallets"
:> Capture "walletId" (ApiT WalletId)
:> "keys"
:> Capture "index" (ApiT DerivationIndex)
:> ReqBody '[JSON] ApiPostAccountKeyData
:> PostAccepted '[JSON] ApiAccountKey

{-------------------------------------------------------------------------------
Assets
Expand Down
13 changes: 13 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Link.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module Cardano.Wallet.Api.Link
-- * WalletKeys
, getWalletKey
, signMetadata
, postAccountKey

-- * Addresses
, postRandomAddress
Expand Down Expand Up @@ -302,6 +303,18 @@ signMetadata w role_ index =
where
wid = w ^. typed @(ApiT WalletId)

postAccountKey
:: forall w.
( HasType (ApiT WalletId) w
)
=> w
-> DerivationIndex
-> (Method, Text)
postAccountKey w index =
endpoint @Api.PostAccountKey (\mk -> mk wid (ApiT index))
where
wid = w ^. typed @(ApiT WalletId)

--
-- Addresses
--
Expand Down
46 changes: 42 additions & 4 deletions lib/core/src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module Cardano.Wallet.Api.Server
, selectCoinsForJoin
, selectCoinsForQuit
, signMetadata
, postAccountPublicKey

-- * Internals
, LiftHandler(..)
Expand All @@ -100,7 +101,7 @@ module Cardano.Wallet.Api.Server
import Prelude

import Cardano.Address.Derivation
( XPrv, XPub, xpubPublicKey )
( XPrv, XPub, xpubPublicKey, xpubToBytes )
import Cardano.Api.Typed
( AnyCardanoEra (..), CardanoEra (..) )
import Cardano.Mnemonic
Expand All @@ -125,6 +126,7 @@ import Cardano.Wallet
, ErrNotASequentialWallet (..)
, ErrPostTx (..)
, ErrQuitStakePool (..)
, ErrReadAccountPublicKey (..)
, ErrReadRewardAccount (..)
, ErrRemoveTx (..)
, ErrSelectAssets (..)
Expand Down Expand Up @@ -159,6 +161,7 @@ import Cardano.Wallet.Api.Server.Tls
import Cardano.Wallet.Api.Types
( AccountPostData (..)
, AddressAmount (..)
, ApiAccountKey (..)
, ApiAccountPublicKey (..)
, ApiAddress (..)
, ApiAsset (..)
Expand All @@ -180,6 +183,7 @@ import Cardano.Wallet.Api.Types
, ApiNetworkInformation
, ApiNetworkParameters (..)
, ApiPoolId (..)
, ApiPostAccountKeyData (..)
, ApiPostRandomAddressData (..)
, ApiPutAddressesData (..)
, ApiSelectCoinsPayments
Expand Down Expand Up @@ -1849,6 +1853,24 @@ derivePublicKey ctx (ApiT wid) (ApiT role_) (ApiT ix) = do
k <- liftHandler $ W.derivePublicKey @_ @s @k @n wrk wid role_ ix
pure $ ApiVerificationKey (xpubPublicKey $ getRawKey k, role_)

postAccountPublicKey
:: forall ctx s k n.
( s ~ SeqState n k
, ctx ~ ApiLayer s k
, HardDerivation k
, WalletKey k
)
=> ctx
-> ApiT WalletId
-> ApiT DerivationIndex
-> ApiPostAccountKeyData
-> Handler ApiAccountKey
postAccountPublicKey ctx (ApiT wid) (ApiT ix) (ApiPostAccountKeyData (ApiT pwd) extd) = do
withWorkerCtx @_ @s @k ctx wid liftE liftE $ \wrk -> do
k <- liftHandler $ W.readPublicAccountKey @_ @s @k @n wrk wid pwd ix
let toBytes = if extd then xpubToBytes else xpubPublicKey
pure $ ApiAccountKey (toBytes $ getRawKey k) extd

{-------------------------------------------------------------------------------
Helpers
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -2703,19 +2725,25 @@ instance LiftHandler ErrSignMetadataWith where
ErrSignMetadataWithNoSuchWallet e -> handler e
ErrSignMetadataWithInvalidIndex e -> handler e

instance LiftHandler ErrReadAccountPublicKey where
handler = \case
ErrReadAccountPublicKeyRootKey e -> handler e
ErrReadAccountPublicKeyNoSuchWallet e -> handler e
ErrReadAccountPublicKeyInvalidIndex e -> handler e

instance LiftHandler ErrDerivePublicKey where
handler = \case
ErrDerivePublicKeyNoSuchWallet e -> handler e
ErrDerivePublicKeyInvalidIndex e -> handler e

instance LiftHandler ErrInvalidDerivationIndex where
instance LiftHandler (ErrInvalidDerivationIndex 'Soft level) where
handler = \case
ErrIndexTooHigh maxIx _ix ->
ErrIndexOutOfBound minIx maxIx _ix ->
apiError err403 SoftDerivationRequired $ mconcat
[ "It looks like you've provided a derivation index that is "
, "out of bound. The index is well-formed, but I require "
, "indexes valid for soft derivation only. That is, indexes "
, "between 0 and ", pretty maxIx, " without a suffix."
, "between ", pretty minIx, " and ", pretty maxIx, " without a suffix."
]

instance LiftHandler ErrSelectAssets where
Expand Down Expand Up @@ -2778,6 +2806,16 @@ instance LiftHandler ErrSelectAssets where
, "or try sending a different, smaller payment."
]

instance LiftHandler (ErrInvalidDerivationIndex 'Hardened level) where
handler = \case
ErrIndexOutOfBound minIx maxIx _ix ->
apiError err403 HardenedDerivationRequired $ mconcat
[ "It looks like you've provided a derivation index that is "
, "out of bound. The index is well-formed, but I require "
, "indexes valid for hardened derivation only. That is, indexes "
, "between ", pretty minIx, " and ", pretty maxIx, " with a suffix 'H'."
]

instance LiftHandler (Request, ServerError) where
handler (req, err@(ServerError code _ body headers))
| not (isJSON body) = case code of
Expand Down
Loading

0 comments on commit d13eaa5

Please sign in to comment.