Skip to content

Commit

Permalink
Refactor: introduce AuthenticatedMessage and SignedMessage
Browse files Browse the repository at this point in the history
An AtuhenticatedMessager is a SignedMessage which we authenticated.
  • Loading branch information
pgrange authored and v0d1ch committed Jul 5, 2023
1 parent 0e5ee7d commit 2e25720
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 38 deletions.
12 changes: 6 additions & 6 deletions hydra-node/exe/hydra-node/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import Hydra.Options (
)
import Hydra.Persistence (Persistence (load), createPersistence, createPersistenceIncremental)
import Hydra.API.ServerOutput (ServerOutput(PeerConnected, PeerDisconnected))
import Hydra.Network.Authenticate (withAuthentication)
import Hydra.Network.Authenticate (withAuthentication, Authenticated (Authenticated))

newtype ParamMismatchError = ParamMismatchError String deriving (Eq, Show)

Expand Down Expand Up @@ -104,28 +104,28 @@ main = do
wallet <- mkTinyWallet (contramap DirectChain tracer) chainConfig
withDirectChain (contramap DirectChain tracer) chainConfig ctx wallet (getChainState hs) (putEvent . OnChainEvent) $ \chain -> do
let RunOptions{host, port, peers, nodeId} = opts
putNetworkEvent = putEvent . NetworkEvent defaultTTL
putNetworkEvent (Authenticated msg _) = putEvent $ NetworkEvent defaultTTL msg
RunOptions{apiHost, apiPort} = opts
apiPersistence <- createPersistenceIncremental $ persistenceDir <> "/server-output"
withAPIServer apiHost apiPort party apiPersistence (contramap APIServer tracer) chain (putEvent . ClientEvent) $ \server -> do
withNetwork (contramap Network tracer) server host port peers nodeId putNetworkEvent $ \hn -> do
withNetwork (contramap Network tracer) server signingKey otherParties host port peers nodeId putNetworkEvent $ \hn -> do
let RunOptions{ledgerConfig} = opts
withCardanoLedger ledgerConfig chainConfig $ \ledger ->
runHydraNode (contramap Node tracer) $
HydraNode{eq, hn, nodeState, oc = chain, server, ledger, env, persistence}
HydraNode{eq, hn = contramap (`Authenticated` party) hn, nodeState, oc = chain, server, ledger, env, persistence}

publish opts = do
(_, sk) <- readKeyPair (publishSigningKey opts)
let PublishOptions{publishNetworkId = networkId, publishNodeSocket = nodeSocket} = opts
txId <- publishHydraScripts networkId nodeSocket sk
putStrLn (decodeUtf8 (serialiseToRawBytesHex txId))

withNetwork tracer Server{sendOutput} host port peers nodeId =
withNetwork tracer Server{sendOutput} signingKey parties host port peers nodeId =
let localhost = Host{hostname = show host, port}
connectionMessages = \case
Connected nodeid -> sendOutput $ PeerConnected nodeid
Disconnected nodeid -> sendOutput $ PeerDisconnected nodeid
in withHeartbeat nodeId connectionMessages $ withOuroborosNetwork tracer localhost peers
in withAuthentication signingKey parties $ withHeartbeat nodeId connectionMessages $ withOuroborosNetwork tracer localhost peers

withCardanoLedger ledgerConfig chainConfig action = do
let DirectChainConfig{networkId, nodeSocket} = chainConfig
Expand Down
34 changes: 22 additions & 12 deletions hydra-node/src/Hydra/Network/Authenticate.hs
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
{-# LANGUAGE DuplicateRecordFields #-}

module Hydra.Network.Authenticate where

import Cardano.Crypto.Util (SignableRepresentation)
import Hydra.Crypto (HydraKey, Key (SigningKey), Signature, sign, verify)
import Hydra.Network (Network (Network, broadcast), NetworkComponent)
import Hydra.Party (Party (Party, vkey), deriveParty)
import Hydra.Party (Party (Party, vkey))
import Hydra.Prelude

-- | Represents a signed message over the network.
-- Becomes valid once its receivers verify it against its other peers
-- verification keys.
-- Messages are signed and turned into authenticated messages before
-- broadcasting them to other peers.
data Authenticated msg = Authenticated
data Signed msg = Signed
{ payload :: msg
, signature :: Signature msg
, party :: Party
}
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)

instance (Arbitrary msg, SignableRepresentation msg) => Arbitrary (Authenticated msg) where
data Authenticated msg = Authenticated
{ payload :: msg
, party :: Party
}
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)

instance (Arbitrary msg, SignableRepresentation msg) => Arbitrary (Signed msg) where
arbitrary = genericArbitrary
shrink = genericShrink

instance ToCBOR msg => ToCBOR (Authenticated msg) where
toCBOR (Authenticated msg sig party) = toCBOR msg <> toCBOR sig <> toCBOR party
instance ToCBOR msg => ToCBOR (Signed msg) where
toCBOR (Signed msg sig party) = toCBOR msg <> toCBOR sig <> toCBOR party

instance FromCBOR msg => FromCBOR (Authenticated msg) where
fromCBOR = Authenticated <$> fromCBOR <*> fromCBOR <*> fromCBOR
instance FromCBOR msg => FromCBOR (Signed msg) where
fromCBOR = Signed <$> fromCBOR <*> fromCBOR <*> fromCBOR

-- | Middleware used to sign messages before broadcasting them to other peers
-- and verify signed messages upon receiving.
Expand All @@ -42,14 +51,15 @@ withAuthentication ::
-- Other party members
[Party] ->
-- The underlying raw network.
NetworkComponent m (Authenticated msg) a ->
NetworkComponent m (Signed msg) a ->
-- The node internal authenticated network.
NetworkComponent m msg a
NetworkComponent m (Authenticated msg) a
withAuthentication signingKey parties withRawNetwork callback action = do
withRawNetwork checkSignature authenticate
where
checkSignature (Authenticated msg sig party@Party{vkey = partyVkey}) = do
checkSignature (Signed msg sig party@Party{vkey = partyVkey}) = do
when (verify partyVkey sig msg && elem party parties) $
callback msg
callback $
Authenticated msg party
authenticate = \Network{broadcast} ->
action $ Network{broadcast = \msg -> broadcast (Authenticated msg (sign signingKey msg) (deriveParty signingKey))}
action $ Network{broadcast = \(Authenticated msg party) -> broadcast (Signed msg (sign signingKey msg) party)}
3 changes: 2 additions & 1 deletion hydra-node/src/Hydra/Network/Heartbeat.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module Hydra.Network.Heartbeat where

import Hydra.Prelude

