Replay Protected Sighash

layout: specification
title: BUIP-HF Digest for replay protected signature verification across hard forks
category: spec
date: 2017-07-16
activation: 1501590000
version: 1.2


This document describes proposed requirements and design for a reusable signing mechanism ensuring replay protection in the event of a chain split. It provides a way for users to create transactions which are invalid on forks lacking support for the mechanism and a fork-specific ID.

The proposed digest algorithm is adapted from BIP1431 as it minimizes redundant data hashing in verification, covers the input value by the signature and is already implemented in a wide variety of applications2.

The proposed digest algorithm is used when the SIGHASH_FORKID bit is set in the signature's sighash type. The verification of signatures which do not set this bit is not affected.



The proposed digest algorithm is only used when the SIGHASH_FORKID bit in the signature sighash's type is set. It is defined as follows:

  // ...
  // ...

In presence of the SIGHASH_FORKID flag in the signature's sighash type, the proposed algorithm is used.

Signatures using the SIGHASH_FORKID digest method must be rejected before UAHF is activated.

In order to ensure proper activation, the reference implementation uses the SCRIPT_ENABLE_SIGHASH_FORKID flag when executing EvalScript .

Digest algorithm

The proposed digest algorithm computes the double SHA256 of the serialization of:

  1. nVersion of the transaction (4-byte little endian)
  2. hashPrevouts (32-byte hash)
  3. hashSequence (32-byte hash)
  4. outpoint (32-byte hash + 4-byte little endian)
  5. scriptCode of the input (serialized as scripts inside CTxOuts)
  6. value of the output spent by this input (8-byte little endian)
  7. nSequence of the input (4-byte little endian)
  8. hashOutputs (32-byte hash)
  9. nLocktime of the transaction (4-byte little endian)
  10. sighash type of the signature (4-byte little endian)

Items 1, 4, 7 and 9 have the same meaning as in the original algorithm3.




In this section, we call script the script being currently executed. This means redeemScript in case of P2SH, or the scriptPubKey in the general case.


  1. Contrary to the original algorithm, this one does not use FindAndDelete to remove the signature from the script.
  2. Because of 1, it is not possible to create a valid signature within redeemScript or scriptPubkey as the signature would be part of the digest. This enforces that the signature is in sigScript .
  3. In case an opcode that requires signature checking is present in sigScript, script is effectively sigScript. However, for reason similar to 2, it is not possible to provide a valid signature in that case.


The 8-byte value of the amount of Bitcoin this input contains.



  1. In the original algorithm3, a uint256 of 0x0000......0001 is committed if the input index for a SINGLE signature is greater than or equal to the number of outputs. In this BIP a 0x0000......0000 is committed, without changing the semantics.

sighash type

The sighash type is altered to include a 24-bit fork id in its most significant bits.

  ss << ((GetForkID() << 8) | nHashType);

This ensure that the proposed digest algorithm will generate different results on forks using different fork ids.


Addition to SignatureHash :

  if (nHashType & SIGHASH_FORKID) {
    uint256 hashPrevouts;
    uint256 hashSequence;
    uint256 hashOutputs;

    if (!(nHashType & SIGHASH_ANYONECANPAY)) {
      hashPrevouts = GetPrevoutHash(txTo);

    if (!(nHashType & SIGHASH_ANYONECANPAY) &&
        (nHashType & 0x1f) != SIGHASH_SINGLE &&
        (nHashType & 0x1f) != SIGHASH_NONE) {
      hashSequence = GetSequenceHash(txTo);

    if ((nHashType & 0x1f) != SIGHASH_SINGLE &&
        (nHashType & 0x1f) != SIGHASH_NONE) {
      hashOutputs = GetOutputsHash(txTo);
    } else if ((nHashType & 0x1f) == SIGHASH_SINGLE &&
               nIn < txTo.vout.size()) {
      CHashWriter ss(SER_GETHASH, 0);
      ss << txTo.vout[nIn];
      hashOutputs = ss.GetHash();

    CHashWriter ss(SER_GETHASH, 0);
    // Version
    ss << txTo.nVersion;
    // Input prevouts/nSequence (none/all, depending on flags)
    ss << hashPrevouts;
    ss << hashSequence;
    // The input being signed (replacing the scriptSig with scriptCode +
    // amount). The prevout may already be contained in hashPrevout, and the
    // nSequence may already be contain in hashSequence.
    ss <<[nIn].prevout;
    ss << static_cast<const CScriptBase &>(scriptCode);
    ss << amount;
    ss <<[nIn].nSequence;
    // Outputs (none/one/all, depending on flags)
    ss << hashOutputs;
    // Locktime
    ss << txTo.nLockTime;
    // Sighash type
    ss << ((GetForkId() << 8) | nHashType);
    return ss.GetHash();

Computation of midstates:

uint256 GetPrevoutHash(const CTransaction &txTo) {
  CHashWriter ss(SER_GETHASH, 0);
  for (unsigned int n = 0; n <; n++) {
    ss <<[n].prevout;

  return ss.GetHash();

uint256 GetSequenceHash(const CTransaction &txTo) {
  CHashWriter ss(SER_GETHASH, 0);
  for (unsigned int n = 0; n <; n++) {
    ss <<[n].nSequence;

  return ss.GetHash();

uint256 GetOutputsHash(const CTransaction &txTo) {
  CHashWriter ss(SER_GETHASH, 0);
  for (unsigned int n = 0; n < txTo.vout.size(); n++) {
    ss << txTo.vout[n];

  return ss.GetHash();

Gating code:

  uint32_t nHashType = GetHashType(vchSig);
  if (nHashType & SIGHASH_FORKID) {
      return set_error(serror, SCRIPT_ERR_ILLEGAL_FORKID);
  } else {
    // Drop the signature in scripts when SIGHASH_FORKID is not used.


In the UAHF, a fork id of 0 is used (see UAHF4 REQ-6-2 NOTE 4), i.e. the GetForkID() function returns zero. In that case the code can be simplified to omit the function.