Verus Coin - this coin was backdoored by it's lead dev and should not be trusted!
https://git.hush.is/duke/backdoors/src/branch/master/vrsc.md
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1434 lines
51 KiB
1434 lines
51 KiB
/********************************************************************
|
|
* (C) 2019 Michael Toutonghi
|
|
*
|
|
* Distributed under the MIT software license, see the accompanying
|
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
*
|
|
* This provides support for PBaaS identity definition,
|
|
*
|
|
* This is a decentralized identity class that provides the minimum
|
|
* basic function needed to enable persistent DID-similar identities,
|
|
* needed for signatories, that will eventually bridge compatibly to
|
|
* DID identities.
|
|
*
|
|
*
|
|
*/
|
|
#include "main.h"
|
|
#include "pbaas/pbaas.h"
|
|
#include "identity.h"
|
|
|
|
extern CTxMemPool mempool;
|
|
|
|
CCommitmentHash::CCommitmentHash(const CTransaction &tx)
|
|
{
|
|
for (auto txOut : tx.vout)
|
|
{
|
|
COptCCParams p;
|
|
if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_COMMITMENT && p.vData.size())
|
|
{
|
|
::FromVector(p.vData[0], *this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UniValue CNameReservation::ToUniValue() const
|
|
{
|
|
UniValue ret(UniValue::VOBJ);
|
|
ret.push_back(Pair("name", name));
|
|
ret.push_back(Pair("salt", salt.GetHex()));
|
|
ret.push_back(Pair("referral", referral.IsNull() ? "" : EncodeDestination(referral)));
|
|
|
|
if (IsVerusActive())
|
|
{
|
|
if (boost::to_lower_copy(name) == VERUS_CHAINNAME)
|
|
{
|
|
ret.push_back(Pair("parent", ""));
|
|
}
|
|
else
|
|
{
|
|
ret.push_back(Pair("parent", EncodeDestination(CIdentityID(ConnectedChains.ThisChain().GetID()))));
|
|
}
|
|
ret.push_back(Pair("nameid", EncodeDestination(DecodeDestination(name + "@"))));
|
|
}
|
|
else
|
|
{
|
|
ret.push_back(Pair("parent", EncodeDestination(CIdentityID(ConnectedChains.ThisChain().GetID()))));
|
|
ret.push_back(Pair("nameid", EncodeDestination(DecodeDestination(name + "." + ConnectedChains.ThisChain().name + "@"))));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
CIdentity::CIdentity(const CTransaction &tx, int *voutNum)
|
|
{
|
|
std::set<uint160> ids;
|
|
int idIndex;
|
|
bool found = false;
|
|
|
|
nVersion = PBAAS_VERSION_INVALID;
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
CIdentity foundIdentity(tx.vout[i].scriptPubKey);
|
|
if (foundIdentity.IsValid() && !found)
|
|
{
|
|
*this = foundIdentity;
|
|
found = true;
|
|
idIndex = i;
|
|
}
|
|
else if (foundIdentity.IsValid())
|
|
{
|
|
*this = CIdentity();
|
|
}
|
|
}
|
|
if (voutNum && IsValid())
|
|
{
|
|
*voutNum = idIndex;
|
|
}
|
|
}
|
|
|
|
CIdentity CIdentity::LookupIdentity(const CIdentityID &nameID, uint32_t height, uint32_t *pHeightOut, CTxIn *pIdTxIn)
|
|
{
|
|
LOCK(mempool.cs);
|
|
|
|
CIdentity ret;
|
|
|
|
uint32_t heightOut = 0;
|
|
|
|
if (!pHeightOut)
|
|
{
|
|
pHeightOut = &heightOut;
|
|
}
|
|
else
|
|
{
|
|
*pHeightOut = 0;
|
|
}
|
|
|
|
CTxIn _idTxIn;
|
|
if (!pIdTxIn)
|
|
{
|
|
pIdTxIn = &_idTxIn;
|
|
}
|
|
CTxIn &idTxIn = *pIdTxIn;
|
|
|
|
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs, unspentNewIDX;
|
|
|
|
uint160 keyID(CCrossChainRPCData::GetConditionID(nameID, EVAL_IDENTITY_PRIMARY));
|
|
|
|
if (GetAddressUnspent(keyID, CScript::P2IDX, unspentNewIDX) && GetAddressUnspent(keyID, CScript::P2PKH, unspentOutputs))
|
|
{
|
|
// combine searches into 1 vector
|
|
unspentOutputs.insert(unspentOutputs.begin(), unspentNewIDX.begin(), unspentNewIDX.end());
|
|
CCoinsViewCache view(pcoinsTip);
|
|
|
|
for (auto it = unspentOutputs.begin(); !ret.IsValid() && it != unspentOutputs.end(); it++)
|
|
{
|
|
CCoins coins;
|
|
|
|
if (view.GetCoins(it->first.txhash, coins))
|
|
{
|
|
if (coins.IsAvailable(it->first.index))
|
|
{
|
|
// check the mempool for spent/modified
|
|
CSpentIndexKey key(it->first.txhash, it->first.index);
|
|
CSpentIndexValue value;
|
|
|
|
COptCCParams p;
|
|
if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
(ret = CIdentity(coins.vout[it->first.index].scriptPubKey)).IsValid())
|
|
{
|
|
if (ret.GetID() == nameID)
|
|
{
|
|
idTxIn = CTxIn(it->first.txhash, it->first.index);
|
|
*pHeightOut = it->second.blockHeight;
|
|
}
|
|
else
|
|
{
|
|
// got an identity masquerading as another, clear it
|
|
ret = CIdentity();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (height != 0 && (*pHeightOut > height || (height == 1 && *pHeightOut == height)))
|
|
{
|
|
*pHeightOut = 0;
|
|
|
|
// if we must check up to a specific height that is less than the latest height, do so
|
|
std::vector<CAddressIndexDbEntry> addressIndex, addressIndex2;
|
|
|
|
if (GetAddressIndex(keyID, CScript::P2PKH, addressIndex, 0, height) &&
|
|
GetAddressIndex(keyID, CScript::P2IDX, addressIndex2, 0, height) &&
|
|
(addressIndex.size() || addressIndex2.size()))
|
|
{
|
|
if (addressIndex2.size())
|
|
{
|
|
addressIndex.insert(addressIndex.begin(), addressIndex2.begin(), addressIndex2.end());
|
|
}
|
|
int txIndex = 0;
|
|
// look from last backward to find the first valid ID
|
|
for (int i = addressIndex.size() - 1; i >= 0; i--)
|
|
{
|
|
if (addressIndex[i].first.blockHeight < *pHeightOut)
|
|
{
|
|
break;
|
|
}
|
|
CTransaction idTx;
|
|
uint256 blkHash;
|
|
COptCCParams p;
|
|
LOCK(mempool.cs);
|
|
if (!addressIndex[i].first.spending &&
|
|
addressIndex[i].first.txindex > txIndex && // always select the latest in a block, if there can be more than one
|
|
myGetTransaction(addressIndex[i].first.txhash, idTx, blkHash) &&
|
|
idTx.vout[addressIndex[i].first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
(ret = CIdentity(idTx.vout[addressIndex[i].first.index].scriptPubKey)).IsValid())
|
|
{
|
|
idTxIn = CTxIn(addressIndex[i].first.txhash, addressIndex[i].first.index);
|
|
*pHeightOut = addressIndex[i].first.blockHeight;
|
|
txIndex = addressIndex[i].first.txindex;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not found at that height
|
|
ret = CIdentity();
|
|
idTxIn = CTxIn();
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
CIdentity CIdentity::LookupIdentity(const std::string &name, uint32_t height, uint32_t *pHeightOut, CTxIn *idTxIn)
|
|
{
|
|
return LookupIdentity(GetID(name), height, pHeightOut, idTxIn);
|
|
}
|
|
|
|
CIdentity CIdentity::LookupFirstIdentity(const CIdentityID &idID, uint32_t *pHeightOut, CTxIn *pIdTxIn, CTransaction *pidTx)
|
|
{
|
|
CIdentity ret;
|
|
|
|
uint32_t heightOut = 0;
|
|
|
|
if (!pHeightOut)
|
|
{
|
|
pHeightOut = &heightOut;
|
|
}
|
|
else
|
|
{
|
|
*pHeightOut = 0;
|
|
}
|
|
|
|
CTxIn _idTxIn;
|
|
if (!pIdTxIn)
|
|
{
|
|
pIdTxIn = &_idTxIn;
|
|
}
|
|
CTxIn &idTxIn = *pIdTxIn;
|
|
|
|
std::vector<CAddressUnspentDbEntry> unspentOutputs, unspentNewIDX;
|
|
|
|
CKeyID keyID(CCrossChainRPCData::GetConditionID(idID, EVAL_IDENTITY_RESERVATION));
|
|
|
|
if (GetAddressUnspent(keyID, CScript::P2IDX, unspentNewIDX) && GetAddressUnspent(keyID, CScript::P2PKH, unspentOutputs))
|
|
{
|
|
if (!unspentOutputs.size() && !unspentNewIDX.size())
|
|
{
|
|
LOCK(mempool.cs);
|
|
// if we are a PBaaS chain and it is in block 1, get it from there
|
|
std::vector<CAddressIndexDbEntry> checkImported;
|
|
uint256 blockHash;
|
|
CTransaction blockOneCB;
|
|
COptCCParams p;
|
|
CIdentity firstIdentity;
|
|
uint160 identityIdx(CCrossChainRPCData::GetConditionID(idID, EVAL_IDENTITY_PRIMARY));
|
|
if (!IsVerusActive() &&
|
|
GetAddressIndex(identityIdx, CScript::P2IDX, checkImported, 1, 1) &&
|
|
checkImported.size() &&
|
|
myGetTransaction(checkImported[0].first.txhash, blockOneCB, blockHash) &&
|
|
blockOneCB.vout.size() > checkImported[0].first.index &&
|
|
blockOneCB.vout[checkImported[0].first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size() &&
|
|
(firstIdentity = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
if (pHeightOut)
|
|
{
|
|
*pHeightOut = 1;
|
|
}
|
|
if (pIdTxIn)
|
|
{
|
|
*pIdTxIn = CTxIn(checkImported[0].first.txhash, checkImported[0].first.index);
|
|
}
|
|
if (pidTx)
|
|
{
|
|
*pidTx = blockOneCB;
|
|
}
|
|
return firstIdentity;
|
|
}
|
|
}
|
|
|
|
// combine searches into 1 vector
|
|
unspentOutputs.insert(unspentOutputs.begin(), unspentNewIDX.begin(), unspentNewIDX.end());
|
|
CCoinsViewCache view(pcoinsTip);
|
|
|
|
for (auto it = unspentOutputs.begin(); !ret.IsValid() && it != unspentOutputs.end(); it++)
|
|
{
|
|
CCoins coins;
|
|
|
|
if (view.GetCoins(it->first.txhash, coins))
|
|
{
|
|
if (coins.IsAvailable(it->first.index))
|
|
{
|
|
COptCCParams p;
|
|
if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_RESERVATION)
|
|
{
|
|
CTransaction idTx;
|
|
uint256 blkHash;
|
|
if (myGetTransaction(it->first.txhash, idTx, blkHash) && (ret = CIdentity(idTx)).IsValid() && ret.GetID() == idID)
|
|
{
|
|
int i;
|
|
for (i = 0; i < idTx.vout.size(); i++)
|
|
{
|
|
COptCCParams p;
|
|
if (idTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3 && p.evalCode == EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < idTx.vout.size())
|
|
{
|
|
if (pidTx)
|
|
{
|
|
*pidTx = idTx;
|
|
}
|
|
idTxIn = CTxIn(it->first.txhash, i);
|
|
*pHeightOut = it->second.blockHeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// this enables earliest rejection of invalid CC transactions
|
|
bool ValidateSpendingIdentityReservation(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height, CCurrencyDefinition issuingChain)
|
|
{
|
|
// CHECK #1 - there is only one reservation output, and there is also one identity output that matches the reservation.
|
|
// the identity output must come first and have from 0 to 3 referral outputs between it and the reservation.
|
|
int numReferrers = 0;
|
|
int identityCount = 0;
|
|
int reservationCount = 0;
|
|
CIdentity newIdentity;
|
|
CNameReservation newName;
|
|
std::vector<CTxDestination> referrers;
|
|
bool valid = true;
|
|
|
|
bool isPBaaS = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
|
|
|
|
for (auto &txout : tx.vout)
|
|
{
|
|
COptCCParams p;
|
|
if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
|
|
{
|
|
if (p.evalCode == EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
if (identityCount++ || p.vData.size() < 2)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
newIdentity = CIdentity(p.vData[0]);
|
|
}
|
|
else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
|
|
{
|
|
if (reservationCount++ || p.vData.size() < 2)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
newName = CNameReservation(p.vData[0]);
|
|
}
|
|
else if (identityCount && !reservationCount)
|
|
{
|
|
if (!issuingChain.IDReferralLevels() ||
|
|
p.vKeys.size() < 1 ||
|
|
referrers.size() > (issuingChain.IDReferralLevels() - 1) ||
|
|
p.evalCode != 0 ||
|
|
p.n > 1 ||
|
|
p.m != 1 ||
|
|
txout.nValue < issuingChain.IDReferralAmount())
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
referrers.push_back(p.vKeys[0]);
|
|
}
|
|
else if (identityCount != reservationCount)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we can close a commitment UTXO without an identity
|
|
if (valid && !identityCount)
|
|
{
|
|
return state.Error("Transaction may not have an identity reservation without a matching identity");
|
|
}
|
|
else if (!valid)
|
|
{
|
|
return state.Error("Improperly formed identity definition transaction");
|
|
}
|
|
|
|
std::vector<CTxDestination> dests;
|
|
int minSigs;
|
|
txnouttype outType;
|
|
if (ExtractDestinations(tx.vout[outNum].scriptPubKey, outType, dests, minSigs))
|
|
{
|
|
uint160 thisID = newIdentity.GetID();
|
|
for (auto &dest : dests)
|
|
{
|
|
uint160 oneDestID;
|
|
if (dest.which() == COptCCParams::ADDRTYPE_ID && (oneDestID = GetDestinationID(dest)) != thisID && !CIdentity::LookupIdentity(CIdentityID(oneDestID)).IsValid())
|
|
{
|
|
return state.Error("Destination includes invalid identity");
|
|
}
|
|
}
|
|
}
|
|
|
|
// CHECK #2 - must be rooted in this chain
|
|
if (newIdentity.parent != ConnectedChains.ThisChain().GetID() &&
|
|
!(isPBaaS && newIdentity.GetID() == ASSETCHAINS_CHAINID && IsVerusActive()))
|
|
{
|
|
return state.Error("Identity parent of new identity must be current chain");
|
|
}
|
|
|
|
// CHECK #3 - if dupID is valid, we need to be spending it to recover. redefinition is invalid
|
|
CTxIn idTxIn;
|
|
uint32_t priorHeightOut;
|
|
CIdentity dupID = newIdentity.LookupIdentity(newIdentity.GetID(), height - 1, &priorHeightOut, &idTxIn);
|
|
|
|
// CHECK #3a - if dupID is invalid, ensure we spend a matching name commitment
|
|
if (dupID.IsValid())
|
|
{
|
|
return state.Error("Identity already exists");
|
|
}
|
|
|
|
int commitmentHeight = 0;
|
|
const CCoins *coins;
|
|
CCoinsView dummy;
|
|
CCoinsViewCache view(&dummy);
|
|
|
|
LOCK(mempool.cs);
|
|
|
|
CCoinsViewMemPool viewMemPool(pcoinsTip, mempool);
|
|
view.SetBackend(viewMemPool);
|
|
|
|
CCommitmentHash ch;
|
|
int idx = -1;
|
|
|
|
CAmount nValueIn = 0;
|
|
{
|
|
// from here, we must spend a matching name commitment
|
|
std::map<uint256, const CCoins *> txMap;
|
|
for (auto &oneTxIn : tx.vin)
|
|
{
|
|
coins = txMap[oneTxIn.prevout.hash];
|
|
if (!coins && !(coins = view.AccessCoins(oneTxIn.prevout.hash)))
|
|
{
|
|
//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());
|
|
//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());
|
|
return state.Error("Cannot access input");
|
|
}
|
|
txMap[oneTxIn.prevout.hash] = coins;
|
|
|
|
if (oneTxIn.prevout.n >= coins->vout.size())
|
|
{
|
|
//extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
|
|
//UniValue uniTx;
|
|
//TxToJSON(tx, uint256(), uniTx);
|
|
//printf("%s\n", uniTx.write(1, 2).c_str());
|
|
return state.Error("Input index out of range");
|
|
}
|
|
|
|
COptCCParams p;
|
|
if (idx == -1 &&
|
|
coins->vout[oneTxIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_COMMITMENT &&
|
|
p.vData.size())
|
|
{
|
|
idx = oneTxIn.prevout.n;
|
|
::FromVector(p.vData[0], ch);
|
|
commitmentHeight = coins->nHeight;
|
|
// this needs to already be in a prior block, or we can't consider it valid
|
|
if (!commitmentHeight || commitmentHeight == -1)
|
|
{
|
|
return state.Error("ID commitment was not already in blockchain");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (idx == -1 || ch.hash.IsNull())
|
|
{
|
|
std::string specificMsg = "Invalid identity commitment in tx: " + tx.GetHash().GetHex();
|
|
return state.Error(specificMsg);
|
|
}
|
|
|
|
// are we spending a matching name commitment?
|
|
if (ch.hash != newName.GetCommitment().hash)
|
|
{
|
|
return state.Error("Mismatched identity commitment");
|
|
}
|
|
|
|
if (!newName.referral.IsNull() && issuingChain.IDReferralLevels() && !(CIdentity::LookupIdentity(newName.referral, commitmentHeight).IsValid()))
|
|
{
|
|
// invalid referral identity
|
|
return state.Error("Invalid referral identity specified");
|
|
}
|
|
|
|
CReserveTransactionDescriptor rtxd(tx, view, height);
|
|
|
|
// 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,
|
|
// all further checks only if referrals are enabled and there is a referrer
|
|
if (!issuingChain.IDReferralLevels() || newName.referral.IsNull())
|
|
{
|
|
// make sure the fees of this transaction are full price for an identity, all further checks only if there is a referrer
|
|
if (rtxd.NativeFees() < issuingChain.IDFullRegistrationAmount())
|
|
{
|
|
return state.Error("Invalid identity registration - insufficient fee");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// CHECK #5 - ensure that the first referring output goes to the referring identity followed by up
|
|
// to two identities that come from the original definition transaction of the referring identity. account for all outputs between
|
|
// identity out and reservation out and ensure that they are correct and pay 20% of the price of an identity
|
|
uint32_t heightOut = 0;
|
|
CTransaction referralTx;
|
|
|
|
CIdentity firstReferralIdentity = CIdentity::LookupFirstIdentity(newName.referral, &heightOut, &idTxIn, &referralTx);
|
|
|
|
// referrer must be mined in when this transaction is put into the mem pool
|
|
if (heightOut >= height || !firstReferralIdentity.IsValid() || firstReferralIdentity.parent != ASSETCHAINS_CHAINID)
|
|
{
|
|
//printf("%s: cannot find first instance of: %s\n", __func__, EncodeDestination(CIdentityID(newName.referral)).c_str());
|
|
return state.Error("Invalid identity registration referral");
|
|
}
|
|
|
|
bool isReferral = false;
|
|
std::vector<CTxDestination> checkReferrers = std::vector<CTxDestination>({newName.referral});
|
|
if (heightOut != 1)
|
|
{
|
|
for (auto &txout : referralTx.vout)
|
|
{
|
|
COptCCParams p;
|
|
if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
|
|
{
|
|
if (p.evalCode == EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
isReferral = true;
|
|
}
|
|
else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
|
|
{
|
|
break;
|
|
}
|
|
else if (isReferral)
|
|
{
|
|
if (p.vKeys.size() == 0 || p.vKeys[0].which() != COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
// invalid referral
|
|
return state.Error("Invalid identity registration referral outputs");
|
|
}
|
|
else
|
|
{
|
|
checkReferrers.push_back(p.vKeys[0]);
|
|
if (checkReferrers.size() == issuingChain.IDReferralLevels())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (referrers.size() != checkReferrers.size())
|
|
{
|
|
return state.Error("Invalid identity registration - incorrect referral payments");
|
|
}
|
|
|
|
// make sure all paid referrers are correct
|
|
for (int i = 0; i < referrers.size(); i++)
|
|
{
|
|
if (referrers[i] != checkReferrers[i])
|
|
{
|
|
return state.Error("Invalid identity registration - incorrect referral payments");
|
|
}
|
|
}
|
|
|
|
// CHECK #6 - ensure that the transaction pays the correct mining and refferal fees
|
|
if (rtxd.NativeFees() < (issuingChain.IDReferredRegistrationAmount() - (referrers.size() * issuingChain.IDReferralAmount())))
|
|
{
|
|
return state.Error("Invalid identity registration - insufficient fee");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PrecheckIdentityReservation(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
|
|
|
|
int numReferrers = 0;
|
|
int identityCount = 0;
|
|
int reservationCount = 0;
|
|
CIdentity newIdentity;
|
|
CNameReservation newName;
|
|
std::vector<CTxDestination> referrers;
|
|
bool valid = true;
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
for (auto &txout : tx.vout)
|
|
{
|
|
COptCCParams p;
|
|
if (txout.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.version >= p.VERSION_V3)
|
|
{
|
|
if (p.evalCode == EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
if (identityCount++ || p.vData.size() < 2)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
newIdentity = CIdentity(p.vData[0]);
|
|
}
|
|
else if (p.evalCode == EVAL_IDENTITY_RESERVATION)
|
|
{
|
|
if (reservationCount++ || p.vData.size() < 2)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
newName = CNameReservation(p.vData[0]);
|
|
}
|
|
else if (identityCount && !reservationCount)
|
|
{
|
|
if (!thisChain.IDReferralLevels() ||
|
|
p.vKeys.size() < 1 ||
|
|
referrers.size() > (thisChain.IDReferralLevels() - 1) ||
|
|
p.evalCode != 0 ||
|
|
p.n > 1 ||
|
|
p.m != 1 ||
|
|
txout.nValue < thisChain.IDReferralAmount())
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
referrers.push_back(p.vKeys[0]);
|
|
}
|
|
else if (identityCount != reservationCount)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we can close a commitment UTXO without an identity
|
|
if (valid && !identityCount)
|
|
{
|
|
return state.Error("Transaction may not have an identity reservation without a matching identity");
|
|
}
|
|
else if (!valid)
|
|
{
|
|
return state.Error("Improperly formed identity definition transaction");
|
|
}
|
|
|
|
int commitmentHeight = 0;
|
|
|
|
LOCK2(cs_main, mempool.cs);
|
|
|
|
CCommitmentHash ch;
|
|
int idx = -1;
|
|
|
|
CAmount nValueIn = 0;
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
|
|
// from here, we must spend a matching name commitment
|
|
std::map<uint256, CTransaction> txMap;
|
|
uint256 hashBlk;
|
|
for (auto &oneTxIn : tx.vin)
|
|
{
|
|
CTransaction sourceTx = txMap[oneTxIn.prevout.hash];
|
|
if (sourceTx.nVersion <= sourceTx.SPROUT_MIN_CURRENT_VERSION && !myGetTransaction(oneTxIn.prevout.hash, sourceTx, hashBlk))
|
|
{
|
|
//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());
|
|
//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());
|
|
return state.Error("Cannot access input");
|
|
}
|
|
txMap[oneTxIn.prevout.hash] = sourceTx;
|
|
|
|
if (oneTxIn.prevout.n >= sourceTx.vout.size())
|
|
{
|
|
//extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
|
|
//UniValue uniTx;
|
|
//TxToJSON(tx, uint256(), uniTx);
|
|
//printf("%s\n", uniTx.write(1, 2).c_str());
|
|
return state.Error("Input index out of range");
|
|
}
|
|
|
|
COptCCParams p;
|
|
if (idx == -1 &&
|
|
sourceTx.vout[oneTxIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_COMMITMENT &&
|
|
p.vData.size())
|
|
{
|
|
idx = oneTxIn.prevout.n;
|
|
::FromVector(p.vData[0], ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (idx == -1 || ch.hash.IsNull())
|
|
{
|
|
std::string specificMsg = "Invalid identity commitment in tx: " + tx.GetHash().GetHex();
|
|
return state.Error(specificMsg);
|
|
}
|
|
|
|
// are we spending a matching name commitment?
|
|
if (ch.hash != newName.GetCommitment().hash)
|
|
{
|
|
return state.Error("Mismatched identity commitment");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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)
|
|
// this transaction spends a prior identity transaction that does not create a clearly invalid mutation between the two
|
|
bool PrecheckIdentityPrimary(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
|
|
bool validReservation = false;
|
|
bool validIdentity = false;
|
|
|
|
CNameReservation nameRes;
|
|
CIdentity identity;
|
|
COptCCParams p, identityP;
|
|
|
|
uint32_t networkVersion = CConstVerusSolutionVector::GetVersionByHeight(height);
|
|
bool isPBaaS = networkVersion >= CActivationHeight::ACTIVATE_PBAAS;
|
|
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
CIdentity checkIdentity;
|
|
auto &output = tx.vout[i];
|
|
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())
|
|
{
|
|
// twice through makes it invalid
|
|
if (validReservation)
|
|
{
|
|
return state.Error("Invalid multiple identity reservations on one transaction");
|
|
}
|
|
validReservation = true;
|
|
}
|
|
else if (p.IsValid() && p.version >= COptCCParams::VERSION_V3 && p.evalCode == EVAL_IDENTITY_PRIMARY && p.vData.size() > 1 && (checkIdentity = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
// twice through makes it invalid
|
|
if (!(isPBaaS && height == 1) && validIdentity)
|
|
{
|
|
return state.Error("Invalid multiple identity definitions on one transaction");
|
|
}
|
|
if (i == outNum)
|
|
{
|
|
identityP = p;
|
|
identity = checkIdentity;
|
|
}
|
|
validIdentity = true;
|
|
}
|
|
}
|
|
|
|
if (!validIdentity)
|
|
{
|
|
return state.Error("Invalid identity definition");
|
|
}
|
|
|
|
CIdentityID idID = identity.GetID();
|
|
p = identityP;
|
|
|
|
bool advancedIdentity = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
|
|
if (advancedIdentity)
|
|
{
|
|
COptCCParams master;
|
|
if (identity.nVersion < identity.VERSION_PBAAS)
|
|
{
|
|
return state.Error("Inadequate identity version for post-PBaaS activation");
|
|
}
|
|
if (p.vData.size() < 3 ||
|
|
!(master = COptCCParams(p.vData.back())).IsValid() ||
|
|
master.evalCode != 0 ||
|
|
master.m != 1)
|
|
{
|
|
return state.Error("Invalid identity output destinations and/or configuration");
|
|
}
|
|
|
|
// we need to have at least 2 of 3 authority spend conditions mandatory at a top level for any primary ID output
|
|
bool revocation = false, recovery = false;
|
|
bool primaryValid = false, revocationValid = false, recoveryValid = false;
|
|
for (auto dest : p.vKeys)
|
|
{
|
|
if (dest.which() == COptCCParams::ADDRTYPE_ID && (idID == GetDestinationID(dest)) && dest.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
primaryValid = true;
|
|
}
|
|
}
|
|
if (!primaryValid)
|
|
{
|
|
std::string errorOut = "Primary identity output condition of \"" + identity.name + "\" is not spendable by self";
|
|
return state.Error(errorOut.c_str());
|
|
}
|
|
for (int i = 1; i < p.vData.size() - 1; i++)
|
|
{
|
|
COptCCParams oneP(p.vData[i]);
|
|
// must be valid and composable
|
|
if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
|
|
{
|
|
std::string errorOut = "Invalid output condition from identity: \"" + identity.name + "\"";
|
|
return state.Error(errorOut.c_str());
|
|
}
|
|
|
|
if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
|
|
{
|
|
// no dups
|
|
if (!revocation)
|
|
{
|
|
revocation = true;
|
|
if (oneP.vKeys.size() == 1 &&
|
|
oneP.m == 1 &&
|
|
oneP.n == 1 &&
|
|
oneP.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
|
|
identity.revocationAuthority == GetDestinationID(oneP.vKeys[0]))
|
|
{
|
|
revocationValid = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
revocationValid = false;
|
|
}
|
|
}
|
|
else if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
|
|
{
|
|
if (!recovery)
|
|
{
|
|
recovery = true;
|
|
if (oneP.vKeys.size() == 1 &&
|
|
oneP.m == 1 &&
|
|
oneP.n == 1 &&
|
|
oneP.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
|
|
identity.recoveryAuthority == GetDestinationID(oneP.vKeys[0]))
|
|
{
|
|
recoveryValid = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
recoveryValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we need separate spend conditions for both revoke and recover in all cases
|
|
bool isRevoked = identity.IsRevoked();
|
|
if ((!isRevoked && !revocationValid) || (isRevoked && !recoveryValid))
|
|
{
|
|
std::string errorOut = "Primary identity output \"" + identity.name + "\" must be spendable by revocation and recovery authorities";
|
|
return state.Error(errorOut.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (identity.nVersion >= identity.VERSION_PBAAS)
|
|
{
|
|
return state.Error("Invalid identity version before PBaaS activation");
|
|
}
|
|
|
|
// ensure that we have all required spend conditions for primary, revocation, and recovery
|
|
// if there are additional spend conditions, their addition or removal is checked for validity
|
|
// depending on which of the mandatory spend conditions is authorized.
|
|
COptCCParams master;
|
|
if (p.vData.size() < 4 || !(master = COptCCParams(p.vData.back())).IsValid() || master.evalCode != 0 || master.m != 1)
|
|
{
|
|
// we need to have 3 authority spend conditions mandatory at a top level for any primary ID output
|
|
bool primary = false, revocation = false, recovery = false;
|
|
|
|
for (auto dest : p.vKeys)
|
|
{
|
|
if (dest.which() == COptCCParams::ADDRTYPE_ID && (idID == GetDestinationID(dest)))
|
|
{
|
|
primary = true;
|
|
}
|
|
}
|
|
if (!primary)
|
|
{
|
|
std::string errorOut = "Primary identity output condition of \"" + identity.name + "\" is not spendable by self";
|
|
return state.Error(errorOut.c_str());
|
|
}
|
|
for (int i = 1; i < p.vData.size() - 1; i++)
|
|
{
|
|
COptCCParams oneP(p.vData[i]);
|
|
// must be valid and composable
|
|
if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
|
|
{
|
|
std::string errorOut = "Invalid output condition from identity: \"" + identity.name + "\"";
|
|
return state.Error(errorOut.c_str());
|
|
}
|
|
|
|
if (oneP.evalCode == EVAL_IDENTITY_REVOKE || oneP.evalCode == EVAL_IDENTITY_RECOVER)
|
|
{
|
|
for (auto dest : oneP.vKeys)
|
|
{
|
|
if (dest.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
if (oneP.evalCode == EVAL_IDENTITY_REVOKE && (identity.revocationAuthority == GetDestinationID(dest)))
|
|
{
|
|
revocation = true;
|
|
}
|
|
else if (oneP.evalCode == EVAL_IDENTITY_RECOVER && (identity.recoveryAuthority == GetDestinationID(dest)))
|
|
{
|
|
recovery = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// we need separate spend conditions for both revoke and recover in all cases
|
|
if (!revocation || !recovery)
|
|
{
|
|
std::string errorOut = "Primary identity output \"" + identity.name + "\" must be spendable by revocation and recovery authorities";
|
|
return state.Error(errorOut.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
extern uint160 VERUS_CHAINID;
|
|
extern std::string VERUS_CHAINNAME;
|
|
|
|
// compare commitment without regard to case or other textual transformations that are irrelevant to matching
|
|
uint160 parentChain = ConnectedChains.ThisChain().GetID();
|
|
if (isPBaaS && identity.GetID() == ASSETCHAINS_CHAINID && IsVerusActive())
|
|
{
|
|
parentChain.SetNull();
|
|
}
|
|
if (validReservation && identity.GetID(nameRes.name, parentChain) == identity.GetID())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// 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
|
|
CTransaction inTx;
|
|
uint256 blkHash;
|
|
LOCK(mempool.cs);
|
|
for (auto &input : tx.vin)
|
|
{
|
|
// first time through may be null
|
|
if ((!input.prevout.hash.IsNull() && input.prevout.hash == inTx.GetHash()) || myGetTransaction(input.prevout.hash, inTx, blkHash))
|
|
{
|
|
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())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: HARDENING at block one, a new PBaaS chain can mint IDs
|
|
// ensure they are valid as per the launch parameters
|
|
if (isPBaaS && height == 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return state.Error("Invalid primary identity - does not include identity reservation or spend matching identity");
|
|
}
|
|
|
|
CIdentity GetOldIdentity(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx=nullptr, uint32_t *pHeight=nullptr);
|
|
CIdentity GetOldIdentity(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx, uint32_t *pHeight)
|
|
{
|
|
CTransaction _sourceTx;
|
|
CTransaction &sourceTx(pSourceTx ? *pSourceTx : _sourceTx);
|
|
|
|
// if not fulfilled, ensure that no part of the primary identity is modified
|
|
CIdentity oldIdentity;
|
|
uint256 blkHash;
|
|
if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
|
|
{
|
|
if (pHeight)
|
|
{
|
|
auto bIt = mapBlockIndex.find(blkHash);
|
|
if (bIt == mapBlockIndex.end() || !bIt->second)
|
|
{
|
|
*pHeight = chainActive.Height();
|
|
}
|
|
else
|
|
{
|
|
*pHeight = bIt->second->GetHeight();
|
|
}
|
|
}
|
|
COptCCParams p;
|
|
if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.version >= COptCCParams::VERSION_V3 &&
|
|
p.vData.size() > 1)
|
|
{
|
|
oldIdentity = CIdentity(p.vData[0]);
|
|
}
|
|
}
|
|
return oldIdentity;
|
|
}
|
|
|
|
bool ValidateIdentityPrimary(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
CTransaction sourceTx;
|
|
CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
|
|
|
|
if (!oldIdentity.IsValid())
|
|
{
|
|
return eval->Error("Spending invalid identity");
|
|
}
|
|
|
|
int idIndex;
|
|
CIdentity newIdentity(spendingTx, &idIndex);
|
|
if (!newIdentity.IsValid())
|
|
{
|
|
return eval->Error("Attempting to define invalid identity");
|
|
}
|
|
|
|
if (!chainActive.LastTip())
|
|
{
|
|
return eval->Error("unable to find chain tip");
|
|
}
|
|
uint32_t height = chainActive.LastTip()->GetHeight() + 1;
|
|
|
|
if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
|
|
{
|
|
LogPrintf("Invalid identity modification %s\n", spendingTx.GetHash().GetHex().c_str());
|
|
return eval->Error("Invalid identity modification");
|
|
}
|
|
|
|
// if not fullfilled and not revoked, we are responsible for rejecting any modification of
|
|
// data under primary authority control
|
|
if (!fulfilled && !oldIdentity.IsRevoked())
|
|
{
|
|
if (oldIdentity.IsPrimaryMutation(newIdentity, height))
|
|
{
|
|
return eval->Error("Unauthorized identity modification");
|
|
}
|
|
// make sure that the primary spend conditions are not modified
|
|
COptCCParams p, q;
|
|
sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
|
|
spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
|
|
|
|
if (q.evalCode != EVAL_IDENTITY_PRIMARY ||
|
|
p.version > q.version ||
|
|
p.m != q.m ||
|
|
p.n != q.n ||
|
|
p.vKeys != q.vKeys)
|
|
{
|
|
return eval->Error("Unauthorized modification of identity primary spend condition");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ValidateIdentityRevoke(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
CTransaction sourceTx;
|
|
CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
|
|
if (!oldIdentity.IsValid())
|
|
{
|
|
return eval->Error("Invalid source identity");
|
|
}
|
|
|
|
int idIndex;
|
|
CIdentity newIdentity(spendingTx, &idIndex);
|
|
if (!newIdentity.IsValid())
|
|
{
|
|
return eval->Error("Attempting to replace identity with one that is invalid");
|
|
}
|
|
|
|
if (!chainActive.LastTip())
|
|
{
|
|
return eval->Error("unable to find chain tip");
|
|
}
|
|
uint32_t height = chainActive.LastTip()->GetHeight() + 1;
|
|
|
|
if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
|
|
{
|
|
return eval->Error("Invalid identity modification");
|
|
}
|
|
|
|
if (oldIdentity.IsRevocation(newIdentity) && oldIdentity.recoveryAuthority == oldIdentity.GetID())
|
|
{
|
|
return eval->Error("Cannot revoke an identity with self as the recovery authority");
|
|
}
|
|
|
|
// make sure that spend conditions are valid and revocation spend conditions are not modified
|
|
COptCCParams p, q;
|
|
|
|
spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
|
|
|
|
if (q.evalCode != EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
return eval->Error("Invalid identity output in spending transaction");
|
|
}
|
|
|
|
bool advanced = newIdentity.nVersion >= newIdentity.VERSION_PBAAS;
|
|
|
|
if (advanced)
|
|
{
|
|
// if not fulfilled, neither recovery data nor its spend condition may be modified
|
|
if (!fulfilled && !oldIdentity.IsRevoked())
|
|
{
|
|
if (oldIdentity.IsRevocation(newIdentity) || oldIdentity.IsRevocationMutation(newIdentity, height))
|
|
{
|
|
return eval->Error("Unauthorized modification of revocation information");
|
|
}
|
|
}
|
|
// aside from that, validity of spend conditions is done in advanced precheck
|
|
return true;
|
|
}
|
|
|
|
COptCCParams oldRevokeP, newRevokeP;
|
|
|
|
for (int i = 1; i < q.vData.size() - 1; i++)
|
|
{
|
|
COptCCParams oneP(q.vData[i]);
|
|
// must be valid and composable
|
|
if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
|
|
{
|
|
std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\"";
|
|
return eval->Error(errorOut.c_str());
|
|
}
|
|
|
|
if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
|
|
{
|
|
if (newRevokeP.IsValid())
|
|
{
|
|
std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\", more than one revocation condition";
|
|
return eval->Error(errorOut.c_str());
|
|
}
|
|
newRevokeP = oneP;
|
|
}
|
|
}
|
|
|
|
if (!newRevokeP.IsValid())
|
|
{
|
|
std::string errorOut = "Invalid revocation output condition for identity: \"" + newIdentity.name + "\"";
|
|
return eval->Error(errorOut.c_str());
|
|
}
|
|
|
|
// if not fulfilled, neither revocation data nor its spend condition may be modified
|
|
if (!fulfilled)
|
|
{
|
|
sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
|
|
|
|
if (oldIdentity.IsRevocation(newIdentity) || oldIdentity.IsRevocationMutation(newIdentity, height))
|
|
{
|
|
return eval->Error("Unauthorized modification of revocation information");
|
|
}
|
|
|
|
for (int i = 1; i < p.vData.size() - 1; i++)
|
|
{
|
|
COptCCParams oneP(p.vData[i]);
|
|
if (oneP.evalCode == EVAL_IDENTITY_REVOKE)
|
|
{
|
|
oldRevokeP = oneP;
|
|
}
|
|
}
|
|
|
|
if (!oldRevokeP.IsValid() ||
|
|
!newRevokeP.IsValid() ||
|
|
oldRevokeP.version > newRevokeP.version ||
|
|
oldRevokeP.m != newRevokeP.m ||
|
|
oldRevokeP.n != newRevokeP.n ||
|
|
oldRevokeP.vKeys != newRevokeP.vKeys)
|
|
{
|
|
return eval->Error("Unauthorized modification of identity revocation spend condition");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ValidateIdentityRecover(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
CTransaction sourceTx;
|
|
CIdentity oldIdentity = GetOldIdentity(spendingTx, nIn, &sourceTx);
|
|
if (!oldIdentity.IsValid())
|
|
{
|
|
return eval->Error("Invalid source identity");
|
|
}
|
|
|
|
int idIndex;
|
|
CIdentity newIdentity(spendingTx, &idIndex);
|
|
if (!newIdentity.IsValid())
|
|
{
|
|
return eval->Error("Attempting to replace identity with one that is invalid");
|
|
}
|
|
|
|
if (!chainActive.LastTip())
|
|
{
|
|
return eval->Error("unable to find chain tip");
|
|
}
|
|
uint32_t height = chainActive.LastTip()->GetHeight() + 1;
|
|
|
|
if (oldIdentity.IsInvalidMutation(newIdentity, height, spendingTx.nExpiryHeight))
|
|
{
|
|
return eval->Error("Invalid identity modification");
|
|
}
|
|
|
|
// make sure that spend conditions are valid and revocation spend conditions are not modified
|
|
COptCCParams p, q;
|
|
|
|
spendingTx.vout[idIndex].scriptPubKey.IsPayToCryptoCondition(q);
|
|
|
|
if (q.evalCode != EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
return eval->Error("Invalid identity output in spending transaction");
|
|
}
|
|
|
|
bool advanced = newIdentity.nVersion >= newIdentity.VERSION_PBAAS;
|
|
|
|
if (advanced)
|
|
{
|
|
// if not fulfilled, neither recovery data nor its spend condition may be modified
|
|
if (!fulfilled)
|
|
{
|
|
if (oldIdentity.IsRecovery(newIdentity) || oldIdentity.IsRecoveryMutation(newIdentity, height))
|
|
{
|
|
return eval->Error("Unauthorized modification of recovery information");
|
|
}
|
|
|
|
// if revoked, only fulfilled recovery condition allows any mutation
|
|
if (oldIdentity.IsRevoked() &&
|
|
(oldIdentity.IsPrimaryMutation(newIdentity, height) ||
|
|
oldIdentity.IsRevocationMutation(newIdentity, height)))
|
|
{
|
|
return eval->Error("Unauthorized modification of revoked identity without recovery authority");
|
|
}
|
|
}
|
|
// aside from that, validity of spend conditions is done in advanced precheck
|
|
return true;
|
|
}
|
|
|
|
COptCCParams oldRecoverP, newRecoverP;
|
|
|
|
for (int i = 1; i < q.vData.size() - 1; i++)
|
|
{
|
|
COptCCParams oneP(q.vData[i]);
|
|
// must be valid and composable
|
|
if (!oneP.IsValid() || oneP.version < oneP.VERSION_V3)
|
|
{
|
|
std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\"";
|
|
return eval->Error(errorOut.c_str());
|
|
}
|
|
|
|
if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
|
|
{
|
|
if (newRecoverP.IsValid())
|
|
{
|
|
std::string errorOut = "Invalid output condition from identity: \"" + newIdentity.name + "\", more than one recovery condition";
|
|
return eval->Error(errorOut.c_str());
|
|
}
|
|
newRecoverP = oneP;
|
|
}
|
|
}
|
|
|
|
if (!newRecoverP.IsValid())
|
|
{
|
|
std::string errorOut = "Invalid recovery output condition for identity: \"" + newIdentity.name + "\"";
|
|
return eval->Error(errorOut.c_str());
|
|
}
|
|
|
|
// if not fulfilled, neither recovery data nor its spend condition may be modified
|
|
if (!fulfilled)
|
|
{
|
|
// if revoked, only fulfilled recovery condition allows primary mutation
|
|
if (oldIdentity.IsRevoked() && (oldIdentity.IsPrimaryMutation(newIdentity, height)))
|
|
{
|
|
return eval->Error("Unauthorized modification of revoked identity without recovery authority");
|
|
}
|
|
|
|
if (oldIdentity.IsRecovery(newIdentity) || oldIdentity.IsRecoveryMutation(newIdentity, height))
|
|
{
|
|
return eval->Error("Unauthorized modification of recovery information");
|
|
}
|
|
|
|
sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p);
|
|
|
|
for (int i = 1; i < p.vData.size() - 1; i++)
|
|
{
|
|
COptCCParams oneP(p.vData[i]);
|
|
if (oneP.evalCode == EVAL_IDENTITY_RECOVER)
|
|
{
|
|
oldRecoverP = oneP;
|
|
}
|
|
}
|
|
|
|
if (!oldRecoverP.IsValid() ||
|
|
!newRecoverP.IsValid() ||
|
|
oldRecoverP.version > newRecoverP.version ||
|
|
oldRecoverP.m != newRecoverP.m ||
|
|
oldRecoverP.n != newRecoverP.n ||
|
|
oldRecoverP.vKeys != newRecoverP.vKeys)
|
|
{
|
|
return eval->Error("Unauthorized modification of identity recovery spend condition");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ValidateIdentityCommitment(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
// if not fulfilled, fail
|
|
if (!fulfilled)
|
|
{
|
|
return eval->Error("missing required signature to spend");
|
|
}
|
|
|
|
if (!chainActive.LastTip())
|
|
{
|
|
return eval->Error("unable to find chain tip");
|
|
}
|
|
uint32_t height = chainActive.LastTip()->GetHeight() + 1;
|
|
|
|
CCommitmentHash ch;
|
|
CNameReservation reservation;
|
|
CTransaction sourceTx;
|
|
uint256 blkHash;
|
|
|
|
LOCK(mempool.cs);
|
|
if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
|
|
{
|
|
COptCCParams p;
|
|
if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_COMMITMENT &&
|
|
p.version >= COptCCParams::VERSION_V3 &&
|
|
p.vData.size() > 1 &&
|
|
!blkHash.IsNull())
|
|
{
|
|
ch = CCommitmentHash(p.vData[0]);
|
|
}
|
|
else
|
|
{
|
|
return eval->Error("Invalid source commitment output");
|
|
}
|
|
|
|
int i;
|
|
int outputNum = -1;
|
|
for (i = 0; i < spendingTx.vout.size(); i++)
|
|
{
|
|
auto &output = spendingTx.vout[i];
|
|
if (output.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_RESERVATION && p.vData.size() > 1)
|
|
{
|
|
if (reservation.IsValid())
|
|
{
|
|
return eval->Error("Invalid identity reservation output spend");
|
|
}
|
|
else
|
|
{
|
|
reservation = CNameReservation(p.vData[0]);
|
|
if (!reservation.IsValid() || reservation.GetCommitment().hash != ch.hash)
|
|
{
|
|
return eval->Error("Identity reservation output spend does not match commitment");
|
|
}
|
|
|
|
outputNum = i;
|
|
}
|
|
}
|
|
}
|
|
if (outputNum != -1)
|
|
{
|
|
// can only be spent by a matching name reservation if validated
|
|
// if there is no matching name reservation, it can be spent just by a valid signature
|
|
CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
|
|
return ValidateSpendingIdentityReservation(spendingTx, outputNum, eval->state, height, thisChain);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("%s: error getting transaction %s to spend\n", __func__, spendingTx.vin[nIn].prevout.hash.GetHex().c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ValidateIdentityReservation(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
// identity reservations are unspendable
|
|
return eval->Error("Identity reservations are unspendable");
|
|
}
|
|
|
|
// quantum key outputs can be spent without restriction
|
|
bool ValidateQuantumKeyOut(struct CCcontract_info *cp, Eval* eval, const CTransaction &spendingTx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool IsQuantumKeyOutInput(const CScript &scriptSig)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool PrecheckQuantumKeyOut(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
// inactive for now
|
|
return false;
|
|
}
|
|
|
|
bool IsIdentityInput(const CScript &scriptSig)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ValidateFinalizeExport(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool IsFinalizeExportInput(const CScript &scriptSig)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FinalizeExportContextualPreCheck(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|