]>
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 | { | |
193 | idAddressMap = ServerTransactionSignatureChecker::ExtractIDMap(srcTx.vout[stakeTx.vin[0].prevout.n].scriptPubKey, stakeParams.blkHeight); | |
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); | |
235 | ||
236 | std::vector<CTxDestination> indexDests; | |
237 | ||
238 | vout = CTxOut(value, MakeMofNCCScript(1, primary, cheatCatcher)); | |
56fe75cb | 239 | } |
c52bd43c | 240 | else if (dest.which() == COptCCParams::ADDRTYPE_PK) |
56fe75cb | 241 | { |
242 | CCcontract_info *cp, C; | |
243 | cp = CCinit(&C,EVAL_STAKEGUARD); | |
905fe35e | 244 | |
56fe75cb | 245 | CPubKey ccAddress = CPubKey(ParseHex(cp->CChexstr)); |
905fe35e | 246 | |
56fe75cb | 247 | // return an output that is bound to the stake transaction and can be spent by presenting either a signed condition by the original |
248 | // destination address or a properly signed stake transaction of the same utxo on a fork | |
c52bd43c | 249 | vout = MakeCC1of2vout(EVAL_STAKEGUARD, value, boost::apply_visitor<GetPubKeyForPubKey>(GetPubKeyForPubKey(), dest), ccAddress); |
905fe35e | 250 | |
56fe75cb | 251 | std::vector<CTxDestination> vKeys; |
252 | vKeys.push_back(dest); | |
253 | vKeys.push_back(ccAddress); | |
254 | ||
255 | std::vector<std::vector<unsigned char>> vData = std::vector<std::vector<unsigned char>>(); | |
905fe35e | 256 | |
56fe75cb | 257 | vData.push_back(std::vector<unsigned char>(utxo.begin(), utxo.end())); |
258 | ||
259 | // prev block hash and height is here to make validation easy | |
260 | vData.push_back(std::vector<unsigned char>(p.prevHash.begin(), p.prevHash.end())); | |
261 | std::vector<unsigned char> height = std::vector<unsigned char>(4); | |
262 | for (int i = 0; i < 4; i++) | |
263 | { | |
264 | height[i] = (p.blkHeight >> (8 * i)) & 0xff; | |
265 | } | |
266 | vData.push_back(height); | |
905fe35e | 267 | |
56fe75cb | 268 | COptCCParams ccp = COptCCParams(COptCCParams::VERSION_V1, EVAL_STAKEGUARD, 1, 2, vKeys, vData); |
905fe35e | 269 | |
56fe75cb | 270 | vout.scriptPubKey << ccp.AsVector() << OP_DROP; |
271 | } | |
272 | ||
905fe35e | 273 | return true; |
274 | } | |
275 | return false; | |
276 | } | |
277 | ||
278 | // validates if a stake transaction is both valid and cheating, defined by: | |
23f0b6ec | 279 | // the same exact utxo source, a target block height of later than that of the provided coinbase tx that is also targeting a fork |
280 | // of the chain. the source transaction must be a coinbase | |
f3b0d2ab | 281 | bool ValidateMatchingStake(const CTransaction &ccTx, uint32_t voutNum, const CTransaction &stakeTx, bool &cheating) |
905fe35e | 282 | { |
f3b0d2ab | 283 | // an invalid or non-matching stake transaction cannot cheat |
284 | cheating = false; | |
285 | ||
79b0432d | 286 | //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); |
287 | ||
905fe35e | 288 | if (ccTx.IsCoinBase()) |
289 | { | |
290 | CStakeParams p; | |
f3b0d2ab | 291 | if (ValidateStakeTransaction(stakeTx, p)) |
905fe35e | 292 | { |
293 | std::vector<std::vector<unsigned char>> vParams = std::vector<std::vector<unsigned char>>(); | |
294 | CScript dummy; | |
295 | ||
296 | if (ccTx.vout[voutNum].scriptPubKey.IsPayToCryptoCondition(&dummy, vParams) && vParams.size() > 0) | |
297 | { | |
298 | COptCCParams ccp = COptCCParams(vParams[0]); | |
56fe75cb | 299 | CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); |
300 | ||
301 | if (p.version >= p.VERSION_EXTENDED_STAKE && ccp.version >= ccp.VERSION_V3 && ccp.vData.size()) | |
905fe35e | 302 | { |
56fe75cb | 303 | CStakeInfo stakeInfo(ccp.vData[0]); |
304 | hw << stakeTx.vin[0].prevout.hash; | |
305 | hw << stakeTx.vin[0].prevout.n; | |
306 | uint256 utxo = hw.GetHash(); | |
905fe35e | 307 | |
56fe75cb | 308 | if (utxo == stakeInfo.utxo) |
309 | { | |
310 | if (p.prevHash != stakeInfo.prevHash && p.blkHeight >= stakeInfo.height) | |
311 | { | |
312 | cheating = true; | |
313 | return true; | |
314 | } | |
315 | // if block height is equal and we are at the else, prevHash must have been equal | |
316 | else if (p.blkHeight >= stakeInfo.height) | |
317 | { | |
318 | return true; | |
319 | } | |
320 | } | |
321 | } | |
6a23bf78 | 322 | else if (p.version < p.VERSION_EXTENDED_STAKE && |
323 | ccp.version < ccp.VERSION_V3 && | |
324 | ccp.IsValid() && | |
325 | ccp.vData.size() >= 3 && | |
56fe75cb | 326 | ccp.vData[2].size() <= 4) |
327 | { | |
f3b0d2ab | 328 | hw << stakeTx.vin[0].prevout.hash; |
329 | hw << stakeTx.vin[0].prevout.n; | |
905fe35e | 330 | uint256 utxo = hw.GetHash(); |
905fe35e | 331 | |
332 | uint32_t height = 0; | |
185b2d4f | 333 | int i, dataLen = ccp.vData[2].size(); |
334 | for (i = dataLen - 1; i >= 0; i--) | |
905fe35e | 335 | { |
185b2d4f | 336 | height = (height << 8) + ccp.vData[2][i]; |
905fe35e | 337 | } |
95c5c69b | 338 | // for debugging strange issue |
339 | // printf("iterator: %d, height: %d, datalen: %d\n", i, height, dataLen); | |
8a727a26 | 340 | |
f3b0d2ab | 341 | if (utxo == uint256(ccp.vData[0])) |
905fe35e | 342 | { |
f3b0d2ab | 343 | if (p.prevHash != uint256(ccp.vData[1]) && p.blkHeight >= height) |
344 | { | |
345 | cheating = true; | |
346 | return true; | |
347 | } | |
348 | // if block height is equal and we are at the else, prevHash must have been equal | |
349 | else if (p.blkHeight == height) | |
350 | { | |
351 | return true; | |
352 | } | |
905fe35e | 353 | } |
354 | } | |
355 | } | |
356 | } | |
357 | } | |
8a727a26 | 358 | return false; |
359 | } | |
360 | ||
905fe35e | 361 | // this attaches an opret to a mutable transaction that provides the necessary evidence of a signed, cheating stake transaction |
362 | bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx) | |
8a727a26 | 363 | { |
905fe35e | 364 | std::vector<unsigned char> vch; |
b2a98c42 | 365 | CDataStream s = CDataStream(SER_DISK, PROTOCOL_VERSION); |
df756d24 | 366 | bool isCheater = false; |
8a727a26 | 367 | |
f3b0d2ab | 368 | if (ValidateMatchingStake(ccTx, voutNum, cheatTx, isCheater) && isCheater) |
905fe35e | 369 | { |
370 | CTxOut vOut = CTxOut(); | |
26bf01e9 | 371 | int64_t opretype_stakecheat = OPRETTYPE_STAKECHEAT; |
905fe35e | 372 | |
373 | CScript vData = CScript(); | |
374 | cheatTx.Serialize(s); | |
375 | vch = std::vector<unsigned char>(s.begin(), s.end()); | |
26bf01e9 | 376 | vData << opretype_stakecheat << vch; |
377 | vch = std::vector<unsigned char>(vData.begin(), vData.end()); | |
378 | vOut.scriptPubKey << OP_RETURN << vch; | |
379 | ||
51848bbc | 380 | // printf("Script encoding inner:\n%s\nouter:\n%s\n", vData.ToString().c_str(), vOut.scriptPubKey.ToString().c_str()); |
26bf01e9 | 381 | |
905fe35e | 382 | vOut.nValue = 0; |
383 | mtx.vout.push_back(vOut); | |
384 | } | |
df756d24 | 385 | return isCheater; |
8a727a26 | 386 | } |
387 | ||
fc1ca997 | 388 | // a version 3 guard output should be a 1 of 2 meta condition, with both of the |
389 | // conditions being stakeguard only and one of the conditions being sent to the public | |
390 | // stakeguard destination. Only for smart transaction V3 and beyond. | |
391 | bool PrecheckStakeGuardOutput(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height) | |
392 | { | |
393 | if (CConstVerusSolutionVector::GetVersionByHeight(height) < CActivationHeight::ACTIVATE_EXTENDEDSTAKE) | |
394 | { | |
395 | return true; | |
396 | } | |
397 | ||
398 | // ensure that we have all required spend conditions for primary, revocation, and recovery | |
399 | // if there are additional spend conditions, their addition or removal is checked for validity | |
400 | // depending on which of the mandatory spend conditions is authorized. | |
401 | COptCCParams p, master, secondary; | |
402 | ||
403 | CCcontract_info *cp, C; | |
404 | cp = CCinit(&C,EVAL_STAKEGUARD); | |
405 | CPubKey defaultPubKey(ParseHex(cp->CChexstr)); | |
406 | ||
407 | if (tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) && | |
408 | p.IsValid() && | |
409 | p.version >= p.VERSION_V3 && | |
410 | p.evalCode == EVAL_STAKEGUARD && | |
411 | p.vData.size() == 3 && | |
412 | (master = COptCCParams(p.vData.back())).IsValid() && | |
413 | master.evalCode == 0 && | |
414 | master.m == 1 && | |
0bd17d21 | 415 | (secondary = COptCCParams(p.vData[1])).IsValid() && |
fc1ca997 | 416 | secondary.evalCode == EVAL_STAKEGUARD && |
417 | secondary.m == 1 && | |
418 | secondary.n == 1 && | |
419 | secondary.vKeys.size() == 1 && | |
420 | secondary.vKeys[0].which() == COptCCParams::ADDRTYPE_PK && | |
421 | GetDestinationBytes(secondary.vKeys[0]) == GetDestinationBytes(defaultPubKey)) | |
422 | { | |
423 | return true; | |
424 | } | |
425 | return false; | |
426 | } | |
427 | ||
191f3bbd | 428 | typedef struct ccFulfillmentCheck { |
281a5e2e | 429 | std::vector<CPubKey> &vPK; |
430 | std::vector<uint32_t> &vCount; | |
191f3bbd | 431 | } ccFulfillmentCheck; |
432 | ||
191f3bbd | 433 | // to figure out which node is signed |
434 | int CCFulfillmentVisitor(CC *cc, struct CCVisitor visitor) | |
435 | { | |
281a5e2e | 436 | //printf("cc_typeName: %s, cc_isFulfilled: %x, cc_isAnon: %x, cc_typeMask: %x, cc_condToJSONString:\n%s\n", |
437 | // cc_typeName(cc), cc_isFulfilled(cc), cc_isAnon(cc), cc_typeMask(cc), cc_conditionToJSONString(cc)); | |
438 | ||
439 | if (strcmp(cc_typeName(cc), "secp256k1-sha-256") == 0) | |
440 | { | |
441 | cJSON *json = cc_conditionToJSON(cc); | |
442 | if (json) | |
443 | { | |
444 | cJSON *pubKeyNode = json->child->next; | |
445 | if (strcmp(pubKeyNode->string, "publicKey") == 0) | |
446 | { | |
447 | ccFulfillmentCheck *pfc = (ccFulfillmentCheck *)(visitor.context); | |
448 | ||
f2450b36 | 449 | //printf("public key: %s\n", pubKeyNode->valuestring); |
281a5e2e | 450 | CPubKey pubKey = CPubKey(ParseHex(pubKeyNode->valuestring)); |
451 | ||
452 | for (int i = 0; i < pfc->vPK.size(); i++) | |
453 | { | |
454 | if (i < pfc->vCount.size() && (pfc->vPK[i] == pubKey)) | |
455 | { | |
456 | pfc->vCount[i]++; | |
457 | } | |
458 | } | |
459 | } | |
460 | cJSON_free(json); | |
461 | } | |
462 | } | |
281a5e2e | 463 | return 1; |
191f3bbd | 464 | } |
465 | ||
466 | int IsCCFulfilled(CC *cc, ccFulfillmentCheck *ctx) | |
467 | { | |
191f3bbd | 468 | struct CCVisitor visitor = {&CCFulfillmentVisitor, NULL, 0, (void *)ctx}; |
469 | cc_visit(cc, visitor); | |
281a5e2e | 470 | |
f2450b36 | 471 | //printf("count key 1: %d, count key 2: %d\n", ctx->vCount[0], ctx->vCount[1]); |
281a5e2e | 472 | return ctx->vCount[0]; |
191f3bbd | 473 | } |
474 | ||
4ecaf167 | 475 | bool StakeGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled) |
8a727a26 | 476 | { |
ca4a5f26 | 477 | // WARNING: this has not been tested combined with time locks |
8a727a26 | 478 | // validate this spend of a transaction with it being past any applicable time lock and one of the following statements being true: |
479 | // 1. the spend is signed by the original output destination's private key and normal payment requirements, spends as normal | |
ca4a5f26 | 480 | // 2. the spend is signed by the private key of the StakeGuard contract and pushes a signed stake transaction |
f3b0d2ab | 481 | // with the same exact utxo source, a target block height of later than or equal to this tx, and a different prevBlock hash |
8a727a26 | 482 | |
483 | // first, check to see if the spending contract is signed by the default destination address | |
484 | // if so, success and we are done | |
485 | ||
486 | // get preConditions and parameters | |
487 | std::vector<std::vector<unsigned char>> preConditions = std::vector<std::vector<unsigned char>>(); | |
488 | std::vector<std::vector<unsigned char>> params = std::vector<std::vector<unsigned char>>(); | |
905fe35e | 489 | CTransaction txOut; |
8a727a26 | 490 | |
191f3bbd | 491 | bool signedByFirstKey = false; |
f3b0d2ab | 492 | bool validCheat = false; |
f3ec769e | 493 | |
191f3bbd | 494 | CC *cc = GetCryptoCondition(tx.vin[nIn].scriptSig); |
495 | ||
23f0b6ec | 496 | // tx is the spending tx, the cc transaction comes back in txOut |
497 | bool validCCParams = GetCCParams(eval, tx, nIn, txOut, preConditions, params); | |
498 | COptCCParams ccp; | |
499 | if (preConditions.size() > 0) | |
500 | { | |
501 | ccp = COptCCParams(preConditions[0]); | |
502 | } | |
503 | ||
504 | if (validCCParams && ccp.IsValid() && ((cc && ccp.version < COptCCParams::VERSION_V3) || (!cc && ccp.version >= COptCCParams::VERSION_V3))) | |
8a727a26 | 505 | { |
281a5e2e | 506 | signedByFirstKey = false; |
191f3bbd | 507 | validCheat = false; |
f3b0d2ab | 508 | |
fc1ca997 | 509 | if (ccp.version >= COptCCParams::VERSION_V3) |
510 | { | |
48afe4a2 | 511 | CPubKey defaultPubKey(ParseHex(cp->CChexstr)); |
512 | ||
513 | CSmartTransactionSignatures smartSigs; | |
514 | bool signedByDefaultKey = false; | |
515 | std::vector<unsigned char> ffVec = GetFulfillmentVector(tx.vin[nIn].scriptSig); | |
516 | smartSigs = CSmartTransactionSignatures(std::vector<unsigned char>(ffVec.begin(), ffVec.end())); | |
517 | CKeyID checkKeyID = defaultPubKey.GetID(); | |
518 | for (auto &keySig : smartSigs.signatures) | |
519 | { | |
520 | CPubKey thisKey; | |
521 | thisKey.Set(keySig.second.pubKeyData.begin(), keySig.second.pubKeyData.end()); | |
522 | if (thisKey.GetID() == checkKeyID) | |
523 | { | |
524 | signedByDefaultKey = true; | |
525 | break; | |
526 | } | |
527 | } | |
528 | ||
529 | // if we don't have enough signatures to satisfy conditions, | |
530 | // it will fail before we check this. that means if it is not signed | |
531 | // by the default key, it must be signed / fulfilled by the alternate, which is | |
532 | // the first condition/key/identity | |
533 | signedByFirstKey = fulfilled || !signedByDefaultKey; | |
534 | ||
fc1ca997 | 535 | if (!signedByFirstKey && |
536 | params.size() == 2 && | |
537 | params[0].size() > 0 && | |
538 | params[0][0] == OPRETTYPE_STAKECHEAT) | |
539 | { | |
540 | CDataStream s = CDataStream(std::vector<unsigned char>(params[1].begin(), params[1].end()), SER_DISK, PROTOCOL_VERSION); | |
541 | bool checkOK = false; | |
542 | CTransaction cheatTx; | |
543 | try | |
544 | { | |
545 | cheatTx.Unserialize(s); | |
546 | checkOK = true; | |
547 | } | |
548 | catch (...) | |
549 | { | |
550 | } | |
551 | if (checkOK && !ValidateMatchingStake(txOut, tx.vin[0].prevout.n, cheatTx, validCheat)) | |
552 | { | |
553 | validCheat = false; | |
554 | } | |
555 | } | |
556 | } | |
557 | else if (ccp.m == 1 && ccp.n == 2 && ccp.vKeys.size() == 2) | |
905fe35e | 558 | { |
23f0b6ec | 559 | std::vector<uint32_t> vc = {0, 0}; |
560 | std::vector<CPubKey> keys; | |
f3b0d2ab | 561 | |
23f0b6ec | 562 | for (auto pk : ccp.vKeys) |
f3b0d2ab | 563 | { |
23f0b6ec | 564 | uint160 keyID = GetDestinationID(pk); |
565 | std::vector<unsigned char> vkch = GetDestinationBytes(pk); | |
566 | if (vkch.size() == 33) | |
f3b0d2ab | 567 | { |
23f0b6ec | 568 | keys.push_back(CPubKey(vkch)); |
b2a98c42 | 569 | } |
23f0b6ec | 570 | } |
b2a98c42 | 571 | |
23f0b6ec | 572 | if (keys.size() == 2) |
573 | { | |
48afe4a2 | 574 | ccFulfillmentCheck fc = {keys, vc}; |
575 | signedByFirstKey = (IsCCFulfilled(cc, &fc) != 0); | |
23f0b6ec | 576 | |
6a23bf78 | 577 | if (!signedByFirstKey && |
578 | ccp.evalCode == EVAL_STAKEGUARD && | |
579 | ccp.vKeys.size() == 2 && | |
580 | params.size() == 2 && | |
581 | params[0].size() > 0 && | |
582 | params[0][0] == OPRETTYPE_STAKECHEAT) | |
23f0b6ec | 583 | { |
584 | CDataStream s = CDataStream(std::vector<unsigned char>(params[1].begin(), params[1].end()), SER_DISK, PROTOCOL_VERSION); | |
585 | bool checkOK = false; | |
586 | CTransaction cheatTx; | |
587 | try | |
588 | { | |
589 | cheatTx.Unserialize(s); | |
590 | checkOK = true; | |
591 | } | |
592 | catch (...) | |
593 | { | |
594 | } | |
595 | if (checkOK && !ValidateMatchingStake(txOut, tx.vin[0].prevout.n, cheatTx, validCheat)) | |
596 | { | |
597 | validCheat = false; | |
598 | } | |
599 | } | |
f3b0d2ab | 600 | } |
905fe35e | 601 | } |
23f0b6ec | 602 | } |
603 | if (cc) | |
604 | { | |
8a727a26 | 605 | cc_free(cc); |
606 | } | |
281a5e2e | 607 | if (!(signedByFirstKey || validCheat)) |
608 | { | |
696d2d67 | 609 | return eval->Error("error reading coinbase or spending proof invalid\n"); |
281a5e2e | 610 | } |
611 | else return true; | |
8a727a26 | 612 | } |
613 | ||
ec8a120b | 614 | bool IsStakeGuardInput(const CScript &scriptSig) |
615 | { | |
68b309c0 MT |
616 | uint32_t ecode; |
617 | return scriptSig.IsPayToCryptoCondition(&ecode) && ecode == EVAL_STAKEGUARD; | |
ec8a120b | 618 | } |
619 | ||
ca4a5f26 | 620 | UniValue StakeGuardInfo() |
8a727a26 | 621 | { |
622 | UniValue result(UniValue::VOBJ); char numstr[64]; | |
623 | CMutableTransaction mtx; | |
624 | CPubKey pk; | |
625 | ||
626 | CCcontract_info *cp,C; | |
627 | ||
ca4a5f26 | 628 | cp = CCinit(&C,EVAL_STAKEGUARD); |
8a727a26 | 629 | |
630 | result.push_back(Pair("result","success")); | |
ca4a5f26 | 631 | result.push_back(Pair("name","StakeGuard")); |
8a727a26 | 632 | |
633 | // all UTXOs to the contract address that are to any of the wallet addresses are to us | |
634 | // each is spendable as a normal transaction, but the spend may fail if it gets spent out | |
635 | // from under us | |
636 | pk = GetUnspendable(cp,0); | |
637 | return(result); | |
638 | } | |
639 |