Send current transaction encrypted

This commit is contained in:
adam.houston 2022-03-21 16:53:12 +00:00
parent 711eb11a2e
commit b36a06b588
8 changed files with 64 additions and 82 deletions

View File

@ -64,18 +64,18 @@ class EncryptedTxEnclaveClient() : EnclaveClient {
} }
} }
override fun enclaveVerifyWithoutSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies) { override fun enclaveVerifyWithoutSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies) {
verifyTx(txAndDependencies, false) val decrypted = decrypt(encryptedTxAndDependencies.encryptedTransaction)
val verifiableTxAndDependencies = VerifiableTxAndDependencies(
decrypted,
encryptedTxAndDependencies.dependencies,
encryptedTxAndDependencies.encryptedDependencies
)
verifyTx(verifiableTxAndDependencies, false)
} }
override fun enclaveVerifyWithSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies): EncryptedTransaction {
verifyTx(txAndDependencies, true)
val ledgerTx = txAndDependencies.conclaveLedgerTxModel
val transactionSignature = getSignature(ledgerTx.signedTransaction.id)
return encrypt(ledgerTx).addSignature(transactionSignature)
}
override fun enclaveVerifyWithSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies): EncryptedTransaction { override fun enclaveVerifyWithSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies): EncryptedTransaction {
val decrypted = decrypt(encryptedTxAndDependencies.encryptedTransaction) val decrypted = decrypt(encryptedTxAndDependencies.encryptedTransaction)

View File

@ -42,37 +42,19 @@ interface EnclaveClient {
fun registerRemoteEnclaveInstanceInfo(invokeId: UUID, payload: ByteArray) fun registerRemoteEnclaveInstanceInfo(invokeId: UUID, payload: ByteArray)
/** /**
* Verify an unencrypted transaction (supplied with its dependencies), without checking the signatures. This would be used during * Verify an encrypted transaction (supplied with its dependencies), without checking the signatures. This would be used during
* [CollectSignaturesFlow], where we need to verify a transaction, but it is not fully signed (e.g. we haven't signed it yet, the * [CollectSignaturesFlow], where we need to verify a transaction, but it is not fully signed (e.g. we haven't signed it yet, the
* notary hasn't signed it yet, and possibly other parties). * notary hasn't signed it yet, and possibly other parties).
* *
* We do not return a signed and encrypted transaction from this call, as we will not be storing these transactions long term. They * We do not return a signed and encrypted transaction from this call, as we will not be storing these transactions long term. They
* are not fully signed at this stage therefore cannot be committed to the ledger. * are not fully signed at this stage therefore cannot be committed to the ledger.
* *
* @param txAndDependencies the transaction to verify * @param encryptedTxAndDependencies the encrypted transaction to verify
* *
* @throws [VerificationException] if verification failed * @throws [VerificationException] if verification failed
*/ */
@Throws(VerificationException::class) @Throws(VerificationException::class)
fun enclaveVerifyWithoutSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies) fun enclaveVerifyWithoutSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies)
/**
* Verify an unencrypted transaction (supplied with its dependencies), and also check the signatures. This would be used during
* [FinalityFlow], where we need to verify a transaction fully.
*
* We return a signed and encrypted transaction from this call, as we can store that and supply it to the enclave whenever we need it
* as proof that we have verified a transaction previously. I.e. our own signature over the id means that we (as an enclave) have
* previously verified this transaction
*
* @param txAndDependencies the transaction to verify
*
* @return an [EncryptedTransaction] which will be an encrypted version of the transaction, along with our enclave's signature
* over the transaction id.
*
* @throws [VerificationException] if verification failed
*/
@Throws(VerificationException::class)
fun enclaveVerifyWithSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies): EncryptedTransaction
/** /**
* Verify an encrypted transaction (supplied with its dependencies) and also check the signatures. This would be used during * Verify an encrypted transaction (supplied with its dependencies) and also check the signatures. This would be used during
@ -141,11 +123,7 @@ class DummyEnclaveClient: EnclaveClient, SingletonSerializeAsToken() {
throw UnsupportedOperationException("Add your custom enclave client implementation") throw UnsupportedOperationException("Add your custom enclave client implementation")
} }
override fun enclaveVerifyWithoutSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies) { override fun enclaveVerifyWithoutSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies) {
throw UnsupportedOperationException("Add your custom enclave client implementation")
}
override fun enclaveVerifyWithSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies): EncryptedTransaction {
throw UnsupportedOperationException("Add your custom enclave client implementation") throw UnsupportedOperationException("Add your custom enclave client implementation")
} }

View File

@ -1,8 +1,8 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.sun.org.apache.xpath.internal.operations.Bool
import net.corda.core.conclave.common.dto.ConclaveLedgerTxModel import net.corda.core.conclave.common.dto.ConclaveLedgerTxModel
import net.corda.core.conclave.common.dto.EncryptedVerifiableTxAndDependencies
import net.corda.core.conclave.common.dto.VerifiableTxAndDependencies import net.corda.core.conclave.common.dto.VerifiableTxAndDependencies
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
@ -13,6 +13,7 @@ import net.corda.core.identity.Party
import net.corda.core.identity.groupPublicKeysByWellKnownParty import net.corda.core.identity.groupPublicKeysByWellKnownParty
import net.corda.core.internal.dependencies import net.corda.core.internal.dependencies
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
@ -279,11 +280,12 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
progressTracker.currentStep = RECEIVING progressTracker.currentStep = RECEIVING
// Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures. // Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures.
var conclaveLedgerTxModel : ConclaveLedgerTxModel? = null var receivedEncryptedTx : EncryptedTransaction? = null
val stx = if (encrypted) { val stx = if (encrypted) {
conclaveLedgerTxModel = subFlow(ReceiveTransactionAsConclaveModelFlow(otherSideSession, checkSufficientSignatures = false, encrypted = true)) val stxAndEncrypted = subFlow(ReceiveTransactionWithEncryptedFlow(otherSideSession, checkSufficientSignatures = false))
conclaveLedgerTxModel.signedTransaction receivedEncryptedTx = stxAndEncrypted.encryptedTransaction
stxAndEncrypted.signedTransaction
} else { } else {
subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false, encrypted = true)) subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false, encrypted = true))
} }
@ -304,6 +306,10 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
val encryptionService = serviceHub.encryptedTransactionService val encryptionService = serviceHub.encryptedTransactionService
val validatedTxSvc = serviceHub.validatedTransactions val validatedTxSvc = serviceHub.validatedTransactions
val usableReceivedTx = receivedEncryptedTx ?: throw IllegalStateException("An encrypted transaction is required")
val locallyEncryptedTransaction = encryptionService.encryptTransactionForLocal(usableReceivedTx)
val encryptedTxs = stx.dependencies.mapNotNull { val encryptedTxs = stx.dependencies.mapNotNull {
validatedTxSvc.getEncryptedTransaction(it) validatedTxSvc.getEncryptedTransaction(it)
}.toSet() }.toSet()
@ -313,8 +319,8 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
}.toSet() }.toSet()
encryptionService.enclaveVerifyWithoutSignatures( encryptionService.enclaveVerifyWithoutSignatures(
VerifiableTxAndDependencies( EncryptedVerifiableTxAndDependencies(
conclaveLedgerTxModel!!, locallyEncryptedTransaction,
signedTxs, signedTxs,
encryptedTxs encryptedTxs
) )

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.conclave.common.dto.ConclaveLedgerTxModel import net.corda.core.conclave.common.dto.ConclaveLedgerTxModel
import net.corda.core.conclave.common.dto.EncryptedVerifiableTxAndDependencies
import net.corda.core.conclave.common.dto.VerifiableTxAndDependencies import net.corda.core.conclave.common.dto.VerifiableTxAndDependencies
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
@ -9,6 +10,8 @@ import net.corda.core.internal.checkParameterHash
import net.corda.core.internal.dependencies import net.corda.core.internal.dependencies
import net.corda.core.internal.pushToLoggingContext import net.corda.core.internal.pushToLoggingContext
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
@ -38,19 +41,28 @@ open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSid
private val encrypted : Boolean = false) : private val encrypted : Boolean = false) :
ReceiveTransactionFlowBase<SignedTransaction>(otherSideSession, checkSufficientSignatures, statesToRecord, encrypted) { ReceiveTransactionFlowBase<SignedTransaction>(otherSideSession, checkSufficientSignatures, statesToRecord, encrypted) {
override fun getReturnVal(stx: SignedTransaction, conclaveLedgerTxModel: ConclaveLedgerTxModel?): SignedTransaction { override fun getReturnVal(stx: SignedTransaction, encryptedTransaction: EncryptedTransaction?): SignedTransaction {
return stx return stx
} }
} }
open class ReceiveTransactionAsConclaveModelFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
private val checkSufficientSignatures: Boolean = true,
private val statesToRecord: StatesToRecord = StatesToRecord.NONE,
private val encrypted : Boolean = false) :
ReceiveTransactionFlowBase<ConclaveLedgerTxModel>(otherSideSession, checkSufficientSignatures, statesToRecord, encrypted) {
override fun getReturnVal(stx: SignedTransaction, conclaveLedgerTxModel: ConclaveLedgerTxModel?): ConclaveLedgerTxModel { @CordaSerializable
return conclaveLedgerTxModel ?: throw IllegalStateException("Cannot return a null ConclaveLedgerTxModel") data class SignedTransactionWithEncrypted(val signedTransaction: SignedTransaction, val encryptedTransaction: EncryptedTransaction)
open class ReceiveTransactionWithEncryptedFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
private val checkSufficientSignatures: Boolean = true,
private val statesToRecord: StatesToRecord = StatesToRecord.NONE) :
ReceiveTransactionFlowBase<SignedTransactionWithEncrypted>(otherSideSession, checkSufficientSignatures, statesToRecord, true) {
override fun getReturnVal(stx: SignedTransaction, encryptedTransaction: EncryptedTransaction?): SignedTransactionWithEncrypted {
require(encryptedTransaction != null) {
"Cannot return a null ConclaveLedgerTxModel"
}
return SignedTransactionWithEncrypted(stx, encryptedTransaction!!)
} }
} }
@ -60,7 +72,7 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
private val encrypted : Boolean = false) : FlowLogic<T>() { private val encrypted : Boolean = false) : FlowLogic<T>() {
@Suspendable @Suspendable
abstract fun getReturnVal(stx: SignedTransaction, conclaveLedgerTxModel: ConclaveLedgerTxModel?) : T abstract fun getReturnVal(stx: SignedTransaction, encryptedTransaction: EncryptedTransaction?) : T
@Suppress("KDocMissingDocumentation") @Suppress("KDocMissingDocumentation")
@Suspendable @Suspendable
@ -75,12 +87,11 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
logger.trace { "Receiving a transaction (but without checking the signatures) from ${otherSideSession.counterparty}" } logger.trace { "Receiving a transaction (but without checking the signatures) from ${otherSideSession.counterparty}" }
} }
var conclaveLedgerTxModel : ConclaveLedgerTxModel? = null var encryptedTx : EncryptedTransaction? = null
var remoteAttestation : ByteArray? = null
if (encrypted) { if (encrypted) {
// The first step in an encrypted exchange, is to request an exchange of attestations // The first step in an encrypted exchange, is to request an exchange of attestations
remoteAttestation = subFlow(ExchangeAttestationFlowHandler(otherSideSession)) subFlow(ExchangeAttestationFlowHandler(otherSideSession))
conclaveLedgerTxModel = otherSideSession.receive<ConclaveLedgerTxModel>().unwrap { it } encryptedTx = otherSideSession.receive<EncryptedTransaction>().unwrap { it }
} }
val stx = otherSideSession.receive<SignedTransaction>().unwrap { val stx = otherSideSession.receive<SignedTransaction>().unwrap {
@ -88,17 +99,13 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.") logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.")
checkParameterHash(it.networkParametersHash) checkParameterHash(it.networkParametersHash)
if (conclaveLedgerTxModel != null) { if (encryptedTx != null) {
require(conclaveLedgerTxModel.signedTransaction == it) { require(encryptedTx.id == it.id) {
"The supplied signed transaction and conclaveLedgerTxModel are different" "The supplied signed transaction and encrypted transactions are different"
} }
} }
require(remoteAttestation != null) { subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted))
"A remote attestation is required for encrypted mode"
}
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted, remoteAttestation = remoteAttestation!!))
logger.info("Transaction dependencies resolution completed.") logger.info("Transaction dependencies resolution completed.")
try { try {
@ -115,8 +122,8 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
validatedTxSvc.getEncryptedTransaction(validatedTxId) validatedTxSvc.getEncryptedTransaction(validatedTxId)
}.toSet() }.toSet()
val verifiableTx = VerifiableTxAndDependencies( val verifiableTx = EncryptedVerifiableTxAndDependencies(
conclaveLedgerTxModel!!, encryptedTx!!,
signedTxs, signedTxs,
encryptedTxs encryptedTxs
) )
@ -146,7 +153,7 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
serviceHub.recordTransactions(statesToRecord, setOf(stx)) serviceHub.recordTransactions(statesToRecord, setOf(stx))
logger.info("Successfully recorded received transaction locally.") logger.info("Successfully recorded received transaction locally.")
} }
return getReturnVal(stx, conclaveLedgerTxModel) return getReturnVal(stx, encryptedTx)
} }
/** /**

View File

@ -128,7 +128,8 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any,
// also send the ledger transaction // also send the ledger transaction
if (payload is SignedTransaction) { if (payload is SignedTransaction) {
val conclaveLedgerTxModel = payload.toLedgerTxModel(serviceHub, false) val conclaveLedgerTxModel = payload.toLedgerTxModel(serviceHub, false)
otherSideSession.send(conclaveLedgerTxModel) val encryptedTransaction = encryptSvc.encryptTransactionForRemote(runId.uuid, conclaveLedgerTxModel)
otherSideSession.send(encryptedTransaction)
} }
} }
// This loop will receive [FetchDataFlow.Request] continuously until the `otherSideSession` has all the data they need // This loop will receive [FetchDataFlow.Request] continuously until the `otherSideSession` has all the data they need

View File

@ -23,8 +23,7 @@ class ResolveTransactionsFlow private constructor(
val txHashes: Set<SecureHash>, val txHashes: Set<SecureHash>,
val otherSide: FlowSession, val otherSide: FlowSession,
val statesToRecord: StatesToRecord, val statesToRecord: StatesToRecord,
val encrypted: Boolean = false, val encrypted: Boolean = false
val remoteAttestation : ByteArray? = null
) : FlowLogic<Unit>() { ) : FlowLogic<Unit>() {
constructor(txHashes: Set<SecureHash>, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE) constructor(txHashes: Set<SecureHash>, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
@ -40,8 +39,8 @@ class ResolveTransactionsFlow private constructor(
: this(transaction, transaction.dependencies, otherSide, statesToRecord) : this(transaction, transaction.dependencies, otherSide, statesToRecord)
// TODO: PoC constructor // TODO: PoC constructor
constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE, encrypted: Boolean, remoteAttestation: ByteArray) constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE, encrypted: Boolean)
: this(transaction, transaction.dependencies, otherSide, statesToRecord, encrypted, remoteAttestation) : this(transaction, transaction.dependencies, otherSide, statesToRecord, encrypted)
private var fetchNetParamsFromCounterpart = false private var fetchNetParamsFromCounterpart = false

View File

@ -26,13 +26,8 @@ class EncryptedTransactionService(val enclaveClient: EnclaveClient = DummyEnclav
enclaveClient.registerRemoteEnclaveInstanceInfo(flowId, remoteAttestation) enclaveClient.registerRemoteEnclaveInstanceInfo(flowId, remoteAttestation)
} }
fun enclaveVerifyWithoutSignatures(txAndDependencies: VerifiableTxAndDependencies) { fun enclaveVerifyWithoutSignatures(encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies) {
return enclaveClient.enclaveVerifyWithoutSignatures(getCurrentFlowIdOrGenerateNewInvokeId(), encryptedTxAndDependencies)
return enclaveClient.enclaveVerifyWithoutSignatures(getCurrentFlowIdOrGenerateNewInvokeId(), txAndDependencies)
}
fun enclaveVerifyWithSignatures(txAndDependencies: VerifiableTxAndDependencies): EncryptedTransaction {
return enclaveClient.enclaveVerifyWithSignatures(getCurrentFlowIdOrGenerateNewInvokeId(), txAndDependencies)
} }
fun enclaveVerifyWithSignatures(encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies): EncryptedTransaction { fun enclaveVerifyWithSignatures(encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies): EncryptedTransaction {

View File

@ -225,10 +225,6 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
@Suspendable @Suspendable
private fun fetchEncryptedRequiredTransactions(requests: Set<SecureHash>): Pair<List<SecureHash>, List<EncryptedTransaction>> { private fun fetchEncryptedRequiredTransactions(requests: Set<SecureHash>): Pair<List<SecureHash>, List<EncryptedTransaction>> {
val remoteAttestation = flow.remoteAttestation ?:
throw IllegalStateException("fetchEncryptedRequiredTransactions requires a remoteAttestation")
val requestedTxs = flow.subFlow(FetchEncryptedTransactionsFlow(requests, flow.otherSide)) val requestedTxs = flow.subFlow(FetchEncryptedTransactionsFlow(requests, flow.otherSide))
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded) return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
} }