BUIP-HF Digest for replay protected signature verification across hard forks
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 BIP143[1] as it minimizes redundant data hashing in verification, covers the input value by the signature and is already implemented in a wide variety of applications[2].
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 algorithm[3].
hashPrevouts
- If the
ANYONECANPAY
flag is not set,hashPrevouts
is the double SHA256 of the serialization of all input outpoints; - Otherwise,
hashPrevouts
is auint256
of0x0000......0000
.
hashSequence
- If none of the
ANYONECANPAY
,SINGLE
,NONE
sighash type is set,hashSequence
is the double SHA256 of the serialization ofnSequence
of all inputs; - Otherwise,
hashSequence
is auint256
of0x0000......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
script
does not contain anyOP_CODESEPARATOR
, thescriptCode
is thescript
serialized as scripts insideCTxOut
. - If the
script
contains anyOP_CODESEPARATOR
, thescriptCode
is thescript
but removing everything up to and including the last executedOP_CODESEPARATOR
before the signature checking opcode being executed, serialized as scripts insideCTxOut
.
Notes:
- Contrary to the original algorithm, this one does not use
FindAndDelete
to remove the signature from the script. - Because of 1, it is not possible to create a valid signature within
redeemScript
orscriptPubkey
as 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
,script
is 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
SINGLE
norNONE
,hashOutputs
is 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
SINGLE
and the input index is smaller than the number of outputs,hashOutputs
is the double SHA256 of the output amount withscriptPubKey
of the same index as the input; - Otherwise,
hashOutputs
is auint256
of0x0000......0000
.
Notes:
- In the original algorithm[3], a
uint256
of0x0000......0001
is committed if the input index for aSINGLE
signature is greater than or equal to the number of outputs. In this BIP a0x0000......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.
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 [4] 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