Add transaction verification

This commit is contained in:
lemjclarke 2022-02-25 15:27:38 +00:00
parent 633be8d631
commit b4946db9ef
7 changed files with 194 additions and 2 deletions

View File

@ -5,3 +5,5 @@ package com.r3.conclave.cordapp.common
* attestation contained in the mail body (requires an enclave host to be registered).
*/
class InitPostOfficeToRemoteEnclave : EnclaveCommand
class VerifyUnencryptedTx : EnclaveCommand

View File

@ -0,0 +1,21 @@
package com.r3.conclave.cordapp.common.dto
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
@CordaSerializable
data class ConclaveNetworkParameters(
val minimumPlatformVersion: Int,
val notaries: Array<NotaryInfo>,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
val epoch: Int,
val whitelistedContractImplementations: Array<Pair<String, Array<AttachmentId>>>,
val eventHorizon: Duration,
val packageOwnership: Array<Pair<String, PublicKey>>
)

View File

@ -0,0 +1,9 @@
package com.r3.conclave.cordapp.common.dto
import net.corda.core.serialization.CordaSerializable
@CordaSerializable
data class ConclaveVerificationResponse(
val verificationStatus: VerificationStatus,
val verificationErrorMessage: String?
)

View File

@ -0,0 +1,8 @@
package com.r3.conclave.cordapp.common.dto
import net.corda.core.serialization.CordaSerializable
@CordaSerializable
enum class VerificationStatus {
SUCCESS, VERIFICATION_ERROR
}

View File

@ -0,0 +1,18 @@
package com.r3.conclave.cordapp.common.dto
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.WireTransaction
@CordaSerializable
data class WireTxAdditionalInfo(
val wireTransaction: WireTransaction,
val inputStates: Array<StateAndRef<ContractState>>,
val attachments: Array<Attachment>,
val conclaveNetworkParameters: ConclaveNetworkParameters,
val references: Array<StateAndRef<ContractState>>,
val serializedInputs: Array<ByteArray>?,
val serializedReferences: Array<ByteArray>?
)

View File

@ -1,10 +1,22 @@
package com.r3.conclave.cordapp.sample.enclave
import com.github.benmanes.caffeine.cache.Caffeine
import com.r3.conclave.common.EnclaveInstanceInfo
import com.r3.conclave.cordapp.common.*
import com.r3.conclave.cordapp.common.dto.ConclaveVerificationResponse
import com.r3.conclave.cordapp.common.dto.VerificationStatus
import com.r3.conclave.cordapp.common.dto.WireTxAdditionalInfo
import com.r3.conclave.enclave.EnclavePostOffice
import com.r3.conclave.mail.EnclaveMail
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.lang.IllegalStateException
import java.security.Security
import java.util.*
/**
@ -15,9 +27,29 @@ class EncryptedTxEnclave : CommandEnclave() {
private lateinit var remoteEnclavePostOffice: EnclavePostOffice
private val commandToCallableMap = mapOf(
InitPostOfficeToRemoteEnclave::class.java.name to ::initPostOfficeToRemoteEnclave
InitPostOfficeToRemoteEnclave::class.java.name to ::initPostOfficeToRemoteEnclave,
VerifyUnencryptedTx::class.java.name to ::verifyUnencryptedTransaction
)
private val serializerFactoriesForContexts = Caffeine.newBuilder()
.maximumSize(128)
.build<SerializationFactoryCacheKey, SerializerFactory>()
.asMap()
private var serializationFactoryImpl: SerializationFactoryImpl
init {
Security.addProvider(BouncyCastleProvider())
val serverScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts)
val clientScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts)
serializationFactoryImpl = SerializationFactoryImpl().apply {
registerScheme(serverScheme)
registerScheme(clientScheme)
}
}
override fun receiveMail(mail: EnclaveMail, flowId: String, route: EnclaveCommand) {
if (commandToCallableMap[route.javaClass.name] != null) {
commandToCallableMap[route.javaClass.name]!!.invoke(mail, flowId)
@ -47,4 +79,39 @@ class EncryptedTxEnclave : CommandEnclave() {
remoteEnclavePostOffice = postOffice(attestation, flowId)
}
/**
* Convert the serialized [WireTxAdditionalInfo] in the message body to a [LedgerTransaction] and verify.
* This command replies with a serialized [ConclaveVerificationResponse] indicating if the verification was a
* success or threw an error.
* @param mail an incoming [EnclaveMail] containing a serialized [WireTxAdditionalInfo].
* @param flowId the ID of the flow executing on our host.
*/
private fun verifyUnencryptedTransaction(mail: EnclaveMail, flowId: String) {
val txBody = mail.bodyAsBytes
val wireTx = serializationFactoryImpl.deserialize(
byteSequence = ByteSequence.of(txBody, 0, txBody.size),
clazz = WireTxAdditionalInfo::class.java,
context = AMQP_P2P_CONTEXT)
val ledgerTx = LedgerTxHelper.toLedgerTxInternal(wireTx)
val response = serializationFactoryImpl.asCurrent {
this.withCurrentContext(AMQP_P2P_CONTEXT) {
try {
ledgerTx.verify()
ConclaveVerificationResponse(VerificationStatus.SUCCESS, null)
} catch (e: Exception) {
println("Exception while verifying transaction $e")
ConclaveVerificationResponse(VerificationStatus.VERIFICATION_ERROR, e.message)
}
}
}
val serializedResponse = serializationFactoryImpl.serialize(response, AMQP_P2P_CONTEXT).bytes
val responseBytes = postOffice(mail).encryptMail(serializedResponse)
postMail(responseBytes, "$flowId:${VerifyUnencryptedTx().serialize()}")
}
}

