mirror of
https://github.com/corda/corda.git
synced 2025-01-03 11:44:16 +00:00
Nonce per tx component (#1125)
Add salt to wire tx and nonces to tx components
This commit is contained in:
parent
907ef97346
commit
36b50aab2d
@ -2,6 +2,7 @@ package net.corda.core.contracts
|
|||||||
|
|
||||||
import net.corda.core.contracts.clauses.Clause
|
import net.corda.core.contracts.clauses.Clause
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.secureRandomBytes
|
||||||
import net.corda.core.flows.FlowLogicRef
|
import net.corda.core.flows.FlowLogicRef
|
||||||
import net.corda.core.flows.FlowLogicRefFactory
|
import net.corda.core.flows.FlowLogicRefFactory
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
@ -434,3 +435,22 @@ fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
|
|||||||
}
|
}
|
||||||
throw FileNotFoundException(path)
|
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." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -47,19 +47,22 @@ class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState
|
|||||||
|
|
||||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||||
stateRef: StateAndRef<OldState>,
|
stateRef: StateAndRef<OldState>,
|
||||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
|
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||||
|
privacySalt: PrivacySalt
|
||||||
): TransactionBuilder {
|
): TransactionBuilder {
|
||||||
val contractUpgrade = upgradedContractClass.newInstance()
|
val contractUpgrade = upgradedContractClass.newInstance()
|
||||||
return TransactionType.General.Builder(stateRef.state.notary)
|
return TransactionType.General.Builder(stateRef.state.notary)
|
||||||
.withItems(
|
.withItems(
|
||||||
stateRef,
|
stateRef,
|
||||||
contractUpgrade.upgrade(stateRef.state.data),
|
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 {
|
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()
|
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||||
// TODO: We need a much faster way of finding our key in the transaction
|
// TODO: We need a much faster way of finding our key in the transaction
|
||||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.corda.core.flows
|
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.identity.Party
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
@ -13,6 +13,7 @@ import net.corda.core.transactions.CoreTransaction
|
|||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||||
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.OpaqueBytes
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
||||||
@ -245,6 +246,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
|||||||
kryo.writeClassAndObject(output, obj.notary)
|
kryo.writeClassAndObject(output, obj.notary)
|
||||||
kryo.writeClassAndObject(output, obj.type)
|
kryo.writeClassAndObject(output, obj.type)
|
||||||
kryo.writeClassAndObject(output, obj.timeWindow)
|
kryo.writeClassAndObject(output, obj.timeWindow)
|
||||||
|
kryo.writeClassAndObject(output, obj.privacySalt)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun attachmentsClassLoader(kryo: Kryo, attachmentHashes: List<SecureHash>): ClassLoader? {
|
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 notary = kryo.readClassAndObject(input) as Party?
|
||||||
val transactionType = kryo.readClassAndObject(input) as TransactionType
|
val transactionType = kryo.readClassAndObject(input) as TransactionType
|
||||||
val timeWindow = kryo.readClassAndObject(input) as TimeWindow?
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,8 @@ data class LedgerTransaction(
|
|||||||
override val id: SecureHash,
|
override val id: SecureHash,
|
||||||
override val notary: Party?,
|
override val notary: Party?,
|
||||||
val timeWindow: TimeWindow?,
|
val timeWindow: TimeWindow?,
|
||||||
val type: TransactionType
|
val type: TransactionType,
|
||||||
|
val privacySalt: PrivacySalt
|
||||||
) : FullTransaction() {
|
) : FullTransaction() {
|
||||||
//DOCEND 1
|
//DOCEND 1
|
||||||
init {
|
init {
|
||||||
|
@ -1,20 +1,37 @@
|
|||||||
package net.corda.core.transactions
|
package net.corda.core.transactions
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.MerkleTree
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.crypto.MerkleTreeException
|
|
||||||
import net.corda.core.crypto.PartialMerkleTree
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
|
import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.util.function.Predicate
|
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
|
* 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
|
* over the flattened components of the underlying transaction structure, taking into account that some
|
||||||
@ -32,6 +49,17 @@ interface TraversableTransaction {
|
|||||||
val notary: Party?
|
val notary: Party?
|
||||||
val type: TransactionType?
|
val type: TransactionType?
|
||||||
val timeWindow: TimeWindow?
|
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:
|
* 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 output that is present
|
||||||
* - Each command that is present
|
* - Each command that is present
|
||||||
* - The notary [Party], if present
|
* - The notary [Party], if present
|
||||||
* - Each required signer ([mustSign]) that is present
|
|
||||||
* - The type of the transaction, if present
|
* - The type of the transaction, if present
|
||||||
* - The time-window 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>
|
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() {
|
get() {
|
||||||
// We may want to specify our own behaviour on certain tx fields.
|
// 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
|
// 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 }
|
notary?.let { result += it }
|
||||||
type?.let { result += it }
|
type?.let { result += it }
|
||||||
timeWindow?.let { result += it }
|
timeWindow?.let { result += it }
|
||||||
|
privacySalt?.let { result += it }
|
||||||
return result
|
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
|
* 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.
|
* 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
|
* 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
|
@CordaSerializable
|
||||||
class FilteredLeaves(
|
class FilteredLeaves(
|
||||||
@ -77,8 +109,21 @@ class FilteredLeaves(
|
|||||||
override val commands: List<Command<*>>,
|
override val commands: List<Command<*>>,
|
||||||
override val notary: Party?,
|
override val notary: Party?,
|
||||||
override val type: TransactionType?,
|
override val type: TransactionType?,
|
||||||
override val timeWindow: TimeWindow?
|
override val timeWindow: TimeWindow?,
|
||||||
|
val nonces: List<SecureHash>
|
||||||
) : TraversableTransaction {
|
) : 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.
|
* Function that checks the whole filtered structure.
|
||||||
* Force type checking on a structure that we obtained, so we don't sign more than expected.
|
* 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) }
|
val checkList = availableComponents.map { checkingFun(it) }
|
||||||
return (!checkList.isEmpty()) && checkList.all { it }
|
return (!checkList.isEmpty()) && checkList.all { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val availableComponentHashes: List<SecureHash> get() = availableComponents.mapIndexed { index, it -> serializedHash(it, nonces[index]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,6 +30,10 @@ data class NotaryChangeWireTransaction(
|
|||||||
check(notary != newNotary) { "The old and new notaries must be different – $newNotary" }
|
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) }
|
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) }
|
||||||
|
|
||||||
fun resolve(services: ServiceHub, sigs: List<DigitalSignature.WithKey>): NotaryChangeLedgerTransaction {
|
fun resolve(services: ServiceHub, sigs: List<DigitalSignature.WithKey>): NotaryChangeLedgerTransaction {
|
||||||
|
@ -33,7 +33,9 @@ open class TransactionBuilder(
|
|||||||
protected val attachments: MutableList<SecureHash> = arrayListOf(),
|
protected val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||||
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||||
protected val commands: MutableList<Command<*>> = 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())
|
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),
|
attachments = ArrayList(attachments),
|
||||||
outputs = ArrayList(outputs),
|
outputs = ArrayList(outputs),
|
||||||
commands = ArrayList(commands),
|
commands = ArrayList(commands),
|
||||||
window = window
|
window = window,
|
||||||
|
privacySalt = privacySalt
|
||||||
)
|
)
|
||||||
|
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
@ -61,6 +64,7 @@ open class TransactionBuilder(
|
|||||||
is Command<*> -> addCommand(t)
|
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 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 TimeWindow -> setTimeWindow(t)
|
||||||
|
is PrivacySalt -> setPrivacySalt(t)
|
||||||
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,7 +73,7 @@ open class TransactionBuilder(
|
|||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
|
|
||||||
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
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)
|
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||||
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction().toLedgerTransaction(services)
|
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 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.
|
// Accessors that yield immutable snapshots.
|
||||||
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
||||||
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
||||||
|
@ -28,7 +28,8 @@ data class WireTransaction(
|
|||||||
override val notary: Party?,
|
override val notary: Party?,
|
||||||
// TODO: remove type
|
// TODO: remove type
|
||||||
override val type: TransactionType,
|
override val type: TransactionType,
|
||||||
override val timeWindow: TimeWindow?
|
override val timeWindow: TimeWindow?,
|
||||||
|
override val privacySalt: PrivacySalt = PrivacySalt()
|
||||||
) : CoreTransaction(), TraversableTransaction {
|
) : CoreTransaction(), TraversableTransaction {
|
||||||
init {
|
init {
|
||||||
checkBaseInvariants()
|
checkBaseInvariants()
|
||||||
@ -92,7 +93,7 @@ data class WireTransaction(
|
|||||||
val resolvedInputs = inputs.map { ref ->
|
val resolvedInputs = inputs.map { ref ->
|
||||||
resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash)
|
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.
|
* 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
|
* @param filtering filtering over the whole WireTransaction
|
||||||
* @returns FilteredLeaves used in PartialMerkleTree calculation and verification.
|
* @returns FilteredLeaves used in PartialMerkleTree calculation and verification.
|
||||||
*/
|
*/
|
||||||
fun filterWithFun(filtering: Predicate<Any>): FilteredLeaves {
|
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(
|
return FilteredLeaves(
|
||||||
inputs.filter { filtering.test(it) },
|
inputs.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index) },
|
||||||
attachments.filter { filtering.test(it) },
|
attachments.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index + offsets[0]) },
|
||||||
outputs.filter { filtering.test(it) },
|
outputs.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index + offsets[1]) },
|
||||||
commands.filter { filtering.test(it) },
|
commands.filterIndexed { index, it -> filterAndNoncesUpdate(filtering, it, index + offsets[2]) },
|
||||||
notNullFalse(notary) as Party?,
|
notNullFalseAndNoncesUpdate(notary, offsets[3]) as Party?,
|
||||||
notNullFalse(type) as TransactionType?,
|
notNullFalseAndNoncesUpdate(type, offsets[4]) as TransactionType?,
|
||||||
notNullFalse(timeWindow) as TimeWindow?
|
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.
|
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
|
||||||
*
|
*
|
||||||
|
@ -99,6 +99,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
|||||||
val attachments = emptyList<Attachment>()
|
val attachments = emptyList<Attachment>()
|
||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
|
val privacySalt: PrivacySalt = PrivacySalt()
|
||||||
val transaction: LedgerTransaction = LedgerTransaction(
|
val transaction: LedgerTransaction = LedgerTransaction(
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
@ -107,7 +108,8 @@ class TransactionTests : TestDependencyInjectionBase() {
|
|||||||
id,
|
id,
|
||||||
null,
|
null,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
TransactionType.General
|
TransactionType.General,
|
||||||
|
privacySalt
|
||||||
)
|
)
|
||||||
|
|
||||||
transaction.verify()
|
transaction.verify()
|
||||||
@ -140,6 +142,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
|||||||
val attachments = emptyList<Attachment>()
|
val attachments = emptyList<Attachment>()
|
||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
|
val privacySalt: PrivacySalt = PrivacySalt()
|
||||||
fun buildTransaction() = LedgerTransaction(
|
fun buildTransaction() = LedgerTransaction(
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
@ -148,7 +151,8 @@ class TransactionTests : TestDependencyInjectionBase() {
|
|||||||
id,
|
id,
|
||||||
notary,
|
notary,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
TransactionType.General
|
TransactionType.General,
|
||||||
|
privacySalt
|
||||||
)
|
)
|
||||||
|
|
||||||
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }
|
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.core.contracts.clauses
|
|||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
import net.corda.core.contracts.CommandData
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
@ -16,7 +17,7 @@ class AllOfTests {
|
|||||||
fun minimal() {
|
fun minimal() {
|
||||||
val counter = AtomicInteger(0)
|
val counter = AtomicInteger(0)
|
||||||
val clause = AllOf(matchedClause(counter), matchedClause(counter))
|
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>>())
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
|
||||||
// Check that we've run the verify() function of two clauses
|
// Check that we've run the verify() function of two clauses
|
||||||
@ -26,7 +27,7 @@ class AllOfTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `not all match`() {
|
fun `not all match`() {
|
||||||
val clause = AllOf(matchedClause(), unmatchedClause())
|
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>>()) }
|
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.core.contracts.clauses
|
|||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
import net.corda.core.contracts.AuthenticatedObject
|
||||||
import net.corda.core.contracts.CommandData
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
@ -15,7 +16,7 @@ class AnyOfTests {
|
|||||||
fun minimal() {
|
fun minimal() {
|
||||||
val counter = AtomicInteger(0)
|
val counter = AtomicInteger(0)
|
||||||
val clause = AnyOf(matchedClause(counter), matchedClause(counter))
|
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>>())
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
|
||||||
// Check that we've run the verify() function of two clauses
|
// Check that we've run the verify() function of two clauses
|
||||||
@ -26,7 +27,7 @@ class AnyOfTests {
|
|||||||
fun `not all match`() {
|
fun `not all match`() {
|
||||||
val counter = AtomicInteger(0)
|
val counter = AtomicInteger(0)
|
||||||
val clause = AnyOf(matchedClause(counter), unmatchedClause(counter))
|
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>>())
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
|
|
||||||
// Check that we've run the verify() function of one clause
|
// Check that we've run the verify() function of one clause
|
||||||
@ -37,7 +38,7 @@ class AnyOfTests {
|
|||||||
fun `none match`() {
|
fun `none match`() {
|
||||||
val counter = AtomicInteger(0)
|
val counter = AtomicInteger(0)
|
||||||
val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter))
|
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) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package net.corda.core.contracts.clauses
|
package net.corda.core.contracts.clauses
|
||||||
|
|
||||||
import net.corda.core.contracts.AuthenticatedObject
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.CommandData
|
|
||||||
import net.corda.core.contracts.ContractState
|
|
||||||
import net.corda.core.contracts.TransactionType
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
@ -23,7 +20,7 @@ class VerifyClausesTests {
|
|||||||
outputs: List<ContractState>,
|
outputs: List<ContractState>,
|
||||||
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
|
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>>())
|
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +33,7 @@ class VerifyClausesTests {
|
|||||||
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
|
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
|
||||||
}
|
}
|
||||||
val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create())
|
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
|
// The clause is matched, but doesn't mark the command as consumed, so this should error
|
||||||
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, listOf(command)) }
|
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, listOf(command)) }
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `building Merkle tree for a transaction`() {
|
fun `building Merkle tree for a tx and nonce test`() {
|
||||||
fun filtering(elem: Any): Boolean {
|
fun filtering(elem: Any): Boolean {
|
||||||
return when (elem) {
|
return when (elem) {
|
||||||
is StateRef -> true
|
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()
|
val d = testTx.serialize().deserialize()
|
||||||
assertEquals(testTx.id, d.id)
|
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(1, leaves.inputs.size)
|
||||||
assertEquals(0, leaves.attachments.size)
|
assertEquals(0, leaves.attachments.size)
|
||||||
assertTrue(mt.filteredLeaves.timeWindow != null)
|
assertEquals(1, leaves.outputs.size)
|
||||||
assertEquals(null, mt.filteredLeaves.type)
|
assertEquals(1, leaves.commands.size)
|
||||||
assertEquals(null, mt.filteredLeaves.notary)
|
assertNull(mt.filteredLeaves.notary)
|
||||||
|
assertNull(mt.filteredLeaves.type)
|
||||||
|
assertNotNull(mt.filteredLeaves.timeWindow)
|
||||||
|
assertNull(mt.filteredLeaves.privacySalt)
|
||||||
|
assertEquals(4, leaves.nonces.size)
|
||||||
assertTrue(mt.verify())
|
assertTrue(mt.verify())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,10 +142,22 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() {
|
|||||||
assertTrue(mt.filteredLeaves.inputs.isEmpty())
|
assertTrue(mt.filteredLeaves.inputs.isEmpty())
|
||||||
assertTrue(mt.filteredLeaves.outputs.isEmpty())
|
assertTrue(mt.filteredLeaves.outputs.isEmpty())
|
||||||
assertTrue(mt.filteredLeaves.timeWindow == null)
|
assertTrue(mt.filteredLeaves.timeWindow == null)
|
||||||
|
assertTrue(mt.filteredLeaves.availableComponents.isEmpty())
|
||||||
|
assertTrue(mt.filteredLeaves.availableComponentHashes.isEmpty())
|
||||||
|
assertTrue(mt.filteredLeaves.nonces.isEmpty())
|
||||||
assertFailsWith<MerkleTreeException> { mt.verify() }
|
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
|
@Test
|
||||||
fun `build Partial Merkle Tree, only left nodes branch`() {
|
fun `build Partial Merkle Tree, only left nodes branch`() {
|
||||||
val inclHashes = listOf(hashed[3], hashed[5])
|
val inclHashes = listOf(hashed[3], hashed[5])
|
||||||
|
@ -21,7 +21,10 @@ import net.corda.core.serialization.deserialize
|
|||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.node.services.vault.schemas.requery.*
|
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 net.corda.testing.contracts.DummyContract
|
||||||
import org.h2.jdbcx.JdbcDataSource
|
import org.h2.jdbcx.JdbcDataSource
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -117,6 +120,7 @@ class VaultSchemaTest : TestDependencyInjectionBase() {
|
|||||||
val attachments = emptyList<Attachment>()
|
val attachments = emptyList<Attachment>()
|
||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
|
val privacySalt: PrivacySalt = PrivacySalt()
|
||||||
transaction = LedgerTransaction(
|
transaction = LedgerTransaction(
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
@ -125,7 +129,8 @@ class VaultSchemaTest : TestDependencyInjectionBase() {
|
|||||||
id,
|
id,
|
||||||
notary,
|
notary,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
TransactionType.General
|
TransactionType.General,
|
||||||
|
privacySalt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +152,7 @@ class VaultSchemaTest : TestDependencyInjectionBase() {
|
|||||||
val attachments = emptyList<Attachment>()
|
val attachments = emptyList<Attachment>()
|
||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
|
val privacySalt: PrivacySalt = PrivacySalt()
|
||||||
return LedgerTransaction(
|
return LedgerTransaction(
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
@ -155,7 +161,8 @@ class VaultSchemaTest : TestDependencyInjectionBase() {
|
|||||||
id,
|
id,
|
||||||
notary,
|
notary,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
TransactionType.General
|
TransactionType.General,
|
||||||
|
privacySalt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ class ContractUpgradeHandler(otherSide: Party) : AbstractStateReplacementFlow.Ac
|
|||||||
val authorisedUpgrade = serviceHub.vaultService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
val authorisedUpgrade = serviceHub.vaultService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||||
val proposedTx = proposal.stx.tx
|
val proposedTx = proposal.stx.tx
|
||||||
val expectedTx = ContractUpgradeFlow.assembleBareTx(oldStateAndRef, proposal.modification).toWireTransaction()
|
val expectedTx = ContractUpgradeFlow.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||||
requireThat {
|
requireThat {
|
||||||
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
|
"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)
|
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade)
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package net.corda.vega.flows
|
package net.corda.vega.flows
|
||||||
|
|
||||||
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.contracts.StateAndRef
|
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.AbstractStateReplacementFlow
|
||||||
import net.corda.core.flows.StateReplacementException
|
import net.corda.core.flows.StateReplacementException
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.vega.contracts.RevisionedState
|
import net.corda.vega.contracts.RevisionedState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flow that generates an update on a mutable deal state and commits the resulting transaction reaching consensus
|
* 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 {
|
object StateRevisionFlow {
|
||||||
class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
||||||
@ -18,6 +19,8 @@ object StateRevisionFlow {
|
|||||||
val state = originalState.state.data
|
val state = originalState.state.data
|
||||||
val tx = state.generateRevision(originalState.state.notary, originalState, modification)
|
val tx = state.generateRevision(originalState.state.notary, originalState, modification)
|
||||||
tx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
tx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
||||||
|
val privacySalt = PrivacySalt()
|
||||||
|
tx.setPrivacySalt(privacySalt)
|
||||||
|
|
||||||
val stx = serviceHub.signInitialTransaction(tx)
|
val stx = serviceHub.signInitialTransaction(tx)
|
||||||
val participantKeys = state.participants.map { it.owningKey }
|
val participantKeys = state.participants.map { it.owningKey }
|
||||||
|
Loading…
Reference in New Issue
Block a user