Document SIGHASH_ALL requirement

This commit is contained in:
Dagur Valberg Johannsson 2026-03-18 10:30:11 +01:00
parent 37cc29bcc0
commit 55ff82bbec
No known key found for this signature in database
GPG key ID: FD701804AEE88107
2 changed files with 32 additions and 1 deletions

View file

@ -248,6 +248,29 @@ at position `inputIndex`. Only inputs that require wallet signing need an entry
pre-set unlocking bytecode can be omitted. This allows the wallet to sign each input without scanning pre-set unlocking bytecode can be omitted. This allows the wallet to sign each input without scanning
or guessing which key was used. or guessing which key was used.
#### SIGHASH requirement (security-critical)
Wallets **MUST** sign every input with `SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_UTXOS` and **MUST**
reject any request that would require different flags.
`SIGHASH_ALL` ensures the signature commits to the entire transaction (all inputs and all outputs).
Without it, an attacker could collect a valid signature and graft it onto a different transaction —
for example, using `SIGHASH_NONE` an attacker could replace every output to redirect funds.
Because `inputPaths` lets the dapp specify which key signs each input, the wallet no longer
independently verifies that the key matches the UTXO's locking bytecode. This is safe **only** when
`SIGHASH_ALL` is enforced: if the dapp provides a wrong path, the resulting signature is invalid
(public key hash mismatch) and the transaction cannot broadcast. Without `SIGHASH_ALL`, a
wrong-key signature could still be repurposed in a different transaction context.
Summary of the flags:
| Flag | Purpose |
|------|---------|
| `SIGHASH_ALL` | Commits to all inputs and outputs — prevents output substitution |
| `SIGHASH_FORKID` | Prevents cross-fork replay (BCH ↔ BTC) |
| `SIGHASH_UTXOS` | Commits to all input UTXOs — prevents input substitution after signing |
### sign_transaction_response ### sign_transaction_response
```typescript ```typescript

View file

@ -163,6 +163,13 @@ application is responsible for:
The wallet library does not auto-sign or auto-reject anything. The wallet library does not auto-sign or auto-reject anything.
**SIGHASH enforcement:** The wallet **MUST** sign every input with
`SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_UTXOS`. See the
[SIGHASH requirement](protocol.md#sighash-requirement-security-critical) section in the protocol
docs for the security rationale. Because the dapp specifies `inputPaths`, the wallet trusts the
dapp's key selection — `SIGHASH_ALL` is what makes this safe (a wrong-key signature is simply
invalid and cannot be repurposed).
## Minimal example ## Minimal example
```typescript ```typescript
@ -178,7 +185,8 @@ class MyAdapter implements WalletAdapter {
getXpub(path: DerivationPath) { /* ... */ } getXpub(path: DerivationPath) { /* ... */ }
async signTransaction(request) { async signTransaction(request) {
// show approval UI, sign, return hex // Show approval UI, sign with SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_UTXOS, return hex.
// See protocol.md "SIGHASH requirement" — other sighash flags MUST be rejected.
return { signedTransactionHex: "..." }; return { signedTransactionHex: "..." };
} }
} }