Corda architecture

This section describes the architecture of Deon Digital’s solution for managing contracts written in the Contract Specification Language (CSL) using the Corda distributed ledger technology for storing contract instances and their tamper-proof histories. For detailed background information about Corda’s architecture, please refer to the Corda documentation. We summarize the most important Corda concepts in the next section.

The reader is assumed to have knowledge of the CSL language, which is documented in the Deon Digital CSL platform documentation.

Demonstration applications can be downloaded from Deon Digital’s public GitLab.

Corda Summary

This section briefly summarizes the main concepts of the Corda system. Note that the Corda nomenclature has concepts called contract and contract state which are different from the similarly named concepts in the context of CSL. To avoid confusion, we will always write “Corda contract” and “Corda contract state” and reserve “contract” and “contract state” for the CSL concepts.


Corda is a platform for maintaining the state of legal agreements between a number of mutually authenticated parties in a distributed fashion, where each party hosts one or more nodes that participate in a network. The nodes act as application servers for “Corda Distributed Apps”, also called CorDapps, which are custom-built components written in Java or Kotlin that make for the main extension point of the platform. A CorDapp comes in two flavors:

States CorDapp
Provides custom data types for the ledger along with custom code for validating transactions updating these types.
Workflows CorDapp
Provides custom workflows (typically just called flows) written using the flow framework. Flows are protocol implementions that the nodes use to create, share and sign transactions in the network.

Furthermore, CorDapps may contain services that run within the node process and has full access to the node API. Services are used to provide auxiliary functionality for CorDapps when hosting such functionality outside the node process is impractical, e.g. when low-latency API access is required.

Node owners communicate with their nodes via an authenticated RPC protocol. The protocol allows node owners to query the state of the ledger and initiate flows.

Corda Data Model

Corda is a distributed ledger where all parties are fully authenticated by a central identity manager which issues public key certificates, and all parties know each other’s legal identities. Each legal entity hosts a node which stores and updates the ledger state in a local database called the vault. Each node’s vault contains Corda contract states which are ledger states representing ownership or legal agreements in which the node owner is a participant. For example, Corda contract states from the Token SDK representing fungible tokens encodes IOU statements such as “Bank A owes €5 to Alice”, for which only Alice would be a participant (the ower is not participating in the lifecycle of the token, but is informed of its history if it is eventually claimed by the final owner). General commercial agreements would be represented by Corda contract states encoding statements such as “Alice has to pay Bob €10 by 2021-02-21”, in which both Alice and Bob are participants. The state of the ledger is thus partitioned across the nodes of the network for privacy: a node is only informed about a Corda contract state if it is a participant or if it has to know about the state in order to verify that another state in which it is a participant has a valid transaction history:

../_images/Corda Sharded Ledger.png

Vault states are updated on the ledger in an append-only fashion via transactions. A transaction is a data record that represents a state update by specifying zero or more input states to consume and zero or more new output states to produce (there has to be at least one input or output). Before a node records a new transaction, it first verifies its validity by checking that all input states are present in the node’s vault and that they have not already been consumed by another transaction. The node then verifies that the output states are valid according to a Corda contract associated with the specified input states. Corda contracts are custom JVM programs that inspect the transaction and raise an error if the Corda contract states are produced or consumed in an invalid way. For example, the Corda contract for cash token IOU states such as “Bank A owes €5 to Alice” would require that the sum of money in the output ownership states equal the sum of money in the input states, and that Alice has cryptographically signed the transaction with her public key to authorize the transfer. The Corda contract for legal agreements such as “Alice has to pay Bob €10 by 2021-02-21” would require that the transaction contains some sort of evidence that the obligation has been fulfilled, for example by attaching a payment confirmation. If these checks pass, the vault of the node is updated by marking the input states as consumed and adding the output states to the node vault.

The following shows the main concepts of a Corda transaction:

../_images/Corda Transaction.png