View File

@ -0,0 +1,67 @@
package com.r3.conclave.cordapp.sample.enclave
import com.r3.conclave.cordapp.common.dto.ConclaveNetworkParameters
import com.r3.conclave.cordapp.common.dto.WireTxAdditionalInfo
import net.corda.core.contracts.CommandWithParties
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.SerializedBytes
import net.corda.core.transactions.LedgerTransaction
class LedgerTxHelper {
companion object {
@JvmStatic
fun toLedgerTxInternal(conclaveLedgerTxModel: WireTxAdditionalInfo): LedgerTransaction {
val serializedResolvedInputs = conclaveLedgerTxModel.serializedInputs?.mapIndexed { index, ref ->
SerializedStateAndRef(SerializedBytes(ref), conclaveLedgerTxModel
.inputStates[index].ref)
}
val serializedResolvedReferences = conclaveLedgerTxModel.serializedReferences?.mapIndexed { index, ref ->
SerializedStateAndRef(SerializedBytes(ref), conclaveLedgerTxModel.references[index].ref)
}
return LedgerTransaction.createForConclaveVerify(
conclaveLedgerTxModel.inputStates.toList(),
conclaveLedgerTxModel.wireTransaction.outputs,
conclaveLedgerTxModel.wireTransaction.commands.map {
CommandWithParties(it.signers, emptyList(), it.value)
},
conclaveLedgerTxModel.attachments.toList(),
conclaveLedgerTxModel.wireTransaction.id,
conclaveLedgerTxModel.wireTransaction.notary,
conclaveLedgerTxModel.wireTransaction.timeWindow,
conclaveLedgerTxModel.wireTransaction.privacySalt,
toNetworkParameters(conclaveLedgerTxModel.conclaveNetworkParameters),
conclaveLedgerTxModel.references.toList(),
conclaveLedgerTxModel.wireTransaction.componentGroups,
serializedResolvedInputs,
serializedResolvedReferences,
conclaveLedgerTxModel.wireTransaction.digestService
)
}
@JvmStatic
fun toNetworkParameters(conclaveNetworkParameters: ConclaveNetworkParameters): NetworkParameters {
return NetworkParameters(
conclaveNetworkParameters.minimumPlatformVersion,
conclaveNetworkParameters.notaries.toList(),
conclaveNetworkParameters.maxMessageSize,
conclaveNetworkParameters.maxTransactionSize,
conclaveNetworkParameters.modifiedTime,
conclaveNetworkParameters.epoch,
conclaveNetworkParameters.whitelistedContractImplementations.map {
it.first to it.second.asList()
}.toMap(),
conclaveNetworkParameters.eventHorizon,
conclaveNetworkParameters.packageOwnership.toMap()
)
}
}
}