mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
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:
commit
f1a2c38e9f
@ -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!" }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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!" }
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 -> {
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user