In this example, the transaction consumes two states and produces two states, but transactions can have any number of inputs and outputs, and the numbers of each do not have to be the same. Also specified in a transaction is a number of commands that contains data needed to verify the transaction and a set of required signers. The Corda platform ensures that transactions are only considered valid if the union of required signers across all commands have signed the transaction. For example, when adding an event to a CSL contract, the event is specified as command data, and the sender of the event is specified as a required signer. Also shown are the Corda contracts associated with each of the input states which both verify the transaction.

The append-only nature of the Corda data model means that every Corda contract state comes with a tamper-proof and independently verifiable history of all the past transactions that lead to that particular state. If a node becomes a participant in a Corda contract state at a late point in its lifecycle, that node will require to be sent the full history for the state so it can verify its authenticity.

Contract Upgrades

Corda contract states specify their Corda contracts via a fully qualified JVM class name. When there is only one version of a CorDapp deployed in the network, then this uniquely specifies the validation rules that should apply, as we assume that node owners have correctly deployed the same code. When there are multiple versions of the same Corda contract, then a JVM class name is ambiguous because different nodes may have different versions of the same class deployed at the same time. This poses a security threat if e.g. an attacker is able to create a transaction that explicitly uses an outdated version of a contract with a known bug to print money, and then claim that the resulting ownership state is valid according to the newest version. To protect against such attacks, the participants need to ensure that they all use the verification logic they have agreed upon.

The mechanism used in Corda to both protect against wrong versions of a Corda contract being used, and to agree on what upgrades of Corda contracts are allowed, is to put contract constraints on the Corda contract states. These constraints come in various flavors:

The simplest way of agreeing is the Hash Constraint. This verifies that the hash of the CorDapp containing the verification logic is never modified. It introduces the limitation that the verification logic can never be updated or modified. Such limitations quickly become too strict, since CorDapps might introduce new features or require bug fixes.

The limitations can be loosened by using the Compatibility Zone Whitelisted Constraint, where the Corda network contains a list of whitelisted hashes. Thus, we allow modifications to the verification logic by modifying the network.

The last method of constraining contracts is called Signature Constraint. Here we connect a certificate to the verification logic and check that the CorDapp is signed by that entity. This allows the trusted entity to modify the CorDapp without making changes to the network. This constraint is recommended by R3.

Note that it is only required to sign CorDapps that defines Corda contracts and Corda contract states.

Conflict Resolution via Notaries

To avoid the situation where two nodes simultaneously register incompatible transactions that consume the same Corda contract states and thus arrive at inconsistent ledger states, Corda employs the concept of a notary which is a special node that must cryptographically sign all transactions. The identity of the notary is specified on the individual Corda contract states. The notary requires that any given Corda contract state is consumed at most once, and will refuse to sign transactions that would violate this invariant. The notary can be configured to run in two modes:

Validating mode
If the notary is validating, it furthermore verifies transactions by running the same Corda contract validity checks that the participant nodes and only signs the transaction if these checks pass.
Non-validating mode
If the notary is non-validating, it does not perform these checks. A non-validating notary thus only ensures that input states are used at most once, but it does not ensure that the transactions are internally consistent. A notary is configured to be non-validating when the nodes of the network requires additional privacy: since a non-validating notary does not have to know the actual contents of the involved Corda contract states but only their identities (derived from cryptographic hashes), the participant nodes can reveal only this part of the transaction to the notary (using the Corda concept called tear-offs, which are essentially Merkle trees), thus ensuring that the details of the transaction remain private between the transacting parties. This comes with a tradeoff because a faulty or malicious node could get an invalid transaction notarized, effectively locking the involved contract states because the notary would have recorded them as being consumed.


