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 if (boost::to_lower_copy(name) == VERUS_CHAINNAME)
46 ret.push_back(Pair("parent", ""));
50 ret.push_back(Pair("parent", EncodeDestination(CIdentityID(ConnectedChains.ThisChain().GetID()))));
52 ret.push_back(Pair("nameid", EncodeDestination(DecodeDestination(name + "@"))));
56 ret.push_back(Pair("parent", EncodeDestination(CIdentityID(ConnectedChains.ThisChain().GetID()))));
57 ret.push_back(Pair("nameid", EncodeDestination(DecodeDestination(name + "." + ConnectedChains.ThisChain().name + "@"))));
63 CIdentity::CIdentity(const CTransaction &tx, int *voutNum)
65 std::set<uint160> ids;
69 nVersion = PBAAS_VERSION_INVALID;
70 for (int i = 0; i < tx.vout.size(); i++)
72 CIdentity foundIdentity(tx.vout[i].scriptPubKey);
73 if (foundIdentity.IsValid() && !found)
75 *this = foundIdentity;
79 else if (foundIdentity.IsValid())
84 if (voutNum && IsValid())
90 CIdentity CIdentity::LookupIdentity(const CIdentityID &nameID, uint32_t height, uint32_t *pHeightOut, CTxIn *pIdTxIn)
96 uint32_t heightOut = 0;
100 pHeightOut = &heightOut;
112 CTxIn &idTxIn = *pIdTxIn;
114 std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs, unspentNewIDX;
116 uint160 keyID(CCrossChainRPCData::GetConditionID(nameID, EVAL_IDENTITY_PRIMARY));
118 if (GetAddressUnspent(keyID, CScript::P2IDX, unspentNewIDX) && GetAddressUnspent(keyID, CScript::P2PKH, unspentOutputs))
120 // combine searches into 1 vector
121 unspentOutputs.insert(unspentOutputs.begin(), unspentNewIDX.begin(), unspentNewIDX.end());
122 CCoinsViewCache view(pcoinsTip);
124 for (auto it = unspentOutputs.begin(); !ret.IsValid() && it != unspentOutputs.end(); it++)
128 if (view.GetCoins(it->first.txhash, coins))
130 if (coins.IsAvailable(it->first.index))
132 // check the mempool for spent/modified
133 CSpentIndexKey key(it->first.txhash, it->first.index);
134 CSpentIndexValue value;
137 if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
139 p.evalCode == EVAL_IDENTITY_PRIMARY &&
140 (ret = CIdentity(coins.vout[it->first.index].scriptPubKey)).IsValid())
142 if (ret.GetID() == nameID)
144 idTxIn = CTxIn(it->first.txhash, it->first.index);
145 *pHeightOut = it->second.blockHeight;
149 // got an identity masquerading as another, clear it
157 if (height != 0 && (*pHeightOut > height || (height == 1 && *pHeightOut == height)))
161 // if we must check up to a specific height that is less than the latest height, do so
162 std::vector<CAddressIndexDbEntry> addressIndex, addressIndex2;
164 if (GetAddressIndex(keyID, CScript::P2PKH, addressIndex, 0, height) &&
165 GetAddressIndex(keyID, CScript::P2IDX, addressIndex2, 0, height) &&
166 (addressIndex.size() || addressIndex2.size()))
168 if (addressIndex2.size())
170 addressIndex.insert(addressIndex.begin(), addressIndex2.begin(), addressIndex2.end());
173 // look from last backward to find the first valid ID
174 for (int i = addressIndex.size() - 1; i >= 0; i--)
176 if (addressIndex[i].first.blockHeight < *pHeightOut)
184 if (!addressIndex[i].first.spending &&
185 addressIndex[i].first.txindex > txIndex && // always select the latest in a block, if there can be more than one
186 myGetTransaction(addressIndex[i].first.txhash, idTx, blkHash) &&
187 idTx.vout[addressIndex[i].first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
189 p.evalCode == EVAL_IDENTITY_PRIMARY &&
190 (ret = CIdentity(idTx.vout[addressIndex[i].first.index].scriptPubKey)).IsValid())
192 idTxIn = CTxIn(addressIndex[i].first.txhash, addressIndex[i].first.index);
193 *pHeightOut = addressIndex[i].first.blockHeight;
194 txIndex = addressIndex[i].first.txindex;
200 // not found at that height
209 CIdentity CIdentity::LookupIdentity(const std::string &name, uint32_t height, uint32_t *pHeightOut, CTxIn *idTxIn)
211 return LookupIdentity(GetID(name), height, pHeightOut, idTxIn);
214 CIdentity CIdentity::LookupFirstIdentity(const CIdentityID &idID, uint32_t *pHeightOut, CTxIn *pIdTxIn, CTransaction *pidTx)
218 uint32_t heightOut = 0;
222 pHeightOut = &heightOut;
234 CTxIn &idTxIn = *pIdTxIn;
236 std::vector<CAddressUnspentDbEntry> unspentOutputs, unspentNewIDX;
238 CKeyID keyID(CCrossChainRPCData::GetConditionID(idID, EVAL_IDENTITY_RESERVATION));
240 if (GetAddressUnspent(keyID, CScript::P2IDX, unspentNewIDX) && GetAddressUnspent(keyID, CScript::P2PKH, unspentOutputs))
242 if (!unspentOutputs.size() && !unspentNewIDX.size())
245 // if we are a PBaaS chain and it is in block 1, get it from there
246 std::vector<CAddressIndexDbEntry> checkImported;
248 CTransaction blockOneCB;
250 CIdentity firstIdentity;
251 uint160 identityIdx(CCrossChainRPCData::GetConditionID(idID, EVAL_IDENTITY_PRIMARY));
252 if (!IsVerusActive() &&
253 GetAddressIndex(identityIdx, CScript::P2IDX, checkImported, 1, 1) &&
254 checkImported.size() &&
255 myGetTransaction(checkImported[0].first.txhash, blockOneCB, blockHash) &&
256 blockOneCB.vout.size() > checkImported[0].first.index &&
257 blockOneCB.vout[checkImported[0].first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
259 p.evalCode == EVAL_IDENTITY_PRIMARY &&
261 (firstIdentity = CIdentity(p.vData[0])).IsValid())
269 *pIdTxIn = CTxIn(checkImported[0].first.txhash, checkImported[0].first.index);
275 return firstIdentity;
279 // combine searches into 1 vector
280 unspentOutputs.insert(unspentOutputs.begin(), unspentNewIDX.begin(), unspentNewIDX.end());
281 CCoinsViewCache view(pcoinsTip);
283 for (auto it = unspentOutputs.begin(); !ret.IsValid() && it != unspentOutputs.end(); it++)
287 if (view.GetCoins(it->first.txhash, coins))
289 if (coins.IsAvailable(it->first.index))
292 if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
294 p.evalCode == EVAL_IDENTITY_RESERVATION)
298 if (myGetTransaction(it->first.txhash, idTx, blkHash) && (ret = CIdentity(idTx)).IsValid() && ret.GetID() == idID)
301 for (i = 0; i < idTx.vout.size(); i++)
304 if (idTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3 && p.evalCode == EVAL_IDENTITY_PRIMARY)
310 if (i < idTx.vout.size())
316 idTxIn = CTxIn(it->first.txhash, i);
317 *pHeightOut = it->second.blockHeight;
328 // this enables earliest rejection of invalid CC transactions
329 bool ValidateSpendingIdentityReservation(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height, CCurrencyDefinition issuingChain)
331 // CHECK #1 - there is only one reservation output, and there is also one identity output that matches the reservation.
332 // the identity output must come first and have from 0 to 3 referral outputs between it and the reservation.
333 int numReferrers = 0;
334 int identityCount = 0;
335 int reservationCount = 0;
336 CIdentity newIdentity;
337 CNameReservation newName;
338 std::vector<CTxDestination> referrers;
341 bool isPBaaS = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
343 for (auto &txout : tx.vout)
346 if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
348 if (p.evalCode == EVAL_IDENTITY_PRIMARY)
350 if (identityCount++ || p.vData.size() < 2)
355 newIdentity = CIdentity(p.vData[0]);
357 else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
359 if (reservationCount++ || p.vData.size() < 2)
364 newName = CNameReservation(p.vData[0]);
366 else if (identityCount && !reservationCount)
368 if (!issuingChain.IDReferralLevels() ||
369 p.vKeys.size() < 1 ||
370 referrers.size() > (issuingChain.IDReferralLevels() - 1) ||
374 txout.nValue < issuingChain.IDReferralAmount())
379 referrers.push_back(p.vKeys[0]);
381 else if (identityCount != reservationCount)
389 // we can close a commitment UTXO without an identity
390 if (valid && !identityCount)
392 return state.Error("Transaction may not have an identity reservation without a matching identity");
396 return state.Error("Improperly formed identity definition transaction");
399 std::vector<CTxDestination> dests;
402 if (ExtractDestinations(tx.vout[outNum].scriptPubKey, outType, dests, minSigs))
404 uint160 thisID = newIdentity.GetID();
405 for (auto &dest : dests)
408 if (dest.which() == COptCCParams::ADDRTYPE_ID && (oneDestID = GetDestinationID(dest)) != thisID && !CIdentity::LookupIdentity(CIdentityID(oneDestID)).IsValid())
410 return state.Error("Destination includes invalid identity");
415 // CHECK #2 - must be rooted in this chain
416 if (newIdentity.parent != ConnectedChains.ThisChain().GetID() &&
417 !(isPBaaS && newIdentity.GetID() == ASSETCHAINS_CHAINID && IsVerusActive()))
419 return state.Error("Identity parent of new identity must be current chain");
422 // CHECK #3 - if dupID is valid, we need to be spending it to recover. redefinition is invalid
424 uint32_t priorHeightOut;
425 CIdentity dupID = newIdentity.LookupIdentity(newIdentity.GetID(), height - 1, &priorHeightOut, &idTxIn);
427 // CHECK #3a - if dupID is invalid, ensure we spend a matching name commitment
430 return state.Error("Identity already exists");
433 int commitmentHeight = 0;
436 CCoinsViewCache view(&dummy);
440 CCoinsViewMemPool viewMemPool(pcoinsTip, mempool);
441 view.SetBackend(viewMemPool);
446 CAmount nValueIn = 0;
448 // from here, we must spend a matching name commitment
449 std::map<uint256, const CCoins *> txMap;
450 for (auto &oneTxIn : tx.vin)
452 coins = txMap[oneTxIn.prevout.hash];
453 if (!coins && !(coins = view.AccessCoins(oneTxIn.prevout.hash)))
455 //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());
456 //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());
457 return state.Error("Cannot access input");
459 txMap[oneTxIn.prevout.hash] = coins;
461 if (oneTxIn.prevout.n >= coins->vout.size())
463 //extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
465 //TxToJSON(tx, uint256(), uniTx);
466 //printf("%s\n", uniTx.write(1, 2).c_str());
467 return state.Error("Input index out of range");
472 coins->vout[oneTxIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
474 p.evalCode == EVAL_IDENTITY_COMMITMENT &&
477 idx = oneTxIn.prevout.n;
478 ::FromVector(p.vData[0], ch);
479 commitmentHeight = coins->nHeight;
480 // this needs to already be in a prior block, or we can't consider it valid
481 if (!commitmentHeight || commitmentHeight == -1)
483 return state.Error("ID commitment was not already in blockchain");
489 if (idx == -1 || ch.hash.IsNull())
491 std::string specificMsg = "Invalid identity commitment in tx: " + tx.GetHash().GetHex();
492 return state.Error(specificMsg);
495 // are we spending a matching name commitment?
496 if (ch.hash != newName.GetCommitment().hash)
498 return state.Error("Mismatched identity commitment");
501 if (!newName.referral.IsNull() && issuingChain.IDReferralLevels() && !(CIdentity::LookupIdentity(newName.referral, commitmentHeight).IsValid()))
503 // invalid referral identity
504 return state.Error("Invalid referral identity specified");
507 CReserveTransactionDescriptor rtxd(tx, view, height);
509 // 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,
510 // all further checks only if referrals are enabled and there is a referrer
511 if (!issuingChain.IDReferralLevels() || newName.referral.IsNull())
513 // make sure the fees of this transaction are full price for an identity, all further checks only if there is a referrer
514 if (rtxd.NativeFees() < issuingChain.IDFullRegistrationAmount())
516 return state.Error("Invalid identity registration - insufficient fee");
521 // CHECK #5 - ensure that the first referring output goes to the referring identity followed by up
522 // to two identities that come from the original definition transaction of the referring identity. account for all outputs between
523 // identity out and reservation out and ensure that they are correct and pay 20% of the price of an identity
524 uint32_t heightOut = 0;
525 CTransaction referralTx;
527 CIdentity firstReferralIdentity = CIdentity::LookupFirstIdentity(newName.referral, &heightOut, &idTxIn, &referralTx);
529 // referrer must be mined in when this transaction is put into the mem pool
530 if (heightOut >= height || !firstReferralIdentity.IsValid() || firstReferralIdentity.parent != ASSETCHAINS_CHAINID)
532 //printf("%s: cannot find first instance of: %s\n", __func__, EncodeDestination(CIdentityID(newName.referral)).c_str());
533 return state.Error("Invalid identity registration referral");
536 bool isReferral = false;
537 std::vector<CTxDestination> checkReferrers = std::vector<CTxDestination>({newName.referral});
540 for (auto &txout : referralTx.vout)
543 if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
545 if (p.evalCode == EVAL_IDENTITY_PRIMARY)
549 else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
555 if (p.vKeys.size() == 0 || p.vKeys[0].which() != COptCCParams::ADDRTYPE_ID)
558 return state.Error("Invalid identity registration referral outputs");
562 checkReferrers.push_back(p.vKeys[0]);
563 if (checkReferrers.size() == issuingChain.IDReferralLevels())
573 if (referrers.size() != checkReferrers.size())
575 return state.Error("Invalid identity registration - incorrect referral payments");
578 // make sure all paid referrers are correct
579 for (int i = 0; i < referrers.size(); i++)
581 if (referrers[i] != checkReferrers[i])
583 return state.Error("Invalid identity registration - incorrect referral payments");
587 // CHECK #6 - ensure that the transaction pays the correct mining and refferal fees
588 if (rtxd.NativeFees() < (issuingChain.IDReferredRegistrationAmount() - (referrers.size() * issuingChain.IDReferralAmount())))
590 return state.Error("Invalid identity registration - insufficient fee");
596 bool PrecheckIdentityReservation(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
598 CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
600 int numReferrers = 0;
601 int identityCount = 0;
602 int reservationCount = 0;
603 CIdentity newIdentity;
604 CNameReservation newName;
605 std::vector<CTxDestination> referrers;
608 AssertLockHeld(cs_main);
610 for (auto &txout : tx.vout)
613 if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
615 if (p.evalCode == EVAL_IDENTITY_PRIMARY)
617 if (identityCount++ || p.vData.size() < 2)
622 newIdentity = CIdentity(p.vData[0]);
624 else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
626 if (reservationCount++ || p.vData.size() < 2)
631 newName = CNameReservation(p.vData[0]);
633 else if (identityCount && !reservationCount)
635 if (!thisChain.IDReferralLevels() ||
636 p.vKeys.size() < 1 ||
637 referrers.size() > (thisChain.IDReferralLevels() - 1) ||
641 txout.nValue < thisChain.IDReferralAmount())
646 referrers.push_back(p.vKeys[0]);
648 else if (identityCount != reservationCount)
656 // we can close a commitment UTXO without an identity
657 if (valid && !identityCount)
659 return state.Error("Transaction may not have an identity reservation without a matching identity");
663 return state.Error("Improperly formed identity definition transaction");
666 int commitmentHeight = 0;
668 LOCK2(cs_main, mempool.cs);
673 CAmount nValueIn = 0;
675 LOCK2(cs_main, mempool.cs);
677 // from here, we must spend a matching name commitment
678 std::map<uint256, CTransaction> txMap;
680 for (auto &oneTxIn : tx.vin)
682 CTransaction sourceTx = txMap[oneTxIn.prevout.hash];
683 if (sourceTx.nVersion <= sourceTx.SPROUT_MIN_CURRENT_VERSION && !myGetTransaction(oneTxIn.prevout.hash, sourceTx, hashBlk))
685 //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());
686 //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());
687 return state.Error("Cannot access input");
689 txMap[oneTxIn.prevout.hash] = sourceTx;
691 if (oneTxIn.prevout.n >= sourceTx.vout.size())
693 //extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
695 //TxToJSON(tx, uint256(), uniTx);
696 //printf("%s\n", uniTx.write(1, 2).c_str());
697 return state.Error("Input index out of range");
702 sourceTx.vout[oneTxIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
704 p.evalCode == EVAL_IDENTITY_COMMITMENT &&
707 idx = oneTxIn.prevout.n;
708 ::FromVector(p.vData[0], ch);
713 if (idx == -1 || ch.hash.IsNull())
715 std::string specificMsg = "Invalid identity commitment in tx: " + tx.GetHash().GetHex();
716 return state.Error(specificMsg);
719 // are we spending a matching name commitment?
720 if (ch.hash != newName.GetCommitment().hash)
722 return state.Error("Mismatched identity commitment");
728 // 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)
729 // this transaction spends a prior identity transaction that does not create a clearly invalid mutation between the two
730 bool PrecheckIdentityPrimary(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
732 AssertLockHeld(cs_main);
734 bool validReservation = false;
735 bool validIdentity = false;
737 CNameReservation nameRes;
739 COptCCParams p, identityP;
741 uint32_t networkVersion = CConstVerusSolutionVector::GetVersionByHeight(height);
742 bool isPBaaS = networkVersion >= CActivationHeight::ACTIVATE_PBAAS;
744 for (int i = 0; i < tx.vout.size(); i++)
746 CIdentity checkIdentity;
747 auto &output = tx.vout[i];
748 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())
750 // twice through makes it invalid
751 if (validReservation)
753 return state.Error("Invalid multiple identity reservations on one transaction");
755 validReservation = true;
757 else if (p.IsValid() && p.version >= COptCCParams::VERSION_V3 && p.evalCode == EVAL_IDENTITY_PRIMARY && p.vData.size() > 1 && (checkIdentity = CIdentity(p.vData[0])).IsValid())
759 // twice through makes it invalid
760 if (!(isPBaaS && height == 1) && validIdentity)
762 return state.Error("Invalid multiple identity definitions on one transaction");
767 identity = checkIdentity;
769 validIdentity = true;
775 return state.Error("Invalid identity definition");
778 CIdentityID idID = identity.GetID();
781 bool advancedIdentity = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
782 if (advancedIdentity)
785 if (identity.nVersion < identity.VERSION_PBAAS)
787 return state.Error("Inadequate identity version for post-PBaaS activation");
789 if (p.vData.size() < 3 ||
790 !(master = COptCCParams(p.vData.back())).IsValid() ||
791 master.evalCode != 0 ||
794 return state.Error("Invalid identity output destinations and/or configuration");
797 // we need to have at least 2 of 3 authority spend conditions mandatory at a top level for any primary ID output
798 bool revocation = false, recovery = false;
799 bool primaryValid = false, revocationValid = false, recoveryValid = false;
800 for (auto dest : p.vKeys)
802 if (dest.which() == COptCCParams::ADDRTYPE_ID && (idID == GetDestinationID(dest)) && dest.which() == COptCCParams::ADDRTYPE_ID)
809 std::string errorOut = "Primary identity output condition of \"" + identity.name + "\" is not spendable by self";
810 return state.Error(errorOut.c_str());
812 for (int i = 1; i < p.vData.size() - 1; i++)
814 COptCCParams oneP(p.vData[i]);
815 // must be valid and composable
816 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
818 std::string errorOut = "Invalid output condition from identity: \"" + identity.name + "\"";
819 return state.Error(errorOut.c_str());
822 if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
828 if (oneP.vKeys.size() == 1 &&
831 oneP.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
832 identity.revocationAuthority == GetDestinationID(oneP.vKeys[0]))
834 revocationValid = true;
839 revocationValid = false;
842 else if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
847 if (oneP.vKeys.size() == 1 &&
850 oneP.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
851 identity.recoveryAuthority == GetDestinationID(oneP.vKeys[0]))
853 recoveryValid = true;
858 recoveryValid = false;
863 // we need separate spend conditions for both revoke and recover in all cases
864 bool isRevoked = identity.IsRevoked();
865 if ((!isRevoked && !revocationValid) || (isRevoked && !recoveryValid))
867 std::string errorOut = "Primary identity output \"" + identity.name + "\" must be spendable by revocation and recovery authorities";
868 return state.Error(errorOut.c_str());
873 if (identity.nVersion >= identity.VERSION_PBAAS)
875 return state.Error("Invalid identity version before PBaaS activation");
878 // ensure that we have all required spend conditions for primary, revocation, and recovery
879 // if there are additional spend conditions, their addition or removal is checked for validity
880 // depending on which of the mandatory spend conditions is authorized.
882 if (p.vData.size() < 4 || !(master = COptCCParams(p.vData.back())).IsValid() || master.evalCode != 0 || master.m != 1)
884 // we need to have 3 authority spend conditions mandatory at a top level for any primary ID output
885 bool primary = false, revocation = false, recovery = false;
887 for (auto dest : p.vKeys)
889 if (dest.which() == COptCCParams::ADDRTYPE_ID && (idID == GetDestinationID(dest)))
896 std::string errorOut = "Primary identity output condition of \"" + identity.name + "\" is not spendable by self";
897 return state.Error(errorOut.c_str());
899 for (int i = 1; i < p.vData.size() - 1; i++)
901 COptCCParams oneP(p.vData[i]);
902 // must be valid and composable
903 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
905 std::string errorOut = "Invalid output condition from identity: \"" + identity.name + "\"";
906 return state.Error(errorOut.c_str());
909 if (oneP.evalCode == EVAL_IDENTITY_REVOKE || oneP.evalCode == EVAL_IDENTITY_RECOVER)
911 for (auto dest : oneP.vKeys)
913 if (dest.which() == COptCCParams::ADDRTYPE_ID)
915 if (oneP.evalCode == EVAL_IDENTITY_REVOKE && (identity.revocationAuthority == GetDestinationID(dest)))
919 else if (oneP.evalCode == EVAL_IDENTITY_RECOVER && (identity.recoveryAuthority == GetDestinationID(dest)))
928 // we need separate spend conditions for both revoke and recover in all cases
929 if (!revocation || !recovery)
931 std::string errorOut = "Primary identity output \"" + identity.name + "\" must be spendable by revocation and recovery authorities";
932 return state.Error(errorOut.c_str());
937 extern uint160 VERUS_CHAINID;
938 extern std::string VERUS_CHAINNAME;
940 // compare commitment without regard to case or other textual transformations that are irrelevant to matching
941 uint160 parentChain = ConnectedChains.ThisChain().GetID();
942 if (isPBaaS && identity.GetID() == ASSETCHAINS_CHAINID && IsVerusActive())
944 parentChain.SetNull();
946 if (validReservation && identity.GetID(nameRes.name, parentChain) == identity.GetID())
951 // 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
955 for (auto &input : tx.vin)
957 // first time through may be null
958 if ((!input.prevout.hash.IsNull() && input.prevout.hash == inTx.GetHash()) || myGetTransaction(input.prevout.hash, inTx, blkHash))
960 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())
967 // TODO: HARDENING at block one, a new PBaaS chain can mint IDs
968 // ensure they are valid as per the launch parameters
969 if (isPBaaS && height == 1)
974 return state.Error("Invalid primary identity - does not include identity reservation or spend matching identity");
977 CIdentity GetOldIdentity(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx=nullptr, uint32_t *pHeight=nullptr);
978 CIdentity GetOldIdentity(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx, uint32_t *pHeight)
980 CTransaction _sourceTx;
981 CTransaction &sourceTx(pSourceTx ? *pSourceTx : _sourceTx);
983 // if not fulfilled, ensure that no part of the primary identity is modified
984 CIdentity oldIdentity;
986 if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
990 auto bIt = mapBlockIndex.find(blkHash);
991 if (bIt == mapBlockIndex.end() || !bIt->second)
993 *pHeight = chainActive.Height();
997 *pHeight = bIt->second->GetHeight();
1001 if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
1003 p.evalCode == EVAL_IDENTITY_PRIMARY &&
1004 p.version >= COptCCParams::VERSION_V3 &&
1007 oldIdentity = CIdentity(p.vData[0]);
1013 bool ValidateIdentityPrimary(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1015 CTransaction sourceTx;
1016 CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
1018 if (!oldIdentity.IsValid())
1020 return eval->Error("Spending invalid identity");
1024 CIdentity newIdentity(spendingTx, &idIndex);
1025 if (!newIdentity.IsValid())
1027 return eval->Error("Attempting to define invalid identity");
1030 if (!chainActive.LastTip())
1032 return eval->Error("unable to find chain tip");
1034 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1036 if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
1038 LogPrintf("Invalid identity modification %s\n", spendingTx.GetHash().GetHex().c_str());
1039 return eval->Error("Invalid identity modification");
1042 // if not fullfilled and not revoked, we are responsible for rejecting any modification of
1043 // data under primary authority control
1044 if (!fulfilled && !oldIdentity.IsRevoked())
1046 if (oldIdentity.IsPrimaryMutation(newIdentity, height))
1048 return eval->Error("Unauthorized identity modification");
1050 // make sure that the primary spend conditions are not modified
1052 sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
1053 spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
1055 if (q.evalCode != EVAL_IDENTITY_PRIMARY ||
1056 p.version > q.version ||
1061 return eval->Error("Unauthorized modification of identity primary spend condition");
1068 bool ValidateIdentityRevoke(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1070 CTransaction sourceTx;
1071 CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
1072 if (!oldIdentity.IsValid())
1074 return eval->Error("Invalid source identity");
1078 CIdentity newIdentity(spendingTx, &idIndex);
1079 if (!newIdentity.IsValid())
1081 return eval->Error("Attempting to replace identity with one that is invalid");
1084 if (!chainActive.LastTip())
1086 return eval->Error("unable to find chain tip");
1088 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1090 if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
1092 return eval->Error("Invalid identity modification");
1095 if (oldIdentity.IsRevocation(newIdentity) && oldIdentity.recoveryAuthority == oldIdentity.GetID())
1097 return eval->Error("Cannot revoke an identity with self as the recovery authority");
1100 // make sure that spend conditions are valid and revocation spend conditions are not modified
1103 spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
1105 if (q.evalCode != EVAL_IDENTITY_PRIMARY)
1107 return eval->Error("Invalid identity output in spending transaction");
1110 bool advanced = newIdentity.nVersion >= newIdentity.VERSION_PBAAS;
1114 // if not fulfilled, neither recovery data nor its spend condition may be modified
1115 if (!fulfilled && !oldIdentity.IsRevoked())
1117 if (oldIdentity.IsRevocation(newIdentity) || oldIdentity.IsRevocationMutation(newIdentity, height))
1119 return eval->Error("Unauthorized modification of revocation information");
1122 // aside from that, validity of spend conditions is done in advanced precheck
1126 COptCCParams oldRevokeP, newRevokeP;
1128 for (int i = 1; i < q.vData.size() - 1; i++)
1130 COptCCParams oneP(q.vData[i]);
1131 // must be valid and composable
1132 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
1134 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\"";
1135 return eval->Error(errorOut.c_str());
1138 if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
1140 if (newRevokeP.IsValid())
1142 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\", more than one revocation condition";
1143 return eval->Error(errorOut.c_str());
1149 if (!newRevokeP.IsValid())
1151 std::string errorOut = "Invalid revocation output condition for identity: \"" + newIdentity.name + "\"";
1152 return eval->Error(errorOut.c_str());
1155 // if not fulfilled, neither revocation data nor its spend condition may be modified
1158 sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
1160 if (oldIdentity.IsRevocation(newIdentity) || oldIdentity.IsRevocationMutation(newIdentity, height))
1162 return eval->Error("Unauthorized modification of revocation information");
1165 for (int i = 1; i < p.vData.size() - 1; i++)
1167 COptCCParams oneP(p.vData[i]);
1168 if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
1174 if (!oldRevokeP.IsValid() ||
1175 !newRevokeP.IsValid() ||
1176 oldRevokeP.version > newRevokeP.version ||
1177 oldRevokeP.m != newRevokeP.m ||
1178 oldRevokeP.n != newRevokeP.n ||
1179 oldRevokeP.vKeys != newRevokeP.vKeys)
1181 return eval->Error("Unauthorized modification of identity revocation spend condition");
1188 bool ValidateIdentityRecover(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1190 CTransaction sourceTx;
1191 CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
1192 if (!oldIdentity.IsValid())
1194 return eval->Error("Invalid source identity");
1198 CIdentity newIdentity(spendingTx, &idIndex);
1199 if (!newIdentity.IsValid())
1201 return eval->Error("Attempting to replace identity with one that is invalid");
1204 if (!chainActive.LastTip())
1206 return eval->Error("unable to find chain tip");
1208 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1210 if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
1212 return eval->Error("Invalid identity modification");
1215 // make sure that spend conditions are valid and revocation spend conditions are not modified
1218 spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
1220 if (q.evalCode != EVAL_IDENTITY_PRIMARY)
1222 return eval->Error("Invalid identity output in spending transaction");
1225 bool advanced = newIdentity.nVersion >= newIdentity.VERSION_PBAAS;
1229 // if not fulfilled, neither recovery data nor its spend condition may be modified
1232 if (oldIdentity.IsRecovery(newIdentity) || oldIdentity.IsRecoveryMutation(newIdentity, height))
1234 return eval->Error("Unauthorized modification of recovery information");
1237 // if revoked, only fulfilled recovery condition allows any mutation
1238 if (oldIdentity.IsRevoked() &&
1239 (oldIdentity.IsPrimaryMutation(newIdentity, height) ||
1240 oldIdentity.IsRevocationMutation(newIdentity, height)))
1242 return eval->Error("Unauthorized modification of revoked identity without recovery authority");
1245 // aside from that, validity of spend conditions is done in advanced precheck
1249 COptCCParams oldRecoverP, newRecoverP;
1251 for (int i = 1; i < q.vData.size() - 1; i++)
1253 COptCCParams oneP(q.vData[i]);
1254 // must be valid and composable
1255 if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
1257 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\"";
1258 return eval->Error(errorOut.c_str());
1261 if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
1263 if (newRecoverP.IsValid())
1265 std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\", more than one recovery condition";
1266 return eval->Error(errorOut.c_str());
1272 if (!newRecoverP.IsValid())
1274 std::string errorOut = "Invalid recovery output condition for identity: \"" + newIdentity.name + "\"";
1275 return eval->Error(errorOut.c_str());
1278 // if not fulfilled, neither recovery data nor its spend condition may be modified
1281 // if revoked, only fulfilled recovery condition allows primary mutation
1282 if (oldIdentity.IsRevoked() && (oldIdentity.IsPrimaryMutation(newIdentity, height)))
1284 return eval->Error("Unauthorized modification of revoked identity without recovery authority");
1287 if (oldIdentity.IsRecovery(newIdentity) || oldIdentity.IsRecoveryMutation(newIdentity, height))
1289 return eval->Error("Unauthorized modification of recovery information");
1292 sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
1294 for (int i = 1; i < p.vData.size() - 1; i++)
1296 COptCCParams oneP(p.vData[i]);
1297 if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
1303 if (!oldRecoverP.IsValid() ||
1304 !newRecoverP.IsValid() ||
1305 oldRecoverP.version > newRecoverP.version ||
1306 oldRecoverP.m != newRecoverP.m ||
1307 oldRecoverP.n != newRecoverP.n ||
1308 oldRecoverP.vKeys != newRecoverP.vKeys)
1310 return eval->Error("Unauthorized modification of identity recovery spend condition");
1316 bool ValidateIdentityCommitment(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1318 // if not fulfilled, fail
1321 return eval->Error("missing required signature to spend");
1324 if (!chainActive.LastTip())
1326 return eval->Error("unable to find chain tip");
1328 uint32_t height = chainActive.LastTip()->GetHeight() + 1;
1331 CNameReservation reservation;
1332 CTransaction sourceTx;
1336 if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
1339 if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
1341 p.evalCode == EVAL_IDENTITY_COMMITMENT &&
1342 p.version >= COptCCParams::VERSION_V3 &&
1343 p.vData.size() > 1 &&
1346 ch = CCommitmentHash(p.vData[0]);
1350 return eval->Error("Invalid source commitment output");
1355 for (i = 0; i < spendingTx.vout.size(); i++)
1357 auto &output = spendingTx.vout[i];
1358 if (output.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_RESERVATION && p.vData.size() > 1)
1360 if (reservation.IsValid())
1362 return eval->Error("Invalid identity reservation output spend");
1366 reservation = CNameReservation(p.vData[0]);
1367 if (!reservation.IsValid() || reservation.GetCommitment().hash != ch.hash)
1369 return eval->Error("Identity reservation output spend does not match commitment");
1376 if (outputNum != -1)
1378 // can only be spent by a matching name reservation if validated
1379 // if there is no matching name reservation, it can be spent just by a valid signature
1380 CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
1381 return ValidateSpendingIdentityReservation(spendingTx, outputNum, eval->state, height, thisChain);
1386 printf("%s: error getting transaction %s to spend\n", __func__, spendingTx.vin[nIn].prevout.hash.GetHex().c_str());
1392 bool ValidateIdentityReservation(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1394 // identity reservations are unspendable
1395 return eval->Error("Identity reservations are unspendable");
1398 // quantum key outputs can be spent without restriction
1399 bool ValidateQuantumKeyOut(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
1404 bool IsQuantumKeyOutInput(const CScript &scriptSig)
1409 bool PrecheckQuantumKeyOut(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
1415 bool IsIdentityInput(const CScript &scriptSig)
1420 bool ValidateFinalizeExport(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
1425 bool IsFinalizeExportInput(const CScript &scriptSig)
1430 bool FinalizeExportContextualPreCheck(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)