Merge branch 'cbdc/conclave-corda-int' into cbdc/poc-encrypt-backchain-merge

# Conflicts:
#	core/src/main/kotlin/net/corda/core/transactions/EncryptedTransaction.kt
#	settings.gradle
This commit is contained in:
adam.houston 2022-03-10 14:11:01 +00:00
commit 525dc6022b
8 changed files with 346 additions and 56 deletions

View File

@ -0,0 +1,47 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
cordapp {
targetPlatformVersion 9
minimumPlatformVersion 9
workflow {
name "CorDapp with enclave flows"
vendor "R3"
licence "Apache License, Version 2.0"
versionId 1
}
signing {
enabled false
}
}
dependencies {
compile group: "org.jetbrains.kotlin", name: "kotlin-stdlib-jdk8", version: kotlin_version
compile group: "org.jetbrains.kotlin", name: "kotlin-reflect", version: kotlin_version
compile group: "com.typesafe", name: "config", version: typesafe_config_version
compile project(":core")
compile project(":node-api")
compile project(":serialization")
// test dependencies
testImplementation "junit:junit:$junit_version"
testCompile group: "org.jetbrains.kotlin", name: "kotlin-test", version: kotlin_version
testCompile project(":node-driver")
testCompile project(":finance")
}
jar {
baseName 'corda-common-mock-enclave'
}
publish {
name jar.baseName
}

View File

@ -0,0 +1,53 @@
package com.r3.conclave.encryptedtx.dto
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
/**
* Enclave representation of a signed and ledger transaction.
* ConclaveLedgerTxModel wraps a [SignedTransaction] and additional properties to allow an enclave to reconstruct and
* verify a ledger transaction.
* @property signedTransaction a serializable transaction with signatures.
* @property inputStates an array of input states that will be consumed by the wrapped transaction.
* @property attachments an array of attachment objects that are required for this transaction to verify.
* @property networkParameters the network parameters that were in force when the enclosed wire transaction was
* constructed.
* @property references an array of reference states.
*/
@CordaSerializable
data class ConclaveLedgerTxModel(
val signedTransaction: SignedTransaction,
val inputStates: Array<StateAndRef<ContractState>>,
val attachments: Array<Attachment>,
val networkParameters: NetworkParameters,
val references: Array<StateAndRef<ContractState>>
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ConclaveLedgerTxModel
if (signedTransaction != other.signedTransaction) return false
if (!inputStates.contentEquals(other.inputStates)) return false
if (!attachments.contentEquals(other.attachments)) return false
if (networkParameters != other.networkParameters) return false
if (!references.contentEquals(other.references)) return false
return true
}
override fun hashCode(): Int {
var result = signedTransaction.hashCode()
result = 31 * result + inputStates.contentHashCode()
result = 31 * result + attachments.contentHashCode()
result = 31 * result + networkParameters.hashCode()
result = 31 * result + references.contentHashCode()
return result
}
}

View File

@ -0,0 +1,10 @@
package com.r3.conclave.encryptedtx.dto
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.EncryptedTransaction
@CordaSerializable
data class VerifiableTxAndDependencies(
val conclaveLedgerTxModel: ConclaveLedgerTxModel,
val dependencies: Set<EncryptedTransaction>
)

View File

