Signing Multisig Transactions from the CLI
A multisig account needs signatures from several keys before a transaction is valid. The
operator ms commands let each cosigner do their part from the command line:
- one cosigner creates the transaction and posts it to the Klever Multisign API, a service that holds it while signatures are collected;
- each remaining cosigner signs it from their own machine with
operator ms sign; - once the threshold is reached, anyone broadcasts it to the network.
No private key ever leaves the cosigner's machine. The API only receives the transaction and the signatures.
To set up the multisig account itself, see Account Permissions and
operator account permission. To sign from a
browser instead, see Multisignature.
Before you start
- Operator version.
operator ms signships in v1.7.20 and later. Check yours withoperator --version. The othermscommands are older. - One key per cosigner. Each cosigner runs the commands with their own PEM file
(
--key-file, default./walletKey.pem). See How To Run A Node for creating a wallet. - A permission that lists the cosigners. The transaction must use a permission whose signers and
threshold match the cosigners you expect. You pass its ID with
--permIDwhen you create the transaction. - The right network. The
mscommands use the mainnet Multisign API unless you pass--multisign-api.--nodehas no network default (it points tohttp://localhost:8080), so every command that builds a transaction, such asaccount permissionoraccount send, must pass it. Use the pair for your network:
| Network | --node | --multisign-api |
|---|---|---|
| Mainnet | https://node.mainnet.klever.org | https://multisign.mainnet.klever.org (default) |
| Testnet | https://node.testnet.klever.org | https://multisign.testnet.klever.org |
Any cosigner can sign with ms sign, with operator v1.7.20 or later. The Multisign API
identifies who signed from the signatures themselves, so no operator upgrade is needed. Earlier
versions of the API used the sender's address that the operator posts with each signature instead,
so once the sender's key had signed, any other cosigner got
address has already signed this transaction.
Running the operator from Docker
The examples call operator directly. With the Docker image, define an alias once per shell and the
examples work unchanged:
alias operator='docker run --rm -i -v "$(pwd)/wallet:/opt/klever-blockchain" --network=host --entrypoint=/usr/local/bin/operator kleverapp/klever-go:latest'
Use -i and not -it. With a TTY attached, the log lines and terminal colors are mixed into
the output, and the transaction JSON you redirect to a file in step 1 is no longer valid JSON. The
confirmation prompts still work with -i.
The workflow at a glance
| Step | Who | Command |
|---|---|---|
| 1. Create and sign the transaction, without broadcasting it | Any cosigner | operator account send ... -c --permID=N (or any other transaction command with -c) |
| 2. Post it to the Multisign API | The same cosigner | operator ms append |
| 3. Find it | Other cosigners | operator ms sign (interactive), ms by-address, or ms by-hash |
| 4. Sign it | Each other cosigner | operator ms sign <txHash> |
| 5. Broadcast it | Anyone, once the threshold is reached | operator ms broadcast <txHash> |
Walkthrough: a 2-of-2 transfer on testnet
This walkthrough was run end to end on testnet. Two cosigners, Alice and Bob, control a multisig
account. Alice's key is the account's own key; Bob is the second signer. The account has a
permission with ID 1 that allows transfers and needs both signatures (threshold 2, weight 1
each).
Set the testnet endpoints once in each cosigner's shell:
NODE=https://node.testnet.klever.org
MS_API=https://multisign.testnet.klever.org
0. Create the permission (once)
Alice, as the account owner, adds permission 1. This example keeps Alice as the single signer of
the Owner permission and adds a 2-of-2 User permission for transfers (operations 01, bit 0 =
Transfer):
operator account permission --node=$NODE \
--perm='{"type":0,"permissionName":"owner","threshold":1,"signers":[{"address":"<ALICE_ADDRESS>","weight":1}]}' \
--perm='{"type":1,"permissionName":"2of2 transfers","threshold":2,"operations":"01","signers":[{"address":"<ALICE_ADDRESS>","weight":1},{"address":"<BOB_ADDRESS>","weight":1}]}'
Updating permissions is itself a transaction with a fee, so the account needs a balance first. On testnet, request KLV from the faucet in the Testnet Explorer.
1. Alice creates the transaction
-c (--create-only) builds and signs the transaction with Alice's key and prints it as JSON
instead of broadcasting it. --permID=1 makes it use the 2-of-2 permission. The amount is in KLV:
operator account send <BOB_ADDRESS> 1 -c -s --permID=1 --node=$NODE > tx.json
-s skips the confirmation prompt. It is needed here because the prompt is printed to the same
output you redirect to tx.json. To review the transaction first, run the command once without -s
and without the redirect, answer no, then run it again as above.
tx.json holds the raw transaction with Alice's signature:
{
"RawData": {
"Nonce": 1,
"Sender": "Xkqo77NSTBBQuZuLYOf53upxfcDvwt3iqpcmLcvoaLU=",
"Contract": [ ... ],
"PermissionID": 1,
...
},
"Signature": [
"D+QyL8dHB1csoRv1uQEMuNTuQ39oQv/L0VbmxMTrsBpSWC1Jdf+wMfTrrwzHR7syZ/W2Rn/fsJS52+/eznHHCw=="
]
}
2. Alice posts it to the Multisign API
operator ms append "$(cat tx.json)" --multisign-api=$MS_API
INFO posting signature...
INFO successful posted signature txHash = 55018659e6854b8ab50b543e6d5b3d9974cadfae6b37d098fd8da87ee0f14df4 address = klv1te92...
Alice sends the txHash to Bob. The API now shows the transaction with 1 of 2 signatures.
3. Bob finds the transaction
With no argument, ms sign lists the pending transactions where Bob's address is a signer:
operator ms sign --multisign-api=$MS_API --node=$NODE
+---+------------------------------------------------------------------+-------------------+------------------+---------+--------+--------+
| # | hash | address | type | signers | weight | signed |
+---+------------------------------------------------------------------+-------------------+------------------+---------+--------+--------+
| 0 | 55018659e6854b8ab50b543e6d5b3d9974cadfae6b37d098fd8da87ee0f14df4 | klv1te92... | TransferContract | 1/2 | 1/2 | false |
+---+------------------------------------------------------------------+-------------------+------------------+---------+--------+--------+
Page 1/1 [0-0]: Select Transaction, [q]uit
Input:
The columns are the transaction hash, the sending account, the contract type, how many listed
signers have signed, the signed weight against the threshold, and whether you have signed. The
list shows three transactions per page; type n and p to move between pages and q to quit
without signing.
4. Bob signs
Bob types the row number, or signs by hash directly:
operator ms sign 55018659e6854b8ab50b543e6d5b3d9974cadfae6b37d098fd8da87ee0f14df4 --multisign-api=$MS_API --node=$NODE
Either way, the operator prints the decoded transaction and asks for confirmation:
Requested Transaction Details:
{
"hash": "55018659e6854b8ab50b543e6d5b3d9974cadfae6b37d098fd8da87ee0f14df4",
"sender": "klv1te92...",
"permissionID": 1,
"contract": [
{
"typeString": "TransferContractType",
"parameter": {
"assetId": "KLV",
"toAddress": "klv10ktp...",
"amount": 1000000
}
}
],
...
}
Please carefully review the transaction above.
Would you like to sign this transaction? (yes/no)
Check the sender, the receiver, the asset, and the amount (in the asset's smallest unit: 1000000
is 1 KLV) before answering yes. The operator signs locally, posts the signature, and logs
successful posted signature. The address in that line is the sending account's (Alice's), not
Bob's: the operator always posts with the sender's address, and the API works out from the
signature that Bob signed.
5. Anyone broadcasts
When the signed weight reaches the threshold, any cosigner can ask the API to broadcast:
operator ms broadcast 55018659e6854b8ab50b543e6d5b3d9974cadfae6b37d098fd8da87ee0f14df4 --multisign-api=$MS_API
INFO successful added txHash = 55018659e6854b8ab50b543e6d5b3d9974cadfae6b37d098fd8da87ee0f14df4
Confirm it landed with operator tx-by-id or on the
Testnet Explorer. Broadcasting before the threshold is reached
fails with transaction is not fully signed yet.
operator ms sign
Signs a pending transaction from the Multisign API with your key and posts the signature back.
operator ms sign [txHash] [flags]
It has two modes:
| Mode | Invocation | What it does |
|---|---|---|
| By hash | operator ms sign <txHash> | Fetches that transaction from the API, checks its hash, asks for confirmation, signs, and posts the signature. |
| Interactive | operator ms sign | Lists the pending transactions for your address, lets you pick one, asks for confirmation, signs, and posts the signature. |
The hash can be given with or without the 0x prefix, but the hex must be lower-case: the
operator compares it with the lower-case hash it computes, so an upper-case hash fails the check
below.
The hash check. In by-hash mode the operator recomputes the hash of the transaction the API
returned and refuses to sign if it differs from the hash you typed
(transaction hash mismatch: expected …, got …). A tampered or wrong API response cannot get you
to sign a different transaction than the one you were told about. Interactive mode has no hash to
compare against, which is one more reason to read the decoded transaction before you answer yes.
Skipping the confirmation. The global -s / --sign flag skips the prompt in by-hash mode, for
scripted cosigners:
operator ms sign <txHash> -s --multisign-api=$MS_API
Interactive mode ignores -s and always asks. There is no --yes / -y flag.
What it checks before signing:
- your address is one of the transaction's listed signers, otherwise it stops with
signer … is not required to sign transaction …; - you have not signed yet, otherwise it prints
Transaction … has already been signed by …and exits without error.
Other ms commands
All ms commands share the --multisign-api flag and the global operator flags.
operator ms append
operator ms append [Transaction JSON]
Posts a transaction to the Multisign API so cosigners can find it. Pass the JSON printed by a
transaction command run with -c. The API records the signatures already on it, so the creator
counts as the first signer.
operator ms broadcast
operator ms broadcast [txHash]
Asks the API to broadcast a fully signed transaction to the network.
operator ms by-hash
operator ms by-hash [txHash]
Prints one transaction from the API as JSON: its hash, sending account, signers (address, weight, whether they signed), threshold, and the raw transaction.
operator ms by-address
operator ms by-address [Address]
Prints, as JSON, the pending transactions the API holds where that address is one of the
permission's signers. A sending account that is not itself a signer does not find its transactions
this way; use ms by-hash. A broadcast transaction stays in the list until it is confirmed and the
account nonce moves past it.
operator ms decode
operator ms decode [Transaction JSON]
Prints a raw transaction in readable form (sender, contracts and their parameters, permission, fees), the same view the confirmation prompt shows. Useful to review a transaction someone sent you as JSON.
operator ms encode
operator ms encode [Transaction JSON]
Prints the payload ms append would post: the transaction hash, the sending account, and the raw
transaction. Use it to get the hash before posting.
Flags
| Flag | Default | Applies to | Meaning |
|---|---|---|---|
--multisign-api | https://multisign.mainnet.klever.org | all ms commands | Multisign API to use. Set it to the testnet URL on testnet. |
-k, --key-file | ./walletKey.pem | all | Your PEM key. Its address is the signer. |
-n, --node | http://localhost:8080 | creating transactions | Node API used to build the transaction and read the account nonce. |
-s, --sign | off | ms sign by hash, -c creation | Sign without the confirmation prompt. Ignored by interactive ms sign. |
-c, --create-only | off | transaction commands | Build and sign the transaction, print it as JSON, and do not broadcast it. |
--permID | 0 | transaction commands | Permission the transaction uses. Must be the multisig permission. |
-p, --password / --password-file | none | all | Password of an encrypted PEM file. |
Troubleshooting
| Message | Cause | What to do |
|---|---|---|
Transaction … has already been signed by … | Your key already signed it. | Nothing. Wait for the other cosigners or broadcast once the threshold is met. |
signer … is not required to sign transaction … | Your address is not a signer of the transaction's permission. | Check --key-file and that the transaction uses the right --permID. |
invalid TX hash length: N / invalid TX hash … | The hash is not 64 hexadecimal characters. | Copy the full hash in lower-case hex; a 0x prefix is fine. |
transaction hash mismatch: expected …, got … | The API returned a transaction whose hash differs from the one you gave. | Do not sign. Confirm the hash with whoever created the transaction and check --multisign-api. |
unexpected HTTP status 404 Not Found on GET …/transaction/<hash> | The API does not know this hash. | Check the hash and that --multisign-api points to the network the transaction was posted on. |
no transactions found for signing | Interactive mode found nothing pending for your address. | Check --key-file, --multisign-api, and that the transaction was appended. |
address has already signed this transaction (HTTP 400 on POST /transaction) | The API already holds every signature the request carries, so it brought nothing new. With earlier versions of the Multisign API, which used the sender's address instead of the signatures, this also happened when a cosigner other than the sender signed after the sender; see the note in Before you start. | Nothing to do if your key already signed; ms by-hash shows who has signed. |
transaction is not fully signed yet | ms broadcast ran before the threshold was reached. | Wait for more signatures; ms by-hash shows who has signed. |
transaction signing has been canceled by the user | You answered anything other than yes at the prompt. | Run the command again if you meant to sign. |
connection refused / no such host | The Multisign API or node is unreachable. | Check the URL, your network, and that you are not mixing mainnet and testnet endpoints. |
Security notes
- Read before you sign. The confirmation prompt is your last check. Verify the sender, the
receiver, the asset, the amount, and the permission ID. Only use
-sfor cosigners whose transactions come from a source you control. - Prefer signing by hash. Get the hash from the creator through a channel other than the Multisign API; the by-hash check then guarantees you sign that exact transaction.
- Protect your key file. The PEM file is the signing key. Keep it encrypted (
--passwordor--password-file), readable only by you, and off shared machines. Only signatures are sent to the API, never the key. - Try it on testnet first. Run the walkthrough on testnet with throwaway keys before using a mainnet multisig account.
Related
- Multisignature — how multisig works and how to sign from KleverScan
- Account Permissions — permissions, thresholds, and weights
- Node Operations — the full operator CLI reference