> ## Documentation Index
> Fetch the complete documentation index at: https://cantonfoundation-generated-references-canton-protobuf-histo.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# External Signing: Party Onboarding

> Onboard external parties for external signing using the Ledger API

# Onboard External Party

This tutorial demonstrates how to onboard an **external party** using the Ledger API.

## Prerequisites

<Warning>
  This tutorial uses openssl to create keys on the file system, which is not secure for production use.
</Warning>

From the artifact directory, start Canton using the command:

```
./bin/canton -c examples/08-interactive-submission/interactive-submission.conf --bootstrap examples/08-interactive-submission/bootstrap.canton
```

<Tip>
  A runnable script `external_party_onboarding.sh` located in the `examples/08-interactive-submission` directory of the Canton artifact puts together the steps in this tutorial as an example. Run the script from the same directory where you started Canton such that the script can find the `canton_ports.json` file which contains the port configuration of the running Canton instance, or invoke the script with the hostname and port of the Ledger API using the command line argument `-p1 <host>:<port>`. Note that the script supports a few command line arguments, which you can see by inspecting the code.

  To obtain a Canton artifact refer to the getting started section.
</Tip>

## Onboarding

The onboarding steps are:

* Create a private key using openssl for the external party.
* Determine the available synchronizer-id.
* Create the topology transaction to define a new external party.
* Sign the topology transaction.
* Upload the signed topology transaction to the Ledger API.

First, determine the available synchronizer-ids using the `v2/connected-synchronizers` endpoint, assuming that there is exactly one. The party allocation must be repeated for each synchronizer-id the party should be hosted on.

Run this command from the same directory where you started Canton such that the command can find the `canton_ports.json` file which contains the port configuration of the running Canton instance.

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
PARTICIPANT1=$(echo "localhost:"$(jq -r ".participant1.jsonApi" canton_ports.json))
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
SYNCHRONIZER_ID=$(curl -f -sS -L ${PARTICIPANT1}/v2/state/connected-synchronizers | jq ".connectedSynchronizers[0].synchronizerId")
```

Next, create a private Ed25519 key for the external party (other types of keys are supported as well). The public key is then extracted in DER format and the binary DER format converted to base64.

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
openssl genpkey -algorithm ed25519 -outform DER -out private_key.der
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
openssl pkey -in private_key.der -pubout -outform DER -out public_key.der 2> /dev/null
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
PUBLIC_KEY_BASE64=$(base64 -w 0 -i public_key.der)
```

Use the convenience endpoint `/v2/parties/external/generate-topology` to generate the topology transactions required to onboard the external party. This is fine if the node is trusted. In other scenarios, the transactions should be built manually or inspected before signing, including recomputing the hash.

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
GENERATE=$(cat << EOF
{
"synchronizer" : $SYNCHRONIZER_ID,
"partyHint" : "Alice",
"publicKey" : {
"format" : "CRYPTO_KEY_FORMAT_DER_X509_SUBJECT_PUBLIC_KEY_INFO",
"keyData": "$PUBLIC_KEY_BASE64",
"keySpec" : "SIGNING_KEY_SPEC_EC_CURVE25519"
},
"otherConfirmingParticipantUids" : []
}
EOF
)
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
ONBOARDING_TX=$(curl -f -sS -d "$GENERATE" -H "Content-Type: application/json" \
    -X POST ${PARTICIPANT1}/v2/parties/external/generate-topology)
```

The convenience endpoint returns the generated topology transactions together with the computed party-id for the new party and the fingerprint of the public key. In addition, it also returns a multi-hash, which is a commitment to the entire set of transactions.

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
PARTY_ID=$(echo $ONBOARDING_TX | jq -r .partyId)
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
TRANSACTIONS=$(echo $ONBOARDING_TX | jq '.topologyTransactions | map({ transaction : .})')
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
PUBLIC_KEY_FINGERPRINT=$(echo $ONBOARDING_TX | jq -r .publicKeyFingerprint)
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
MULTI_HASH=$(echo $ONBOARDING_TX | jq -r .multiHash)
```

