diff --git a/docs/source/protocol-state-machines.rst b/docs/source/protocol-state-machines.rst index b20686ce13..21c34d4e66 100644 --- a/docs/source/protocol-state-machines.rst +++ b/docs/source/protocol-state-machines.rst @@ -140,8 +140,8 @@ Let's unpack what this code does: - Two of the classes are simply wrappers for parameters to the trade; things like what is being sold, what the price of the asset is, how much the buyer is willing to pay and so on. The ``myKeyPair`` field is simply the public key that the seller wishes the buyer to send the cash to. The session ID field is sent from buyer to seller when the - trade is being set up and is just a big random number. It's used to keep messages separated on the network, and stop - malicious entities trying to interfere with the message stream. + trade is being set up and is used to keep messages separated on the network, and stop malicious entities trying to + interfere with the message stream. - The other two classes define empty abstract classes called ``Buyer`` and ``Seller``. These inherit from a class called ``ProtocolStateMachine`` and provide two type parameters: the arguments class we just defined for each side and the type of the object that the protocol finally produces (this doesn't have to be identical for each side, even @@ -149,6 +149,9 @@ Let's unpack what this code does: - Finally it simply defines a static method that creates an instance of an object that inherits from this base class and returns it, with a ``StateMachineManager`` as an instance. The Impl class will be defined below. +.. note:: Session IDs keep different traffic streams separated, so for security they must be large and random enough +to be unguessable. 63 bits is good enough. + Alright, so using this protocol shouldn't be too hard: in the simplest case we can just pass in the details of the trade to either runBuyer or runSeller, depending on who we are, and then call ``.get()`` on the resulting future to block the calling thread until the protocol has finished. Or we could register a callback on the returned future that will be @@ -179,7 +182,6 @@ Implementing the seller private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) : TwoPartyTradeProtocol() { companion object { val TRADE_TOPIC = "com.r3cev.protocols.trade" - fun makeSessionID() = Math.abs(SecureRandom.getInstanceStrong().nextLong()) } class SellerImpl : Seller() { @@ -206,10 +208,7 @@ Implementing the seller We start with a skeleton on which we will build the protocol. Putting things in a *companion object* in Kotlin is like declaring them as static members in Java. Here, we define a "topic" that will identify trade related messages that -arrive at a node (see :doc:`messaging` for details), and a convenience function to pick a large random session ID. - -.. note:: Session IDs keep different traffic streams separated, so for security they must be large and random enough - to be unguessable. 63 bits is good enough. +arrive at a node (see :doc:`messaging` for details). The runSeller and runBuyer methods simply start the state machines, passing in a reference to the classes and the topics each side will use. @@ -240,7 +239,7 @@ Next we add some code to the ``SellerImpl.call`` method: .. sourcecode:: kotlin - val sessionID = makeSessionID() + val sessionID = random63BitValue() // Make the first message we'll send to kick off the protocol. val hello = SellerTradeInfo(args.assetToSell, args.price, args.myKeyPair.public, sessionID) diff --git a/src/main/kotlin/contracts/protocols/TwoPartyTradeProtocol.kt b/src/main/kotlin/contracts/protocols/TwoPartyTradeProtocol.kt index 956e717e7f..64e9e392b3 100644 --- a/src/main/kotlin/contracts/protocols/TwoPartyTradeProtocol.kt +++ b/src/main/kotlin/contracts/protocols/TwoPartyTradeProtocol.kt @@ -16,7 +16,6 @@ import core.serialization.deserialize import core.utilities.trace import java.security.KeyPair import java.security.PublicKey -import java.security.SecureRandom /** * This asset trading protocol has two parties (B and S for buyer and seller) and the following steps: @@ -73,7 +72,6 @@ abstract class TwoPartyTradeProtocol { private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) : TwoPartyTradeProtocol() { companion object { val TRADE_TOPIC = "com.r3cev.protocols.trade" - fun makeSessionID() = Math.abs(SecureRandom.getInstanceStrong().nextLong()) } // This object is serialised to the network and is the first protocol message the seller sends to the buyer. @@ -92,7 +90,7 @@ private class TwoPartyTradeProtocolImpl(private val smm: StateMachineManager) : // learn more about the protocol state machine framework. class SellerImpl : Seller() { override fun call(args: SellerInitialArgs): Pair { - val sessionID = makeSessionID() + val sessionID = random63BitValue() // Make the first message we'll send to kick off the protocol. val hello = SellerTradeInfo(args.assetToSell, args.price, args.myKeyPair.public, sessionID) diff --git a/src/main/kotlin/core/Utils.kt b/src/main/kotlin/core/Utils.kt index 85dcac6c5a..b278103c59 100644 --- a/src/main/kotlin/core/Utils.kt +++ b/src/main/kotlin/core/Utils.kt @@ -13,6 +13,7 @@ import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.SettableFuture import org.slf4j.Logger +import java.security.SecureRandom import java.time.Duration import java.util.* import java.util.concurrent.Executor @@ -44,6 +45,12 @@ val Int.hours: Duration get() = Duration.ofHours(this.toLong()) val Int.minutes: Duration get() = Duration.ofMinutes(this.toLong()) val Int.seconds: Duration get() = Duration.ofSeconds(this.toLong()) +/** + * Returns a random positive long generated using a secure RNG. This function sacrifies a bit of entropy in order to + * avoid potential bugs where the value is used in a context where negative numbers are not expected. + */ +fun random63BitValue(): Long = Math.abs(SecureRandom.getInstanceStrong().nextLong()) + fun ListenableFuture.whenComplete(executor: Executor? = null, body: () -> Unit) { addListener(Runnable { body() }, executor ?: MoreExecutors.directExecutor()) } diff --git a/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt b/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt index e489a50270..d7c9629702 100644 --- a/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt +++ b/src/test/kotlin/core/messaging/TwoPartyTradeProtocolTests.kt @@ -12,15 +12,11 @@ import com.google.common.util.concurrent.MoreExecutors import contracts.Cash import contracts.CommercialPaper import contracts.protocols.TwoPartyTradeProtocol -import core.ContractState -import core.DOLLARS -import core.StateAndRef -import core.days +import core.* import core.testutils.* import org.junit.After import org.junit.Before import org.junit.Test -import java.security.SecureRandom import java.util.concurrent.Executors import java.util.logging.Formatter import java.util.logging.Level @@ -77,7 +73,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() { val tpSeller = TwoPartyTradeProtocol.create(StateMachineManager(alicesServices, backgroundThread)) val tpBuyer = TwoPartyTradeProtocol.create(StateMachineManager(bobsServices, backgroundThread)) - val buyerSessionID = SecureRandom.getInstanceStrong().nextLong() + val buyerSessionID = random63BitValue() val aliceResult = tpSeller.runSeller( bobsAddress, @@ -134,7 +130,7 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() { val smmBuyer = StateMachineManager(bobsServices, MoreExecutors.directExecutor()) val tpBuyer = TwoPartyTradeProtocol.create(smmBuyer) - val buyerSessionID = SecureRandom.getInstanceStrong().nextLong() + val buyerSessionID = random63BitValue() tpSeller.runSeller( bobsAddress,