Unconstraint Signature Constraint Migration
November 18, 2019
In this blockchain world where autonomous, independent nodes interact with each other, contract upgrades are essential. We cannot assume that a contract will never change. Developers will make mistakes, and security bugs will need to be fixed, and client requirements will change.
A few days back, I was asked a question on slack about contract upgrades and hash constraint. They mentioned they were quite fascinated about the signature constraint released in Corda version 4.0. They were enthusiastic about using the new and simpler signature constraint solution. They were willing to move from hash constraint to signature constraint and were interested to know the correct way to do this. So I decided to write a blog about how you can migrate from older constraints (hash and whitelist) to newer signature constraint, what are these constraints, and why do we use them.
So let’s look into the WHAT, WHY, and HOW around contract constraints.
WHY use Contract Constraints
In Corda, data and contract logic are separate. The contract logic is not embedded in the transaction, instead referred to from the transaction. This jar contains the contract logic. When a transaction is built, the contract jar is referred to by the transaction as an attachment. Once the transaction is committed to the ledger, the reference to this jar is also committed. Going forward, whenever this transaction will be verified, this reference will be used to refer to the contract and run the contract code for verification.
The ledger is going to grow. Transaction chains will be created, and contracts will be upgraded. We want to make sure even after 30 years, the same contract which was used 30 years back will be used to verify that specific transaction. Hence contract jars are added as attachments to the transactions.
For a malicious user, this is an easy hack. Create a java class, compile it, bundle it into a jar, and somehow maliciously change the reference from their transaction to point to this new contract jar (upgrade to new contract), which transfers money from your account to their account. BOOM!
This is the kind of problem you might face, and constraints are a way to address them. A contract governs each state, and a constraint governs each contract. Each state specifies which contract and contract constraint it will use when it is first created. Transactions have the jar containing the contract and the states attached to them. Before running the verification code of a contract, the jar containing the contract is tested for compliance by the contract constraints. If there is a breach in the constraints, the transaction is rejected.
WHAT are the different types of Contract
Hash Constraint
The very first constraint which was used by default was a Hash Constraint. Only a contract of a specified hash can be used. This is similar to Bitcoin and Ethereum, the code is locked down and cannot be changed. The only way to upgrade will be to take approval from all parties and wait till everyone agrees. This is done by explicitly running a ContractUpgradeFlow.
WhiteListZone Constraint
The constraint (hash of the contract jar) is included in the network-param file. The states will refer to these contract jars to perform upgrades. With time new contracts jars hash can be added to this file to perform a contract upgrade. This is the implicit way to perform a contract upgrade. This is much more flexible than a hash constraint, but involves the network operator to update the network-param file, and requires a flag day.
Signature Constraint
Signature Constraint is the most flexible and recommended solution. It allows a state to use any version of a contract that is signed by the original key (single or composite) that was used to install the contract jar for the first time. The keys are not held in the network parameters, giving the application developer a lot of flexibility in upgrading and distributing new versions of contract code.
HOW to migrate to Signature Constraint
When a transaction is built, unless specified, the output state uses the constraint of the input state by default. This helps to ensure the output state is spent in a similar way it was created. Before Corda version 4, the constraint propagation logic had to be enforced in the contract. As of version 4, the logic is handled by the platform.
Migrate From Hash Constraint to Signature Constraint
New logic has been added to the platform to allow you to migrate from hash constraints to signature constraints. Performing this migration requires each node to set the disableHashConstraint node property to true.
Any transaction which has input states issued with hash constraint will enforce the use of same hash constraint for output states. To migrate from hash to signature follow the steps below-
- Stop your node.
- Sign the jar. To externally sign the jar use the jarsigner. By default, if you do not disable signing in Corda version 4, the jar is signed when you run the deployNodes task.
- Replace the old jar with this newly signed jar. Though you replace the old jar, the old jar is available with the node in the AttachmentStorage.
- Start the node by disabling the hash constraint, by setting the Java system property to -Dnet.corda.node.disableHashConstraints=”true”. This will disable the hash to signature check by the platform. If you start the flow without disabling the hash constraint and try to consume a hash constraint in input state and use signature constraint for output state, the flow will throw below exception.
☠ Contract constraints failed for corda.samples.upgrades.contracts.OldContract, transaction: 1DA975800FBF014756BAE52F9B28C1ED0008AB5D9FDD98AA09EC9E7985B387B1
5. You can explicitly specify SignatureAttachmentConstraint to use while adding the output state to the transaction builder. If not specified by default, the signature constraint will be used if the jar is signed.
// Create the key that will have to pass for all future versions. PublicKey ownersKey = signers.get(0);
transactionBuilder.addOutputState(output, "corda.samples.upgrades.contracts.OldContract", new SignatureAttachmentConstraint(ownersKey));
/* If you do not specify contract and SignatureAttachmentConstraint explicitly, by default SignatureAttachmentConstraint is used if the jar is signed. @BelongsToContract which binds state to a contract is used to determine which contract to use when not specified. */
//transactionBuilder.addOutputState(output);
6.Start the node.
7. Run a flow which consumes old hash constraint states and issues new signature constraint states. You should see the new states using signature constraints.
Migrate From WhiteListZone to SignatureConstraint
WhiteListZone trusts the contract jar hash, which has been added to network param file, and signature constraint requires the jar to be signed. So it makes sense to add this signed jars hash to whitelist zone network param, to migrate from whiteListZone to signature constraint.
The network parameters are a set of values that every node participating in the zone needs to agree on and use to communicate with each other correctly. Therefore they need to be set before the Network Map service can be started. Setting/updating the network param file in dev mode can be done using network-bootstrapper-tool.jar. In CENM, Network Map jar is started in a unique “set network parameters” flag to set/update the network param file. So the organisation running the network map service needs to take care of adding the signed jars hash to the network param file.
- Stop the node. Sign the old jar and replace the old jar with this signed jar.
- Add this signed jars hash to whitelistedContractImplementations to the network param file.
- Create a new file named include_whitelist.txt, add the full path of contract class to this file.
- To update the network param of the bootstrapped network in dev mode, place the network-bootstrapper-tool, signed contract jar, include_whitelist.txt in the nodes folder, and run the below command.
java -jar network-bootstrapper.jar --dir .
5. This updates the network param and adds the signed jars hash to whitelistedContractImplementations in the network param file.
Updated NetworkParameters { minimumPlatformVersion=4 ……… whitelistedContractImplementations { corda.samples.upgrades.contracts.OldContract=[B66F8B2E768D5809F281160437C7C240E92E340E9128441D89E180D1A86F127E, 26973C032E20F57E62CCC7A5F0EA883E21619D433B706A743D5AD08806B7924E] } ……… }
6. You can specify explicitly SignatureAttachmentConstraint to use while adding the output state to the transaction builder. If not specified by default, the signature constraint will be used if the jar is signed.
// Create the key that will have to pass for all future versions. PublicKey ownersKey = signers.get(0);
transactionBuilder.addOutputState(output, "corda.samples.upgrades.contracts.OldContract", new SignatureAttachmentConstraint(ownersKey));
/* If you do not specify contract and SignatureAttachmentConstraint explicitly, by default SignatureAttachmentConstraint is used if the jar is signed. @BelongsToContract which binds state to a contract is used to determine which contract to use when not specified. */
transactionBuilder.addOutputState(output);
7. Start the node.
8. Run a flow which consumes old whiteListZone constraint states and issues new signature constraint states. You should see the new states using signature constraints.
Conclusion
Using the steps detailed in this post, you now know how to migrate away from the older hash and whitelist contract constraints towards the shiny signature constraints. Once on these constraints, your cordapp can be upgraded and accepted more easily across the network.
Clone this repository, follow the instructions in README.txt to perform constraint migrations.
Thanks to Dan Newton, Tudor, and The Corda Team.
Thanks for reading — Sneha Damle, Developer Evangelist (R3).
Unconstraint Signature Constraint Migration was originally published in Corda on Medium, where people are continuing the conversation by highlighting and responding to this story.