@ -0,0 +1,93 @@
package com.r3.conclave.encryptedtx.enclave
import com.github.benmanes.caffeine.cache.Caffeine
import com.r3.conclave.encryptedtx.dto.ConclaveLedgerTxModel
import com.r3.conclave.encryptedtx.dto.VerifiableTxAndDependencies
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.sign
import net.corda.core.internal.dependencies
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction
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
class EncryptedTxEnclave {
private val enclaveKeyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
private val signatureMetadata = SignatureMetadata(
platformVersion = 1,
schemeNumberID = Crypto.findSignatureScheme(enclaveKeyPair.public).schemeNumberID
)
private val serializerFactoriesForContexts = Caffeine.newBuilder()
.maximumSize(128)
.build<SerializationFactoryCacheKey, SerializerFactory>()
.asMap()
private val serializationFactoryImpl: SerializationFactoryImpl
init {
val serverScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts)
val clientScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts)
serializationFactoryImpl = SerializationFactoryImpl().apply {
registerScheme(serverScheme)
registerScheme(clientScheme)
}
}
fun encryptSignedTx(ledgerTxBytes: ByteArray): EncryptedTransaction {
val ledgerTxModel = serializationFactoryImpl.deserialize(
byteSequence = ByteSequence.of(ledgerTxBytes, 0, ledgerTxBytes.size),
clazz = ConclaveLedgerTxModel::class.java,
context = AMQP_P2P_CONTEXT
)
val signedTransaction = ledgerTxModel.signedTransaction
val signableData = SignableData(signedTransaction.id, signatureMetadata)
val transactionSignature = enclaveKeyPair.sign(signableData)
return EncryptedTransaction(
id = signedTransaction.id,
encryptedBytes = ledgerTxBytes,
dependencies = signedTransaction.dependencies,
sigs = listOf(transactionSignature)
)
}
fun verifyTx(txAndDependenciesBytes: ByteArray) {
val txAndDependencies = serializationFactoryImpl.deserialize(
byteSequence = ByteSequence.of(txAndDependenciesBytes, 0, txAndDependenciesBytes.size),
clazz = VerifiableTxAndDependencies::class.java,
context = AMQP_P2P_CONTEXT)
val signedTransaction = txAndDependencies.conclaveLedgerTxModel.signedTransaction
signedTransaction.verifyRequiredSignatures()
val dependencies = decryptDependencies(txAndDependencies.dependencies)
dependencies.forEach {
it.verifyRequiredSignatures()
}
val ledgerTransaction = LedgerTxHelper.toLedgerTxInternal(txAndDependencies.conclaveLedgerTxModel, dependencies)
ledgerTransaction.verify()
}
private fun decryptDependencies(dependencies: Set<EncryptedTransaction>): Set<SignedTransaction> {
// simply deserialize for this "mock enclave"
return dependencies.map {
val conclaveLedgerTxModel = serializationFactoryImpl.deserialize(
byteSequence = ByteSequence.of(it.encryptedBytes, 0, it.encryptedBytes.size),
clazz = ConclaveLedgerTxModel::class.java,
context = AMQP_P2P_CONTEXT)
conclaveLedgerTxModel.signedTransaction
}.toSet()
}
}

View File

@ -0,0 +1,7 @@
package com.r3.conclave.encryptedtx.internal
import com.r3.conclave.encryptedtx.dto.ConclaveLedgerTxModel
import net.corda.core.crypto.SecureHash
val ConclaveLedgerTxModel.dependencies: Set<SecureHash>
get() = (inputStates.asSequence() + references.asSequence()).map { it.ref.txhash }.toSet()

View File

@ -0,0 +1,110 @@
package com.r3.conclave.encryptedtx
import com.github.benmanes.caffeine.cache.Caffeine
import com.r3.conclave.encryptedtx.dto.ConclaveLedgerTxModel
import com.r3.conclave.encryptedtx.dto.VerifiableTxAndDependencies
import com.r3.conclave.encryptedtx.enclave.EncryptedTxEnclave
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.testing.core.*
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import org.junit.After
import org.junit.Before
import org.junit.Test
class EncryptedTxEnclaveTest {
private lateinit var mockNet: MockNetwork
private val initialBalance = 2000.DOLLARS
private val ref = OpaqueBytes.of(0x01)
private lateinit var bankOfCordaNode: StartedMockNode
private lateinit var bankOfCorda: Party
private lateinit var aliceNode: StartedMockNode
private lateinit var bobNode: StartedMockNode
private val serializerFactoriesForContexts = Caffeine.newBuilder()
.maximumSize(128)
.build<SerializationFactoryCacheKey, SerializerFactory>()
.asMap()
private lateinit var serializationFactoryImpl: SerializationFactoryImpl
private val encryptedTxEnclave = EncryptedTxEnclave()
@Before
fun start() {
mockNet = MockNetwork(MockNetworkParameters(servicePeerAllocationStrategy = RoundRobin(), cordappsForAllNodes = FINANCE_CORDAPPS))
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
val serverScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts)
val clientScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts)
serializationFactoryImpl = SerializationFactoryImpl().apply {
registerScheme(serverScheme)
registerScheme(clientScheme)
}
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
@Test(timeout=300_000)
fun `pay some cash`() {
val payTo = aliceNode.info.singleIdentity()
val expectedPayment = 500.DOLLARS
var future = bankOfCordaNode.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity))
val issuanceStx = future.getOrThrow().stx
val issuanceConclaveLedgerTxBytes = issuanceStx
.toLedgerTxModel(bankOfCordaNode.services)
.serialize(serializationFactoryImpl)
.bytes
val encryptedTx = encryptedTxEnclave.encryptSignedTx(issuanceConclaveLedgerTxBytes)
future = bankOfCordaNode.startFlow(CashPaymentFlow(expectedPayment, payTo))
mockNet.runNetwork()
val paymentStx = future.getOrThrow().stx
val ledgerTxModel = paymentStx.toLedgerTxModel(bankOfCordaNode.services)
val txAndDependenciesBytes = VerifiableTxAndDependencies(ledgerTxModel, setOf(encryptedTx)).serialize()
encryptedTxEnclave.verifyTx(txAndDependenciesBytes.bytes)
}
private fun SignedTransaction.toLedgerTxModel(services: ServiceHub): ConclaveLedgerTxModel {
val ledgerTx = this.toLedgerTransaction(services)
return ConclaveLedgerTxModel(
signedTransaction = this,
inputStates = ledgerTx.inputs.toTypedArray(),
attachments = ledgerTx.attachments.toTypedArray(),
networkParameters = ledgerTx.networkParameters!!,
references = ledgerTx.references.toTypedArray()
)
}
}

