Skip to main content

Submit Externally Signed Transactions - Part 1

This tutorial demonstrates how to submit Daml commands to a Canton ledger using an external private key for transaction authorization. Before proceeding, it is recommended to review the external signing overview to understand the concept of external signing. The tutorial illustrates the external signing process using two external parties, Alice and Bob, leveraging the Ping Daml Template which is included by default in all participant nodes.
  • In Part 1 Alice creates a Ping contract.
  • In Part 2 Bob exercises the Respond choice on the contract and archives it.
This tutorial is for demo purposes. The code snippets should not be used directly in a production environment.

Prerequisites

For simplicity, this tutorial assumes a minimal Canton setup consisting of one participant node connected to one synchronizer (which includes both a sequencer node and a mediator node).
If you already have such an instance running or have completed the onboarding tutorial, proceed to the Setup section.

Start Canton

To obtain a Canton artifact refer to the getting started section. First, navigate to the interactive submission example folder located at examples/08-interactive-submission in the Canton release artifact.
All commands in this tutorial are expected to be run from that folder.
From the artifact directory, start Canton using the command:
Once the Welcome to Canton message appears, you are ready to proceed.

Setup

Navigate to the interactive submission example folder located at examples/08-interactive-submission in the Canton release artifact. This tutorial demonstrates external signing with two external parties: Alice and Bob. If you haven’t onboarded an external party yet, refer to the onboarding tutorial. To proceed, gather the following information:
  • Alice’s Party Id, protocol signing private key, and protocol signing public key fingerprint
  • Bob’s Party Id
  • Synchronizer Id to which the participant is connected
  • gRPC Ledger API endpoint
The Party IDs and key-related information should already be known from the onboarding tutorial. To retrieve the participant and synchronizer IDs, as well as the gRPC Ledger API ports, run the following commands in the Canton console:
For this tutorial, the following values will be used (replace them with actual values):
  • Alice Party Id: alice::1220d466a5d96a3509736c821e25fe81fc8a73f226d92e57e94a65170e58b07fc08e
  • Bob Party Id: bob::1220254d06095b407f8c6a378b6fc443a67d3356ab8edfbf1378cb3e44218de32c8a
  • Synchronizer Id: da::12203c0ecb446b35b0efa78e0bda9fd91716855866150a5eb7611a2ed5d418129de3
  • gRPC Ledger API endpoint: localhost:4001

API

This tutorial interacts with the InteractiveSubmissionService, a service available on the gRPC Ledger API of the participant node.

Python

It is recommended to use a dedicated python environment to avoid conflicting dependencies. Considering using venv.
Then run the setup script to generate the necessary python files to interact with Canton’s gRPC interface:

Shell

For a terminal-based approach, install the following tools:

1. Prepare the transaction

Transform Ledger Command into a Daml Transaction. Bash Request:
Record the response in create_ping_prepare_response.json to make it easier to submit the transaction afterwards. Now inspect the response with
Python
Ensure you have followed the setup instructions for Python before proceeding.

Request

  • user_id: Identifier for the application interacting with the ledger.
  • command_id: Unique, random string identifying this specific command. Each command submission must have a new and unique command_id.
  • act_as: ID of the party issuing the command.
  • synchronizer_id: ID of the synchronizer that processes the transaction upon submission.
  • commands: Ledger commands for submission. In this case, it shows the creation of a Ping contract with Alice as the initiator, Bob as the responder, and a ping_id value. See the Ledger API command reference for details.

Response

Transaction

Represents the explicit ledger changes upon successful commitment.
  • version: Version of the transaction. This is also called LF Version.
  • roots: List of root node ids. A Daml transaction is a list of trees. The nodes are flattened in a single list (see below). The root node ids design the root node of each individual tree in the transaction.
A current limitation of externally signed transactions is that they can only contain a single root node, and therefore a single transaction tree.
  • nodes: List of all nodes in the transaction. There are 4 types of nodes: Create, Exercise, Fetch and Rollback. The number, type and content of each node depends on the Daml model and the state of the ledger.
  • node_seeds: List of seeds used by the Canton protocol to generate cryptographically secure salts. They can be ignored.

Metadata

Additional information required for transaction processing.
  • ledger_effective_time: Time picked during the interpretation of the command into a transaction. Set if and only if the daml model makes use of time.
  • submitter_info: Contains the act_as party and command_id
  • synchronizer_id: Synchronizer that will be used for transaction processing
  • transaction_uuid: Unique value generated by the prepare endpoint to uniquely identify this transaction
    • The transaction UUID is randomly selected during the prepare step and is fixed from that point forward. This allows the mediator node to de-duplicate transactions and prevent replays.
  • mediator_group: Group of mediators that will gather confirmation responses for the transaction. Can be ignored.
  • submission_time: The timestamp that the Canton protocol will use as a submission time to perform validations (e.g for de-duplication)
  • disclosed_events: Existing input contracts used in the transaction
  • global_key_mapping: Unused in the current version, can be ignored.

