mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
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:
commit
525dc6022b
47
common/mock-enclave/build.gradle
Normal file
47
common/mock-enclave/build.gradle
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user