View File

@ -1,20 +1,29 @@
package net.corda.core.transactions
import net.corda.core.contracts.NamedByHash
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.serialization.CordaSerializable
import java.security.PublicKey
/**
* EncryptedTransaction wraps a serialized and encrypted enclave representation of a ledger transaction (a wire
* transaction with inputs, references, attachments and network parameters).
* @property id the hash of the [WireTransaction] Merkle tree root.
* @property encryptedBytes the serialized and encrypted enclave ledger tx.
* @property dependencies a set of transaction hashes this transaction depends on.
* @property sigs a list of signatures from individual public keys.
*/
@CordaSerializable
data class EncryptedTransaction (
override val id : SecureHash,
val bytes : ByteArray
// TODO: will need to also store the signature of who verified this tx
) : NamedByHash{
data class EncryptedTransaction(
override val id: SecureHash,
val encryptedBytes: ByteArray,
val dependencies: Set<SecureHash>,
override val sigs: List<TransactionSignature>
) : TransactionWithSignatures {
fun toVerified(verifierSignature: ByteArray) : VerifiedEncryptedTransaction {
return VerifiedEncryptedTransaction(this, verifierSignature)
}
override val requiredSigningKeys: Set<PublicKey> = emptySet()
override fun getKeyDescriptions(keys: Set<PublicKey>): List<String> = emptyList()
override fun equals(other: Any?): Boolean {
if (this === other) return true
@ -23,44 +32,16 @@ data class EncryptedTransaction (
other as EncryptedTransaction
if (id != other.id) return false
if (!bytes.contentEquals(other.bytes)) return false
if (!encryptedBytes.contentEquals(other.encryptedBytes)) return false
if (sigs != other.sigs) return false
return true
}
override fun hashCode(): Int {
return 31 * (id.hashCode() + bytes.contentHashCode())
}
}
@CordaSerializable
data class VerifiedEncryptedTransaction (
val encryptedTransaction: EncryptedTransaction,
val verifierSignature: ByteArray
) : NamedByHash {
constructor(id : SecureHash, bytes : ByteArray, verifierSignature: ByteArray) :
this(EncryptedTransaction(id, bytes), verifierSignature)
override val id: SecureHash
get() = encryptedTransaction.id
val bytes: ByteArray
get() = encryptedTransaction.bytes
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as VerifiedEncryptedTransaction
if (encryptedTransaction != other.encryptedTransaction) return false
if (!verifierSignature.contentEquals(other.verifierSignature)) return false
return true
}
override fun hashCode(): Int {
return 31 * (encryptedTransaction.hashCode() + verifierSignature.contentHashCode())
var result = id.hashCode()
result = 31 * result + encryptedBytes.contentHashCode()
result = 31 * result + sigs.hashCode()
return result
}
}

View File

@ -22,16 +22,8 @@ pluginManagement {
mavenLocal()
gradlePluginPortal()
maven { url "${artifactory_contextUrl}/corda-dependencies" }
maven { url "$settingsDir/repo" }
}
}
plugins {
println("CONCLAVE VERSION: $conclaveVersion")
// TODO: fix conclaveVersion property
// (none of the conclave properties seem to be sourced from gradle.properties, they appear to be coming from somewhere else)
id 'com.r3.conclave.enclave' version '1.1' apply false
}
}
// The project is named 'corda-project' and not 'corda' because if this is named the same as the
// output JAR from the capsule then the buildCordaJAR task goes into an infinite loop.
@ -110,9 +102,6 @@ include 'testing:cordapps:dbfailure:dbfworkflows'
include 'testing:cordapps:missingmigration'
include 'testing:cordapps:sleeping'
// Conclave
include 'enclave'
// Common libraries - start
include 'common-validation'
project(":common-validation").projectDir = new File("$settingsDir/common/validation")
@ -124,8 +113,8 @@ project(":common-configuration-parsing").projectDir = new File("$settingsDir/com
include 'common-logging'
project(":common-logging").projectDir = new File("$settingsDir/common/logging")
include 'common-enclave'
project(":common-enclave").projectDir = new File("$settingsDir/common/enclave")
include 'common-mock-enclave'
project(":common-mock-enclave").projectDir = new File("$settingsDir/common/mock-enclave")
// Common libraries - end
apply from: 'buildCacheSettings.gradle'