1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
{-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses #-}
{- Copyright 2016 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
module Cost (
module Cost,
module Types.Cost
) where
import Types.Cost
import Data.List
import Data.Maybe
import Text.Read
-- | Cost in seconds, with the type of hardware needed.
totalCost :: Cost op -> (Seconds, [UsingHardware])
totalCost (CPUCost s _) = (s, [UsingCPU])
raiseCostPower :: Cost c -> Entropy e -> Cost c
raiseCostPower c (Entropy e) = mapCost (* 2^e) c
mapCost :: (Integer -> Integer) -> Cost op -> Cost op
mapCost f (CPUCost (Seconds n) d) = CPUCost (Seconds (f n)) d
type NumCores = Integer
showCostMinutes :: NumCores -> Cost op -> String
showCostMinutes numcores (CPUCost (Seconds n) (Divisibility d))
| n' < 61 = "1 minute"
| otherwise = show (n' `div` 60) ++ " minutes"
where
n' = n `div` min numcores d
-- If an operation took n seconds on a number of cores,
-- multiply to get the CPUCost, which is for a single core.
coreCost :: NumCores -> Seconds -> Divisibility -> Cost op
coreCost cores (Seconds n) d = CPUCost (Seconds (cores * n)) d
castCost :: Cost a -> Cost b
castCost (CPUCost s d) = CPUCost s d
-- | CostCalc for a brute force linear search through an entropy space
-- in which each step entails paying a cost.
--
-- On average, the solution will be found half way through.
-- This is equivilant to one bit less of entropy.
bruteForceLinearSearch :: Cost step -> CostCalc BruteForceOp t
bruteForceLinearSearch stepcost e =
castCost stepcost `raiseCostPower` reduceEntropy e 1
-- | Estimate of cost of a brute force attack.
estimateBruteforceOf :: Bruteforceable t a => t -> Entropy a -> Cost BruteForceOp
estimateBruteforceOf t e = getBruteCostCalc t e
data DataCenterPrice = DataCenterPrice
{ instanceCpuCores :: Integer
, instanceCpuCoreMultiplier :: Integer
-- ^ If the cores are twice as fast as the commodity hardware
-- that keysafe's cost estimates are based on, use 2 to indicate
-- this, etc.
, instanceCostPerHour :: Cents
}
-- August 2016 spot pricing: 36 CPU core c4.8xlarge at 33c/hour
spotAWS :: DataCenterPrice
spotAWS = DataCenterPrice
{ instanceCpuCores = 36
, instanceCpuCoreMultiplier = 2
, instanceCostPerHour = Cents 33
}
-- | Estimate of cost of brute force attack using a datacenter.
--
-- Note that this assumes that CPU cores and GPU cores are of equal number,
-- which is unlikely to be the case; typically there will be many more
-- cores than GPUs. So, this underestimates the price to brute force
-- operations which run faster on GPUs.
estimateAttackCost :: DataCenterPrice -> Cost BruteForceOp -> Dollars
estimateAttackCost dc opcost = centsToDollars $ costcents
where
(Seconds cpuseconds) = fst (totalCost opcost)
cpuyears = cpuseconds `div` (60*60*24*365)
costpercpuyear = Cents $
fromIntegral (instanceCostPerHour dc) * 24 * 365
`div` (instanceCpuCores dc * instanceCpuCoreMultiplier dc)
costcents = Cents cpuyears * costpercpuyear
newtype Cents = Cents Integer
deriving (Num, Integral, Enum, Real, Ord, Eq, Show)
newtype Dollars = Dollars Integer
deriving (Num, Integral, Enum, Real, Ord, Eq)
instance Show Dollars where
show (Dollars n) = go
[ (1000000000000, "trillion")
, (1000000000, "billion")
, (1000000, "million")
, (1000, "thousand")
]
where
go [] = "$" ++ show n
go ((d, u):us)
| n >= d =
let n' = n `div` d
in "$" ++ show n' ++ " " ++ u
| otherwise = go us
centsToDollars :: Cents -> Dollars
centsToDollars (Cents c) = Dollars (c `div` 100)
type Year = Integer
-- | Apply Moore's law to show how a cost might vary over time.
costOverTime :: Dollars -> Year -> [(Dollars, Year)]
costOverTime (Dollars currcost) thisyear =
(Dollars currcost, thisyear) : map calc otheryears
where
otheryears = [thisyear+1, thisyear+5, thisyear+10]
calc y =
let monthdelta = (fromIntegral ((y * 12) - (thisyear * 12))) :: Double
cost = floor $ fromIntegral currcost / 2 ** (monthdelta / 18)
in (Dollars cost, y)
costOverTimeTable :: Dollars -> Year -> [String]
costOverTimeTable cost thisyear = go [] thisyear $ costOverTime cost thisyear
where
go t _ [] = reverse t
go t yprev ((c, y):ys) =
let s = " in " ++ show y ++ ": " ++ show c
in if yprev < y - 1
then go (s:" ...":t) y ys
else go (s:t) y ys
-- Number of physical cores. This is not the same as
-- getNumProcessors, which includes hyper-threading.
getNumCores :: IO (Maybe NumCores)
getNumCores = getmax . mapMaybe parse . lines <$> readFile "/proc/cpuinfo"
where
getmax [] = Nothing
getmax l = Just $
maximum l + 1 -- add 1 because /proc/cpuinfo counts from 0
parse l
| "core id" `isPrefixOf` l =
readMaybe $ drop 1 $ dropWhile (/= ':') l
| otherwise = Nothing
|