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

Commit 537260a

Browse files
authored
Merge pull request #3823 from input-output-hk/KtorZ/RCD-40/enforce-address-discrimination-when-sending-tx
[RCD-40] Enforce address discrimination check when sending transactions
2 parents 11f220d + c91aafa commit 537260a

File tree

7 files changed

+92
-8
lines changed

7 files changed

+92
-8
lines changed

core/test/Test/Pos/Core/Arbitrary.hs

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Test.Pos.Core.Arbitrary
1919
, SafeCoinPairSub (..)
2020
, UnreasonableEoS (..)
2121

22+
, genAddress
2223
, genSlotId
2324
, genLocalSlotIndex
2425
) where
@@ -259,6 +260,11 @@ instance Arbitrary Address where
259260
arbitrary = makeAddress <$> arbitrary <*> arbitrary
260261
shrink = genericShrink
261262

263+
genAddress :: NetworkMagic -> Gen Address
264+
genAddress nm = makeAddress <$> arbitrary <*> genAddrAttr
265+
where
266+
genAddrAttr = AddrAttributes <$> arbitrary <*> arbitrary <*> pure nm
267+
262268
----------------------------------------------------------------------------
263269
-- Attributes
264270
----------------------------------------------------------------------------

wallet-new/src/Cardano/Wallet/API/V1/ReifyWalletError.hs

+3
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,6 @@ newPaymentError e = case e of
361361

