Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

[RCD-40] Enforce address discrimination check when sending transactions #3823

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/test/Test/Pos/Core/Arbitrary.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module Test.Pos.Core.Arbitrary
, SafeCoinPairSub (..)
, UnreasonableEoS (..)

, genAddress
, genSlotId
, genLocalSlotIndex
) where
Expand Down Expand Up @@ -259,6 +260,11 @@ instance Arbitrary Address where
arbitrary = makeAddress <$> arbitrary <*> arbitrary
shrink = genericShrink

genAddress :: NetworkMagic -> Gen Address
genAddress nm = makeAddress <$> arbitrary <*> genAddrAttr
where
genAddrAttr = AddrAttributes <$> arbitrary <*> arbitrary <*> pure nm

----------------------------------------------------------------------------
-- Attributes
----------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions wallet-new/src/Cardano/Wallet/API/V1/ReifyWalletError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -361,3 +361,6 @@ newPaymentError e = case e of

(NewPaymentUnknownAccountId e') ->
unknownHdAccount e'

ex@(NewPaymentAddressBadNetworkMagic _ _) ->
V1.UnknownError $ (sformat build ex)
7 changes: 7 additions & 0 deletions wallet-new/src/Cardano/Wallet/WalletLayer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import Pos.Chain.Block (Blund)
import Pos.Chain.Txp (Tx, TxId, Utxo)
import Pos.Chain.Update (ConfirmedProposalState, SoftwareVersion)
import Pos.Core (Coin, Timestamp)
import qualified Pos.Core as Core (Address)
import Pos.Core.Chrono (NE, NewestFirst (..), OldestFirst (..))
import Pos.Core.NetworkMagic (NetworkMagic)
import Pos.Crypto (EncryptedSecretKey, PassPhrase)

import Cardano.Wallet.API.Request (RequestParams (..))
Expand Down Expand Up @@ -569,6 +571,7 @@ data NewPaymentError =
| NewPaymentTimeLimitReached TimeExecutionLimit
| NewPaymentWalletIdDecodingFailed Text
| NewPaymentUnknownAccountId Kernel.UnknownHdAccount
| NewPaymentAddressBadNetworkMagic NetworkMagic (NonEmpty Core.Address)

-- | Unsound show instance needed for the 'Exception' instance.
instance Show NewPaymentError where
Expand All @@ -585,6 +588,10 @@ instance Buildable NewPaymentError where
bprint ("NewPaymentWalletIdDecodingFailed " % build) txt
build (NewPaymentUnknownAccountId err) =
bprint ("NewPaymentUnknownAccountId " % build) err
build (NewPaymentAddressBadNetworkMagic expectedNM dstAddrs) =
bprint ("NewPaymentAddressBadNetworkMagic " % build % " " % build)
expectedNM
(toList dstAddrs)


data EstimateFeesError =
Expand Down
38 changes: 36 additions & 2 deletions wallet-new/src/Cardano/Wallet/WalletLayer/Kernel/Active.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import Data.Time.Units (Second)

import Pos.Binary.Class (decodeFull')
import Pos.Chain.Txp (Tx (..), TxSigData (..))
import Pos.Core (Address, Coin, TxFeePolicy)
import Pos.Core (AddrAttributes (..), Address (..), Coin, TxFeePolicy)
import Pos.Core.Attributes (Attributes (..))
import Pos.Core.NetworkMagic (NetworkMagic, makeNetworkMagic)
import Pos.Crypto (PassPhrase, PublicKey, Signature (..))

import Cardano.Crypto.Wallet (xsignature)
Expand All @@ -29,6 +31,7 @@ import Cardano.Wallet.Kernel.CoinSelection.FromGeneric
InputGrouping, newOptions)
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
import Cardano.Wallet.Kernel.DB.TxMeta.Types
import Cardano.Wallet.Kernel.Internal (walletProtocolMagic)
import qualified Cardano.Wallet.Kernel.NodeStateAdaptor as Node
import qualified Cardano.Wallet.Kernel.Transactions as Kernel
import Cardano.Wallet.WalletLayer (EstimateFeesError (..),
Expand All @@ -52,8 +55,39 @@ pay activeWallet pw grouping regulation payment = liftIO $ do
runExceptT $ do
(opts, accId, payees) <- withExceptT NewPaymentWalletIdDecodingFailed $
setupPayment policy grouping regulation payment

-- Verify that all payee addresses are of the same `NetworkMagic`
-- as our `ActiveWallet`.
let nm = makeNetworkMagic $ Kernel.walletPassive activeWallet ^. walletProtocolMagic
ExceptT $ pure $ verifyPayeesNM nm payees

-- Pay the payees
withExceptT NewPaymentError $ ExceptT $
Kernel.pay activeWallet pw opts accId payees
Kernel.pay activeWallet pw opts accId payees

-- | Verifies that the `NetworkMagic` of each payee address matches the
-- provided `NetworkMagic`.
verifyPayeesNM
:: NetworkMagic
-> NonEmpty (Address, Coin)
-> Either NewPaymentError ()
verifyPayeesNM nm payees =
case nonEmpty invalidPayees of
Nothing -> Right ()
Just is -> Left $ NewPaymentAddressBadNetworkMagic nm is
where
addressHasValidMagic :: AddrAttributes -> Bool
addressHasValidMagic addrAttrs = nm == (aaNetworkMagic addrAttrs)
--
verifyPayeeNM
:: (Address, Coin)
-> Either Address ()
verifyPayeeNM (addr, _)
| (addressHasValidMagic ((attrData . addrAttributes) addr)) = Right ()
| otherwise = Left addr
--
invalidPayees :: [Address]
invalidPayees = fst $ partitionEithers (toList (map verifyPayeeNM payees))

-- | Estimates the fees for a payment.
estimateFees :: MonadIO m
Expand Down
34 changes: 33 additions & 1 deletion wallet-new/test/unit/Test/Spec/CoinSelection/Generators.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
module Test.Spec.CoinSelection.Generators (
genGroupedUtxo
, genPayee
, genPayeeWithNM
, genPayees
, genFiddlyPayees
, genUtxo
Expand All @@ -26,11 +27,12 @@ import Test.QuickCheck (Gen, arbitrary, choose, suchThat)

import qualified Pos.Chain.Txp as Core
import qualified Pos.Core as Core
import Pos.Core.NetworkMagic (NetworkMagic)

import Cardano.Wallet.Kernel.Util.Core (paymentAmount, utxoBalance)

-- type class instances
import Test.Pos.Core.Arbitrary ()
import Test.Pos.Core.Arbitrary (genAddress)

{-------------------------------------------------------------------------------
Useful types
Expand Down Expand Up @@ -92,6 +94,16 @@ arbitraryAddress opts = do
not (Core.isRedeemAddress a)
arbitrary `suchThat` (\a -> fiddlyCondition a && redeemCondition a)

arbitraryAddressWithNM :: NetworkMagic
-> StakeGenOptions
-> Gen Core.Address
arbitraryAddressWithNM nm opts = do
let fiddlyCondition a = not (fiddlyAddresses opts) ||
(length (sformat F.build a) < 104)
let redeemCondition a = allowRedeemAddresses opts ||
not (Core.isRedeemAddress a)
(genAddress nm) `suchThat` (\a -> fiddlyCondition a && redeemCondition a)


-- | Finalise the generation of 'a' by transferring all the remaining \"slack\".
finalise :: Semigroup a
Expand Down Expand Up @@ -257,6 +269,14 @@ genTxOut opts = fromStakeOptions opts genOne paymentAmount
addr <- arbitraryAddress opts
return (Core.TxOut addr coins :| [])

genTxOutWithNM :: NetworkMagic -> StakeGenOptions -> Gen (NonEmpty Core.TxOut)
genTxOutWithNM nm opts = fromStakeOptions opts genOne paymentAmount
where
genOne :: Maybe (NonEmpty Core.TxOut) -> Core.Coin -> Gen (NonEmpty Core.TxOut)
genOne _ coins = do
addr <- arbitraryAddressWithNM nm opts
return (Core.TxOut addr coins :| [])

utxoSmallestEntry :: Core.Utxo -> Core.Coin
utxoSmallestEntry utxo =
case sort (Map.toList utxo) of
Expand Down Expand Up @@ -320,6 +340,18 @@ genPayee _utxo payment = do
, allowRedeemAddresses = False
}

genPayeeWithNM :: NetworkMagic -> Core.Utxo -> Pay -> Gen (NonEmpty Core.TxOut)
genPayeeWithNM nm _utxo payment = do
let balance = toLovelaces payment
genTxOutWithNM nm StakeGenOptions {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genuine question: I thought stake distribution wasn't used in cardano addresses?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it important to get an answer to this?

stakeMaxValue = Nothing
, stakeGenerationTarget = AtLeast
, stakeNeeded = Core.mkCoin balance
, stakeMaxEntries = Just 1
, fiddlyAddresses = False
, allowRedeemAddresses = False
}

-- | Generates a single payee which has a redeem address inside.
genRedeemPayee :: Core.Utxo -> Pay -> Gen (NonEmpty Core.TxOut)
genRedeemPayee _utxo payment = do
Expand Down
8 changes: 5 additions & 3 deletions wallet-new/test/unit/Test/Spec/GetTransactions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import Cardano.Wallet.WalletLayer.Kernel.Transactions (toTransaction)

import qualified Test.Spec.Addresses as Addresses
import Test.Spec.CoinSelection.Generators (InitialBalance (..),
Pay (..), genPayee, genUtxoWithAtLeast)
Pay (..), genPayeeWithNM, genUtxoWithAtLeast)
import qualified Test.Spec.Fixture as Fixture
import qualified Test.Spec.NewPayment as NewPayment
import TxMetaStorageSpecs (Isomorphic (..), genMeta)
Expand Down Expand Up @@ -297,8 +297,9 @@ spec = do
prop "pay works normally for coin selection with additional utxos and changes" $ withMaxSuccess 10 $
monadicIO $ do
pm <- pick arbitrary
let nm = makeNetworkMagic pm
distr <- fmap (\(TxOut addr coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin))
<$> pick (genPayee mempty (PayLovelace 100))
<$> pick (genPayeeWithNM nm mempty (PayLovelace 100))
withUtxosFixture @IO pm [300, 400, 500, 600, 5000000] $ \_keystore _activeLayer aw f@Fix{..} -> do
let pw = Kernel.walletPassive aw
-- get the balance before the payment
Expand All @@ -320,8 +321,9 @@ spec = do
prop "estimateFees looks sane for coin selection with additional utxos and changes" $ withMaxSuccess 10 $
monadicIO $ do
pm <- pick arbitrary
let nm = makeNetworkMagic pm
distr <- fmap (\(TxOut addr coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin))
<$> pick (genPayee mempty (PayLovelace 100))
<$> pick (genPayeeWithNM nm mempty (PayLovelace 100))
withUtxosFixture @IO pm [300, 400, 500, 600, 5000000] $ \_keystore _activeLayer aw Fix{..} -> do
let pw = Kernel.walletPassive aw
-- do the payment
Expand Down
4 changes: 2 additions & 2 deletions wallet-new/test/unit/Test/Spec/NewPayment.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import Pos.Crypto (EncryptedSecretKey, ProtocolMagic,
safeDeterministicKeyGen)

import Test.Spec.CoinSelection.Generators (InitialBalance (..),
Pay (..), genPayee, genUtxoWithAtLeast)
Pay (..), genPayeeWithNM, genUtxoWithAtLeast)

import qualified Cardano.Wallet.API.V1.Types as V1
import qualified Cardano.Wallet.Kernel as Kernel
Expand Down Expand Up @@ -110,7 +110,7 @@ prepareFixtures nm initialBalance toPay = do
(getHdAddressIx newIndex)
return $ M.insert txIn (TxOutAux (TxOut addr coin)) acc
) M.empty (M.toList utxo)
payees <- fmap (\(TxOut addr coin) -> (addr, coin)) <$> pick (genPayee utxo toPay)
payees <- fmap (\(TxOut addr coin) -> (addr, coin)) <$> pick (genPayeeWithNM nm utxo toPay)

return $ \keystore aw -> do
liftIO $ Keystore.insert (WalletIdHdRnd newRootId) esk keystore
Expand Down