How to partially sign a transaction?

My scenario is I am trying to register my stake pool with funds and a stake addr from a 2nd party. The solution to this would be to sign the registration certificate with the pool keys and then send the partially signed certificate to the 2nd party to sign with the rest of the keys. Once this is done I then need to do the same for the certificate submission transactions .

Is this even possible?

My tests with partial signatures don’t seem to have worked since trying to sign an already signed transaction doesn’t work.

Shelley command failed: transaction sign Error: tx.tmp: TextView type error: Expected: TxUnsignedShelley Actual: TxSignedShelley

1 Like

Were you ever able to figure out if this can work? Is it possible to partially sign a transaction and then send this to the other party to sign the rest?

Currently, it would seem I need to know there .skey as well which is a security risk for them if I know it.

OK, here is an example of all the steps to register a stake pool using 3 different keys:

  • Pool reward account (key residing on a hardware token eg: Trezor T)
  • Payment account (this example generates a new software key and some funds are transferred to this account for paying the transaction fees)
  • Pool cold key

In this example

  • –pool-reward-account-verification-key-file
  • –pool-owner-stake-verification-key-file

are both the same key. If you set them to different keys then you will need 4 different keys to sign the transaction.

1. Update your pool metadata and extended json files

You should already have created your metadata json file etc.
Check the formatting of the json file:

jq '.' metadata.json

Update the hash

cardano-cli stake-pool metadata-hash --pool-metadata-file metadata.json

a0b87dbab7655a52d3224633cd82411e3b28a2b4293338d3bc39619419f8e653

That is my metadata hash, yours will be different.

2. Generate temporary payment keys and address to pay transaction fees

You could use a wallet you already have, but if you want to keep things separate from your personal wallet then just generate a new wallet for this temporary use.

cardano-cli address key-gen \
  --verification-key-file payment.vkey \
  --signing-key-file payment.skey; \

cardano-cli stake-address key-gen \
  --verification-key-file stake.vkey \
  --signing-key-file stake.skey; \

cardano-cli address build \
  --payment-verification-key-file payment.vkey \
  --stake-verification-key-file stake.vkey \
  --out-file payment.addr \
  --mainnet; \

cat payment.addr && echo;

3. Transfer 5 Ada to the temporary payment.addr from another wallet you own or from an exchange. (If this is a first time pool registration then transfer 505 Ada because you will need to pay the 500 Ada pool registration deposit too.)

4. Generate hw-stake.vkey from hardware wallet

cardano-hw-cli address key-gen \
  --path 1852H/1815H/0H/2/0 \
  --verification-key-file hw-stake.vkey \
  --hw-signing-file hw-stake.hwsfile

You can check the stake address is correct:

cardano-cli stake-address build \
  --stake-verification-key-file hw-stake.vkey \
  --out-file hw-stake.addr \
  --mainnet; \

cat hw-stake.addr && echo

Look on cardanoscan.io to be sure that the address correctly identifies your hardware wallet.

5. Generate new stake pool registration certificate:

cardano-cli stake-pool registration-certificate \
  --cold-verification-key-file cold.vkey \
  --vrf-verification-key-file vrf.vkey \
  --pool-pledge 100000000000 \
  --pool-cost 340000000 \
  --pool-margin 0.01 \
  --pool-reward-account-verification-key-file hw-stake.vkey \
  --pool-owner-stake-verification-key-file hw-stake.vkey \
  --mainnet \
  --pool-relay-ipv4 180.150.102.25 \
  --pool-relay-port 2700 \
  --pool-relay-ipv4 180.150.96.245 \
  --pool-relay-port 2700 \
  --pool-relay-ipv4 144.126.157.46 \
  --pool-relay-port 2700 \
  --single-host-pool-relay relays.terminada.io \
  --pool-relay-port 2700 \
  --metadata-url https://terminada.io/metadata.json \
  --metadata-hash a0b87dbab7655a52d3224633cd82411e3b28a2b4293338d3bc39619419f8e653 \
  --out-file pool-registration.cert

