]>
Commit | Line | Data |
---|---|---|
8a727a26 | 1 | /******************************************************************** |
2 | * (C) 2018 Michael Toutonghi | |
3 | * | |
4 | * Distributed under the MIT software license, see the accompanying | |
5 | * file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
6 | * | |
7 | * This crypto-condition eval solves the problem of nothing-at-stake | |
8 | * in a proof of stake consensus system. | |
9 | * | |
10 | */ | |
11 | ||
ca4a5f26 | 12 | #include "StakeGuard.h" |
06f41160 | 13 | #include "script/script.h" |
8a727a26 | 14 | #include "main.h" |
905fe35e | 15 | #include "hash.h" |
b2a98c42 | 16 | #include "key_io.h" |
905fe35e | 17 | |
a4f9bc97 | 18 | #include <vector> |
19 | #include <map> | |
20 | ||
905fe35e | 21 | #include "streams.h" |
8a727a26 | 22 | |
23 | extern int32_t VERUS_MIN_STAKEAGE; | |
24 | ||
3bfa5e22 | 25 | bool IsData(opcodetype opcode) |
26 | { | |
27 | return (opcode >= 0 && opcode <= OP_PUSHDATA4) || (opcode >= OP_1 && opcode <= OP_16); | |
28 | } | |
29 | ||
06f41160 | 30 | bool UnpackStakeOpRet(const CTransaction &stakeTx, std::vector<std::vector<unsigned char>> &vData) |
8a727a26 | 31 | { |
32 | bool isValid = stakeTx.vout[stakeTx.vout.size() - 1].scriptPubKey.GetOpretData(vData); | |
33 | ||
3bfa5e22 | 34 | if (isValid && vData.size() == 1) |
8a727a26 | 35 | { |
06f41160 | 36 | CScript data = CScript(vData[0].begin(), vData[0].end()); |
37 | vData.clear(); | |
38 | ||
39 | uint32_t bytesTotal; | |
40 | CScript::const_iterator pc = data.begin(); | |
41 | std::vector<unsigned char> vch = std::vector<unsigned char>(); | |
42 | opcodetype op; | |
43 | bool moreData = true; | |
44 | ||
45 | for (bytesTotal = vch.size(); | |
3bfa5e22 | 46 | bytesTotal <= nMaxDatacarrierBytes && !(isValid = (pc == data.end())) && (moreData = data.GetOp(pc, op, vch)) && IsData(op); |
06f41160 | 47 | bytesTotal += vch.size()) |
48 | { | |
3bfa5e22 | 49 | if (op >= OP_1 && op <= OP_16) |
50 | { | |
51 | vch.resize(1); | |
52 | vch[0] = (op - OP_1) + 1; | |
53 | } | |
06f41160 | 54 | vData.push_back(vch); |
55 | } | |
56 | ||
57 | // if we ran out of data, we're ok | |
06f41160 | 58 | if (isValid && (vData.size() >= CStakeParams::STAKE_MINPARAMS) && (vData.size() <= CStakeParams::STAKE_MAXPARAMS)) |
59 | { | |
60 | return true; | |
61 | } | |
8a727a26 | 62 | } |
63 | return false; | |
64 | } | |
65 | ||
3bfa5e22 | 66 | CStakeParams::CStakeParams(const std::vector<std::vector<unsigned char>> &vData) |
8a727a26 | 67 | { |
56fe75cb | 68 | // An original format stake OP_RETURN contains: |
8a727a26 | 69 | // 1. source block height in little endian 32 bit |
70 | // 2. target block height in little endian 32 bit | |
71 | // 3. 32 byte prev block hash | |
3bfa5e22 | 72 | // 4. 33 byte pubkey, or not present to use same as stake destination |
56fe75cb | 73 | // New format serialization and deserialization is handled by normal stream serialization. |
74 | version = VERSION_INVALID; | |
8a727a26 | 75 | srcHeight = 0; |
76 | blkHeight = 0; | |
77 | if (vData[0].size() == 1 && | |
56fe75cb | 78 | vData[0][0] == OPRETTYPE_STAKEPARAMS2 && |
79 | vData.size() == 2) | |
80 | { | |
81 | ::FromVector(vData[1], *this); | |
82 | } | |
83 | else if (vData[0].size() == 1 && | |
8a727a26 | 84 | vData[0][0] == OPRETTYPE_STAKEPARAMS && vData[1].size() <= 4 && |
85 | vData[2].size() <= 4 && | |
86 | vData[3].size() == sizeof(prevHash) && | |
06f41160 | 87 | (vData.size() == STAKE_MINPARAMS || (vData.size() == STAKE_MAXPARAMS && vData[4].size() == 33))) |
8a727a26 | 88 | { |
56fe75cb | 89 | version = VERSION_ORIGINAL; |
3bfa5e22 | 90 | for (int i = 0, size = vData[1].size(); i < size; i++) |
8a727a26 | 91 | { |
3bfa5e22 | 92 | srcHeight = srcHeight | vData[1][i] << (8 * i); |
8a727a26 | 93 | } |
3bfa5e22 | 94 | for (int i = 0, size = vData[2].size(); i < size; i++) |
8a727a26 | 95 | { |
3bfa5e22 | 96 | blkHeight = blkHeight | vData[2][i] << (8 * i); |
8a727a26 | 97 | } |
98 | ||
99 | prevHash = uint256(vData[3]); | |
100 | ||
101 | if (vData.size() == 4) | |
102 | { | |
06f41160 | 103 | pk = CPubKey(); |
8a727a26 | 104 | } |
105 | else if (vData[4].size() == 33) | |
106 | { | |
06f41160 | 107 | pk = CPubKey(vData[4]); |
108 | if (!pk.IsValid()) | |
8a727a26 | 109 | { |
110 | // invalidate | |
111 | srcHeight = 0; | |
56fe75cb | 112 | version = VERSION_INVALID; |
8a727a26 | 113 | } |
114 | } | |
115 | else | |
116 | { | |
117 | // invalidate | |
118 | srcHeight = 0; | |
56fe75cb | 119 | version = VERSION_INVALID; |
8a727a26 | 120 | } |
121 | } | |
122 | } | |
123 | ||
905fe35e | 124 | bool GetStakeParams(const CTransaction &stakeTx, CStakeParams &stakeParams) |
125 | { | |
126 | std::vector<std::vector<unsigned char>> vData = std::vector<std::vector<unsigned char>>(); | |
127 | ||
3bfa5e22 | 128 | //printf("opret stake script: %s\nvalue at scriptPubKey[0]: %x\n", stakeTx.vout[1].scriptPubKey.ToString().c_str(), stakeTx.vout[1].scriptPubKey[0]); |
129 | ||
905fe35e | 130 | if (stakeTx.vin.size() == 1 && |
131 | stakeTx.vout.size() == 2 && | |
132 | stakeTx.vout[0].nValue > 0 && | |
133 | stakeTx.vout[1].scriptPubKey.IsOpReturn() && | |
134 | UnpackStakeOpRet(stakeTx, vData)) | |
135 | { | |
136 | stakeParams = CStakeParams(vData); | |
3bfa5e22 | 137 | return stakeParams.IsValid(); |
905fe35e | 138 | } |
139 | return false; | |
140 | } | |
141 | ||
f3b0d2ab | 142 | // this validates the format of the stake transaction and, optionally, whether or not it is |
143 | // properly signed to spend the source stake. | |
144 | // it does not validate the relationship to a coinbase guard, PoS eligibility or the actual stake spend. | |
56fe75cb | 145 | // the only time it matters is to validate a properly formed stake transaction for either pre-check before PoS validity check, |
146 | // or to validate the stake transaction on a fork that will be used to spend a winning stake that cheated by being posted | |
8a727a26 | 147 | // on two fork chains |
f3b0d2ab | 148 | bool ValidateStakeTransaction(const CTransaction &stakeTx, CStakeParams &stakeParams, bool validateSig) |
8a727a26 | 149 | { |
150 | std::vector<std::vector<unsigned char>> vData = std::vector<std::vector<unsigned char>>(); | |
151 | ||
152 | // a valid stake transaction has one input and two outputs, one output is the monetary value and one is an op_ret with CStakeParams | |
153 | // stake output #1 must be P2PK or P2PKH, unless a delegate for the coinbase is specified | |
f3ec769e | 154 | if (GetStakeParams(stakeTx, stakeParams)) |
8a727a26 | 155 | { |
f3ec769e | 156 | // if we have gotten this far and are still valid, we need to validate everything else |
157 | // even if the utxo is spent, this can succeed, as it only checks that is was ever valid | |
158 | CTransaction srcTx = CTransaction(); | |
159 | uint256 blkHash = uint256(); | |
160 | txnouttype txType; | |
161 | CBlockIndex *pindex; | |
162 | if (myGetTransaction(stakeTx.vin[0].prevout.hash, srcTx, blkHash)) | |
8a727a26 | 163 | { |
d565e7b7 | 164 | BlockMap::const_iterator it = mapBlockIndex.find(blkHash); |
56fe75cb | 165 | if (it != mapBlockIndex.end() && (pindex = it->second) != NULL && chainActive.Contains(pindex)) |
8a727a26 | 166 | { |
f3ec769e | 167 | std::vector<std::vector<unsigned char>> vAddr = std::vector<std::vector<unsigned char>>(); |
5c7c7edc | 168 | bool extendedStake = CConstVerusSolutionVector::GetVersionByHeight(stakeParams.blkHeight) >= CActivationHeight::ACTIVATE_EXTENDEDSTAKE; |
169 | COptCCParams p; | |
8a727a26 | 170 | |
f3ec769e | 171 | if (stakeParams.srcHeight == pindex->GetHeight() && |
172 | (stakeParams.blkHeight - stakeParams.srcHeight >= VERUS_MIN_STAKEAGE) && | |
5c7c7edc | 173 | ((srcTx.vout[stakeTx.vin[0].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) && |
174 | extendedStake && | |
175 | p.IsValid() && | |
176 | srcTx.vout[stakeTx.vin[0].prevout.n].scriptPubKey.IsSpendableOutputType(p)) || | |
177 | (!p.IsValid() && Solver(srcTx.vout[stakeTx.vin[0].prevout.n].scriptPubKey, txType, vAddr)))) | |
f3ec769e | 178 | { |
5c7c7edc | 179 | if (!p.IsValid() && txType == TX_PUBKEY && !stakeParams.pk.IsValid()) |
8a727a26 | 180 | { |
f3ec769e | 181 | stakeParams.pk = CPubKey(vAddr[0]); |
182 | } | |
56fe75cb | 183 | // once extended stake hits, we only accept extended form of staking |
184 | if (!(extendedStake && stakeParams.Version() < stakeParams.VERSION_EXTENDED_STAKE) && | |
185 | !(!extendedStake && stakeParams.Version() >= stakeParams.VERSION_EXTENDED_STAKE) && | |
5c7c7edc | 186 | ((extendedStake && p.IsValid()) || (txType == TX_PUBKEY) || (txType == TX_PUBKEYHASH && (extendedStake || stakeParams.pk.IsFullyValid())))) |
f3ec769e | 187 | { |
188 | auto consensusBranchId = CurrentEpochBranchId(stakeParams.blkHeight, Params().GetConsensus()); | |
f3b0d2ab | 189 | |
a4f9bc97 | 190 | std::map<uint160, pair<int, std::vector<std::vector<unsigned char>>>> idAddressMap; |
191 | if (validateSig) | |
192 | { | |
4ebb5ccf | 193 | idAddressMap = ServerTransactionSignatureChecker::ExtractIDMap(srcTx.vout[stakeTx.vin[0].prevout.n].scriptPubKey, stakeParams.blkHeight, true); |
a4f9bc97 | 194 | } |
195 | ||
f3b0d2ab | 196 | if (!validateSig || VerifyScript(stakeTx.vin[0].scriptSig, |
197 | srcTx.vout[stakeTx.vin[0].prevout.n].scriptPubKey, | |
477fd227 | 198 | MANDATORY_SCRIPT_VERIFY_FLAGS, |
a4f9bc97 | 199 | TransactionSignatureChecker(&stakeTx, (uint32_t)0, srcTx.vout[stakeTx.vin[0].prevout.n].nValue, &idAddressMap), |
f3b0d2ab | 200 | consensusBranchId)) |
8a727a26 | 201 | { |
f3ec769e | 202 | return true; |
8a727a26 | 203 | } |
204 | } | |
205 | } | |
206 | } | |
8a727a26 | 207 | } |
208 | } | |
f3ec769e | 209 | return false; |
8a727a26 | 210 | } |
211 | ||
c52bd43c | 212 | bool MakeGuardedOutput(CAmount value, CTxDestination &dest, CTransaction &stakeTx, CTxOut &vout) |
8a727a26 | 213 | { |
56fe75cb | 214 | CStakeParams p; |
215 | if (GetStakeParams(stakeTx, p) && p.IsValid()) | |
216 | { | |
217 | CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); | |
8a727a26 | 218 | |
56fe75cb | 219 | hw << stakeTx.vin[0].prevout.hash; |
220 | hw << stakeTx.vin[0].prevout.n; | |
8a727a26 | 221 | |
56fe75cb | 222 | uint256 utxo = hw.GetHash(); |
8a727a26 | 223 | |
56fe75cb | 224 | if (p.Version() >= p.VERSION_EXTENDED_STAKE) |
225 | { | |
226 | CCcontract_info *cp, C; | |
227 | cp = CCinit(&C,EVAL_STAKEGUARD); | |
228 | ||
229 | CStakeInfo stakeInfo(p.blkHeight, p.srcHeight, utxo, p.prevHash); | |
fc1ca997 | 230 | |
231 | std::vector<CTxDestination> dests1({dest}); | |
232 | CConditionObj<CStakeInfo> primary(EVAL_STAKEGUARD, dests1, 1, &stakeInfo); | |
233 | std::vector<CTxDestination> dests2({CTxDestination(CPubKey(ParseHex(cp->CChexstr)))}); | |
234 | CConditionObj<CStakeInfo> cheatCatcher(EVAL_STAKEGUARD, dests2, 1); | |
fc1ca997 | 235 | vout = CTxOut(value, MakeMofNCCScript(1, primary, cheatCatcher)); |
56fe75cb | 236 | } |
c52bd43c | 237 | else if (dest.which() == COptCCParams::ADDRTYPE_PK) |
56fe75cb | 238 | { |
239 | CCcontract_info *cp, C; | |
240 | cp = CCinit(&C,EVAL_STAKEGUARD); | |
905fe35e | 241 | |
56fe75cb | 242 | CPubKey ccAddress = CPubKey(ParseHex(cp->CChexstr)); |
905fe35e | 243 | |
56fe75cb | 244 | // return an output that is bound to the stake transaction and can be spent by presenting either a signed condition by the original |
245 | // destination address or a properly signed stake transaction of the same utxo on a fork | |
c52bd43c | 246 | vout = MakeCC1of2vout(EVAL_STAKEGUARD, value, boost::apply_visitor<GetPubKeyForPubKey>(GetPubKeyForPubKey(), dest), ccAddress); |
905fe35e | 247 | |
56fe75cb | 248 | std::vector<CTxDestination> vKeys; |
249 | vKeys.push_back(dest); | |
250 | vKeys.push_back(ccAddress); | |
251 | ||
252 | std::vector<std::vector<unsigned char>> vData = std::vector<std::vector<unsigned char>>(); | |
905fe35e | 253 | |
56fe75cb | 254 | vData.push_back(std::vector<unsigned char>(utxo.begin(), utxo.end())); |
255 | ||
256 | // prev block hash and height is here to make validation easy | |
257 | vData.push_back(std::vector<unsigned char>(p.prevHash.begin(), p.prevHash.end())); | |
258 | std::vector<unsigned char> height = std::vector<unsigned char>(4); | |
259 | for (int i = 0; i < 4; i++) | |
260 | { | |
261 | height[i] = (p.blkHeight >> (8 * i)) & 0xff; | |
262 | } | |
263 | vData.push_back(height); | |
905fe35e | 264 | |
56fe75cb | 265 | COptCCParams ccp = COptCCParams(COptCCParams::VERSION_V1, EVAL_STAKEGUARD, 1, 2, vKeys, vData); |
905fe35e | 266 | |
56fe75cb | 267 | vout.scriptPubKey << ccp.AsVector() << OP_DROP; |
268 | } | |
269 | ||
905fe35e | 270 | return true; |
271 | } | |
272 | return false; | |
273 | } | |
274 | ||
275 | // validates if a stake transaction is both valid and cheating, defined by: | |
23f0b6ec | 276 | // the same exact utxo source, a target block height of later than that of the provided coinbase tx that is also targeting a fork |
277 | // of the chain. the source transaction must be a coinbase | |
f3b0d2ab | 278 | bool ValidateMatchingStake(const CTransaction &ccTx, uint32_t voutNum, const CTransaction &stakeTx, bool &cheating) |
905fe35e | 279 | { |
f3b0d2ab | 280 | // an invalid or non-matching stake transaction cannot cheat |
281 | cheating = false; | |
282 | ||
79b0432d | 283 | //printf("ValidateMatchingStake: ccTx.vin[0].prevout.hash: %s, ccTx.vin[0].prevout.n: %d\n", ccTx.vin[0].prevout.hash.GetHex().c_str(), ccTx.vin[0].prevout.n); |
284 | ||
905fe35e | 285 | if (ccTx.IsCoinBase()) |
286 | { | |
287 | CStakeParams p; | |
f3b0d2ab | 288 | if (ValidateStakeTransaction(stakeTx, p)) |
905fe35e | 289 | { |
290 | std::vector<std::vector<unsigned char>> vParams = std::vector<std::vector<unsigned char>>(); | |
291 | CScript dummy; | |
292 | ||
293 | if (ccTx.vout[voutNum].scriptPubKey.IsPayToCryptoCondition(&dummy, vParams) && vParams.size() > 0) | |
294 | { | |
295 | COptCCParams ccp = COptCCParams(vParams[0]); | |
56fe75cb | 296 | CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); |
297 | ||
298 | if (p.version >= p.VERSION_EXTENDED_STAKE && ccp.version >= ccp.VERSION_V3 && ccp.vData.size()) | |
905fe35e | 299 | { |
56fe75cb | 300 | CStakeInfo stakeInfo(ccp.vData[0]); |
301 | hw << stakeTx.vin[0].prevout.hash; | |
302 | hw << stakeTx.vin[0].prevout.n; | |
303 | uint256 utxo = hw.GetHash(); | |
905fe35e | 304 | |
56fe75cb | 305 | if (utxo == stakeInfo.utxo) |
306 | { | |
307 | if (p.prevHash != stakeInfo.prevHash && p.blkHeight >= stakeInfo.height) | |
308 | { | |
309 | cheating = true; | |
310 | return true; | |
311 | } | |
312 | // if block height is equal and we are at the else, prevHash must have been equal | |
313 | else if (p.blkHeight >= stakeInfo.height) | |
314 | { | |
315 | return true; | |
316 | } | |
317 | } | |
318 | } | |
6a23bf78 | 319 | else if (p.version < p.VERSION_EXTENDED_STAKE && |
320 | ccp.version < ccp.VERSION_V3 && | |
321 | ccp.IsValid() && | |
322 | ccp.vData.size() >= 3 && | |
56fe75cb | 323 | ccp.vData[2].size() <= 4) |
324 | { | |
f3b0d2ab | 325 | hw << stakeTx.vin[0].prevout.hash; |
326 | hw << stakeTx.vin[0].prevout.n; | |
905fe35e | 327 | uint256 utxo = hw.GetHash(); |
905fe35e | 328 | |
329 | uint32_t height = 0; | |
185b2d4f | 330 | int i, dataLen = ccp.vData[2].size(); |
331 | for (i = dataLen - 1; i >= 0; i--) | |
905fe35e | 332 | { |
185b2d4f | 333 | height = (height << 8) + ccp.vData[2][i]; |
905fe35e | 334 | } |
95c5c69b | 335 | // for debugging strange issue |
336 | // printf("iterator: %d, height: %d, datalen: %d\n", i, height, dataLen); | |
8a727a26 | 337 | |
f3b0d2ab | 338 | if (utxo == uint256(ccp.vData[0])) |
905fe35e | 339 | { |
f3b0d2ab | 340 | if (p.prevHash != uint256(ccp.vData[1]) && p.blkHeight >= height) |
341 | { | |
342 | cheating = true; | |
343 | return true; | |
344 | } | |
345 | // if block height is equal and we are at the else, prevHash must have been equal | |
346 | else if (p.blkHeight == height) | |
347 | { | |
348 | return true; | |
349 | } | |
905fe35e | 350 | } |
351 | } | |
352 | } | |
353 | } | |
354 | } | |
8a727a26 | 355 | return false; |
356 | } | |
357 | ||
905fe35e | 358 | // this attaches an opret to a mutable transaction that provides the necessary evidence of a signed, cheating stake transaction |
359 | bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx) | |
8a727a26 | 360 | { |
905fe35e | 361 | std::vector<unsigned char> vch; |
b2a98c42 | 362 | CDataStream s = CDataStream(SER_DISK, PROTOCOL_VERSION); |
df756d24 | 363 | bool isCheater = false; |
8a727a26 | 364 | |
f3b0d2ab | 365 | if (ValidateMatchingStake(ccTx, voutNum, cheatTx, isCheater) && isCheater) |
905fe35e | 366 | { |
367 | CTxOut vOut = CTxOut(); | |
26bf01e9 | 368 | int64_t opretype_stakecheat = OPRETTYPE_STAKECHEAT; |
905fe35e | 369 | |
370 | CScript vData = CScript(); | |
371 | cheatTx.Serialize(s); | |
372 | vch = std::vector<unsigned char>(s.begin(), s.end()); | |
26bf01e9 | 373 | vData << opretype_stakecheat << vch; |
374 | vch = std::vector<unsigned char>(vData.begin(), vData.end()); | |
375 | vOut.scriptPubKey << OP_RETURN << vch; | |
376 | ||
51848bbc | 377 | // printf("Script encoding inner:\n%s\nouter:\n%s\n", vData.ToString().c_str(), vOut.scriptPubKey.ToString().c_str()); |
26bf01e9 | 378 | |
905fe35e | 379 | vOut.nValue = 0; |
380 | mtx.vout.push_back(vOut); | |
381 | } | |
df756d24 | 382 | return isCheater; |
8a727a26 | 383 | } |
384 | ||
fc1ca997 | 385 | // a version 3 guard output should be a 1 of 2 meta condition, with both of the |
386 | // conditions being stakeguard only and one of the conditions being sent to the public | |
387 | // stakeguard destination. Only for smart transaction V3 and beyond. | |
388 | bool PrecheckStakeGuardOutput(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height) | |
389 | { | |
390 | if (CConstVerusSolutionVector::GetVersionByHeight(height) < CActivationHeight::ACTIVATE_EXTENDEDSTAKE) | |
391 | { | |
392 | return true; | |
393 | } | |
394 | ||
395 | // ensure that we have all required spend conditions for primary, revocation, and recovery | |
396 | // if there are additional spend conditions, their addition or removal is checked for validity | |
397 | // depending on which of the mandatory spend conditions is authorized. | |
398 | COptCCParams p, master, secondary; | |
399 | ||
400 | CCcontract_info *cp, C; | |
401 | cp = CCinit(&C,EVAL_STAKEGUARD); | |
402 | CPubKey defaultPubKey(ParseHex(cp->CChexstr)); | |
403 | ||
404 | if (tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) && | |
405 | p.IsValid() && | |
406 | p.version >= p.VERSION_V3 && | |
407 | p.evalCode == EVAL_STAKEGUARD && | |
408 | p.vData.size() == 3 && | |
409 | (master = COptCCParams(p.vData.back())).IsValid() && | |
410 | master.evalCode == 0 && | |
411 | master.m == 1 && | |
0bd17d21 | 412 | (secondary = COptCCParams(p.vData[1])).IsValid() && |
fc1ca997 | 413 | secondary.evalCode == EVAL_STAKEGUARD && |
414 | secondary.m == 1 && | |
415 | secondary.n == 1 && | |
416 | secondary.vKeys.size() == 1 && | |
417 | secondary.vKeys[0].which() == COptCCParams::ADDRTYPE_PK && | |
418 | GetDestinationBytes(secondary.vKeys[0]) == GetDestinationBytes(defaultPubKey)) | |
419 | { | |
420 | return true; | |
421 | } | |
422 | return false; | |
423 | } | |
424 | ||
191f3bbd | 425 | typedef struct ccFulfillmentCheck { |
281a5e2e | 426 | std::vector<CPubKey> &vPK; |
427 | std::vector<uint32_t> &vCount; | |
191f3bbd | 428 | } ccFulfillmentCheck; |
429 | ||
191f3bbd | 430 | // to figure out which node is signed |
431 | int CCFulfillmentVisitor(CC *cc, struct CCVisitor visitor) | |
432 | { | |
281a5e2e | 433 | //printf("cc_typeName: %s, cc_isFulfilled: %x, cc_isAnon: %x, cc_typeMask: %x, cc_condToJSONString:\n%s\n", |
434 | // cc_typeName(cc), cc_isFulfilled(cc), cc_isAnon(cc), cc_typeMask(cc), cc_conditionToJSONString(cc)); | |
435 | ||
436 | if (strcmp(cc_typeName(cc), "secp256k1-sha-256") == 0) | |
437 | { | |
438 | cJSON *json = cc_conditionToJSON(cc); | |
439 | if (json) | |
440 | { | |
441 | cJSON *pubKeyNode = json->child->next; | |
442 | if (strcmp(pubKeyNode->string, "publicKey") == 0) | |
443 | { | |
444 | ccFulfillmentCheck *pfc = (ccFulfillmentCheck *)(visitor.context); | |
445 | ||
f2450b36 | 446 | //printf("public key: %s\n", pubKeyNode->valuestring); |
281a5e2e | 447 | CPubKey pubKey = CPubKey(ParseHex(pubKeyNode->valuestring)); |
448 | ||
449 | for (int i = 0; i < pfc->vPK.size(); i++) | |
450 | { | |
451 | if (i < pfc->vCount.size() && (pfc->vPK[i] == pubKey)) | |
452 | { | |
453 | pfc->vCount[i]++; | |
454 | } | |
455 | } | |
456 | } | |
457 | cJSON_free(json); | |
458 | } | |
459 | } | |
281a5e2e | 460 | return 1; |
191f3bbd | 461 | } |
462 | ||
463 | int IsCCFulfilled(CC *cc, ccFulfillmentCheck *ctx) | |
464 | { | |
191f3bbd | 465 | struct CCVisitor visitor = {&CCFulfillmentVisitor, NULL, 0, (void *)ctx}; |
466 | cc_visit(cc, visitor); | |
281a5e2e | 467 | |
f2450b36 | 468 | //printf("count key 1: %d, count key 2: %d\n", ctx->vCount[0], ctx->vCount[1]); |
281a5e2e | 469 | return ctx->vCount[0]; |
191f3bbd | 470 | } |
471 | ||
4ecaf167 | 472 | bool StakeGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled) |
8a727a26 | 473 | { |
ca4a5f26 | 474 | // WARNING: this has not been tested combined with time locks |
8a727a26 | 475 | // validate this spend of a transaction with it being past any applicable time lock and one of the following statements being true: |
476 | // 1. the spend is signed by the original output destination's private key and normal payment requirements, spends as normal | |
ca4a5f26 | 477 | // 2. the spend is signed by the private key of the StakeGuard contract and pushes a signed stake transaction |
f3b0d2ab | 478 | // with the same exact utxo source, a target block height of later than or equal to this tx, and a different prevBlock hash |
8a727a26 | 479 | |
480 | // first, check to see if the spending contract is signed by the default destination address | |
481 | // if so, success and we are done | |
482 | ||
483 | // get preConditions and parameters | |
484 | std::vector<std::vector<unsigned char>> preConditions = std::vector<std::vector<unsigned char>>(); | |
485 | std::vector<std::vector<unsigned char>> params = std::vector<std::vector<unsigned char>>(); | |
905fe35e | 486 | CTransaction txOut; |
8a727a26 | 487 | |
191f3bbd | 488 | bool signedByFirstKey = false; |
f3b0d2ab | 489 | bool validCheat = false; |
f3ec769e | 490 | |
191f3bbd | 491 | CC *cc = GetCryptoCondition(tx.vin[nIn].scriptSig); |
492 | ||
23f0b6ec | 493 | // tx is the spending tx, the cc transaction comes back in txOut |
494 | bool validCCParams = GetCCParams(eval, tx, nIn, txOut, preConditions, params); | |
495 | COptCCParams ccp; | |
496 | if (preConditions.size() > 0) | |
497 | { | |
498 | ccp = COptCCParams(preConditions[0]); | |
499 | } | |
500 | ||
501 | if (validCCParams && ccp.IsValid() && ((cc && ccp.version < COptCCParams::VERSION_V3) || (!cc && ccp.version >= COptCCParams::VERSION_V3))) | |
8a727a26 | 502 | { |
281a5e2e | 503 | signedByFirstKey = false; |
191f3bbd | 504 | validCheat = false; |
f3b0d2ab | 505 | |
fc1ca997 | 506 | if (ccp.version >= COptCCParams::VERSION_V3) |
507 | { | |
48afe4a2 | 508 | CPubKey defaultPubKey(ParseHex(cp->CChexstr)); |
509 | ||
510 | CSmartTransactionSignatures smartSigs; | |
511 | bool signedByDefaultKey = false; | |
512 | std::vector<unsigned char> ffVec = GetFulfillmentVector(tx.vin[nIn].scriptSig); | |
513 | smartSigs = CSmartTransactionSignatures(std::vector<unsigned char>(ffVec.begin(), ffVec.end())); | |
514 | CKeyID checkKeyID = defaultPubKey.GetID(); | |
515 | for (auto &keySig : smartSigs.signatures) | |
516 | { | |
517 | CPubKey thisKey; | |
518 | thisKey.Set(keySig.second.pubKeyData.begin(), keySig.second.pubKeyData.end()); | |
519 | if (thisKey.GetID() == checkKeyID) | |
520 | { | |
521 | signedByDefaultKey = true; | |
522 | break; | |
523 | } | |
524 | } | |
525 | ||
526 | // if we don't have enough signatures to satisfy conditions, | |
527 | // it will fail before we check this. that means if it is not signed | |
528 | // by the default key, it must be signed / fulfilled by the alternate, which is | |
529 | // the first condition/key/identity | |
530 | signedByFirstKey = fulfilled || !signedByDefaultKey; | |
531 | ||
fc1ca997 | 532 | if (!signedByFirstKey && |
533 | params.size() == 2 && | |
534 | params[0].size() > 0 && | |
535 | params[0][0] == OPRETTYPE_STAKECHEAT) | |
536 | { | |
537 | CDataStream s = CDataStream(std::vector<unsigned char>(params[1].begin(), params[1].end()), SER_DISK, PROTOCOL_VERSION); | |
538 | bool checkOK = false; | |
539 | CTransaction cheatTx; | |
540 | try | |
541 | { | |
542 | cheatTx.Unserialize(s); | |
543 | checkOK = true; | |
544 | } | |
545 | catch (...) | |
546 | { | |
547 | } | |
548 | if (checkOK && !ValidateMatchingStake(txOut, tx.vin[0].prevout.n, cheatTx, validCheat)) | |
549 | { | |
550 | validCheat = false; | |
551 | } | |
552 | } | |
553 | } | |
554 | else if (ccp.m == 1 && ccp.n == 2 && ccp.vKeys.size() == 2) | |
905fe35e | 555 | { |
23f0b6ec | 556 | std::vector<uint32_t> vc = {0, 0}; |
557 | std::vector<CPubKey> keys; | |
f3b0d2ab | 558 | |
23f0b6ec | 559 | for (auto pk : ccp.vKeys) |
f3b0d2ab | 560 | { |
23f0b6ec | 561 | uint160 keyID = GetDestinationID(pk); |
562 | std::vector<unsigned char> vkch = GetDestinationBytes(pk); | |
563 | if (vkch.size() == 33) | |
f3b0d2ab | 564 | { |
23f0b6ec | 565 | keys.push_back(CPubKey(vkch)); |
b2a98c42 | 566 | } |
23f0b6ec | 567 | } |
b2a98c42 | 568 | |
23f0b6ec | 569 | if (keys.size() == 2) |
570 | { | |
48afe4a2 | 571 | ccFulfillmentCheck fc = {keys, vc}; |
572 | signedByFirstKey = (IsCCFulfilled(cc, &fc) != 0); | |
23f0b6ec | 573 | |
6a23bf78 | 574 | if (!signedByFirstKey && |
575 | ccp.evalCode == EVAL_STAKEGUARD && | |
576 | ccp.vKeys.size() == 2 && | |
577 | params.size() == 2 && | |
578 | params[0].size() > 0 && | |
579 | params[0][0] == OPRETTYPE_STAKECHEAT) | |
23f0b6ec | 580 | { |
581 | CDataStream s = CDataStream(std::vector<unsigned char>(params[1].begin(), params[1].end()), SER_DISK, PROTOCOL_VERSION); | |
582 | bool checkOK = false; | |
583 | CTransaction cheatTx; | |
584 | try | |
585 | { | |
586 | cheatTx.Unserialize(s); | |
587 | checkOK = true; | |
588 | } | |
589 | catch (...) | |
590 | { | |
591 | } | |
592 | if (checkOK && !ValidateMatchingStake(txOut, tx.vin[0].prevout.n, cheatTx, validCheat)) | |
593 | { | |
594 | validCheat = false; | |
595 | } | |
596 | } | |
f3b0d2ab | 597 | } |
905fe35e | 598 | } |
23f0b6ec | 599 | } |
600 | if (cc) | |
601 | { | |
8a727a26 | 602 | cc_free(cc); |
603 | } | |
281a5e2e | 604 | if (!(signedByFirstKey || validCheat)) |
605 | { | |
696d2d67 | 606 | return eval->Error("error reading coinbase or spending proof invalid\n"); |
281a5e2e | 607 | } |
608 | else return true; | |
8a727a26 | 609 | } |
610 | ||
ec8a120b | 611 | bool IsStakeGuardInput(const CScript &scriptSig) |
612 | { | |
68b309c0 MT |
613 | uint32_t ecode; |
614 | return scriptSig.IsPayToCryptoCondition(&ecode) && ecode == EVAL_STAKEGUARD; | |
ec8a120b | 615 | } |
616 | ||
ca4a5f26 | 617 | UniValue StakeGuardInfo() |
8a727a26 | 618 | { |
619 | UniValue result(UniValue::VOBJ); char numstr[64]; | |
620 | CMutableTransaction mtx; | |
621 | CPubKey pk; | |
622 | ||
623 | CCcontract_info *cp,C; | |
624 | ||
ca4a5f26 | 625 | cp = CCinit(&C,EVAL_STAKEGUARD); |
8a727a26 | 626 | |
627 | result.push_back(Pair("result","success")); | |
ca4a5f26 | 628 | result.push_back(Pair("name","StakeGuard")); |
8a727a26 | 629 | |
630 | // all UTXOs to the contract address that are to any of the wallet addresses are to us | |
631 | // each is spendable as a normal transaction, but the spend may fail if it gets spent out | |
632 | // from under us | |
633 | pk = GetUnspendable(cp,0); | |
634 | return(result); | |
635 | } | |
636 |