Defining transaction tear-offs
This tutorial will take you through the steps involved in defining a transaction tear-off.
Introduction
A transaction tear-off is a form of filtered transaction, in which the transaction proposer(s) uses a nested Merkle tree approach to “tear off” any parts of the transaction that the oracle/notary doesn’t need to see before presenting it to them for signing. Transaction tear-offs are used to hide transaction components for privacy purposes. With a transaction tear-off, oracles and non-validating notaries can only see their “related” transaction components, but not the full transaction details.
Filtering the transaction fields
Suppose we want to construct a transaction that includes commands containing interest rate fix data as in
Writing oracle services. Before sending the transaction to the oracle to obtain its signature, we need to filter out every part
of the transaction except for the Fix
commands.
To do so, we need to create a filtering function that specifies which fields of the transaction should be included. Each field will only be included if the filtering function returns true when the field is passed in as input.
val filtering = Predicate<Any> {
when (it) {
is Command<*> -> oracle.owningKey in it.signers && it.value is Fix
else -> false
}
}
Constructing a filtered transaction
We can now use our filtering function to construct a FilteredTransaction
:
val ftx: FilteredTransaction = stx.buildFilteredTransaction(filtering)
In the oracle example, this step takes place in RatesFixFlow
by overriding the filtering
function. See
Using an oracle.
Both WireTransaction
and FilteredTransaction
inherit from TraversableTransaction
, so access to the
transaction components is exactly the same. Note that unlike WireTransaction
,
FilteredTransaction
only holds data that we wanted to reveal (after filtering).
// Direct access to included commands, inputs, outputs, attachments etc.
val cmds: List<Command<*>> = ftx.commands
val ins: List<StateRef> = ftx.inputs
val timeWindow: TimeWindow? = ftx.timeWindow
// ...
Implementing transaction signing
The following code snippet is taken from NodeInterestRates.kt
and implements a signing part of an Oracle.
fun sign(ftx: FilteredTransaction): TransactionSignature {
ftx.verify()
// Performing validation of obtained filtered components.
fun commandValidator(elem: Command<*>): Boolean {
require(services.myInfo.legalIdentities.first().owningKey in elem.signers && elem.value is Fix) {
"Oracle received unknown command (not in signers or not Fix)."
}
val fix = elem.value as Fix
val known = knownFixes[fix.of]
if (known == null || known != fix)
throw UnknownFix(fix.of)
return true
}
fun check(elem: Any): Boolean {
return when (elem) {
is Command<*> -> commandValidator(elem)
else -> throw IllegalArgumentException("Oracle received data of different type than expected.")
}
}
require(ftx.checkWithFun(::check))
ftx.checkCommandVisibility(services.myInfo.legalIdentities.first().owningKey)
// It all checks out, so we can return a signature.
//
// Note that we will happily sign an invalid transaction, as we are only being presented with a filtered
// version so we can't resolve or check it ourselves. However, that doesn't matter much, as if we sign
// an invalid transaction the signature is worthless.
return services.createSignature(ftx, services.myInfo.legalIdentities.first().owningKey)
}
FilteredTransaction
is constructed ensures that after signing of the root hash, it is impossible to add or remove
components (leaves). However, it can happen that having transaction with multiple commands one party reveals only subset of them to the Oracle.
As signing is done now over the Merkle root hash, the service signs all commands of a given type, even though it didn’t see
all of them. In the case, however, where all of the commands should be visible to an oracle, you can add ftx.checkAllComponentsVisible(COMMANDS_GROUP)
before invoking ftx.verify
.
checkAllComponentsVisible
uses a sophisticated, underlying partial Merkle tree check to guarantee that all of the components of a particular group that existed in the original WireTransaction
are included in the received FilteredTransaction
.Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.