1 /********************************************************************
2 * (C) 2019 Michael Toutonghi
4 * Distributed under the MIT software license, see the accompanying
5 * file COPYING or http://www.opensource.org/licenses/mit-license.php.
7 * This provides support for PBaaS identity definition,
9 * This is a decentralized identity class that provides the minimum
10 * basic function needed to enable persistent DID-similar identities,
11 * needed for signatories, that will eventually bridge compatibly to
17 #include "pbaas/pbaas.h"
20 extern CTxMemPool mempool;
22 CCommitmentHash::CCommitmentHash(const CTransaction &tx)
24 for (auto txOut : tx.vout)
27 if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_COMMITMENT && p.vData.size())
29 ::FromVector(p.vData[0], *this);
35 UniValue CNameReservation::ToUniValue() const
37 UniValue ret(UniValue::VOBJ);
38 ret.push_back(Pair("name", name));
39 ret.push_back(Pair("salt", salt.GetHex()));
40 ret.push_back(Pair("referral", referral.IsNull() ? "" : EncodeDestination(referral)));
44 ret.push_back(Pair("parent", ""));
45 ret.push_back(Pair("nameid", EncodeDestination(DecodeDestination(name + "@"))));
49 ret.push_back(Pair("parent", ConnectedChains.ThisChain().name));
50 ret.push_back(Pair("nameid", EncodeDestination(DecodeDestination(name + "." + ConnectedChains.ThisChain().name + "@"))));
56 CIdentity::CIdentity(const CTransaction &tx, int *voutNum)
58 std::set<uint160> ids;
62 nVersion = PBAAS_VERSION_INVALID;
63 for (int i = 0; i < tx.vout.size(); i++)
65 CIdentity foundIdentity(tx.vout[i].scriptPubKey);
66 if (foundIdentity.IsValid() && !found)
68 *this = foundIdentity;
72 else if (foundIdentity.IsValid())
77 if (voutNum && IsValid())
83 CIdentity CIdentity::LookupIdentity(const CIdentityID &nameID, uint32_t height, uint32_t *pHeightOut, CTxIn *pIdTxIn)
89 uint32_t heightOut = 0;
93 pHeightOut = &heightOut;
105 CTxIn &idTxIn = *pIdTxIn;
107 std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs, unspentNewIDX;
109 uint160 keyID(CCrossChainRPCData::GetConditionID(nameID, EVAL_IDENTITY_PRIMARY));
111 if (GetAddressUnspent(keyID, CScript::P2IDX, unspentNewIDX) && GetAddressUnspent(keyID, CScript::P2PKH, unspentOutputs))
113 // combine searches into 1 vector
114 unspentOutputs.insert(unspentOutputs.begin(), unspentNewIDX.begin(), unspentNewIDX.end());
115 CCoinsViewCache view(pcoinsTip);
117 for (auto it = unspentOutputs.begin(); !ret.IsValid() && it != unspentOutputs.end(); it++)
121 if (view.GetCoins(it->first.txhash, coins))
123 if (coins.IsAvailable(it->first.index))
125 // check the mempool for spent/modified
126 CSpentIndexKey key(it->first.txhash, it->first.index);
127 CSpentIndexValue value;
130 if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
132 p.evalCode == EVAL_IDENTITY_PRIMARY &&
133 (ret = CIdentity(coins.vout[it->first.index].scriptPubKey)).IsValid())
135 if (ret.GetID() == nameID)
137 idTxIn = CTxIn(it->first.txhash, it->first.index);
138 *pHeightOut = it->second.blockHeight;
142 // got an identity masquerading as another, clear it
150 if (height != 0 && (*pHeightOut > height || (height == 1 && *pHeightOut == height)))
154 // if we must check up to a specific height that is less than the latest height, do so
155 std::vector<CAddressIndexDbEntry> addressIndex, addressIndex2;
157 if (GetAddressIndex(keyID, CScript::P2PKH, addressIndex, 0, height) &&
158 GetAddressIndex(keyID, CScript::P2IDX, addressIndex2, 0, height) &&
159 (addressIndex.size() || addressIndex2.size()))
161 if (addressIndex2.size())
163 addressIndex.insert(addressIndex.begin(), addressIndex2.begin(), addressIndex2.end());
166 // look from last backward to find the first valid ID
167 for (int i = addressIndex.size() - 1; i >= 0; i--)
169 if (addressIndex[i].first.blockHeight < *pHeightOut)
177 if (!addressIndex[i].first.spending &&
178 addressIndex[i].first.txindex > txIndex && // always select the latest in a block, if there can be more than one
179 myGetTransaction(addressIndex[i].first.txhash, idTx, blkHash) &&
180 idTx.vout[addressIndex[i].first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
182 p.evalCode == EVAL_IDENTITY_PRIMARY &&
183 (ret = CIdentity(idTx.vout[addressIndex[i].first.index].scriptPubKey)).IsValid())
185 idTxIn = CTxIn(addressIndex[i].first.txhash, addressIndex[i].first.index);
186 *pHeightOut = addressIndex[i].first.blockHeight;
187 txIndex = addressIndex[i].first.txindex;
193 // not found at that height
202 CIdentity CIdentity::LookupIdentity(const std::string &name, uint32_t height, uint32_t *pHeightOut, CTxIn *idTxIn)
204 return LookupIdentity(GetID(name), height, pHeightOut, idTxIn);
207 CIdentity CIdentity::LookupFirstIdentity(const CIdentityID &idID, uint32_t *pHeightOut, CTxIn *pIdTxIn, CTransaction *pidTx)
211 uint32_t heightOut = 0;
215 pHeightOut = &heightOut;
227 CTxIn &idTxIn = *pIdTxIn;
229 std::vector<CAddressUnspentDbEntry> unspentOutputs, unspentNewIDX;
231 CKeyID keyID(CCrossChainRPCData::GetConditionID(idID, EVAL_IDENTITY_RESERVATION));
233 if (GetAddressUnspent(keyID, CScript::P2IDX, unspentNewIDX) && GetAddressUnspent(keyID, CScript::P2PKH, unspentOutputs))
235 // combine searches into 1 vector
236 unspentOutputs.insert(unspentOutputs.begin(), unspentNewIDX.begin(), unspentNewIDX.end());
237 CCoinsViewCache view(pcoinsTip);
239 for (auto it = unspentOutputs.begin(); !ret.IsValid() && it != unspentOutputs.end(); it++)
243 if (view.GetCoins(it->first.txhash, coins))
245 if (coins.IsAvailable(it->first.index))
248 if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
250 p.evalCode == EVAL_IDENTITY_RESERVATION)
254 if (myGetTransaction(it->first.txhash, idTx, blkHash) && (ret = CIdentity(idTx)).IsValid() && ret.GetID() == idID)
257 for (i = 0; i < idTx.vout.size(); i++)
260 if (idTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3 && p.evalCode == EVAL_IDENTITY_PRIMARY)
266 if (i < idTx.vout.size())
272 idTxIn = CTxIn(it->first.txhash, i);
273 *pHeightOut = it->second.blockHeight;
284 // this enables earliest rejection of invalid CC transactions
285 bool ValidateSpendingIdentityReservation(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height, CCurrencyDefinition issuingChain)
287 // CHECK #1 - there is only one reservation output, and there is also one identity output that matches the reservation.
288 // the identity output must come first and have from 0 to 3 referral outputs between it and the reservation.
289 int numReferrers = 0;
290 int identityCount = 0;
291 int reservationCount = 0;
292 CIdentity newIdentity;
293 CNameReservation newName;
294 std::vector<CTxDestination> referrers;
297 bool isPBaaS = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
299 for (auto &txout : tx.vout)
302 if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
304 if (p.evalCode == EVAL_IDENTITY_PRIMARY)
306 if (identityCount++ || p.vData.size() < 2)
311 newIdentity = CIdentity(p.vData[0]);
313 else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
315 if (reservationCount++ || p.vData.size() < 2)
320 newName = CNameReservation(p.vData[0]);
322 else if (identityCount && !reservationCount)
324 if (!issuingChain.IDReferralLevels() ||
325 p.vKeys.size() < 1 ||
326 referrers.size() > (issuingChain.IDReferralLevels() - 1) ||
330 txout.nValue < issuingChain.IDReferralAmount())
335 referrers.push_back(p.vKeys[0]);
337 else if (identityCount != reservationCount)
345 // we can close a commitment UTXO without an identity
346 if (valid && !identityCount)
348 return state.Error("Transaction may not have an identity reservation without a matching identity");
352 return state.Error("Improperly formed identity definition transaction");
355 std::vector<CTxDestination> dests;
358 if (ExtractDestinations(tx.vout[outNum].scriptPubKey, outType, dests, minSigs))
360 uint160 thisID = newIdentity.GetID();
361 for (auto &dest : dests)
364 if (dest.which() == COptCCParams::ADDRTYPE_ID && (oneDestID = GetDestinationID(dest)) != thisID && !CIdentity::LookupIdentity(CIdentityID(oneDestID)).IsValid())
366 return state.Error("Destination includes invalid identity");
371 // CHECK #2 - must be rooted in this chain
372 if (newIdentity.parent != ConnectedChains.ThisChain().GetID() &&
373 !(isPBaaS && newIdentity.GetID() == ASSETCHAINS_CHAINID && IsVerusActive()))
375 return state.Error("Identity parent of new identity must be current chain");
378 // CHECK #3 - if dupID is valid, we need to be spending it to recover. redefinition is invalid
380 uint32_t priorHeightOut;
381 CIdentity dupID = newIdentity.LookupIdentity(newIdentity.GetID(), height - 1, &priorHeightOut, &idTxIn);
383 // CHECK #3a - if dupID is invalid, ensure we spend a matching name commitment
386 return state.Error("Identity already exists");
389 int commitmentHeight = 0;
392 CCoinsViewCache view(&dummy);
396 CCoinsViewMemPool viewMemPool(pcoinsTip, mempool);
397 view.SetBackend(viewMemPool);
402 CAmount nValueIn = 0;
404 // from here, we must spend a matching name commitment
405 std::map<uint256, const CCoins *> txMap;
406 for (auto &oneTxIn : tx.vin)
408 coins = txMap[oneTxIn.prevout.hash];
409 if (!coins && !(coins = view.AccessCoins(oneTxIn.prevout.hash)))
411 //LogPrintf("Cannot access input from output %u of transaction %s in transaction %s\n", oneTxIn.prevout.n, oneTxIn.prevout.hash.GetHex().c_str(), tx.GetHash().GetHex().c_str());
412 //printf("Cannot access input from output %u of transaction %s in transaction %s\n", oneTxIn.prevout.n, oneTxIn.prevout.hash.GetHex().c_str(), tx.GetHash().GetHex().c_str());
413 return state.Error("Cannot access input");
415 txMap[oneTxIn.prevout.hash] = coins;
417 if (oneTxIn.prevout.n >= coins->vout.size())
419 //extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
421 //TxToJSON(tx, uint256(), uniTx);
422 //printf("%s\n", uniTx.write(1, 2).c_str());
423 return state.Error("Input index out of range");
428 coins->vout[oneTxIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
430 p.evalCode == EVAL_IDENTITY_COMMITMENT &&
433 idx = oneTxIn.prevout.n;
434 ::FromVector(p.vData[0], ch);
435 commitmentHeight = coins->nHeight;
436 // this needs to already be in a prior block, or we can't consider it valid
437 if (!commitmentHeight || commitmentHeight == -1)
439 return state.Error("ID commitment was not already in blockchain");
445 if (idx == -1 || ch.hash.IsNull())
447 std::string specificMsg = "Invalid identity commitment in tx: " + tx.GetHash().GetHex();
448 return state.Error(specificMsg);
451 // are we spending a matching name commitment?
452 if (ch.hash != newName.GetCommitment().hash)
454 return state.Error("Mismatched identity commitment");
457 if (!newName.referral.IsNull() && issuingChain.IDReferralLevels() && !(CIdentity::LookupIdentity(newName.referral, commitmentHeight).IsValid()))
459 // invalid referral identity
460 return state.Error("Invalid referral identity specified");
463 CReserveTransactionDescriptor rtxd(tx, view, height);
465 // CHECK #4 - if blockchain referrals are not enabled or if there is no referring identity, make sure the fees of this transaction are full price for an identity,
466 // all further checks only if referrals are enabled and there is a referrer
467 if (!issuingChain.IDReferralLevels() || newName.referral.IsNull())
469 // make sure the fees of this transaction are full price for an identity, all further checks only if there is a referrer
470 if (rtxd.NativeFees() < issuingChain.IDFullRegistrationAmount())
472 return state.Error("Invalid identity registration - insufficient fee");
477 // CHECK #5 - ensure that the first referring output goes to the referring identity followed by up
478 // to two identities that come from the original definition transaction of the referring identity. account for all outputs between
479 // identity out and reservation out and ensure that they are correct and pay 20% of the price of an identity
480 uint32_t heightOut = 0;
481 CTransaction referralTx;
483 CIdentity firstReferralIdentity = CIdentity::LookupFirstIdentity(newName.referral, &heightOut, &idTxIn, &referralTx);
485 // referrer must be mined in when this transaction is put into the mem pool
486 if (heightOut >= height || !firstReferralIdentity.IsValid() || firstReferralIdentity.parent != ASSETCHAINS_CHAINID)
488 return state.Error("Invalid identity registration referral");
491 bool isReferral = false;
492 std::vector<CTxDestination> checkReferrers = std::vector<CTxDestination>({newName.referral});
495 for (auto &txout : referralTx.vout)
498 if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
500 if (p.evalCode == EVAL_IDENTITY_PRIMARY)
504 else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
510 if (p.vKeys.size() == 0 || p.vKeys[0].which() != COptCCParams::ADDRTYPE_ID)
513 return state.Error("Invalid identity registration referral outputs");
517 checkReferrers.push_back(p.vKeys[0]);
518 if (checkReferrers.size() == issuingChain.IDReferralLevels())
528 if (referrers.size() != checkReferrers.size())
530 return state.Error("Invalid identity registration - incorrect referral payments");
533 // make sure all paid referrers are correct
534 for (int i = 0; i < referrers.size(); i++)
536 if (referrers[i] != checkReferrers[i])
538 return state.Error("Invalid identity registration - incorrect referral payments");
542 // CHECK #6 - ensure that the transaction pays the correct mining and refferal fees
543 if (rtxd.NativeFees() < (issuingChain.IDReferredRegistrationAmount() - (referrers.size() * issuingChain.IDReferralAmount())))
545 return state.Error("Invalid identity registration - insufficient fee");
551 bool PrecheckIdentityReservation(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
553 CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
555 int numReferrers = 0;
556 int identityCount = 0;
557 int reservationCount = 0;
558 CIdentity newIdentity;
559 CNameReservation newName;
560 std::vector<CTxDestination> referrers;
563 AssertLockHeld(cs_main);
565 for (auto &txout : tx.vout)
568 if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
570 if (p.evalCode == EVAL_IDENTITY_PRIMARY)
572 if (identityCount++ || p.vData.size() < 2)
577 newIdentity = CIdentity(p.vData[0]);
579 else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
581 if (reservationCount++ || p.vData.size() < 2)
586 newName = CNameReservation(p.vData[0]);
588 else if (identityCount && !reservationCount)
590 if (!thisChain.IDReferralLevels() ||
591 p.vKeys.size() < 1 ||
592 referrers.size() > (thisChain.IDReferralLevels() - 1) ||
596 txout.nValue < thisChain.IDReferralAmount())
601 referrers.push_back(p.vKeys[0]);
603 else if (identityCount != reservationCount)
611 // we can close a commitment UTXO without an identity
612 if (valid && !identityCount)
614 return state.Error("Transaction may not have an identity reservation without a matching identity");
618 return state.Error("Improperly formed identity definition transaction");
621 int commitmentHeight = 0;
623 LOCK2(cs_main, mempool.cs);
628 CAmount nValueIn = 0;
630 LOCK2(cs_main, mempool.cs);
632 // from here, we must spend a matching name commitment
633 std::map<uint256, CTransaction> txMap;
635 for (auto &oneTxIn : tx.vin)
637 CTransaction sourceTx = txMap[oneTxIn.prevout.hash];
638 if (sourceTx.nVersion <= sourceTx.SPROUT_MIN_CURRENT_VERSION && !myGetTransaction(oneTxIn.prevout.hash, sourceTx, hashBlk))
640 //LogPrintf("Cannot access input from output %u of transaction %s in transaction %s\n", oneTxIn.prevout.n, oneTxIn.prevout.hash.GetHex().c_str(), tx.GetHash().GetHex().c_str());
641 //printf("Cannot access input from output %u of transaction %s in transaction %s\n", oneTxIn.prevout.n, oneTxIn.prevout.hash.GetHex().c_str(), tx.GetHash().GetHex().c_str());
642 return state.Error("Cannot access input");
644 txMap[oneTxIn.prevout.hash] = sourceTx;
646 if (oneTxIn.prevout.n >= sourceTx.vout.size())
648 //extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
650 //TxToJSON(tx, uint256(), uniTx);
651 //printf("%s\n", uniTx.write(1, 2).c_str());
652 return state.Error("Input index out of range");
657 sourceTx.vout[oneTxIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
659 p.evalCode == EVAL_IDENTITY_COMMITMENT &&
662 idx = oneTxIn.prevout.n;
663 ::FromVector(p.vData[0], ch);
668 if (idx == -1 || ch.hash.IsNull())
670 std::string specificMsg = "Invalid identity commitment in tx: " + tx.GetHash().GetHex();
671 return state.Error(specificMsg);
674 // are we spending a matching name commitment?
675 if (ch.hash != newName.GetCommitment().hash)
677 return state.Error("Mismatched identity commitment");
683 // with the thorough check for an identity reservation, the only thing we need to check is that either 1) this transaction includes an identity reservation output or 2)
684 // this transaction spends a prior identity transaction that does not create a clearly invalid mutation between the two
685 bool PrecheckIdentityPrimary(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
687 AssertLockHeld(cs_main);
689 bool validReservation = false;
690 bool validIdentity = false;
692 CNameReservation nameRes;
694 COptCCParams p, identityP;
696 uint32_t networkVersion = CConstVerusSolutionVector::GetVersionByHeight(height);
697 bool isPBaaS = networkVersion >= CActivationHeight::ACTIVATE_PBAAS;
699 for (int i = 0; i < tx.vout.size(); i++)
701 CIdentity checkIdentity;
702 auto &output = tx.vout[i];
703 if (output.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= COptCCParams::VERSION_V3 && p.evalCode == EVAL_IDENTITY_RESERVATION && p.vData.size() > 1 && (nameRes = CNameReservation(p.vData[0])).IsValid())
705 // twice through makes it invalid
706 if (validReservation)
708 return state.Error("Invalid multiple identity reservations on one transaction");
710 validReservation = true;
712 else if (p.IsValid() && p.version >= COptCCParams::VERSION_V3 && p.evalCode == EVAL_IDENTITY_PRIMARY && p.vData.size() > 1 && (checkIdentity = CIdentity(p.vData[0])).IsValid())
714 // twice through makes it invalid
715 if (!(isPBaaS && height == 1) && validIdentity)
717 return state.Error("Invalid multiple identity definitions on one transaction");
722 identity = checkIdentity;
724 validIdentity = true;
730 return state.Error("Invalid identity definition");
733 CIdentityID idID = identity.GetID();
736 bool advancedIdentity = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
737 if (advancedIdentity)
740 if (identity.nVersion < identity.VERSION_PBAAS)
742 return state.Error("Inadequate identity version for post-PBaaS activation");
744 if (p.vData.size() < 3 ||
745 !(master = COptCCParams(p.vData.back())).IsValid() ||
746 master.evalCode != 0 ||
749 return state.Error("Invalid identity output destinations and/or configuration");
752 // we need to have at least 2 of 3 authority spend conditions mandatory at a top level for any primary ID output
753 bool revocation = false, recovery = false;
754 bool primaryValid = false, revocationValid = false, recoveryValid = false;
755 for (auto dest : p.vKeys)
757 if (dest.which() == COptCCParams::ADDRTYPE_ID && (idID == GetDestinationID(dest)) && dest.which() == COptCCParams::ADDRTYPE_ID)
764 std::string errorOut = "Primary identity output condition of \"" + identity.name + "\" is not spendable by self";
765 return state.Error(errorOut.c_str());
767 for (int i = 1; i < p.vData.size() - 1; i++)
769 COptCCParams oneP(p.vData[i]);
770 // must be valid and composable
771 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
773 std::string errorOut = "Invalid output condition from identity: \"" + identity.name + "\"";
774 return state.Error(errorOut.c_str());
777 if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
783 if (oneP.vKeys.size() == 1 &&
786 oneP.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
787 identity.revocationAuthority == GetDestinationID(oneP.vKeys[0]))
789 revocationValid = true;
794 revocationValid = false;
797 else if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
802 if (oneP.vKeys.size() == 1 &&
805 oneP.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
806 identity.recoveryAuthority == GetDestinationID(oneP.vKeys[0]))
808 recoveryValid = true;
813 recoveryValid = false;
818 // we need separate spend conditions for both revoke and recover in all cases
819 bool isRevoked = identity.IsRevoked();
820 if ((!isRevoked && !revocationValid) || (isRevoked && !recoveryValid))
822 std::string errorOut = "Primary identity output \"" + identity.name + "\" must be spendable by revocation and recovery authorities";
823 return state.Error(errorOut.c_str());
828 if (identity.nVersion >= identity.VERSION_PBAAS)
830 return state.Error("Invalid identity version before PBaaS activation");
833 // ensure that we have all required spend conditions for primary, revocation, and recovery
834 // if there are additional spend conditions, their addition or removal is checked for validity
835 // depending on which of the mandatory spend conditions is authorized.
837 if (p.vData.size() < 4 || !(master = COptCCParams(p.vData.back())).IsValid() || master.evalCode != 0 || master.m != 1)
839 // we need to have 3 authority spend conditions mandatory at a top level for any primary ID output
840 bool primary = false, revocation = false, recovery = false;
842 for (auto dest : p.vKeys)
844 if (dest.which() == COptCCParams::ADDRTYPE_ID && (idID == GetDestinationID(dest)))
851 std::string errorOut = "Primary identity output condition of \"" + identity.name + "\" is not spendable by self";
852 return state.Error(errorOut.c_str());
854 for (int i = 1; i < p.vData.size() - 1; i++)
856 COptCCParams oneP(p.vData[i]);
857 // must be valid and composable
858 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
860 std::string errorOut = "Invalid output condition from identity: \"" + identity.name + "\"";
861 return state.Error(errorOut.c_str());
864 if (oneP.evalCode == EVAL_IDENTITY_REVOKE || oneP.evalCode == EVAL_IDENTITY_RECOVER)
866 for (auto dest : oneP.vKeys)
868 if (dest.which() == COptCCParams::ADDRTYPE_ID)
870 if (oneP.evalCode == EVAL_IDENTITY_REVOKE && (identity.revocationAuthority == GetDestinationID(dest)))
874 else if (oneP.evalCode == EVAL_IDENTITY_RECOVER && (identity.recoveryAuthority == GetDestinationID(dest)))
883 // we need separate spend conditions for both revoke and recover in all cases
884 if (!revocation || !recovery)
886 std::string errorOut = "Primary identity output \"" + identity.name + "\" must be spendable by revocation and recovery authorities";
887 return state.Error(errorOut.c_str());
892 extern uint160 VERUS_CHAINID;
893 extern std::string VERUS_CHAINNAME;
895 // compare commitment without regard to case or other textual transformations that are irrelevant to matching
896 uint160 parentChain = ConnectedChains.ThisChain().GetID();
897 if (isPBaaS && identity.GetID() == ASSETCHAINS_CHAINID && IsVerusActive())
899 parentChain.SetNull();
901 if (validReservation && identity.GetID(nameRes.name, parentChain) == identity.GetID())
906 // if we made it to here without an early, positive exit, we must determine that we are spending a matching identity, and if so, all is fine so far
910 for (auto &input : tx.vin)
912 // first time through may be null
913 if ((!input.prevout.hash.IsNull() && input.prevout.hash == inTx.GetHash()) || myGetTransaction(input.prevout.hash, inTx, blkHash))
915 if (inTx.vout[input.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_PRIMARY && p.vData.size() > 1 && (identity = CIdentity(p.vData[0])).IsValid())
922 // TODO: HARDENING at block one, a new PBaaS chain can mint IDs
923 // ensure they are valid as per the launch parameters
924 if (isPBaaS && height == 1)
929 return state.Error("Invalid primary identity - does not include identity reservation or spend matching identity");
932 CIdentity GetOldIdentity(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx=nullptr, uint32_t *pHeight=nullptr);
933 CIdentity GetOldIdentity(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx, uint32_t *pHeight)
935 CTransaction _sourceTx;
936 CTransaction &sourceTx(pSourceTx ? *pSourceTx : _sourceTx);
938 // if not fulfilled, ensure that no part of the primary identity is modified
939 CIdentity oldIdentity;
941 if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
945 auto bIt = mapBlockIndex.find(blkHash);
946 if (bIt == mapBlockIndex.end() || !bIt->second)
948 *pHeight = chainActive.Height();
952 *pHeight = bIt->second->GetHeight();
956 if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
958 p.evalCode == EVAL_IDENTITY_PRIMARY &&
959 p.version >= COptCCParams::VERSION_V3 &&
962 oldIdentity = CIdentity(p.vData[0]);
968 bool ValidateIdentityPrimary(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
970 CTransaction sourceTx;
971 CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
973 if (!oldIdentity.IsValid())
975 return eval->Error("Spending invalid identity");
979 CIdentity newIdentity(spendingTx, &idIndex);
980 if (!newIdentity.IsValid())
982 return eval->Error("Attempting to define invalid identity");
985 if (!chainActive.LastTip())
987 return eval->Error("unable to find chain tip");
989 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
991 if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
993 LogPrintf("Invalid identity modification %s\n", spendingTx.GetHash().GetHex().c_str());
994 return eval->Error("Invalid identity modification");
997 // if not fullfilled and not revoked, we are responsible for rejecting any modification of
998 // data under primary authority control
999 if (!fulfilled && !oldIdentity.IsRevoked())
1001 if (oldIdentity.IsPrimaryMutation(newIdentity, height))
1003 return eval->Error("Unauthorized identity modification");
1005 // make sure that the primary spend conditions are not modified
1007 sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
1008 spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
1010 if (q.evalCode != EVAL_IDENTITY_PRIMARY ||
1011 p.version > q.version ||
1016 return eval->Error("Unauthorized modification of identity primary spend condition");
1023 bool ValidateIdentityRevoke(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1025 CTransaction sourceTx;
1026 CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
1027 if (!oldIdentity.IsValid())
1029 return eval->Error("Invalid source identity");
1033 CIdentity newIdentity(spendingTx, &idIndex);
1034 if (!newIdentity.IsValid())
1036 return eval->Error("Attempting to replace identity with one that is invalid");
1039 if (!chainActive.LastTip())
1041 return eval->Error("unable to find chain tip");
1043 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1045 if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
1047 return eval->Error("Invalid identity modification");
1050 if (oldIdentity.IsRevocation(newIdentity) && oldIdentity.recoveryAuthority == oldIdentity.GetID())
1052 return eval->Error("Cannot revoke an identity with self as the recovery authority");
1055 // make sure that spend conditions are valid and revocation spend conditions are not modified
1058 spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
1060 if (q.evalCode != EVAL_IDENTITY_PRIMARY)
1062 return eval->Error("Invalid identity output in spending transaction");
1065 bool advanced = newIdentity.nVersion >= newIdentity.VERSION_PBAAS;
1069 // if not fulfilled, neither recovery data nor its spend condition may be modified
1070 if (!fulfilled && !oldIdentity.IsRevoked())
1072 if (oldIdentity.IsRevocation(newIdentity) || oldIdentity.IsRevocationMutation(newIdentity, height))
1074 return eval->Error("Unauthorized modification of revocation information");
1077 // aside from that, validity of spend conditions is done in advanced precheck
1081 COptCCParams oldRevokeP, newRevokeP;
1083 for (int i = 1; i < q.vData.size() - 1; i++)
1085 COptCCParams oneP(q.vData[i]);
1086 // must be valid and composable
1087 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
1089 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\"";
1090 return eval->Error(errorOut.c_str());
1093 if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
1095 if (newRevokeP.IsValid())
1097 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\", more than one revocation condition";
1098 return eval->Error(errorOut.c_str());
1104 if (!newRevokeP.IsValid())
1106 std::string errorOut = "Invalid revocation output condition for identity: \"" + newIdentity.name + "\"";
1107 return eval->Error(errorOut.c_str());
1110 // if not fulfilled, neither revocation data nor its spend condition may be modified
1113 sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
1115 if (oldIdentity.IsRevocation(newIdentity) || oldIdentity.IsRevocationMutation(newIdentity, height))
1117 return eval->Error("Unauthorized modification of revocation information");
1120 for (int i = 1; i < p.vData.size() - 1; i++)
1122 COptCCParams oneP(p.vData[i]);
1123 if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
1129 if (!oldRevokeP.IsValid() ||
1130 !newRevokeP.IsValid() ||
1131 oldRevokeP.version > newRevokeP.version ||
1132 oldRevokeP.m != newRevokeP.m ||
1133 oldRevokeP.n != newRevokeP.n ||
1134 oldRevokeP.vKeys != newRevokeP.vKeys)
1136 return eval->Error("Unauthorized modification of identity revocation spend condition");
1143 bool ValidateIdentityRecover(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1145 CTransaction sourceTx;
1146 CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
1147 if (!oldIdentity.IsValid())
1149 return eval->Error("Invalid source identity");
1153 CIdentity newIdentity(spendingTx, &idIndex);
1154 if (!newIdentity.IsValid())
1156 return eval->Error("Attempting to replace identity with one that is invalid");
1159 if (!chainActive.LastTip())
1161 return eval->Error("unable to find chain tip");
1163 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1165 if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
1167 return eval->Error("Invalid identity modification");
1170 // make sure that spend conditions are valid and revocation spend conditions are not modified
1173 spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
1175 if (q.evalCode != EVAL_IDENTITY_PRIMARY)
1177 return eval->Error("Invalid identity output in spending transaction");
1180 bool advanced = newIdentity.nVersion >= newIdentity.VERSION_PBAAS;
1184 // if not fulfilled, neither recovery data nor its spend condition may be modified
1187 if (oldIdentity.IsRecovery(newIdentity) || oldIdentity.IsRecoveryMutation(newIdentity, height))
1189 return eval->Error("Unauthorized modification of recovery information");
1192 // if revoked, only fulfilled recovery condition allows any mutation
1193 if (oldIdentity.IsRevoked() &&
1194 (oldIdentity.IsPrimaryMutation(newIdentity, height) ||
1195 oldIdentity.IsRevocationMutation(newIdentity, height)))
1197 return eval->Error("Unauthorized modification of revoked identity without recovery authority");
1200 // aside from that, validity of spend conditions is done in advanced precheck
1204 COptCCParams oldRecoverP, newRecoverP;
1206 for (int i = 1; i < q.vData.size() - 1; i++)
1208 COptCCParams oneP(q.vData[i]);
1209 // must be valid and composable
1210 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
1212 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\"";
1213 return eval->Error(errorOut.c_str());
1216 if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
1218 if (newRecoverP.IsValid())
1220 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\", more than one recovery condition";
1221 return eval->Error(errorOut.c_str());
1227 if (!newRecoverP.IsValid())
1229 std::string errorOut = "Invalid recovery output condition for identity: \"" + newIdentity.name + "\"";
1230 return eval->Error(errorOut.c_str());
1233 // if not fulfilled, neither recovery data nor its spend condition may be modified
1236 // if revoked, only fulfilled recovery condition allows primary mutation
1237 if (oldIdentity.IsRevoked() && (oldIdentity.IsPrimaryMutation(newIdentity, height)))
1239 return eval->Error("Unauthorized modification of revoked identity without recovery authority");
1242 if (oldIdentity.IsRecovery(newIdentity) || oldIdentity.IsRecoveryMutation(newIdentity, height))
1244 return eval->Error("Unauthorized modification of recovery information");
1247 sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
1249 for (int i = 1; i < p.vData.size() - 1; i++)
1251 COptCCParams oneP(p.vData[i]);
1252 if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
1258 if (!oldRecoverP.IsValid() ||
1259 !newRecoverP.IsValid() ||
1260 oldRecoverP.version > newRecoverP.version ||
1261 oldRecoverP.m != newRecoverP.m ||
1262 oldRecoverP.n != newRecoverP.n ||
1263 oldRecoverP.vKeys != newRecoverP.vKeys)
1265 return eval->Error("Unauthorized modification of identity recovery spend condition");
1271 bool ValidateIdentityCommitment(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1273 // if not fulfilled, fail
1276 return eval->Error("missing required signature to spend");
1279 if (!chainActive.LastTip())
1281 return eval->Error("unable to find chain tip");
1283 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1286 CNameReservation reservation;
1287 CTransaction sourceTx;
1291 if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
1294 if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
1296 p.evalCode == EVAL_IDENTITY_COMMITMENT &&
1297 p.version >= COptCCParams::VERSION_V3 &&
1298 p.vData.size() > 1 &&
1301 ch = CCommitmentHash(p.vData[0]);
1305 return eval->Error("Invalid source commitment output");
1310 for (i = 0; i < spendingTx.vout.size(); i++)
1312 auto &output = spendingTx.vout[i];
1313 if (output.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_RESERVATION && p.vData.size() > 1)
1315 if (reservation.IsValid())
1317 return eval->Error("Invalid identity reservation output spend");
1321 reservation = CNameReservation(p.vData[0]);
1322 if (!reservation.IsValid() || reservation.GetCommitment().hash != ch.hash)
1324 return eval->Error("Identity reservation output spend does not match commitment");
1331 if (outputNum != -1)
1333 // can only be spent by a matching name reservation if validated
1334 // if there is no matching name reservation, it can be spent just by a valid signature
1335 CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
1336 return ValidateSpendingIdentityReservation(spendingTx, outputNum, eval->state, height, thisChain);
1341 printf("%s: error getting transaction %s to spend\n", __func__, spendingTx.vin[nIn].prevout.hash.GetHex().c_str());
1347 bool ValidateIdentityReservation(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1349 // identity reservations are unspendable
1350 return eval->Error("Identity reservations are unspendable");
1353 // quantum key outputs can be spent without restriction
1354 bool ValidateQuantumKeyOut(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1359 bool IsQuantumKeyOutInput(const CScript &scriptSig)
1364 bool PrecheckQuantumKeyOut(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
1370 bool IsIdentityInput(const CScript &scriptSig)
1375 bool ValidateFinalizeExport(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
1380 bool IsFinalizeExportInput(const CScript &scriptSig)
1385 bool FinalizeExportContextualPreCheck(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)