import Cardano.Binary (serialize')
import Cardano.Crypto.Util (SignableRepresentation (getSignableRepresentation))
import Control.Concurrent.Class.MonadSTM (modifyTVar', newTVarIO, readTVarIO)
import qualified Data.Map as Map
import qualified Data.Set as Set
Expand Down Expand Up @@ -61,7 +63,6 @@ instance (FromCBOR msg) => FromCBOR (Heartbeat msg) where
instance ToCBOR msg => SignableRepresentation (Heartbeat msg) where
getSignableRepresentation = serialize'


-- | Delay between each heartbeat check.
heartbeatDelay :: DiffTime
heartbeatDelay = 0.5
Expand Down
36 changes: 17 additions & 19 deletions hydra-node/test/Hydra/Network/AuthenticateSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import Control.Monad.IOSim (runSimOrThrow)
import Data.ByteString (pack)
import Hydra.Crypto (sign)
import Hydra.Network (Network (..))
import Hydra.Network.Authenticate (Authenticated (Authenticated), withAuthentication)
import Hydra.Network.Authenticate (Authenticated (..), Signed (Signed), withAuthentication)
import Hydra.Network.HeartbeatSpec (noop)
import Hydra.NetworkSpec (prop_canRoundtripCBOREncoding)
import Hydra.Party (deriveParty)
import Test.Hydra.Fixture (alice, aliceSk, bob, bobSk, carol, carolSk)
import Test.QuickCheck (generate, listOf)
import Test.QuickCheck (listOf)

spec :: Spec
spec = parallel $ do
Expand All @@ -28,50 +27,50 @@ spec = parallel $ do

it "pass the authenticated messages around" $ do
let receivedMsgs = runSimOrThrow $ do
receivedMessages <- newTVarIO ([] :: [ByteString])
receivedMessages <- newTVarIO ([] :: [Authenticated ByteString])

withAuthentication
aliceSk
[bob]
( \incoming _ -> do
incoming (Authenticated "1" (sign bobSk "1") bob)
incoming (Signed "1" (sign bobSk "1") bob)
)
(captureIncoming receivedMessages)
$ \_ ->
threadDelay 1

readTVarIO receivedMessages

receivedMsgs `shouldBe` ["1"]
receivedMsgs `shouldBe` [Authenticated "1" bob]

it "drop message coming from unknown party" $ do
let receivedMsgs = runSimOrThrow $ do
receivedMessages <- newTVarIO ([] :: [ByteString])
receivedMessages <- newTVarIO ([] :: [Authenticated ByteString])

withAuthentication
aliceSk
[bob]
( \incoming _ -> do
incoming (Authenticated "1" (sign bobSk "1") bob)
incoming (Authenticated "2" (sign aliceSk "2") alice)
incoming (Signed "1" (sign bobSk "1") bob)
incoming (Signed "2" (sign aliceSk "2") alice)
)
(captureIncoming receivedMessages)
$ \_ ->
threadDelay 1

readTVarIO receivedMessages

receivedMsgs `shouldBe` ["1"]
receivedMsgs `shouldBe` [Authenticated "1" bob]

it "drop message comming from party with wrong signature" $ do
let receivedMsgs = runSimOrThrow $ do
receivedMessages <- newTVarIO ([] :: [ByteString])
receivedMessages <- newTVarIO ([] :: [Authenticated ByteString])

withAuthentication
aliceSk
[bob, carol]
( \incoming _ -> do
incoming (Authenticated "1" (sign carolSk "1") bob)
incoming (Signed "1" (sign carolSk "1") bob)
)
(captureIncoming receivedMessages)
$ \_ ->
Expand All @@ -82,23 +81,22 @@ spec = parallel $ do
receivedMsgs `shouldBe` []

it "authenticate the message to broadcast" $ do
signingKey <- generate arbitrary
let someMessage = "1"
let someMessage = Authenticated "1" bob
sentMsgs = runSimOrThrow $ do
sentMessages <- newTVarIO ([] :: [Authenticated ByteString])
sentMessages <- newTVarIO ([] :: [Signed ByteString])

withAuthentication signingKey [] (captureOutgoing sentMessages) noop $ \Network{broadcast} -> do
withAuthentication bobSk [] (captureOutgoing sentMessages) noop $ \Network{broadcast} -> do
threadDelay 0.6
broadcast someMessage
threadDelay 1

readTVarIO sentMessages

sentMsgs `shouldBe` [Authenticated "1" (sign signingKey "1") (deriveParty signingKey)]
sentMsgs `shouldBe` [Signed "1" (sign bobSk "1") bob]

describe "Serialization" $ do
prop "can roundtrip CBOR encoding/decoding of Authenticated Hydra Message" $
prop_canRoundtripCBOREncoding @(Authenticated Msg)
prop "can roundtrip CBOR encoding/decoding of Signed Hydra Message" $
prop_canRoundtripCBOREncoding @(Signed Msg)

newtype Msg = Msg ByteString
deriving newtype (Eq, Show, ToCBOR, FromCBOR, SignableRepresentation)
Expand Down

0 comments on commit 2e25720

Please sign in to comment.