Transactions are created, signed and distributed to the relevant nodes by flows which are essentially custom JVM programs deployed on Corda nodes that implement an agreed-upon protocol for how transactions pertaining to a particular class of Corda contract states should be created. For example, a flow for transferring ownership of a state “Bank A owes €5 to Alice” would be initiated at Alice’s node and begins by creating a transaction that consumes the ownership state and produces a new state “Bank B owes €5 to Bob”. The flow would then sign the transaction with Alice’s public key and send the transaction to the notary for notarization, and finally it would send the transaction and the two signatures by Alice and the notary to Bob and record the transaction in Alice’s vault. On Bob’s node, a corresponding counterflow would receive the signed transaction and record it in Bob’s vault. Flows can be arbitrarily complex and may involve more than two participant nodes, for example when a signature by more than one participant is needed to authorize a transaction. There are very few generic flows in Corda; flows are usually tailored to work with a particular type of Corda contract state.

The Contract States CorDapp

A ledger integration for Corda is provided by Deon Digital in the form of a CorDapp, called the contract states CorDapp, which defines a Corda contract state type for representing CSL contract instances, and a Corda contract for verifying that transactions update such Corda contract states correctly according to the Deon Digital Runtime. The CorDapp also defines a set of CSL-specific commands that must be included in transactions that consume and/or produce CSL states. Transactions can perform the following operations on contract instances, where each operation is represented by a corresponding command type:

  • Create a new contract instance from a parsed and type checked CSL file, and instantiation information. A new contract instance is assigned a unique identifier derived from the identity of the transaction that created it.
  • Apply an event to a contract instance. This requires authorization from the relevant party or system that performed the action that triggered the event, and the event must be expected by the contract instance as determined by the Deon Digital Runtime.
  • Change the set of Corda participants in a contract instance. This requires authorization from all current participants.
  • Terminate a contract instance before all obligations have been fulfilled. This requires authorization from all current participants.

The following shows a single transaction advancing the state of the ledger by applying an event to a contract instance:

../_images/Corda CSL Event Apply.png

The transaction is valid only because the output state contains a contract that is the result of computing the residual contract of the input contract with the event specified within the transaction applied to it.

In the following we explain in more detail how this works and how to integrate CSL contracts with other Corda contract states.

Structure of a CSL Transaction

We use the term CSL transaction to refer to a Corda transaction that operates on one or more CSL contract states. A CSL transaction has the following general structure:

../_images/Corda CSL Transaction Structure.png

That is, a CSL transaction:

  • Has zero or more CSL contract state inputs;
  • Has zero or more Corda contract state inputs of other types;
  • Has one or more CSL commands;
  • Has zero or more commands pertaining to other Corda contract states;
  • Has zero or more CSL contract state outputs;
  • Has zero or more Corda contract state outputs of other types.

Apart from the general size limits imposed by Corda, the contract state CorDapp imposes no restrictions on what other contract states unrelated to CSL that can be specified as inputs and outputs to the transaction or what other commands can be specified. There are also no restrictions on the number of CSL contract state inputs, outputs and commands.

The Corda contract for the contract states CorDapp verifies CSL transactions using a simple execution model which interprets CSL commands in a transaction as instructions that each consume zero or one contract states and returns a resulting contract state. First, all CSL contract states among the inputs are put on an ordered list called the input list in the order they occur in the transaction, ignoring non-CSL contract states. Another list, the work list, is initialized to be empty. The CSL commands are then executed in the order they occur in the transaction (non-CSL commands are ignored). Each command returns a contract state which is inserted to the beginning of the work list when the command returns successfully. Some commands also consume a state by removing it from either the input or work list before executing. When all commands have been executed, the Corda contract checks that the input list is empty and that the work list equals the list of CSL contract states in the outputs of the transaction (non-CSL states are ignored).

The following is a complete summary of all CSL commands and their executions:

