You've already forked specification
225 lines
8.1 KiB
Markdown
225 lines
8.1 KiB
Markdown
# Replay Protected Sighash
|
|
|
|
layout: specification
|
|
title: BUIP-HF Digest for replay protected signature verification across protocl upgrades
|
|
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 BIP143<sup>[1][1]</sup> 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<sup>[2][2]</sup>.
|
|
|
|
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:
|
|
|
|
````cpp
|
|
// ...
|
|
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](/protocol/forks/bch-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 algorithm<sup>[3][3]</sup>.
|
|
|
|
#### hashPrevouts
|
|
|
|
* If the `ANYONECANPAY` flag is not set, `hashPrevouts` is the double SHA256 of the serialization of all input outpoints;
|
|
* Otherwise, `hashPrevouts` is a `uint256` of `0x0000......0000`.
|
|
|
|
#### hashSequence
|
|
|
|
* If none of the `ANYONECANPAY`, `SINGLE`, `NONE` sighash type is set, `hashSequence` is the double SHA256 of the serialization of `nSequence` of all inputs;
|
|
* Otherwise, `hashSequence` is a `uint256` of `0x0000......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 any `OP_CODESEPARATOR`, the `scriptCode` is the `script` serialized as scripts inside `CTxOut`.
|
|
* If the `script` contains any `OP_CODESEPARATOR`, the `scriptCode` is the `script` but removing everything up to and including the last executed `OP_CODESEPARATOR` before the signature checking opcode being executed, serialized as scripts inside `CTxOut`.
|
|
|
|
Notes:
|
|
|
|
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.
|
|
|
|
#### value
|
|
|
|
The 8-byte value of the amount of Bitcoin this input contains.
|
|
|
|
#### hashOutputs
|
|
|
|
* If the sighash type is neither `SINGLE` nor `NONE`, `hashOutputs` is the double SHA256 of the serialization of all output amounts (8-byte little endian) paired up with their `scriptPubKey` (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 with `scriptPubKey` of the same index as the input;
|
|
* Otherwise, `hashOutputs` is a `uint256` of `0x0000......0000`.
|
|
|
|
Notes:
|
|
|
|
1. In the original algorithm<sup>[3][3]</sup>, 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.
|
|
|
|
````cpp
|
|
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` :
|
|
|
|
````cpp
|
|
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:
|
|
|
|
````cpp
|
|
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:
|
|
|
|
````cpp
|
|
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 UAHF<sup>[4][4]</sup> REQ-6-2 NOTE 4), i.e.
|
|
the GetForkID() function returns zero.
|
|
In that case the code can be simplified to omit the function.
|
|
|
|
|
|
|
|
[1]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
|
\[1]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
|
|
|
[2]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#Motivation
|
|
\[2]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#Motivation
|
|
|
|
[3]: https://en.bitcoin.it/wiki/OP_CHECKSIG
|
|
\[3]: https://en.bitcoin.it/wiki/OP_CHECKSIG
|
|
|
|
[4]: https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/uahf-technical-spec.md
|
|
\[4]: https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/uahf-technical-spec.md
|