Cardano in the Shelley era supports creating multisig transactions, transactions containing multisig addresses and witnesses.
Before we create the transaction itself we will create a multisig address first. We will create a payment native script and a stake native script from which we will then assemble the address. More information about native scripts can be found in the CIP-1854.
To create a multisig address you need a payment native script and a stake native script. All public keys used inside the scripts need to be derived using the 1854H
paths.
The derivation path is described in CIP-1854. It is similar to the 1852H
derivation path, but it begins with 1854H
instead. We can generate the payment key like this:
cardano-hw-cli address key-gen \
--path 1854H/1815H/0H/0/0 \
--verification-key-file payment-hw.vkey \
--hw-signing-file payment-hw.hwsfile
The stake key has the role 2
, as is the case with 1852H
keys:
cardano-hw-cli address key-gen \
--path 1854H/1815H/0H/2/0 \
--verification-key-file stake.vkey \
--hw-signing-file stake.hwsfile
The payment native script will be in this example an "all" script with two public key hashes. One public key will be the one we generated above on the hardware wallet. The second one we will generate using cardano-cli
.
cardano-cli address key-gen \
--normal-key \
--verification-key-file payment-cli.vkey \
--signing-key-file payment-cli.skey
cat >payment.script <<EOF
{
"type": "all",
"scripts": [
{
"type": "sig",
"keyHash": "$(cardano-cli address key-hash --payment-verification-key-file payment-hw.vkey)"
},
{
"type": "sig",
"keyHash": "$(cardano-cli address key-hash --payment-verification-key-file payment-cli.vkey)"
}
]
}
EOF
The stake native script will be a simple sig
(pubkey hash) native script:
cat >stake.script <<EOF
{
"type": "sig",
"keyHash": "$(cardano-cli stake-address key-hash --stake-verification-key-file stake.vkey)"
}
EOF
You can read more about scripts in the cardano-node
docs. Feel free to experiment with creating more complex scripts. This example is just a very simple demonstration.
Now you will want to create the payment address:
cardano-cli address build \
--payment-script-file payment.script \
--stake-script-file stake.script \
--out-file payment.addr \
--mainnet
Or you can swap --mainnet
for --testnet-magic PROTOCOL_MAGIC
if you want to create a testnet address (see https://book.world.dev.cardano.org/environments.html for current values of PROTOCOL_MAGIC).
Now we have created a multisig address and we are able to continue with creating a simple transaction. We will assume you have already sent some funds to your newly created address (for testnet addresses you can use the Faucet).
First we need to query for the UTxO:
cardano-cli query utxo \
--address $(cat payment.addr) \
--mainnet
Example return:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d 0 1000000000 lovelace + TxOutDatumHashNone
cardano-cli transaction build-raw \
--alonzo-era \
--tx-in "94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d#0" \
--tx-in-script-file payment.script \
--tx-out "$(cat payment.addr)"+0 \
--fee 0 \
--out-file tx.draft \
--cddl-format
Note: if you don't have a protocol.json
file available you can obtain it with cardano-cli
:
cardano-cli query protocol-parameters \
--out-file protocol.json \
--mainnet
Then we can calculate the fee:
cardano-cli transaction calculate-min-fee \
--tx-body-file tx.draft \
--tx-in-count 1 \
--tx-out-count 1 \
--witness-count 2 \
--mainnet \
--protocol-params-file ./protocol.json
Example output:
194654 Lovelace
Note that an ordinary transaction looking like this would most likely require one signature. However, our payment part is defined with a script that requires two signatures. Therefore, we need two witnesses for this transaction.
Now we can calculate the output amount (input minus the fee) and create the unsigned transaction.
expr 1000000000 - 194654
cardano-cli transaction build-raw \
--alonzo-era \
--tx-in "94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d#0" \
--tx-in-script-file payment.script \
--tx-out "$(cat payment.addr)"+999805346 \
--fee 194654 \
--out-file tx.raw \
--cddl-format
At this point, the native script is present in the transaction witness set - now we only need to add the witnesses with signatures.
HW wallets expect the transaction CBOR to be in canonical format (see CIP-0021). Unfortunately, cardano-cli sometimes produces tx files not compliant with CIP-0021. Use the following command to fix the formatting issues.
cardano-hw-cli transaction transform \
--tx-file tx.raw \
--out-file tx.transformed
Because we need two witnesses, one from the key from hardware wallet and one from the key from cardano-cli
, we will create them separately and afterwards assemble them together to create a signed transaction.
cardano-hw-cli transaction witness \
--tx-file tx.transformed \
--hw-signing-file payment-hw.hwsfile \
--out-file payment-hw.witness \
--mainnet
cardano-cli transaction witness \
--tx-body-file tx.transformed \
--signing-key-file payment-cli.skey \
--out-file payment-cli.witness \
--mainnet
And now assembling:
cardano-cli transaction assemble \
--tx-body-file tx.transformed \
--witness-file payment-hw.witness \
--witness-file payment-cli.witness \
--out-file tx.signed
We now have the signed transaction ready and we can submit it to the blockchain:
cardano-cli transaction submit \
--tx-file tx.signed \
--mainnet