The IP addressess are for my stake pool and the relays.terminada.io domain name points to the same 3 IP addresses. The pledge in this example is 100K (expressed in lovelaces) so change to your needs.

6. On relay check transaction hash and index of UTxO to spend:

Check the payment.addr for your wallet that will pay the transaction fees.

cat payment.addr && echo

Now on relay check the balance of this address (following your transfer of 5 Ada above.)

CARDANO_NODE_SOCKET_PATH="/run/cardano/mainnet-node.socket" cardano-cli query utxo \
  --address "addr1blahblahblah" \
  --mainnet

Hashblahblahblah 0 5000000 lovelace + TxOutDatumNone

7. Build the transaction

This step needs to be done on a live synced relay
Nb. fees are automatically paid and all change will go to --change-address

cardano-cli transaction build \
  --mainnet \
  --tx-in Hashblahblahblah#0 \
  --change-address addr1blahblahblah \
  --certificate-file pool-registration.cert \
  --witness-override 3 \
  --out-file tx.raw

Note the ‘witness-override 3’. There are 3 keys that need to sign this transaction:

  • Pool cold key
  • Stake pool reward key (on hardware token)
  • Payment key (for paying the transaction fees)

Set the change address to go back to any account you want. For example, use your hardware wallet account if you like and this will result in your temporary software wallet being emptied after the transaction.

8. Create transaction witnesses

Payment witness (Payer of pool deposit and transaction fees) (payment-skey)
Nb. For this particular case the pool deposit of 500 Ada has already paid previously. If this is to be a first time pool registration then you will need to have 505 Ada in your wallet rather than just 5.

cardano-cli transaction witness \
  --tx-body-file tx.raw \
  --signing-key-file payment.skey \
  --mainnet \
  --out-file payment.witness

Pool witness (Pool cold.skey)
This should be done on your air gaped machine.

cardano-cli transaction witness \
  --tx-body-file tx.raw \
  --signing-key-file cold.skey \
  --mainnet \
  --out-file pool.witness

Owner witness (hw-stake.hwsfile)

cardano-hw-cli transaction witness \
  --tx-body-file tx.raw \
  --hw-signing-file hw-stake.hwsfile \
  --mainnet \
  --out-file hw-stake.witness

Each of the above signatures are done separately. They can be done on separate machines by different individuals at different locations. Each of the separate out-files then needs to be gathered for the next step.

9. Create signed transaction

cardano-cli transaction assemble \
  --tx-body-file tx.raw \
  --witness-file payment.witness \
  --witness-file pool.witness \
  --witness-file hw-stake.witness \
  --out-file tx.signed

10. Submit the transaction

Copy tx.signed to live synced relay

CARDANO_NODE_SOCKET_PATH="/run/cardano/mainnet-node.socket" cardano-cli transaction submit \
  --tx-file tx.signed \
  --mainnet

11. Confirm new stake pool registration details by looking for it at pooltool.io.

Good Luck.

2 Likes

Or shorter: Yes, cardano-cli transaction sign won’t let you accumulate signatures from several sources.

But cardano-cli transaction sign is only a shortcut for cardano-cli transaction witness (creating a signature/witness for a transaction) followed by cardano-cli transaction assemble (putting together an unsigned transaction with an arbitrary number of signatures/witnesses to a signed transaction that can be submitted).

You can send the unsigned transaction to all people that have to sign. They can independently sign with cardano-cli transaction witness and send you back the witness. And once all witnesses are there, you can do cardano-cli transaction assemble with all of them.

(EDIT: You should set the time to live --invalid-hereafter really high for that.)

2 Likes

@HeptaSean Can you show me an example with the --invalid-hereafter, what is the default for this?

@Terminada Oh and on a side note I was succesfull getting it up and running using your technique :), look at it here (Preview TTSP1 Trading Tools Software Pool - Cardanoscan), you can see the multiple owners.

1 Like