Merged in mnesbit-sign-before-notary-protocol (pull request #402)

Never send transactions to the Notary that aren't signed by all parties.
This commit is contained in:
Matthew Nesbit 2016-10-11 11:01:24 +01:00
commit f1a2c38e9f
10 changed files with 45 additions and 30 deletions

View File

@ -88,9 +88,10 @@ object TwoPartyTradeProtocol {
val partialTX: SignedTransaction = receiveAndCheckProposedTransaction()
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = signWithOurKey(partialTX)
val notarySignature = getNotarySignature(partialTX)
return sendSignatures(partialTX, ourSignature, notarySignature)
val ourSignature = calculateOurSignature(partialTX)
val allPartySignedTx = partialTX + ourSignature
val notarySignature = getNotarySignature(allPartySignedTx)
return sendSignatures(allPartySignedTx, ourSignature, notarySignature)
}
@Suspendable
@ -138,16 +139,16 @@ object TwoPartyTradeProtocol {
}
}
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
open fun calculateOurSignature(partialTX: SignedTransaction): DigitalSignature.WithKey {
progressTracker.currentStep = SIGNING
return myKeyPair.signWithECDSA(partialTX.txBits)
}
@Suspendable
private fun sendSignatures(partialTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
private fun sendSignatures(allPartySignedTx: SignedTransaction, ourSignature: DigitalSignature.WithKey,
notarySignature: DigitalSignature.LegallyIdentifiable): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = partialTX + ourSignature + notarySignature
val fullySigned = allPartySignedTx + notarySignature
logger.trace { "Built finished transaction, sending back to secondary!" }

View File

@ -79,7 +79,9 @@ abstract class AbstractStateReplacementProtocol<T> {
val participantSignatures = parties.map { getParticipantSignature(it, stx) }
val allSignatures = participantSignatures + getNotarySignature(stx)
val allPartySignedTx = stx + participantSignatures
val allSignatures = participantSignatures + getNotarySignature(allPartySignedTx)
parties.forEach { send(it, allSignatures) }
return allSignatures

View File

@ -48,6 +48,11 @@ object NotaryProtocol {
check(wtx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) {
"Input states must have the same Notary"
}
try {
stx.verifySignatures(notaryParty.owningKey)
} catch (ex: SignedTransaction.SignaturesMissingException) {
throw NotaryException(NotaryError.SignaturesMissing(ex.missing))
}
val request = SignRequest(stx, serviceHub.myInfo.legalIdentity)
val response = sendAndReceive<Result>(notaryParty, request)

View File

@ -139,10 +139,11 @@ object TwoPartyDealProtocol {
val stx: SignedTransaction = verifyPartialTransaction(getPartialTransaction())
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = signWithOurKey(stx)
val notarySignature = getNotarySignature(stx)
val ourSignature = computeOurSignature(stx)
val allPartySignedTx = stx + ourSignature
val notarySignature = getNotarySignature(allPartySignedTx)
val fullySigned = sendSignatures(stx, ourSignature, notarySignature)
val fullySigned = sendSignatures(allPartySignedTx, ourSignature, notarySignature)
progressTracker.currentStep = RECORDING
@ -167,16 +168,16 @@ object TwoPartyDealProtocol {
return subProtocol(NotaryProtocol.Client(stx))
}
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
open fun computeOurSignature(partialTX: SignedTransaction): DigitalSignature.WithKey {
progressTracker.currentStep = SIGNING
return myKeyPair.signWithECDSA(partialTX.txBits)
}
@Suspendable
private fun sendSignatures(partialTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
private fun sendSignatures(allPartySignedTx: SignedTransaction, ourSignature: DigitalSignature.WithKey,
notarySignature: DigitalSignature.LegallyIdentifiable): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = partialTX + ourSignature + notarySignature
val fullySigned = allPartySignedTx + notarySignature
logger.trace { "Built finished transaction, sending back to other party!" }

View File

@ -38,7 +38,7 @@ class ValidatingNotaryProtocol(otherSide: Party,
private fun checkSignatures(stx: SignedTransaction) {
try {
stx.verifySignatures(serviceHub.myInfo.legalIdentity.owningKey)
stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey)
} catch(e: SignedTransaction.SignaturesMissingException) {
throw NotaryException(NotaryError.SignaturesMissing(e.missing))
}

View File

@ -242,17 +242,20 @@ Let's implement the ``Seller.call`` method. This will be run when the protocol i
@Suspendable
override fun call(): SignedTransaction {
val partialTX: SignedTransaction = receiveAndCheckProposedTransaction()
val ourSignature: DigitalSignature.WithKey = signWithOurKey(partialTX)
val notarySignature = getNotarySignature(partialTX)
val result: SignedTransaction = sendSignatures(partialTX, ourSignature, notarySignature)
val ourSignature: DigitalSignature.WithKey = computeOurSignature(partialTX)
val allPartySignedTx = partialTX + ourSignature
val notarySignature = getNotarySignature(allPartySignedTx)
val result: SignedTransaction = sendSignatures(allPartySignedTx, ourSignature, notarySignature)
return result
}
Here we see the outline of the procedure. We receive a proposed trade transaction from the buyer and check that it's
valid. Then we sign with our own key and request a notary to assert with another signature that the
valid. The buyer has already attached their signature before sending it. Then we calculate and attach our own signature so that the transaction is
now signed by both the buyer and the seller. We then send this request to a notary to assert with another signature that the
timestamp in the transaction (if any) is valid and there are no double spends, and send back both
our signature and the notaries signature. Finally, we hand back to the code that invoked the protocol the
finished transaction.
our signature and the notaries signature. Note we should not send to the notary until all other required signatures have been appended
as the notary may validate the signatures as well as verifying for itself the transactional integrity.
Finally, we hand back to the code that invoked the protocol the finished transaction.
Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
@ -369,12 +372,12 @@ Here's the rest of the code:
.. sourcecode:: kotlin
open fun signWithOurKey(partialTX: SignedTransaction) = myKeyPair.signWithECDSA(partialTX.txBits)
open fun computeOurSignature(partialTX: SignedTransaction) = myKeyPair.signWithECDSA(partialTX.txBits)
@Suspendable
private fun sendSignatures(partialTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
private fun sendSignatures(allPartySignedTX: SignedTransaction, ourSignature: DigitalSignature.WithKey,
notarySignature: DigitalSignature.LegallyIdentifiable): SignedTransaction {
val fullySigned = partialTX + ourSignature + notarySignature
val fullySigned = allPartySignedTX + notarySignature
logger.trace { "Built finished transaction, sending back to secondary!" }
send(otherSide, SignaturesFromSeller(ourSignature, notarySignature))
return fullySigned

View File

@ -66,9 +66,10 @@ class ValidatingNotaryServiceTests {
tx.toSignedTransaction(false)
}
val future = runClient(stx)
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val ex = assertFailsWith(ExecutionException::class) {
val future = runClient(stx)
future.get()
}
val notaryError = (ex.cause as NotaryException).error
assertThat(notaryError).isInstanceOf(NotaryError.SignaturesMissing::class.java)

View File

@ -27,6 +27,7 @@ import com.r3corda.node.services.config.NodeConfiguration
import com.r3corda.node.services.messaging.NodeMessagingClient
import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.transactions.SimpleNotaryService
import com.r3corda.node.services.transactions.ValidatingNotaryService
import com.r3corda.testing.node.MockNetwork
import joptsimple.OptionParser
import joptsimple.OptionSet
@ -406,7 +407,7 @@ private fun startNode(params: CliParams.RunNode, networkMap: SingleMessageRecipi
val networkMapId =
when (params.node) {
IRSDemoNode.NodeA -> {
advertisedServices = setOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type))
advertisedServices = setOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type))
null
}
IRSDemoNode.NodeB -> {

View File

@ -28,7 +28,7 @@ import com.r3corda.node.services.config.FullNodeConfiguration
import com.r3corda.node.services.messaging.NodeMessagingClient
import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.persistence.NodeAttachmentService
import com.r3corda.node.services.transactions.SimpleNotaryService
import com.r3corda.node.services.transactions.ValidatingNotaryService
import com.r3corda.node.utilities.databaseTransaction
import com.r3corda.protocols.NotaryProtocol
import com.r3corda.protocols.TwoPartyTradeProtocol
@ -141,7 +141,7 @@ fun main(args: Array<String>) {
// the map is not very helpful, but we need one anyway. So just make the buyer side run the network map as it's
// the side that sticks around waiting for the seller.
val networkMapId = if (role == Role.BUYER) {
advertisedServices = setOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type))
advertisedServices = setOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type))
null
} else {
advertisedServices = emptySet()

View File

@ -24,6 +24,7 @@ import com.r3corda.node.services.persistence.DBCheckpointStorage
import com.r3corda.node.services.persistence.PerFileCheckpointStorage
import com.r3corda.node.services.transactions.InMemoryUniquenessProvider
import com.r3corda.node.services.transactions.SimpleNotaryService
import com.r3corda.node.services.transactions.ValidatingNotaryService
import com.r3corda.node.utilities.databaseTransaction
import org.slf4j.Logger
import java.nio.file.Files
@ -240,7 +241,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
}
fun createNotaryNode(legalName: String? = null, keyPair: KeyPair? = null): MockNode {
return createNode(null, -1, defaultFactory, true, legalName, keyPair, ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type))
return createNode(null, -1, defaultFactory, true, legalName, keyPair, ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type))
}
fun createPartyNode(networkMapAddr: SingleMessageRecipient, legalName: String? = null, keyPair: KeyPair? = null): MockNode {