Add utility functions for requesting transaction keys

Add utility functions for requesting transaction keys and a small example flow which uses
these functions for testing purposes. These keys are required in for the anonymisation of
transactions, by providing per-transaction keys which cannot be associated with the owning
party's keys without an intermediary certificate.
This commit is contained in:
Ross Nicoll 2017-03-15 14:21:13 +00:00
parent cec4e20bc8
commit c73a2e6034
3 changed files with 145 additions and 0 deletions

View File

@ -0,0 +1,43 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.composite
import net.corda.core.flows.FlowLogic
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.unwrap
import java.security.cert.Certificate
object TxKeyFlowUtilities {
/**
* Receive a key from a counterparty. This would normally be triggered by a flow as part of a transaction assembly
* process.
*/
@Suspendable
fun receiveKey(flow: FlowLogic<*>, otherSide: Party): Pair<CompositeKey, Certificate?> {
val untrustedKey = flow.receive<Response>(otherSide)
return untrustedKey.unwrap {
// TODO: Verify the certificate connects the given key to the counterparty, once we have certificates
Pair(it.key, it.certificate)
}
}
/**
* Generates a new key and then returns it to the counterparty and as the result from the function. Note that this
* is an expensive operation, and should only be called once the calling flow has confirmed it wants to be part of
* a transaction with the counterparty, in order to avoid a DoS risk.
*/
@Suspendable
fun provideKey(flow: FlowLogic<*>, otherSide: Party): CompositeKey {
val key = flow.serviceHub.keyManagementService.freshKey().public.composite
// TODO: Generate and sign certificate for the key, once we have signing support for composite keys
// (in this case the legal identity key)
flow.send(otherSide, Response(key, null))
return key
}
@CordaSerializable
data class Response(val key: CompositeKey, val certificate: Certificate?)
}

View File

@ -0,0 +1,58 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.node.PluginServiceHub
import net.corda.core.utilities.ProgressTracker
import net.corda.flows.TxKeyFlowUtilities
import java.security.cert.Certificate
/**
* Very basic flow which requests a transaction key from a counterparty, used for testing [TxKeyFlowUtilities].
* This MUST not be provided on any real node, as the ability for arbitrary parties to request keys would enable
* DoS of the node, as key generation/storage is vastly more expensive than submitting a request.
*/
object TxKeyFlow {
fun registerFlowInitiator(services: PluginServiceHub) {
services.registerFlowInitiator(Requester::class.java, ::Provider)
}
class Requester(val otherSide: Party,
override val progressTracker: ProgressTracker): FlowLogic<Pair<CompositeKey, Certificate?>>() {
constructor(otherSide: Party) : this(otherSide, tracker())
companion object {
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
fun tracker() = ProgressTracker(AWAITING_KEY)
}
@Suspendable
override fun call(): Pair<CompositeKey, Certificate?> {
progressTracker.currentStep = AWAITING_KEY
return TxKeyFlowUtilities.receiveKey(this, otherSide)
}
}
/**
* Flow which waits for a key request from a counterparty, generates a new key and then returns it to the
* counterparty and as the result from the flow.
*/
class Provider(val otherSide: Party,
override val progressTracker: ProgressTracker): FlowLogic<CompositeKey>() {
constructor(otherSide: Party) : this(otherSide, tracker())
companion object {
object SENDING_KEY : ProgressTracker.Step("Sending key")
fun tracker() = ProgressTracker(SENDING_KEY)
}
@Suspendable
override fun call(): CompositeKey {
progressTracker.currentStep == SENDING_KEY
return TxKeyFlowUtilities.provideKey(this, otherSide)
}
}
}

View File

@ -0,0 +1,44 @@
package net.corda.core.flows
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.MOCK_IDENTITY_SERVICE
import net.corda.testing.ledger
import net.corda.testing.node.MockNetwork
import org.junit.Before
import org.junit.Test
import kotlin.test.assertNotNull
class TxKeyFlowUtilitiesTests {
lateinit var net: MockNetwork
@Before
fun before() {
net = MockNetwork(false)
net.identities += MOCK_IDENTITY_SERVICE.identities
}
@Test
fun `issue key`() {
// We run this in parallel threads to help catch any race conditions that may exist.
net = MockNetwork(false, true)
// Set up values we'll need
val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name)
val bobNode = net.createPartyNode(notaryNode.info.address, BOB.name)
val aliceKey: Party = aliceNode.services.myInfo.legalIdentity
val bobKey: Party = bobNode.services.myInfo.legalIdentity
// Run the flows
TxKeyFlow.registerFlowInitiator(bobNode.services)
val requesterFlow = aliceNode.services.startFlow(TxKeyFlow.Requester(bobKey))
// Get the results
val actual: CompositeKey = requesterFlow.resultFuture.get().first
assertNotNull(actual)
}
}