diff --git a/test/Wallet/Cip30/SignData.js b/examples/SignData.js similarity index 64% rename from test/Wallet/Cip30/SignData.js rename to examples/SignData.js index 353135d766..f681dc76a9 100644 --- a/test/Wallet/Cip30/SignData.js +++ b/examples/SignData.js @@ -1,5 +1,8 @@ -import * as csl from "@mlabs-haskell/cardano-serialization-lib-gc"; +"use strict"; + +// eslint-disable-next-line no-unused-vars import * as lib from "@mlabs-haskell/cardano-message-signing"; +import * as CSL from "@mlabs-haskell/cardano-serialization-lib-gc"; function opt_chain(maybe, obj) { const isNothing = x => x === null || x === undefined; @@ -14,42 +17,10 @@ function opt_chain(maybe, obj) { return isNothing(result) ? maybe.nothing : maybe.just(result); } -const fromBytes = name => bytes => () => { - return lib[name].from_bytes(bytes); -}; - -// ----------------------------------------------------------------------------- -// PublicKey -// ----------------------------------------------------------------------------- - -// verifySignature :: COSESign1 -> PublicKey -> CborBytes -> Effect Boolean -export function verifySignature(coseSign1) { - return publicKey => sigStructBytes => () => { - const signature = csl.Ed25519Signature.from_bytes(coseSign1.signature()); - return publicKey.verify(sigStructBytes, signature); - }; -} - -// ----------------------------------------------------------------------------- -// COSESign1 -// ----------------------------------------------------------------------------- - -// _fromBytesCoseSign1 :: CborBytes -> Effect COSESign1 -export const fromBytesCoseSign1 = fromBytes("COSESign1"); - -// getSignedData :: COSESign1 -> Effect CborBytes -export function getSignedData(coseSign1) { - return () => { - return coseSign1.signed_data(null, null).to_bytes(); - }; -} - -// getCoseSign1ProtectedHeaders :: COSESign1 -> HeaderMap const getCoseSign1ProtectedHeaders = coseSign1 => { return coseSign1.headers().protected().deserialized_headers(); }; -// getCoseSign1ProtectedHeaderAlg :: MaybeFfiHelper -> COSESign1 -> Maybe Int export function _getCoseSign1ProtectedHeaderAlg(maybe) { return coseSign1 => { const protectedHeaders = getCoseSign1ProtectedHeaders(coseSign1); @@ -63,8 +34,6 @@ export function _getCoseSign1ProtectedHeaderAlg(maybe) { }; } -// _getCoseSign1ProtectedHeaderAddress -// :: MaybeFfiHelper -> COSESign1 -> Maybe CborBytes export function _getCoseSign1ProtectedHeaderAddress(maybe) { return coseSign1 => { const protectedHeaders = getCoseSign1ProtectedHeaders(coseSign1); @@ -73,37 +42,18 @@ export function _getCoseSign1ProtectedHeaderAddress(maybe) { }; } -// _getCoseSign1ProtectedHeaderKid -// :: MaybeFfiHelper -> COSESign1 -> Maybe RawBytes -export function _getCoseSign1ProtectedHeaderKid(maybe) { - return coseSign1 => { - const protectedHeaders = getCoseSign1ProtectedHeaders(coseSign1); - return opt_chain(maybe, protectedHeaders, "key_id"); - }; -} - -// ----------------------------------------------------------------------------- -// COSEKey -// ----------------------------------------------------------------------------- - -// _fromBytesCoseKey :: CborBytes -> Effect COSEKey -export const fromBytesCoseKey = fromBytes("COSEKey"); - -// _getCoseKeyHeaderKty :: MaybeFfiHelper -> COSEKey -> Maybe Int export function _getCoseKeyHeaderKty(maybe) { return coseKey => { return opt_chain(maybe, coseKey.key_type(), "as_int", "as_i32"); }; } -// _getCoseKeyHeaderAlg :: MaybeFfiHelper -> COSEKey -> Maybe Int export function _getCoseKeyHeaderAlg(maybe) { return coseKey => { return opt_chain(maybe, coseKey, "algorithm_id", "as_int", "as_i32"); }; } -// _getCoseKeyHeaderCrv :: MaybeFfiHelper -> COSEKey -> Maybe Int export function _getCoseKeyHeaderCrv(maybe) { return coseKey => { const cborValue = coseKey.header( @@ -115,7 +65,18 @@ export function _getCoseKeyHeaderCrv(maybe) { }; } -// _getCoseKeyHeaderX :: MaybeFfiHelper -> COSEKey -> Maybe RawBytes +export function _getCoseSign1ProtectedHeaderKid(maybe) { + return coseSign1 => { + const protectedHeaders = getCoseSign1ProtectedHeaders(coseSign1); + return opt_chain(maybe, protectedHeaders, "key_id"); + }; +} + +export function _getCoseKeyHeaderKid(maybe) { + return coseKey => { + return opt_chain(maybe, coseKey, "key_id"); + }; +} export function _getCoseKeyHeaderX(maybe) { return coseKey => { const cborValue = coseKey.header( @@ -126,10 +87,19 @@ export function _getCoseKeyHeaderX(maybe) { return opt_chain(maybe, cborValue, "as_bytes"); }; } - -// _getCoseKeyHeaderKid :: MaybeFfiHelper -> COSESign1 -> Maybe RawBytes -export function _getCoseKeyHeaderKid(maybe) { - return coseKey => { - return opt_chain(maybe, coseKey, "key_id"); +export function getSignedData(coseSign1) { + return () => { + return coseSign1.signed_data(null, null).to_bytes(); }; } +export function verifySignature(coseSign1) { + return publicKey => sigStructBytes => () => { + const signature = CSL.Ed25519Signature.from_bytes(coseSign1.signature()); + return publicKey.verify(sigStructBytes, signature); + }; +} +const fromBytes = name => bytes => () => { + return lib[name].from_bytes(bytes); +}; +export const fromBytesCoseKey = fromBytes("COSEKey"); +export const fromBytesCoseSign1 = fromBytes("COSESign1"); diff --git a/examples/SignData.purs b/examples/SignData.purs index 5ac619d014..e777a4a1b2 100644 --- a/examples/SignData.purs +++ b/examples/SignData.purs @@ -2,7 +2,11 @@ module Ctl.Examples.SignData (main, example, contract) where import Contract.Prelude -import Cardano.Types (RawBytes) +import Cardano.AsCbor (encodeCbor) +import Cardano.MessageSigning (DataSignature) +import Cardano.Types (CborBytes, PublicKey, RawBytes) +import Cardano.Types.PublicKey as PublicKey +import Cardano.Wallet.Cip30.SignData (COSEKey, COSESign1) import Contract.Address (Address) import Contract.Config (ContractParams, testnetNamiConfig) import Contract.Log (logInfo') @@ -11,8 +15,10 @@ import Contract.Wallet (getChangeAddress, getRewardAddresses, signData) import Data.Array (head) as Array import Data.ByteArray (byteArrayFromAscii) import Data.Maybe (fromJust) +import Effect.Aff (error) +import Effect.Class (class MonadEffect) +import Effect.Exception (throw, throwException) import Partial.Unsafe (unsafePartial) -import Test.Ctl.Wallet.Cip30.SignData (checkCip30SignDataResponse) main :: Effect Unit main = example testnetNamiConfig @@ -41,3 +47,137 @@ contract = do dataSignature <- signData address payload logInfo' $ "signData " <> addressLabel <> ": " <> show dataSignature void $ liftAff $ checkCip30SignDataResponse address dataSignature + +type DeserializedDataSignature = + { coseKey :: COSEKey + , coseSign1 :: COSESign1 + } + +checkCip30SignDataResponse + :: Address -> DataSignature -> Aff DeserializedDataSignature +checkCip30SignDataResponse address { key, signature } = do + coseSign1 <- liftEffect $ fromBytesCoseSign1 signature + coseKey <- liftEffect $ fromBytesCoseKey key + + checkCoseSign1ProtectedHeaders coseSign1 + checkCoseKeyHeaders coseKey + checkKidHeaders coseSign1 coseKey + liftEffect $ checkVerification coseSign1 coseKey + pure { coseKey, coseSign1 } + where + checkCoseSign1ProtectedHeaders :: COSESign1 -> Aff Unit + checkCoseSign1ProtectedHeaders coseSign1 = do + assertTrue "COSE_Sign1's alg (1) header must be set to EdDSA (-8)" + (getCoseSign1ProtectedHeaderAlg coseSign1 == Just (-8)) + + assertTrue "COSE_Sign1's \"address\" header must be set to address bytes" + ( getCoseSign1ProtectedHeaderAddress coseSign1 + == Just (encodeCbor address) + ) + + checkCoseKeyHeaders :: COSEKey -> Aff Unit + checkCoseKeyHeaders coseKey = do + assertTrue "COSE_Key's kty (1) header must be set to OKP (1)" + (getCoseKeyHeaderKty coseKey == Just 1) + + assertTrue "COSE_Key's alg (3) header must be set to EdDSA (-8)" + (getCoseKeyHeaderAlg coseKey == Just (-8)) + + assertTrue "COSE_Key's crv (-1) header must be set to Ed25519 (6)" + (getCoseKeyHeaderCrv coseKey == Just (6)) + + checkKidHeaders :: COSESign1 -> COSEKey -> Aff Unit + checkKidHeaders coseSign1 coseKey = + assertTrue + "COSE_Sign1's kid (4) and COSE_Key's kid (2) headers, if present, must \ + \be set to the same value" + (getCoseSign1ProtectedHeaderKid coseSign1 == getCoseKeyHeaderKid coseKey) + + checkVerification :: COSESign1 -> COSEKey -> Effect Unit + checkVerification coseSign1 coseKey = do + publicKey <- + errMaybe "COSE_Key's x (-2) header must be set to public key bytes" + $ getCoseKeyHeaderX coseKey + >>= PublicKey.fromRawBytes + sigStructBytes <- getSignedData coseSign1 + assertTrue "Signature verification failed" + =<< verifySignature coseSign1 publicKey sigStructBytes + +getCoseSign1ProtectedHeaderAlg :: COSESign1 -> Maybe Int +getCoseSign1ProtectedHeaderAlg = _getCoseSign1ProtectedHeaderAlg maybeFfiHelper + +getCoseSign1ProtectedHeaderAddress :: COSESign1 -> Maybe CborBytes +getCoseSign1ProtectedHeaderAddress = + _getCoseSign1ProtectedHeaderAddress maybeFfiHelper + +type MaybeFfiHelper = + { nothing :: forall (x :: Type). Maybe x + , just :: forall (x :: Type). x -> Maybe x + , from :: forall (x :: Type). x -> Maybe x -> x + } + +maybeFfiHelper :: MaybeFfiHelper +maybeFfiHelper = { nothing: Nothing, just: Just, from: fromMaybe } + +getCoseKeyHeaderKty :: COSEKey -> Maybe Int +getCoseKeyHeaderKty = _getCoseKeyHeaderKty maybeFfiHelper + +getCoseKeyHeaderAlg :: COSEKey -> Maybe Int +getCoseKeyHeaderAlg = _getCoseKeyHeaderAlg maybeFfiHelper + +getCoseKeyHeaderCrv :: COSEKey -> Maybe Int +getCoseKeyHeaderCrv = _getCoseKeyHeaderCrv maybeFfiHelper + +getCoseSign1ProtectedHeaderKid :: COSESign1 -> Maybe RawBytes +getCoseSign1ProtectedHeaderKid = _getCoseSign1ProtectedHeaderKid maybeFfiHelper + +getCoseKeyHeaderKid :: COSEKey -> Maybe RawBytes +getCoseKeyHeaderKid = _getCoseKeyHeaderKid maybeFfiHelper + +assertTrue + :: forall (m :: Type -> Type) + . Applicative m + => MonadEffect m + => String + -> Boolean + -> m Unit +assertTrue msg b = unless b $ liftEffect $ throwException $ error msg + +errMaybe + :: forall (m :: Type -> Type) (a :: Type) + . MonadEffect m + => String + -> Maybe a + -> m a +errMaybe msg = maybe (liftEffect $ throw msg) pure + +getCoseKeyHeaderX :: COSEKey -> Maybe RawBytes +getCoseKeyHeaderX = _getCoseKeyHeaderX maybeFfiHelper + +-------------------------------------------------------------------------------- +-- Foreign functions +-------------------------------------------------------------------------------- + +foreign import _getCoseSign1ProtectedHeaderAlg + :: MaybeFfiHelper -> COSESign1 -> Maybe Int + +foreign import _getCoseSign1ProtectedHeaderAddress + :: MaybeFfiHelper -> COSESign1 -> Maybe CborBytes + +foreign import _getCoseKeyHeaderX :: MaybeFfiHelper -> COSEKey -> Maybe RawBytes +foreign import _getCoseKeyHeaderKty :: MaybeFfiHelper -> COSEKey -> Maybe Int +foreign import _getCoseKeyHeaderAlg :: MaybeFfiHelper -> COSEKey -> Maybe Int +foreign import _getCoseKeyHeaderCrv :: MaybeFfiHelper -> COSEKey -> Maybe Int +foreign import _getCoseSign1ProtectedHeaderKid + :: MaybeFfiHelper -> COSESign1 -> Maybe RawBytes + +foreign import _getCoseKeyHeaderKid + :: MaybeFfiHelper -> COSEKey -> Maybe RawBytes + +foreign import getSignedData :: COSESign1 -> Effect CborBytes + +foreign import verifySignature + :: COSESign1 -> PublicKey -> CborBytes -> Effect Boolean + +foreign import fromBytesCoseSign1 :: CborBytes -> Effect COSESign1 +foreign import fromBytesCoseKey :: CborBytes -> Effect COSEKey diff --git a/packages.dhall b/packages.dhall index 081878fc47..ab0b497fd7 100644 --- a/packages.dhall +++ b/packages.dhall @@ -318,6 +318,54 @@ let additions = , repo = "https://github.com/mlabs-haskell/purescript-plutus-types" , version = "v1.0.1" } + , cip30-mock = + { dependencies = + [ "aff-promise", "console", "effect", "functions", "prelude" ] + , repo = "https://github.com/mlabs-haskell/purescript-cip30-mock" + , version = "v1.0.0" + } + , cardano-collateral-select = + { dependencies = + [ "arrays" + , "cardano-types" + , "console" + , "effect" + , "exceptions" + , "foldable-traversable" + , "lists" + , "maybe" + , "newtype" + , "ordered-collections" + , "partial" + , "prelude" + , "tuples" + ] + , repo = + "https://github.com/mlabs-haskell/purescript-cardano-collateral-select" + , version = "v1.0.0" + } + , cardano-key-wallet = + { dependencies = + [ "aeson" + , "aff" + , "arrays" + , "cardano-collateral-select" + , "cardano-message-signing" + , "cardano-types" + , "console" + , "effect" + , "either" + , "foldable-traversable" + , "maybe" + , "newtype" + , "prelude" + , "profunctor-lenses" + , "typelevel-prelude" + ] + , repo = + "https://github.com/mlabs-haskell/purescript-cardano-key-wallet" + , version = "v1.0.0" + } , uplc-apply-args = { dependencies = [ "aff" diff --git a/spago-packages.nix b/spago-packages.nix index 504b0f7fe4..ecec85db80 100644 --- a/spago-packages.nix +++ b/spago-packages.nix @@ -209,6 +209,18 @@ let installPhase = "ln -s $src $out"; }; + "cardano-collateral-select" = pkgs.stdenv.mkDerivation { + name = "cardano-collateral-select"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-cardano-collateral-select"; + rev = "193bf49be979b42aa1f0f9cb3d7582d6bc98e3b9"; + sha256 = "1jbl6k779brbqzf7jf80is63b23k3mqzf2mzr222qswd3wg8s5b0"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "cardano-hd-wallet" = pkgs.stdenv.mkDerivation { name = "cardano-hd-wallet"; version = "v1.0.0"; @@ -221,6 +233,18 @@ let installPhase = "ln -s $src $out"; }; + "cardano-key-wallet" = pkgs.stdenv.mkDerivation { + name = "cardano-key-wallet"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-cardano-key-wallet"; + rev = "55f176dbedddbd37297a3d1f90c756420159454e"; + sha256 = "1fr77kvgdvxqi0jhg98balrwpf7rlhwiyrf1v8z2112yyln2myj9"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "cardano-message-signing" = pkgs.stdenv.mkDerivation { name = "cardano-message-signing"; version = "v1.0.0"; @@ -305,6 +329,18 @@ let installPhase = "ln -s $src $out"; }; + "cip30-mock" = pkgs.stdenv.mkDerivation { + name = "cip30-mock"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-cip30-mock"; + rev = "7b4b7b2800f6d0ebd25554de63141cbd8c1e14a0"; + sha256 = "1b412s7p144h98csvy5w9z6vjhlpya9mqkxm2k8nxfdhq2znwfih"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "cip30-typesafe" = pkgs.stdenv.mkDerivation { name = "cip30-typesafe"; version = "d72e51fbc0255eb3246c9132d295de7f65e16a99"; @@ -1569,10 +1605,9 @@ let name = "uplc-apply-args"; version = "v1.0.0"; src = pkgs.fetchgit { - url = "https://github.com/mlabs-haskell/purescript-uplc-apply-args.git"; + url = "https://github.com/mlabs-haskell/purescript-uplc-apply-args"; rev = "aa528d5310cbfbd01b4d94557f404d95cfb6bb3c"; sha256 = "1r064ca2m16hkbcswrvlng032ax1ygbpr2gxrlaqmjlf2gnin280"; - }; phases = "installPhase"; installPhase = "ln -s $src $out"; diff --git a/spago.dhall b/spago.dhall index 957f2487e0..81cabcc47b 100644 --- a/spago.dhall +++ b/spago.dhall @@ -18,13 +18,14 @@ You can edit this file as you like. , "bignumber" , "bytearrays" , "cardano-hd-wallet" - , "cardano-message-signing" , "uplc-apply-args" , "cardano-plutus-data-schema" , "cardano-serialization-lib" + , "cardano-key-wallet" , "cardano-types" , "checked-exceptions" , "cip30" + , "cip30-mock" , "cip30-typesafe" , "console" , "control" diff --git a/src/Contract/Config.purs b/src/Contract/Config.purs index 57d523e14a..b7b8fa5605 100644 --- a/src/Contract/Config.purs +++ b/src/Contract/Config.purs @@ -23,11 +23,15 @@ module Contract.Config , module Data.Log.Message , module Ctl.Internal.ServerConfig , module Ctl.Internal.Wallet.Spec - , module Ctl.Internal.Wallet.Key + , module Cardano.Wallet.Key , module X ) where import Cardano.Types (NetworkId(MainnetId, TestnetId)) +import Cardano.Wallet.Key + ( PrivatePaymentKey(PrivatePaymentKey) + , PrivateStakeKey(PrivateStakeKey) + ) import Ctl.Internal.BalanceTx.Sync ( disabledSynchronizationParams ) as X @@ -67,10 +71,6 @@ import Ctl.Internal.ServerConfig , defaultKupoServerConfig , defaultOgmiosWsConfig ) -import Ctl.Internal.Wallet.Key - ( PrivatePaymentKey(PrivatePaymentKey) - , PrivateStakeKey(PrivateStakeKey) - ) import Ctl.Internal.Wallet.Spec ( Cip1852DerivationPath , MnemonicSource(MnemonicString, MnemonicFile) diff --git a/src/Contract/Wallet.purs b/src/Contract/Wallet.purs index 391ea46933..4ad52a1bab 100644 --- a/src/Contract/Wallet.purs +++ b/src/Contract/Wallet.purs @@ -18,6 +18,12 @@ import Cardano.Types (Address, StakePubKeyHash, UtxoMap, Value) import Cardano.Types.PaymentPubKeyHash (PaymentPubKeyHash) import Cardano.Types.TransactionUnspentOutput (TransactionUnspentOutput) import Cardano.Types.Value as Value +import Cardano.Wallet.Key + ( KeyWallet + , PrivatePaymentKey(PrivatePaymentKey) + , PrivateStakeKey(PrivateStakeKey) + , privateKeysToKeyWallet + ) as X import Contract.Config (PrivatePaymentKey, PrivateStakeKey) import Contract.Log (logTrace') import Contract.Monad (Contract) @@ -58,12 +64,6 @@ import Ctl.Internal.Wallet ) , isWalletAvailable ) as X -import Ctl.Internal.Wallet.Key - ( KeyWallet - , PrivatePaymentKey(PrivatePaymentKey) - , PrivateStakeKey(PrivateStakeKey) - , privateKeysToKeyWallet - ) as X import Ctl.Internal.Wallet.KeyFile (formatPaymentKey, formatStakeKey) as X import Ctl.Internal.Wallet.Spec ( Cip1852DerivationPath diff --git a/src/Contract/Wallet/Key.purs b/src/Contract/Wallet/Key.purs index 0849396a62..3b962f04c5 100644 --- a/src/Contract/Wallet/Key.purs +++ b/src/Contract/Wallet/Key.purs @@ -6,10 +6,10 @@ module Contract.Wallet.Key import Cardano.Types (PrivateKey, PublicKey) import Cardano.Types.PrivateKey as PrivateKey -import Ctl.Internal.Wallet.Key +import Cardano.Wallet.Key ( KeyWallet(KeyWallet) - , keyWalletPrivatePaymentKey - , keyWalletPrivateStakeKey + , getPrivatePaymentKey + , getPrivateStakeKey , privateKeysToKeyWallet ) as X import Ctl.Internal.Wallet.Spec diff --git a/src/Contract/Wallet/KeyFile.purs b/src/Contract/Wallet/KeyFile.purs index eeec865eb4..cb139e0047 100644 --- a/src/Contract/Wallet/KeyFile.purs +++ b/src/Contract/Wallet/KeyFile.purs @@ -6,7 +6,7 @@ module Contract.Wallet.KeyFile import Prelude -import Ctl.Internal.Wallet.Key (KeyWallet, privateKeysToKeyWallet) +import Cardano.Wallet.Key (KeyWallet, privateKeysToKeyWallet) import Ctl.Internal.Wallet.KeyFile ( privatePaymentKeyFromFile , privatePaymentKeyFromTextEnvelope diff --git a/src/Internal/Contract/Wallet.purs b/src/Internal/Contract/Wallet.purs index 08a351965b..c0262a8c0e 100644 --- a/src/Internal/Contract/Wallet.purs +++ b/src/Internal/Contract/Wallet.purs @@ -28,6 +28,7 @@ import Cardano.Types.Value (Value, valueToCoin) import Cardano.Types.Value (geq, lovelaceValueOf, sum) as Value import Control.Monad.Reader.Trans (asks) import Control.Parallel (parTraverse) +import Ctl.Internal.BalanceTx.Collateral.Select (minRequiredCollateral) import Ctl.Internal.Contract (getProtocolParameters) import Ctl.Internal.Contract.Monad (Contract, filterLockedUtxos, getQueryHandle) import Ctl.Internal.Helpers (bugTrackerLink, liftM, liftedM) @@ -59,12 +60,16 @@ getChangeAddress = withWallet do actionBasedOnWallet _.getChangeAddress \kw -> do networkId <- asks _.networkId - pure $ (unwrap kw).address networkId + addr <- liftAff $ (unwrap kw).address networkId + pure addr getRewardAddresses :: Contract (Array Address) getRewardAddresses = withWallet $ actionBasedOnWallet _.getRewardAddresses - \kw -> asks _.networkId <#> Array.singleton <<< (unwrap kw).address + \kw -> do + networkId <- asks _.networkId + addr <- liftAff $ (unwrap kw).address networkId + pure $ Array.singleton addr -- | Get all `Address`es of the browser wallet. getWalletAddresses :: Contract (Array Address) @@ -72,7 +77,8 @@ getWalletAddresses = withWallet do actionBasedOnWallet _.getUsedAddresses ( \kw -> do networkId <- asks _.networkId - pure $ Array.singleton $ (unwrap kw).address networkId + addr <- liftAff $ (unwrap kw).address networkId + pure $ Array.singleton $ addr ) signData :: Address -> RawBytes -> Contract DataSignature @@ -127,13 +133,16 @@ getWalletCollateral = do actionBasedOnWallet _.getCollateral \kw -> do queryHandle <- getQueryHandle networkId <- asks _.networkId - let addr = (unwrap kw).address networkId + addr <- liftAff $ (unwrap kw).address networkId utxos <- (liftAff $ queryHandle.utxosAt addr) <#> hush >>> fromMaybe Map.empty >>= filterLockedUtxos - pure $ (unwrap kw).selectCollateral coinsPerUtxoByte + mColl <- liftAff $ (unwrap kw).selectCollateral + minRequiredCollateral + coinsPerUtxoByte (UInt.toInt maxCollateralInputs) utxos + pure mColl let {- This is a workaround for the case where Eternl wallet, in addition to designated collateral UTxO, returns all UTxO's with diff --git a/src/Internal/Plutip/Server.purs b/src/Internal/Plutip/Server.purs index dbe4d522f1..fff9653f2d 100644 --- a/src/Internal/Plutip/Server.purs +++ b/src/Internal/Plutip/Server.purs @@ -20,6 +20,7 @@ import Affjax.ResponseFormat as Affjax.ResponseFormat import Cardano.Types (NetworkId(MainnetId)) import Cardano.Types.BigNum as BigNum import Cardano.Types.PrivateKey (PrivateKey(PrivateKey)) +import Cardano.Wallet.Key (PrivatePaymentKey(PrivatePaymentKey)) import Contract.Chain (waitNSlots) import Contract.Config (defaultSynchronizationParams, defaultTimeParams) import Contract.Monad (Contract, ContractEnv, liftContractM, runContractInEnv) @@ -79,7 +80,6 @@ import Ctl.Internal.Test.UtxoDistribution , transferFundsFromEnterpriseToBase ) import Ctl.Internal.Types.UsedTxOuts (newUsedTxOuts) -import Ctl.Internal.Wallet.Key (PrivatePaymentKey(PrivatePaymentKey)) import Data.Array as Array import Data.Bifunctor (lmap) import Data.Either (Either(Left, Right), either, isLeft) diff --git a/src/Internal/QueryM.purs b/src/Internal/QueryM.purs index 6fd3fa2989..5dece7cc83 100644 --- a/src/Internal/QueryM.purs +++ b/src/Internal/QueryM.purs @@ -67,6 +67,7 @@ import Cardano.Types (PlutusScript) import Cardano.Types.CborBytes (CborBytes) import Cardano.Types.PlutusScript as PlutusScript import Cardano.Types.TransactionHash (TransactionHash) +import Cardano.Wallet.Key (PrivatePaymentKey, PrivateStakeKey) import Control.Alt (class Alt) import Control.Alternative (class Alternative) import Control.Monad.Error.Class @@ -154,7 +155,6 @@ import Ctl.Internal.Service.Error ) import Ctl.Internal.Types.Chain as Chain import Ctl.Internal.Types.SystemStart (SystemStart) -import Ctl.Internal.Wallet.Key (PrivatePaymentKey, PrivateStakeKey) import Data.Bifunctor (lmap) import Data.ByteArray (byteArrayToHex) import Data.Either (Either(Left, Right), either, isRight) diff --git a/src/Internal/Test/E2E/Runner.purs b/src/Internal/Test/E2E/Runner.purs index debdd06738..383fef8d18 100644 --- a/src/Internal/Test/E2E/Runner.purs +++ b/src/Internal/Test/E2E/Runner.purs @@ -12,6 +12,11 @@ import Affjax (printError) import Affjax.ResponseFormat as Affjax.ResponseFormat import Cardano.Types.BigNum as BigNum import Cardano.Types.PrivateKey as PrivateKey +import Cardano.Wallet.Key + ( PrivateStakeKey + , getPrivatePaymentKey + , getPrivateStakeKey + ) import Control.Alt ((<|>)) import Control.Monad.Error.Class (liftMaybe) import Control.Promise (Promise, toAffE) @@ -73,11 +78,6 @@ import Ctl.Internal.Test.E2E.Wallets , namiSign ) import Ctl.Internal.Test.UtxoDistribution (withStakeKey) -import Ctl.Internal.Wallet.Key - ( PrivateStakeKey - , keyWalletPrivatePaymentKey - , keyWalletPrivateStakeKey - ) import Data.Array (catMaybes, mapMaybe, nub) import Data.Array as Array import Data.ByteArray (hexToByteArray) @@ -263,13 +263,15 @@ testPlan opts@{ tests } rt@{ wallets } = -- https://github.com/Plutonomicon/cardano-transaction-lib/issues/1197 liftAff $ withPlutipContractEnv (buildPlutipConfig opts) distr \env wallet -> do + kwPaymentKey <- liftAff $ getPrivatePaymentKey wallet + kwMStakeKey <- liftAff $ getPrivateStakeKey wallet (clusterSetup :: ClusterSetup) <- case env.backend of CtlBackend backend _ -> pure { ogmiosConfig: backend.ogmios.config , kupoConfig: backend.kupoConfig , keys: - { payment: keyWalletPrivatePaymentKey wallet - , stake: keyWalletPrivateStakeKey wallet + { payment: kwPaymentKey + , stake: kwMStakeKey } } _ -> liftEffect $ throw "Unsupported backend" diff --git a/src/Internal/Test/KeyDir.purs b/src/Internal/Test/KeyDir.purs index 12495fa548..42ff140a88 100644 --- a/src/Internal/Test/KeyDir.purs +++ b/src/Internal/Test/KeyDir.purs @@ -11,6 +11,7 @@ import Cardano.Types.MultiAsset as MultiAsset import Cardano.Types.PrivateKey as PrivateKey import Cardano.Types.PublicKey as PublicKey import Cardano.Types.Value as Value +import Cardano.Wallet.Key (KeyWallet) import Contract.Config (ContractParams) import Contract.Log (logError', logTrace') import Contract.Monad @@ -38,8 +39,8 @@ import Contract.Wallet , withKeyWallet ) import Contract.Wallet.Key - ( keyWalletPrivatePaymentKey - , keyWalletPrivateStakeKey + ( getPrivatePaymentKey + , getPrivateStakeKey ) import Contract.Wallet.KeyFile ( privatePaymentKeyFromTextEnvelope @@ -70,7 +71,6 @@ import Ctl.Internal.Types.TxConstraints , mustSpendPubKeyOutput , singleton ) -import Ctl.Internal.Wallet.Key (KeyWallet) import Data.Array (catMaybes) import Data.Array as Array import Data.Either (Either(Right, Left), hush) @@ -235,8 +235,8 @@ markAsInactive :: FilePath -> Array KeyWallet -> Contract Unit markAsInactive backup wallets = do flip parTraverse_ wallets \wallet -> do networkId <- asks _.networkId + address <- liftAff $ Address.toBech32 <$> (unwrap wallet).address networkId let - address = Address.toBech32 $ (unwrap wallet).address networkId inactiveFlagFile = Path.concat [ backup, address, "inactive" ] liftAff $ writeTextFile UTF8 inactiveFlagFile $ "This address was marked as inactive. " @@ -285,12 +285,12 @@ backupWallets :: FilePath -> ContractEnv -> Array KeyWallet -> Aff Unit backupWallets backup env walletsArray = liftAff $ flip parTraverse_ walletsArray \wallet -> do + payment <- getPrivatePaymentKey wallet + mbStake <- getPrivateStakeKey wallet + address <- liftAff $ Address.toBech32 <$> (unwrap wallet).address + env.networkId let - address = Address.toBech32 $ (unwrap wallet).address env.networkId - payment = keyWalletPrivatePaymentKey wallet - mbStake = keyWalletPrivateStakeKey wallet folder = Path.concat [ backup, address ] - mkdir folder privatePaymentKeyToFile (Path.concat [ folder, "payment_signing_key" ]) payment @@ -302,14 +302,12 @@ fundWallets :: ContractEnv -> Array KeyWallet -> Array (Array UtxoAmount) -> Aff BigNum fundWallets env walletsArray distrArray = runContractInEnv env $ noLogs do logTrace' "Funding wallets" - let - constraints = flip foldMap (Array.zip walletsArray distrArray) - \(wallet /\ walletDistr) -> flip foldMap walletDistr - \value -> mustPayToKeyWallet wallet $ - Value.mkValue - (wrap value) - MultiAsset.empty - + constraints <- liftAff $ flip foldMap (Array.zip walletsArray distrArray) + \(wallet /\ walletDistr) -> flip foldMap walletDistr + \value -> mustPayToKeyWallet wallet $ + Value.mkValue + (wrap value) + MultiAsset.empty txHash <- submitTxFromConstraints mempty constraints awaitTxConfirmed txHash let @@ -445,18 +443,20 @@ mustPayToKeyWallet :: forall (i :: Type) (o :: Type) . KeyWallet -> Value - -> TxConstraints -mustPayToKeyWallet wallet value = + -> Aff TxConstraints +mustPayToKeyWallet wallet value = do + kwPaymentKey <- getPrivatePaymentKey wallet + kwMStakeKey <- getPrivateStakeKey wallet + let convert = PublicKey.hash <<< PrivateKey.toPublicKey - payment = over wrap convert $ keyWalletPrivatePaymentKey wallet - mbStake = over wrap convert <$> keyWalletPrivateStakeKey wallet - in - maybe - -- We don't use `mustPayToPubKey payment` to avoid the compile-time - -- warning that is tied to it (it should not be propagated to - -- `runContractTestWithKeyDir`) - (singleton <<< MustPayToPubKeyAddress payment Nothing Nothing Nothing) - (mustPayToPubKeyAddress payment) - mbStake - value + payment = over wrap convert $ kwPaymentKey + mbStake = over wrap convert <$> kwMStakeKey + pure $ maybe + -- We don't use `mustPayToPubKey payment` to avoid the compile-time + -- warning that is tied to it (it should not be propagated to + -- `runContractTestWithKeyDir`) + (singleton <<< MustPayToPubKeyAddress payment Nothing Nothing Nothing) + (mustPayToPubKeyAddress payment) + mbStake + value diff --git a/src/Internal/Test/UtxoDistribution.purs b/src/Internal/Test/UtxoDistribution.purs index ef04613156..b6644938ea 100644 --- a/src/Internal/Test/UtxoDistribution.purs +++ b/src/Internal/Test/UtxoDistribution.purs @@ -25,6 +25,12 @@ import Cardano.Types import Cardano.Types.Address (Address(EnterpriseAddress)) import Cardano.Types.PrivateKey (PrivateKey) import Cardano.Types.UtxoMap (UtxoMap) +import Cardano.Wallet.Key + ( KeyWallet + , PrivatePaymentKey(PrivatePaymentKey) + , PrivateStakeKey + , privateKeysToKeyWallet + ) import Contract.Address (getNetworkId) import Contract.Monad (Contract, liftedM) import Contract.Prelude (foldM, foldMap, null) @@ -48,12 +54,6 @@ import Contract.Wallet import Control.Alternative (guard) import Control.Monad.Reader (asks) import Control.Monad.State.Trans (StateT(StateT), runStateT) -import Ctl.Internal.Wallet.Key - ( KeyWallet - , PrivatePaymentKey(PrivatePaymentKey) - , PrivateStakeKey - , privateKeysToKeyWallet - ) import Data.Array (head) import Data.Array as Array import Data.FoldableWithIndex (foldMapWithIndex) diff --git a/src/Internal/Wallet.purs b/src/Internal/Wallet.purs index 16d43bb315..8a6ba2180e 100644 --- a/src/Internal/Wallet.purs +++ b/src/Internal/Wallet.purs @@ -19,14 +19,14 @@ module Ctl.Internal.Wallet import Prelude import Cardano.Wallet.Cip30 as Cip30 -import Control.Monad.Error.Class (catchError, throwError) -import Ctl.Internal.Wallet.Cip30 (Cip30Wallet, mkCip30WalletAff) -import Ctl.Internal.Wallet.Key +import Cardano.Wallet.Key ( KeyWallet , PrivatePaymentKey , PrivateStakeKey , privateKeysToKeyWallet ) +import Control.Monad.Error.Class (catchError, throwError) +import Ctl.Internal.Wallet.Cip30 (Cip30Wallet, mkCip30WalletAff) import Data.Int (toNumber) import Data.Maybe (Maybe) import Data.Newtype (wrap) diff --git a/src/Internal/Wallet/Cip30Mock.js b/src/Internal/Wallet/Cip30Mock.js deleted file mode 100644 index 610baa03b4..0000000000 --- a/src/Internal/Wallet/Cip30Mock.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable no-global-assign */ - -export function injectCip30Mock(walletName) { - return mock => () => { - let window_ = typeof window != "undefined" ? window : (global.window = {}); - - if ( - typeof window_ == "object" && - typeof window_.cardano == "object" && - typeof window_.cardano[walletName] != "undefined" - ) { - throw ( - "injectCip30Mock: refusing to overwrite existing wallet (" + - walletName + - ")" - ); - } - - window_.cardano = {}; - window_.cardano[walletName] = { - enable: () => { - return new Promise((resolve, _reject) => - resolve({ - getNetworkId: mock.getNetworkId, - getUtxos: mock.getUtxos, - experimental: { - getCollateral: mock.getCollateral - }, - getBalance: mock.getBalance, - getUsedAddresses: mock.getUsedAddresses, - getUnusedAddresses: mock.getUnusedAddresses, - getChangeAddress: mock.getChangeAddress, - getRewardAddresses: mock.getRewardAddresses, - signTx: mock.signTx, - signData: mock.signData - }) - ); - } - }; - - return () => { - delete window_.cardano[walletName]; - }; - }; -} diff --git a/src/Internal/Wallet/Cip30Mock.purs b/src/Internal/Wallet/Cip30Mock.purs index a1a6b87c78..95d5b9d50d 100644 --- a/src/Internal/Wallet/Cip30Mock.purs +++ b/src/Internal/Wallet/Cip30Mock.purs @@ -22,11 +22,19 @@ import Cardano.Types.NetworkId (NetworkId(MainnetId, TestnetId)) import Cardano.Types.PrivateKey as PrivateKey import Cardano.Types.PublicKey as PublicKey import Cardano.Types.TransactionUnspentOutput as TransactionUnspentOutput +import Cardano.Wallet.Cip30Mock (Cip30Mock, injectCip30Mock) +import Cardano.Wallet.Key + ( KeyWallet(KeyWallet) + , PrivatePaymentKey + , PrivateStakeKey + , privateKeysToKeyWallet + ) import Contract.Monad (Contract) import Control.Monad.Error.Class (liftMaybe, try) import Control.Monad.Reader (ask) import Control.Monad.Reader.Class (local) -import Control.Promise (Promise, fromAff) +import Control.Promise (fromAff) +import Ctl.Internal.BalanceTx.Collateral.Select (minRequiredCollateral) import Ctl.Internal.Contract.Monad (getQueryHandle) import Ctl.Internal.Helpers (liftEither) import Ctl.Internal.Wallet @@ -41,22 +49,15 @@ import Ctl.Internal.Wallet ) , mkWalletAff ) -import Ctl.Internal.Wallet.Key - ( KeyWallet(KeyWallet) - , PrivatePaymentKey - , PrivateStakeKey - , privateKeysToKeyWallet - ) import Data.Array as Array import Data.ByteArray (byteArrayToHex, hexToByteArray) import Data.Either (hush) import Data.Foldable (fold, foldMap) -import Data.Function.Uncurried (Fn2, mkFn2) +import Data.Function.Uncurried (mkFn2) import Data.Map as Map import Data.Maybe (Maybe(Just), maybe) import Data.Newtype (unwrap, wrap) import Data.UInt as UInt -import Effect (Effect) import Effect.Aff (Aff) import Effect.Aff.Class (liftAff) import Effect.Class (liftEffect) @@ -92,8 +93,9 @@ withCip30Mock -> Contract a -> Contract a withCip30Mock (KeyWallet keyWallet) mock contract = do - cip30Mock <- mkCip30Mock keyWallet.paymentKey - keyWallet.stakeKey + kwPaymentKey <- liftAff keyWallet.paymentKey + kwMStakeKey <- liftAff keyWallet.stakeKey + cip30Mock <- mkCip30Mock kwPaymentKey kwMStakeKey deleteMock <- liftEffect $ injectCip30Mock mockString cip30Mock wallet <- liftAff mkWalletAff' res <- try $ local _ { wallet = Just wallet } contract @@ -118,24 +120,6 @@ withCip30Mock (KeyWallet keyWallet) mock contract = do MockNuFi -> "nufi" MockGenericCip30 name -> name -type Cip30Mock = - { getNetworkId :: Effect (Promise Int) - -- we ignore both the amount parameter and pagination: - , getUtxos :: Effect (Promise (Array String)) - -- we ignore the amount parameter: - , getCollateral :: Effect (Promise (Array String)) - , getBalance :: Effect (Promise String) - -- we ignore pagination parameter: - , getUsedAddresses :: Effect (Promise (Array String)) - , getUnusedAddresses :: Effect (Promise (Array String)) - , getChangeAddress :: Effect (Promise String) - , getRewardAddresses :: Effect (Promise (Array String)) - -- we ignore the 'isPartial' parameter - , signTx :: String -> Promise String - , signData :: - Fn2 String String (Promise { key :: String, signature :: String }) - } - mkCip30Mock :: PrivatePaymentKey -> Maybe PrivateStakeKey -> Contract Cip30Mock mkCip30Mock pKey mSKey = do @@ -148,22 +132,24 @@ mkCip30Mock pKey mSKey = do coinsPerUtxoByte = pparams.coinsPerUtxoByte maxCollateralInputs = UInt.toInt $ pparams.maxCollateralInputs - mbCollateral = fold $ - (unwrap keyWallet).selectCollateral coinsPerUtxoByte - maxCollateralInputs - utxos - pure mbCollateral + coll <- liftAff $ + (unwrap keyWallet).selectCollateral + minRequiredCollateral + coinsPerUtxoByte + maxCollateralInputs + utxos + pure $ fold coll ownUtxos = do - let ownAddress = (unwrap keyWallet).address env.networkId + ownAddress <- liftAff $ (unwrap keyWallet).address env.networkId liftMaybe (error "No UTxOs at address") <<< hush =<< do queryHandle.utxosAt ownAddress keyWallet = privateKeysToKeyWallet pKey mSKey - addressHex = - byteArrayToHex $ unwrap $ encodeCbor - ((unwrap keyWallet).address env.networkId :: Address) - + addressHex <- liftAff $ + (byteArrayToHex <<< unwrap <<< encodeCbor) <$> + ((unwrap keyWallet).address env.networkId :: Aff Address) + let mbRewardAddressHex = mSKey <#> \stakeKey -> let stakePubKey = PrivateKey.toPublicKey (unwrap stakeKey) @@ -223,6 +209,3 @@ mkCip30Mock pKey mSKey = do , signature: byteArrayToHex $ unwrap signature } } - --- returns an action that removes the mock. -foreign import injectCip30Mock :: String -> Cip30Mock -> Effect (Effect Unit) diff --git a/src/Internal/Wallet/Key.purs b/src/Internal/Wallet/Key.purs deleted file mode 100644 index be85f52188..0000000000 --- a/src/Internal/Wallet/Key.purs +++ /dev/null @@ -1,178 +0,0 @@ -module Ctl.Internal.Wallet.Key - ( KeyWallet(KeyWallet) - , PrivatePaymentKey(PrivatePaymentKey) - , PrivateStakeKey(PrivateStakeKey) - , privateKeysToAddress - , privateKeysToKeyWallet - , keyWalletPrivatePaymentKey - , keyWalletPrivateStakeKey - ) where - -import Prelude - -import Aeson - ( class DecodeAeson - , class EncodeAeson - , JsonDecodeError(TypeMismatch) - , decodeAeson - , encodeAeson - ) -import Cardano.MessageSigning (DataSignature) -import Cardano.MessageSigning (signData) as MessageSigning -import Cardano.Types.Address (Address(BaseAddress, EnterpriseAddress)) -import Cardano.Types.Coin (Coin) -import Cardano.Types.Credential (Credential(PubKeyHashCredential)) -import Cardano.Types.NetworkId (NetworkId) -import Cardano.Types.PaymentCredential (PaymentCredential(PaymentCredential)) -import Cardano.Types.PrivateKey (PrivateKey(PrivateKey)) -import Cardano.Types.PrivateKey as PrivateKey -import Cardano.Types.PublicKey as PublicKey -import Cardano.Types.RawBytes (RawBytes) -import Cardano.Types.StakeCredential (StakeCredential(StakeCredential)) -import Cardano.Types.Transaction (Transaction, hash) -import Cardano.Types.TransactionUnspentOutput (TransactionUnspentOutput) -import Cardano.Types.TransactionWitnessSet (TransactionWitnessSet) -import Cardano.Types.UtxoMap (UtxoMap) -import Contract.Prelude (class Newtype) -import Ctl.Internal.BalanceTx.Collateral.Select as Collateral -import Ctl.Internal.Lens (_vkeys) -import Data.Array (fromFoldable) -import Data.Either (note) -import Data.Foldable (fold) -import Data.Lens (set) -import Data.Maybe (Maybe(Just, Nothing)) -import Data.Newtype (unwrap, wrap) -import Effect.Aff (Aff) -import Effect.Class (liftEffect) - -------------------------------------------------------------------------------- --- Key backend -------------------------------------------------------------------------------- - --- | A wrapper over `PrivateKey` that provides an interface for CTL -newtype KeyWallet = KeyWallet - { address :: NetworkId -> Address - , selectCollateral :: - Coin - -> Int - -> UtxoMap - -> Maybe (Array TransactionUnspentOutput) - , signTx :: Transaction -> Aff TransactionWitnessSet - , signData :: NetworkId -> RawBytes -> Aff DataSignature - , paymentKey :: PrivatePaymentKey - , stakeKey :: Maybe PrivateStakeKey - } - -derive instance Newtype KeyWallet _ - -newtype PrivatePaymentKey = PrivatePaymentKey PrivateKey - -derive instance Newtype PrivatePaymentKey _ - -instance Show PrivatePaymentKey where - show _ = "(PrivatePaymentKey )" - -instance EncodeAeson PrivatePaymentKey where - encodeAeson (PrivatePaymentKey pk) = encodeAeson - (PrivateKey.toBech32 pk) - -instance DecodeAeson PrivatePaymentKey where - decodeAeson aeson = - decodeAeson aeson >>= - note (TypeMismatch "PrivateKey") - <<< map PrivatePaymentKey - <<< PrivateKey.fromBech32 - -newtype PrivateStakeKey = PrivateStakeKey PrivateKey - -derive instance Newtype PrivateStakeKey _ - -instance Show PrivateStakeKey where - show _ = "(PrivateStakeKey )" - -instance EncodeAeson PrivateStakeKey where - encodeAeson (PrivateStakeKey pk) = encodeAeson - (PrivateKey.toBech32 pk) - -instance DecodeAeson PrivateStakeKey where - decodeAeson aeson = - decodeAeson aeson >>= - note (TypeMismatch "PrivateKey") - <<< map PrivateStakeKey - <<< PrivateKey.fromBech32 - -keyWalletPrivatePaymentKey :: KeyWallet -> PrivatePaymentKey -keyWalletPrivatePaymentKey = unwrap >>> _.paymentKey - -keyWalletPrivateStakeKey :: KeyWallet -> Maybe PrivateStakeKey -keyWalletPrivateStakeKey = unwrap >>> _.stakeKey - -privateKeysToAddress - :: PrivatePaymentKey -> Maybe PrivateStakeKey -> NetworkId -> Address -privateKeysToAddress payKey mbStakeKey networkId = do - let pubPayKey = PrivateKey.toPublicKey (unwrap payKey) - case mbStakeKey of - Just stakeKey -> - let - pubStakeKey = PrivateKey.toPublicKey (unwrap stakeKey) - in - BaseAddress - { networkId - , paymentCredential: - ( PaymentCredential $ PubKeyHashCredential $ PublicKey.hash $ - pubPayKey - ) - , stakeCredential: - ( StakeCredential $ PubKeyHashCredential $ PublicKey.hash $ - pubStakeKey - ) - } - - Nothing -> pubPayKey # PublicKey.hash - >>> PubKeyHashCredential - >>> wrap - >>> { networkId, paymentCredential: _ } - >>> EnterpriseAddress - -privateKeysToKeyWallet - :: PrivatePaymentKey -> Maybe PrivateStakeKey -> KeyWallet -privateKeysToKeyWallet payKey mbStakeKey = - KeyWallet - { address - , selectCollateral - , signTx - , signData - , paymentKey: payKey - , stakeKey: mbStakeKey - } - where - address :: NetworkId -> Address - address = privateKeysToAddress payKey mbStakeKey - - selectCollateral - :: Coin - -> Int - -> UtxoMap - -> Maybe (Array TransactionUnspentOutput) - selectCollateral coinsPerUtxoByte maxCollateralInputs utxos = fromFoldable - <$> Collateral.selectCollateral coinsPerUtxoByte maxCollateralInputs utxos - - signTx :: Transaction -> Aff TransactionWitnessSet - signTx tx = liftEffect do - let - txHash = hash tx - payWitness = PrivateKey.makeVkeyWitness txHash (unwrap payKey) - mbStakeWitness = - mbStakeKey <#> \stakeKey -> - PrivateKey.makeVkeyWitness txHash (unwrap stakeKey) - let - witnessSet' = set _vkeys - ([ payWitness ] <> fold (pure <$> mbStakeWitness)) - mempty - pure witnessSet' - - signData :: NetworkId -> RawBytes -> Aff DataSignature - signData networkId payload = do - liftEffect $ MessageSigning.signData (unwrap payKey) - (address networkId) - payload diff --git a/src/Internal/Wallet/KeyFile.purs b/src/Internal/Wallet/KeyFile.purs index 3238cb04fc..80b0a7d946 100644 --- a/src/Internal/Wallet/KeyFile.purs +++ b/src/Internal/Wallet/KeyFile.purs @@ -16,6 +16,10 @@ import Prelude import Aeson (encodeAeson) import Cardano.Types.PrivateKey (PrivateKey) import Cardano.Types.PrivateKey as PrivateKey +import Cardano.Wallet.Key + ( PrivatePaymentKey(PrivatePaymentKey) + , PrivateStakeKey(PrivateStakeKey) + ) import Control.Monad.Error.Class (liftMaybe) import Control.Monad.Except (catchError) import Ctl.Internal.Cardano.TextEnvelope @@ -27,10 +31,6 @@ import Ctl.Internal.Cardano.TextEnvelope , decodeTextEnvelope ) import Ctl.Internal.Helpers (liftM) -import Ctl.Internal.Wallet.Key - ( PrivatePaymentKey(PrivatePaymentKey) - , PrivateStakeKey(PrivateStakeKey) - ) import Data.ByteArray (ByteArray, byteArrayToHex) import Data.Maybe (Maybe(Nothing)) import Data.Newtype (unwrap, wrap) diff --git a/src/Internal/Wallet/Spec.purs b/src/Internal/Wallet/Spec.purs index 29bd687802..21f2b5bdd1 100644 --- a/src/Internal/Wallet/Spec.purs +++ b/src/Internal/Wallet/Spec.purs @@ -28,6 +28,12 @@ import Cardano.Wallet.HD , derivePaymentKey , deriveStakeKey ) +import Cardano.Wallet.Key + ( KeyWallet + , PrivatePaymentKey(PrivatePaymentKey) + , PrivateStakeKey(PrivateStakeKey) + , privateKeysToKeyWallet + ) import Control.Monad.Error.Class (liftEither) import Ctl.Internal.Wallet ( Wallet(KeyWallet) @@ -44,12 +50,6 @@ import Ctl.Internal.Wallet , mkKeyWallet , mkWalletAff ) -import Ctl.Internal.Wallet.Key - ( KeyWallet - , PrivatePaymentKey(PrivatePaymentKey) - , PrivateStakeKey(PrivateStakeKey) - , privateKeysToKeyWallet - ) import Ctl.Internal.Wallet.KeyFile ( privatePaymentKeyFromFile , privateStakeKeyFromFile diff --git a/templates/ctl-scaffold/packages.dhall b/templates/ctl-scaffold/packages.dhall index 8be52f038c..669048fce7 100644 --- a/templates/ctl-scaffold/packages.dhall +++ b/templates/ctl-scaffold/packages.dhall @@ -318,6 +318,54 @@ let additions = , repo = "https://github.com/mlabs-haskell/purescript-plutus-types" , version = "v1.0.1" } + , cip30-mock = + { dependencies = + [ "aff-promise", "console", "effect", "functions", "prelude" ] + , repo = "https://github.com/mlabs-haskell/purescript-cip30-mock" + , version = "v1.0.0" + } + , cardano-collateral-select = + { dependencies = + [ "arrays" + , "cardano-types" + , "console" + , "effect" + , "exceptions" + , "foldable-traversable" + , "lists" + , "maybe" + , "newtype" + , "ordered-collections" + , "partial" + , "prelude" + , "tuples" + ] + , repo = + "https://github.com/mlabs-haskell/purescript-cardano-collateral-select" + , version = "v1.0.0" + } + , cardano-key-wallet = + { dependencies = + [ "aeson" + , "aff" + , "arrays" + , "cardano-collateral-select" + , "cardano-message-signing" + , "cardano-types" + , "console" + , "effect" + , "either" + , "foldable-traversable" + , "maybe" + , "newtype" + , "prelude" + , "profunctor-lenses" + , "typelevel-prelude" + ] + , repo = + "https://github.com/mlabs-haskell/purescript-cardano-key-wallet" + , version = "v1.0.0" + } , uplc-apply-args = { dependencies = [ "aff" @@ -450,7 +498,6 @@ let additions = , "bignumber" , "bytearrays" , "cardano-hd-wallet" - , "cardano-message-signing" , "cardano-plutus-data-schema" , "cardano-serialization-lib" , "cardano-types" diff --git a/templates/ctl-scaffold/spago-packages.nix b/templates/ctl-scaffold/spago-packages.nix index f8db4dae6d..e80b0fecbf 100644 --- a/templates/ctl-scaffold/spago-packages.nix +++ b/templates/ctl-scaffold/spago-packages.nix @@ -209,6 +209,18 @@ let installPhase = "ln -s $src $out"; }; + "cardano-collateral-select" = pkgs.stdenv.mkDerivation { + name = "cardano-collateral-select"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-cardano-collateral-select"; + rev = "193bf49be979b42aa1f0f9cb3d7582d6bc98e3b9"; + sha256 = "1jbl6k779brbqzf7jf80is63b23k3mqzf2mzr222qswd3wg8s5b0"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "cardano-hd-wallet" = pkgs.stdenv.mkDerivation { name = "cardano-hd-wallet"; version = "v1.0.0"; @@ -221,6 +233,18 @@ let installPhase = "ln -s $src $out"; }; + "cardano-key-wallet" = pkgs.stdenv.mkDerivation { + name = "cardano-key-wallet"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-cardano-key-wallet"; + rev = "55f176dbedddbd37297a3d1f90c756420159454e"; + sha256 = "1fr77kvgdvxqi0jhg98balrwpf7rlhwiyrf1v8z2112yyln2myj9"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "cardano-message-signing" = pkgs.stdenv.mkDerivation { name = "cardano-message-signing"; version = "v1.0.0"; @@ -257,7 +281,7 @@ let installPhase = "ln -s $src $out"; }; - "cardano-transaction-lib" = pkgs.stdenv.mkDerivation { + "cardano-transaction-lib" = pkgs.stdenv.mkDerivation { name = "cardano-transaction-lib"; version = "423e27b3f56b1a66db8d3126c22cea9bda7e50da"; src = pkgs.fetchgit { @@ -317,6 +341,18 @@ let installPhase = "ln -s $src $out"; }; + "cip30-mock" = pkgs.stdenv.mkDerivation { + name = "cip30-mock"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-cip30-mock"; + rev = "7b4b7b2800f6d0ebd25554de63141cbd8c1e14a0"; + sha256 = "1b412s7p144h98csvy5w9z6vjhlpya9mqkxm2k8nxfdhq2znwfih"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "cip30-typesafe" = pkgs.stdenv.mkDerivation { name = "cip30-typesafe"; version = "d72e51fbc0255eb3246c9132d295de7f65e16a99"; @@ -1581,10 +1617,9 @@ let name = "uplc-apply-args"; version = "v1.0.0"; src = pkgs.fetchgit { - url = "https://github.com/mlabs-haskell/purescript-uplc-apply-args.git"; + url = "https://github.com/mlabs-haskell/purescript-uplc-apply-args"; rev = "aa528d5310cbfbd01b4d94557f404d95cfb6bb3c"; sha256 = "1r064ca2m16hkbcswrvlng032ax1ygbpr2gxrlaqmjlf2gnin280"; - }; phases = "installPhase"; installPhase = "ln -s $src $out"; diff --git a/templates/ctl-scaffold/spago.dhall b/templates/ctl-scaffold/spago.dhall index 40de0f604f..b25547240d 100644 --- a/templates/ctl-scaffold/spago.dhall +++ b/templates/ctl-scaffold/spago.dhall @@ -7,7 +7,6 @@ You can edit this file as you like. [ "aff" , "bytearrays" , "cardano-hd-wallet" - , "cardano-message-signing" , "cardano-plutus-data-schema" , "cardano-serialization-lib" , "cardano-transaction-lib" diff --git a/test/Plutip/Common.purs b/test/Plutip/Common.purs index 3cd0304fb4..fbca65804a 100644 --- a/test/Plutip/Common.purs +++ b/test/Plutip/Common.purs @@ -5,10 +5,10 @@ module Test.Ctl.Plutip.Common import Prelude +import Cardano.Wallet.Key (PrivateStakeKey) import Contract.Keys (privateKeyFromBytes) import Contract.Test.Plutip (defaultPlutipConfig) import Ctl.Internal.Plutip.Types (PlutipConfig) -import Ctl.Internal.Wallet.Key (PrivateStakeKey) import Data.ByteArray (hexToByteArray) import Data.Maybe (fromJust) import Data.Newtype (wrap) diff --git a/test/Plutip/Staking.purs b/test/Plutip/Staking.purs index 630a407d17..076db91dac 100644 --- a/test/Plutip/Staking.purs +++ b/test/Plutip/Staking.purs @@ -64,7 +64,7 @@ import Contract.Wallet , ownStakePubKeyHashes , withKeyWallet ) -import Contract.Wallet.Key (keyWalletPrivateStakeKey, publicKeyFromPrivateKey) +import Contract.Wallet.Key (getPrivateStakeKey, publicKeyFromPrivateKey) import Ctl.Examples.AlwaysSucceeds (alwaysSucceedsScript) import Ctl.Examples.Helpers (submitAndLog) import Ctl.Examples.IncludeDatum (only42Script) @@ -285,8 +285,9 @@ suite = do ubTx <- mkUnbalancedTx lookups constraints balanceTx ubTx >>= signTransaction >>= submitAndLog - privateStakeKey <- liftM (error "Failed to get private stake key") $ - keyWalletPrivateStakeKey alice + kwMStakeKey <- liftAff $ getPrivateStakeKey alice + privateStakeKey <- liftM (error "Failed to get private stake key") + kwMStakeKey networkId <- getNetworkId let poolOperator = PoolPubKeyHash $ publicKeyHash $ diff --git a/test/Unit.purs b/test/Unit.purs index c7882764b2..da961ca05e 100644 --- a/test/Unit.purs +++ b/test/Unit.purs @@ -34,7 +34,6 @@ import Test.Ctl.Types.TokenName as Types.TokenName import Test.Ctl.Types.Transaction as Types.Transaction import Test.Ctl.UsedTxOuts as UsedTxOuts import Test.Ctl.Wallet.Bip32 as Bip32 -import Test.Ctl.Wallet.Cip30.SignData as Cip30SignData import Test.Spec.Runner (defaultConfig) -- Run with `spago test --main Test.Ctl.Unit` @@ -51,7 +50,6 @@ testPlan = do Ipv6.suite NativeScript.suite Bip32.suite - Cip30SignData.suite CslGc.suite Data.suite Hashing.suite diff --git a/test/Wallet/Bip32.purs b/test/Wallet/Bip32.purs index 7638934155..074f841254 100644 --- a/test/Wallet/Bip32.purs +++ b/test/Wallet/Bip32.purs @@ -6,11 +6,11 @@ import Contract.Prelude import Cardano.Types.Address as Address import Cardano.Types.NetworkId (NetworkId(MainnetId)) +import Cardano.Wallet.Key (KeyWallet) import Contract.Wallet.Key ( StakeKeyPresence(WithStakeKey) , mkKeyWalletFromMnemonic ) -import Ctl.Internal.Wallet.Key (KeyWallet(KeyWallet)) import Data.Lens (_Left, preview) import Data.UInt as UInt import Effect.Aff (Aff) @@ -31,15 +31,19 @@ suite = do <> ")" ) do - Address.fromBech32 addressStr `shouldEqual` - hush - ( mkKeyWalletFromMnemonic phrase1 + addr <- liftAff $ do + case + ( hush $ mkKeyWalletFromMnemonic phrase1 { accountIndex: UInt.fromInt accountIndex , addressIndex: UInt.fromInt addressIndex } - WithStakeKey <#> - \(KeyWallet wallet) -> wallet.address MainnetId + WithStakeKey ) + of + Nothing -> pure Nothing + Just (wlt :: KeyWallet) -> do + Just <$> (unwrap wlt).address MainnetId + Address.fromBech32 addressStr `shouldEqual` addr group "Invalid mnemonics" do test "handles errors for invalid phrases" do blush (mkKeyWalletFromMnemonic invalidPhrase zero WithStakeKey) diff --git a/test/Wallet/Cip30/SignData.purs b/test/Wallet/Cip30/SignData.purs deleted file mode 100644 index 0d9e5abe0b..0000000000 --- a/test/Wallet/Cip30/SignData.purs +++ /dev/null @@ -1,230 +0,0 @@ -module Test.Ctl.Wallet.Cip30.SignData - ( suite - , COSEKey - , COSESign1 - , checkCip30SignDataResponse - ) where - -import Prelude - -import Cardano.AsCbor (encodeCbor) -import Cardano.MessageSigning (signData) -import Cardano.Types - ( Address - , CborBytes - , PrivateKey(PrivateKey) - , PublicKey - , RawBytes - ) -import Cardano.Types.NetworkId (NetworkId(MainnetId)) -import Cardano.Types.NetworkId as NetworkId -import Cardano.Types.PrivateKey as PrivateKey -import Cardano.Types.PublicKey as PublicKey -import Contract.Keys (publicKeyFromBytes) -import Ctl.Internal.FfiHelpers (MaybeFfiHelper, maybeFfiHelper) -import Ctl.Internal.Wallet.Cip30 (DataSignature) -import Ctl.Internal.Wallet.Key - ( PrivatePaymentKey - , PrivateStakeKey - , privateKeysToAddress - ) -import Data.ByteArray (byteArrayFromIntArrayUnsafe) -import Data.Maybe (Maybe(Just), fromJust, fromMaybe) -import Data.Newtype (class Newtype, unwrap, wrap) -import Data.Traversable (traverse_) -import Effect (Effect) -import Effect.Aff (Aff) -import Effect.Class (liftEffect) -import Mote (group, test) -import Mote.TestPlanM (TestPlanM) -import Partial.Unsafe (unsafePartial) -import Test.Ctl.Utils (assertTrue, errMaybe) -import Test.QuickCheck.Arbitrary (class Arbitrary, arbitrary) -import Test.QuickCheck.Gen (Gen, chooseInt, randomSample, vectorOf) - -suite :: TestPlanM (Aff Unit) Unit -suite = - group "signData (CIP-30)" do - test "generates a valid signature and key for a given payload" do - traverse_ testCip30SignData =<< liftEffect (randomSample arbitrary) - --------------------------------------------------------------------------------- --- Tests --------------------------------------------------------------------------------- - -type TestInput = - { privateKey :: ArbitraryPrivatePaymentKey - , privateStakeKey :: Maybe ArbitraryPrivateStakeKey - , payload :: RawBytes - , networkId :: ArbitraryNetworkId - } - -type DeserializedDataSignature = - { coseKey :: COSEKey - , coseSign1 :: COSESign1 - } - -testCip30SignData :: TestInput -> Aff Unit -testCip30SignData { privateKey, privateStakeKey, payload, networkId } = do - let - address = privateKeysToAddress (unwrap privateKey) - (unwrap <$> privateStakeKey) - (unwrap networkId) - - dataSignature <- liftEffect $ signData (privatePaymentKey) address - payload - { coseKey } <- checkCip30SignDataResponse address dataSignature - - assertTrue "COSE_Key's x (-2) header must be set to public key bytes" - (getCoseKeyHeaderX coseKey == Just (PublicKey.toRawBytes publicPaymentKey)) - where - privatePaymentKey :: PrivateKey - privatePaymentKey = unwrap $ unwrap privateKey - - publicPaymentKey :: PublicKey - publicPaymentKey = PrivateKey.toPublicKey privatePaymentKey - -checkCip30SignDataResponse - :: Address -> DataSignature -> Aff DeserializedDataSignature -checkCip30SignDataResponse address { key, signature } = do - coseSign1 <- liftEffect $ fromBytesCoseSign1 signature - coseKey <- liftEffect $ fromBytesCoseKey key - - checkCoseSign1ProtectedHeaders coseSign1 - checkCoseKeyHeaders coseKey - checkKidHeaders coseSign1 coseKey - liftEffect $ checkVerification coseSign1 coseKey - pure { coseKey, coseSign1 } - where - checkCoseSign1ProtectedHeaders :: COSESign1 -> Aff Unit - checkCoseSign1ProtectedHeaders coseSign1 = do - assertTrue "COSE_Sign1's alg (1) header must be set to EdDSA (-8)" - (getCoseSign1ProtectedHeaderAlg coseSign1 == Just (-8)) - - assertTrue "COSE_Sign1's \"address\" header must be set to address bytes" - ( getCoseSign1ProtectedHeaderAddress coseSign1 - == Just (encodeCbor address) - ) - - checkCoseKeyHeaders :: COSEKey -> Aff Unit - checkCoseKeyHeaders coseKey = do - assertTrue "COSE_Key's kty (1) header must be set to OKP (1)" - (getCoseKeyHeaderKty coseKey == Just 1) - - assertTrue "COSE_Key's alg (3) header must be set to EdDSA (-8)" - (getCoseKeyHeaderAlg coseKey == Just (-8)) - - assertTrue "COSE_Key's crv (-1) header must be set to Ed25519 (6)" - (getCoseKeyHeaderCrv coseKey == Just (6)) - - checkKidHeaders :: COSESign1 -> COSEKey -> Aff Unit - checkKidHeaders coseSign1 coseKey = - assertTrue - "COSE_Sign1's kid (4) and COSE_Key's kid (2) headers, if present, must \ - \be set to the same value" - (getCoseSign1ProtectedHeaderKid coseSign1 == getCoseKeyHeaderKid coseKey) - - checkVerification :: COSESign1 -> COSEKey -> Effect Unit - checkVerification coseSign1 coseKey = do - publicKey <- - errMaybe "COSE_Key's x (-2) header must be set to public key bytes" - $ getCoseKeyHeaderX coseKey >>= publicKeyFromBytes - sigStructBytes <- getSignedData coseSign1 - assertTrue "Signature verification failed" - =<< verifySignature coseSign1 publicKey sigStructBytes - --------------------------------------------------------------------------------- --- Arbitrary --------------------------------------------------------------------------------- - -newtype ArbitraryPrivatePaymentKey = - ArbitraryPrivatePaymentKey PrivatePaymentKey - -derive instance Newtype ArbitraryPrivatePaymentKey _ - -instance Arbitrary ArbitraryPrivatePaymentKey where - arbitrary = - wrap <<< wrap <<< unwrap <$> (arbitrary :: Gen ArbitraryPrivateKey) - -newtype ArbitraryPrivateStakeKey = ArbitraryPrivateStakeKey PrivateStakeKey - -derive instance Newtype ArbitraryPrivateStakeKey _ - -instance Arbitrary ArbitraryPrivateStakeKey where - arbitrary = - wrap <<< wrap <<< unwrap <$> (arbitrary :: Gen ArbitraryPrivateKey) - -newtype ArbitraryPrivateKey = ArbitraryPrivateKey PrivateKey - -derive instance Newtype ArbitraryPrivateKey _ - -instance Arbitrary ArbitraryPrivateKey where - arbitrary = - wrap <<< unsafePartial fromJust <$> - (PrivateKey.fromRawBytes <$> privateKeyBytes) - where - privateKeyBytes :: Gen RawBytes - privateKeyBytes = - wrap <<< byteArrayFromIntArrayUnsafe <$> vectorOf 32 (chooseInt 0 255) - -newtype ArbitraryNetworkId = ArbitraryNetworkId NetworkId - -derive instance Newtype ArbitraryNetworkId _ - -instance Arbitrary ArbitraryNetworkId where - arbitrary = - wrap <<< fromMaybe MainnetId <<< NetworkId.fromInt <$> chooseInt 0 1 - --------------------------------------------------------------------------------- --- FFI --------------------------------------------------------------------------------- - -foreign import data COSESign1 :: Type -foreign import _getCoseSign1ProtectedHeaderAlg - :: MaybeFfiHelper -> COSESign1 -> Maybe Int - -foreign import _getCoseSign1ProtectedHeaderAddress - :: MaybeFfiHelper -> COSESign1 -> Maybe CborBytes - -foreign import _getCoseSign1ProtectedHeaderKid - :: MaybeFfiHelper -> COSESign1 -> Maybe RawBytes - -foreign import data COSEKey :: Type -foreign import _getCoseKeyHeaderKty :: MaybeFfiHelper -> COSEKey -> Maybe Int -foreign import _getCoseKeyHeaderAlg :: MaybeFfiHelper -> COSEKey -> Maybe Int -foreign import _getCoseKeyHeaderCrv :: MaybeFfiHelper -> COSEKey -> Maybe Int -foreign import _getCoseKeyHeaderX :: MaybeFfiHelper -> COSEKey -> Maybe RawBytes -foreign import _getCoseKeyHeaderKid - :: MaybeFfiHelper -> COSEKey -> Maybe RawBytes - -foreign import fromBytesCoseSign1 :: CborBytes -> Effect COSESign1 -foreign import fromBytesCoseKey :: CborBytes -> Effect COSEKey - -foreign import getSignedData :: COSESign1 -> Effect CborBytes -foreign import verifySignature - :: COSESign1 -> PublicKey -> CborBytes -> Effect Boolean - -getCoseSign1ProtectedHeaderAlg :: COSESign1 -> Maybe Int -getCoseSign1ProtectedHeaderAlg = _getCoseSign1ProtectedHeaderAlg maybeFfiHelper - -getCoseSign1ProtectedHeaderAddress :: COSESign1 -> Maybe CborBytes -getCoseSign1ProtectedHeaderAddress = - _getCoseSign1ProtectedHeaderAddress maybeFfiHelper - -getCoseSign1ProtectedHeaderKid :: COSESign1 -> Maybe RawBytes -getCoseSign1ProtectedHeaderKid = _getCoseSign1ProtectedHeaderKid maybeFfiHelper - -getCoseKeyHeaderKty :: COSEKey -> Maybe Int -getCoseKeyHeaderKty = _getCoseKeyHeaderKty maybeFfiHelper - -getCoseKeyHeaderAlg :: COSEKey -> Maybe Int -getCoseKeyHeaderAlg = _getCoseKeyHeaderAlg maybeFfiHelper - -getCoseKeyHeaderCrv :: COSEKey -> Maybe Int -getCoseKeyHeaderCrv = _getCoseKeyHeaderCrv maybeFfiHelper - -getCoseKeyHeaderX :: COSEKey -> Maybe RawBytes -getCoseKeyHeaderX = _getCoseKeyHeaderX maybeFfiHelper - -getCoseKeyHeaderKid :: COSEKey -> Maybe RawBytes -getCoseKeyHeaderKid = _getCoseKeyHeaderKid maybeFfiHelper