Example of encrypted backchain - WIP for investigation purposes

This commit is contained in:
adam.houston 2022-02-18 16:24:37 +00:00
parent 0a21fda87c
commit a165c69d3a
27 changed files with 900 additions and 45 deletions

View File

@ -9,4 +9,4 @@ package net.corda.common.logging
* (originally added to source control for ease of use) * (originally added to source control for ease of use)
*/ */
internal const val CURRENT_MAJOR_RELEASE = "4.8.5" internal const val CURRENT_MAJOR_RELEASE = "4.8.5"

View File

@ -21,7 +21,7 @@ jdkClassifier11=jdk11
dockerJavaVersion=3.2.5 dockerJavaVersion=3.2.5
proguardVersion=6.1.1 proguardVersion=6.1.1
bouncycastleVersion=1.68 bouncycastleVersion=1.68
classgraphVersion=4.8.90 classgraphVersion=4.8.135
disruptorVersion=3.4.2 disruptorVersion=3.4.2
typesafeConfigVersion=1.3.4 typesafeConfigVersion=1.3.4
jsr305Version=3.0.2 jsr305Version=3.0.2

View File

@ -180,7 +180,8 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
oldV3Broadcast(notarised, oldParticipants.toSet()) oldV3Broadcast(notarised, oldParticipants.toSet())
for (session in sessions) { for (session in sessions) {
try { try {
subFlow(SendTransactionFlow(session, notarised)) // PoC send encrypted
subFlow(SendTransactionFlow(session, notarised, true))
logger.info("Party ${session.counterparty} received the transaction.") logger.info("Party ${session.counterparty} received the transaction.")
} catch (e: UnexpectedFlowEndException) { } catch (e: UnexpectedFlowEndException) {
throw UnexpectedFlowEndException( throw UnexpectedFlowEndException(
@ -282,7 +283,7 @@ class ReceiveFinalityFlow @JvmOverloads constructor(private val otherSideSession
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() { private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() {
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
return subFlow(object : ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = true, statesToRecord = statesToRecord) { return subFlow(object : ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = true, statesToRecord = statesToRecord, encrypted = true) {
override fun checkBeforeRecording(stx: SignedTransaction) { override fun checkBeforeRecording(stx: SignedTransaction) {
require(expectedTxId == null || expectedTxId == stx.id) { require(expectedTxId == null || expectedTxId == stx.id) {
"We expected to receive transaction with ID $expectedTxId but instead got ${stx.id}. Transaction was" + "We expected to receive transaction with ID $expectedTxId but instead got ${stx.id}. Transaction was" +

View File

@ -4,8 +4,10 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.checkParameterHash import net.corda.core.internal.checkParameterHash
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.transactions.RawDependency
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
@ -30,7 +32,8 @@ import java.security.SignatureException
*/ */
open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession, open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
private val checkSufficientSignatures: Boolean = true, private val checkSufficientSignatures: Boolean = true,
private val statesToRecord: StatesToRecord = StatesToRecord.NONE) : FlowLogic<SignedTransaction>() { private val statesToRecord: StatesToRecord = StatesToRecord.NONE,
private val encrypted : Boolean = false) : FlowLogic<SignedTransaction>() {
@Suppress("KDocMissingDocumentation") @Suppress("KDocMissingDocumentation")
@Suspendable @Suspendable
@Throws(SignatureException::class, @Throws(SignatureException::class,
@ -47,11 +50,59 @@ open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSid
it.pushToLoggingContext() it.pushToLoggingContext()
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)
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord))
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted))
logger.info("Transaction dependencies resolution completed.") logger.info("Transaction dependencies resolution completed.")
try { try {
it.verify(serviceHub, checkSufficientSignatures) if (encrypted) {
it
val validatedTxSvc = serviceHub.validatedTransactions
val encryptedTxs = it.dependencies.mapNotNull {
validatedTxId ->
validatedTxSvc.getEncryptedTransaction(validatedTxId)?.let { etx ->
etx.id to etx
}
}.toMap()
val signedTxs = it.dependencies.mapNotNull {
validatedTxId ->
validatedTxSvc.getTransaction(validatedTxId)?.let { stx ->
stx.id to stx
}
}.toMap()
val networkParameters = it.dependencies.mapNotNull { depTxId ->
val npHash = when {
encryptedTxs[depTxId] != null -> serviceHub.encryptedTransactionService.getNetworkParameterHash(encryptedTxs[depTxId]!!)
?: serviceHub.networkParametersService.defaultHash
signedTxs[depTxId] != null -> signedTxs[depTxId]!!.networkParametersHash
?: serviceHub.networkParametersService.defaultHash
else -> null
}
npHash?.let { depTxId to npHash }
}.associate {
netParams ->
netParams.first to serviceHub.networkParametersService.lookup(netParams.second)
}
val rawDependencies = it.dependencies.associate {
txId ->
txId to RawDependency(
encryptedTxs[txId],
signedTxs[txId],
networkParameters[txId]
)
}
serviceHub.encryptedTransactionService.verifyTransaction(it, serviceHub, checkSufficientSignatures, rawDependencies)
it
} else {
it.verify(serviceHub, checkSufficientSignatures)
it
}
} catch (e: Exception) { } catch (e: Exception) {
logger.warn("Transaction verification failed.") logger.warn("Transaction verification failed.")
throw e throw e

View File

@ -67,7 +67,7 @@ class MaybeSerializedSignedTransaction(override val id: SecureHash, val serializ
* @param otherSide the target party. * @param otherSide the target party.
* @param stx the [SignedTransaction] being sent to the [otherSideSession]. * @param stx the [SignedTransaction] being sent to the [otherSideSession].
*/ */
open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : DataVendingFlow(otherSide, stx) open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction, override val encrypted: Boolean = false) : DataVendingFlow(otherSide, stx, encrypted)
/** /**
* The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify * The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify
@ -80,7 +80,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
*/ */
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs) open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() { open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any, open val encrypted: Boolean = false) : FlowLogic<Void?>() {
@Suspendable @Suspendable
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload) protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
@ -91,6 +91,9 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
val encryptSvc = serviceHub.encryptedTransactionService
val networkMaxMessageSize = serviceHub.networkParameters.maxMessageSize val networkMaxMessageSize = serviceHub.networkParameters.maxMessageSize
val maxPayloadSize = networkMaxMessageSize / 2 val maxPayloadSize = networkMaxMessageSize / 2
@ -146,20 +149,48 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
var numSent = 0 var numSent = 0
payload = when (dataRequest.dataType) { payload = when (dataRequest.dataType) {
FetchDataFlow.DataType.TRANSACTION -> dataRequest.hashes.map { txId -> FetchDataFlow.DataType.TRANSACTION -> dataRequest.hashes.map { txId ->
logger.trace { "Sending: TRANSACTION (dataRequest.hashes.size=${dataRequest.hashes.size})" }
logger.trace { "Sending: TRANSACTION (dataRequest.hashes.size=${dataRequest.hashes.size}), encrypted: $encrypted" }
if (!authorisedTransactions.isAuthorised(txId)) { if (!authorisedTransactions.isAuthorised(txId)) {
throw FetchDataFlow.IllegalTransactionRequest(txId) throw FetchDataFlow.IllegalTransactionRequest(txId)
} }
val tx = serviceHub.validatedTransactions.getTransaction(txId)
?: throw FetchDataFlow.HashNotFound(txId) if (encrypted) {
authorisedTransactions.removeAuthorised(tx.id) var encryptedTx = serviceHub.validatedTransactions.getEncryptedTransaction(txId)
authorisedTransactions.addAuthorised(getInputTransactions(tx))
totalByteCount += tx.txBits.size if (encryptedTx == null) {
numSent++ val tx = serviceHub.validatedTransactions.getTransaction(txId)
tx ?: throw FetchDataFlow.HashNotFound(txId)
encryptedTx = encryptSvc.encryptTransaction(tx)
}
authorisedTransactions.removeAuthorised(encryptedTx.id)
authorisedTransactions.addAuthorised(encryptSvc.getDependencies(encryptedTx))
totalByteCount += encryptedTx.bytes.size
numSent++
encryptedTx
} else {
val tx = serviceHub.validatedTransactions.getTransaction(txId)
?: throw FetchDataFlow.HashNotFound(txId)
authorisedTransactions.removeAuthorised(tx.id)
authorisedTransactions.addAuthorised(getInputTransactions(tx))
totalByteCount += tx.txBits.size
numSent++
tx
}
} }
// Loop on all items returned using dataRequest.hashes.map: // Loop on all items returned using dataRequest.hashes.map:
FetchDataFlow.DataType.BATCH_TRANSACTION -> dataRequest.hashes.map { txId -> FetchDataFlow.DataType.BATCH_TRANSACTION -> dataRequest.hashes.map { txId ->
// TODO: adding this for PoC to ensure that we don't fall into this
if (encrypted) {
throw FlowException("Batch mode disabled for encryption PoC")
}
if (!authorisedTransactions.isAuthorised(txId)) { if (!authorisedTransactions.isAuthorised(txId)) {
throw FetchDataFlow.IllegalTransactionRequest(txId) throw FetchDataFlow.IllegalTransactionRequest(txId)
} }

View File

@ -18,6 +18,7 @@ import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.SerializationToken import net.corda.core.serialization.SerializationToken
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializeAsTokenContext import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
@ -273,6 +274,12 @@ class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid) override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)
} }
class FetchEncryptedTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
FetchDataFlow<EncryptedTransaction, EncryptedTransaction>(requests, otherSide, DataType.TRANSACTION) {
override fun load(txid: SecureHash): EncryptedTransaction? = serviceHub.validatedTransactions.getEncryptedTransaction(txid)
}
class FetchBatchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) : class FetchBatchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
FetchDataFlow<MaybeSerializedSignedTransaction, MaybeSerializedSignedTransaction>(requests, otherSide, DataType.BATCH_TRANSACTION) { FetchDataFlow<MaybeSerializedSignedTransaction, MaybeSerializedSignedTransaction>(requests, otherSide, DataType.BATCH_TRANSACTION) {

View File

@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.ContractUpgradeWireTransaction
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.debug import net.corda.core.utilities.debug
@ -21,7 +22,8 @@ class ResolveTransactionsFlow private constructor(
val initialTx: SignedTransaction?, val initialTx: SignedTransaction?,
val txHashes: Set<SecureHash>, val txHashes: Set<SecureHash>,
val otherSide: FlowSession, val otherSide: FlowSession,
val statesToRecord: StatesToRecord val statesToRecord: StatesToRecord,
val encrypted: Boolean = false
) : FlowLogic<Unit>() { ) : FlowLogic<Unit>() {
constructor(txHashes: Set<SecureHash>, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE) constructor(txHashes: Set<SecureHash>, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
@ -36,6 +38,10 @@ class ResolveTransactionsFlow private constructor(
constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE) constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
: this(transaction, transaction.dependencies, otherSide, statesToRecord) : this(transaction, transaction.dependencies, otherSide, statesToRecord)
// TODO: PoC constructor
constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE, encrypted: Boolean)
: this(transaction, transaction.dependencies, otherSide, statesToRecord, encrypted)
private var fetchNetParamsFromCounterpart = false private var fetchNetParamsFromCounterpart = false
@Suppress("MagicNumber") @Suppress("MagicNumber")
@ -49,16 +55,25 @@ class ResolveTransactionsFlow private constructor(
// Fetch missing parameters flow was added in version 4. This check is needed so we don't end up with node V4 sending parameters // Fetch missing parameters flow was added in version 4. This check is needed so we don't end up with node V4 sending parameters
// request to node V3 that doesn't know about this protocol. // request to node V3 that doesn't know about this protocol.
fetchNetParamsFromCounterpart = counterpartyPlatformVersion >= PlatformVersionSwitches.FETCH_MISSING_NETWORK_PARAMETERS fetchNetParamsFromCounterpart = counterpartyPlatformVersion >= PlatformVersionSwitches.FETCH_MISSING_NETWORK_PARAMETERS
val batchMode = counterpartyPlatformVersion >= PlatformVersionSwitches.BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN
// disable batch mode for encrypted
val batchMode = counterpartyPlatformVersion >= PlatformVersionSwitches.BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN && !encrypted
logger.debug { "ResolveTransactionsFlow.call(): Otherside Platform Version = '$counterpartyPlatformVersion': Batch mode = $batchMode" } logger.debug { "ResolveTransactionsFlow.call(): Otherside Platform Version = '$counterpartyPlatformVersion': Batch mode = $batchMode" }
// TODO: attachments and net params are not encrypted (yet)
if (initialTx != null) { if (initialTx != null) {
fetchMissingAttachments(initialTx) fetchMissingAttachments(initialTx)
fetchMissingNetworkParameters(initialTx) fetchMissingNetworkParameters(initialTx)
} }
val resolver = (serviceHub as ServiceHubCoreInternal).createTransactionsResolver(this) val resolver = (serviceHub as ServiceHubCoreInternal).createTransactionsResolver(this)
resolver.downloadDependencies(batchMode)
if (encrypted) {
resolver.downloadEncryptedDependencies()
} else {
resolver.downloadDependencies(batchMode)
}
logger.trace { "ResolveTransactionsFlow: Sending END." } logger.trace { "ResolveTransactionsFlow: Sending END." }
otherSide.send(FetchDataFlow.Request.End) // Finish fetching data. otherSide.send(FetchDataFlow.Request.End) // Finish fetching data.
@ -66,7 +81,12 @@ class ResolveTransactionsFlow private constructor(
// If transaction resolution is performed for a transaction where some states are relevant, then those should be // If transaction resolution is performed for a transaction where some states are relevant, then those should be
// recorded if this has not already occurred. // recorded if this has not already occurred.
val usedStatesToRecord = if (statesToRecord == StatesToRecord.NONE) StatesToRecord.ONLY_RELEVANT else statesToRecord val usedStatesToRecord = if (statesToRecord == StatesToRecord.NONE) StatesToRecord.ONLY_RELEVANT else statesToRecord
resolver.recordDependencies(usedStatesToRecord)
if (encrypted) {
resolver.recordEncryptedDependencies(usedStatesToRecord)
} else {
resolver.recordDependencies(usedStatesToRecord)
}
} }
/** /**
@ -108,4 +128,25 @@ class ResolveTransactionsFlow private constructor(
false false
} }
} }
// PoC variants.
// TODO: no support for contract upgrade!
@Suspendable
fun fetchMissingAttachments(transaction: EncryptedTransaction): Boolean {
val attachmentIds = serviceHub.encryptedTransactionService.getAttachmentIds(transaction)
val downloads = subFlow(FetchAttachmentsFlow(attachmentIds, otherSide)).downloaded
return (downloads.isNotEmpty())
}
@Suspendable
fun fetchMissingNetworkParameters(transaction: EncryptedTransaction): Boolean {
return if (fetchNetParamsFromCounterpart) {
serviceHub.encryptedTransactionService.getNetworkParameterHash(transaction)?.let {
val downloads = subFlow(FetchNetworkParametersFlow(setOf(it), otherSide)).downloaded
downloads.isNotEmpty()
} ?: false
} else {
false
}
}
} }

View File

@ -32,4 +32,12 @@ interface TransactionsResolver {
@Suspendable @Suspendable
fun recordDependencies(usedStatesToRecord: StatesToRecord) fun recordDependencies(usedStatesToRecord: StatesToRecord)
// for Poc we will create a completely parallel set of functions, perhaps a different implementation of TransactionsResolver is
// preferable long term
@Suspendable
fun downloadEncryptedDependencies()
@Suspendable
fun recordEncryptedDependencies(usedStatesToRecord: StatesToRecord)
} }

View File

@ -438,3 +438,7 @@ class ContractVerifier(private val transactionClassLoader: ClassLoader) : Functi
} }
} }
} }
// BOB
// E32DC1E8E08D41FE635B27DED128193B75833DC2053BD6A8BB52FB21448EF045 -> 68BB0EA190E6CCD4A5E39CCCEE18A27
// E32DC1E8E08D41FE635B27DED128193B75833DC2053BD6A8BB52FB21448EF045 -> 68BB0EA190E6CCD4A5E39CCCEE18A27

View File

@ -49,6 +49,8 @@ interface ServicesForResolution {
/** Returns the network parameters the node is operating under. */ /** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters val networkParameters: NetworkParameters
val encryptedTransactionService: EncryptedTransactionService
/** /**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
* *
@ -120,6 +122,8 @@ interface ServiceHub : ServicesForResolution {
// NOTE: Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid // NOTE: Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid
// their internal state from being serialized in checkpoints. // their internal state from being serialized in checkpoints.
override val encryptedTransactionService: EncryptedTransactionService
/** /**
* The vault service lets you observe, soft lock and add notes to states that involve you or are relevant to your * The vault service lets you observe, soft lock and add notes to states that involve you or are relevant to your
* node in some way. Additionally you may query and track states that correspond to various criteria. * node in some way. Additionally you may query and track states that correspond to various criteria.

View File

@ -0,0 +1,149 @@
package net.corda.core.node.services
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.dependencies
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.RawDependency
import net.corda.core.transactions.RawDependencyMap
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.SignedTransactionDependencies
import net.corda.core.transactions.SignedTransactionDependencyMap
import net.corda.core.utilities.toHexString
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
// TODO: this should be an interface
class EncryptedTransactionService() : SingletonSerializeAsToken() {
companion object {
private const val CRYPTO_TRANSFORMATION = "AES/CBC/PKCS5PADDING"
private const val CRYPTO_ALGORITHM = "AES"
private const val IV_BYTE_LENGTH = 16
// we will use this when we want to sign over an encrypted transaction to attest that we've verified it
private val enclaveKeyPair = Crypto.generateKeyPair()
// this key we will use internally for encryption/decryption n.b. this is JUST for a quick PoC, as we will lose this key on restart
private val encryptionKey = KeyGenerator
.getInstance(CRYPTO_ALGORITHM)
.also { it.init(256) }
.generateKey()
private fun generateRandomBytes(length: Int): ByteArray {
val arr = ByteArray(length)
SecureRandom().nextBytes(arr)
return arr
}
fun getPublicEnclaveKey() = enclaveKeyPair.public
}
fun getDependencies(encryptedTransaction: EncryptedTransaction): Set<SecureHash> {
val stx = decryptTransaction(encryptedTransaction)
return stx.dependencies
}
fun getAttachmentIds(encryptedTransaction: EncryptedTransaction): Set<SecureHash> {
val stx = decryptTransaction(encryptedTransaction)
return stx.tx.attachments.toSet()
}
fun getNetworkParameterHash(encryptedTransaction: EncryptedTransaction): SecureHash? {
val stx = decryptTransaction(encryptedTransaction)
return stx.tx.networkParametersHash
}
// TODO: this is glossing over a lot of difficulty here. toLedgerTransaction resolves a lot of stuff via services which wont be available within an enclave
fun verifyTransaction(encryptedTransaction: EncryptedTransaction, serviceHub : ServiceHub, checkSufficientSignatures: Boolean, rawDependencies: RawDependencyMap): ByteArray {
println("Verifying encrypted ${encryptedTransaction.id} ${serviceHub.myInfo.legalIdentities.single()}")
val stx = decryptTransaction(encryptedTransaction)
val dependencies = extractDependencies(stx.inputs + stx.references, rawDependencies)
// will throw if cannot verify
stx.toLedgerTransaction(serviceHub, checkSufficientSignatures, dependencies).verify()
return Crypto.doSign(enclaveKeyPair.private, stx.txBits.bytes)
}
fun verifyTransaction(signedTransaction: SignedTransaction, serviceHub: ServiceHub, checkSufficientSignatures: Boolean, rawDependencies: RawDependencyMap) {
println("Verifying ${signedTransaction.id} ${serviceHub.myInfo.legalIdentities.single()}")
val dependencies = extractDependencies(signedTransaction.inputs + signedTransaction.references, rawDependencies)
// will throw if cannot verify
signedTransaction.toLedgerTransaction(serviceHub, checkSufficientSignatures, dependencies).verify()
}
fun encryptTransaction(signedTransaction: SignedTransaction): EncryptedTransaction {
println("Encrypting ${signedTransaction.id} with key: $encryptionKey ${encryptionKey.encoded.toHexString()}")
val ivBytes = generateRandomBytes(IV_BYTE_LENGTH)
val initialisationVector = IvParameterSpec(ivBytes)
val encryptionCipher = Cipher
.getInstance(CRYPTO_TRANSFORMATION)
.also { it.init(Cipher.ENCRYPT_MODE, encryptionKey, initialisationVector) }
val encryptedTxBytes = encryptionCipher.doFinal(ivBytes + signedTransaction.serialize().bytes)
return EncryptedTransaction(signedTransaction.id, encryptedTxBytes)
}
private fun decryptTransaction(encryptedTransaction: EncryptedTransaction): SignedTransaction {
val encryptedBytes = encryptedTransaction.bytes
// first IV_BYTE_LENGTH bytes are the IV
val initialisationVector = IvParameterSpec(encryptedTransaction.bytes.copyOf(IV_BYTE_LENGTH))
// remainder is the transaction
val encryptedTransactionBytes = encryptedTransaction.bytes.copyOfRange(IV_BYTE_LENGTH, encryptedBytes.size)
val decryptionCipher = Cipher
.getInstance(CRYPTO_TRANSFORMATION)
.also { it.init(Cipher.DECRYPT_MODE, encryptionKey, initialisationVector) }
println("Decrypting ${encryptedTransaction.id} with key: $encryptionKey ${encryptionKey.encoded.toHexString()}")
return decryptionCipher.doFinal(encryptedTransactionBytes).deserialize()
}
private fun extractDependencies(requiredStateRefs: List<StateRef>, rawDependencies: RawDependencyMap) : SignedTransactionDependencyMap {
val requiredStates = requiredStateRefs.map { it.txhash }.distinct()
val dependencies = requiredStateRefs.map {
val rawDependency = rawDependencies[it.txhash] ?: throw IllegalArgumentException("Missing raw dependency for ${it.txhash}")
val tx = rawDependency.getTransaction() ?: throw IllegalArgumentException("Missing raw dependency data for ${it.txhash} $rawDependency")
it to tx.coreTransaction.outputs[it.index]
}.groupBy { it.first.txhash }
return requiredStates.map {
val resolvedDependencies = dependencies[it] ?: throw IllegalArgumentException("Missing encrypted transaction resolved reference for $it")
it to SignedTransactionDependencies(
inputsAndRefs = resolvedDependencies.toMap(),
networkParameters = rawDependencies[it]?.networkParameters
)
}.toMap()
}
private fun RawDependency.getTransaction() : SignedTransaction? {
return signedTransaction ?: encryptedTransaction?.let { decryptTransaction(it) }
}
}

View File

@ -5,7 +5,9 @@ import net.corda.core.DoNotImplement
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.debug
import rx.Observable import rx.Observable
/** /**
@ -19,6 +21,11 @@ interface TransactionStorage {
*/ */
fun getTransaction(id: SecureHash): SignedTransaction? fun getTransaction(id: SecureHash): SignedTransaction?
/**
* Return the encrypted transaction with the given [id], or null if no such transaction exists.
*/
fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction?
/** /**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already * Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
* incorporate the update. * incorporate the update.

View File

@ -0,0 +1,29 @@
package net.corda.core.transactions
import net.corda.core.contracts.NamedByHash
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
@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{
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as EncryptedTransaction
if (id != other.id) return false
if (!bytes.contentEquals(other.bytes)) return false
return true
}
override fun hashCode(): Int {
return 31 * (id.hashCode() + bytes.contentHashCode())
}
}

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.Party
import net.corda.core.internal.TransactionDeserialisationException import net.corda.core.internal.TransactionDeserialisationException
import net.corda.core.internal.TransactionVerifierServiceInternal import net.corda.core.internal.TransactionVerifierServiceInternal
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -363,6 +364,47 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash) class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
: NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id)) : NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id))
//region Added for encryption PoC
@JvmOverloads
@DeleteForDJVM
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true, dependencyMap: SignedTransactionDependencyMap): LedgerTransaction {
if (checkSufficientSignatures) {
verifyRequiredSignatures() // It internally invokes checkSignaturesAreValid().
} else {
checkSignaturesAreValid()
}
// We need parameters check here, because finality flow calls stx.toLedgerTransaction() and then verify.
resolveAndCheckNetworkParameters(services, dependencyMap)
return tx.toLedgerTransaction(services, dependencyMap)
}
@DeleteForDJVM
private fun resolveAndCheckNetworkParameters(services: ServiceHub, dependencyMap: SignedTransactionDependencyMap) {
val defaultNetworkParameters = services.networkParametersService.lookup(services.networkParametersService.defaultHash)
val txNetworkParameters = if (networkParametersHash != null) {
services.networkParametersService.lookup(networkParametersHash!!)
} else {
defaultNetworkParameters
} ?: throw TransactionResolutionException(id)
val groupedInputsAndRefs = (inputs + references).groupBy { it.txhash }
groupedInputsAndRefs.map { entry ->
val dependencies = dependencyMap[entry.key] ?: throw TransactionResolutionException(id)
val params = (dependencies.networkParameters ?: defaultNetworkParameters) ?: throw TransactionResolutionException(id)
if (txNetworkParameters.epoch < params.epoch) {
throw TransactionVerificationException.TransactionNetworkParameterOrderingException(id, entry.value.first(), txNetworkParameters, params)
}
}
}
//endregion
//region Deprecated //region Deprecated
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */ /** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
@Deprecated("No replacement, this should not be used outside of Corda core") @Deprecated("No replacement, this should not be used outside of Corda core")
@ -373,3 +415,25 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
fun isNotaryChangeTransaction() = this.coreTransaction is NotaryChangeWireTransaction fun isNotaryChangeTransaction() = this.coreTransaction is NotaryChangeWireTransaction
//endregion //endregion
} }
typealias SignedTransactionDependencyMap = Map<SecureHash, SignedTransactionDependencies>
data class SignedTransactionDependencies(
val inputsAndRefs : Map<StateRef, TransactionState<ContractState>>,
val networkParameters : NetworkParameters?
)
//data class RawDependencies(
// val encryptedTransactions: Map<SecureHash, EncryptedTransaction>,
// val signedTransactions: Map<SecureHash, SignedTransaction>
// val networkParameters: NetworkParameters
//)
typealias RawDependencyMap = Map<SecureHash, RawDependency>
data class RawDependency(
val encryptedTransaction: EncryptedTransaction?,
val signedTransaction: SignedTransaction?,
val networkParameters: NetworkParameters?
)

View File

@ -123,6 +123,31 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
) )
} }
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
@DeleteForDJVM
fun toLedgerTransaction(services: ServicesForResolution, dependencyMap: SignedTransactionDependencyMap): LedgerTransaction {
return services.specialise(
toLedgerTransactionInternal(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRefAsSerialized = {
val dependencies = dependencyMap[it.txhash] ?: throw TransactionResolutionException(it.txhash)
val stateRef = dependencies.inputsAndRefs[it] ?: throw TransactionResolutionException(it.txhash)
stateRef.serialize()
},
resolveParameters = {
val hashToResolve = it ?: services.networkParametersService.defaultHash
services.networkParametersService.lookup(hashToResolve)
},
// `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true },
attachmentsClassLoaderCache = (services as? ServiceHubCoreInternal)?.attachmentsClassLoaderCache
)
)
}
// Helper for deprecated toLedgerTransaction // Helper for deprecated toLedgerTransaction
// TODO: revisit once Deterministic JVM code updated // TODO: revisit once Deterministic JVM code updated
@Suppress("UNUSED") // not sure if this field can be removed safely?? @Suppress("UNUSED") // not sure if this field can be removed safely??

View File

@ -4,8 +4,10 @@ import net.corda.core.identity.Party
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.trackBy import net.corda.core.node.services.trackBy
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.toHexString
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.`issued by` import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
@ -29,6 +31,9 @@ class CashPaymentFlowTests {
private lateinit var bankOfCordaNode: StartedMockNode private lateinit var bankOfCordaNode: StartedMockNode
private lateinit var bankOfCorda: Party private lateinit var bankOfCorda: Party
private lateinit var aliceNode: StartedMockNode private lateinit var aliceNode: StartedMockNode
private lateinit var bobNode: StartedMockNode
private lateinit var issuanceTx: SignedTransaction
@Before @Before
fun start() { fun start() {
@ -36,8 +41,9 @@ class CashPaymentFlowTests {
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
val future = bankOfCordaNode.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)) val future = bankOfCordaNode.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity))
future.getOrThrow() issuanceTx = future.getOrThrow().stx
} }
@After @After
@ -58,7 +64,12 @@ class CashPaymentFlowTests {
val future = bankOfCordaNode.startFlow(CashPaymentFlow(expectedPayment, payTo)) val future = bankOfCordaNode.startFlow(CashPaymentFlow(expectedPayment, payTo))
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() val payTx = future.getOrThrow().stx
// pay Bob (not anonymously as we want to check that Bob owns it)
val futureBob = aliceNode.startFlow(CashPaymentFlow(expectedPayment, bobNode.info.singleIdentity(), false))
mockNet.runNetwork()
val bobTx = futureBob.getOrThrow().stx
// Check Bank of Corda vault updates - we take in some issued cash and split it into $500 to the notary // Check Bank of Corda vault updates - we take in some issued cash and split it into $500 to the notary
// and $1,500 back to us, so we expect to consume one state, produce one state for our own vault // and $1,500 back to us, so we expect to consume one state, produce one state for our own vault
@ -80,6 +91,18 @@ class CashPaymentFlowTests {
assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount) assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
} }
} }
listOf(bobNode, aliceNode, bankOfCordaNode).forEach { node ->
listOf(issuanceTx, payTx, bobTx).forEach { stx ->
println("${node.info.singleIdentity()} UNENCRYPTED: ${node.services.validatedTransactions.getTransaction(stx.id)}")
println("${node.info.singleIdentity()} ENCRYPTED: ${node.services.validatedTransactions.getEncryptedTransaction(stx.id)?.let { "${stx.id} -> ${it.bytes.toHexString()}"}}")
}
}
bobNode.services.vaultService.queryBy(Cash.State::class.java).states.forEach {
println("BOB: ${it.state.data}")
}
} }
@Test(timeout=300_000) @Test(timeout=300_000)

View File

@ -53,6 +53,7 @@ import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.ContractUpgradeService import net.corda.core.node.services.ContractUpgradeService
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
import net.corda.core.node.services.EncryptedTransactionService
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.TransactionVerifierService import net.corda.core.node.services.TransactionVerifierService
@ -290,7 +291,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory) val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
@Suppress("LeakingThis") @Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize() val keyManagementService = makeKeyManagementService(identityService).tokenize()
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage).also {
val encryptedTransactionService = EncryptedTransactionService().tokenize()
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage, encryptedTransactionService).also {
attachments.servicesForResolution = it attachments.servicesForResolution = it
} }
@Suppress("LeakingThis") @Suppress("LeakingThis")
@ -1134,6 +1138,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override val diagnosticsService: DiagnosticsService get() = this@AbstractNode.diagnosticsService override val diagnosticsService: DiagnosticsService get() = this@AbstractNode.diagnosticsService
override val externalOperationExecutor: ExecutorService get() = this@AbstractNode.externalOperationExecutor override val externalOperationExecutor: ExecutorService get() = this@AbstractNode.externalOperationExecutor
override val notaryService: NotaryService? get() = this@AbstractNode.notaryService override val notaryService: NotaryService? get() = this@AbstractNode.notaryService
override val encryptedTransactionService: EncryptedTransactionService = this@AbstractNode.encryptedTransactionService
private lateinit var _myInfo: NodeInfo private lateinit var _myInfo: NodeInfo
override val myInfo: NodeInfo get() = _myInfo override val myInfo: NodeInfo get() = _myInfo

View File

@ -6,6 +6,7 @@ import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.EncryptedTransactionService
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
@ -19,7 +20,8 @@ data class ServicesForResolutionImpl(
override val attachments: AttachmentStorage, override val attachments: AttachmentStorage,
override val cordappProvider: CordappProvider, override val cordappProvider: CordappProvider,
override val networkParametersService: NetworkParametersService, override val networkParametersService: NetworkParametersService,
private val validatedTransactions: TransactionStorage private val validatedTransactions: TransactionStorage,
override val encryptedTransactionService: EncryptedTransactionService
) : ServicesForResolution { ) : ServicesForResolution {
override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?: override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
throw IllegalArgumentException("No current parameters in network parameters storage") throw IllegalArgumentException("No current parameters in network parameters storage")

View File

@ -10,6 +10,7 @@ import net.corda.core.internal.readObject
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.EncryptedTransactionService
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
@ -38,7 +39,8 @@ class MigrationServicesForResolution(
override val attachments: AttachmentStorageInternal, override val attachments: AttachmentStorageInternal,
private val transactions: TransactionStorage, private val transactions: TransactionStorage,
private val cordaDB: CordaPersistence, private val cordaDB: CordaPersistence,
cacheFactory: MigrationNamedCacheFactory cacheFactory: MigrationNamedCacheFactory,
override val encryptedTransactionService: EncryptedTransactionService = EncryptedTransactionService()
): ServicesForResolution { ): ServicesForResolution {
companion object { companion object {

View File

@ -3,11 +3,14 @@ package net.corda.node.services
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.internal.FetchEncryptedTransactionsFlow
import net.corda.core.internal.FetchTransactionsFlow import net.corda.core.internal.FetchTransactionsFlow
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.TransactionsResolver import net.corda.core.internal.TransactionsResolver
import net.corda.core.internal.dependencies import net.corda.core.internal.dependencies
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.RawDependency
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
@ -113,6 +116,131 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
} }
} }
@Suspendable
override fun downloadEncryptedDependencies() {
logger.debug { "Downloading encrypted dependencies for transactions ${flow.txHashes}" }
val transactionStorage = flow.serviceHub.validatedTransactions as WritableTransactionStorage
val encryptSvc = flow.serviceHub.encryptedTransactionService
val nextRequests = LinkedHashSet<SecureHash>(flow.txHashes) // Keep things unique but ordered, for unit test stability.
val topologicalSort = TopologicalSort()
while (nextRequests.isNotEmpty()) {
logger.debug { "Main fetch loop: size_remaining=${nextRequests.size}" }
// Don't re-download the same tx when we haven't verified it yet but it's referenced multiple times in the
// graph we're traversing.
nextRequests.removeAll(topologicalSort.transactionIds)
if (nextRequests.isEmpty()) {
// Done early.
break
}
// Request the standalone transaction data (which may refer to things we don't yet have).
val (existingTxIds, downloadedTxs) = fetchEncryptedRequiredTransactions(Collections.singleton(nextRequests.first())) // Fetch first item only
for (tx in downloadedTxs) {
val dependencies = encryptSvc.getDependencies(tx)
topologicalSort.add(tx.id, dependencies)
}
var suspended = true
for (downloaded in downloadedTxs) {
suspended = false
val dependencies = encryptSvc.getDependencies(downloaded)
// Do not keep in memory as this bloats the checkpoint. Write each item to the database.
transactionStorage.addUnverifiedEncryptedTransaction(downloaded)
// The write locks are only released over a suspend, so need to keep track of whether the flow has been suspended to ensure
// that locks are not held beyond each while loop iteration (as doing this would result in a deadlock due to claiming locks
// in the wrong order)
val suspendedViaAttachments = flow.fetchMissingAttachments(downloaded)
val suspendedViaParams = flow.fetchMissingNetworkParameters(downloaded)
suspended = suspended || suspendedViaAttachments || suspendedViaParams
// Add all input states and reference input states to the work queue.
nextRequests.addAll(dependencies)
}
// If the flow did not suspend on the last iteration of the downloaded loop above, perform a suspend here to ensure that
// all data is flushed to the database.
if (!suspended) {
FlowLogic.sleep(0.seconds)
}
// It's possible that the node has a transaction in storage already. Dependencies should also be present for this transaction,
// so just remove these IDs from the set of next requests.
nextRequests.removeAll(existingTxIds)
}
sortedDependencies = topologicalSort.complete()
logger.debug { "Downloaded ${sortedDependencies?.size} dependencies from remote peer for transactions ${flow.txHashes}" }
}
@Suspendable
override fun recordEncryptedDependencies(usedStatesToRecord: StatesToRecord) {
val sortedDependencies = checkNotNull(this.sortedDependencies)
val encryptSvc = flow.serviceHub.encryptedTransactionService
logger.trace { "Recording ${sortedDependencies.size} dependencies for ${flow.txHashes.size} transactions" }
val transactionStorage = flow.serviceHub.validatedTransactions as WritableTransactionStorage
for (txId in sortedDependencies) {
// Retrieve and delete the transaction from the unverified store.
val (tx, isVerified) = checkNotNull(transactionStorage.getEncryptedTransactionInternal(txId)) {
"Somehow the unverified transaction ($txId) that we stored previously is no longer there."
}
if (!isVerified) {
val dependencies = encryptSvc.getDependencies(tx)
val encryptedTxs = dependencies.mapNotNull {
depTxId ->
transactionStorage.getEncryptedTransaction(depTxId)?.let { etx ->
etx.id to etx
}
}.toMap()
val signedTxs = dependencies.mapNotNull {
depTxId ->
transactionStorage.getTransaction(depTxId)?.let { stx ->
stx.id to stx
}
}.toMap()
val services = flow.serviceHub
val networkParameters = dependencies.mapNotNull { depTxId ->
val npHash = when {
encryptedTxs[depTxId] != null -> encryptSvc.getNetworkParameterHash(encryptedTxs[depTxId]!!)
?: services.networkParametersService.defaultHash
signedTxs[depTxId] != null -> signedTxs[depTxId]!!.networkParametersHash
?: services.networkParametersService.defaultHash
else -> null
}
npHash?.let { depTxId to npHash }
}.associate {
it.first to services.networkParametersService.lookup(it.second)
}
val rawDependencies = dependencies.associate {
it to RawDependency(
encryptedTxs[it],
signedTxs[it],
networkParameters[it]
)
}
encryptSvc.verifyTransaction(tx, flow.serviceHub, true, rawDependencies)
// TODO: why does this usually go through the serviceHub's recordTransactions function and not
// direct to the validatedTransactions service??
// flow.serviceHub.recordTransactions(usedStatesToRecord, listOf(tx))
val transactionStorage = flow.serviceHub.validatedTransactions as WritableTransactionStorage
transactionStorage.addEncryptedTransaction(tx)
} else {
logger.debug { "No need to record $txId as it's already been verified" }
}
}
}
// The transactions already present in the database do not need to be checkpointed on every iteration of downloading // The transactions already present in the database do not need to be checkpointed on every iteration of downloading
// dependencies for other transactions, so strip these down to just the IDs here. // dependencies for other transactions, so strip these down to just the IDs here.
@Suspendable @Suspendable
@ -121,6 +249,12 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded) return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
} }
@Suspendable
private fun fetchEncryptedRequiredTransactions(requests: Set<SecureHash>): Pair<List<SecureHash>, List<EncryptedTransaction>> {
val requestedTxs = flow.subFlow(FetchEncryptedTransactionsFlow(requests, flow.otherSide))
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
}
/** /**
* Provides a way to topologically sort SignedTransactions represented just their [SecureHash] IDs. This means that given any two transactions * Provides a way to topologically sort SignedTransactions represented just their [SecureHash] IDs. This means that given any two transactions
* T1 and T2 in the list returned by [complete] if T1 is a dependency of T2 then T1 will occur earlier than T2. * T1 and T2 in the list returned by [complete] if T1 is a dependency of T2 then T1 will occur earlier than T2.

View File

@ -14,8 +14,10 @@ import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.NetworkMapCacheBase
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.DbTransactionsResolver import net.corda.node.services.DbTransactionsResolver
@ -23,6 +25,7 @@ import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapUpdater import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.persistence.AttachmentStorageInternal import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -253,17 +256,33 @@ interface WritableTransactionStorage : TransactionStorage {
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry. // TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
fun addTransaction(transaction: SignedTransaction): Boolean fun addTransaction(transaction: SignedTransaction): Boolean
/**
* Add a new encrypted transaction to the store
*/
fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean
/** /**
* Add a new *unverified* transaction to the store. * Add a new *unverified* transaction to the store.
*/ */
fun addUnverifiedTransaction(transaction: SignedTransaction) fun addUnverifiedTransaction(transaction: SignedTransaction)
/**
* Add a new *unverified* encrypted transaction to the store.
*/
fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction)
/** /**
* Return the transaction with the given ID from the store, and a flag of whether it's verified. Returns null if no transaction with the * Return the transaction with the given ID from the store, and a flag of whether it's verified. Returns null if no transaction with the
* ID exists. * ID exists.
*/ */
fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>?
/**
* Return the transaction with the given ID from the store, and a flag of whether it's verified. Returns null if no transaction with the
* ID exists.
*/
fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>?
/** /**
* Returns a future that completes with the transaction corresponding to [id] once it has been committed. Do not warn when run inside * Returns a future that completes with the transaction corresponding to [id] once it has been committed. Do not warn when run inside
* a DB transaction. * a DB transaction.

View File

@ -13,6 +13,7 @@ import net.corda.core.serialization.*
import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
@ -53,8 +54,13 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
val status: TransactionStatus, val status: TransactionStatus,
@Column(name = "timestamp", nullable = false) @Column(name = "timestamp", nullable = false)
val timestamp: Instant val timestamp: Instant,
)
@Column(name = "encrypted", nullable = false)
val encrypted: Boolean = false
// TODO: will need to also store the signature of who verified this tx
)
enum class TransactionStatus { enum class TransactionStatus {
UNVERIFIED, UNVERIFIED,
@ -120,17 +126,33 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
name = "DBTransactionStorage_transactions", name = "DBTransactionStorage_transactions",
toPersistentEntityKey = SecureHash::toString, toPersistentEntityKey = SecureHash::toString,
fromPersistentEntity = { fromPersistentEntity = {
SecureHash.create(it.txId) to TxCacheValue( if (it.encrypted) {
it.transaction.deserialize(context = contextToUse()), SecureHash.create(it.txId) to TxCacheValue(
it.status) EncryptedTransaction(
SecureHash.parse(it.txId),
it.transaction
),
it.status
)
} else {
SecureHash.create(it.txId) to TxCacheValue(
it.transaction.deserialize<SignedTransaction>(context = contextToUse()),
it.status
)
}
}, },
toPersistentEntity = { key: SecureHash, value: TxCacheValue -> toPersistentEntity = { key: SecureHash, value: TxCacheValue ->
DBTransaction( DBTransaction(
txId = key.toString(), txId = key.toString(),
stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id?.uuid?.toString(), stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id?.uuid?.toString(),
transaction = value.toSignedTx().serialize(context = contextToUse().withEncoding(SNAPPY)).bytes, transaction = if( value.encrypted ) {
value.txBits
} else {
value.toSignedTx().serialize(context = contextToUse().withEncoding(SNAPPY)).bytes
},
status = value.status, status = value.status,
timestamp = clock.instant() timestamp = clock.instant(),
encrypted = value.encrypted
) )
}, },
persistentEntityClass = DBTransaction::class.java, persistentEntityClass = DBTransaction::class.java,
@ -138,6 +160,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
) )
} }
// TODO: weight of transactions will be wrong at this stage for encrypted transactions
private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int { private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int {
val actTx = tx.peekableValue ?: return 0 val actTx = tx.peekableValue ?: return 0
return actTx.sigs.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.txBits.size return actTx.sigs.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.txBits.size
@ -187,7 +210,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
override fun getTransaction(id: SecureHash): SignedTransaction? { override fun getTransaction(id: SecureHash): SignedTransaction? {
return database.transaction { return database.transaction {
txStorage.content[id]?.let { if (it.status.isVerified()) it.toSignedTx() else null } txStorage.content[id]?.let { if (it.status.isVerified() && !it.encrypted ) it.toSignedTx() else null }
} }
} }
@ -207,7 +230,58 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? { override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? {
return database.transaction { return database.transaction {
txStorage.content[id]?.let { it.toSignedTx() to it.status.isVerified() } txStorage.content[id]?.let {
if (!it.encrypted) {
it.toSignedTx() to it.status.isVerified()
} else null
}
}
}
override fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean {
val transactionId = encryptedTransaction.id
return database.transaction {
txStorage.locked {
val cachedValue = TxCacheValue(encryptedTransaction, TransactionStatus.VERIFIED)
val addedOrUpdated = addOrUpdate(transactionId, cachedValue) { k, _ -> updateTransaction(k) }
if (addedOrUpdated) {
logger.debug { "Transaction $transactionId has been recorded as verified" }
} else {
logger.debug { "Transaction $transactionId is already recorded as verified, so no need to re-record" }
}
addedOrUpdated
}
}
}
override fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction? {
return database.transaction {
txStorage.content[id]?.let { if (it.status.isVerified() && it.encrypted ) it.toEncryptedTx() else null }
}
}
override fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction) {
val transactionId = encryptedTransaction.id
database.transaction {
txStorage.locked {
val cacheValue = TxCacheValue(encryptedTransaction, status = TransactionStatus.UNVERIFIED)
val added = addWithDuplicatesAllowed(transactionId, cacheValue)
if (added) {
logger.debug { "Encrypted Transaction $transactionId recorded as unverified." }
} else {
logger.info("Encrypted Transaction $transactionId already exists so no need to record.")
}
}
}
}
override fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>? {
return database.transaction {
txStorage.content[id]?.let {
if (it.encrypted) {
it.toEncryptedTx() to it.status.isVerified()
} else null
}
} }
} }
@ -262,21 +336,70 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
private fun snapshot(): List<SignedTransaction> { private fun snapshot(): List<SignedTransaction> {
return txStorage.content.allPersisted.use { return txStorage.content.allPersisted.use {
it.filter { it.second.status.isVerified() }.map { it.second.toSignedTx() }.toList() it.filter { it.second.status.isVerified() && !it.second.encrypted }.map { it.second.toSignedTx() }.toList()
} }
} }
// Cache value type to just store the immutable bits of a signed transaction plus conversion helpers // Cache value type to just store the immutable bits of a signed transaction plus conversion helpers
private data class TxCacheValue( private data class TxCacheValue(
val txBits: SerializedBytes<CoreTransaction>, val id: SecureHash,
val txBits: ByteArray,
val sigs: List<TransactionSignature>, val sigs: List<TransactionSignature>,
val status: TransactionStatus val status: TransactionStatus,
val encrypted: Boolean
) { ) {
constructor(stx: SignedTransaction, status: TransactionStatus) : this( constructor(stx: SignedTransaction, status: TransactionStatus) : this(
stx.txBits, stx.id,
Collections.unmodifiableList(stx.sigs), stx.txBits.bytes,
status) stx.sigs,
status,
false)
fun toSignedTx() = SignedTransaction(txBits, sigs) constructor(encryptedTransaction: EncryptedTransaction, status: TransactionStatus) : this(
encryptedTransaction.id,
encryptedTransaction.bytes,
emptyList(),
status,
true)
fun toSignedTx() : SignedTransaction {
return if (!encrypted) {
val txBitsAsSerialized = SerializedBytes<CoreTransaction>(txBits)
SignedTransaction(txBitsAsSerialized, sigs)
} else {
throw IllegalArgumentException("Cannot get signed transaction for encrypted tx")
}
}
fun toEncryptedTx() : EncryptedTransaction {
return if (encrypted) {
// TODO: EncryptedTransaction will be extended to include verification signature
EncryptedTransaction(id, txBits)
} else {
throw IllegalArgumentException("Cannot get encrypted transaction for signed tx")
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TxCacheValue
if (!txBits.contentEquals(other.txBits)) return false
if (sigs != other.sigs) return false
if (status != other.status) return false
if (encrypted != other.encrypted) return false
return true
}
override fun hashCode(): Int {
var result = txBits.contentHashCode()
result = 31 * result + sigs.hashCode()
result = 31 * result + status.hashCode()
result = 31 * result + encrypted.hashCode()
return result
}
} }
} }

View File

@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="add_encrypted_column_to_node_transactions">
<addColumn tableName="node_transactions">
<column name="encrypted" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@ -18,6 +18,7 @@ import net.corda.core.node.services.Vault
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@ -786,6 +787,31 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
delegate.getTransactionInternal(id) delegate.getTransactionInternal(id)
} }
} }
// TODO: these Encrypted transactions may need an overhaul is probably indicative that overloading the current storage was a bad idea
override fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean {
return database.transaction {
delegate.addEncryptedTransaction(encryptedTransaction)
}
}
override fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction) {
return database.transaction {
delegate.addUnverifiedEncryptedTransaction(encryptedTransaction)
}
}
override fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction? {
return database.transaction {
delegate.getEncryptedTransaction(id)
}
}
override fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>? {
return database.transaction {
delegate.getEncryptedTransactionInternal(id)
}
}
} }
interface TxRecord { interface TxRecord {

View File

@ -7,7 +7,13 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.serialization.serialize
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.node.CordaClock import net.corda.node.CordaClock
import net.corda.node.MutableClock import net.corda.node.MutableClock
@ -15,6 +21,7 @@ import net.corda.node.SimpleClock
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.serialization.internal.CordaSerializationEncoding
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
@ -32,12 +39,17 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import rx.plugins.RxJavaHooks import rx.plugins.RxJavaHooks
import java.security.SecureRandom
import java.time.Clock import java.time.Clock
import java.time.Instant import java.time.Instant
import java.util.concurrent.Semaphore import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class DBTransactionStorageTests { class DBTransactionStorageTests {
private companion object { private companion object {
@ -390,6 +402,47 @@ class DBTransactionStorageTests {
assertThat(warning).isEqualTo(DBTransactionStorage.TRANSACTION_ALREADY_IN_PROGRESS_WARNING) assertThat(warning).isEqualTo(DBTransactionStorage.TRANSACTION_ALREADY_IN_PROGRESS_WARNING)
} }
@Test(timeout=300_000)
fun `encrypted transaction`() {
val now = Instant.ofEpochSecond(111222333L)
val transactionClock = TransactionClock(now)
newTransactionStorage(clock = transactionClock)
val transaction = newTransaction()
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key = keygen.generateKey()
val cipherTransformation = "AES/CBC/PKCS5PADDING"
val encryptionCipher = Cipher.getInstance(cipherTransformation)
val iv = generateIv()
encryptionCipher.init(Cipher.ENCRYPT_MODE, key, iv)
val encryptedTxBytes = encryptionCipher.doFinal(transaction.serialize(context = contextToUse().withEncoding(CordaSerializationEncoding.SNAPPY)).bytes)
val encryptedTx = EncryptedTransaction(transaction.id, encryptedTxBytes)
transactionStorage.addEncryptedTransaction(encryptedTx)
val storedTx = transactionStorage.getEncryptedTransaction(transaction.id)
val decryptionCipher = Cipher.getInstance(cipherTransformation)
decryptionCipher.init(Cipher.DECRYPT_MODE, key, iv)
assertNotNull(storedTx, "Could not find stored encrypted message")
val decryptedTx = decryptionCipher.doFinal(storedTx!!.bytes).deserialize<SignedTransaction>(context = contextToUse())
assertEquals(decryptedTx, transaction)
assertEquals(now, readTransactionTimestampFromDB(transaction.id))
}
fun generateIv(): IvParameterSpec? {
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
return IvParameterSpec(iv)
}
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null, clock: CordaClock = SimpleClock(Clock.systemUTC())) { private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null, clock: CordaClock = SimpleClock(Clock.systemUTC())) {
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride
?: 1024), clock) ?: 1024), clock)
@ -413,4 +466,12 @@ class DBTransactionStorageTests {
listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID))) listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)))
) )
} }
private fun contextToUse(): SerializationContext {
return if (effectiveSerializationEnv.serializationFactory.currentContext?.useCase == SerializationContext.UseCase.Storage) {
effectiveSerializationEnv.serializationFactory.currentContext!!
} else {
SerializationDefaults.STORAGE_CONTEXT
}
}
} }

View File

@ -86,7 +86,8 @@ open class MockServices private constructor(
override val keyManagementService: KeyManagementService = MockKeyManagementService( override val keyManagementService: KeyManagementService = MockKeyManagementService(
identityService, identityService,
*arrayOf(initialIdentity.keyPair) + moreKeys *arrayOf(initialIdentity.keyPair) + moreKeys
) ),
override val encryptedTransactionService : EncryptedTransactionService = EncryptedTransactionService()
) : ServiceHub { ) : ServiceHub {
companion object { companion object {
@ -457,7 +458,7 @@ open class MockServices private constructor(
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService() override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
protected val servicesForResolution: ServicesForResolution protected val servicesForResolution: ServicesForResolution
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions) get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions, encryptedTransactionService)
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal { internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(clock, keyManagementService, servicesForResolution, database, schemaService, cordappLoader.appClassLoader).apply { start() } return NodeVaultService(clock, keyManagementService, servicesForResolution, database, schemaService, cordappLoader.appClassLoader).apply { start() }

View File

@ -6,6 +6,7 @@ import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.transactions.EncryptedTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
@ -30,6 +31,7 @@ open class MockTransactionStorage : WritableTransactionStorage, SingletonSeriali
} }
private val txns = HashMap<SecureHash, TxHolder>() private val txns = HashMap<SecureHash, TxHolder>()
private val encryptedTxns = HashMap<SecureHash, EncryptedTxHolder>()
private val _updatesPublisher = PublishSubject.create<SignedTransaction>() private val _updatesPublisher = PublishSubject.create<SignedTransaction>()
@ -61,5 +63,27 @@ open class MockTransactionStorage : WritableTransactionStorage, SingletonSeriali
override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? = txns[id]?.let { Pair(it.stx, it.isVerified) } override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? = txns[id]?.let { Pair(it.stx, it.isVerified) }
override fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean {
val current = encryptedTxns.putIfAbsent(encryptedTransaction.id, EncryptedTxHolder(encryptedTransaction, isVerified = true))
return if (current == null) {
true
} else if (!current.isVerified) {
current.isVerified = true
true
} else {
false
}
}
override fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction) {
encryptedTxns.putIfAbsent(encryptedTransaction.id, EncryptedTxHolder(encryptedTransaction, isVerified = false))
}
override fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction? = encryptedTxns[id]?.let { if (it.isVerified) it.etx else null }
override fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>? =
encryptedTxns[id]?.let { Pair(it.etx, it.isVerified) }
private class TxHolder(val stx: SignedTransaction, var isVerified: Boolean) private class TxHolder(val stx: SignedTransaction, var isVerified: Boolean)
private class EncryptedTxHolder(val etx: EncryptedTransaction, var isVerified: Boolean)
} }