Contains an event value which is applied to the consumed state using the Deon Digital Runtime. The command requires that the node representing the event agent signs the transaction with its public key. If applying the event is successful (i.e. the consumed contract state expected the event), then the resulting contract state is returned.
Creates a new contract state. The command consumes no states. It contains a list of participants, a CSL declaration (as the hash of a Corda attachment), the name of an entrypoint within the declaration, and a list of values to pass as arguments to this entrypoint. The command requires that all participants sign the transaction. The Deon Digital Runtime is used to load the CSL declaration and compute the result of instantiating the entrypoint. This computed result is then returned by the command.
Changes the set of participants of the consumed state. The command contains the list of the new participants, and it requires that all current and all new participants sign the transaction. The actual CSL contract state is unchanged, only the Corda participants are changed.
Terminates the consumed state so that no further commands can be applied to it. The command requires all current participants to sign the transaction. The returned state is identical to the consumed state, except that a flag is set that indicates that it has been terminated. The execution model will raise an error if such a state is used by another command.


Because commands can consume states from either the input or the work list, transactions can atomically apply multiple operations to a given contract state. This allows complex consensus protocols to be implemented as flows. For example, a set of parties can agree to replace a contract in which an error has occurred with another contract by signing a transaction containing a TerminateContractCommand terminating the old contract and an InstantiateCommand that simultaneously instantiates the new one. By performing both commands in a transaction, the participants have an all-or-nothing guarantee protecting them from getting stuck in an intermediate state where the old contract is terminated but where they cannot agree on its replacement:

../_images/Terminate Instantiate Contract.png

As another example, two parties Alice and Bob can agree that Alice applies an event A to contract C1 if and only if Bob applies events B and C to another contract C2 by creating a transaction with two ApplyEventCommand commands that does both. The transaction is only valid if both Alice and Bob signs it, thus ensuring atomicity:

../_images/Corda Apply Multiple.png

Integration with other CorDapps

As described in the previous section, the Corda contract validating CSL transactions allows any number of non-CSL states and commands to be present in the transaction. This allows for integrations with other CorDapps that extend the semantics of CSL contracts in various ways by imposing further restrictions apart from the ones required by the base execution model, but without requiring modification of the contract states CorDapp itself.

The purpose of integrating CSL with other CorDapps is typically to provide interpretations of CSL events from a particular business domain, imposing extra requirements on evidence that must be provided when certain events are applied to contracts. For example, in the domain of financial instruments, the event ontology will contain events such as Payment and PriceObservation that represent cash payments and observations of the current market price of an underlying, respectively. In order to eliminate the need for later reconciliation between CSL events and e.g. bank statements and price histories, one might require that a CSL transaction applying a Payment event also contains concrete evidence of the payment. For example, this evidence could take the form of a signed confirmation from a payment agent, or alternatively as a token transfer on Corda itself, performed atomically in the same transaction as the CSL event application. Similarly, a transaction applying a PriceObservation event can also be required to contain evidence of the authenticity of the observation, for instance in the form of a signature from a calculation agent.

We can use Corda’s encumbrance mechanism to implement such integrations. In Corda, any state A produced in a transaction can specify that it is encumbered by up to one other state B, called its encumbrance state, produced in the same transaction. In order to consume the state A, state B must also be consumed in the same transaction, otherwise it will be rejected by Corda. In order to avoid the situation where state B is consumed without state A, which would effectively lock it forever, Corda requires that all encumbrances form a cycle such that state B must specify that it is encumbered by A, either directly or transitively via another encumbrance. The states A and B thus effectively act as a single state whose Corda contract is the conjunction of the Corda contracts of each. Typically, it is the case that the Corda contract for A is unaware of B’s existence, but that the Corda contract for B performs extra checks on top of the checks performed by the Corda contract for A. The setup is depicted in this diagram:

../_images/Encumbrance Transaction.png

Example: Payment Confirmations

The following example illustrates how the approach outlined above can be used to extend the validation rules of CSL to integrate the contract management on Corda with external resource managers for managing cash transactions, e.g. banks. Consider an event ontology containing the following definition of a Payment event:

