Assume a linear state with 3 parties. How does the contract enforce that only the borrower can move the linear state from one lifecycle to another?
So far the method the I'm using is on the responder side I'm doing require(obligation.borrower == otherFlow.counterparty) Not sure how one would go about enforcing in the contract as contract doesn't have access to who is initiating the transaction.
signers == allParticipantsKeys just enforces everybody signed but not who should/should not be spending the state?
Can a non-participant who thru some means got hold of the state/historical txes be able to spend it somehow?
Lender : Party
Borrower : Party
Bank : Party
participants = listOf(Lender, Borrower, Bank)
You cannot enforce rules about who proposes a transaction.
However, you can enforce that the borrower is a required signer, by writing something like this in the contract:
requireThat {
val linearState = tx.inputsOfType<MyLinearState>().single()
val allRequiredSigners = tx.commands.flatMap { it.signers }
"The borrower is a required signer." using
allRequiredSigners.contains(linearState.borrower)
}
Then you simply need to write the flow so that the borrower will refuse to sign if they are not the proposer of the transaction.
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);
I am learning Account concept which is released in Corda 4.3. The concept also allows node sign the transaction by using account key rather than node key. I look into few aspects and have still questions that:
In which case we should sign transaction with account key rather than node key?
What would be a crucial benefit to use account key signing over node key?
The framework allows transaction between accounts in the same node to be signed with account key. Why should we do that?
Thank you in advance.
It's not about what's the crucial benefit of signing with an account key instead of a node key, it's what your state contract dictates.
For instance if you look at the gold-trading example:
The state has an attribute owningAccount:
data class LoanBook(val dealId: UUID, val valueInUSD: Long,
val owningAccount: PublicKey? = null) : ContractState
The contract dictates that the owningAccount is a required signer for the TRANSFER command:
inputGold.owningAccount?.let {
require(inputGold.owningAccount in command.signers) {
"The account sending the loan must be a required signer" }
}
Notice how the flow signs the transaction with the node's key (because the initiator of a flow is a required signer), and the owningAccount's key (because the contract dictates that):
val locallySignedTx = serviceHub.signInitialTransaction(transactionBuilder,
listOfNotNull(loanBook.state.data.owningAccount,
ourIdentity.owningKey))
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?
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!