From 36b50aab2d6ceefa78f586852b520ca549539a32 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Mon, 31 Jul 2017 12:19:47 +0100 Subject: [PATCH] Nonce per tx component (#1125) Add salt to wire tx and nonces to tx components --- .../net/corda/core/contracts/Structures.kt | 20 ++++++ .../corda/core/flows/ContractUpgradeFlow.kt | 9 ++- .../net/corda/core/flows/NotaryChangeFlow.kt | 4 +- .../net/corda/core/serialization/Kryo.kt | 5 +- .../core/transactions/LedgerTransaction.kt | 3 +- .../core/transactions/MerkleTransaction.kt | 67 ++++++++++++++++--- .../transactions/NotaryChangeTransactions.kt | 4 ++ .../core/transactions/TransactionBuilder.kt | 15 ++++- .../core/transactions/WireTransaction.kt | 67 ++++++++++++++++--- .../corda/core/contracts/TransactionTests.kt | 8 ++- .../core/contracts/clauses/AllOfTests.kt | 5 +- .../core/contracts/clauses/AnyOfTests.kt | 7 +- .../contracts/clauses/VerifyClausesTests.kt | 9 +-- .../core/crypto/PartialMerkleTreeTest.kt | 34 +++++++--- .../services/vault/schemas/VaultSchemaTest.kt | 13 +++- .../corda/node/services/CoreFlowHandlers.kt | 2 +- .../net/corda/vega/flows/StateRevisionFlow.kt | 9 ++- 17 files changed, 223 insertions(+), 58 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index b609101b8a..d9fba86bb1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts import net.corda.core.contracts.clauses.Clause import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.secureRandomBytes import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.identity.AbstractParty @@ -434,3 +435,22 @@ fun JarInputStream.extractFile(path: String, outputTo: OutputStream) { } throw FileNotFoundException(path) } + +/** + * A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot + * use brute force techniques and reveal the content of a merkle-leaf hashed value. + * Because this salt serves the role of the seed to compute nonces, its size and entropy should be equal to the + * underlying hash function used for Merkle tree generation, currently [SHA256], which has an output of 32 bytes. + * There are two constructors, one that generates a new 32-bytes random salt, and another that takes a [ByteArray] input. + * The latter is required in cases where the salt value needs to be pre-generated (agreed between transacting parties), + * but it is highlighted that one should always ensure it has sufficient entropy. + */ +@CordaSerializable +class PrivacySalt(bytes: ByteArray) : OpaqueBytes(bytes) { + constructor() : this(secureRandomBytes(32)) + + init { + require(bytes.size == 32) { "Privacy salt should be 32 bytes." } + require(!bytes.all { it == 0.toByte() }) { "Privacy salt should not be all zeros." } + } +} diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index 38b24bd5b1..51102e660d 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -47,19 +47,22 @@ class ContractUpgradeFlow assembleBareTx( stateRef: StateAndRef, - upgradedContractClass: Class> + upgradedContractClass: Class>, + privacySalt: PrivacySalt ): TransactionBuilder { val contractUpgrade = upgradedContractClass.newInstance() return TransactionType.General.Builder(stateRef.state.notary) .withItems( stateRef, contractUpgrade.upgrade(stateRef.state.data), - Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey })) + Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }), + privacySalt + ) } } override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx { - val baseTx = assembleBareTx(originalState, modification) + val baseTx = assembleBareTx(originalState, modification, PrivacySalt()) val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet() // TODO: We need a much faster way of finding our key in the transaction val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single() diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt index e16387a482..bc4bc628fa 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt @@ -1,6 +1,8 @@ package net.corda.core.flows -import net.corda.core.contracts.* +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef import net.corda.core.identity.Party import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.SignedTransaction diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index b0cbc94d01..bc780a3cff 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -13,6 +13,7 @@ import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.OpaqueBytes import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec @@ -245,6 +246,7 @@ object WireTransactionSerializer : Serializer() { kryo.writeClassAndObject(output, obj.notary) kryo.writeClassAndObject(output, obj.type) kryo.writeClassAndObject(output, obj.timeWindow) + kryo.writeClassAndObject(output, obj.privacySalt) } private fun attachmentsClassLoader(kryo: Kryo, attachmentHashes: List): ClassLoader? { @@ -272,7 +274,8 @@ object WireTransactionSerializer : Serializer() { val notary = kryo.readClassAndObject(input) as Party? val transactionType = kryo.readClassAndObject(input) as TransactionType val timeWindow = kryo.readClassAndObject(input) as TimeWindow? - return WireTransaction(inputs, attachmentHashes, outputs, commands, notary, transactionType, timeWindow) + val privacySalt = kryo.readClassAndObject(input) as PrivacySalt + return WireTransaction(inputs, attachmentHashes, outputs, commands, notary, transactionType, timeWindow, privacySalt) } } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 00bba9f45a..a5db2506e6 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -35,7 +35,8 @@ data class LedgerTransaction( override val id: SecureHash, override val notary: Party?, val timeWindow: TimeWindow?, - val type: TransactionType + val type: TransactionType, + val privacySalt: PrivacySalt ) : FullTransaction() { //DOCEND 1 init { diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index 1bc09476b2..c18a480a94 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -1,20 +1,37 @@ package net.corda.core.transactions import net.corda.core.contracts.* -import net.corda.core.crypto.MerkleTree -import net.corda.core.crypto.MerkleTreeException -import net.corda.core.crypto.PartialMerkleTree -import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT import net.corda.core.serialization.serialize +import java.nio.ByteBuffer import java.util.function.Predicate -fun serializedHash(x: T): SecureHash { - return x.serialize(context = P2P_CONTEXT.withoutReferences()).hash +/** + * If a privacy salt is provided, the resulted output (merkle-leaf) is computed as + * Hash(serializedObject || Hash(privacy_salt || obj_index_in_merkle_tree)). + */ +fun serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): SecureHash { + return if (privacySalt != null) + serializedHash(x, computeNonce(privacySalt, index)) + else + serializedHash(x) } +fun serializedHash(x: T, nonce: SecureHash): SecureHash { + return if (x !is PrivacySalt) // PrivacySalt is not required to have an accompanied nonce. + (x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes + nonce.bytes).sha256() + else + serializedHash(x) +} + +fun serializedHash(x: T): SecureHash = x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes.sha256() + +/** The nonce is computed as Hash(privacySalt || index). */ +fun computeNonce(privacySalt: PrivacySalt, index: Int) = (privacySalt.bytes + ByteBuffer.allocate(4).putInt(index).array()).sha256() + /** * Implemented by [WireTransaction] and [FilteredLeaves]. A TraversableTransaction allows you to iterate * over the flattened components of the underlying transaction structure, taking into account that some @@ -32,6 +49,17 @@ interface TraversableTransaction { val notary: Party? val type: TransactionType? val timeWindow: TimeWindow? + /** + * For privacy purposes, each part of a transaction should be accompanied by a nonce. + * To avoid storing a random number (nonce) per component, an initial "salt" is the sole value utilised, + * so that all component nonces are deterministically computed in the following way: + * nonce1 = H(salt || 1) + * nonce2 = H(salt || 2) + * + * Thus, all of the nonces are "independent" in the sense that knowing one or some of them, you can learn + * nothing about the rest. + */ + val privacySalt: PrivacySalt? /** * Returns a flattened list of all the components that are present in the transaction, in the following order: @@ -41,11 +69,13 @@ interface TraversableTransaction { * - Each output that is present * - Each command that is present * - The notary [Party], if present - * - Each required signer ([mustSign]) that is present * - The type of the transaction, if present * - The time-window of the transaction, if present + * - The privacy salt required for nonces, always presented in [WireTransaction] and always null in [FilteredLeaves] */ val availableComponents: List + // NOTE: if the order below is altered or components are added/removed in the future, one should also reflect + // this change to the indexOffsets() method in WireTransaction. get() { // We may want to specify our own behaviour on certain tx fields. // Like if we include them at all, what to do with null values, if we treat list as one or not etc. for building @@ -54,6 +84,7 @@ interface TraversableTransaction { notary?.let { result += it } type?.let { result += it } timeWindow?.let { result += it } + privacySalt?.let { result += it } return result } @@ -62,12 +93,13 @@ interface TraversableTransaction { * The root of the tree is the transaction identifier. The tree structure is helpful for privacy, please * see the user-guide section "Transaction tear-offs" to learn more about this topic. */ - val availableComponentHashes: List get() = availableComponents.map { serializedHash(it) } + val availableComponentHashes: List get() = availableComponents.mapIndexed { index, it -> serializedHash(it, privacySalt, index) } } /** * Class that holds filtered leaves for a partial Merkle transaction. We assume mixed leaf types, notice that every - * field from [WireTransaction] can be used in [PartialMerkleTree] calculation. + * field from [WireTransaction] can be used in [PartialMerkleTree] calculation, except for the privacySalt. + * A list of nonces is also required to (re)construct component hashes. */ @CordaSerializable class FilteredLeaves( @@ -77,8 +109,21 @@ class FilteredLeaves( override val commands: List>, override val notary: Party?, override val type: TransactionType?, - override val timeWindow: TimeWindow? + override val timeWindow: TimeWindow?, + val nonces: List ) : TraversableTransaction { + + /** + * PrivacySalt should be always null for FilteredLeaves, because making it accidentally visible would expose all + * nonces (including filtered out components) causing privacy issues, see [serializedHash] and + * [TraversableTransaction.privacySalt]. + */ + override val privacySalt: PrivacySalt? get() = null + + init { + require(availableComponents.size == nonces.size) { "Each visible component should be accompanied by a nonce." } + } + /** * Function that checks the whole filtered structure. * Force type checking on a structure that we obtained, so we don't sign more than expected. @@ -92,6 +137,8 @@ class FilteredLeaves( val checkList = availableComponents.map { checkingFun(it) } return (!checkList.isEmpty()) && checkList.all { it } } + + override val availableComponentHashes: List get() = availableComponents.mapIndexed { index, it -> serializedHash(it, nonces[index]) } } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt index b6456f781e..361efb86f9 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -30,6 +30,10 @@ data class NotaryChangeWireTransaction( check(notary != newNotary) { "The old and new notaries must be different – $newNotary" } } + /** + * A privacy salt is not really required in this case, because we already used nonces in normal transactions and + * thus input state refs will always be unique. Also, filtering doesn't apply on this type of transactions. + */ override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) } fun resolve(services: ServiceHub, sigs: List): NotaryChangeLedgerTransaction { diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index aaae247c04..bd066f5cb2 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -33,7 +33,9 @@ open class TransactionBuilder( protected val attachments: MutableList = arrayListOf(), protected val outputs: MutableList> = arrayListOf(), protected val commands: MutableList> = arrayListOf(), - protected var window: TimeWindow? = null) { + protected var window: TimeWindow? = null, + protected var privacySalt: PrivacySalt = PrivacySalt() + ) { constructor(type: TransactionType, notary: Party) : this(type, notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()) /** @@ -46,7 +48,8 @@ open class TransactionBuilder( attachments = ArrayList(attachments), outputs = ArrayList(outputs), commands = ArrayList(commands), - window = window + window = window, + privacySalt = privacySalt ) // DOCSTART 1 @@ -61,6 +64,7 @@ open class TransactionBuilder( is Command<*> -> addCommand(t) is CommandData -> throw IllegalArgumentException("You passed an instance of CommandData, but that lacks the pubkey. You need to wrap it in a Command object first.") is TimeWindow -> setTimeWindow(t) + is PrivacySalt -> setPrivacySalt(t) else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}") } } @@ -69,7 +73,7 @@ open class TransactionBuilder( // DOCEND 1 fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments), - ArrayList(outputs), ArrayList(commands), notary, type, window) + ArrayList(outputs), ArrayList(commands), notary, type, window, privacySalt) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class) fun toLedgerTransaction(services: ServiceHub) = toWireTransaction().toLedgerTransaction(services) @@ -136,6 +140,11 @@ open class TransactionBuilder( */ fun setTimeWindow(time: Instant, timeTolerance: Duration) = setTimeWindow(TimeWindow.withTolerance(time, timeTolerance)) + fun setPrivacySalt(privacySalt: PrivacySalt): TransactionBuilder { + this.privacySalt = privacySalt + return this + } + // Accessors that yield immutable snapshots. fun inputStates(): List = ArrayList(inputs) fun attachments(): List = ArrayList(attachments) diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index c3a0db0704..d37c361d26 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -28,7 +28,8 @@ data class WireTransaction( override val notary: Party?, // TODO: remove type override val type: TransactionType, - override val timeWindow: TimeWindow? + override val timeWindow: TimeWindow?, + override val privacySalt: PrivacySalt = PrivacySalt() ) : CoreTransaction(), TraversableTransaction { init { checkBaseInvariants() @@ -92,7 +93,7 @@ data class WireTransaction( val resolvedInputs = inputs.map { ref -> resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) } - return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, type) + return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, type, privacySalt) } /** @@ -109,22 +110,68 @@ data class WireTransaction( /** * Construction of partial transaction from WireTransaction based on filtering. + * Note that list of nonces to be sent is updated on the fly, based on the index of the filtered tx component. * @param filtering filtering over the whole WireTransaction * @returns FilteredLeaves used in PartialMerkleTree calculation and verification. */ fun filterWithFun(filtering: Predicate): FilteredLeaves { - fun notNullFalse(elem: Any?): Any? = if (elem == null || !filtering.test(elem)) null else elem + val nonces: MutableList = mutableListOf() + val offsets = indexOffsets() + fun notNullFalseAndNoncesUpdate(elem: Any?, index: Int): Any? { + return if (elem == null || !filtering.test(elem)) { + null + } else { + nonces.add(computeNonce(privacySalt, index)) + elem + } + } + + fun filterAndNoncesUpdate(filtering: Predicate, t: T, index: Int): Boolean { + return if (filtering.test(t)) { + nonces.add(computeNonce(privacySalt, index)) + true + } else { + false + } + } + // TODO: We should have a warning (require) if all leaves (excluding salt) are visible after filtering. + // Consider the above after refactoring FilteredTransaction to implement TraversableTransaction, + // so that a WireTransaction can be used when required to send a full tx (e.g. RatesFixFlow in Oracles). return FilteredLeaves( - inputs.filter { filtering.test(it) }, - attachments.filter { filtering.test(it) }, - outputs.filter { filtering.test(it) }, - commands.filter { filtering.test(it) }, - notNullFalse(notary) as Party?, - notNullFalse(type) as TransactionType?, - notNullFalse(timeWindow) as TimeWindow? + inputs.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index) }, + attachments.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index + offsets[0]) }, + outputs.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index + offsets[1]) }, + commands.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index + offsets[2]) }, + notNullFalseAndNoncesUpdate(notary, offsets[3]) as Party?, + notNullFalseAndNoncesUpdate(type, offsets[4]) as TransactionType?, + notNullFalseAndNoncesUpdate(timeWindow, offsets[5]) as TimeWindow?, + nonces ) } + // We use index offsets, to get the actual leaf-index per transaction component required for nonce computation. + private fun indexOffsets(): List { + // There is no need to add an index offset for inputs, because they are the first components in the + // transaction format and it is always zero. Thus, offsets[0] corresponds to attachments, + // offsets[1] to outputs, offsets[2] to commands and so on. + val offsets = mutableListOf(inputs.size, inputs.size + attachments.size) + offsets.add(offsets.last() + outputs.size) + offsets.add(offsets.last() + commands.size) + if (notary != null) { + offsets.add(offsets.last() + 1) + } else { + offsets.add(offsets.last()) + } + offsets.add(offsets.last() + 1) // For tx type. + if (timeWindow != null) { + offsets.add(offsets.last() + 1) + } else { + offsets.add(offsets.last()) + } + // No need to add offset for privacySalt as it doesn't require a nonce. + return offsets + } + /** * Checks that the given signature matches one of the commands and that it is a correct signature over the tx. * diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt index e672cd57db..4fe2827f5c 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt @@ -99,6 +99,7 @@ class TransactionTests : TestDependencyInjectionBase() { val attachments = emptyList() val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null + val privacySalt: PrivacySalt = PrivacySalt() val transaction: LedgerTransaction = LedgerTransaction( inputs, outputs, @@ -107,7 +108,8 @@ class TransactionTests : TestDependencyInjectionBase() { id, null, timeWindow, - TransactionType.General + TransactionType.General, + privacySalt ) transaction.verify() @@ -140,6 +142,7 @@ class TransactionTests : TestDependencyInjectionBase() { val attachments = emptyList() val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null + val privacySalt: PrivacySalt = PrivacySalt() fun buildTransaction() = LedgerTransaction( inputs, outputs, @@ -148,7 +151,8 @@ class TransactionTests : TestDependencyInjectionBase() { id, notary, timeWindow, - TransactionType.General + TransactionType.General, + privacySalt ) assertFailsWith { buildTransaction() } diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt index 4138547708..2e224d54c6 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData +import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash import net.corda.core.transactions.LedgerTransaction @@ -16,7 +17,7 @@ class AllOfTests { fun minimal() { val counter = AtomicInteger(0) val clause = AllOf(matchedClause(counter), matchedClause(counter)) - val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) verifyClause(tx, clause, emptyList>()) // Check that we've run the verify() function of two clauses @@ -26,7 +27,7 @@ class AllOfTests { @Test fun `not all match`() { val clause = AllOf(matchedClause(), unmatchedClause()) - val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) assertFailsWith { verifyClause(tx, clause, emptyList>()) } } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt index 7c3aa1390f..c7233eb647 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData +import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash import net.corda.core.transactions.LedgerTransaction @@ -15,7 +16,7 @@ class AnyOfTests { fun minimal() { val counter = AtomicInteger(0) val clause = AnyOf(matchedClause(counter), matchedClause(counter)) - val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) verifyClause(tx, clause, emptyList>()) // Check that we've run the verify() function of two clauses @@ -26,7 +27,7 @@ class AnyOfTests { fun `not all match`() { val counter = AtomicInteger(0) val clause = AnyOf(matchedClause(counter), unmatchedClause(counter)) - val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) verifyClause(tx, clause, emptyList>()) // Check that we've run the verify() function of one clause @@ -37,7 +38,7 @@ class AnyOfTests { fun `none match`() { val counter = AtomicInteger(0) val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter)) - val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) assertFailsWith(IllegalArgumentException::class) { verifyClause(tx, clause, emptyList>()) } diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt index 275713e10d..c08a55ec1c 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt @@ -1,9 +1,6 @@ package net.corda.core.contracts.clauses -import net.corda.core.contracts.AuthenticatedObject -import net.corda.core.contracts.CommandData -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionType +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.transactions.LedgerTransaction import net.corda.testing.contracts.DummyContract @@ -23,7 +20,7 @@ class VerifyClausesTests { outputs: List, commands: List>, groupingKey: Unit?): Set = emptySet() } - val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) verifyClause(tx, clause, emptyList>()) } @@ -36,7 +33,7 @@ class VerifyClausesTests { commands: List>, groupingKey: Unit?): Set = emptySet() } val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create()) - val tx = LedgerTransaction(emptyList(), emptyList(), listOf(command), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General) + val tx = LedgerTransaction(emptyList(), emptyList(), listOf(command), emptyList(), SecureHash.randomSHA256(), null, null, TransactionType.General, PrivacySalt()) // The clause is matched, but doesn't mark the command as consumed, so this should error assertFailsWith { verifyClause(tx, clause, listOf(command)) } } diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index 74d19e1832..c1f1ac2277 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -97,7 +97,7 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { } @Test - fun `building Merkle tree for a transaction`() { + fun `building Merkle tree for a tx and nonce test`() { fun filtering(elem: Any): Boolean { return when (elem) { is StateRef -> true @@ -109,17 +109,21 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { } } - val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) - val leaves = mt.filteredLeaves val d = testTx.serialize().deserialize() assertEquals(testTx.id, d.id) - assertEquals(1, leaves.commands.size) - assertEquals(1, leaves.outputs.size) + + val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) + val leaves = mt.filteredLeaves + assertEquals(1, leaves.inputs.size) assertEquals(0, leaves.attachments.size) - assertTrue(mt.filteredLeaves.timeWindow != null) - assertEquals(null, mt.filteredLeaves.type) - assertEquals(null, mt.filteredLeaves.notary) + assertEquals(1, leaves.outputs.size) + assertEquals(1, leaves.commands.size) + assertNull(mt.filteredLeaves.notary) + assertNull(mt.filteredLeaves.type) + assertNotNull(mt.filteredLeaves.timeWindow) + assertNull(mt.filteredLeaves.privacySalt) + assertEquals(4, leaves.nonces.size) assertTrue(mt.verify()) } @@ -138,10 +142,22 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { assertTrue(mt.filteredLeaves.inputs.isEmpty()) assertTrue(mt.filteredLeaves.outputs.isEmpty()) assertTrue(mt.filteredLeaves.timeWindow == null) + assertTrue(mt.filteredLeaves.availableComponents.isEmpty()) + assertTrue(mt.filteredLeaves.availableComponentHashes.isEmpty()) + assertTrue(mt.filteredLeaves.nonces.isEmpty()) assertFailsWith { mt.verify() } + + // Including only privacySalt still results to an empty FilteredTransaction. + fun filterPrivacySalt(elem: Any): Boolean = elem is PrivacySalt + val mt2 = testTx.buildFilteredTransaction(Predicate(::filterPrivacySalt)) + assertTrue(mt2.filteredLeaves.privacySalt == null) + assertTrue(mt2.filteredLeaves.availableComponents.isEmpty()) + assertTrue(mt2.filteredLeaves.availableComponentHashes.isEmpty()) + assertTrue(mt2.filteredLeaves.nonces.isEmpty()) + assertFailsWith { mt2.verify() } } - // Partial Merkle Tree building tests + // Partial Merkle Tree building tests. @Test fun `build Partial Merkle Tree, only left nodes branch`() { val inclHashes = listOf(hashed[3], hashed[5]) diff --git a/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt b/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt index 63f2d56f82..ccde8b8dc0 100644 --- a/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt +++ b/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt @@ -21,7 +21,10 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.LedgerTransaction import net.corda.node.services.vault.schemas.requery.* -import net.corda.testing.* +import net.corda.testing.ALICE +import net.corda.testing.BOB +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.contracts.DummyContract import org.h2.jdbcx.JdbcDataSource import org.junit.After @@ -117,6 +120,7 @@ class VaultSchemaTest : TestDependencyInjectionBase() { val attachments = emptyList() val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null + val privacySalt: PrivacySalt = PrivacySalt() transaction = LedgerTransaction( inputs, outputs, @@ -125,7 +129,8 @@ class VaultSchemaTest : TestDependencyInjectionBase() { id, notary, timeWindow, - TransactionType.General + TransactionType.General, + privacySalt ) } @@ -147,6 +152,7 @@ class VaultSchemaTest : TestDependencyInjectionBase() { val attachments = emptyList() val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null + val privacySalt: PrivacySalt = PrivacySalt() return LedgerTransaction( inputs, outputs, @@ -155,7 +161,8 @@ class VaultSchemaTest : TestDependencyInjectionBase() { id, notary, timeWindow, - TransactionType.General + TransactionType.General, + privacySalt ) } diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index 36a4f24deb..ef9199bd54 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -108,7 +108,7 @@ class ContractUpgradeHandler(otherSide: Party) : AbstractStateReplacementFlow.Ac val authorisedUpgrade = serviceHub.vaultService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?: throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}") val proposedTx = proposal.stx.tx - val expectedTx = ContractUpgradeFlow.assembleBareTx(oldStateAndRef, proposal.modification).toWireTransaction() + val expectedTx = ContractUpgradeFlow.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction() requireThat { "The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants) "The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade) diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt index 61684381f3..cb1d4a97cb 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt @@ -1,15 +1,16 @@ package net.corda.vega.flows +import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.StateAndRef -import net.corda.core.identity.Party -import net.corda.core.utilities.seconds import net.corda.core.flows.AbstractStateReplacementFlow import net.corda.core.flows.StateReplacementException +import net.corda.core.identity.Party +import net.corda.core.utilities.seconds import net.corda.vega.contracts.RevisionedState /** * Flow that generates an update on a mutable deal state and commits the resulting transaction reaching consensus - * on the update between two parties + * on the update between two parties. */ object StateRevisionFlow { class Requester(curStateRef: StateAndRef>, @@ -18,6 +19,8 @@ object StateRevisionFlow { val state = originalState.state.data val tx = state.generateRevision(originalState.state.notary, originalState, modification) tx.setTimeWindow(serviceHub.clock.instant(), 30.seconds) + val privacySalt = PrivacySalt() + tx.setPrivacySalt(privacySalt) val stx = serviceHub.signInitialTransaction(tx) val participantKeys = state.participants.map { it.owningKey }