Hash

  • prepared_transaction_hash: Pre-computed transaction hash. For security reasons the hash should be re-computed client-side as mentioned in the Compute transaction hash section.
The prepare API can return additional details on how the Canton node is hashing the transaction to help troubleshoot hash-related errors (for example: pre-computed and re-computed hash mismatch). To enable it:
  1. Enable verbose hashing on the participant config ledger-api.interactive-submission-service.enable-verbose-hashing = true.
  2. Set the verbose_hashing field in the PrepareSubmissionRequest to true.

Hashing scheme version

Version of the hashing scheme:
If the gRPC Ledger API authorization is enabled, the user must have the readAs claim on behalf of Alice to call the prepare endpoint.

Traffic cost estimation

Estimates the traffic cost for the Participant Node that submits the transaction to the synchronizer. See the API documentation for details on the estimation response fields. The precision of the estimation is influenced by several factors that cannot be known when the transaction is being prepared. Additionally, the traffic cost will be incurred by the node submitting the transaction, not the one preparing it. When they are the same, the cost estimation will be more accurate. The following factors contribute to the uncertainty of the cost estimation:
  • Hosting relationship of the submitting party with the executing node
  • Number and type of external signatures provided upon submission of the transaction
  • Topology state of the network when the transaction will be submitted
  • Request amplification during submission
  • Part of the transaction that will be confirmed (root view or sub views)
  • Whether nodes approve or reject the transaction
  • IDs of contract created within the transaction are not suffixed in the cost estimation, whereas they are suffixed in the actual submission
  • Whether session signing keys are enabled on the submitting or confirming nodes
In most cases the impact of those factors is low and the variance they cause can generally be expected to be 10% or less. Hints can be specified in the prepare request to help improve the precision of the estimation. It’s worth noting that request amplification can significantly increase traffic cost when triggered. The estimation provided is valid for a single confirmation request submission. Subsequent amplified requests sent will cost additional traffic cost as they correspond to a new confirmation request being sent.
Traffic cost estimation is enabled by default. To disable it, set the disabled field in the CostEstimationHints message to true.
For details on traffic management, read the related explanation page.

2. Validate the transaction

Deserialize and inspect the transaction to verify its correctness before proceeding. The initiator of the transaction must be able to inspect and validate it to ensure it matches their intent before proceeding. See the Trust Model for guidance.

3. Compute the transaction hash

It is strongly recommended that the transaction hash be recomputed from the transaction and metadata to verify correctness. The pre-computed hash provided in the Prepare step is for debugging purposes.
  • The hashing algorithm specification is available here as well as in the release artifact under protobuf/ledger-api/com/daml/ledger/api/v2/interactive/README.md
  • An example implementation in python is available in the release articact under examples/08-interactive-submission/daml_transaction_hashing_v2.py

4. Sign the transaction hash

Using Alice’s protocol signing private key, sign the hash.
Technically what is needed is the ability to sign with Alice’s key, not the key itself. The management of the key can be delegated to a wallet, HSM or crypto custody provider. In this tutorial the key is managed locally and explicitly to demonstrate the signing process. Refer to the onboarding tutorial for details on how to generate a key for this tutorial.
Bash Assuming Alice's private key is stored in a file called alice::1220d466a5d96a3509736c821e25fe81fc8a73f226d92e57e94a65170e58b07fc08e-private-key.der, he hash can be signed using openssl.
In this tutorial the hash retrieved from the response of step 1 will be signed, without re-computing it as suggested in step 3. For an example of how to re-compute the hash, see the Python example.
Python The Python example demo includes an implementation of the transaction hashing algorithm. In this example, party_private_key is assumed to be an EllipticCurvePrivateKey Python object containing Alice’s private key. If the onboarding tutorial was followed, this key should already be available.

5. Execute the transaction

