Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce (on-chain) that Hydra transitions are authenticated by participants #292

Merged
merged 18 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a117def
Inject signer mutation for CollectCom #284
abailly-iohk Mar 24, 2022
cc1f759
Sketch validation of CollectCom signers #284
abailly-iohk Mar 24, 2022
239f0bc
Extract tokens from outputs and implement signatory verification.
KtorZ Mar 24, 2022
0b1e47c
Try to generate key for signing CollectCom tx #284
abailly-iohk Mar 24, 2022
a5cc71d
Extract 'signWith' from TinyWallet and use it to authenticate Collect…
KtorZ Mar 24, 2022
99e1572
Add signer's vk to the explicit required signers list when signing.
KtorZ Mar 24, 2022
d86f5c5
Add 'mutate required signer' to Abort. Mutation tests fail.
KtorZ Mar 24, 2022
bcc3f46
Refactor head contract to re-use logic for checking signatories on-ch…
KtorZ Mar 24, 2022
b797019
Add signers to the health abort transaction
KtorZ Mar 24, 2022
598727a
Add mutation mutation to close tx about required signers.
KtorZ Mar 24, 2022
8954ed5
Implement signature verification on-chain for close transition.
KtorZ Mar 24, 2022
e6e2cef
Repair healthy close tx, and correctly gather signatures on-chain.
KtorZ Mar 25, 2022
20c3f35
Fix finding Head currency symbol.
KtorZ Mar 25, 2022
4663032
Provide required signers when constructing Hydra on-chain transactions.
KtorZ Mar 25, 2022
45da91e
Actually sign transaction AFTER modifying its body content
KtorZ Mar 25, 2022
73c22e5
Revert adding required signers during 'signWith'
KtorZ Mar 25, 2022
663249a
Leave note about failing (albeit wrong) integration test.
KtorZ Mar 25, 2022
afea828
Simplify Cardano cluster integration test #284
abailly-iohk Mar 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hydra-cluster/hydra-cluster.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ test-suite integration
other-modules:
Paths_hydra_cluster
Spec
Test.CardanoClusterSpec
Test.DirectChainSpec
Test.EndToEndSpec
Test.GeneratorSpec
Test.Ledger.Cardano.ConfigurationSpec
Test.LocalClusterSpec
Test.LogFilterSpec

build-depends:
Expand Down
62 changes: 62 additions & 0 deletions hydra-cluster/test/Test/CardanoClusterSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module Test.CardanoClusterSpec where

import Hydra.Prelude
import Test.Hydra.Prelude

import CardanoCluster (
Actor (Alice),
ClusterConfig (..),
ClusterLog (..),
Marked (Normal),
RunningCluster (..),
defaultNetworkId,
keysFor,
seedFromFaucet_,
withCluster,
)
import CardanoNode (ChainTip (..), RunningNode (..), cliQueryTip)
import Hydra.Logging (Tracer, showLogsOnFailure)

spec :: Spec
spec =
it "should produce blocks, provide funds, and send Hydra OCV transactions" $ do
showLogsOnFailure $ \tr ->
withTempDir "hydra-cluster" $ \tmp -> do
let config =
ClusterConfig
{ parentStateDirectory = tmp
, networkId = defaultNetworkId
}
withCluster tr config $ \cluster -> do
failAfter 30 $ assertNetworkIsProducingBlock tr cluster
failAfter 30 $ assertCanSpendInitialFunds cluster

assertNetworkIsProducingBlock :: Tracer IO ClusterLog -> RunningCluster -> IO ()
assertNetworkIsProducingBlock tracer = go (-1)
where
go blk cluster = case cluster of
RunningCluster _ (RunningNode nodeId socket : _) -> do
waitForNewBlock
tip <- cliQueryTip (contramap (MsgFromNode nodeId) tracer) socket
if block tip > blk
then pure ()
else go (block tip) cluster
_ ->
error "empty cluster?"

assertCanSpendInitialFunds :: HasCallStack => RunningCluster -> IO ()
assertCanSpendInitialFunds = \case
(RunningCluster ClusterConfig{networkId} (node : _)) -> do
(vk, _) <- keysFor Alice
seedFromFaucet_ networkId node vk 100_000_000 Normal
_ ->
error "empty cluster?"

waitForNewBlock :: IO ()
waitForNewBlock = threadDelay (2 * slotLength)

slotLength :: DiffTime
slotLength = 1 -- FIXME this should be found in the genesis file

sshow :: (IsString s, Show a) => a -> s
sshow = fromString . show
137 changes: 0 additions & 137 deletions hydra-cluster/test/Test/LocalClusterSpec.hs

