Nonce per tx component (#1125)

Add salt to wire tx and nonces to tx components
This commit is contained in:
Konstantinos Chalkias 2017-07-31 12:19:47 +01:00 committed by GitHub
parent 907ef97346
commit 36b50aab2d
17 changed files with 223 additions and 58 deletions

View File

@ -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." }
}
}

View File

@ -47,19 +47,22 @@ class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
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()

View File

@ -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

View File

@ -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<WireTransaction>() {
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<SecureHash>): ClassLoader? {
@ -272,7 +274,8 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
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)
}
}
}

View File

@ -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 {

View File

@ -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 <T : Any> 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 <T : Any> serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): SecureHash {
return if (privacySalt != null)
serializedHash(x, computeNonce(privacySalt, index))
else
serializedHash(x)
}
fun <T : Any> 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 <T : Any> 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<Any>
// 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<SecureHash> get() = availableComponents.map { serializedHash(it) }
val availableComponentHashes: List<SecureHash> 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<Command<*>>,
override val notary: Party?,
override val type: TransactionType?,
override val timeWindow: TimeWindow?
override val timeWindow: TimeWindow?,
val nonces: List<SecureHash>
) : 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<SecureHash> get() = availableComponents.mapIndexed { index, it -> serializedHash(it, nonces[index]) }
}
/**

View File

@ -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<DigitalSignature.WithKey>): NotaryChangeLedgerTransaction {

View File

@ -33,7 +33,9 @@ open class TransactionBuilder(
protected val attachments: MutableList<SecureHash> = arrayListOf(),
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
protected val commands: MutableList<Command<*>> = 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<StateRef> = ArrayList(inputs)
fun attachments(): List<SecureHash> = ArrayList(attachments)

View File

@ -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<Any>): FilteredLeaves {
fun notNullFalse(elem: Any?): Any? = if (elem == null || !filtering.test(elem)) null else elem
val nonces: MutableList<SecureHash> = 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<T : Any> filterAndNoncesUpdate(filtering: Predicate<Any>, 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<Int> {
// 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.
*

View File

@ -99,6 +99,7 @@ class TransactionTests : TestDependencyInjectionBase() {
val attachments = emptyList<Attachment>()
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<Attachment>()
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<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }

View File

@ -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<AuthenticatedObject<CommandData>>())
// 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<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
}
}

View File

@ -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<AuthenticatedObject<CommandData>>())
// 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<AuthenticatedObject<CommandData>>())
// 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<AuthenticatedObject<CommandData>>())
}

View File

@ -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<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = 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<AuthenticatedObject<CommandData>>())
}
@ -36,7 +33,7 @@ class VerifyClausesTests {
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = 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<IllegalStateException> { verifyClause(tx, clause, listOf(command)) }
}

View File

@ -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<MerkleTreeException> { 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<MerkleTreeException> { 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])

View File

@ -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<Attachment>()
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<Attachment>()
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
)
}

View File

@ -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)

View File

@ -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<T>(curStateRef: StateAndRef<RevisionedState<T>>,
@ -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 }