@@ -112,6 +112,9 @@ module Convex.ThreatModel (
112112 monitorThreatModel ,
113113 monitorLocalThreatModel ,
114114
115+ -- * Wallet selection
116+ SigningWallet (.. ),
117+
115118 -- * Cardano API helpers
116119 -- $cardanoHelpers
117120 projectAda ,
@@ -153,7 +156,7 @@ import Convex.Class (MockChainState, MonadMockchain (..), coverageData, getUtxo,
153156import Convex.MockChain (applyTransaction , runMockchain )
154157import Convex.NodeParams (NodeParams , ledgerProtocolParameters )
155158import Convex.ThreatModel.Cardano.Api
156- import Convex.ThreatModel.Cardano.Api qualified as TM
159+ import Convex.ThreatModel.Cardano.Api qualified as TM ( detectSigningWallet , rebalanceAndSign , txRequiredSigners )
157160import Convex.ThreatModel.Pretty
158161import Convex.ThreatModel.TxModifier
159162import Convex.Wallet (Wallet )
@@ -178,6 +181,13 @@ data ThreatModelEnv = ThreatModelEnv
178181 , pparams :: LedgerProtocolParameters Era
179182 }
180183
184+ -- | How to determine the wallet for re-balancing and re-signing modified transactions.
185+ data SigningWallet
186+ = -- | Detect the signing wallet automatically from the transaction's witnesses.
187+ AutoSign
188+ | -- | Use the specified wallet for signing.
189+ SignWith Wallet
190+
181191-- | Create `ThreatModelEnv`s by reapplying the given transactions in order, starting with the given chain state.
182192threatModelEnvs :: NodeParams Era -> [Tx Era ] -> MockChainState Era -> [ThreatModelEnv ]
183193threatModelEnvs params txs chainState0 = fst $ foldM go chainState0 txs
@@ -336,16 +346,22 @@ then performs full Phase 1 + Phase 2 validation via 'applyTransaction'.
336346This catches vulnerabilities that would be masked by signature/fee failures
337347in the simpler Phase 2-only validation.
338348
349+ The wallet parameter controls signing:
350+ - @SignWith wallet@ - use the specified wallet for signing
351+ - @AutoSign@ - detect the signing wallet from the transaction's witnesses
352+
339353Usage:
340354@
341355result <- runMockchain0IOWith utxos params $ do
342356 -- ... run your actions to get a transaction ...
343- runThreatModelM Wallet.w1 unprotectedScriptOutput [env]
357+ runThreatModelM (SignWith Wallet.w1) unprotectedScriptOutput [env]
358+ -- or auto-detect:
359+ runThreatModelM AutoSign unprotectedScriptOutput [env]
344360@
345361-}
346362runThreatModelM
347363 :: (MonadMockchain Era m , MonadFail m , MonadIO m )
348- => Wallet
364+ => SigningWallet
349365 -> ThreatModel a
350366 -> [ThreatModelEnv ]
351367 -> m Property
@@ -359,10 +375,12 @@ output cluttering test results.
359375
360376The property still succeeds/fails correctly based on shouldValidate/shouldNotValidate
361377checks, but Monitor/MonitorLocal annotations (counterexampleTM, etc.) are ignored.
378+
379+ The wallet parameter controls signing (see 'runThreatModelM' for details).
362380-}
363381runThreatModelMQuiet
364382 :: (MonadMockchain Era m , MonadFail m , MonadIO m )
365- => Wallet
383+ => SigningWallet
366384 -> ThreatModel a
367385 -> [ThreatModelEnv ]
368386 -> m Property
@@ -373,14 +391,21 @@ runThreatModelM'
373391 :: (MonadMockchain Era m , MonadFail m , MonadIO m )
374392 => Bool
375393 -- ^ quiet: suppress counterexample annotations
376- -> Wallet
394+ -> SigningWallet
377395 -> ThreatModel a
378396 -> [ThreatModelEnv ]
379397 -> m Property
380- runThreatModelM' quiet wallet = go False
398+ runThreatModelM' quiet signingWallet = go False
381399 where
382400 go b _model [] = pure $ b ==> property True
383- go b model (env : envs) = interpM initialMon model
401+ go b model (env : envs) = do
402+ -- Resolve wallet: use provided or detect from transaction
403+ let resolvedWallet = case signingWallet of
404+ SignWith w -> Right w
405+ AutoSign -> TM. detectSigningWallet (currentTx env)
406+ case resolvedWallet of
407+ Left err -> pure $ counterexample err False
408+ Right wallet -> interpM initialMon wallet model
384409 where
385410 initialMon = if quiet then id else counterexample (show info)
386411
@@ -398,7 +423,7 @@ runThreatModelM' quiet wallet = go False
398423 , " "
399424 ]
400425
401- interpM mon = \ case
426+ interpM mon wallet = \ case
402427 Validate mods k -> do
403428 let (modifiedTx, modifiedUtxo) = applyTxModifier (currentTx env) (currentUTxOs env) mods
404429 -- Re-balance and re-sign the modified transaction
@@ -408,20 +433,20 @@ runThreatModelM' quiet wallet = go False
408433 (report, covData) <- validateTxM params rebalancedTx modifiedUtxo
409434 -- Accumulate coverage into the running MockChainState
410435 modifyMockChainState $ \ s -> (() , s & coverageData %~ (<> covData))
411- interpM mon (k report)
436+ interpM mon wallet (k report)
412437 Generate gen _shr k -> do
413438 -- Use QuickCheck's generate in IO
414439 a <- liftIO $ QC. generate gen
415- interpM mon (k a)
440+ interpM mon wallet (k a)
416441 GetCtx k ->
417- interpM mon (k env)
442+ interpM mon wallet (k env)
418443 Skip -> go b model envs
419- InPrecondition k -> interpM mon (k False )
444+ InPrecondition k -> interpM mon wallet (k False )
420445 Fail err -> pure $ if quiet then property False else mon $ counterexample err False
421- Monitor m k -> if quiet then interpM mon k else m <$> interpM mon k
422- MonitorLocal m k -> if quiet then interpM mon k else interpM (mon . m) k
446+ Monitor m k -> if quiet then interpM mon wallet k else m <$> interpM mon wallet k
447+ MonitorLocal m k -> if quiet then interpM mon wallet k else interpM (mon . m) wallet k
423448 Done {} -> go True model envs
424- Named _n k -> interpM mon k
449+ Named _n k -> interpM mon wallet k
425450
426451-- | Extract the name from a threat model, if it was defined with 'Named'.
427452getThreatModelName :: ThreatModel a -> Maybe String
@@ -434,19 +459,30 @@ getThreatModelName _ = Nothing
434459 Rebalancing failures (e.g., "No change output found") are treated as skipped
435460 because they indicate the transaction modification cannot be applied to this
436461 particular transaction, similar to a precondition failure.
462+
463+ The wallet parameter controls signing:
464+ - @SignWith wallet@ - use the specified wallet for signing
465+ - @AutoSign@ - detect the signing wallet from the transaction's witnesses
437466-}
438467runThreatModelCheck
439468 :: (MonadMockchain Era m , MonadFail m , MonadIO m )
440- => Wallet
469+ => SigningWallet
441470 -> ThreatModel a
442471 -> [ThreatModelEnv ]
443472 -> m ThreatModelOutcome
444- runThreatModelCheck wallet = go False
473+ runThreatModelCheck signingWallet = go False
445474 where
446475 go b _model [] = pure $ if b then TMPassed else TMSkipped
447- go b model (env : envs) = checkInterp model
476+ go b model (env : envs) = do
477+ -- Resolve wallet: use provided or detect from transaction
478+ let resolvedWallet = case signingWallet of
479+ SignWith w -> Right w
480+ AutoSign -> TM. detectSigningWallet (currentTx env)
481+ case resolvedWallet of
482+ Left err -> pure (TMError err) -- Continue to next env would lose the error, so return it
483+ Right wallet -> checkInterp wallet model
448484 where
449- checkInterp = \ case
485+ checkInterp wallet = \ case
450486 Validate mods k -> do
451487 let (modifiedTx, modifiedUtxo) = applyTxModifier (currentTx env) (currentUTxOs env) mods
452488 params <- askNodeParams
@@ -458,19 +494,19 @@ runThreatModelCheck wallet = go False
458494 Right rebalancedTx -> do
459495 (report, covData) <- validateTxM params rebalancedTx modifiedUtxo
460496 modifyMockChainState $ \ s -> (() , s & coverageData %~ (<> covData))
461- checkInterp (k report)
497+ checkInterp wallet (k report)
462498 Generate gen _shr k -> do
463499 a <- liftIO $ QC. generate gen
464- checkInterp (k a)
500+ checkInterp wallet (k a)
465501 GetCtx k ->
466- checkInterp (k env)
502+ checkInterp wallet (k env)
467503 Skip -> go b model envs
468- InPrecondition k -> checkInterp (k False )
504+ InPrecondition k -> checkInterp wallet (k False )
469505 Fail err -> pure (TMFailed err)
470- Monitor _m k -> checkInterp k -- No Property to wrap; drop monitoring
471- MonitorLocal _m k -> checkInterp k -- No Property to wrap; drop monitoring
506+ Monitor _m k -> checkInterp wallet k -- No Property to wrap; drop monitoring
507+ MonitorLocal _m k -> checkInterp wallet k -- No Property to wrap; drop monitoring
472508 Done {} -> go True model envs
473- Named _n k -> checkInterp k
509+ Named _n k -> checkInterp wallet k
474510
475511{- | Check a precondition. If the argument threat model fails, the evaluation of the current
476512 transaction is skipped. If all transactions in an evaluation of `runThreatModel` are skipped
0 commit comments