This file was deleted.

12 changes: 6 additions & 6 deletions hydra-node/src/Hydra/Chain/Direct/State.hs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ abort ::
HasCallStack =>
OnChainHeadState 'StInitialized ->
Tx
abort OnChainHeadState{networkId, stateMachine} = do
abort OnChainHeadState{ownVerificationKey, stateMachine} = do
let (i, o, dat, _) = initialThreadOutput
initials = Map.fromList $ map tripleToPair initialInitials
commits = Map.fromList $ map tripleToPair initialCommits
in case abortTx networkId (i, o, dat) (initialHeadTokenScript stateMachine) initials commits of
in case abortTx ownVerificationKey (i, o, dat) (initialHeadTokenScript stateMachine) initials commits of
Left err ->
-- FIXME: Exception with MonadThrow?
error $ show err
Expand All @@ -250,9 +250,9 @@ abort OnChainHeadState{networkId, stateMachine} = do
collect ::
OnChainHeadState 'StInitialized ->
Tx
collect OnChainHeadState{networkId, stateMachine} = do
collect OnChainHeadState{networkId, ownVerificationKey, stateMachine} = do
let commits = Map.fromList $ fmap tripleToPair initialCommits
in collectComTx networkId initialThreadOutput commits
in collectComTx networkId ownVerificationKey initialThreadOutput commits
where
Initialized
{ initialThreadOutput
Expand All @@ -263,13 +263,13 @@ close ::
ConfirmedSnapshot Tx ->
OnChainHeadState 'StOpen ->
Tx
close confirmedSnapshot OnChainHeadState{stateMachine} = do
close confirmedSnapshot OnChainHeadState{ownVerificationKey, stateMachine} = do
let (sn, sigs) =
case confirmedSnapshot of
ConfirmedSnapshot{snapshot, signatures} -> (snapshot, signatures)
InitialSnapshot{snapshot} -> (snapshot, mempty)
(i, o, dat, _) = openThreadOutput
in closeTx sn sigs (i, o, dat)
in closeTx ownVerificationKey sn sigs (i, o, dat)
where
Open{openThreadOutput} = stateMachine

Expand Down
19 changes: 13 additions & 6 deletions hydra-node/src/Hydra/Chain/Direct/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ mkHeadTokenScript =
fromPlutusScript @PlutusScriptV1 . HeadTokens.validatorScript . toPlutusTxOutRef

hydraHeadV1AssetName :: AssetName
hydraHeadV1AssetName = AssetName (fromBuiltin HeadTokens.hydraHeadV1)
hydraHeadV1AssetName = AssetName (fromBuiltin Head.hydraHeadV1)

-- FIXME: sould not be hardcoded
headValue :: Value
Expand Down Expand Up @@ -178,6 +178,8 @@ mkCommitDatum (partyFromVerKey . vkey -> party) headValidatorHash utxo =
-- i.e. driving the Head script state.
collectComTx ::
NetworkId ->
-- | Party who's authorizing this transaction
VerificationKey PaymentKey ->
-- | Everything needed to spend the Head state-machine output.
(TxIn, TxOut CtxUTxO, ScriptData, [OnChain.Party]) ->
-- | Data needed to spend the commit output produced by each party.
Expand All @@ -187,11 +189,12 @@ collectComTx ::
-- TODO(SN): utxo unused means other participants would not "see" the opened
-- utxo when observing. Right now, they would be trusting the OCV checks this
-- and construct their "world view" from observed commit txs in the HeadLogic
collectComTx networkId (headInput, initialHeadOutput, ScriptDatumForTxIn -> headDatumBefore, parties) commits =
collectComTx networkId vk (headInput, initialHeadOutput, ScriptDatumForTxIn -> headDatumBefore, parties) commits =
unsafeBuildTransaction $
emptyTxBody
& addInputs ((headInput, headWitness) : (mkCommit <$> Map.toList commits))
& addOutputs [headOutput]
& addExtraRequiredSigners [verificationKeyHash vk]
where
headWitness =
BuildTxWith $ ScriptWitness scriptWitnessCtx $ mkScriptWitness headScript headDatumBefore headRedeemer
Expand Down Expand Up @@ -232,18 +235,21 @@ collectComTx networkId (headInput, initialHeadOutput, ScriptDatumForTxIn -> head

-- | Create a transaction closing a head with given snapshot number and utxo.
closeTx ::
-- | Party who's authorizing this transaction
VerificationKey PaymentKey ->
Snapshot Tx ->
-- | Multi-signature of the whole snapshot
MultiSigned (Snapshot Tx) ->
-- | Everything needed to spend the Head state-machine output.
-- FIXME(SN): should also contain some Head identifier/address and stored Value (maybe the TxOut + Data?)
UTxOWithScript ->
Tx
closeTx Snapshot{number, utxo} sig (headInput, headOutputBefore, ScriptDatumForTxIn -> headDatumBefore) =
closeTx vk Snapshot{number, utxo} sig (headInput, headOutputBefore, ScriptDatumForTxIn -> headDatumBefore) =
unsafeBuildTransaction $
emptyTxBody
& addInputs [(headInput, headWitness)]
& addOutputs [headOutputAfter]
& addExtraRequiredSigners [verificationKeyHash vk]
where
headWitness =
BuildTxWith $ ScriptWitness scriptWitnessCtx $ mkScriptWitness headScript headDatumBefore headRedeemer
Expand Down Expand Up @@ -299,8 +305,8 @@ data AbortTxError = OverlappingInputs
-- | Create transaction which aborts a head by spending the Head output and all
-- other "initial" outputs.
abortTx ::
-- | Network identifier for address discrimination
NetworkId ->
-- | Party who's authorizing this transaction
VerificationKey PaymentKey ->
-- | Everything needed to spend the Head state-machine output.
(TxIn, TxOut CtxUTxO, ScriptData) ->
-- | Script for monetary policy to burn tokens
Expand All @@ -312,7 +318,7 @@ abortTx ::
-- Should contain the PT and is locked by commit script.
Map TxIn (TxOut CtxUTxO, ScriptData) ->
Either AbortTxError Tx
abortTx _networkId (headInput, initialHeadOutput, ScriptDatumForTxIn -> headDatumBefore) headTokenScript initialsToAbort commitsToAbort
abortTx vk (headInput, initialHeadOutput, ScriptDatumForTxIn -> headDatumBefore) headTokenScript initialsToAbort commitsToAbort
| isJust (lookup headInput initialsToAbort) =
Left OverlappingInputs
| otherwise =
Expand All @@ -322,6 +328,7 @@ abortTx _networkId (headInput, initialHeadOutput, ScriptDatumForTxIn -> headDatu
& addInputs ((headInput, headWitness) : initialInputs <> commitInputs)
& addOutputs commitOutputs
& burnTokens headTokenScript Burn headTokens
& addExtraRequiredSigners [verificationKeyHash vk]
where
headWitness =
BuildTxWith $ ScriptWitness scriptWitnessCtx $ mkScriptWitness headScript headDatumBefore headRedeemer
Expand Down
22 changes: 22 additions & 0 deletions hydra-node/src/Hydra/Chain/Direct/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ module Hydra.Chain.Direct.Util where
import Hydra.Prelude

import qualified Cardano.Crypto.DSIGN as Crypto
import Cardano.Ledger.Alonzo.Tx (ValidatedTx (..))
import Cardano.Ledger.Alonzo.TxWitness (TxWitness (..))
import Cardano.Ledger.Crypto (DSIGN)
import qualified Cardano.Ledger.SafeHash as SafeHash
import qualified Cardano.Ledger.TxIn as Ledger
import Cardano.Slotting.Slot (WithOrigin (..))
import Control.Tracer (nullTracer)
import Data.Map.Strict ((!))
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Hydra.Cardano.Api hiding (AlonzoEra, Block, SigningKey, VerificationKey)
import qualified Hydra.Cardano.Api as Api
import qualified Hydra.Cardano.Api as Shelley
import Ouroboros.Consensus.Byron.Ledger.Config (CodecConfig (..))
import Ouroboros.Consensus.Cardano (CardanoBlock)
Expand Down Expand Up @@ -174,6 +180,22 @@ retry predicate action =
where
catchIf f a b = a `catch` \e -> if f e then b e else throwIO e

signWith ::
(Api.VerificationKey Api.PaymentKey, Api.SigningKey Api.PaymentKey) ->
ValidatedTx Api.LedgerEra ->
ValidatedTx Api.LedgerEra
signWith credentials validatedTx@ValidatedTx{body, wits} =
validatedTx
{ wits =
wits{txwitsVKey = Set.union (txwitsVKey wits) sig}
}
where
txid =
Ledger.TxId (SafeHash.hashAnnotated body)
sig =
toLedgerKeyWitness
[Api.signWith @Api.Era (fromLedgerTxId txid) credentials]

-- | Marker datum used to identify payment UTXO
markerDatum :: Data
markerDatum = toData $ toBuiltinData ("Hydra Head Payment" :: BuiltinByteString)
Expand Down
Loading