mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +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.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." }
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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]) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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() }
|
||||
|
@ -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>>()) }
|
||||
}
|
||||
}
|
||||
|
@ -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>>())
|
||||
}
|
||||
|
@ -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)) }
|
||||
}
|
||||
|
@ -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])
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 }
|
||||
|
Loading…
Reference in New Issue
Block a user