Skip to content

Commit

Permalink
Nonce per tx component (corda#1125)
Browse files Browse the repository at this point in the history
Add salt to wire tx and nonces to tx components
  • Loading branch information
Konstantinos Chalkias authored Jul 31, 2017
1 parent 907ef97 commit 36b50aa
Show file tree
Hide file tree
Showing 17 changed files with 223 additions and 58 deletions.
20 changes: 20 additions & 0 deletions core/src/main/kotlin/net/corda/core/contracts/Structures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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." }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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? {
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -54,6 +84,7 @@ interface TraversableTransaction {
notary?.let { result += it }
type?.let { result += it }
timeWindow?.let { result += it }
privacySalt?.let { result += it }
return result
}

Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -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]) }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())

/**
Expand All @@ -46,7 +48,8 @@ open class TransactionBuilder(
attachments = ArrayList(attachments),
outputs = ArrayList(outputs),
commands = ArrayList(commands),
window = window
window = window,
privacySalt = privacySalt
)

// DOCSTART 1
Expand All @@ -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}")
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
67 changes: 57 additions & 10 deletions core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
}

/**
Expand All @@ -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.
*
Expand Down
Loading

0 comments on commit 36b50aa

Please sign in to comment.