{-# LANGUAGE OverloadedStrings #-}
module Convex.ThreatModel.LargeData (
largeDataAttack,
largeDataAttackWith,
largeDataAttackWithGen,
bloatData,
) where
import Convex.ThreatModel
import Data.ByteString qualified as BS
import Data.List (minimumBy)
import Data.Maybe (mapMaybe)
import Data.Ord (comparing)
import Test.QuickCheck (Gen, choose)
largeDataAttack :: ThreatModel ()
largeDataAttack :: ThreatModel ()
largeDataAttack = Gen Int -> ThreatModel ()
largeDataAttackWithGen ((Int, Int) -> Gen Int
forall a. Random a => (a, a) -> Gen a
choose (Int
1, Int
1000))
largeDataAttackWith :: Int -> ThreatModel ()
largeDataAttackWith :: Int -> ThreatModel ()
largeDataAttackWith = Gen Int -> ThreatModel ()
largeDataAttackWithGen (Gen Int -> ThreatModel ())
-> (Int -> Gen Int) -> Int -> ThreatModel ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Gen Int
forall a. a -> Gen a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
largeDataAttackWithGen :: Gen Int -> ThreatModel ()
largeDataAttackWithGen :: Gen Int -> ThreatModel ()
largeDataAttackWithGen Gen Int
fieldsGen =
[Char] -> ThreatModel () -> ThreatModel ()
forall a. [Char] -> ThreatModel a -> ThreatModel a
Named [Char]
"Large Data Attack" (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ do
Int
n <- Gen Int -> (Int -> [Int]) -> ThreatModel Int
forall a. Show a => Gen a -> (a -> [a]) -> ThreatModel a
forAllTM Gen Int
fieldsGen Int -> [Int]
shrinkPositive
Bool -> ThreatModel ()
ensure (Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
1)
(Output
target, ScriptData
originalDatum) <- ThreatModel (Output, ScriptData)
anyGuardedOutputWithInlineDatum
let bloatedDatum :: ScriptData
bloatedDatum = Int -> ScriptData -> ScriptData
bloatData Int
n ScriptData
originalDatum
if ScriptData
bloatedDatum ScriptData -> ScriptData -> Bool
forall a. Eq a => a -> a -> Bool
== ScriptData
originalDatum
then
[Char] -> ThreatModel ()
forall a. [Char] -> ThreatModel a
failPrecondition ([Char] -> ThreatModel ()) -> [Char] -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[[Char]] -> [Char]
unwords
[ [Char]
"Large data attack cannot bloat a"
, ScriptData -> [Char]
datumShape ScriptData
originalDatum
, [Char]
"datum: the modification would be a no-op"
]
else () -> ThreatModel ()
forall a. a -> ThreatModel a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
[Char] -> ThreatModel ()
counterexampleTM ([Char] -> ThreatModel ()) -> [Char] -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[[Char]] -> [Char]
paragraph
[ [Char]
"Injecting " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
n
, [Char]
"extra members into the inline datum of output"
, TxIx -> [Char]
forall a. Show a => a -> [Char]
show (Output -> TxIx
outputIx Output
target)
, [Char]
"and asserting the transaction no longer validates."
]
[Char] -> [[Char]] -> ThreatModel ()
tabulateTM [Char]
"fields injected" [Int -> [Char]
bucket Int
n]
TxModifier -> ThreatModel ()
shouldNotValidate (TxModifier -> ThreatModel ()) -> TxModifier -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Output -> Datum -> TxModifier
forall t. IsInputOrOutput t => t -> Datum -> TxModifier
changeDatumOf Output
target (ScriptData -> Datum
toInlineDatum ScriptData
bloatedDatum)
bucket :: Int -> String
bucket :: Int -> [Char]
bucket Int
n
| Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
10 = [Char]
"001-010"
| Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
100 = [Char]
"011-100"
| Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
500 = [Char]
"101-500"
| Bool
otherwise = [Char]
"501-1000"
bloatData :: Int -> ScriptData -> ScriptData
bloatData :: Int -> ScriptData -> ScriptData
bloatData Int
n ScriptData
sd = case ScriptData
sd of
ScriptDataConstructor Integer
idx [ScriptData]
fields ->
Integer -> [ScriptData] -> ScriptData
ScriptDataConstructor Integer
idx ([ScriptData]
fields [ScriptData] -> [ScriptData] -> [ScriptData]
forall a. [a] -> [a] -> [a]
++ Int -> ScriptData -> [ScriptData]
forall a. Int -> a -> [a]
replicate Int
n (Integer -> ScriptData
ScriptDataNumber Integer
42))
ScriptDataList [ScriptData]
xs ->
[ScriptData] -> ScriptData
ScriptDataList ([ScriptData]
xs [ScriptData] -> [ScriptData] -> [ScriptData]
forall a. [a] -> [a] -> [a]
++ Int -> ScriptData -> [ScriptData]
forall a. Int -> a -> [a]
replicate Int
n ([ScriptData] -> ScriptData
junkMemberLike [ScriptData]
xs))
ScriptDataMap [(ScriptData, ScriptData)]
kvs ->
[(ScriptData, ScriptData)] -> ScriptData
ScriptDataMap ([(ScriptData, ScriptData)]
kvs [(ScriptData, ScriptData)]
-> [(ScriptData, ScriptData)] -> [(ScriptData, ScriptData)]
forall a. [a] -> [a] -> [a]
++ Int -> [(ScriptData, ScriptData)] -> [(ScriptData, ScriptData)]
forall a. Int -> [a] -> [a]
take Int
n ([(ScriptData, ScriptData)] -> [(ScriptData, ScriptData)]
junkEntriesFor [(ScriptData, ScriptData)]
kvs))
ScriptData
_ -> ScriptData
sd
junkMemberLike :: [ScriptData] -> ScriptData
junkMemberLike :: [ScriptData] -> ScriptData
junkMemberLike [] = Integer -> ScriptData
ScriptDataNumber Integer
42
junkMemberLike [ScriptData]
xs
| Int
smallestSize Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
junkSizeBudget = ScriptData
smallest
| Bool
otherwise = ScriptData -> ScriptData
shrink ScriptData
smallest
where
(Int
smallestSize, ScriptData
smallest) = ((Int, ScriptData) -> (Int, ScriptData) -> Ordering)
-> [(Int, ScriptData)] -> (Int, ScriptData)
forall (t :: * -> *) a.
Foldable t =>
(a -> a -> Ordering) -> t a -> a
minimumBy (((Int, ScriptData) -> Int)
-> (Int, ScriptData) -> (Int, ScriptData) -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing (Int, ScriptData) -> Int
forall a b. (a, b) -> a
fst) [(ScriptData -> Int
dataSize ScriptData
x, ScriptData
x) | ScriptData
x <- [ScriptData]
xs]
shrink :: ScriptData -> ScriptData
shrink (ScriptDataBytes ByteString
bs) = ByteString -> ScriptData
ScriptDataBytes (Int -> ByteString -> ByteString
BS.take Int
junkSizeBudget ByteString
bs)
shrink (ScriptDataNumber Integer
_) = Integer -> ScriptData
ScriptDataNumber Integer
42
shrink ScriptData
other = ScriptData
other
junkSizeBudget :: Int
junkSizeBudget :: Int
junkSizeBudget = Int
8
dataSize :: ScriptData -> Int
dataSize :: ScriptData -> Int
dataSize ScriptData
sd = case ScriptData
sd of
ScriptDataNumber Integer
_ -> Int
1
ScriptDataBytes ByteString
bs -> Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
BS.length ByteString
bs
ScriptDataList [ScriptData]
xs -> Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ((ScriptData -> Int) -> [ScriptData] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ScriptData -> Int
dataSize [ScriptData]
xs)
ScriptDataConstructor Integer
_ [ScriptData]
fields -> Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ((ScriptData -> Int) -> [ScriptData] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ScriptData -> Int
dataSize [ScriptData]
fields)
ScriptDataMap [(ScriptData, ScriptData)]
kvs -> Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [ScriptData -> Int
dataSize ScriptData
k Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ScriptData -> Int
dataSize ScriptData
v | (ScriptData
k, ScriptData
v) <- [(ScriptData, ScriptData)]
kvs]
junkEntriesFor :: [(ScriptData, ScriptData)] -> [(ScriptData, ScriptData)]
junkEntriesFor :: [(ScriptData, ScriptData)] -> [(ScriptData, ScriptData)]
junkEntriesFor [(ScriptData, ScriptData)]
kvs = (ScriptData -> (ScriptData, ScriptData))
-> [ScriptData] -> [(ScriptData, ScriptData)]
forall a b. (a -> b) -> [a] -> [b]
map (\ScriptData
k -> (ScriptData
k, ScriptData
junkValue)) [ScriptData]
freshKeys
where
keys :: [ScriptData]
keys = ((ScriptData, ScriptData) -> ScriptData)
-> [(ScriptData, ScriptData)] -> [ScriptData]
forall a b. (a -> b) -> [a] -> [b]
map (ScriptData, ScriptData) -> ScriptData
forall a b. (a, b) -> a
fst [(ScriptData, ScriptData)]
kvs
junkValue :: ScriptData
junkValue = [ScriptData] -> ScriptData
junkMemberLike (((ScriptData, ScriptData) -> ScriptData)
-> [(ScriptData, ScriptData)] -> [ScriptData]
forall a b. (a -> b) -> [a] -> [b]
map (ScriptData, ScriptData) -> ScriptData
forall a b. (a, b) -> b
snd [(ScriptData, ScriptData)]
kvs)
freshKeys :: [ScriptData]
freshKeys = case [ScriptData]
keys of
[] -> (Integer -> ScriptData) -> [Integer] -> [ScriptData]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> ScriptData
ScriptDataNumber [Integer
0 ..]
ScriptData
template : [ScriptData]
_ ->
case Int -> ScriptData -> Maybe ScriptData
perturb Int
0 ScriptData
template of
Maybe ScriptData
Nothing -> []
Just ScriptData
_ -> (Int -> Maybe ScriptData) -> [Int] -> [ScriptData]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Int
i -> Int -> ScriptData -> Maybe ScriptData
perturb Int
i ScriptData
template) [Int
0 ..]
bytesPad :: ByteString
bytesPad = Int -> Word8 -> ByteString
BS.replicate (Int
maxBytesLen Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Word8
0
perturb :: Int -> ScriptData -> Maybe ScriptData
perturb Int
i = (ScriptData -> ScriptData) -> ScriptData -> Maybe ScriptData
replaceFirstLeaf ((ScriptData -> ScriptData) -> ScriptData -> Maybe ScriptData)
-> (ScriptData -> ScriptData) -> ScriptData -> Maybe ScriptData
forall a b. (a -> b) -> a -> b
$ \ScriptData
leaf -> case ScriptData
leaf of
ScriptDataBytes ByteString
_ -> ByteString -> ScriptData
ScriptDataBytes (ByteString
bytesPad ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString
word32BE Int
i)
ScriptData
_ -> Integer -> ScriptData
ScriptDataNumber (Integer
maxNumber Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
1 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
i)
keyLeaves :: [ScriptData]
keyLeaves = (ScriptData -> [ScriptData]) -> [ScriptData] -> [ScriptData]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ScriptData -> [ScriptData]
leaves [ScriptData]
keys
maxNumber :: Integer
maxNumber = [Integer] -> Integer
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum (Integer
0 Integer -> [Integer] -> [Integer]
forall a. a -> [a] -> [a]
: [Integer
i | ScriptDataNumber Integer
i <- [ScriptData]
keyLeaves])
maxBytesLen :: Int
maxBytesLen = [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum (Int
0 Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
: [ByteString -> Int
BS.length ByteString
bs | ScriptDataBytes ByteString
bs <- [ScriptData]
keyLeaves])
replaceFirstLeaf :: (ScriptData -> ScriptData) -> ScriptData -> Maybe ScriptData
replaceFirstLeaf :: (ScriptData -> ScriptData) -> ScriptData -> Maybe ScriptData
replaceFirstLeaf ScriptData -> ScriptData
f ScriptData
sd = case ScriptData
sd of
ScriptDataNumber{} -> ScriptData -> Maybe ScriptData
forall a. a -> Maybe a
Just (ScriptData -> ScriptData
f ScriptData
sd)
ScriptDataBytes{} -> ScriptData -> Maybe ScriptData
forall a. a -> Maybe a
Just (ScriptData -> ScriptData
f ScriptData
sd)
ScriptDataList [ScriptData]
xs -> [ScriptData] -> ScriptData
ScriptDataList ([ScriptData] -> ScriptData)
-> Maybe [ScriptData] -> Maybe ScriptData
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [ScriptData] -> Maybe [ScriptData]
inFirst [ScriptData]
xs
ScriptDataConstructor Integer
idx [ScriptData]
fields -> Integer -> [ScriptData] -> ScriptData
ScriptDataConstructor Integer
idx ([ScriptData] -> ScriptData)
-> Maybe [ScriptData] -> Maybe ScriptData
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [ScriptData] -> Maybe [ScriptData]
inFirst [ScriptData]
fields
ScriptDataMap [(ScriptData, ScriptData)]
kvs ->
[(ScriptData, ScriptData)] -> ScriptData
ScriptDataMap ([(ScriptData, ScriptData)] -> ScriptData)
-> ([ScriptData] -> [(ScriptData, ScriptData)])
-> [ScriptData]
-> ScriptData
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ScriptData] -> [(ScriptData, ScriptData)]
forall {b}. [b] -> [(b, b)]
pairs ([ScriptData] -> ScriptData)
-> Maybe [ScriptData] -> Maybe ScriptData
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [ScriptData] -> Maybe [ScriptData]
inFirst ([(ScriptData, ScriptData)] -> [ScriptData]
forall {a}. [(a, a)] -> [a]
unpairs [(ScriptData, ScriptData)]
kvs)
where
inFirst :: [ScriptData] -> Maybe [ScriptData]
inFirst [] = Maybe [ScriptData]
forall a. Maybe a
Nothing
inFirst (ScriptData
x : [ScriptData]
xs) = case (ScriptData -> ScriptData) -> ScriptData -> Maybe ScriptData
replaceFirstLeaf ScriptData -> ScriptData
f ScriptData
x of
Just ScriptData
x' -> [ScriptData] -> Maybe [ScriptData]
forall a. a -> Maybe a
Just (ScriptData
x' ScriptData -> [ScriptData] -> [ScriptData]
forall a. a -> [a] -> [a]
: [ScriptData]
xs)
Maybe ScriptData
Nothing -> (ScriptData
x ScriptData -> [ScriptData] -> [ScriptData]
forall a. a -> [a] -> [a]
:) ([ScriptData] -> [ScriptData])
-> Maybe [ScriptData] -> Maybe [ScriptData]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [ScriptData] -> Maybe [ScriptData]
inFirst [ScriptData]
xs
unpairs :: [(a, a)] -> [a]
unpairs [(a, a)]
ps = [[a]] -> [a]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[a
k, a
v] | (a
k, a
v) <- [(a, a)]
ps]
pairs :: [b] -> [(b, b)]
pairs (b
k : b
v : [b]
rest) = (b
k, b
v) (b, b) -> [(b, b)] -> [(b, b)]
forall a. a -> [a] -> [a]
: [b] -> [(b, b)]
pairs [b]
rest
pairs [b]
_ = []
leaves :: ScriptData -> [ScriptData]
leaves :: ScriptData -> [ScriptData]
leaves ScriptData
sd = case ScriptData
sd of
ScriptDataNumber{} -> [ScriptData
sd]
ScriptDataBytes{} -> [ScriptData
sd]
ScriptDataList [ScriptData]
xs -> (ScriptData -> [ScriptData]) -> [ScriptData] -> [ScriptData]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ScriptData -> [ScriptData]
leaves [ScriptData]
xs
ScriptDataConstructor Integer
_ [ScriptData]
fields -> (ScriptData -> [ScriptData]) -> [ScriptData] -> [ScriptData]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ScriptData -> [ScriptData]
leaves [ScriptData]
fields
ScriptDataMap [(ScriptData, ScriptData)]
kvs -> [[ScriptData]] -> [ScriptData]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ScriptData -> [ScriptData]
leaves ScriptData
k [ScriptData] -> [ScriptData] -> [ScriptData]
forall a. Semigroup a => a -> a -> a
<> ScriptData -> [ScriptData]
leaves ScriptData
v | (ScriptData
k, ScriptData
v) <- [(ScriptData, ScriptData)]
kvs]
word32BE :: Int -> BS.ByteString
word32BE :: Int -> ByteString
word32BE Int
i = [Word8] -> ByteString
BS.pack [Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int
i Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
d Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
256) | Int
d <- [Int
16777216, Int
65536, Int
256, Int
1]]
datumShape :: ScriptData -> String
datumShape :: ScriptData -> [Char]
datumShape ScriptData
sd = case ScriptData
sd of
ScriptDataConstructor{} -> [Char]
"Constr"
ScriptDataList{} -> [Char]
"List"
ScriptDataMap{} -> [Char]
"Map"
ScriptDataNumber{} -> [Char]
"Number"
ScriptDataBytes{} -> [Char]
"Bytes"