type Currency | DKK | EUR
type Payment : Event {
  payee: Agent,
  currency: Currency,
  amount: Int

A Payment event p represents the fact that p.agent paid p.amount in currency p.currency to p.payee. The amount is assumed to be in the smallest unit of the currency, e.g. cents for the EUR currency.

We want to ensure that whenever a participant in a contract applies a Payment event, then the payment has to be confirmed by a designated payment agent, and this confirmation must be recorded on the ledger together with the event. We thus get a single cryptographically signed, and thus non-repudiable, piece of evidence from which we can derive both the state of the contract and the accounting books of the external resource managers.

To this end, we define a new CorDapp with a Corda contract state type called MustConfirmPayments which contains the identity of the Corda node that will act as the payment agent. Participants that enter a contract in which Payment events must be confirmed must instantiate the contract such that the initial state has an encumbrance with a MustConfirmPayments state specifying the payment agent. We also define a new command called ConfirmPayments which contains a list of tuples (i, ref) where i is the index of an ApplyEventCommand within the transaction and ref is an external reference number used to uniquely identify a cash transaction at the payment agent. The Corda contract for the new Corda state verifies transactions as follows:

  1. Verify that MustConfirmPayments encumbrances are maintained correctly, meaning:
    1. An output CSL contract state is encumbered by at most one MustConfirmPayments state, in which case we just say that the CSL state is encumbered.
    2. If an output CSL state is encumbered, then it was instantiated in the transaction or it is the result of applying one or more commands to an input CSL state that was also encumbered.
    3. If an input CSL state was encumbered, then any CSL state in the output resulting from applying commands to it is also encumbered, and the payment agents are equal.
  2. Verify that all payment obligations are matched to payment references at the relevant payment agents:
    1. For each input CSL state encumbered with payment agent agent, we first find all ApplyEventCommand commands that apply a Payment event to it, and we record a payment obligation (i, agent) where i is the index of the command in the transaction command list and agent is the payment agent of the encumbered CSL state that we are processing.
    2. The transaction must contain exactly one ConfirmPayments command with one required signer agent if and only if there exists a payment obligation of the form (i, agent) for some index i.
    3. Each ConfirmPayments command with signer agent must contain a list [(i_1, ref_1), ..., (i_n, ref_n)] where each i_j occurs at most once, and where there is one tuple (i_j, ref_j) for each payment obligation (i_j, agent). The ref_j values can be arbitrary.

These requirements ensure that every Payment event is paired with a unique transaction reference at a given payment agent using ConfirmPayments commands, and that payment agents must authorize such transactions before they can be notarized.

Note that if there are no payment events being applied in a transaction, then the rules only require that encumbrances on CSL states are maintained correctly. For example, if E is a non-Payment event, then the following is a valid transaction


On the other hand, consider the case where we have two contracts, Contract A and Contract B, which are both encumbered such that payment agents Charlie and Denise must confirm all payments, respectively, and we apply two payment events to Contract A and one payment event to Contract B. Then a valid transaction looks as follows:


The commands are explicitly annotated with their indices. The first two payments p1 and p2 are matched to transaction references XYZ and ABC at payment agent Charlie, and the payment p3 is matched to FZN at payment agent Denise. By signing off on the Corda transaction, the payment agents confirm that they have indeed performed cash transactions corresponding to the payments, and that those cash transactions have not been used as confirmations in other Corda transactions.

We can use transactions with payment confirmations to implement a two-phase commit protocol in which the cash transactions are only executed if and only if the Corda transaction is notarized, thus preventing the case where e.g. the cash transactions are executed but the CSL contract update failed because the contract was updated by another participant. In order to prevent that, a payment is divided up into multiple phases.

  • First, the payer contacts the payment agent and registers the payment by specifying the amount and the payee. The payment agent then generates a new, unique reference id and returns that to the payer. This is the identifier that will end up in the transaction.
  • Second, when the payer has received transaction identifiers for all payments, the payer creates the CSL transaction, signs it, and then sends it to the payment agents. The payment agents check that there are enough funds available to execute the transfer, and then reserves the funds, signs the transaction, and returns the signature to the payer. Note that the transfers have not been executed yet, they can still be canceled by the payer, but the funds needed to execute them have been reserved.
  • Third, when all signatures have been received, the payer notarizes the transaction by sending it, and its signatures, to the notary. This step succeeds if the transaction is valid and if the CSL contract has not been consumed by another transaction during the flow.
  • Finally, when the payer has received the notary signature, the transaction and its signatures are distributed to counterparties (Bob) and the two payment agents. Upon receiving the notary signature, the payment agents execute the prepared cash transactions, thus spending the reserved funds.

A successful execution of the flow is depicted in the following interaction diagram:

participant Alice
participant Bob
participant Notary
participant Charlie
participant Denise

-> Alice : Start payments flow
activate Alice

Alice -> Charlie ++ : Prepare transaction 1
Charlie -> Charlie : Check authorization & create transaction
Charlie -> Alice : Transaction ref# XYZ

Alice -> Charlie : Prepare transaction 2
Charlie -> Charlie : Check authorization & create transaction
Charlie -> Alice : Transaction ref# ABC

Alice -> Denise ++ : Prepare transaction 3
Denise -> Denise : Check authorization & create transaction
Denise -> Alice : Transaction ref# FZN

Alice -> Alice : Create Corda transaction

Alice -> Charlie : Send (blinded) transaction
Alice -> Denise : Send (blinded) transaction

Charlie -> Charlie ++ #ffe0e0 : Check balance & reserve
Denise -> Denise ++ #ffe0e0 : Check balance & reserve

Charlie -> Alice : Sign transaction
Denise -> Alice : Sign transaction

Alice -> Notary : Notarize transaction
Notary -> Notary : Validate & commit
Notary -> Alice : Sign transaction

Alice -> Bob : Send transaction & signatures
Alice -> Charlie : Send (blinded) transaction & signatures
Alice -> Denise : Send (blinded) transaction & signatures

Charlie -> Charlie : Perform transaction
deactivate Charlie
deactivate Charlie

Denise -> Denise : Perform transaction
deactivate Denise
deactivate Denise

<- Alice : Return
deactivate Alice

On a final note, the transactions being sent to the payment agents have been marked as blinded in the interaction diagram. This indicates that the flow may use the Corda concept called transaction tear-offs to hide the parts of the transaction that is not relevant to the payment agent. Because ConfirmPayments commands are grouped by payment agent, only the ConfirmPayments command relevant to the payment agent, and the ApplyEvent commands referred by it, have to be revealed to the payment agent. In particular, the CSL contract state can be kept private between Alice and Bob.

The Flows CorDapp

Flows must also be deployed to enable the nodes to create, authorize and distribute transactions that modify contract instances. Deon Digital provides a flows CorDapp implementing standard flows for performing basic operations on contract states. The CorDapp also contains a user query service that is used by counterflows that need to obtain authorization from the node owner.

Note that the standard flows are limited in several ways: they only support creating transactions that work exclusively on CSL contract states, and not all complex combinations of commands and operations over multiple contract states are supported. Furthermore, the flows are not aware of any integrations with other CorDapps.

In practice, this means that custom flows must almost always be created to support application-specific integrations and usage patterns. The standard flows presented here, however, serve as good starting points for developing such custom flows.

User query service

When a responder flow receives a request to sign a transaction, a human or an external system is often required to assess whether the action perform by the transaction can be authorized. For example, a responder flow deployed on a node should not automatically sign any incoming transaction containing an InstantiateContractCommand if this can be regarded as a legally binding action.

The user query service is a Corda service that allows a flow to submit a query for authorization and suspend itself until an answer has been provided. The query service has a “mailbox” of queries that are persisted between node restarts, and which can both be queried and subscribed to by the Corda Ledger component. The typical interaction between a (responder) flow, the query service and Corda Ledger is depicted below:

participant "Responder Flow" as flow
participant CSLUserQueryService as qs
participant CordaLedger as app

qs <- app : Subscribe to user queries
qs --> app : Return observable
-> flow : Transaction proposal
activate flow
flow -> qs : Submit query
flow -> flow -- : Suspend
qs -> qs : Add query to mailbox
qs -> app : Notify
app <-- : Provide answer
qs <-- app : Submit answer
qs -> qs : Remove query from mailbox
flow <-- qs : Return answer
flow -> flow ++ : Resume

Change participants flow

This flow creates a transaction with a single SetParticipantsCommand which replaces the set of participants in the contract, with some or all participants using fresh confidential identities.

In this flow, a single node has the role of initiator and coordinates the communication between all the other nodes, which we call the counterparties. In practice, one of the counterparties almost always have the same identity as the initiator and is thus communicating with itself. This design was chosen because it is easier to structure the flow with a central coordinator that does not have to take their own participation in the contract into account, and from the outside there is no observable difference.

From a high level, the flow operates as follows:

  • First, the initiator of the flow chooses the CSL contract state to update and the set of new participants all identified by their public Corda identities.
  • The initiator then sends a proposal to change the participants to all current and all new participants — these sets can possibly overlap.
  • In the counterflows, existing particpants looks up the contract state from their vault, and “invited” participants, who do not have access to the contract, download the full contract history from the initiator.
  • Each counterflow then submits a user query to the CSL query service and suspends until the query has been answered. The query asks the node owner for authorization to perform the change of participants, and the node owner can choose to either reject or accept, and if accepting, whether a confidential identity should be used or not. Asking the node owner is necessary because the request to perform the SetParticipantsCommand was initiated by another party, and it is not given that the node owner agrees.
  • If the node owner rejects, then a response is sent to the initiator and the flow terminates. Otherwise, a fresh confidential identity is generated if that was chosen by the node owner, and a response signaling the intent to participate together with the chosen identity is return to the initiator.
  • The initiator receives responses from all counterflows and validates each confidential identity by checking that they have valid certificate paths and that the respective nodes can prove that they know the private keys of their chosen confidential identities.
  • The initiator generates a random privacy salt used to deterministically generate blinding factors for the Corda transaction. This salt, and the chosen confidential identities, are then broadcast to all counterparties.
  • Each counterparty then validates the confidential identities and builds a Corda transaction containing a SetParticipantsCommand performing the intended action — the privacy salt received from the initiator is necessary to ensure that all nodes generate identical transactions. The counterparties then sign the hash of the generated transaction and return their signatures to the initiator.
  • The initiator builds the same transaction and sends it and the signatures it received from the counterparties to the notary for notarization. When the transaction is notarized, the notary signature is distributed to the counterparties and the flow is done.

The interaction is depicted in the following diagram (right click and select “view image” if the text appears too small):

actor Initiator
participant "SetParticipantsFlow" as coord
collections "SetParticipantsResponderFlow" as cohort
participant CSLUserQueryService as service
actor "Participant" as participant
participant Notary

Initiator -> coord : Start SetParticipantsFlow\nargs: (contractId, newParticipants)
activate coord
coord -> coord : S <- latest unconsumed state of contractId
coord -> cohort : SetParticipantsProposal(Ref(S), newParticipants)
activate cohort

alt if counterparty not a current participant
note over coord, cohort : Invoke SendTransactionFlow / ReceiveTransactionFlow
else if counterparty is already a participant
cohort -> cohort : S <- fetch Ref(S) from vault

cohort -> service : Request authorization from user\n(suspends flow)
deactivate cohort
service -> participant : Notify
participant --> service : Submit answer\n(AuthLeave / AuthEnter / Reject)
service --> cohort : Complete authorization Future\n(resumes flow)
activate cohort

alt if entering contract and user chose to anonymize
cohort -> cohort : Generate confidential identity\nand signed CertificateOwnershipAssertion

cohort --> coord : SetParticipantsResponse\n(AcknowledgeLeave / AcknowledgeEnter / Reject)
coord -> coord : Validate confidential identities:\n1. confidential key must sign well-known X500 name of party,\n2. confidential key has cert w/name equal to the well-known name\n3. the certificate path is valid
coord -> coord : Generate transaction privacy salt
coord -> cohort : Broadcast confidential identities and privacy salt

cohort -> cohort : Validate confidential identities
cohort -> cohort : Build transaction and sign tx hash\n(up to two signatures if generating fresh\nconfidential identity and already a participant)
cohort --> coord : Return signatures
coord -> coord : Build transaction

coord -> Notary : Notarisation request (includes full transaction)
Notary --> coord : Notary signature

coord -> cohort: Broadcast transaction signatures
cohort -> cohort : Register transaction in vault

deactivate cohort
deactivate coord


The flow is able to create transactions using the commands ApplyEventCommand and InstantiateContractCommand, possibly applied across multiple contracts and in batches on individual contracts. The following are examples of transactions that can be created using this flow:

  • Instante one or more new contracts and apply zero or more events to each
  • Apply one or more events to one or more existing contracts
  • Any combination of the above

Terminate contract flow

The flow is similar to the flow for setting contract participants, but instead it terminates the contract by creating a transaction containing a TerminateContractCommand. The initiating flow takes as arguments a contract identifier, a textual description of why the contract is to be terminated and a point in time on the global timeline. The flow then collects consent signatures from all participants in the contract, and the respective counterflows use the user query service to obtain authorizations from the node owners.

Deployment Overview

The following diagram shows an overview of the architecture of a single Corda node with Deon Digital’s CSL components deployed:

../_images/Corda Deployment.png

The JVM blocks refer to separate Java Virtual Machine processes that may be running on separate machines.

A deployment of Deon Digital’s CSL tools for Corda consists of a Corda node that is responsible for managing a distributed ledger on which CSL contracts are stored; and the Corda Ledger which is a client process that interacts with the Corda node via RPC and provides an API to the part of the contract manager that runs outside the Corda node for initiating CSL contract operations and querying the state of CSL contracts. Apart from the Corda Ledger, a number of custom clients may also initiate flows and perform vault queries on CSL contract states via the Corda RPC API. A cache in the Corda Ledger stores contract instances, their histories and some derived metadata to increase performance.

Both the Corda Ledger and the Corda node use the Deon Digital Runtime for representing CSL contracts and events and for computing residual contract states. The contract states CorDapp and flows CorDapp, which were described above, are additionally deployed on the Corda node.

Also shown in the figure is a remote Corda node, which is hosted on the premises of another legal entity.

Corda Ledger


The CSL Corda Ledger is a CSL-specific component and not provided by Corda. It is named as such because it implements a ledger for CSL on Corda.

The primary purpose of the CSL Corda Ledger is to invoke the Deon Digital flows and make vault queries. It is a Deon Digital component that provides some boilerplate functionality and serves as a wrapper around the CordaRPCOps client that is used to connect to a Corda Node.

In addition to providing the functionality for interacting with the Deon Digital flows, it also maintains a cache of contracts and their states. Invoking flows is synchronous, so all nodes must acknowledge the update before control is returned to the client.

The CSL Corda Ledger exposes some programmatic interfaces to interact both with the contract manager and the Corda node. These include both the generated interfaces from The sic boilerplate generator, but also direct querying of contract states. With these interfaces, it is possible to build a layer of business logic that forms the base of an application.

Integration interfaces

There are a number of integration interfaces in the CSL Corda Ledger depending on the level of abstraction that is required. Using any combination of these interfaces in a single application is expected and supported. Application developers need not restrict themselves to a single integration interface.

Corda RPC

Directly communicate with the Corda network by querying the vault or get network information. Using the RPC client to invoke Deon Digital flows is discouraged.


ContractOperations is a Kotlin interface generated by The sic boilerplate generator which contains methods for working directly with a CSL contract in an ergonomic way. With this interface, it is possible to instantiate contracts in a type safe manner, query instantiated contracts, apply events, run CSL defined reports and query CSL specific analyses like nextEvents.