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
Abstract
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.
Specification
Activation
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:
// ...
SIGHASH_SINGLE = 3,
SIGHASH_FORKID = 0x40,
SIGHASH_ANYONECANPAY = 0x80,
// ...
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:
- nVersion of the transaction (4-byte little endian)
- hashPrevouts (32-byte hash)
- hashSequence (32-byte hash)
- outpoint (32-byte hash + 4-byte little endian)
- scriptCode of the input (serialized as scripts inside CTxOuts)
- value of the output spent by this input (8-byte little endian)
- nSequence of the input (4-byte little endian)
- hashOutputs (32-byte hash)
- nLocktime of the transaction (4-byte little endian)
- sighash type of the signature (4-byte little endian)
Items 1, 4, 7 and 9 have the same meaning as in the original algorithm3.
hashPrevouts
- If the
ANYONECANPAYflag is not set,hashPrevoutsis the double SHA256 of the serialization of all input outpoints; - Otherwise,
hashPrevoutsis auint256of0x0000......0000.
hashSequence
- If none of the
ANYONECANPAY,SINGLE,NONEsighash type is set,hashSequenceis the double SHA256 of the serialization ofnSequenceof all inputs; - Otherwise,
hashSequenceis auint256of0x0000......0000.
scriptCode
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.
- If the
scriptdoes not contain anyOP_CODESEPARATOR, thescriptCodeis thescriptserialized as scripts insideCTxOut. - If the
scriptcontains anyOP_CODESEPARATOR, thescriptCodeis thescriptbut removing everything up to and including the last executedOP_CODESEPARATORbefore the signature checking opcode being executed, serialized as scripts insideCTxOut.
Notes:
- Contrary to the original algorithm, this one does not use
FindAndDeleteto remove the signature from the script. - Because of 1, it is not possible to create a valid signature within
redeemScriptorscriptPubkeyas the signature would be part of the digest. This enforces that the signature is insigScript. - In case an opcode that requires signature checking is present in
sigScript,scriptis effectivelysigScript. However, for reason similar to 2, it is not possible to provide a valid signature in that case.
value
The 8-byte value of the amount of Bitcoin this input contains.
hashOutputs
- If the sighash type is neither
SINGLEnorNONE,hashOutputsis the double SHA256 of the serialization of all output amounts (8-byte little endian) paired up with theirscriptPubKey(serialized as scripts inside CTxOuts); - If sighash type is
SINGLEand the input index is smaller than the number of outputs,hashOutputsis the double SHA256 of the output amount withscriptPubKeyof the same index as the input; - Otherwise,
hashOutputsis auint256of0x0000......0000.
Notes:
- In the original algorithm3, a
uint256of0x0000......0001is committed if the input index for aSINGLEsignature is greater than or equal to the number of outputs. In this BIP a0x0000......0000is 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.
Implementation
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 << txTo.vin[nIn].prevout;
ss << static_cast<const CScriptBase &>(scriptCode);
ss << amount;
ss << txTo.vin[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 < txTo.vin.size(); n++) {
ss << txTo.vin[n].prevout;
}
return ss.GetHash();
}
uint256 GetSequenceHash(const CTransaction &txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
ss << txTo.vin[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) {
if (!(flags & SCRIPT_ENABLE_SIGHASH_FORKID))
return set_error(serror, SCRIPT_ERR_ILLEGAL_FORKID);
} else {
// Drop the signature in scripts when SIGHASH_FORKID is not used.
scriptCode.FindAndDelete(CScript(vchSig));
}
Note
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.
References
[1]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
[2]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#Motivation
[3]: https://en.bitcoin.it/wiki/OP_CHECKSIG
[4]: https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/uahf-technical-spec.md