Skip to content

Commit 89aaaa4

Browse files
OttoAllmendingerllm-git
andcommitted
feat(altcoins): add Bitcoin Cash replay-protected sighash specification
Add documentation for Bitcoin Cash's replay protection mechanism. This specification details how BCH uses a modified signature hash algorithm with SIGHASH_FORKID to prevent transaction replay across forks. Issue: BTC-2656 Co-authored-by: llm-git <[email protected]>
1 parent 4668580 commit 89aaaa4

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
layout: specification
3+
title: BUIP-HF Digest for replay protected signature verification across hard forks
4+
category: spec
5+
date: 2017-07-16
6+
activation: 1501590000
7+
version: 1.2
8+
---
9+
10+
## Abstract
11+
12+
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.
13+
14+
The proposed digest algorithm is adapted from BIP143[[1]](#bip143) 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]](#bip143Motivation).
15+
16+
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.
17+
18+
## Specification
19+
20+
### Activation
21+
22+
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:
23+
24+
````cpp
25+
// ...
26+
SIGHASH_SINGLE = 3,
27+
SIGHASH_FORKID = 0x40,
28+
SIGHASH_ANYONECANPAY = 0x80,
29+
// ...
30+
````
31+
32+
In presence of the `SIGHASH_FORKID` flag in the signature's sighash type, the proposed algorithm is used.
33+
34+
Signatures using the `SIGHASH_FORKID` digest method must be rejected before [UAHF](https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/uahf-technical-spec.md) is activated.
35+
36+
In order to ensure proper activation, the reference implementation uses the `SCRIPT_ENABLE_SIGHASH_FORKID` flag when executing `EvalScript` .
37+
38+
### Digest algorithm
39+
40+
The proposed digest algorithm computes the double SHA256 of the serialization of:
41+
1. nVersion of the transaction (4-byte little endian)
42+
2. hashPrevouts (32-byte hash)
43+
3. hashSequence (32-byte hash)
44+
4. outpoint (32-byte hash + 4-byte little endian)
45+
5. scriptCode of the input (serialized as scripts inside CTxOuts)
46+
6. value of the output spent by this input (8-byte little endian)
47+
7. nSequence of the input (4-byte little endian)
48+
8. hashOutputs (32-byte hash)
49+
9. nLocktime of the transaction (4-byte little endian)
50+
10. sighash type of the signature (4-byte little endian)
51+
52+
Items 1, 4, 7 and 9 have the same meaning as in the original algorithm[[3]](#OP_CHECKSIG).
53+
54+
#### hashPrevouts
55+
56+
* If the `ANYONECANPAY` flag is not set, `hashPrevouts` is the double SHA256 of the serialization of all input outpoints;
57+
* Otherwise, `hashPrevouts` is a `uint256` of `0x0000......0000`.
58+
59+
#### hashSequence
60+
61+
* If none of the `ANYONECANPAY`, `SINGLE`, `NONE` sighash type is set, `hashSequence` is the double SHA256 of the serialization of `nSequence` of all inputs;
62+
* Otherwise, `hashSequence` is a `uint256` of `0x0000......0000`.
63+
64+
#### scriptCode
65+
66+
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.
67+
68+
* If the `script` does not contain any `OP_CODESEPARATOR`, the `scriptCode` is the `script` serialized as scripts inside `CTxOut`.
69+
* 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`.
70+
71+
Notes:
72+
1. Contrary to the original algorithm, this one does not use `FindAndDelete` to remove the signature from the script.
73+
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` .
74+
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.
75+
76+
#### value
77+
78+
The 8-byte value of the amount of Bitcoin this input contains.
79+
80+
#### hashOutputs
81+
82+
* 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);
83+
* 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;
84+
* Otherwise, `hashOutputs` is a `uint256` of `0x0000......0000`.
85+
86+
Notes:
87+
1. In the original algorithm[[3]](#OP_CHECKSIG), 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.
88+
89+
#### sighash type
90+
91+
The sighash type is altered to include a 24-bit *fork id* in its most significant bits.
92+
93+
````cpp
94+
ss << ((GetForkID() << 8) | nHashType);
95+
````
96+
97+
This ensure that the proposed digest algorithm will generate different results on forks using different *fork ids*.
98+
99+
## Implementation
100+
101+
Addition to `SignatureHash` :
102+
103+
````cpp
104+
if (nHashType & SIGHASH_FORKID) {
105+
uint256 hashPrevouts;
106+
uint256 hashSequence;
107+
uint256 hashOutputs;
108+
109+
if (!(nHashType & SIGHASH_ANYONECANPAY)) {
110+
hashPrevouts = GetPrevoutHash(txTo);
111+
}
112+
113+
if (!(nHashType & SIGHASH_ANYONECANPAY) &&
114+
(nHashType & 0x1f) != SIGHASH_SINGLE &&
115+
(nHashType & 0x1f) != SIGHASH_NONE) {
116+
hashSequence = GetSequenceHash(txTo);
117+
}
118+
119+
if ((nHashType & 0x1f) != SIGHASH_SINGLE &&
120+
(nHashType & 0x1f) != SIGHASH_NONE) {
121+
hashOutputs = GetOutputsHash(txTo);
122+
} else if ((nHashType & 0x1f) == SIGHASH_SINGLE &&
123+
nIn < txTo.vout.size()) {
124+
CHashWriter ss(SER_GETHASH, 0);
125+
ss << txTo.vout[nIn];
126+
hashOutputs = ss.GetHash();
127+
}
128+
129+
CHashWriter ss(SER_GETHASH, 0);
130+
// Version
131+
ss << txTo.nVersion;
132+
// Input prevouts/nSequence (none/all, depending on flags)
133+
ss << hashPrevouts;
134+
ss << hashSequence;
135+
// The input being signed (replacing the scriptSig with scriptCode +
136+
// amount). The prevout may already be contained in hashPrevout, and the
137+
// nSequence may already be contain in hashSequence.
138+
ss << txTo.vin[nIn].prevout;
139+
ss << static_cast<const CScriptBase &>(scriptCode);
140+
ss << amount;
141+
ss << txTo.vin[nIn].nSequence;
142+
// Outputs (none/one/all, depending on flags)
143+
ss << hashOutputs;
144+
// Locktime
145+
ss << txTo.nLockTime;
146+
// Sighash type
147+
ss << ((GetForkId() << 8) | nHashType);
148+
return ss.GetHash();
149+
}
150+
````
151+
152+
Computation of midstates:
153+
154+
````cpp
155+
uint256 GetPrevoutHash(const CTransaction &txTo) {
156+
CHashWriter ss(SER_GETHASH, 0);
157+
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
158+
ss << txTo.vin[n].prevout;
159+
}
160+
161+
return ss.GetHash();
162+
}
163+
164+
uint256 GetSequenceHash(const CTransaction &txTo) {
165+
CHashWriter ss(SER_GETHASH, 0);
166+
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
167+
ss << txTo.vin[n].nSequence;
168+
}
169+
170+
return ss.GetHash();
171+
}
172+
173+
uint256 GetOutputsHash(const CTransaction &txTo) {
174+
CHashWriter ss(SER_GETHASH, 0);
175+
for (unsigned int n = 0; n < txTo.vout.size(); n++) {
176+
ss << txTo.vout[n];
177+
}
178+
179+
return ss.GetHash();
180+
}
181+
````
182+
183+
Gating code:
184+
185+
````cpp
186+
uint32_t nHashType = GetHashType(vchSig);
187+
if (nHashType & SIGHASH_FORKID) {
188+
if (!(flags & SCRIPT_ENABLE_SIGHASH_FORKID))
189+
return set_error(serror, SCRIPT_ERR_ILLEGAL_FORKID);
190+
} else {
191+
// Drop the signature in scripts when SIGHASH_FORKID is not used.
192+
scriptCode.FindAndDelete(CScript(vchSig));
193+
}
194+
````
195+
196+
## Note
197+
198+
In the UAHF, a `fork id` of 0 is used (see [[4]](#uahfspec) REQ-6-2 NOTE 4), i.e.
199+
the GetForkID() function returns zero.
200+
In that case the code can be simplified to omit the function.
201+
202+
## References
203+
204+
<a name="bip143">[1]</a> https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
205+
206+
<a name="bip143Motivation">[2]</a> https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#Motivation
207+
208+
<a name="OP_CHECKSIG">[3]</a> https://en.bitcoin.it/wiki/OP_CHECKSIG
209+
210+
<a name="uahfspec">[4]</a> https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/uahf-technical-spec.md

0 commit comments

Comments
 (0)