{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
module Convex.ThreatModel.ValueUnderpayment (
valueUnderpaymentAttack,
valueUnderpaymentAttackWith,
) where
import Cardano.Api qualified as C
import Convex.ThreatModel
minOutputAda :: C.Lovelace
minOutputAda :: Lovelace
minOutputAda = Lovelace
2_000_000
valueUnderpaymentAttack :: ThreatModel ()
valueUnderpaymentAttack :: ThreatModel ()
valueUnderpaymentAttack = Double -> ThreatModel ()
valueUnderpaymentAttackWith Double
0.5
valueUnderpaymentAttackWith :: Double -> ThreatModel ()
valueUnderpaymentAttackWith :: Double -> ThreatModel ()
valueUnderpaymentAttackWith Double
reductionFactor = [Char] -> ThreatModel () -> ThreatModel ()
forall a. [Char] -> ThreatModel a -> ThreatModel a
Named ([Char]
"Value Underpayment Attack (" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Double -> [Char]
forall a. Show a => a -> [Char]
show (Double
reductionFactor Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"% reduction)") (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ do
[Output]
outputs <- ThreatModel [Output]
getTxOutputs
let scriptOutputs :: [Output]
scriptOutputs = (Output -> Bool) -> [Output] -> [Output]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Output -> Bool) -> Output -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AddressAny -> Bool
isKeyAddressAny (AddressAny -> Bool) -> (Output -> AddressAny) -> Output -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Output -> AddressAny
forall t. IsInputOrOutput t => t -> AddressAny
addressOf) [Output]
outputs
ThreatModel () -> ThreatModel ()
forall a. ThreatModel a -> ThreatModel a
threatPrecondition (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Bool -> ThreatModel ()
ensure (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Output] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Output]
scriptOutputs)
let hasEnoughAda :: p -> Bool
hasEnoughAda p
out =
let adaValue :: Lovelace
adaValue = Value -> Lovelace
C.selectLovelace (p -> Value
forall t. IsInputOrOutput t => t -> Value
valueOf p
out)
in Lovelace
adaValue Lovelace -> Lovelace -> Bool
forall a. Ord a => a -> a -> Bool
> Lovelace
minOutputAda
reducibleOutputs :: [Output]
reducibleOutputs = (Output -> Bool) -> [Output] -> [Output]
forall a. (a -> Bool) -> [a] -> [a]
filter Output -> Bool
forall {p}. IsInputOrOutput p => p -> Bool
hasEnoughAda [Output]
scriptOutputs
ThreatModel () -> ThreatModel ()
forall a. ThreatModel a -> ThreatModel a
threatPrecondition (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Bool -> ThreatModel ()
ensure (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Output] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Output]
reducibleOutputs)
Output
target <- [Output] -> ThreatModel Output
forall a. Show a => [a] -> ThreatModel a
pickAny [Output]
reducibleOutputs
let currentValue :: Value
currentValue = Output -> Value
forall t. IsInputOrOutput t => t -> Value
valueOf Output
target
currentAda :: Lovelace
currentAda = Value -> Lovelace
C.selectLovelace Value
currentValue
reducedAda :: Lovelace
reducedAda = Lovelace -> Lovelace -> Lovelace
forall a. Ord a => a -> a -> a
max Lovelace
minOutputAda (Integer -> Lovelace
forall a. Num a => Integer -> a
fromInteger (Integer -> Lovelace) -> Integer -> Lovelace
forall a b. (a -> b) -> a -> b
$ Double -> Integer
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (Lovelace -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Lovelace
currentAda Double -> Double -> Double
forall a. Num a => a -> a -> a
* (Double
1 Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
reductionFactor)))
adaDifference :: Value
adaDifference = Value -> Value
C.negateValue (Value -> Value) -> Value -> Value
forall a b. (a -> b) -> a -> b
$ Lovelace -> Value
C.lovelaceToValue (Lovelace
currentAda Lovelace -> Lovelace -> Lovelace
forall a. Num a => a -> a -> a
- Lovelace
reducedAda)
reducedValue :: Value
reducedValue = Value
currentValue Value -> Value -> Value
forall a. Semigroup a => a -> a -> a
<> Value
adaDifference
[Char] -> ThreatModel ()
counterexampleTM ([Char] -> ThreatModel ()) -> [Char] -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[[Char]] -> [Char]
paragraph
[ [Char]
"The transaction contains a script output at index"
, TxIx -> [Char]
forall a. Show a => a -> [Char]
show (Output -> TxIx
outputIx Output
target)
, [Char]
"."
]
[Char] -> ThreatModel ()
counterexampleTM ([Char] -> ThreatModel ()) -> [Char] -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[[Char]] -> [Char]
paragraph
[ [Char]
"Testing if the ADA value can be reduced from"
, Lovelace -> [Char]
forall a. Show a => a -> [Char]
show Lovelace
currentAda
, [Char]
"to"
, Lovelace -> [Char]
forall a. Show a => a -> [Char]
show Lovelace
reducedAda
, [Char]
"(reduction factor:"
, Double -> [Char]
forall a. Show a => a -> [Char]
show (Double
reductionFactor Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"%)"
, [Char]
"while keeping the datum unchanged."
]
[Char] -> ThreatModel ()
counterexampleTM ([Char] -> ThreatModel ()) -> [Char] -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[[Char]] -> [Char]
paragraph
[ [Char]
"If this validates, the script's value validation is insufficient."
, [Char]
"An attacker could exploit this to:"
, [Char]
"1) Increase their balance without depositing matching funds"
, [Char]
"2) Steal funds from pooled reserves"
, [Char]
"3) Create inconsistency between datum balance and actual UTxO value"
]
TxModifier -> ThreatModel ()
shouldNotValidate (TxModifier -> ThreatModel ()) -> TxModifier -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Output -> Value -> TxModifier
forall t. IsInputOrOutput t => t -> Value -> TxModifier
changeValueOf Output
target Value
reducedValue