What your wallet does with a PSBT, and how to sign safely
Signing a transaction that spends an inscription is not like approving a payment. The defaults in some wallet APIs will sign more than you meant, and the difference is measurable.
A PSBT is a partly signed transaction handed to a wallet to complete. Approving one is not the same as approving a payment: you are authorising a specific transaction, and the interesting question is whether the thing that comes back is still that transaction.
Three things a wallet can do that you did not ask for
Add inputs
Some wallets helpfully cover a shortfall by adding an input. In an ordinary payment that is convenient. In a transaction carrying an inscription it is dangerous, because satoshis are assigned to outputs in order — an extra input at the front shifts everything after it, and the inscription lands somewhere nobody chose.
Finalise
Several wallet APIs finalise by default: they sign and produce a broadcast-ready transaction in one step, giving you nothing to inspect in between. For anything holding an inscription, you want the unfinalised result so it can be compared with what was sent.
Sign more inputs than asked
If the input list is left unspecified, a wallet signs whatever it believes it owns. In a commit/reveal pair one input belongs to an ephemeral key the wallet does not hold and must not touch.
What we measured
This is not hypothetical worry. We probed UniSat on signet with a
reveal-shaped PSBT and a real 1,161,844-sat UTXO, asking it to sign input 0 and
nothing else, with autoFinalized: false and toSignInputs
naming index 0.
inputs 2 in, 2 out - nothing added
outputs 3 in, 3 out - nothing dropped
outpoints and order unchanged
sequences 0xfffffffd both, so RBF survives
output values and scripts unchanged
input 0 signed, 64 bytes, SIGHASH_DEFAULT
input 1 untouched - no signature
fields added by the wallet none
It behaved exactly as asked. But that result proves something narrower than it looks: it proves UniSat is well behaved when told properly. It says nothing about the defaults, and the defaults are the hazard. Both flags have to be set explicitly, every time.
Check what came back
The only real protection is re-reading the signed bytes and comparing them against the plan before broadcasting. Every one of these is worth an explicit check:
- Input count and every outpoint, in order.
- Output count, and every value and script.
- That the inscription's output is returned whole rather than trimmed to postage.
- Sequences, so replaceability survived.
- The sighash byte on every signature — anything but
SIGHASH_DEFAULTorSIGHASH_ALLlets the transaction be changed after signing. - Version and locktime. A locktime set into the future produces a perfectly valid, perfectly signed transaction that no miner may include yet, with your inscription stuck behind it.
None of this assumes a wallet is malicious. It assumes wallets are built for payments, where all of these behaviours are helpful, and that an inscription is not a payment.
Questions
- Is it safe to sign a transaction that spends my inscription?
- It is safe if what you sign is what was shown to you. The risks are a wallet that adds inputs of its own, one that finalises a transaction you wanted to review, and a signature type that lets the transaction be edited afterwards.
- What is SIGHASH and why does it matter here?
- It decides how much of the transaction your signature covers. Anything other than the defaults lets parts be changed after you sign — with an inscription in the transaction, that is a way to lose it.
More:
- How parent and child inscriptions work
- Where an inscription goes when you spend its UTXO
- Why some inscriptions cannot be moved safely
- What is actually inside an inscription
- Commit and reveal, and what to do when a reveal sticks
- How to verify an Ordinals collection yourself
- Inscription number, inscription id, collection index
- Recursive inscriptions, with a complete worked example