Chip-2026-06: op_sighash

This is a low hanging fruit which we can get nearly for free and it will be generally useful.
It has already seen some good discussion, and I think we can go ahead with it for May '27.

Summary

This proposal adds OP_SIGHASH, an opcode that computes the transaction’s signature hash (sighash) and pushes it to the stack without verifying a signature.
It completes the “checksig unbundling”: OP_CHECKSIG is functionally OP_SIGHASH followed by OP_CHECKDATASIG verifying a signature over that digest.

The opcode pops a sighash-type byte from the stack and pushes the resulting 32-byte sighash digest.
It uses the identical sighash algorithm, type flags, and scriptCode conventions as OP_CHECKSIG, so it produces byte-for-byte the same digest that a checksig verify would compare against.

No new cryptography, sighash algorithms, or flag semantics are introduced.
The opcode exposes work the VM already does internally.


3 Likes

Except internally the sighash that is computed is the one used for the txn to sign.

This would have it so that you may have to potentially rehash – depending on the type requested.

I would cost this appropriately – consider it always to take worst-case hashing cost even if the “cached” value ended up being returned.

4 Likes

I would cost this appropriately – consider it always to take worst-case hashing cost even if the “cached” value ended up being returned.

Agree.

From a BCHN implementation point of view, do you think this’ll be difficult @cculianu ? In my head, I imagine it’s probably mostly a matter of spitting OP_CHECKSIG, but I haven’t actually looked at the BCHN code yet. Happy to draft a PR if you’d be willing to take a look once done?

EDIT:

Glancing at the code, I’m thinking it might not necessarily be a matter of “splitting” OP_CHECKSIG as such, but just adding the OP_SIGHASH case and invoking the SignatureHash function here: src/script/interpreter.cpp · master · Bitcoin Cash Node / Bitcoin Cash Node · GitLab ?

OP_CHECKSIG itself wraps the signature validation under checker functions

3 Likes

Thanks for writing this up as a CHIP!! :pray:

Script-Implemented Authentication Schemes

Several projects implement alternative signature schemes directly in Script: Quantumroot (post-quantum vaults), Lamport signatures, and secp256r1 verification. All of them must construct a sighash manually using transaction introspection opcodes. This is a big part of the cost: ~600 bytes of Script just to produce the 32-byte digest.

Sahid Miller, who implemented secp256k1 and secp256r1 verifiers in Script:

In my custom secp256k1 and p256 signature verification scripts, I use ~600 bytes to create a sighash to verify against. OP_SIGHASH would make that unnecessary.

A manual sighash implementation using loops (post-May 2026) can be compressed to ~153 bytes. OP_SIGHASH does the same work in 2 bytes (sighash byte + opcode), using the identical, audited sighash recipes that node implementations already maintain.

Standardized Sighash for Novel Signers

Every Script-based signer that constructs its own sighash must reimplement the sighash algorithm, including edge cases around SIGHASH_SINGLE , ANYONECANPAY , FORKID , UTXOS , and the scriptCode substitution rules. Errors in these reimplementations are consensus-compatible (the script simply fails to verify, or some unintetinoal malleability gap is left) but still represent wasted funds and debugging effort.

OP_SIGHASH gives every Script-based signer the same canonical sighash: the one computed by the node’s own C++ codebase, which has been battle-tested for over a decade. Implementations become simpler, auditable, and audited once.

this to me really is the most important part, these two related points make the lives of the builders and the tinkerers much easier. We want to lower the barrier to entry for people to explore novel cryptography in script. It’s a very nice way to offload unneeded complexity to the VM instead of the the contract implementation

3 Likes

Umm… the CHIP starts off with this statement/assertion:

It completes the “checksig unbundling”: OP_CHECKSIG is functionally OP_SIGHASH followed by OP_CHECKDATASIG verifying a signature over that digest.

No, it doesn’t. This is just wrong. OP_CHECKDATASIG accepts a MESSAGE to check a signature against, not a message hash.

To be clear, OP_CHECKDATASIG pops 3 items off the stack:

  • PUBKEY
  • MESSAGE
  • SIGNATURE

It then proceeds to HASH the MESSAGE → produces a MESSAGE_HASH itself internally.

It then checks SIGNATURE versus the MESSAGE_HASH (which is just: HASH(MESSAGE)).

