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) {
verifyTx(txAndDependencies, false)
override fun enclaveVerifyWithoutSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies) {
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 {
val decrypted = decrypt(encryptedTxAndDependencies.encryptedTransaction)

View File

@ -42,37 +42,19 @@ interface EnclaveClient {
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
* 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
* 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::class)
fun enclaveVerifyWithoutSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies)
/**
* 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
fun enclaveVerifyWithoutSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies)
/**
* 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")
}
override fun enclaveVerifyWithoutSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies) {
throw UnsupportedOperationException("Add your custom enclave client implementation")
}
override fun enclaveVerifyWithSignatures(invokeId: UUID, txAndDependencies: VerifiableTxAndDependencies): EncryptedTransaction {
override fun enclaveVerifyWithoutSignatures(invokeId: UUID, encryptedTxAndDependencies: EncryptedVerifiableTxAndDependencies) {
throw UnsupportedOperationException("Add your custom enclave client implementation")
}

View File

@ -1,8 +1,8 @@
package net.corda.core.flows
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.EncryptedVerifiableTxAndDependencies
import net.corda.core.conclave.common.dto.VerifiableTxAndDependencies
import net.corda.core.crypto.TransactionSignature
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.internal.dependencies
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker
@ -279,11 +280,12 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
progressTracker.currentStep = RECEIVING
// 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) {
conclaveLedgerTxModel = subFlow(ReceiveTransactionAsConclaveModelFlow(otherSideSession, checkSufficientSignatures = false, encrypted = true))
conclaveLedgerTxModel.signedTransaction
val stxAndEncrypted = subFlow(ReceiveTransactionWithEncryptedFlow(otherSideSession, checkSufficientSignatures = false))
receivedEncryptedTx = stxAndEncrypted.encryptedTransaction
stxAndEncrypted.signedTransaction
} else {
subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false, encrypted = true))
}
@ -304,6 +306,10 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
val encryptionService = serviceHub.encryptedTransactionService
val validatedTxSvc = serviceHub.validatedTransactions
val usableReceivedTx = receivedEncryptedTx ?: throw IllegalStateException("An encrypted transaction is required")
val locallyEncryptedTransaction = encryptionService.encryptTransactionForLocal(usableReceivedTx)
val encryptedTxs = stx.dependencies.mapNotNull {
validatedTxSvc.getEncryptedTransaction(it)
}.toSet()
@ -313,8 +319,8 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
}.toSet()
encryptionService.enclaveVerifyWithoutSignatures(
VerifiableTxAndDependencies(
conclaveLedgerTxModel!!,
EncryptedVerifiableTxAndDependencies(
locallyEncryptedTransaction,
signedTxs,
encryptedTxs
)

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
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.contracts.*
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.pushToLoggingContext
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.utilities.trace
import net.corda.core.utilities.unwrap
@ -38,19 +41,28 @@ open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSid
private val encrypted : Boolean = false) :
ReceiveTransactionFlowBase<SignedTransaction>(otherSideSession, checkSufficientSignatures, statesToRecord, encrypted) {
override fun getReturnVal(stx: SignedTransaction, conclaveLedgerTxModel: ConclaveLedgerTxModel?): SignedTransaction {
override fun getReturnVal(stx: SignedTransaction, encryptedTransaction: EncryptedTransaction?): SignedTransaction {
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 {
return conclaveLedgerTxModel ?: throw IllegalStateException("Cannot return a null ConclaveLedgerTxModel")
@CordaSerializable
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>() {
@Suspendable
abstract fun getReturnVal(stx: SignedTransaction, conclaveLedgerTxModel: ConclaveLedgerTxModel?) : T
abstract fun getReturnVal(stx: SignedTransaction, encryptedTransaction: EncryptedTransaction?) : T
@Suppress("KDocMissingDocumentation")
@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}" }
}
var conclaveLedgerTxModel : ConclaveLedgerTxModel? = null
var remoteAttestation : ByteArray? = null
var encryptedTx : EncryptedTransaction? = null
if (encrypted) {
// The first step in an encrypted exchange, is to request an exchange of attestations
remoteAttestation = subFlow(ExchangeAttestationFlowHandler(otherSideSession))
conclaveLedgerTxModel = otherSideSession.receive<ConclaveLedgerTxModel>().unwrap { it }
subFlow(ExchangeAttestationFlowHandler(otherSideSession))
encryptedTx = otherSideSession.receive<EncryptedTransaction>().unwrap { it }
}
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}.")
checkParameterHash(it.networkParametersHash)
if (conclaveLedgerTxModel != null) {
require(conclaveLedgerTxModel.signedTransaction == it) {
"The supplied signed transaction and conclaveLedgerTxModel are different"
if (encryptedTx != null) {
require(encryptedTx.id == it.id) {
"The supplied signed transaction and encrypted transactions are different"
}
}
require(remoteAttestation != null) {
"A remote attestation is required for encrypted mode"
}
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted, remoteAttestation = remoteAttestation!!))
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted))
logger.info("Transaction dependencies resolution completed.")
try {
@ -115,8 +122,8 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
validatedTxSvc.getEncryptedTransaction(validatedTxId)
}.toSet()
val verifiableTx = VerifiableTxAndDependencies(
conclaveLedgerTxModel!!,
val verifiableTx = EncryptedVerifiableTxAndDependencies(
encryptedTx!!,
signedTxs,
encryptedTxs
)
@ -146,7 +153,7 @@ abstract class ReceiveTransactionFlowBase<T> @JvmOverloads constructor(private v
serviceHub.recordTransactions(statesToRecord, setOf(stx))
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
if (payload is SignedTransaction) {
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

View File

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

View File

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

View File

@ -225,10 +225,6 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
@Suspendable
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))
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
}