362362
(NewPaymentUnknownAccountId e') ->
363363
unknownHdAccount e'
364+
365+
ex@(NewPaymentAddressBadNetworkMagic _ _) ->
366+
V1.UnknownError $ (sformat build ex)

wallet-new/src/Cardano/Wallet/WalletLayer.hs

+7
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ import Pos.Chain.Block (Blund)
3939
import Pos.Chain.Txp (Tx, TxId, Utxo)
4040
import Pos.Chain.Update (ConfirmedProposalState, SoftwareVersion)
4141
import Pos.Core (Coin, Timestamp)
42+
import qualified Pos.Core as Core (Address)
4243
import Pos.Core.Chrono (NE, NewestFirst (..), OldestFirst (..))
44+
import Pos.Core.NetworkMagic (NetworkMagic)
4345
import Pos.Crypto (EncryptedSecretKey, PassPhrase)
4446

4547
import Cardano.Wallet.API.Request (RequestParams (..))
@@ -569,6 +571,7 @@ data NewPaymentError =
569571
| NewPaymentTimeLimitReached TimeExecutionLimit
570572
| NewPaymentWalletIdDecodingFailed Text
571573
| NewPaymentUnknownAccountId Kernel.UnknownHdAccount
574+
| NewPaymentAddressBadNetworkMagic NetworkMagic (NonEmpty Core.Address)
572575

573576
-- | Unsound show instance needed for the 'Exception' instance.
574577
instance Show NewPaymentError where
@@ -585,6 +588,10 @@ instance Buildable NewPaymentError where
585588
bprint ("NewPaymentWalletIdDecodingFailed " % build) txt
586589
build (NewPaymentUnknownAccountId err) =
587590
bprint ("NewPaymentUnknownAccountId " % build) err
591+
build (NewPaymentAddressBadNetworkMagic expectedNM dstAddrs) =
592+
bprint ("NewPaymentAddressBadNetworkMagic " % build % " " % build)
593+
expectedNM
594+
(toList dstAddrs)
588595

589596

590597
data EstimateFeesError =

wallet-new/src/Cardano/Wallet/WalletLayer/Kernel/Active.hs

+36-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import Data.Time.Units (Second)
1717

1818
import Pos.Binary.Class (decodeFull')
1919
import Pos.Chain.Txp (Tx (..), TxSigData (..))
20-
import Pos.Core (Address, Coin, TxFeePolicy)
20+
import Pos.Core (AddrAttributes (..), Address (..), Coin, TxFeePolicy)
21+
import Pos.Core.Attributes (Attributes (..))
22+
import Pos.Core.NetworkMagic (NetworkMagic, makeNetworkMagic)
2123
import Pos.Crypto (PassPhrase, PublicKey, Signature (..))
2224

2325
import Cardano.Crypto.Wallet (xsignature)
@@ -29,6 +31,7 @@ import Cardano.Wallet.Kernel.CoinSelection.FromGeneric
2931
InputGrouping, newOptions)
3032
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
3133
import Cardano.Wallet.Kernel.DB.TxMeta.Types
34+
import Cardano.Wallet.Kernel.Internal (walletProtocolMagic)
3235
import qualified Cardano.Wallet.Kernel.NodeStateAdaptor as Node
3336
import qualified Cardano.Wallet.Kernel.Transactions as Kernel
3437
import Cardano.Wallet.WalletLayer (EstimateFeesError (..),
@@ -52,8 +55,39 @@ pay activeWallet pw grouping regulation payment = liftIO $ do
5255
runExceptT $ do
5356
(opts, accId, payees) <- withExceptT NewPaymentWalletIdDecodingFailed $
5457
setupPayment policy grouping regulation payment
58+
59+
-- Verify that all payee addresses are of the same `NetworkMagic`
60+
-- as our `ActiveWallet`.
61+
let nm = makeNetworkMagic $ Kernel.walletPassive activeWallet ^. walletProtocolMagic
62+
ExceptT $ pure $ verifyPayeesNM nm payees
63+
64+
-- Pay the payees
5565
withExceptT NewPaymentError $ ExceptT $
56-
Kernel.pay activeWallet pw opts accId payees
66+
Kernel.pay activeWallet pw opts accId payees
67+
68+
-- | Verifies that the `NetworkMagic` of each payee address matches the
69+
-- provided `NetworkMagic`.
70+
verifyPayeesNM
71+
:: NetworkMagic
72+
-> NonEmpty (Address, Coin)
73+
-> Either NewPaymentError ()
74+
verifyPayeesNM nm payees =
75+
case nonEmpty invalidPayees of
76+
Nothing -> Right ()
77+
Just is -> Left $ NewPaymentAddressBadNetworkMagic nm is
78+
where
79+
addressHasValidMagic :: AddrAttributes -> Bool
80+
addressHasValidMagic addrAttrs = nm == (aaNetworkMagic addrAttrs)
81+
--
82+
verifyPayeeNM
83+
:: (Address, Coin)
84+
-> Either Address ()
85+
verifyPayeeNM (addr, _)
86+
| (addressHasValidMagic ((attrData . addrAttributes) addr)) = Right ()
87+
| otherwise = Left addr
88+
--
89+
invalidPayees :: [Address]
90+
invalidPayees = fst $ partitionEithers (toList (map verifyPayeeNM payees))
5791

5892
-- | Estimates the fees for a payment.
5993
estimateFees :: MonadIO m

wallet-new/test/unit/Test/Spec/CoinSelection/Generators.hs

+33-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
module Test.Spec.CoinSelection.Generators (
33
genGroupedUtxo
44
, genPayee
5+
, genPayeeWithNM
56
, genPayees
67
, genFiddlyPayees
78
, genUtxo
@@ -26,11 +27,12 @@ import Test.QuickCheck (Gen, arbitrary, choose, suchThat)
2627

2728
import qualified Pos.Chain.Txp as Core
2829
import qualified Pos.Core as Core
30+
import Pos.Core.NetworkMagic (NetworkMagic)
2931

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

3234
-- type class instances
33-
import Test.Pos.Core.Arbitrary ()
35+
import Test.Pos.Core.Arbitrary (genAddress)
3436

3537
{-------------------------------------------------------------------------------
3638
Useful types
@@ -92,6 +94,16 @@ arbitraryAddress opts = do
9294
not (Core.isRedeemAddress a)
9395
arbitrary `suchThat` (\a -> fiddlyCondition a && redeemCondition a)
9496

97+
arbitraryAddressWithNM :: NetworkMagic
98+
-> StakeGenOptions
99+
-> Gen Core.Address
100+
arbitraryAddressWithNM nm opts = do
101+
let fiddlyCondition a = not (fiddlyAddresses opts) ||
102+
(length (sformat F.build a) < 104)
103+
let redeemCondition a = allowRedeemAddresses opts ||
104+
not (Core.isRedeemAddress a)
105+
(genAddress nm) `suchThat` (\a -> fiddlyCondition a && redeemCondition a)
106+
95107

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

272+
genTxOutWithNM :: NetworkMagic -> StakeGenOptions -> Gen (NonEmpty Core.TxOut)
273+
genTxOutWithNM nm opts = fromStakeOptions opts genOne paymentAmount
274+
where
275+
genOne :: Maybe (NonEmpty Core.TxOut) -> Core.Coin -> Gen (NonEmpty Core.TxOut)
276+
genOne _ coins = do
277+
addr <- arbitraryAddressWithNM nm opts
278+
return (Core.TxOut addr coins :| [])
279+
260280
utxoSmallestEntry :: Core.Utxo -> Core.Coin
261281
utxoSmallestEntry utxo =
262282
case sort (Map.toList utxo) of
@@ -320,6 +340,18 @@ genPayee _utxo payment = do
320340
, allowRedeemAddresses = False
321341
}
322342

343+
genPayeeWithNM :: NetworkMagic -> Core.Utxo -> Pay -> Gen (NonEmpty Core.TxOut)
344+
genPayeeWithNM nm _utxo payment = do
345+
let balance = toLovelaces payment
346+
genTxOutWithNM nm StakeGenOptions {
347+
stakeMaxValue = Nothing
348+
, stakeGenerationTarget = AtLeast
349+
, stakeNeeded = Core.mkCoin balance
350+
, stakeMaxEntries = Just 1
351+
, fiddlyAddresses = False
352+
, allowRedeemAddresses = False
353+
}
354+
323355
-- | Generates a single payee which has a redeem address inside.
324356
genRedeemPayee :: Core.Utxo -> Pay -> Gen (NonEmpty Core.TxOut)
325357
genRedeemPayee _utxo payment = do

wallet-new/test/unit/Test/Spec/GetTransactions.hs

+5-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import Cardano.Wallet.WalletLayer.Kernel.Transactions (toTransaction)
7272

7373
import qualified Test.Spec.Addresses as Addresses
7474
import Test.Spec.CoinSelection.Generators (InitialBalance (..),
75-
Pay (..), genPayee, genUtxoWithAtLeast)
75+
Pay (..), genPayeeWithNM, genUtxoWithAtLeast)
7676
import qualified Test.Spec.Fixture as Fixture
7777
import qualified Test.Spec.NewPayment as NewPayment
7878
import TxMetaStorageSpecs (Isomorphic (..), genMeta)
@@ -297,8 +297,9 @@ spec = do
297297
prop "pay works normally for coin selection with additional utxos and changes" $ withMaxSuccess 10 $
298298
monadicIO $ do
299299
pm <- pick arbitrary
300+
let nm = makeNetworkMagic pm
300301
distr <- fmap (\(TxOut addr coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin))
301-
<$> pick (genPayee mempty (PayLovelace 100))
302+
<$> pick (genPayeeWithNM nm mempty (PayLovelace 100))
302303
withUtxosFixture @IO pm [300, 400, 500, 600, 5000000] $ \_keystore _activeLayer aw f@Fix{..} -> do
303304
let pw = Kernel.walletPassive aw
304305
-- get the balance before the payment
@@ -320,8 +321,9 @@ spec = do
320321
prop "estimateFees looks sane for coin selection with additional utxos and changes" $ withMaxSuccess 10 $
321322
monadicIO $ do
322323
pm <- pick arbitrary
324+
let nm = makeNetworkMagic pm
323325
distr <- fmap (\(TxOut addr coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin))
324-
<$> pick (genPayee mempty (PayLovelace 100))
326+
<$> pick (genPayeeWithNM nm mempty (PayLovelace 100))
325327
withUtxosFixture @IO pm [300, 400, 500, 600, 5000000] $ \_keystore _activeLayer aw Fix{..} -> do
326328
let pw = Kernel.walletPassive aw
327329
-- do the payment

wallet-new/test/unit/Test/Spec/NewPayment.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import Pos.Crypto (EncryptedSecretKey, ProtocolMagic,
3232
safeDeterministicKeyGen)
3333

3434
import Test.Spec.CoinSelection.Generators (InitialBalance (..),
35-
Pay (..), genPayee, genUtxoWithAtLeast)
35+
Pay (..), genPayeeWithNM, genUtxoWithAtLeast)
3636

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

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

0 commit comments

Comments
 (0)