Therefore the CHIP’s intro paragraph is wrong. (As is the CHIP’s motivation section, which cites the interop with OP_CHECKDATASIG as the reason for the CHIP to exist.)

Moreover, OP_CHECKDATASIG(VERIFY) also only hashes the message ONCE (not twice, as is done for sigchecks input sighash)…

I ask – is this op-code still necessary given that information?

Basically the entire motivation for this CHIP evaporates in light of the above observation… no?

Also – this section in the CHIP (ac-0353f40e / sighash · GitLab) needs to be explained better. As it stands – the claim it is making here is sort of nonsense.

What is the context of the above code’s execution? The redeemScript of the scriptSig? If so – the regular signature certainly commits to that redeemScript because it commits to hashPrevOuts… or it can even commit to the redeemScript more directly using SIGHASH_UTXOs.

If there exists another concrete motivation for this CHIP and for this OP_CODE to exist – it needs to be fleshed out more clearly.

The example cited seems to be almost nonsensical… since any traditional signature for that input would already commit to indirectly anyway (via the redeemScript!)…

But if there is some sort of morsel here with this idea – it needs to be more explicitly fleshed out and made clear.

Are you saying <DATA> comes from the scriptSig pushes? If so – then maybe you have something here. You need to explain that better in the CHIP – since that’s your only potential usecase for this op-code.

The example as stated leads one to believe that <DATA> was embedded in the redeemScript… !

You need to make it clear that you want <DATA> to come from the scriptSig pushes themselves…

Minimally, this CHIP needs rewriting…

1 Like

The strongest part of the motivation, imo, is it’s useful for manipulation of sighash with other data, then followed by signature/commitment from a third party . That was implied in the “Oracles and Covenant Patterns” section, but imo needs to be explicitly and generally described.

The op_checksig unbundling part (which as calin said above, happens to also be erroneous) confuses this point.

That was implied in the “Oracles and Covenant Patterns” section, but imo needs to be explicitly and generally described.

The op_checksig unbundling part (which as calin said above, happens to also be erroneous) confuses this point.

Agreed.

Yeah the oracle stuff and/or signatures committing to data in a scriptSig may be interesting… but I can’t think of a real-world application that would want to do that – but hey it’s at least some usecase one can imagine.

As it stands – he needs to go back and redo the CHIP and kill the “OP_CHECKSIG unbundling” stuff since it’s 100% wrong.

EDIT: Turns out you CAN “unbundle” if you do the following – which ends up being equivalent to OP_CHECKSIG, given a stack that has <sig>, <pubkey> on it…

<0x41> OP_SIGHASH OP_SHA256 OP_SWAP OP_CHECKDATASIG

Also this section in the CHIP is buried deep and contradicts the claim that it’s identical to OP_CHECKSIG.

From: ac-0353f40e / sighash · GitLab

The final digest is performed using a single sha256 instead of the double sha256 (sha256d) as used in CHECKSIG. This is for composability with OP_CHECKDATASIG which internally performes one sha256 on the message before verifying the signature.

First off – THERE IS NO COMPOSABILITY with OP_CHECKDATASIG (see my previous messages). Turns out the single-sha allows composability… if you hash again.

Given that the SIGHASH cannot be composed with OP_CHECKDATASIG – Why does this even matter if it’s a single or double hash? It can, but you need to hash again.

Why do I care? Because internally, in BCHN, the code very easily computes the double-hash – but the single-hash would be more of a codechange to extract out.

Why does the CHIP care if it’s single or double?

This hash is arbitrary – it can be double or single. What matters is that it’s unique to a particular transaction given its inputs and outputs. That’s all that matters.

Having it be single versus the usual double is… unusual, here… given that the ACTUAL sighash used internally for OP_CHECKSIG is a double-sha256d… and this CHIP pretends like we are “merely” extracting “what we have already anyway” – which is not true if it’s a SINGLE hash. We HAVE the DOUBLE hash. Not the SINGLE (in BCHN at least)!

One more issue with the CHIP – You need to specify what happens here in cases of using SIGHASH_SINGLE with non-forkid flags… and the input number exceeds the number of outputs.

Normally a “synthetic” double-sighash of 0x000001 is used… what do we return for your proposed single sighash?

This corner case can only really happen in tests… by the way, where flags are weird. I will just return one here in BCHN…

See BCHN sources: src/script/interpreter.cpp · master · Calin Culianu / Bitcoin Cash Node · GitLab