diff --git a/lib/core/src/Cardano/Wallet/Api.hs b/lib/core/src/Cardano/Wallet/Api.hs index 81955f046bb..f055ad6d974 100644 --- a/lib/core/src/Cardano/Wallet/Api.hs +++ b/lib/core/src/Cardano/Wallet/Api.hs @@ -32,6 +32,7 @@ module Cardano.Wallet.Api , WalletKeys , GetWalletKey , SignMetadata + , GetAccountKey , Assets , ListAssets @@ -124,6 +125,7 @@ import Cardano.Wallet ( WalletLayer (..), WalletLog ) import Cardano.Wallet.Api.Types ( AnyAddress + , ApiAccountKey , ApiAddressData , ApiAddressIdT , ApiAddressInspect @@ -313,6 +315,7 @@ type GetUTxOsStatistics = "wallets" type WalletKeys = GetWalletKey :<|> SignMetadata + :<|> GetAccountKey -- | https://input-output-hk.github.io/cardano-wallet/api/#operation/getWalletKey type GetWalletKey = "wallets" @@ -331,6 +334,13 @@ type SignMetadata = "wallets" :> ReqBody '[JSON] ApiWalletSignData :> Post '[OctetStream] ByteString +-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/getAccountExtendedKey +type GetAccountKey = "wallets" + :> Capture "walletId" (ApiT WalletId) + :> "keys" + :> Capture "accountIx" (ApiT DerivationIndex) + :> Get '[JSON] ApiAccountKey + {------------------------------------------------------------------------------- Assets diff --git a/lib/core/src/Cardano/Wallet/Api/Server.hs b/lib/core/src/Cardano/Wallet/Api/Server.hs index 0680b516a7c..8419bd04c22 100644 --- a/lib/core/src/Cardano/Wallet/Api/Server.hs +++ b/lib/core/src/Cardano/Wallet/Api/Server.hs @@ -80,6 +80,7 @@ module Cardano.Wallet.Api.Server , selectCoinsForJoin , selectCoinsForQuit , signMetadata + , getAccountPublicKey -- * Internals , LiftHandler(..) @@ -165,6 +166,7 @@ import Cardano.Wallet.Api.Server.Tls import Cardano.Wallet.Api.Types ( AccountPostData (..) , AddressAmount (..) + , ApiAccountKey , ApiAccountPublicKey (..) , ApiAddress (..) , ApiAsset (..) @@ -1915,6 +1917,19 @@ 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_) +getAccountPublicKey + :: forall ctx s k n. + ( s ~ SeqState n k + , ctx ~ ApiLayer s k + , SoftDerivation k + , WalletKey k + ) + => ctx + -> ApiT WalletId + -> ApiT DerivationIndex + -> Handler ApiAccountKey +getAccountPublicKey _ctx (ApiT _wid) (ApiT _ix) = undefined + {------------------------------------------------------------------------------- Helpers -------------------------------------------------------------------------------} diff --git a/lib/core/src/Cardano/Wallet/Api/Types.hs b/lib/core/src/Cardano/Wallet/Api/Types.hs index dff770c7a53..66e90651ab4 100644 --- a/lib/core/src/Cardano/Wallet/Api/Types.hs +++ b/lib/core/src/Cardano/Wallet/Api/Types.hs @@ -115,6 +115,7 @@ module Cardano.Wallet.Api.Types , ApiWithdrawal (..) , ApiWalletSignData (..) , ApiVerificationKey (..) + , ApiAccountKey (..) -- * API Types (Byron) , ApiByronWallet (..) @@ -250,8 +251,8 @@ import Control.Arrow import Control.DeepSeq ( NFData ) import Control.Monad - ( guard, (>=>) ) -import Data.Aeson.Types + ( guard, unless, (>=>) ) +import Data.Aeson ( FromJSON (..) , SumEncoding (..) , ToJSON (..) @@ -940,6 +941,11 @@ newtype ApiVerificationKey = ApiVerificationKey } deriving (Eq, Generic, Show) deriving anyclass NFData +newtype ApiAccountKey = ApiAccountKey + { getApiAccountKey :: ByteString + } deriving (Eq, Generic, Show) + deriving anyclass NFData + -- | Error codes returned by the API, in the form of snake_cased strings data ApiErrorCode = NoSuchWallet @@ -1256,6 +1262,37 @@ instance FromJSON ApiVerificationKey where | otherwise = fail "Not a valid Ed25519 public key. Must be 32 bytes, without chain code" +instance ToJSON ApiAccountKey where + toJSON (ApiAccountKey pub) = + toJSON $ Bech32.encodeLenient hrp $ dataPartFromBytes pub + where + hrp = [humanReadablePart|acct_xvk|] + +instance FromJSON ApiAccountKey where + parseJSON value = do + (hrp, bytes) <- parseJSON value >>= parseBech32 + unless (hrp == [humanReadablePart|acct_xvk|]) $ + fail "Wrong human-readable part. Expected : \"acct_xvk\"." + ApiAccountKey <$> parsePub bytes + where + parseBech32 = + either (const $ fail errBech32) parseDataPart . Bech32.decodeLenient + where + errBech32 = + "Malformed extended account public key. Expected a bech32-encoded key." + + parseDataPart = + maybe (fail errDataPart) pure . traverse dataPartToBytes + where + errDataPart = + "Couldn't decode data-part to valid UTF-8 bytes." + + parsePub bytes + | BS.length bytes == 64 = + pure bytes + | otherwise = + fail "Not a valid Ed25519 extended public key. Must be 64 bytes, with chain code" + instance FromJSON ApiEpochInfo where parseJSON = genericParseJSON defaultRecordTypeOptions instance ToJSON ApiEpochInfo where diff --git a/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs b/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs index ba88c1129b4..6c85f31a477 100644 --- a/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs +++ b/lib/shelley/src/Cardano/Wallet/Shelley/Api/Server.hs @@ -59,6 +59,7 @@ import Cardano.Wallet.Api.Server , deleteTransaction , deleteWallet , derivePublicKey + , getAccountPublicKey , getAsset , getAssetDefault , getCurrentEpoch @@ -225,6 +226,7 @@ server byron icarus shelley spl ntp = walletKeys :: Server WalletKeys walletKeys = derivePublicKey shelley :<|> signMetadata shelley + :<|> getAccountPublicKey shelley assets :: Server Assets assets = listAssets shelley :<|> getAsset shelley :<|> getAssetDefault shelley diff --git a/specifications/api/swagger.yaml b/specifications/api/swagger.yaml index 57eb6f0ffbf..959d24d59de 100644 --- a/specifications/api/swagger.yaml +++ b/specifications/api/swagger.yaml @@ -1561,6 +1561,11 @@ components: format: bech32 pattern: "^((addr_vk)|(stake_vk)|(script_vk))1[0-9a-z]*$" + ApiAccountKey: &ApiAccountKey + type: string + format: bech32 + pattern: "^(acct_xvk)1[0-9a-z]*$" + ApiTxId: &ApiTxId type: object required: @@ -2177,17 +2182,6 @@ x-parametersWalletId: ¶metersWalletId maxLength: 40 minLength: 40 -x-parametersAccountIx: ¶metersAccountIx - in: path - name: accountIx - required: true - schema: - type: integer - minimum: 2147483648 - maximum: 4294967295 - description: | - The account index for Shelley account uses hard derivation, ie., indices (2 ^ 31, 2 ^ 32 - 1). - x-parametersIntendedStakeAmount: ¶metersIntendedStakeAmount in: query name: stake @@ -3303,6 +3297,16 @@ x-responsesGetKey: &responsesGetKey application/json: schema: *ApiVerificationKey +x-responsesGetAccountKey: &responsesGetAccountKey + <<: *responsesErr400 + <<: *responsesErr404 + <<: *responsesErr406 + 200: + description: Ok + content: + application/json: + schema: *ApiAccountKey + x-responsesListStakePools: &responsesListStakePools 400: description: Bad Request @@ -3800,7 +3804,7 @@ paths: - *parametersAddressState responses: *responsesListAddresses - /wallets/{walletId}/keys/{accountIx}: + /wallets/{walletId}/keys/{index}: get: operationId: getAccountExtendedKey tags: ["Keys"] @@ -3808,11 +3812,11 @@ paths: description: |

status: stable

- Return an extended account public key for a given account index. + Return an extended account public key for a given account index (hard derivation is used). parameters: - *parametersWalletId - - *parametersAccountIx - responses: *responsesGetKey + - *parametersDerivationSegment + responses: *responsesGetAccountKey /wallets/{walletId}/keys/{role}/{index}: get: