Signing¶
Signing fills each input's signature_script with proof that the
spender controls the key the UTXO is locked to. Kaspa defaults to
Schnorr signatures over secp256k1; ECDSA is supported for inputs
locked to ECDSA addresses. Multisig inputs combine multiple
signatures under a script-hash lockup.
For Schnorr vs ECDSA on the addressing side, see Addresses → Versions.
Sign a manually built transaction¶
Run update_transaction_mass first, then sign_transaction:
from kaspa import sign_transaction, update_transaction_mass
update_transaction_mass("mainnet", tx) # mass is signed over — fill first
signed = sign_transaction(tx, [private_key], verify_sig=True)
sign_transaction(tx, signers, verify_sig):
signers— a list ofPrivateKey. The signer for each input is inferred from the input's UTXO lockup; pass every key any input needs.verify_sig=True— verify each signature after writing it. Raises on mismatch (a corrupt input or wrong key). Cheap insurance during development; disable in performance-sensitive paths once you trust the inputs.
Changing inputs, outputs, or mass after signing invalidates the signature — sign last.
Sign a generator-produced PendingTransaction¶
A PendingTransaction yielded by Generator (see Transaction Generator) signs in one call:
For multisig, hand in every cosigner's key:
For per-input control:
sign_input is the right surface when different inputs need different
signers (mixed-key wallets, partially-co-signed flows).
SighashType¶
The hash that gets signed describes which parts of the transaction
the signature commits to.
SighashType.All is the
default and the only one most code should use — it signs every input
and every output.
The other variants (_None, Single, and the *AnyOneCanPay
flavors) exist for advanced collaborative flows like coinjoins,
where cosigners add inputs or outputs after one party signs. If
you don't have a specific protocol reason, leave it at All.
Build a signature without filling the input¶
When you need raw signature bytes — e.g. to send to a co-signer for
aggregation — use create_input_signature:
from kaspa import SighashType, create_input_signature
sig_hex = create_input_signature(
tx,
input_index=0,
private_key=key,
sighash_type=SighashType.All,
)
The same method exists on PendingTransaction
(pending.create_input_signature(...)); write the resulting script
back with pending.fill_input(...).
Compute a sighash without signing¶
When the key isn't available in-process — an external or hardware
signer, multisig digest distribution, or verifying a signature
against the exact digest the node checks — use
compute_sighash to
get the signature hash for an input without signing it:
from kaspa import compute_sighash, sign_script_hash
digest = compute_sighash(tx, input_index=0) # Hash, SighashType.All
sig_hex = sign_script_hash(digest.to_hex(), private_key)
Every input must carry its UTXO entry — the sighash commits to each
input's script public key and amount. Schnorr is the default; pass
ecdsa=True for inputs locked to ECDSA addresses (an extra hash
round over the Schnorr digest).
sign_script_hash
always signs Schnorr and appends the SighashType.All hashtype
byte, so it only composes with digests computed with the defaults
shown above. For any other sighash_type, or for a digest computed
with ecdsa=True, sign the digest with your external signer and
assemble the signature script yourself: the signature followed by
the hashtype byte matching the digest. The blob from
sign_script_hash is a complete signature script for a single-key
(P2PK) input; for script-hash lockups, wrap it with
pay_to_script_hash_signature_script.
Multisig and sig_op_count¶
Two fields interact with mass when you sign:
sig_op_counton each input — number of signature ops the input actually performs.1for a single-key spend,Mfor anM-of-Nmultisig.minimum_signaturespassed toupdate_transaction_mass(..., minimum_signatures=M)andcalculate_transaction_mass— tells the mass calculator how big the filled-in signature script will be.
Wrong values yield wrong mass — the transaction is rejected (mass
too low) or pays more fee than it needs (mass too high). The
Generator handles this when you pass sig_op_count and
minimum_signatures to the constructor.
For the full multisig flow, see
examples/transactions/multisig.py.