{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Convex.ThreatModel.TokenForgery (
tokenForgeryAttack,
tokenForgeryAttackWith,
) where
import Cardano.Api qualified as C
import Convex.ThreatModel
import Convex.ThreatModel.Cardano.Api (IsPlutusScriptInEra, mintedPlutusPolicies)
import Convex.ThreatModel.TxModifier (addPlutusScriptMint)
import GHC.Exts (fromList, toList)
tokenForgeryAttack :: ThreatModel ()
tokenForgeryAttack :: ThreatModel ()
tokenForgeryAttack = String -> ThreatModel () -> ThreatModel ()
forall a. String -> ThreatModel a -> ThreatModel a
Named String
"Token Forgery Attack" (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ do
Tx Era
tx <- ThreatModel (Tx Era)
originalTx
UTxO Era
utxos <- ThreatModelEnv -> UTxO Era
currentUTxOs (ThreatModelEnv -> UTxO Era)
-> ThreatModel ThreatModelEnv -> ThreatModel (UTxO Era)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ThreatModel ThreatModelEnv
getThreatModelEnv
let candidates :: [(AssetName, ScriptInAnyLang, ScriptData)]
candidates =
[ (AssetName
assetName, ScriptInAnyLang
scriptInAnyLang, ScriptData
redeemer)
| (PolicyId
_policyId, PolicyAssets
assets, ScriptInAnyLang
scriptInAnyLang, ScriptData
redeemer) <- Tx Era
-> UTxO Era
-> [(PolicyId, PolicyAssets, ScriptInAnyLang, ScriptData)]
mintedPlutusPolicies Tx Era
tx UTxO Era
utxos
, (AssetName
assetName, Quantity
quantity) <- PolicyAssets -> [Item PolicyAssets]
forall l. IsList l => l -> [Item l]
toList PolicyAssets
assets
, Quantity
quantity Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
> Quantity
0
]
case [(AssetName, ScriptInAnyLang, ScriptData)]
candidates of
[] -> String -> ThreatModel ()
forall a. String -> ThreatModel a
failPrecondition String
"Transaction does not mint any Plutus policy assets"
[(AssetName, ScriptInAnyLang, ScriptData)]
_ -> do
(AssetName
assetName, ScriptInAnyLang
scriptInAnyLang, ScriptData
redeemer) <- [(AssetName, ScriptInAnyLang, ScriptData)]
-> ThreatModel (AssetName, ScriptInAnyLang, ScriptData)
forall a. Show a => [a] -> ThreatModel a
pickAny [(AssetName, ScriptInAnyLang, ScriptData)]
candidates
case ScriptInAnyLang
scriptInAnyLang of
C.ScriptInAnyLang (C.PlutusScriptLanguage PlutusScriptVersion lang
C.PlutusScriptV1) (C.PlutusScript PlutusScriptVersion lang
_ PlutusScript lang
script) ->
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
forall lang.
IsPlutusScriptInEra lang =>
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
mintExtraUnit ScriptData
redeemer PlutusScript lang
script AssetName
assetName
C.ScriptInAnyLang (C.PlutusScriptLanguage PlutusScriptVersion lang
C.PlutusScriptV2) (C.PlutusScript PlutusScriptVersion lang
_ PlutusScript lang
script) ->
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
forall lang.
IsPlutusScriptInEra lang =>
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
mintExtraUnit ScriptData
redeemer PlutusScript lang
script AssetName
assetName
C.ScriptInAnyLang (C.PlutusScriptLanguage PlutusScriptVersion lang
C.PlutusScriptV3) (C.PlutusScript PlutusScriptVersion lang
_ PlutusScript lang
script) ->
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
forall lang.
IsPlutusScriptInEra lang =>
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
mintExtraUnit ScriptData
redeemer PlutusScript lang
script AssetName
assetName
ScriptInAnyLang
_ -> String -> ThreatModel ()
forall a. String -> ThreatModel a
failPrecondition String
"Minting policy is not a Plutus script (V1, V2, or V3)"
tokenForgeryAttackWith
:: (IsPlutusScriptInEra lang)
=> C.ScriptData
-> C.PlutusScript lang
-> C.AssetName
-> ThreatModel ()
tokenForgeryAttackWith :: forall lang.
IsPlutusScriptInEra lang =>
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
tokenForgeryAttackWith ScriptData
redeemer PlutusScript lang
mintScript AssetName
assetName =
String -> ThreatModel () -> ThreatModel ()
forall a. String -> ThreatModel a -> ThreatModel a
Named String
"Token Forgery Attack" (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
forall lang.
IsPlutusScriptInEra lang =>
ScriptData -> PlutusScript lang -> AssetName -> ThreatModel ()
mintExtraUnit ScriptData
redeemer PlutusScript lang
mintScript AssetName
assetName
mintExtraUnit :: (IsPlutusScriptInEra lang) => C.ScriptData -> C.PlutusScript lang -> C.AssetName -> ThreatModel ()
ScriptData
redeemer PlutusScript lang
mintScript AssetName
assetName = do
Output
output <- (Output -> Bool) -> ThreatModel Output
anyOutputSuchThat (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)
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"Testing Token Forgery vulnerability:"
, String
"Attempting to mint additional tokens using the provided minting policy."
, String
"Adding minted tokens to output at " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Doc -> String
forall a. Show a => a -> String
show (AddressAny -> Doc
prettyAddress (AddressAny -> Doc) -> AddressAny -> Doc
forall a b. (a -> b) -> a -> b
$ Output -> AddressAny
forall t. IsInputOrOutput t => t -> AddressAny
addressOf Output
output) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"."
]
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"If this validates, the minting policy is too permissive."
, String
"An attacker could forge tokens to:"
, String
"1) Bypass validation token requirements"
, String
"2) Steal assets protected by token checks"
, String
"3) Manipulate protocol state"
]
let scriptHash :: ScriptHash
scriptHash = Script lang -> ScriptHash
forall lang. Script lang -> ScriptHash
C.hashScript (Script lang -> ScriptHash) -> Script lang -> ScriptHash
forall a b. (a -> b) -> a -> b
$ PlutusScriptVersion lang -> PlutusScript lang -> Script lang
forall lang.
IsPlutusScriptLanguage lang =>
PlutusScriptVersion lang -> PlutusScript lang -> Script lang
C.PlutusScript PlutusScriptVersion lang
forall lang.
IsPlutusScriptLanguage lang =>
PlutusScriptVersion lang
plutusScriptVersion PlutusScript lang
mintScript
policyId :: PolicyId
policyId = ScriptHash -> PolicyId
C.PolicyId ScriptHash
scriptHash
mintedValue :: Value
mintedValue = [Item Value] -> Value
forall l. IsList l => [Item l] -> l
fromList [(PolicyId -> AssetName -> AssetId
C.AssetId PolicyId
policyId AssetName
assetName, Quantity
1)]
valueWithForgedToken :: Value
valueWithForgedToken = Output -> Value
forall t. IsInputOrOutput t => t -> Value
valueOf Output
output Value -> Value -> Value
forall a. Semigroup a => a -> a -> a
<> Value
mintedValue
LedgerProtocolParameters Era
envPParams <- ThreatModelEnv -> LedgerProtocolParameters Era
pparams (ThreatModelEnv -> LedgerProtocolParameters Era)
-> ThreatModel ThreatModelEnv
-> ThreatModel (LedgerProtocolParameters Era)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ThreatModel ThreatModelEnv
getThreatModelEnv
let C.TxOut AddressInEra Era
outAddr TxOutValue Era
_ TxOutDatum CtxTx Era
outDatum ReferenceScript Era
outRefScript = Output -> TxOut CtxTx Era
outputTxOut Output
output
candidateTxOut :: TxOut CtxTx Era
candidateTxOut =
AddressInEra Era
-> TxOutValue Era
-> TxOutDatum CtxTx Era
-> ReferenceScript Era
-> TxOut CtxTx Era
forall ctx era.
AddressInEra era
-> TxOutValue era
-> TxOutDatum ctx era
-> ReferenceScript era
-> TxOut ctx era
C.TxOut
AddressInEra Era
outAddr
(ShelleyBasedEra Era
-> Value (ShelleyLedgerEra Era) -> TxOutValue Era
forall era.
(Eq (Value (ShelleyLedgerEra era)),
Show (Value (ShelleyLedgerEra era))) =>
ShelleyBasedEra era
-> Value (ShelleyLedgerEra era) -> TxOutValue era
C.TxOutValueShelleyBased ShelleyBasedEra Era
forall era. IsShelleyBasedEra era => ShelleyBasedEra era
C.shelleyBasedEra (Value -> MaryValue
C.toMaryValue Value
valueWithForgedToken))
TxOutDatum CtxTx Era
outDatum
ReferenceScript Era
outRefScript
minCoin :: Coin
minCoin = ShelleyBasedEra Era
-> PParams (ShelleyLedgerEra Era) -> TxOut CtxTx Era -> Coin
forall era.
HasCallStack =>
ShelleyBasedEra era
-> PParams (ShelleyLedgerEra era) -> TxOut CtxTx era -> Coin
C.calculateMinimumUTxO ShelleyBasedEra Era
forall era. IsShelleyBasedEra era => ShelleyBasedEra era
C.shelleyBasedEra (LedgerProtocolParameters Era -> PParams (ShelleyLedgerEra Era)
forall era.
LedgerProtocolParameters era -> PParams (ShelleyLedgerEra era)
C.unLedgerProtocolParameters LedgerProtocolParameters Era
envPParams) TxOut CtxTx Era
candidateTxOut
shortfall :: Coin
shortfall = Coin
minCoin Coin -> Coin -> Coin
forall a. Num a => a -> a -> a
- Value -> Coin
C.selectLovelace Value
valueWithForgedToken
newValue :: Value
newValue
| Coin
shortfall Coin -> Coin -> Bool
forall a. Ord a => a -> a -> Bool
> Coin
0 = Value
valueWithForgedToken Value -> Value -> Value
forall a. Semigroup a => a -> a -> a
<> Coin -> Value
C.lovelaceToValue Coin
shortfall
| Bool
otherwise = Value
valueWithForgedToken
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
output Value
newValue
TxModifier -> TxModifier -> TxModifier
forall a. Semigroup a => a -> a -> a
<> PlutusScript lang
-> AssetName -> Quantity -> ScriptData -> TxModifier
forall lang.
IsPlutusScriptInEra lang =>
PlutusScript lang
-> AssetName -> Quantity -> ScriptData -> TxModifier
addPlutusScriptMint PlutusScript lang
mintScript AssetName
assetName (Integer -> Quantity
C.Quantity Integer
1) ScriptData
redeemer