From release-M13 of Corda, in the CordApp-Tutorial example, there are some constraints checks made within the flow itself (ExampleFlow.Acceptor). My question is, what constraints may I check on the flow, and what constraints in the Contract? Or it's just an organization issue?
This is a great question. I believe you are referring to:
#InitiatedBy(Initiator::class)
class Acceptor(val otherParty: Party) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.iou.value < 100)
}
}
return subFlow(signTransactionFlow)
}
}
The CollectSignaturesFlow and its counterpart, the SignTransactionFlow automates the collection of signatures for any type of transaction. This automation is super useful because developers don't have to manually write flows for signature collection anymore! However, developers have to be aware that given any valid transaction - as per the referenced contract code in the transaction - the counter-party will always sign! This is because transactions are validated in isolation, not relative to some expected external values.
Let me provide two examples:
If I have access to one of your unspent cash states from a previous transaction, then perhaps I could create a cash spend transaction (from you to me) and ask you to sign it via the CollectSignaturesFlow. If the transaction is valid then, without any additional checking, you'll sign it which will result in you sending me the cash. Clearly you don't want this!
The contract code can only go part of the way to verifying a transaction. If you want to check that the transaction represents a deal you want to enter into e.g. price < some amount then you'll have to do some additional checking. The contract code cannot opine on what constitutes a commercially viable deal for you. This checking has to be done as part of the SignTransactionFlow by overriding signTransaction
In a production CorDapp, one may wish to defer to human judgement on whether to sign a transaction and enter into a deal. Or alternatively, this process could be automated by reaching out to some external reference data system via HTTP API or MQ to determine if the deal is one that should be entered into.
In the code example above, we added two simple constraints:
One which prevents a borrower from creating an overly large (greater than 100) IOU state
One which ensures the transaction does indeed contain an IOU state and not some other state we are not expecting
Note that these two constraints cannot be placed inside the contract code. Contract code is more appropriate for defining the constraints that govern how an asset or agreement should evolve over time. For example, with regards to an IOU:
Issuances must be signed by lender and borrower
Issuances must be for a value greater than zero
Redemptions must involve a cash payment of the correct currency
The IOU must be redeemed before the expiry date
More examples available here: https://github.com/roger3cev/iou-cordapp-v2/blob/master/src/main/kotlin/net/corda/iou/contract/IOUContract.kt
Remember, Corda is designed for potentially mutually distrusting parties to enter into consensus about shared facts. As such, nodes cannot implicitly trust what they receive over the wire from their counter-parties, therefore we always have to check what we receive is what we expect to receive.
Hope that makes sense!
Related
What is literally happening in multiple responder flow?Are we communicating with multiple nodes?Can we do use multiple responder flow for some other purpose?
Imagine this, you (the initiator) is applying for a loan; you go to 5 different banks (i.e. 5 responders). Every bank has their own criteria to approve your loan application (one bank requires you to own a house as a guarantee, another requires you to have an annual salary higher than 100,000, and so on).
So even though all responding nodes can use the responder flow that was supplied by the organization that wrote the initiating flow; they are not obliged too, actually it is the responsibility of the responding node to write its own version of the responder flow to implement its own business rules.
If the received transaction passes those business rules, the responder approves it (i.e. it signs the transaction); otherwise it rejects it (i.e. throws a FlowException).
I suppose you are referring to the overriding of responder flows. It allows developers to configure a node to respond using an overridden responder flow rather than the base responder.
You could find more details here:
https://docs.corda.net/docs/corda-os/4.5/flow-overriding.html
As the previous comments say, you can choose to create multiple responder flows or having a single one that implements different logics depending on the information stored in the responding node. There are multiple example in the source code of Corda, if you look for flow handlers, like this one below (on github) where you can see that different logics apply depending on the role of the responding node (set by the InitiatingFlow, in this case):
class ObserverAwareFinalityFlowHandler(val otherSession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val role = otherSession.receive<TransactionRole>().unwrap { it }
val statesToRecord = when (role) {
TransactionRole.PARTICIPANT -> StatesToRecord.ONLY_RELEVANT
TransactionRole.OBSERVER -> StatesToRecord.ALL_VISIBLE
}
// If states are issued to self, then ReceiveFinalityFlow does not need to be invoked.
if (!serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) {
subFlow(ReceiveFinalityFlow(otherSideSession = otherSession, statesToRecord = statesToRecord))
}
}
}
I was going through Corda training material and there is one activity "Impose a constraint on the required signatures in IOUContract.verify". How to implement this constraint requiring both the participant (lender and borrower) to sign the
transaction?
If anybody is aware of then please let me know.
My code:
if (!((command.getSigners().contains(state.getLender().getOwningKey())) && (command.getSigners().contains(state.getBorrower().getOwningKey()))))
throw new IllegalArgumentException("Both lender and borrower together only may sign IOU issue transaction.");
Your code looks legit.
If you cannot pass the test, please make sure the getter of the lender and the borrower are correct in the IOUState class.
If you are uncertain, you can look into this contract class example.
Also, you can check here for an alternative syntax to implement the contract class.
The code you paste in is from the contract of a cordapp. You will initiate the signature collection in the flow of the cordapp.
Let's assume the case that there are only lender and borrower in this transaction (no third party moderator), and the lender is the flow initiator. The lender will draft the state and sign it himself, and sent a half signed transaction to the borrower via a session and the borrower will check it and sign it via the responder.
At that moment you will have a fully signed transaction.
As for code, I would suggest you to take look at this simple negotiation cordapp for reference. https://github.com/corda/samples/blob/release-V4/negotiation-cordapp/workflows-kotlin/src/main/kotlin/negotiation/flows/AcceptanceFlow.kt
val partStx = serviceHub.signInitialTransaction(txBuilder)
// Gathering the counterparty's signature.
val counterparty = if (ourIdentity == input.proposer) input.proposee else input.proposer
val counterpartySession = initiateFlow(counterparty)
val fullyStx = subFlow(CollectSignaturesFlow(partStx, listOf(counterpartySession)))
The state contract will enforce who are the required signers. In this example, the contract is expecting all participants (i.e. lender and borrower) to be required signers of the Create command.
When the flow is crafting a transaction to create a new IOU, it must specify which command to use (Create IOU in our case); and who will sign this command (so the flow will work on collecting those signatures).
The flow will verify the transaction before signing it, which in return will run the contract; if the requested signatures that were specified on the Create command inside the flow don't match what the contract is expecting to see as required signers; then the contract will fail the transaction. Otherwise the contract will pass the transaction, and the flow will proceed to collect the signature of the lender, then collect the signature of the borrower.
So in short, the contract will say who are the required signers for a certain state command, and the flow that's creating a transaction for that state/command combination must obey the contract rules by specifying the same set of signers and collect their signatures.
Thanks, everyone for the detailed explanation. I have implemented the below code and it's working fine now.
List<PublicKey> signers = tx.getCommands().get(0).getSigners();
HashSet<PublicKey> signersSet = new HashSet<>();
for (PublicKey key: signers) {signersSet.add(key);}
List<AbstractParty> participants = tx.getOutputStates().get(0).getParticipants();
HashSet<PublicKey> participantKeys = new HashSet<>();
for (AbstractParty party: participants) {participantKeys.add(party.getOwningKey());}
require.using("Both lender and borrower together only may sign IOU issue transaction.", signersSet.containsAll(participantKeys) && signersSet.size() == 2);
Recently I've been looking into the security and vulnerability aspects of contract commands in Corda. A debate arose as to whether some contract command constraints should be strict or whether they should be relaxed in order to allow transaction composition of different inputs, outputs and commands.
The problem is that whilst I can see the benefit of allowing transaction composition, I feel like relaxed contract command constraints actually pose security vulnerabilities, and in my opinion, it would be better to secure against these vulnerabilities at the contract level, in such that the signing participants of the command reach consensus through contract verification as a whole, rather than relying on flow level checks, which could be overlooked by a developer or circumvented by a malicious node.
Example - Bankruptcy Declaration
This example allows nodes on the network to declare bankruptcy. Assume in this case that a bankruptcy declaration state is simply the identity of the node declaring bankruptcy, and a reason.
#BelongsToContract(BankruptcyDeclarationContract::class)
data class BankruptcyDeclarationState(
override val owner: AbstractParty,
val reason: String
) : OwnableState { ... }
Strict Verification
Strict verification requires that, on issuance...
Zero input states must be consumed.
One output state must be created.
Only the owner must sign.
fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Zero input states must be consumed." using (tx.inputs.isEmpty())
"One output state must be created." using (tx.outputs.size == 1)
val state = tx.outputsOfType<BankruptcyDeclarationState>().single()
"Only the owner must sign." using (state.owner.owningKey == signers.single())
}
Relaxed Verification
Relaxed verification requires that, on issuance...
Zero input states of type BankruptcyDeclarationState must be consumed.
One output state of type BankruptcyDeclarationState must be created.
Only the owner must sign.
fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
val inputs = tx.inputsOfType<BankruptcyDeclarationState>()
val outputs = tx.outputsOfType<BankruptcyDeclarationState>()
"Zero input states of type BankruptcyDeclarationState must be consumed." using
(inputs.isEmpty())
"One output state of type BankruptcyDeclarationState must be created." using
(outputs.size == 1)
"Only the owner must sign." using (outputs.single().owner.owningKey == signers.single())
}
Observations
Strict verification ensures that the inputs and outputs are checked globally, rather than checking for specific input and output types, however this has a disadvantage that transaction composition of inputs and outputs is impossible.
Relaxed verification ensures that only inputs and outputs of the required state type are checked, which would allow transaction composition of different input and output types.
The key here is that only the node declaring bankruptcy must sign, which means that issuance of a BankruptcyDeclarationState can only occur from that node. Nobody else should be allowed to declare bankruptcy on behalf of another node on the network.
Identifying The Vulnerability
Suppose we chose to model our contract command constraints to be relaxed, so that we can compose transactions. Also, suppose that we have a contract command for some ObligationState which when issued, requires that:
Zero input states of type ObligationState must be consumed.
One output state of type ObligationState must be created.
The obligor and obligee must sign.
Now that we have two state types and two contract commands, we can compose a transaction that uses both, and identify the vulnerability. Assume here that bob is initiating this transaction.
val transaction = with(TransactionBuilder(notary)) {
addOutputState(ObligationState(alice, bob), ObligationContract.ID)
addCommand(ObligationContract.Issue(), aliceKey, bobKey)
addOutputState(BankruptcyDeclarationState(alice, "..."), BankruptcyDeclarationContract.ID)
addCommand(BankruptcyDeclarationContract.Issue(), aliceKey)
}
Remember that only the owner of a BankruptcyDeclarationState must sign, but the obligor and obligee of an ObligationState must sign, therefore this initiating flow will collect signatures from the required counter-parties. The vulnerability here is that bob initiates this transaction, but includes an output of type BankruptcyDeclarationState which is owned by alice. He shouldn't be allowed to do that because only the owner should be allowed to issue BankruptcyDeclarationState but in this case alice will unwittingly sign because of the requirement to sign for the ObligationState.
There is an argument to be made here that the flows could be written in such a way that alice would check the transaction before signing to ensure that certain states were not included, but I do not feel like this is enough. This requires developers and node administrators to carry out due diligence of flows ensuring their security.
In contrast, strict contract command constraints would prevent these vulnerabilities in what I believe to be a much more secure way - due diligence is therefore only required at a contract level, rather than from every developer writing flows that consume the contracts.
What I am looking for in this respect is some definitive guide to whether contract command constraints should be strict, relaxed, or whether there are other considerations to be made that I've missed. Thanks.
As you pointed out correctly, the identical contract code is shared among all transacting parties. That is the only agreement among them.
But each party is responsible for his/her action (signing) by developing his/her own secure flows. The fundamental in writing flows is to verify the transaction against the contract code before signing. Who would sign anything digitally or otherwise without reading/checking the contract?
Did I miss anything?
I'm trying to understand how corda oracles work from an example on github. It seems like in every example oracle verification function checks the data in command and data in output state. I don't understand why that should work because we (issuer node) manage that data and put it in command/output state.
// Our contract does not check that the Nth prime is correct. Instead, it checks that the
// information in the command and state match.
override fun verify(tx: LedgerTransaction) = requireThat {
"There are no inputs" using (tx.inputs.isEmpty())
val output = tx.outputsOfType<PrimeState>().single()
val command = tx.commands.requireSingleCommand<Create>().value
"The prime in the output does not match the prime in the command." using
(command.n == output.n && command.nthPrime == output.nthPrime)
}
In this example state gets Nth prime number from oracle but after it's issued the verification function doesn't rerun generateNth prime function to make sure that this number is really the one we needed. I understand that data in this example is deterministic since Nth prime cannot change but what about the case where we have dynamic data like stock values? Shouldn't oracle verification function also send another http request and get current values to check them?
Firstly, note that contracts in Corda are not able to access the outside world in any way (DB reads, HTTP requests, etc.). If they could, transaction validity would be non-deterministic. A transaction that is found to be valid on day n may become invalid on day n+1 (because a database row changed, or a website went down, etc.). This would cause disagreements about whether a given transaction was a valid ledger update.
However, we sometimes need a transaction to include external data for verification (whether a company is bankrupt, whether a natural catastrophe happened, etc.). To do this, we use a trusted oracle that only signs the transaction if a given piece of data is valid.
We could embed the information in the input or output states. However, this would require us to reveal the entire input or output state to the oracle for signing. For privacy reasons, it is therefore preferable to embed the data in a command that only contains the data of interest to the oracle, so that we can filter out all the other parts of the transaction and only present this command to the oracle for signing.
The oracle will usually perform a DB read or make an HTTP request to check the validity of the data before signing.
The commands webinar states (at around 3:10) that "input and output states are always grouped by type and that a command is required for each group." The narrator seems to imply that if a transaction consists of multiple commands then each command will be associated with a distinct subset of the state transitions proposed in the transaction.
However such a view of things does not seem to be captured in the structure of LedgerTransaction. It consists of completely independent lists of inputs, outputs and commands. There's nothing denoting an association between particular commands and particular input or output states.
In my contract code I can group states by type, e.g.:
override fun verify(tx: LedgerTransaction) {
val fooStates = tx.inputsOfType<FooState>()
val barStates = tx.inputsOfType<BarStates>()
But I'm just grouping by choice - I don't have to, and there's nothing tying these groups to particular commands.
So what is the webinar referring to when it says "a command is required for each group"?
The sense behind signatures being associated with commands would be clear if the relationship between commands and state transitions existed as described in the webinar. However in reality one doesn't sign off on particular transitions on a per command basis as the LedgerTransaction class does not capture such relationships.
In the key concepts section on commands one has a coupon command and a pay command and it makes sense that the set of people who have to sign off on the bond state transition may be different to those who need to sign off on the cash state transition. But in the code there's nothing tying a coupon command to the particular states that the signers associated with the command are agreeing to if they sign.
Is this stated requirement that each group must have an associated command just something that the developer should implement in their contract verify logic without being something that one tries to capture in the structure of transactions?
Good question.
You touched on grouping within the contract, and that's correct it is down to the contract implementation, you just need to extend it further to enforce which parties are required to sign depending on the command in the transaction.
So, your verify function might look like the below simplified version of the contract within the Option CorDapp sample:
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Commands>()
when (command.value) {
is Commands.Issue -> {
requireThat {
val cashInputs = tx.inputsOfType<Cash.State>()
val cashOutputs = tx.outputsOfType<Cash.State>()
"Cash.State inputs are consumed" using (cashInputs.isNotEmpty())
"Cash.State outputs are created" using (cashOutputs.isNotEmpty())
val option = tx.outputsOfType<OptionState>().single()
"The issue command requires the issuer's signature" using (option.issuer.owningKey in command.signers)
}
}
is Commands.Trade -> {
requireThat {
val cashInputs = tx.inputsOfType<Cash.State>()
val cashOutputs = tx.outputsOfType<Cash.State>()
"Cash.State inputs are consumed" using (cashInputs.isNotEmpty())
"Cash.State outputs are created" using (cashOutputs.isNotEmpty())
val inputOption = tx.inputsOfType<OptionState>().single()
val outputOption = tx.outputsOfType<OptionState>().single()
"The transfer command requires the old owner's signature" using (inputOption.owner.owningKey in command.signers)
"The transfer command requires the new owner's signature" using (outputOption.owner.owningKey in command.signers)
}
}
else -> throw IllegalArgumentException("Unknown command.")
}
}
We first pull the command (or commands) from the transaction and that gives us context.
Based on that we can then pull the states we're interested in from either the inputs or outputs e.g. cash/option and begin to check that our constraints are met.
You can find the full version of the above sample at https://github.com/CaisR3/cordapp-option and the contract code can be found in base\src\main\kotlin\net\corda\option\base\contract\OptionContract.kt