<Note>
  Do not make assumptions on the number of onboarding transactions returned by the endpoint as it is subject to change. To deserialize and inspect the transaction, check out the [external party topology transaction tutorial](/appdev/deep-dives/external-signing-topology).
</Note>

Sign the hash and convert the signature to base64:

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
echo $MULTI_HASH | base64 --decode > hash_binary.bin
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
openssl pkeyutl -sign -inkey private_key.der -rawin -in hash_binary.bin -out signature.bin -keyform DER
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
SIGNATURE=$(base64 -w 0 < signature.bin)
```

<Tip>
  The transactions can be signed one by one, or together as one hash, as done here.
</Tip>

Using the signature and the data from the previous step, submit the topology transactions and the signature to the ledger API to complete the onboarding of the new external party:

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
ALLOCATE=$(cat << EOF
{
"synchronizer" : $SYNCHRONIZER_ID,
"onboardingTransactions": $TRANSACTIONS,
"multiHashSignatures": [
{
"format" : "SIGNATURE_FORMAT_CONCAT",
"signature": "$SIGNATURE",
"signedBy" : "$PUBLIC_KEY_FINGERPRINT",
"signingAlgorithmSpec" : "SIGNING_ALGORITHM_SPEC_ED25519"
}
]
}
EOF
)
```

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -f -sS -d "$ALLOCATE" -H "Content-Type: application/json" \
    -X POST ${PARTICIPANT1}/v2/parties/external/allocate
```

When allocating parties on a single node like here, the `allocate` endpoint is synchronous, meaning it returns only when the party is allocated. The party should now appear on the Ledger API:

```shell theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -f -sS "${PARTICIPANT1}/v2/parties?filter-party=$PARTY_ID"
```

# Onboard Multi-Hosted External Party

This tutorial demonstrates how to onboard an **external party** using the Ledger API which is hosted on multiple validators. It is a simple extension to the onboard external party tutorial.

## Prerequisites

Make sure that you have completed the onboard external party tutorial and still have a running Canton example instance.

## Run The Script

The example script used in the previous tutorial also supports onboarding a multi-hosted external party. It will onboard by default on two nodes if invoked with the `--multi-hosted` command line argument.

```
./examples/08-interactive-submission/external_party_onboarding.sh --multi-hosted
```

## The Details of the Script

The flag `--multi-hosted` will pass the second participant id into the `generate-topology` request through the

```
`"otherConfirmingParticipantUids" : [$OTHER_PARTICIPANT_ID]`
```

field. This will cause the generated topology transaction to include the additional participant id in the hosting relation ship. Other options are fields such as `observingParticipantUids`, `confirmationThreshold` and more. If not configured, then the confirmation threshold will be set to the number of confirming nodes.

The generated topology transactions then just need to be uploaded to the Ledger API of the second participant:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
ALLOCATE=$(cat << EOF
{
  "synchronizer" : $SYNCHRONIZER_ID,
  "onboardingTransactions": $TRANSACTIONS,
  "multiHashSignatures": [{
     "format" : "SIGNATURE_FORMAT_CONCAT",
     "signature": "$SIGNATURE",
     "signedBy" : "$PUBLIC_KEY_FINGERPRINT",
     "signingAlgorithmSpec" : "SIGNING_ALGORITHM_SPEC_ED25519"
  }]
}
EOF
)

RESULT=$(curl -f -s -d "$ALLOCATE" -H "Content-Type: application/json" \
  -X POST ${PARTICIPANT1}/v2/parties/external/allocate)
```

You can try this out on the Canton console if you have two participants connected to the same synchronizer. In the following example, you will use the participant1 to create the hosting proposal for an internal party. This way, you don't need to deal with creating signatures for the topology transactions externally. The approval of the proposal will be done using participant2.

First, create a hosting proposal using participant1:

```none theme={"theme":{"light":"github-light","dark":"github-dark"}}
@ participant1.topology.party_to_participant_mappings.propose(
        com.digitalasset.canton.topology.PartyId.tryCreate("Alice", participant1.id.uid.namespace),
        newParticipants = Seq(
            (participant1.id, ParticipantPermission.Confirmation),
            (participant2.id, ParticipantPermission.Confirmation),
        ),
    )
    res1: SignedTopologyTransaction[TopologyChangeOp, PartyToParticipant] = SignedTopologyTransaction(
      TopologyTransaction(
        PartyToParticipant(
          Alice::12201ff69b1d...,
          PositiveNumeric(1),
          Vector(
            HostingParticipant(PAR::participant1::12201ff69b1d..., Confirmation, false),
            HostingParticipant(PAR::participant2::1220a4d7463b..., Confirmation, false)
          ),
          None
        ),
        serial = 1,
        operation = Replace,
        hash = SHA-256:483ecaff7581...
      ),
      signatures = 12201ff69b1d...,
      proposal
    )
```

Then, list the proposals on participant2. The new proposal should appear shortly:

```none theme={"theme":{"light":"github-light","dark":"github-dark"}}
@ participant2.topology.party_to_participant_mappings.list_hosting_proposals(sequencer1.synchronizer_id, participant2.id)
    res2: Seq[com.digitalasset.canton.admin.api.client.data.topology.ListMultiHostingProposal] = Vector(
      ListMultiHostingProposal(
        txHash = SHA-256:483ecaff7581...,
        party = Alice::12201ff69b1d...,
        permission = Confirmation$,
        others = PAR::participant1::12201ff69b1d... -> Confirmation$,
        threshold = 1
      )
    )
```

This will show the pending proposal, awaiting the signature of the second participant. The proposal is identified by the transaction hash `txHash`, which can be obtained from the output of the previous command:

```none theme={"theme":{"light":"github-light","dark":"github-dark"}}
@ val txHash = participant2.topology.party_to_participant_mappings.list_hosting_proposals(sequencer1.synchronizer_id, participant2.id).head.txHash
    txHash : TopologyTransaction.TxHash = TxHash(hash = SHA-256:483ecaff7581...)
```

Authorize the proposal using the console command topology.transactions.authorize:

```none theme={"theme":{"light":"github-light","dark":"github-dark"}}
@ participant2.topology.transactions.authorize(sequencer1.synchronizer_id, txHash)
    res4: SignedTopologyTransaction[TopologyChangeOp, TopologyMapping] = SignedTopologyTransaction(
      TopologyTransaction(
        PartyToParticipant(
          Alice::12201ff69b1d...,
          PositiveNumeric(1),
          Vector(
            HostingParticipant(PAR::participant1::12201ff69b1d..., Confirmation, false),
            HostingParticipant(PAR::participant2::1220a4d7463b..., Confirmation, false)
          ),
          None
        ),
        serial = 1,
        operation = Replace,
        hash = SHA-256:483ecaff7581...
      ),
      signatures = Seq(12201ff69b1d..., 1220a4d7463b...),
      proposal
    )
```

This will add the signature of participant2 to the proposal. Because the proposal is now fully signed, the party will appear as being hosted on both nodes:

```none theme={"theme":{"light":"github-light","dark":"github-dark"}}
@ participant1.parties.hosted("Alice")
    res5: Seq[ListPartiesResult] = Vector(
      ListPartiesResult(
        party = Alice::12201ff69b1d...,
        participants = Vector(
          ParticipantSynchronizers(
            participant = PAR::participant1::12201ff69b1d...,
            synchronizers = Vector(
              SynchronizerPermission(synchronizerId = local::122032922613..., permission = Confirmation)
            )
          ),
          ParticipantSynchronizers(
            participant = PAR::participant2::1220a4d7463b...,
            synchronizers = Vector(
              SynchronizerPermission(synchronizerId = local::122032922613..., permission = Confirmation)
            )
          )
        )
      )
    )
```
