diff --git a/lib/ui/src/Cardano/Wallet/UI/Deposit/API/Payments.hs b/lib/ui/src/Cardano/Wallet/UI/Deposit/API/Payments.hs index 46da7ce17dd..5ab83ba3e0d 100644 --- a/lib/ui/src/Cardano/Wallet/UI/Deposit/API/Payments.hs +++ b/lib/ui/src/Cardano/Wallet/UI/Deposit/API/Payments.hs @@ -37,6 +37,7 @@ import Data.Aeson , withObject , withText , (.:) + , (.:?) ) import Data.Aeson.Types ( Parser @@ -72,6 +73,9 @@ import Web.FormUrlEncoded import qualified Data.Aeson as Aeson import qualified Data.Map.Monoidal.Strict as MonoidalMap +import Data.Maybe + ( fromMaybe + ) import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL @@ -132,8 +136,14 @@ instance FromJSON Transaction where dataType <- o .: "type" description <- o .: "description" cborHex <- o .: "cborHex" - bip32Paths <- o .: "bip32Paths" - pure Transaction{dataType, description, cborHex, bip32Paths} + bip32Paths <- o .:? "bip32Paths" + pure + Transaction + { dataType + , description + , cborHex + , bip32Paths = fromMaybe [] bip32Paths + } -- Orphan instances for BIP32Path -- TODO: move where they belong, in the module defining BIP32Path @@ -179,16 +189,36 @@ instance FromJSON BIP32Path where newtype Password = Password Text -data SignatureForm = SignatureForm - { signatureFormState :: State - , signaturePassword :: Password - } +data SignatureForm + = SignatureForm + { signatureFormState :: State + , signaturePassword :: Password + } + | ExternalSignatureForm + { signatureFormState :: State + , signatureSignedTransaction :: Transaction + } instance FromForm SignatureForm where fromForm form = do signatureFormState <- fromForm form - signaturePassword <- Password <$> parseUnique "passphrase" form - pure SignatureForm{signatureFormState, signaturePassword} + let + signature = do + signaturePassword <- Password <$> parseUnique "passphrase" form + pure SignatureForm{signatureFormState, signaturePassword} + externalSignature = do + signatureSignedTransaction <- parseUnique "signed-transaction" form + pure + ExternalSignatureForm{signatureFormState, signatureSignedTransaction} + case signature of + Left _ -> externalSignature + Right s -> pure s + +instance FromHttpApiData Transaction where + parseQueryParam :: Text -> Either Text Transaction + parseQueryParam t = case Aeson.decode $ TL.encodeUtf8 $ TL.fromStrict t of + Nothing -> Left "Invalid JSON for a Transaction" + Just tx -> pure tx data StateA t = NoState @@ -219,6 +249,7 @@ data Signal = AddReceiver Receiver | DeleteReceiver Address | Sign Password + | ExternallySign Transaction | Unsign | Submit | Reset @@ -274,6 +305,8 @@ step c (Unsigned utx) (DeleteReceiver addr) = do step c (Unsigned utx) (Sign pwd) = do stx <- sign c utx pwd pure $ Just $ Signed utx stx +step _ (Unsigned utx) (ExternallySign stx) = do + pure $ Just $ Signed utx stx step c (Signed utx _) (AddReceiver receiver) = do Just <$> addReceiver c utx receiver step c (Signed utx _) (DeleteReceiver addr) = do diff --git a/lib/ui/src/Cardano/Wallet/UI/Deposit/Html/Pages/Payments/Page.hs b/lib/ui/src/Cardano/Wallet/UI/Deposit/Html/Pages/Payments/Page.hs index d09c121480a..691496d8f3d 100644 --- a/lib/ui/src/Cardano/Wallet/UI/Deposit/Html/Pages/Payments/Page.hs +++ b/lib/ui/src/Cardano/Wallet/UI/Deposit/Html/Pages/Payments/Page.hs @@ -4,13 +4,10 @@ module Cardano.Wallet.UI.Deposit.Html.Pages.Payments.Page ( paymentsH , paymentsElementH - -- , receiversH - -- , updateReceiversH , availableBalanceElementH , receiverAddressValidationH , receiverAmountValidationH , paymentsChangeH - -- , submitH ) where @@ -89,6 +86,9 @@ import Control.Monad ( forM_ , when ) +import Data.Foldable + ( Foldable (..) + ) import Data.Maybe ( fromMaybe ) @@ -153,7 +153,7 @@ paymentsChangeH balance transaction = do setInspection Nothing setBalance balance Nothing Unsigned (utx, inspect) -> do - setReceivers $ Just (signatureFormH canSign, inspect) + setReceivers $ Just (signatureFormH utx canSign, inspect) setInspection $ Just (inspect, utx, Nothing) setBalance balance $ Just inspect Signed utx (stx, inspect) -> do @@ -183,7 +183,8 @@ setReceivers mInspect = $ case mInspect of Nothing -> receiversH Nothing Just (canSign, inspect) -> do - receiversH $ Just (canSign, extractReceivers inspect) + receiversH $ Just (extractReceivers inspect) + canSign setInspection :: Maybe (InspectTx, Transaction, Maybe Transaction) @@ -253,7 +254,7 @@ newReceiverH = do ] mempty -receiversH :: Maybe (Html (), Receivers) -> Html () +receiversH :: Maybe Receivers -> Html () receiversH m = do div_ [class_ "d-flex justify-content-end"] $ do table_ @@ -266,7 +267,7 @@ receiversH m = do thEnd (Just 9) "Amount" thEnd (Just 5) "Actions" tbody_ [id_ "payment-state"] - $ forM_ (MonoidalMap.assocs $ foldMap snd m) + $ forM_ (MonoidalMap.assocs $ fold m) $ \(address, Sum amount) -> do tr_ $ do tdEnd $ do @@ -285,9 +286,6 @@ receiversH m = do ] $ i_ [class_ "bi bi-trash"] mempty newReceiverH - case m of - Just (h, _) -> div_ [class_ "px-2"] h - _ -> pure () ifNotEmpty :: (Foldable t, Monoid b) => t a -> b -> b ifNotEmpty xs b = if null xs then mempty else b @@ -345,7 +343,9 @@ transactionInspectionH (InspectTx{..}, utx, mstx) = do $ do thead_ $ do tr_ $ do - thEnd Nothing $ toHtml $ truncatableText WithoutCopy "" "Change Address" + thEnd Nothing + $ toHtml + $ truncatableText WithoutCopy "" "Change Address" thEnd (Just 7) "Amount" tbody_ $ forM_ change @@ -386,16 +386,15 @@ transactionInspectionH (InspectTx{..}, utx, mstx) = do transactionCBORH :: Text -> Transaction -> Html () transactionCBORH copyName cbor = - truncatableText WithCopy copyName -- "unsigned-transaction-copy" + truncatableText WithCopy copyName $ toHtml $ Aeson.encode cbor -signatureFormH :: CanSign -> Html () -signatureFormH = \case +signatureFormH :: Transaction -> CanSign -> Html () +signatureFormH utx = \case CanSign -> do div_ [class_ "d-flex justify-content-end"] $ do div_ [class_ "input-group", style_ "max-width:35em"] $ do - -- span_ [class_ "input-group-text"] "Sign" input_ [ id_ "signature-password" , class_ "form-control text-end" @@ -410,7 +409,25 @@ signatureFormH = \case , hxInclude_ "#signature-password, #payment-state" ] "Sign" - CannotSign -> "paste signed tx not implemented" + CannotSign -> do + record (Just 15) Full Striped $ do + field [] "unsigned transaction" + $ transactionCBORH "unsigned-transaction-signature-copy" utx + div_ [class_ "d-flex justify-content-end"] $ do + div_ [class_ "input-group", style_ "max-width:35em"] $ do + input_ + [ id_ "signed-transaction" + , class_ "form-control text-end" + , name_ "signed-transaction" + , placeholder_ "signed transaction" + ] + button_ + [ class_ "btn btn-secondary" + , hxPost_ $ linkText paymentsSignLink + , hxInclude_ "#payment-state, #signed-transaction" + , hxTarget_ "#none" + ] + "Accept" submitH :: Html () submitH = do @@ -492,7 +509,7 @@ paymentsElementH = box "New" mempty $ do setState [] NoState - box "Payment Receivers" mempty + box "Transaction Creation" mempty $ do div_ [ id_ "receivers" diff --git a/lib/ui/src/Cardano/Wallet/UI/Deposit/Server/Payments/Page.hs b/lib/ui/src/Cardano/Wallet/UI/Deposit/Server/Payments/Page.hs index 6b1aac3b29e..0262506b6d4 100644 --- a/lib/ui/src/Cardano/Wallet/UI/Deposit/Server/Payments/Page.hs +++ b/lib/ui/src/Cardano/Wallet/UI/Deposit/Server/Payments/Page.hs @@ -1,5 +1,3 @@ -{-# LANGUAGE NamedFieldPuns #-} - module Cardano.Wallet.UI.Deposit.Server.Payments.Page ( servePaymentsPage , servePaymentsNewReceiver @@ -154,15 +152,18 @@ servePaymentsSign -> SignatureForm -> Maybe RequestCookies -> Handler (CookieResponse RawHtml) -servePaymentsSign ul SignatureForm{signatureFormState, signaturePassword} = +servePaymentsSign ul r = -- SignatureForm{signatureFormState, signaturePassword} = withSessionLayer ul $ \layer -> do renderHtml <$> signalHandler layer alertH paymentsChangeH - signatureFormState - (Sign signaturePassword) + (signatureFormState r) + (case r of + SignatureForm _ s -> Sign s + ExternalSignatureForm _ s -> ExternallySign s + ) servePaymentsSubmit :: UILayer WalletResource diff --git a/scripts/sign-tx.sh b/scripts/sign-tx.sh new file mode 100755 index 00000000000..868f6a528ce --- /dev/null +++ b/scripts/sign-tx.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env -S nix shell nixpkgs#jq .#cardano-cli .#cardano-address -c bash +# shellcheck shell=bash + +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +temp_dir=$(mktemp -d) + +# cleanup function +cleanup() { + rm -rf "$temp_dir" +} + +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# collect arguments +mnemonic=$1 +unsigned=$2 + +# create the root key +cardano-address key from-recovery-phrase Shelley <<<"$mnemonic" >"$temp_dir/root.xsk" + +# extract bip32 paths from the unsigned tx json +paths=$(jq -r '.bip32Paths[]' <<<"$unsigned") + +# derive keys, convert to cardano-cli format and collect --signing-key-file arguments +index=0 +signing_key_files="" +for path in $paths; do + key_file="$temp_dir/key${index}.xsk" + cardano-address key child "$path" <"$temp_dir/root.xsk" >"$key_file" + cli_key_file="$temp_dir/key${index}.skey" + cardano-cli key convert-cardano-address-key \ + --shelley-payment-key \ + --signing-key-file "$key_file" \ + --out-file "$cli_key_file" + signing_key_files="$signing_key_files --signing-key-file $temp_dir/key${index}.skey" + index=$((index + 1)) +done + +# dump unsigned tx to a file +echo "$unsigned" >"$temp_dir/tx.unsigned" + +# sign the transaction +# shellcheck disable=SC2086 +cardano-cli conway transaction sign \ + $signing_key_files \ + --tx-body-file "$temp_dir/tx.unsigned" \ + --out-file "$temp_dir/tx.signed" \ + --testnet-magic 1 + +# print the signed transaction +cat "$temp_dir/tx.signed"