Submit the transaction and its signature to the ledger. Bash
Python
In the request, note the presence of:
  • submission_id: Random string uniquely generated for this submission. This differs from the command_id in that a retry of this same prepared transaction would necessitate a new submission_id. The submission_id is used to correlate several submissions of the same command with completion events (See next step for more on completion events).
    • Because submission_id is not part of the signature, a command can be re-submitted with a different submission_id without requiring a new signature.
  • signatures: Object containing the signature of the transaction hash, along with metadata. In particular:
    • signing_algorithm_spec: Will vary depending on the key used during onboarding.
    • signed_by: Fingerprint of the protocol signing public key of Alice. This tutorial assumes the same key was used to create Alice’s namespace and her protocol signing key. This is why the fingerprint of the signing key matches the second part of her Party Id (after ::). For more details check out the onboarding tutorial and the Daml parties guide.
If the gRPC Ledger API authorization is enabled, the user must have the actAs claim on behalf of Alice to call the execute endpoint.

6. Observe the transaction outcome

Monitor the completion stream for transaction confirmation, then retrieve the contract ID and binary blob representation. Bash
Wait until observing a completion event:
  • status.code: A value of 0 indicates the command completed successfully
  • offset: Ledger offset for the event
  • update_id: Unique Id for this completion event
  • submission_id: The submission Id chosen in the submission step
Let’s record both the offset and update_id for the next steps.
You may need to interrupt the command with Ctrl-C as the completion stream is a gRPC server streaming RPC which waits for updates from the server until interrupted.
Let’s now retrieve the corresponding transaction:
The events list includes a created object, representing the newly created contract. Extract the corresponding contract_id for reference. To finalize this step, retrieve the binary blob representation of the created contract. This serialized form will be required when executing a choice on the contract in Part 2.
The request above might return multiple contracts if additional ones were created after the offset. The relevant contract should be identified by matching its contract_id. The created_event_blob contains a serialized version of the contract, which can be used in subsequent transactions to exercise choices on it. Python
At this stage, the contract has been successfully created, and its contract_id is available.
The execution and extraction of the contract_id above are summarized in the following function, which is reused in Part 2 of the tutorial:
To complete this part, the next step is to retrieve the binary blob of the creation event for the Ping contract. This serialized representation will be required in Part 2 when executing a choice on the contract.
This concludes Part 1 of the tutorial. In Part 2, Bob exercises the Respond choice to archive the contract.

Submit Externally Signed Transactions - Part 2

Complete Part 1 before proceeding. The tutorial illustrates the external signing process using two external parties, Alice and Bob, leveraging the same Ping Daml Template used in Part 1 of the tutorial.
  • In Part 1 Alice created a Ping contract.
  • In Part 2 Bob exercises the Respond choice on the contract and archives it.
The majority of the work involved in external transaction signing was completed in Part 1. The key addition in Part 2 is utilizing the Ping contract created earlier through explicit disclosure and executing the Respond choice on that contract. The overall process remains similar to Part 1.
This tutorial is for demo purposes. The code snippets should not be used directly in a production environment.

Setup

To proceed, gather the following information:
  • Bob’s Party ID, protocol signing private key, and protocol signing public key fingerprint
  • Synchronizer ID of the synchronizer to which the participant is connected
  • gRPC Ledger API endpoint
  • ping_created_event: Event retrieved in the last step of Part 1.
  • contract_id: ID of the contract created in Part 1.
This information should already be known from the onboarding tutorial and the first part of the external signing tutorial.

Python

If you are following this tutorial in Python, generate gRPC Python classes by following the setup instructions in the README in the example folder.

Exercise Respond Choice

This tutorial does not repeat the material covered in Part 1 regarding transaction preparation, validation, signing, and execution, as these steps remain largely the same. Instead, it highlights the key differences from Part 1.

Prepare the transaction

The Prepare request is very similar to the one from Part 1, with the following differences:
  • act_as: Now the responder, Bob, instead of the initiator, Alice. This makes sense because Bob is the one exercising the choice on the contract.
  • commands: The command is now an Exercisecommand instead of a Create command. Notably it requires the contract_id from Part 1.
  • disclosed_contracts: The serialized representation of contracts required to process the transaction.

Metadata

The only significant difference with Part 1 in the metadata is: disclosed_events. This field now contains the input Ping contract. It is also included in the hash of the transaction.
Like in Part 1, the transaction must be validated, hashed and signed. The hash computation and signature is performed by the execute_and_get_contract_id function provided at the end of Part 1, as shown in the next section.

Submit and observe archived contract

By querying the event service and filtering for the contract ID, an archived event is observed, confirming that the contract has been successfully archived. This concludes the external signing tutorial. The code used in this tutorial is available in the examples/08-interactive-submission folder and can be run with

Tooling

The scripts mentioned in this tutorial can be used as tools for testing and development purposes

Decode base64 encoded prepared transaction to JSON

Compute hash of base64 encoded prepared transaction