From c46c11bfc6700dcefcedb824f0e0c333cb8fa06d Mon Sep 17 00:00:00 2001 From: Mathieu Geukens Date: Wed, 1 Jul 2026 16:20:07 +0200 Subject: [PATCH] Document sighash flags in signature, checkSig and HashType docs (#384, #310) --- website/docs/language/functions.md | 3 +++ website/docs/language/types.md | 2 +- website/docs/sdk/signature-templates.md | 23 ++++++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/website/docs/language/functions.md b/website/docs/language/functions.md index ea7abe2b..6a8f84f8 100644 --- a/website/docs/language/functions.md +++ b/website/docs/language/functions.md @@ -95,6 +95,8 @@ bool checkSig(sig s, pubkey pk) Checks that transaction signature `s` is valid for the current transaction and matches with public key `pk`. +A signature always authorizes the spending of a single input, but the signer chooses how much of the surrounding transaction that authorization is bound to. This is controlled by the [sighash flag][hashtype], encoded in the last byte of `s`. It defaults to `SIGHASH_ALL`, which binds the signature to all of the transaction's inputs and outputs, so the signed input can only be spent in exactly that transaction. Other flags leave parts of the transaction context uncommitted. See the [HashType][hashtype] documentation for the available flags and how to restrict which ones your contract accepts. + ### checkMultiSig() ```solidity bool checkMultiSig(sig[] sigs, pubkey[] pks) @@ -129,3 +131,4 @@ Using `bytes20 placeholderPkh = toPaddedBytes(0, 20)` will generate a 20 byte ze [bip146]: https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki [local-state-guide]: /docs/guides/covenants#keeping-local-state-in-nfts +[hashtype]: /docs/sdk/signature-templates#hashtype diff --git a/website/docs/language/types.md b/website/docs/language/types.md index cdcdf61f..e5ef16d3 100644 --- a/website/docs/language/types.md +++ b/website/docs/language/types.md @@ -127,7 +127,7 @@ Operators: - `!=` (inequality) ### Transaction Signature -`sig`: Byte sequence representing a transaction signature. Generally 65 bytes long. +`sig`: Byte sequence representing a transaction signature. Generally 65 bytes long: a 64-byte Schnorr signature followed by a single sighash flag byte that indicates which parts of the transaction were signed. See [HashType](/docs/sdk/signature-templates#hashtype) for the meaning of this byte. Operators: diff --git a/website/docs/sdk/signature-templates.md b/website/docs/sdk/signature-templates.md index 9f988966..a0b59cf0 100644 --- a/website/docs/sdk/signature-templates.md +++ b/website/docs/sdk/signature-templates.md @@ -93,7 +93,9 @@ const signature = aliceTemplate.signMessageHash(sha256(hexToBin('000000000000000 ### HashType -The default `hashtype` is `HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS` because this is the most secure option for smart contract use cases. +The `hashtype` (also called the sighash flag) determines which parts of the spending transaction the signature commits to. When the transaction is signed, these parts are serialized into a signing serialization (or sighash preimage), which is hashed and signed. + +The default `hashtype` is `HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS` because this is the most secure option for smart contract use cases: it commits to every input and output of the transaction, so the signed transaction cannot be altered in any way after signing. ```ts export enum HashType { @@ -105,6 +107,20 @@ export enum HashType { } ``` +`SIGHASH_ALL`, `SIGHASH_NONE` and `SIGHASH_SINGLE` choose which outputs are signed, while `SIGHASH_UTXOS` and `SIGHASH_ANYONECANPAY` are modifiers that can be OR'd on top to change what else is committed to. + +| Flag | Value | Commits to | Typical use | +| --- | --- | --- | --- | +| `SIGHASH_ALL` | `0x01` | all inputs and **all** outputs | the default — sign the exact transaction | +| `SIGHASH_NONE` | `0x02` | all inputs, but **no** outputs | let the outputs be decided after signing | +| `SIGHASH_SINGLE` | `0x03` | all inputs, and only the **one** output at the same index as the signed input | pair a single input to a single output | +| `SIGHASH_UTXOS` | `0x20` | *(modifier)* additionally commits to the full contents of the UTXOs being spent | recommended for all contracts — see below | +| `SIGHASH_ANYONECANPAY` | `0x80` | *(modifier)* only the **current** input, allowing other inputs to be added | crowdfunding-style transactions where anyone can add an input | + +The default `SIGHASH_ALL | SIGHASH_UTXOS` is the right choice for almost all smart contract use cases: `SIGHASH_UTXOS` commits to the full value and token contents of every UTXO being spent, which protects introspection-based covenants against an attacker substituting a spent UTXO for a different one of equal amount. The other flags weaken these commitments. `SIGHASH_NONE` and `SIGHASH_SINGLE` leave outputs unsigned, making the transaction malleable, and `SIGHASH_ANYONECANPAY` only signs the current input. Only use them when you specifically need that flexibility. + +For a full technical breakdown of the signing serialization and every valid flag combination, see the [Bitcoin Cash transaction signing reference][tx-signing]. + #### Example ```ts const wif = 'L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1'; @@ -116,6 +132,10 @@ const signatureTemplate = new SignatureTemplate( const configuredHashType = signatureTemplate.getHashType() ``` +#### The hashtype byte + +The `hashtype` is appended as a single byte to the end of every signature, so a validator or contract can tell how a signature was produced by inspecting its last byte. On Bitcoin Cash the `SIGHASH_FORKID` bit (`0x40`) is always set. `SignatureTemplate` OR's it in automatically, so the byte appended to the signature is your `hashtype` combined with `0x40`. For example the default `SIGHASH_ALL | SIGHASH_UTXOS` (`0x21`) is appended as `0x61`. This is the value to compare against when inspecting a signature's last byte inside a contract. + ### SignatureAlgorithm The `signatureAlgorithm` parameter determines the cryptographic algorithm used for signing. By default, the modern and compact Schnorr algorithm is used. @@ -141,3 +161,4 @@ const configuredSignatureAlgorithm = signatureTemplate.getSignatureAlgorithm() [wif]: https://en.bitcoin.it/wiki/Wallet_import_format [ecpair]: https://bchjs.fullstack.cash/#api-ECPair [privatekey]: https://github.com/bitpay/bitcore/blob/master/packages/bitcore-lib-cash/docs/privatekey.md +[tx-signing]: https://documentation.cash/protocol/blockchain/transaction/transaction-signing.html